From ca73f362331ffc8500a59144973c66b55f56889e Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Mon, 29 Oct 2018 11:37:06 -0400 Subject: [PATCH] Merge back with master (#24746) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Translate global navigation bar component (#23993) Translate global navigation bar component * [backport] add back earlier 6.x minor versions We still backport to these branches, primarily for doc changes. * [dev/build] fix invalid assertion * Skip this test until snapshots are updated (#24650) * Feat/expression threading (#24598) Replaces https://github.com/elastic/kibana/pull/23301 Closes https://github.com/elastic/kibana/issues/23080 --- This is a minimal threading implementation for Canvas. There's still a lot to be done to make this concept great, but this is a start. What it does: - Creates a server side abstraction on top of the interpreter - Determines where to send the expression by checking the first function to be run - Loads common functions in a separate worker thread on the server. - Routes to a single forked worker (thread), the main thread (server), or the browser (browser), in that order - Defers back to the router when a function isn't found. Fails if the function isn't found in any of the above 3 environments - Times out the worker if it takes too long, and respawns it as needed. - Simplifies the error dialog to remove the stack. What is does not.: - Round robin a pool of workers - Queue. If one expression in the threaded env fails then anything sent to it in the meantime will fail. The upstream environment handles managing timeouts. I think this would only make sense todo with a pool. - Client side. This doesn't implement web workers, but we could use roughly the same architecture. - Implement a specific, pluggable `worker` environment on the server. Right now it's just common functions, so plugin authors will always end up in a thread if they put their function in the common directory. What I don't like: - The socketProvider code. This was reused across the server & browser, but now that it's only used in the browser there's no good reason for the abstraction - The serialize/deserialize stuff feels messy. Do we really need serialization? * Polish 6.5 (#24556) * Updates waterfall item design for timeline rows * Adjusts span and tx flyouts and updates tooltips to EUI * Heading size fixes and clean up * Updates tooltip snapshots * Review tweaks and snapshot updates * Revert experiment :) Co-Authored-By: jasonrhodes * Fixes bug with v1 waterfall state * Fixes bug with timeline bar height * Updates snapshot tests * Updated test so it doesn't mount and rely on EUI makeId() which is non-deterministic per test run * Don't throw errors in optimizer (#24660) * Fixed label position on progress elements (#24623) * [kbn/es] add context to error message (#24664) This just tweaks the kbn-es error message to provide more context than just `Not Found` * [BeatsCM] Beats without tags should return an empty array via the config API (#24665) * [ML] Change file data visualizer JSON format label to NDJSON (#24643) * [ML] Change file datavisualizer JSON format label to NDJSON * [ML] Update edit flyout overrides snapshot * Translations for Coordinate Map (#23952) translate Coordinate Map * Translations for Region Map (#23875) add translations for region_map plugin * [Tools] Add TemplateLiteral parsing to i18n_check tool (#24580) * [Tools] Add TemplateLiteral parsing to i18n_check tool * Add comments * [ML] Remove obsolete sentence from info tooltip. (#24716) * Translate security/users component (#23940) Translate security/users * [Docs] Remove beta notes for ML and Query bar (#24718) * Translations for Table Vis plugin (#23679) add translations for table vis plugin * Feature/translate new nav bar (#24326) translate new_nav_bar * center content in fullscreen mode, hide K7 top nav (#24589) * [APM] Fixes rare cases where KibanaLink is loaded outside of React context (#24705) * Fixes rare cases where KibanaLink will be loaded outside of React context and requires no redux connect dependency * Fixes tests for updated Kibana link component * Removes obsolete snapshot * Secops structure code (#24652) * add basic structure for secops application * finalize skeleton for secops * fix type issue and hapi new version * remove route home, not needed for now * Add configuration + delete noise * prepend elastic license to generated file * Cut down on all tests except for secops tests and one example of infr… (#24693) * Cut down on all tests except for secops tests and one example of infra integration tests * Commented out code for only this branch * Added comments and "please see issue number" * https://github.com/elastic/ingest-dev/issues/60 --- .backportrc.json | 2 +- .i18nrc.json | 6 +- docs/apm/using-the-apm-ui.asciidoc | 14 +- packages/kbn-es/src/install/snapshot.js | 2 +- .../__snapshots__/add_data.test.js.snap | 2 +- .../kibana/public/home/components/add_data.js | 2 +- .../region_map/public/choropleth_layer.js | 24 +- .../region_map/public/region_map_vis.js | 20 +- .../public/region_map_vis_params.html | 109 +- .../public/region_map_visualization.js | 17 +- .../table_vis/public/table_vis.html | 5 +- .../table_vis/public/table_vis.js | 21 +- .../table_vis/public/table_vis_params.html | 32 +- .../public/base_maps_visualization.js | 6 +- .../public/coordinate_maps_visualization.js | 5 +- .../public/editors/_tooltip_formatter.js | 6 +- .../public/editors/tile_map_vis_params.html | 33 +- .../tile_map/public/editors/wms_options.html | 104 +- .../tile_map/public/editors/wms_options.js | 3 +- .../tile_map/public/geohash_layer.js | 8 +- .../tile_map/public/tile_map_vis.js | 33 +- src/dev/build/lib/__tests__/config.js | 1 - .../__snapshots__/i18n_call.test.js.snap | 14 +- .../__snapshots__/react.test.js.snap | 4 +- src/dev/i18n/extractors/i18n_call.test.js | 12 + src/dev/i18n/utils.js | 39 +- src/optimize/base_optimizer.js | 2 +- .../directives/global_nav/global_nav.js | 10 +- .../header_global_nav/components/header.tsx | 19 +- .../components/header_app_menu.tsx | 13 +- .../header_global_nav/header_global_nav.js | 3 +- .../DetailView/__test__/DetailView.test.js | 33 +- .../__snapshots__/DetailView.test.js.snap | 3636 +++++++++-------- .../Watcher/WatcherFlyOut.js | 8 +- .../components/app/ErrorGroupOverview/view.js | 1 + .../TransactionDetails/Distribution/index.tsx | 37 +- .../StickyTransactionProperties.tsx | 7 +- .../SpanFlyout/StickySpanProperties.tsx | 6 +- .../Waterfall/SpanFlyout/index.tsx | 55 +- .../Waterfall/TransactionFlyout/index.tsx | 63 +- .../Waterfall/WaterfallItem.tsx | 90 +- .../TransactionDetails/Transaction/index.tsx | 4 +- .../StickyProperties/StickyProperties.test.js | 8 +- .../StickyProperties.test.js.snap | 249 +- .../StickyProperties/{index.js => index.tsx} | 55 +- .../shared/charts/TransactionCharts/index.js | 16 +- .../apm/public/store/selectors/waterfall.ts | 8 +- .../__test__/__snapshots__/url.test.js.snap | 23 +- .../apm/public/utils/__test__/url.test.js | 8 +- x-pack/plugins/apm/public/utils/url.tsx | 17 +- .../tags/elasticsearch_tags_adapter.ts | 5 +- .../renderers/progress/shapes/semicircle.svg | 2 +- .../progress/shapes/vertical_bar.svg | 2 +- .../progress/shapes/vertical_pill.svg | 2 +- .../canvas/common/interpreter/create_error.js | 13 + .../canvas/common/interpreter/interpret.js | 36 +- .../common/interpreter/socket_interpret.js | 17 +- x-pack/plugins/canvas/init.js | 36 +- .../canvas/public/components/app/index.js | 2 + .../canvas/public/components/error/error.js | 6 +- .../components/fullscreen/fullscreen.scss | 9 +- .../canvas/public/lib/browser_registries.js | 74 + .../plugins/canvas/public/lib/interpreter.js | 9 +- .../canvas/public/lib/load_browser_plugins.js | 53 - x-pack/plugins/canvas/public/socket.js | 4 +- .../canvas/server/lib/get_plugin_stream.js | 4 +- .../server/lib/route_expression/browser.js | 44 + .../server/lib/route_expression/index.js | 32 + .../server/lib/route_expression/server.js | 33 + .../lib/route_expression/thread/babeled.js | 9 + .../lib/route_expression/thread/index.js | 98 + .../lib/route_expression/thread/worker.js | 68 + ...server_plugins.js => server_registries.js} | 34 +- x-pack/plugins/canvas/server/routes/socket.js | 60 +- .../canvas/server/sample_data/index.js | 3 +- .../server/sample_data/load_sample_data.js | 32 + .../components/json_tooltip/tooltips.json | 2 +- .../components/about_panel/welcome_content.js | 4 +- .../__snapshots__/overrides.test.js.snap | 8 +- .../edit_flyout/options/option_lists.js | 2 +- .../file_datavisualizer_view/utils.js | 2 +- .../import_view/importer/importer_factory.js | 6 +- .../{json_importer.js => ndjson_importer.js} | 2 +- .../management/users/confirm_delete.js | 54 +- .../components/management/users/edit_user.js | 212 +- .../components/management/users/users.js | 68 +- .../__snapshots__/login_page.test.tsx.snap | 36 +- .../components/login_page/login_page.tsx | 20 +- .../public/views/management/edit_user.js | 13 +- .../security/public/views/management/users.js | 3 +- .../api_integration/apis/beats/get_beat.js | 36 + .../apis/monitoring/common/mappings_exist.js | 2 +- 92 files changed, 3543 insertions(+), 2509 deletions(-) rename x-pack/plugins/apm/public/components/shared/StickyProperties/{index.js => index.tsx} (80%) create mode 100644 x-pack/plugins/canvas/common/interpreter/create_error.js create mode 100644 x-pack/plugins/canvas/public/lib/browser_registries.js delete mode 100644 x-pack/plugins/canvas/public/lib/load_browser_plugins.js create mode 100644 x-pack/plugins/canvas/server/lib/route_expression/browser.js create mode 100644 x-pack/plugins/canvas/server/lib/route_expression/index.js create mode 100644 x-pack/plugins/canvas/server/lib/route_expression/server.js create mode 100644 x-pack/plugins/canvas/server/lib/route_expression/thread/babeled.js create mode 100644 x-pack/plugins/canvas/server/lib/route_expression/thread/index.js create mode 100644 x-pack/plugins/canvas/server/lib/route_expression/thread/worker.js rename x-pack/plugins/canvas/server/lib/{load_server_plugins.js => server_registries.js} (55%) create mode 100644 x-pack/plugins/canvas/server/sample_data/load_sample_data.js rename x-pack/plugins/ml/public/file_datavisualizer/components/import_view/importer/{json_importer.js => ndjson_importer.js} (94%) diff --git a/.backportrc.json b/.backportrc.json index c9c60ae134de4..1183ed5e2bb4d 100644 --- a/.backportrc.json +++ b/.backportrc.json @@ -1,5 +1,5 @@ { "upstream": "elastic/kibana", - "branches": [{ "name": "6.x", "checked": true }, "6.5", "6.4", "6.3", "5.6"], + "branches": [{ "name": "6.x", "checked": true }, "6.5", "6.4", "6.3", "6.2", "6.1", "6.0", "5.6"], "labels": ["backport"] } diff --git a/.i18nrc.json b/.i18nrc.json index 2b15e16458650..5e7447cda0d1c 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -6,10 +6,14 @@ "kbnVislibVisTypes": "src/core_plugins/kbn_vislib_vis_types", "markdownVis": "src/core_plugins/markdown_vis", "metricVis": "src/core_plugins/metric_vis", + "tableVis": "src/core_plugins/table_vis", + "regionMap": "src/core_plugins/region_map", "statusPage": "src/core_plugins/status_page", + "tileMap": "src/core_plugins/tile_map", "tagCloud": "src/core_plugins/tagcloud", "xpack.idxMgmt": "x-pack/plugins/index_management", - "xpack.watcher": "x-pack/plugins/watcher" + "xpack.watcher": "x-pack/plugins/watcher", + "xpack.security": "x-pack/plugins/security" }, "exclude": [ "src/ui/ui_render/bootstrap/app_bootstrap.js", diff --git a/docs/apm/using-the-apm-ui.asciidoc b/docs/apm/using-the-apm-ui.asciidoc index 0fc8008d4cf23..012f778c3a311 100644 --- a/docs/apm/using-the-apm-ui.asciidoc +++ b/docs/apm/using-the-apm-ui.asciidoc @@ -65,12 +65,7 @@ Watches are managed separately in the dedicated Watcher UI available in Advanced image::apm/images/apm-errors-watcher-assistant.png[Example view of the Watcher assistant for errors in APM UI in Kibana] [[machine-learning-integration]] -=== Machine Learning integration (beta) - -[NOTE] -============ -Please note this feature is in beta. We kindly ask that you https://discuss.elastic.co/c/apm[provide feedback] if you experience any issues. -============ +=== Machine Learning integration The Machine Learning integration will initiate a new job predefined to calculate anomaly scores on transaction response times. The response time graph will show the expected bounds and annotate the graph when the anomaly score is 75 or above. @@ -80,12 +75,7 @@ image::apm/images/apm-ml-integration.png[Example view of anomaly scores on respo Jobs can be created per transaction type and based on the average response time. You can manage jobs in the Machine Learning jobs management page. It might take some time for results to appear on the graph. [[query-bar]] -=== Query bar (beta) - -[NOTE] -============ -Please note this feature is in beta. We kindly ask that you https://discuss.elastic.co/c/apm[provide feedback] if you experience any issues. -============ +=== Query bar The query bar is a powerful data query feature. Similar to the query bar in {kibana-ref}/discover.html[Discover] it enables you to pass advanced queries on your data to filter on particular pieces of information that you're interested in. It comes with a handy autocomplete that helps find the fields and even provides suggestions to the data they include. The query bar is available on Services, Transaction and Errors views, and any input will persist as you move between them. diff --git a/packages/kbn-es/src/install/snapshot.js b/packages/kbn-es/src/install/snapshot.js index d61f6e58ba20d..47edd8e3db6f9 100644 --- a/packages/kbn-es/src/install/snapshot.js +++ b/packages/kbn-es/src/install/snapshot.js @@ -89,7 +89,7 @@ function downloadFile(url, dest, log) { } if (!res.ok) { - return reject(new Error(res.statusText)); + return reject(new Error(`Unable to download elasticsearch snapshot: ${res.statusText}`)); } const stream = fs.createWriteStream(downloadPath); diff --git a/src/core_plugins/kibana/public/home/components/__snapshots__/add_data.test.js.snap b/src/core_plugins/kibana/public/home/components/__snapshots__/add_data.test.js.snap index d2002077b15d5..220d5e472c99b 100644 --- a/src/core_plugins/kibana/public/home/components/__snapshots__/add_data.test.js.snap +++ b/src/core_plugins/kibana/public/home/components/__snapshots__/add_data.test.js.snap @@ -832,7 +832,7 @@ exports[`mlEnabled 1`] = ` type="button" > diff --git a/src/core_plugins/kibana/public/home/components/add_data.js b/src/core_plugins/kibana/public/home/components/add_data.js index 8e598e4f26c3a..a45eda343cfca 100644 --- a/src/core_plugins/kibana/public/home/components/add_data.js +++ b/src/core_plugins/kibana/public/home/components/add_data.js @@ -229,7 +229,7 @@ const AddDataUi = ({ apmUiEnabled, isNewKibanaInstance, intl, mlEnabled }) => { > diff --git a/src/core_plugins/region_map/public/choropleth_layer.js b/src/core_plugins/region_map/public/choropleth_layer.js index 04a998f60996c..bc3a21d39d2c9 100644 --- a/src/core_plugins/region_map/public/choropleth_layer.js +++ b/src/core_plugins/region_map/public/choropleth_layer.js @@ -21,6 +21,7 @@ import $ from 'jquery'; 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 { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps'; import * as topojson from 'topojson-client'; @@ -123,7 +124,10 @@ export default class ChoroplethLayer extends KibanaMapLayer { featureCollection = topojson.feature(data, features);//conversion to geojson } else { //should never happen - throw new Error('Unrecognized format ' + formatType); + throw new Error(i18n.translate('regionMap.choroplethLayer.unrecognizedFormatErrorMessage', { + defaultMessage: 'Unrecognized format {formatType}', + values: { formatType }, + })); } this._sortedFeatures = featureCollection.features.slice(); this._sortFeatures(); @@ -143,15 +147,23 @@ export default class ChoroplethLayer extends KibanaMapLayer { let errorMessage; if (e.status === 404) { - errorMessage = `Server responding with '404' when attempting to fetch ${geojsonUrl}. - Make sure the file exists at that location.`; + errorMessage = i18n.translate('regionMap.choroplethLayer.downloadingVectorData404ErrorMessage', { + defaultMessage: 'Server responding with \'404\' when attempting to fetch {geojsonUrl}. \ +Make sure the file exists at that location.', + values: { geojsonUrl }, + }); } else { - errorMessage = `Cannot download ${geojsonUrl} file. Please ensure the -CORS configuration of the server permits requests from the Kibana application on this host.`; + errorMessage = i18n.translate('regionMap.choroplethLayer.downloadingVectorDataErrorMessage', { + defaultMessage: 'Cannot download {geojsonUrl} file. Please ensure the \ +CORS configuration of the server permits requests from the Kibana application on this host.', + values: { geojsonUrl }, + }); } toastNotifications.addDanger({ - title: 'Error downloading vector data', + title: i18n.translate('regionMap.choroplethLayer.downloadingVectorDataErrorMessageTitle', { + defaultMessage: 'Error downloading vector data', + }), text: errorMessage, }); diff --git a/src/core_plugins/region_map/public/region_map_vis.js b/src/core_plugins/region_map/public/region_map_vis.js index fdf94ee4cc268..5d85272b8360d 100644 --- a/src/core_plugins/region_map/public/region_map_vis.js +++ b/src/core_plugins/region_map/public/region_map_vis.js @@ -27,7 +27,7 @@ import { mapToLayerWithId } from './util'; import { RegionMapsVisualizationProvider } from './region_map_visualization'; import { Status } from 'ui/vis/update_status'; -VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmapsConfig, config) { +VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmapsConfig, config, i18n) { const VisFactory = Private(VisFactoryProvider); const RegionMapsVisualization = Private(RegionMapsVisualizationProvider); @@ -37,9 +37,9 @@ VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmaps return VisFactory.createBaseVisualization({ name: 'region_map', - title: 'Region Map', - description: 'Show metrics on a thematic map. Use one of the provided base maps, or add your own. ' + - 'Darker colors represent higher values.', + title: i18n('regionMap.mapVis.regionMapTitle', { defaultMessage: 'Region Map' }), + description: i18n('regionMap.mapVis.regionMapDescription', { defaultMessage: 'Show metrics on a thematic map. Use one of the \ +provided base maps, or add your own. Darker colors represent higher values.' }), category: CATEGORY.MAP, icon: 'visMapRegion', visConfig: { @@ -65,16 +65,16 @@ VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmaps collections: { legendPositions: [{ value: 'bottomleft', - text: 'bottom left', + text: i18n('regionMap.mapVis.regionMapEditorConfig.bottomLeftText', { defaultMessage: 'bottom left' }), }, { value: 'bottomright', - text: 'bottom right', + text: i18n('regionMap.mapVis.regionMapEditorConfig.bottomRightText', { defaultMessage: 'bottom right' }), }, { value: 'topleft', - text: 'top left', + text: i18n('regionMap.mapVis.regionMapEditorConfig.topLeftText', { defaultMessage: 'top left' }), }, { value: 'topright', - text: 'top right', + text: i18n('regionMap.mapVis.regionMapEditorConfig.topRightText', { defaultMessage: 'top right' }), }], colorSchemas: Object.keys(truncatedColorMaps), vectorLayers: vectorLayers @@ -83,7 +83,7 @@ VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmaps { group: 'metrics', name: 'metric', - title: 'Value', + title: i18n('regionMap.mapVis.regionMapEditorConfig.schemas.metricTitle', { defaultMessage: 'Value' }), min: 1, max: 1, aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits', @@ -96,7 +96,7 @@ VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmaps group: 'buckets', name: 'segment', icon: 'fa fa-globe', - title: 'shape field', + title: i18n('regionMap.mapVis.regionMapEditorConfig.schemas.segmentTitle', { defaultMessage: 'shape field' }), min: 1, max: 1, aggFilter: ['terms'] diff --git a/src/core_plugins/region_map/public/region_map_vis_params.html b/src/core_plugins/region_map/public/region_map_vis_params.html index 024041134d4b2..58be7df7a739a 100644 --- a/src/core_plugins/region_map/public/region_map_vis_params.html +++ b/src/core_plugins/region_map/public/region_map_vis_params.html @@ -1,15 +1,20 @@
-
- Layer Settings -
+
- +
- + +
- +
 
- +
 
@@ -79,12 +110,19 @@
-
Style settings
+
- +
0 && shouldShowWarning) { toastNotifications.addWarning({ - title: `Unable to show ${event.mismatches.length} ${event.mismatches.length > 1 ? 'results' : 'result'} on map`, - text: `Ensure that each of these term matches a shape on that shape's join field: ${event.mismatches.join(', ')}`, + title: i18n('regionMap.visualization.unableToShowMismatchesWarningTitle', { + defaultMessage: 'Unable to show {mismatchesLength} {oneMismatch, plural, one {result} other {results}} on map', + values: { + mismatchesLength: event.mismatches.length, + oneMismatch: event.mismatches.length > 1 ? 0 : 1, + }, + }), + text: i18n('regionMap.visualization.unableToShowMismatchesWarningText', { + defaultMessage: 'Ensure that each of these term matches a shape on that shape\'s join field: {mismatches}', + values: { + mismatches: event.mismatches.join(', '), + }, + }), }); } }); diff --git a/src/core_plugins/table_vis/public/table_vis.html b/src/core_plugins/table_vis/public/table_vis.html index 24cef37caaeb6..c225d34f441bc 100644 --- a/src/core_plugins/table_vis/public/table_vis.html +++ b/src/core_plugins/table_vis/public/table_vis.html @@ -1,7 +1,10 @@
-

No results found

+

diff --git a/src/core_plugins/table_vis/public/table_vis.js b/src/core_plugins/table_vis/public/table_vis.js index dfa28c34968c8..0cd14d43253d3 100644 --- a/src/core_plugins/table_vis/public/table_vis.js +++ b/src/core_plugins/table_vis/public/table_vis.js @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import './table_vis_controller'; import './table_vis_params'; import 'ui/agg_table'; @@ -51,9 +52,13 @@ function TableVisTypeProvider(Private) { return VisFactory.createAngularVisualization({ type: 'table', name: 'table', - title: 'Data Table', + title: i18n.translate('tableVis.tableVisTitle', { + defaultMessage: 'Data Table', + }), icon: 'visTable', - description: 'Display values in a table', + description: i18n.translate('tableVis.tableVisDescription', { + defaultMessage: 'Display values in a table', + }), category: CATEGORY.DATA, visConfig: { defaults: { @@ -75,7 +80,9 @@ function TableVisTypeProvider(Private) { { group: 'metrics', name: 'metric', - title: 'Metric', + title: i18n.translate('tableVis.tableVisEditorConfig.schemas.metricTitle', { + defaultMessage: 'Metric', + }), aggFilter: ['!geo_centroid', '!geo_bounds'], min: 1, defaults: [ @@ -85,13 +92,17 @@ function TableVisTypeProvider(Private) { { group: 'buckets', name: 'bucket', - title: 'Split Rows', + title: i18n.translate('tableVis.tableVisEditorConfig.schemas.bucketTitle', { + defaultMessage: 'Split Rows', + }), aggFilter: ['!filter'] }, { group: 'buckets', name: 'split', - title: 'Split Table', + title: i18n.translate('tableVis.tableVisEditorConfig.schemas.splitTitle', { + defaultMessage: 'Split Table', + }), aggFilter: ['!filter'] } ]) diff --git a/src/core_plugins/table_vis/public/table_vis_params.html b/src/core_plugins/table_vis/public/table_vis_params.html index a928d3d02b7bc..6e93a05e0027c 100644 --- a/src/core_plugins/table_vis/public/table_vis_params.html +++ b/src/core_plugins/table_vis/public/table_vis_params.html @@ -1,40 +1,60 @@
- +
- + - +
- +
 
diff --git a/src/core_plugins/tile_map/public/editors/wms_options.html b/src/core_plugins/tile_map/public/editors/wms_options.html index 23c580953dbdf..a0d9b008e3b9e 100644 --- a/src/core_plugins/tile_map/public/editors/wms_options.html +++ b/src/core_plugins/tile_map/public/editors/wms_options.html @@ -3,16 +3,21 @@
-
- Base Layer Settings -
+
- +
 
@@ -45,15 +53,20 @@
-

WMS is an OGC standard for map image services. For more information, go here.

-
+

+
-

* if this parameter is incorrect, maps will fail to load.

+

diff --git a/src/core_plugins/tile_map/public/editors/wms_options.js b/src/core_plugins/tile_map/public/editors/wms_options.js index 067b7efffb721..47c379b255e10 100644 --- a/src/core_plugins/tile_map/public/editors/wms_options.js +++ b/src/core_plugins/tile_map/public/editors/wms_options.js @@ -21,7 +21,7 @@ import { uiModules } from 'ui/modules'; import wmsOptionsTemplate from './wms_options.html'; const module = uiModules.get('kibana'); -module.directive('wmsOptions', function (serviceSettings) { +module.directive('wmsOptions', function (serviceSettings, i18n) { return { restrict: 'E', template: wmsOptionsTemplate, @@ -31,6 +31,7 @@ module.directive('wmsOptions', function (serviceSettings) { collections: '=', }, link: function ($scope) { + $scope.wmsLinkText = i18n('tileMap.wmsOptions.wmsLinkText', { defaultMessage: 'here' }); new Promise((resolve, reject) => { diff --git a/src/core_plugins/tile_map/public/geohash_layer.js b/src/core_plugins/tile_map/public/geohash_layer.js index 7fa4c1ab51331..570255e51c441 100644 --- a/src/core_plugins/tile_map/public/geohash_layer.js +++ b/src/core_plugins/tile_map/public/geohash_layer.js @@ -19,6 +19,7 @@ import L from 'leaflet'; import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; import { KibanaMapLayer } from 'ui/vis/map/kibana_map_layer'; import { HeatmapMarkers } from './markers/heatmap'; @@ -83,7 +84,12 @@ export class GeohashLayer extends KibanaMapLayer { }, this._zoom, this._featureCollectionMetaData.max); break; default: - throw new Error(`${this._geohashOptions.mapType} mapType not recognized`); + throw new Error(i18n.translate('tileMap.geohashLayer.mapTitle', { + defaultMessage: '{mapType} mapType not recognized', + values: { + mapType: this._geohashOptions.mapType, + }, + })); } diff --git a/src/core_plugins/tile_map/public/tile_map_vis.js b/src/core_plugins/tile_map/public/tile_map_vis.js index 736939e1dd787..670af56e76f65 100644 --- a/src/core_plugins/tile_map/public/tile_map_vis.js +++ b/src/core_plugins/tile_map/public/tile_map_vis.js @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options'; import './editors/tile_map_vis_params'; import { supports } from 'ui/utils/supports'; @@ -36,9 +37,13 @@ VisTypesRegistryProvider.register(function TileMapVisType(Private, getAppState, return VisFactory.createBaseVisualization({ name: 'tile_map', - title: 'Coordinate Map', + title: i18n.translate('tileMap.vis.mapTitle', { + defaultMessage: 'Coordinate Map', + }), icon: 'visMapCoordinate', - description: 'Plot latitude and longitude coordinates on a map', + description: i18n.translate('tileMap.vis.mapDescription', { + defaultMessage: 'Plot latitude and longitude coordinates on a map', + }), category: CATEGORY.MAP, visConfig: { canDesaturate: !!supports.cssFilters, @@ -63,16 +68,24 @@ VisTypesRegistryProvider.register(function TileMapVisType(Private, getAppState, colorSchemas: Object.keys(truncatedColorMaps), legendPositions: [{ value: 'bottomleft', - text: 'bottom left', + text: i18n.translate('tileMap.vis.map.editorConfig.legendPositions.bottomLeftText', { + defaultMessage: 'bottom left', + }), }, { value: 'bottomright', - text: 'bottom right', + text: i18n.translate('tileMap.vis.map.editorConfig.legendPositions.bottomRightText', { + defaultMessage: 'bottom right', + }), }, { value: 'topleft', - text: 'top left', + text: i18n.translate('tileMap.vis.map.editorConfig.legendPositions.topLeftText', { + defaultMessage: 'top left', + }), }, { value: 'topright', - text: 'top right', + text: i18n.translate('tileMap.vis.map.editorConfig.legendPositions.topRightText', { + defaultMessage: 'top right', + }), }], mapTypes: [ 'Scaled Circle Markers', @@ -87,7 +100,9 @@ VisTypesRegistryProvider.register(function TileMapVisType(Private, getAppState, { group: 'metrics', name: 'metric', - title: 'Value', + title: i18n.translate('tileMap.vis.map.editorConfig.schemas.metricTitle', { + defaultMessage: 'Value', + }), min: 1, max: 1, aggFilter: ['count', 'avg', 'sum', 'min', 'max', 'cardinality', 'top_hits'], @@ -98,7 +113,9 @@ VisTypesRegistryProvider.register(function TileMapVisType(Private, getAppState, { group: 'buckets', name: 'segment', - title: 'Geo Coordinates', + title: i18n.translate('tileMap.vis.map.editorConfig.schemas.geoCoordinatesTitle', { + defaultMessage: 'Geo Coordinates', + }), aggFilter: 'geohash_grid', min: 1, max: 1 diff --git a/src/dev/build/lib/__tests__/config.js b/src/dev/build/lib/__tests__/config.js index db42da6cbe136..6b395ce137e63 100644 --- a/src/dev/build/lib/__tests__/config.js +++ b/src/dev/build/lib/__tests__/config.js @@ -108,7 +108,6 @@ describe('dev/build/lib/config', () => { const { config } = await setup({ targetAllPlatforms: false }); const platforms = config.getNodePlatforms(); expect(platforms).to.be.an('array'); - expect(platforms).to.have.length(process.platform === 'linux'); if (process.platform !== 'linux') { expect(platforms).to.have.length(2); expect(platforms[0]).to.be(config.getPlatformForThisOs()); diff --git a/src/dev/i18n/extractors/__snapshots__/i18n_call.test.js.snap b/src/dev/i18n/extractors/__snapshots__/i18n_call.test.js.snap index d98c2e9261a1f..092a16dde6e24 100644 --- a/src/dev/i18n/extractors/__snapshots__/i18n_call.test.js.snap +++ b/src/dev/i18n/extractors/__snapshots__/i18n_call.test.js.snap @@ -20,7 +20,19 @@ Array [ ] `; -exports[`dev/i18n/extractors/i18n_call throws if defaultMessage is not a string literal 1`] = `"defaultMessage value should be a string literal (\\"message-id\\")."`; +exports[`dev/i18n/extractors/i18n_call extracts "i18n" and "i18n.translate" functions call message 3`] = ` +Array [ + "message-id-3", + Object { + "context": "Message +context 3", + "message": "Default +message 3", + }, +] +`; + +exports[`dev/i18n/extractors/i18n_call throws if defaultMessage is not a string literal 1`] = `"defaultMessage value should be a string or template literal (\\"message-id\\")."`; exports[`dev/i18n/extractors/i18n_call throws if message id value is not a string literal 1`] = `"Message id should be a string literal."`; diff --git a/src/dev/i18n/extractors/__snapshots__/react.test.js.snap b/src/dev/i18n/extractors/__snapshots__/react.test.js.snap index 6a51a5e216004..555015f6ddf0c 100644 --- a/src/dev/i18n/extractors/__snapshots__/react.test.js.snap +++ b/src/dev/i18n/extractors/__snapshots__/react.test.js.snap @@ -20,8 +20,8 @@ Array [ ] `; -exports[`dev/i18n/extractors/react extractIntlMessages throws if context value is not a string literal 1`] = `"context value should be a string literal (\\"message-id\\")."`; +exports[`dev/i18n/extractors/react extractIntlMessages throws if context value is not a string literal 1`] = `"context value should be a string or template literal (\\"message-id\\")."`; -exports[`dev/i18n/extractors/react extractIntlMessages throws if defaultMessage value is not a string literal 1`] = `"defaultMessage value should be a string literal (\\"message-id\\")."`; +exports[`dev/i18n/extractors/react extractIntlMessages throws if defaultMessage value is not a string literal 1`] = `"defaultMessage value should be a string or template literal (\\"message-id\\")."`; exports[`dev/i18n/extractors/react extractIntlMessages throws if message id is not a string literal 1`] = `"Message id should be a string literal."`; diff --git a/src/dev/i18n/extractors/i18n_call.test.js b/src/dev/i18n/extractors/i18n_call.test.js index f3ab92f4f1d6e..15d6ec340373e 100644 --- a/src/dev/i18n/extractors/i18n_call.test.js +++ b/src/dev/i18n/extractors/i18n_call.test.js @@ -31,6 +31,12 @@ const translateCallMessageSource = ` i18n.translate('message-id-2', { defaultMessage: 'Default message 2', context: 'Message context 2' }); `; +const i18nCallMessageWithTemplateLiteralSource = ` +i18n('message-id-3', { defaultMessage: \`Default +message 3\`, context: \`Message +context 3\` }); +`; + describe('dev/i18n/extractors/i18n_call', () => { test('extracts "i18n" and "i18n.translate" functions call message', () => { let callExpressionNode = [...traverseNodes(parse(i18nCallMessageSource).program.body)].find( @@ -44,6 +50,12 @@ describe('dev/i18n/extractors/i18n_call', () => { ); expect(extractI18nCallMessages(callExpressionNode)).toMatchSnapshot(); + + callExpressionNode = [ + ...traverseNodes(parse(i18nCallMessageWithTemplateLiteralSource).program.body), + ].find(node => isCallExpression(node)); + + expect(extractI18nCallMessages(callExpressionNode)).toMatchSnapshot(); }); test('throws if message id value is not a string literal', () => { diff --git a/src/dev/i18n/utils.js b/src/dev/i18n/utils.js index c5c99d1b3316a..5b6778e949913 100644 --- a/src/dev/i18n/utils.js +++ b/src/dev/i18n/utils.js @@ -25,6 +25,7 @@ import { isObjectExpression, isObjectProperty, isStringLiteral, + isTemplateLiteral, } from '@babel/types'; import fs from 'fs'; import glob from 'glob'; @@ -197,20 +198,46 @@ export function extractMessageIdFromNode(node) { return node.value; } +function parseTemplateLiteral(node, messageId) { + // TemplateLiteral consists of quasis (strings) and expressions. + // If we have at least one expression in template literal, then quasis length + // will be greater than 1 + if (node.quasis.length > 1) { + throw createFailError(`expressions are not allowed in template literals ("${messageId}").`); + } + + // Babel reads 'cooked' and 'raw' versions of a string. + // 'cooked' acts like a normal StringLiteral value and interprets backslashes + // 'raw' is primarily designed for TaggedTemplateLiteral and escapes backslashes + return node.quasis[0].value.cooked; +} + export function extractMessageValueFromNode(node, messageId) { - if (!isStringLiteral(node)) { - throw createFailError(`defaultMessage value should be a string literal ("${messageId}").`); + if (isStringLiteral(node)) { + return node.value; } - return node.value; + if (isTemplateLiteral(node)) { + return parseTemplateLiteral(node, messageId); + } + + throw createFailError( + `defaultMessage value should be a string or template literal ("${messageId}").` + ); } export function extractContextValueFromNode(node, messageId) { - if (!isStringLiteral(node)) { - throw createFailError(`context value should be a string literal ("${messageId}").`); + if (isStringLiteral(node)) { + return node.value; } - return node.value; + if (isTemplateLiteral(node)) { + return parseTemplateLiteral(node, messageId); + } + + throw createFailError( + `context value should be a string or template literal ("${messageId}").` + ); } export function extractValuesKeysFromNode(node, messageId) { diff --git a/src/optimize/base_optimizer.js b/src/optimize/base_optimizer.js index dceb7b8c44be4..386d0c9748a72 100644 --- a/src/optimize/base_optimizer.js +++ b/src/optimize/base_optimizer.js @@ -427,7 +427,7 @@ export default class BaseOptimizer { Stats.presetToOptions('minimal') )); - throw Boom.internal( + return Boom.internal( `Optimizations failure.\n${details.split('\n').join('\n ')}\n`, stats.toJson(Stats.presetToOptions('detailed')) ); diff --git a/src/ui/public/chrome/directives/global_nav/global_nav.js b/src/ui/public/chrome/directives/global_nav/global_nav.js index 7abf81971b7b0..409d20a5951cf 100644 --- a/src/ui/public/chrome/directives/global_nav/global_nav.js +++ b/src/ui/public/chrome/directives/global_nav/global_nav.js @@ -27,7 +27,7 @@ import { uiModules } from '../../../modules'; const module = uiModules.get('kibana'); -module.directive('globalNav', (globalNavState, chrome) => { +module.directive('globalNav', (globalNavState, chrome, i18n) => { return { restrict: 'E', replace: true, @@ -46,8 +46,12 @@ module.directive('globalNav', (globalNavState, chrome) => { scope.isGlobalNavOpen = isOpen; scope.globalNavToggleButton = { classes: isOpen ? 'global-nav-link--close' : undefined, - title: isOpen ? 'Collapse' : 'Expand', - tooltipContent: isOpen ? 'Collapse side bar' : 'Expand side bar', + title: isOpen ? + i18n('common.ui.chrome.globalNav.navToggleButtonCollapseTitle', { defaultMessage: 'Collapse' }) + : i18n('common.ui.chrome.globalNav.navToggleButtonExpandTitle', { defaultMessage: 'Expand' }), + tooltipContent: isOpen ? + i18n('common.ui.chrome.globalNav.navToggleButtonCollapseTooltip', { defaultMessage: 'Collapse side bar' }) + : i18n('common.ui.chrome.globalNav.navToggleButtonExpandTooltip', { defaultMessage: 'Expand side bar' }), }; // Notify visualizations, e.g. the dashboard, that they should re-render. diff --git a/src/ui/public/chrome/directives/header_global_nav/components/header.tsx b/src/ui/public/chrome/directives/header_global_nav/components/header.tsx index 444e92cadcc2d..b22c673450c3c 100644 --- a/src/ui/public/chrome/directives/header_global_nav/components/header.tsx +++ b/src/ui/public/chrome/directives/header_global_nav/components/header.tsx @@ -36,6 +36,7 @@ import { HeaderAppMenu } from './header_app_menu'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; import { HeaderNavControls } from './header_nav_controls'; +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import { ChromeHeaderNavControlsRegistry } from 'ui/registry/chrome_header_nav_controls'; import { Breadcrumb, NavControlSide, NavLink } from '../'; @@ -46,12 +47,22 @@ interface Props { isVisible: boolean; navLinks: NavLink[]; navControls: ChromeHeaderNavControlsRegistry; + intl: InjectedIntl; } -export class Header extends Component { +class HeaderUI extends Component { public renderLogo() { - const { homeHref } = this.props; - return ; + const { homeHref, intl } = this.props; + return ( + + ); } public render() { @@ -85,3 +96,5 @@ export class Header extends Component { ); } } + +export const Header = injectI18n(HeaderUI); diff --git a/src/ui/public/chrome/directives/header_global_nav/components/header_app_menu.tsx b/src/ui/public/chrome/directives/header_global_nav/components/header_app_menu.tsx index 3fa46e75a1187..1d142f14351d5 100644 --- a/src/ui/public/chrome/directives/header_global_nav/components/header_app_menu.tsx +++ b/src/ui/public/chrome/directives/header_global_nav/components/header_app_menu.tsx @@ -32,17 +32,19 @@ import { EuiPopover, } from '@elastic/eui'; +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import { NavLink } from '../'; interface Props { navLinks: NavLink[]; + intl: InjectedIntl; } interface State { isOpen: boolean; } -export class HeaderAppMenu extends Component { +class HeaderAppMenuUI extends Component { constructor(props: Props) { super(props); @@ -52,14 +54,17 @@ export class HeaderAppMenu extends Component { } public render() { - const { navLinks = [] } = this.props; + const { navLinks = [], intl } = this.props; const button = ( @@ -106,3 +111,5 @@ export class HeaderAppMenu extends Component { ); } + +export const HeaderAppMenu = injectI18n(HeaderAppMenuUI); diff --git a/src/ui/public/chrome/directives/header_global_nav/header_global_nav.js b/src/ui/public/chrome/directives/header_global_nav/header_global_nav.js index 1b81119c08043..1d6048b3f6b7c 100644 --- a/src/ui/public/chrome/directives/header_global_nav/header_global_nav.js +++ b/src/ui/public/chrome/directives/header_global_nav/header_global_nav.js @@ -23,6 +23,7 @@ import { Header } from './components/header'; import './header_global_nav.less'; import { chromeHeaderNavControlsRegistry } from 'ui/registry/chrome_header_nav_controls'; import { breadcrumbs } from '../../services/breadcrumb_state'; +import { injectI18nProvider } from '@kbn/i18n/react'; const module = uiModules.get('kibana'); @@ -31,7 +32,7 @@ module.directive('headerGlobalNav', (reactDirective, chrome, Private) => { const navLinks = chrome.getNavLinks(); const homeHref = chrome.addBasePath('/app/kibana#/home'); - return reactDirective(Header, [ + return reactDirective(injectI18nProvider(Header), [ // scope accepted by directive, passed in as React props 'appTitle', 'isVisible', diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/DetailView.test.js b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/DetailView.test.js index c41fe7891a6b6..1111a7aec186a 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/DetailView.test.js +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/DetailView.test.js @@ -7,43 +7,36 @@ import React from 'react'; import DetailView from '../index'; import props from './props.json'; +import { shallow } from 'enzyme'; -import { - mountWithRouterAndStore, - mockMoment, - toJson -} from '../../../../../utils/testHelpers'; +import { mockMoment } from '../../../../../utils/testHelpers'; describe('DetailView', () => { - let storeState; beforeEach(() => { // Avoid timezone issues mockMoment(); - - storeState = { - location: { search: '' } - }; }); it('should render empty state', () => { - const wrapper = mountWithRouterAndStore( - , - storeState + const wrapper = shallow( + ); - - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('should render with data', () => { - const wrapper = mountWithRouterAndStore( + const wrapper = shallow( , - storeState + location={{ state: '' }} + /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/__snapshots__/DetailView.test.js.snap b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/__snapshots__/DetailView.test.js.snap index fae5b56c02892..7b59b132d3040 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/__snapshots__/DetailView.test.js.snap +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/__test__/__snapshots__/DetailView.test.js.snap @@ -3,1812 +3,1988 @@ exports[`DetailView should render empty state 1`] = `""`; exports[`DetailView should render with data 1`] = ` -.c4 { - margin-bottom: 8px; - font-size: 12px; - color: #999999; -} - -.c4 span { - cursor: help; -} - -.c6 { - color: #999999; -} - -.c5 { - display: inline-block; - line-height: 16px; -} - -.c7 { - display: inline-block; - line-height: 16px; - max-width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.c2 { - margin: 24px 0; - font-size: 20px; - margin: 0; -} - -.c9 { - display: inline-block; - font-size: 16px; - padding: 16px 20px; - text-align: center; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - border-bottom: 2px solid #006E8A; -} - -.c10 { - display: inline-block; - font-size: 16px; - padding: 16px 20px; - text-align: center; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.c15 { - position: relative; - border-radius: 0 0 5px 5px; -} - -.c16 { - position: absolute; - width: 100%; - height: 18px; - top: 126px; - pointer-events: none; - background-color: #FCF2E6; -} - -.c17 { - position: absolute; - top: 0; - left: 0; - border-radius: 0 0 0 5px; - background: #f5f5f5; -} - -.c18 { - position: relative; - min-width: 42px; - padding-left: 8px; - padding-right: 4px; - color: #999999; - line-height: 18px; - text-align: right; - border-right: 1px solid #d9d9d9; -} - -.c18:last-of-type { - border-radius: 0 0 0 5px; -} - -.c19 { - position: relative; - min-width: 42px; - padding-left: 8px; - padding-right: 4px; - color: #999999; - line-height: 18px; - text-align: right; - border-right: 1px solid #d9d9d9; - background-color: #FCF2E6; -} - -.c19:last-of-type { - border-radius: 0 0 0 5px; -} - -.c20 { - overflow: auto; - margin: 0 0 0 42px; - padding: 0; - background-color: #ffffff; -} - -.c20:last-of-type { - border-radius: 0 0 5px 0; -} - -.c21 { - margin: 0; - color: inherit; - background: inherit; - border: 0; - border-radius: 0; - overflow: initial; - padding: 0 18px; - line-height: 18px; -} - -.c22 { - position: relative; - padding: 0; - margin: 0; - white-space: pre; - z-index: 2; -} - -.c12 { - color: #999999; - padding: 8px; - border-bottom: 1px solid #d9d9d9; - border-radius: 5px 5px 0 0; -} - -.c14 { - font-weight: bold; -} - -.c11 { - margin: 0 0 24px 0; - position: relative; - font-family: "SFMono-Regular",Consolas,"Liberation Mono",Menlo,Courier,monospace; - border: 1px solid #d9d9d9; - border-radius: 5px; - background: #f5f5f5; -} - -.c11 .c13 { - color: #000000; -} - -.c23 { - margin: 0 0 24px 0; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.c0 { - position: relative; - border: 1px solid #d9d9d9; - border-radius: 5px; - margin-top: 24px; -} - -.c1 { - display: -webkit-box; - display: -webkit-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-pack: justify; - -webkit-justify-content: space-between; - -ms-flex-pack: justify; - justify-content: space-between; - -webkit-align-items: flex-start; - -webkit-box-align: flex-start; - -ms-flex-align: flex-start; - align-items: flex-start; - padding: 24px 24px 0; - margin-bottom: 16px; -} - -.c8 { - padding: 0 24px; - border-bottom: 1px solid #d9d9d9; -} - -.c3 { - padding: 24px 24px 0; -} - -
- -
-
+ -
+ + + -
- - Timestamp - -
-
- 1337 minutes ago (mocking 1515508740) - - - ( - 1st of January (mocking 1515508740) - ) - -
-
-
-
- - URL - -
- - N/A - -
-
-
- - Request method - -
-
- GET -
-
-
-
- - Handled - -
-
- N/A -
-
-
-
- - User ID - -
-
- N/A -
-
-
-
-
-
+ + + Exception stacktrace -
-
+ Request -
-
+ Response -
-
+ System -
-
+ Service -
-
+ Process -
-
+ User -
-
+ Tags -
-
+ Custom -
-
-
-
-

- Stack traces -

-
-
- - server/coffee.js - - in - - - <anonymous> - - at - - - line - 9 - -
-
-
-
-
- 2 - . -
-
- 3 - . -
-
- 4 - . -
-
- 5 - . -
-
- 6 - . -
-
- 7 - . -
-
- 8 - . -
-
- 9 - . -
-
- 10 - . -
-
- 11 - . -
-
- 12 - . -
-
- 13 - . -
-
- 14 - . -
-
- 15 - . -
-
- 16 - . -
-
-
-
-              
-                
-
-              
-            
-
-              
-                
-                  var
-                
-                 express = 
-                
-                  require
-                
-                (
-                
-                  'express'
-                
-                )
-              
-            
-
-              
-                
-                  var
-                
-                 apm = 
-                
+  
+  
+     (server/coffee.js)",
+                "exception": Object {
+                  "message": "Cannot read property 'level' of undefined",
+                  "stacktrace": Array [
                     Object {
-                      "color": "#5c2699",
-                    }
-                  }
-                >
-                  require
-                
-                (
-                ",
+                      "libraryFrame": false,
+                      "line": Object {
+                        "context": "  if (req.paarms.level === 11) {",
+                        "number": 9,
+                      },
+                    },
                     Object {
-                      "color": "#c41a16",
-                    }
-                  }
-                >
-                  'elastic-apm-node'
-                
-                )
-              
-            
-
-              
-                
-
-              
-            
-
-              
-                 3) {",
+                          "    // not a standard request handler",
+                          "    return next();",
+                          "  }",
+                          "",
+                          "  try {",
+                        ],
+                      },
+                      "filename": "node_modules/express/lib/router/layer.js",
+                      "function": "handle",
+                      "libraryFrame": true,
+                      "line": Object {
+                        "context": "    fn(req, res, next);",
+                        "number": 95,
+                      },
+                    },
                     Object {
-                      "color": "#aa0d91",
-                    }
-                  }
-                >
-                  var
-                
-                 app = 
-                
-                  module
-                
-                .exports = 
-                
-                  new
-                
-                 express.Router()
-              
-            
-
-              
-                
-
-              
-            
-
-              
-                app.get(
-                 3) {",
+                          "    // not a standard request handler",
+                          "    return next();",
+                          "  }",
+                          "",
+                          "  try {",
+                        ],
+                      },
+                      "filename": "node_modules/express/lib/router/layer.js",
+                      "function": "handle",
+                      "libraryFrame": true,
+                      "line": Object {
+                        "context": "    fn(req, res, next);",
+                        "number": 95,
+                      },
+                    },
                     Object {
-                      "color": "#c41a16",
-                    }
-                  }
-                >
-                  '/is-it-coffee-time'
-                
-                , 
-                
-                  
-                    function
-                  
-                   (
-                  
-                    req, res
-                  
-                  ) 
-                
-                {
-              
-            
-
-              
-                  
-                ",
+                      "libraryFrame": true,
+                      "line": Object {
+                        "context": "        return layer.handle_request(req, res, next);",
+                        "number": 281,
+                      },
+                    },
                     Object {
-                      "color": "#aa0d91",
-                    }
-                  }
-                >
-                  if
-                
-                 (req.paarms.level === 
-                
-                  11
-                
-                ) {
-              
-            
-
-              
-                    res.send(
-                
-                  'Of course!'
-                
-                )
-              
-            
-
-              
-                  } 
-                
-                  else
-                
-                 {
-              
-            
-
-              
-                    res.send(
-                
-                  'You can\\'t have any!'
-                
-                )
-              
-            
-
-              
-                  }
-              
-            
-
-              
-                })
-              
-            
-
-              
-                
-
-              
-            
-
-              
-                app.get(
-                 3) {",
+                          "    // not a standard request handler",
+                          "    return next();",
+                          "  }",
+                          "",
+                          "  try {",
+                        ],
+                      },
+                      "filename": "node_modules/express/lib/router/layer.js",
+                      "function": "handle",
+                      "libraryFrame": true,
+                      "line": Object {
+                        "context": "    fn(req, res, next);",
+                        "number": 95,
+                      },
+                    },
                     Object {
-                      "color": "#c41a16",
-                    }
-                  }
-                >
-                  '/log-error'
-                
-                , 
-                
-                  
-                    function
-                  
-                   (
-                  
-                    req, res
-                  
-                  ) 
-                
-                {
-              
-            
-
-
-
-
-
- -
-
-
-
-
- - server.js - - in - - - <anonymous> - - at - - - line - 27 - -
-
-
-
-
- 20 - . -
-
- 21 - . -
-
- 22 - . -
-
- 23 - . -
-
- 24 - . -
-
- 25 - . -
-
- 26 - . -
-
- 27 - . -
-
- 28 - . -
-
- 29 - . -
-
- 30 - . -
-
- 31 - . -
-
- 32 - . -
-
- 33 - . -
-
- 34 - . -
-
-
-
-              
-                app.use(
-                
-                  require
-                
-                (
-                ",
+                      "libraryFrame": true,
+                      "line": Object {
+                        "context": "      trim_prefix(layer, layerError, layerPath, path);",
+                        "number": 284,
+                      },
+                    },
                     Object {
-                      "color": "#c41a16",
-                    }
-                  }
-                >
-                  'body-parser'
-                
-                ).json())
-              
-            
-
-              
-                app.use(express.static(
-                
-                  'client/build'
-                
-                ))
-              
-            
-
-              
-                app.use(
-                
-                  
-                    function
-                  
-                   (
-                  
-                    req, res, next
-                  
-                  ) 
-                
-                {
-              
-            
-
-              
-                  apm.setTag(
-                
-                  'foo'
-                
-                , 
-                ",
+                      "libraryFrame": false,
+                      "line": Object {
+                        "context": "  next()",
+                        "number": 27,
+                      },
+                    },
                     Object {
-                      "color": "#c41a16",
-                    }
-                  }
-                >
-                  'bar'
-                
-                )
-              
-            
-
-              
-                  apm.setTag(
-                 3) {",
+                          "    // not a standard request handler",
+                          "    return next();",
+                          "  }",
+                          "",
+                          "  try {",
+                        ],
+                      },
+                      "filename": "node_modules/express/lib/router/layer.js",
+                      "function": "handle",
+                      "libraryFrame": true,
+                      "line": Object {
+                        "context": "    fn(req, res, next);",
+                        "number": 95,
+                      },
+                    },
                     Object {
-                      "color": "#c41a16",
-                    }
-                  }
-                >
-                  'lorem'
-                
-                , 
-                
-                  'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.'
-                
-                )
-              
-            
-
-              
-                  apm.setTag(
-                ",
+                      "libraryFrame": true,
+                      "line": Object {
+                        "context": "      trim_prefix(layer, layerError, layerPath, path);",
+                        "number": 284,
+                      },
+                    },
                     Object {
-                      "color": "#c41a16",
-                    }
-                  }
-                >
-                  'this-is-a-very-long-tag-name-without-any-spaces'
-                
-                , 
-                
-                  'test'
-                
-                )
-              
-            
-
-              
-                  apm.setTag(
-                
-                  'multi-line'
-                
-                , 
-                
-                  'foo\\nbar\\nbaz'
-                
-                )
-              
-            
-
-              
-                  next()
-              
-            
-
-              
-                })
-              
-            
-
-              
-                
-
-              
-            
-
-              
-                app.use(
-                
-                  require
-                
-                (
-                
-                  './server/coffee'
-                
-                ))
-              
-            
-
-              
-                app.use(
-                
-                  '/api'
-                
-                , 
-                
-                  require
-                
-                (
-                
-                  './server/routes'
-                
-                ))
-              
-            
-
-              
-                app.get(
-                
-                  '*'
-                
-                , 
-                
-                  
-                    function
-                  
-                   (
-                  
-                    req, res
-                  
-                  ) 
-                
-                {
-              
-            
-
-              
-                  res.sendFile(path.resolve(__dirname, 
-                
-                  'client/build'
-                
-                , 
-                
-                  'index.html'
-                
-                ))
-              
-            
-
-              
-                })
-              
-            
-
-
-
-
-
- -
-
-
-
-
-
+ "absPath": "fs.js", + "filename": "fs.js", + "function": "FSReqWrap.oncomplete", + "libraryFrame": true, + "line": Object { + "number": 123, + }, + }, + ], + "type": "TypeError", + }, + "groupingKey": "c00e245c2fbebaf178fc31eeb2bb0250", + "id": "c5e55dfc-09cc-4e0d-ace3-1ba4233f66eb", + }, + "processor": Object { + "event": "error", + "name": "error", + }, + }, + "groupId": "c00e245c2fbebaf178fc31eeb2bb0250", + "occurrencesCount": 18, + }, + "status": "SUCCESS", + } + } + excStackframes={ + Array [ + Object { + "absPath": "/app/server/coffee.js", + "context": Object { + "post": Array [ + " res.send('Of course!')", + " } else {", + " res.send('You can\\\\'t have any!')", + " }", + "})", + "", + "app.get('/log-error', function (req, res) {", + ], + "pre": Array [ + "", + "var express = require('express')", + "var apm = require('elastic-apm-node')", + "", + "var app = module.exports = new express.Router()", + "", + "app.get('/is-it-coffee-time', function (req, res) {", + ], + }, + "filename": "server/coffee.js", + "function": "", + "libraryFrame": false, + "line": Object { + "context": " if (req.paarms.level === 11) {", + "number": 9, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/layer.js", + "context": Object { + "post": Array [ + " } catch (err) {", + " next(err);", + " }", + "};", + "", + "/**", + " * Check if this route matches \`path\`, if so", + ], + "pre": Array [ + "", + " if (fn.length > 3) {", + " // not a standard request handler", + " return next();", + " }", + "", + " try {", + ], + }, + "filename": "node_modules/express/lib/router/layer.js", + "function": "handle", + "libraryFrame": true, + "line": Object { + "context": " fn(req, res, next);", + "number": 95, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/route.js", + "context": Object { + "post": Array [ + " }", + " }", + "};", + "", + "/**", + " * Add a handler for all HTTP verbs to this route.", + " *", + ], + "pre": Array [ + " if (layer.method && layer.method !== method) {", + " return next(err);", + " }", + "", + " if (err) {", + " layer.handle_error(err, req, res, next);", + " } else {", + ], + }, + "filename": "node_modules/express/lib/router/route.js", + "function": "next", + "libraryFrame": true, + "line": Object { + "context": " layer.handle_request(req, res, next);", + "number": 137, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/route.js", + "context": Object { + "post": Array [ + "", + " function next(err) {", + " // signal to exit route", + " if (err && err === 'route') {", + " return done();", + " }", + "", + ], + "pre": Array [ + " var method = req.method.toLowerCase();", + " if (method === 'head' && !this.methods['head']) {", + " method = 'get';", + " }", + "", + " req.route = this;", + "", + ], + }, + "filename": "node_modules/express/lib/router/route.js", + "function": "dispatch", + "libraryFrame": true, + "line": Object { + "context": " next();", + "number": 112, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/layer.js", + "context": Object { + "post": Array [ + " } catch (err) {", + " next(err);", + " }", + "};", + "", + "/**", + " * Check if this route matches \`path\`, if so", + ], + "pre": Array [ + "", + " if (fn.length > 3) {", + " // not a standard request handler", + " return next();", + " }", + "", + " try {", + ], + }, + "filename": "node_modules/express/lib/router/layer.js", + "function": "handle", + "libraryFrame": true, + "line": Object { + "context": " fn(req, res, next);", + "number": 95, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + " }", + "", + " trim_prefix(layer, layerError, layerPath, path);", + " });", + " }", + "", + " function trim_prefix(layer, layerError, layerPath, path) {", + ], + "pre": Array [ + " // this should be done for the layer", + " self.process_params(layer, paramcalled, req, res, function (err) {", + " if (err) {", + " return next(layerError || err);", + " }", + "", + " if (route) {", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "", + "libraryFrame": true, + "line": Object { + "context": " return layer.handle_request(req, res, next);", + "number": 281, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + " }", + "", + " var i = 0;", + " var name;", + " var paramIndex = 0;", + " var key;", + " var paramVal;", + ], + "pre": Array [ + " var params = this.params;", + "", + " // captured parameters from the layer, keys and values", + " var keys = layer.keys;", + "", + " // fast track", + " if (!keys || keys.length === 0) {", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "process_params", + "libraryFrame": true, + "line": Object { + "context": " return done();", + "number": 335, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + " if (err) {", + " return next(layerError || err);", + " }", + "", + " if (route) {", + " return layer.handle_request(req, res, next);", + " }", + ], + "pre": Array [ + " // Capture one-time layer values", + " req.params = self.mergeParams", + " ? mergeParams(layer.params, parentParams)", + " : layer.params;", + " var layerPath = layer.path;", + "", + " // this should be done for the layer", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "next", + "libraryFrame": true, + "line": Object { + "context": " self.process_params(layer, paramcalled, req, res, function (err) {", + "number": 275, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + "", + " function next(err) {", + " var layerError = err === 'route'", + " ? null", + " : err;", + "", + " // remove added slash", + ], + "pre": Array [ + " });", + " }", + "", + " // setup basic req values", + " req.baseUrl = parentUrl;", + " req.originalUrl = req.originalUrl || req.url;", + "", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "handle", + "libraryFrame": true, + "line": Object { + "context": " next();", + "number": 174, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + " }", + "", + " // mixin Router class functions", + " setPrototypeOf(router, proto)", + "", + " router.params = {};", + " router._params = [];", + ], + "pre": Array [ + " * @public", + " */", + "", + "var proto = module.exports = function(options) {", + " var opts = options || {};", + "", + " function router(req, res, next) {", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "router", + "libraryFrame": true, + "line": Object { + "context": " router.handle(req, res, next);", + "number": 47, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/layer.js", + "context": Object { + "post": Array [ + " } catch (err) {", + " next(err);", + " }", + "};", + "", + "/**", + " * Check if this route matches \`path\`, if so", + ], + "pre": Array [ + "", + " if (fn.length > 3) {", + " // not a standard request handler", + " return next();", + " }", + "", + " try {", + ], + }, + "filename": "node_modules/express/lib/router/layer.js", + "function": "handle", + "libraryFrame": true, + "line": Object { + "context": " fn(req, res, next);", + "number": 95, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + " }", + " }", + "};", + "", + "/**", + " * Process any parameters for the layer.", + " * @private", + ], + "pre": Array [ + " }", + "", + " debug('%s %s : %s', layer.name, layerPath, req.originalUrl);", + "", + " if (layerError) {", + " layer.handle_error(layerError, req, res, next);", + " } else {", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "trim_prefix", + "libraryFrame": true, + "line": Object { + "context": " layer.handle_request(req, res, next);", + "number": 317, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + " });", + " }", + "", + " function trim_prefix(layer, layerError, layerPath, path) {", + " if (layerPath.length !== 0) {", + " // Validate path breaks on a path separator", + " var c = path[layerPath.length]", + ], + "pre": Array [ + " return next(layerError || err);", + " }", + "", + " if (route) {", + " return layer.handle_request(req, res, next);", + " }", + "", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "", + "libraryFrame": true, + "line": Object { + "context": " trim_prefix(layer, layerError, layerPath, path);", + "number": 284, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + " }", + "", + " var i = 0;", + " var name;", + " var paramIndex = 0;", + " var key;", + " var paramVal;", + ], + "pre": Array [ + " var params = this.params;", + "", + " // captured parameters from the layer, keys and values", + " var keys = layer.keys;", + "", + " // fast track", + " if (!keys || keys.length === 0) {", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "process_params", + "libraryFrame": true, + "line": Object { + "context": " return done();", + "number": 335, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + " if (err) {", + " return next(layerError || err);", + " }", + "", + " if (route) {", + " return layer.handle_request(req, res, next);", + " }", + ], + "pre": Array [ + " // Capture one-time layer values", + " req.params = self.mergeParams", + " ? mergeParams(layer.params, parentParams)", + " : layer.params;", + " var layerPath = layer.path;", + "", + " // this should be done for the layer", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "next", + "libraryFrame": true, + "line": Object { + "context": " self.process_params(layer, paramcalled, req, res, function (err) {", + "number": 275, + }, + }, + Object { + "absPath": "/app/server.js", + "context": Object { + "post": Array [ + "})", + "", + "app.use(require('./server/coffee'))", + "app.use('/api', require('./server/routes'))", + "app.get('*', function (req, res) {", + " res.sendFile(path.resolve(__dirname, 'client/build', 'index.html'))", + "})", + ], + "pre": Array [ + "app.use(require('body-parser').json())", + "app.use(express.static('client/build'))", + "app.use(function (req, res, next) {", + " apm.setTag('foo', 'bar')", + " apm.setTag('lorem', 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.')", + " apm.setTag('this-is-a-very-long-tag-name-without-any-spaces', 'test')", + " apm.setTag('multi-line', 'foo\\\\nbar\\\\nbaz')", + ], + }, + "filename": "server.js", + "function": "", + "libraryFrame": false, + "line": Object { + "context": " next()", + "number": 27, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/layer.js", + "context": Object { + "post": Array [ + " } catch (err) {", + " next(err);", + " }", + "};", + "", + "/**", + " * Check if this route matches \`path\`, if so", + ], + "pre": Array [ + "", + " if (fn.length > 3) {", + " // not a standard request handler", + " return next();", + " }", + "", + " try {", + ], + }, + "filename": "node_modules/express/lib/router/layer.js", + "function": "handle", + "libraryFrame": true, + "line": Object { + "context": " fn(req, res, next);", + "number": 95, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + " }", + " }", + "};", + "", + "/**", + " * Process any parameters for the layer.", + " * @private", + ], + "pre": Array [ + " }", + "", + " debug('%s %s : %s', layer.name, layerPath, req.originalUrl);", + "", + " if (layerError) {", + " layer.handle_error(layerError, req, res, next);", + " } else {", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "trim_prefix", + "libraryFrame": true, + "line": Object { + "context": " layer.handle_request(req, res, next);", + "number": 317, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + " });", + " }", + "", + " function trim_prefix(layer, layerError, layerPath, path) {", + " if (layerPath.length !== 0) {", + " // Validate path breaks on a path separator", + " var c = path[layerPath.length]", + ], + "pre": Array [ + " return next(layerError || err);", + " }", + "", + " if (route) {", + " return layer.handle_request(req, res, next);", + " }", + "", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "", + "libraryFrame": true, + "line": Object { + "context": " trim_prefix(layer, layerError, layerPath, path);", + "number": 284, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + " }", + "", + " var i = 0;", + " var name;", + " var paramIndex = 0;", + " var key;", + " var paramVal;", + ], + "pre": Array [ + " var params = this.params;", + "", + " // captured parameters from the layer, keys and values", + " var keys = layer.keys;", + "", + " // fast track", + " if (!keys || keys.length === 0) {", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "process_params", + "libraryFrame": true, + "line": Object { + "context": " return done();", + "number": 335, + }, + }, + Object { + "absPath": "/app/node_modules/express/lib/router/index.js", + "context": Object { + "post": Array [ + " if (err) {", + " return next(layerError || err);", + " }", + "", + " if (route) {", + " return layer.handle_request(req, res, next);", + " }", + ], + "pre": Array [ + " // Capture one-time layer values", + " req.params = self.mergeParams", + " ? mergeParams(layer.params, parentParams)", + " : layer.params;", + " var layerPath = layer.path;", + "", + " // this should be done for the layer", + ], + }, + "filename": "node_modules/express/lib/router/index.js", + "function": "next", + "libraryFrame": true, + "line": Object { + "context": " self.process_params(layer, paramcalled, req, res, function (err) {", + "number": 275, + }, + }, + Object { + "absPath": "/app/node_modules/elastic-apm-node/lib/instrumentation/modules/express.js", + "context": Object { + "post": Array [ + " }", + " }", + " }", + " })", + "", + " return express", + "}", + ], + "pre": Array [ + " return function serveStatic (req, res, next) {", + " req._elastic_apm_static = true", + "", + " return origServeStatic(req, res, nextHook)", + "", + " function nextHook (err) {", + " if (!err) req._elastic_apm_static = false", + ], + }, + "filename": "node_modules/elastic-apm-node/lib/instrumentation/modules/express.js", + "function": "nextHook", + "libraryFrame": true, + "line": Object { + "context": " return next.apply(this, arguments)", + "number": 90, + }, + }, + Object { + "absPath": "/app/node_modules/serve-static/index.js", + "context": Object { + "post": Array [ + " })", + "", + " // pipe", + " stream.pipe(res)", + " }", + "}", + "", + ], + "pre": Array [ + " // forward errors", + " stream.on('error', function error (err) {", + " if (forwardError || !(err.statusCode < 500)) {", + " next(err)", + " return", + " }", + "", + ], + }, + "filename": "node_modules/serve-static/index.js", + "function": "error", + "libraryFrame": true, + "line": Object { + "context": " next()", + "number": 121, + }, + }, + Object { + "absPath": "events.js", + "filename": "events.js", + "function": "emitOne", + "libraryFrame": true, + "line": Object { + "number": 96, + }, + }, + Object { + "absPath": "events.js", + "filename": "events.js", + "function": "emit", + "libraryFrame": true, + "line": Object { + "number": 188, + }, + }, + Object { + "absPath": "/app/node_modules/send/index.js", + "context": Object { + "post": Array [ + " expose: false", + " }))", + " }", + "", + " var res = this.res", + " var msg = statuses[status] || String(status)", + " var doc = createHtmlDocument('Error', escapeHtml(msg))", + ], + "pre": Array [ + " * @param {Error} [err]", + " * @private", + " */", + "", + "SendStream.prototype.error = function error (status, err) {", + " // emit if listeners instead of responding", + " if (hasListeners(this, 'error')) {", + ], + }, + "filename": "node_modules/send/index.js", + "function": "error", + "libraryFrame": true, + "line": Object { + "context": " return this.emit('error', createError(status, err, {", + "number": 270, + }, + }, + Object { + "absPath": "/app/node_modules/send/index.js", + "context": Object { + "post": Array [ + " break", + " default:", + " this.error(500, error)", + " break", + " }", + "}", + "", + ], + "pre": Array [ + " */", + "", + "SendStream.prototype.onStatError = function onStatError (error) {", + " switch (error.code) {", + " case 'ENAMETOOLONG':", + " case 'ENOENT':", + " case 'ENOTDIR':", + ], + }, + "filename": "node_modules/send/index.js", + "function": "onStatError", + "libraryFrame": true, + "line": Object { + "context": " this.error(404, error)", + "number": 421, + }, + }, + Object { + "absPath": "/app/node_modules/send/index.js", + "context": Object { + "post": Array [ + " : self.error(404)", + " }", + "", + " var p = path + '.' + self._extensions[i++]", + "", + " debug('stat \\"%s\\"', p)", + " fs.stat(p, function (err, stat) {", + ], + "pre": Array [ + " self.emit('file', path, stat)", + " self.send(path, stat)", + " })", + "", + " function next (err) {", + " if (self._extensions.length <= i) {", + " return err", + ], + }, + "filename": "node_modules/send/index.js", + "function": "next", + "libraryFrame": true, + "line": Object { + "context": " ? self.onStatError(err)", + "number": 736, + }, + }, + Object { + "absPath": "/app/node_modules/send/index.js", + "context": Object { + "post": Array [ + " }", + " if (err) return self.onStatError(err)", + " if (stat.isDirectory()) return self.redirect(path)", + " self.emit('file', path, stat)", + " self.send(path, stat)", + " })", + "", + ], + "pre": Array [ + " var i = 0", + " var self = this", + "", + " debug('stat \\"%s\\"', path)", + " fs.stat(path, function onstat (err, stat) {", + " if (err && err.code === 'ENOENT' && !extname(path) && path[path.length - 1] !== sep) {", + " // not found, check extensions", + ], + }, + "filename": "node_modules/send/index.js", + "function": "onstat", + "libraryFrame": true, + "line": Object { + "context": " return next(err)", + "number": 725, + }, + }, + Object { + "absPath": "/app/node_modules/elastic-apm-node/lib/instrumentation/index.js", + "context": Object { + "post": Array [ + " ins.currentTransaction = prev", + " return result", + " }", + "}", + "", + "Instrumentation.prototype._recoverTransaction = function (trans) {", + " if (this.currentTransaction === trans) return", + ], + "pre": Array [ + " var trans = this.currentTransaction", + "", + " return elasticAPMCallbackWrapper", + "", + " function elasticAPMCallbackWrapper () {", + " var prev = ins.currentTransaction", + " ins.currentTransaction = trans", + ], + }, + "filename": "node_modules/elastic-apm-node/lib/instrumentation/index.js", + "function": "elasticAPMCallbackWrapper", + "libraryFrame": true, + "line": Object { + "context": " var result = original.apply(this, arguments)", + "number": 116, + }, + }, + Object { + "absPath": "fs.js", + "filename": "fs.js", + "function": "FSReqWrap.oncomplete", + "libraryFrame": true, + "line": Object { + "number": 123, + }, + }, + ] + } + /> + + `; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherFlyOut.js b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherFlyOut.js index 75110c5931a0f..d7fb8636baa3c 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherFlyOut.js +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/Watcher/WatcherFlyOut.js @@ -33,7 +33,7 @@ import { import { XPACK_DOCS } from '../../../../utils/documentation/xpack'; -import { KibanaLink } from '../../../../utils/url'; +import { UnconnectedKibanaLink } from '../../../../utils/url'; import { createErrorGroupWatch } from './createErrorGroupWatch'; import chrome from 'ui/chrome'; @@ -187,12 +187,13 @@ export default class WatcherFlyout extends Component {

The watch is now ready and will send error reports for{' '} {this.props.serviceName}.{' '} - View watch. - +

) }); @@ -419,6 +420,7 @@ export default class WatcherFlyout extends Component { } WatcherFlyout.propTypes = { + location: PropTypes.object.isRequired, isOpen: PropTypes.bool.isRequired, serviceName: PropTypes.string, onClose: PropTypes.func.isRequired diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/view.js b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/view.js index 9287028347b1d..0348ee5f711f0 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/view.js +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/view.js @@ -53,6 +53,7 @@ class ErrorGroupOverview extends Component { /> { return (
- - Response time distribution - - + +
+ Response time distribution{' '} + + + Sampling + + + Each bucket will show a sample transaction. If there's + no sample available, it's most likely because of the + sampling limit set in the agent configuration. + +
+ } + > + + + + + - - - - -

Span details

-
-
+ + + + + + +

Span details

+
+
- - - {`View span in Discover`} - - -
-
- - - - - - - - - - -
+ + + {`View span in Discover`} + + +
+
+ + + + + + + + + + + + ); } diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx index 82ef27823ca6e..4460404066fba 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx @@ -5,17 +5,16 @@ */ import { - EuiButton, EuiFlexGroup, EuiFlexItem, EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiHorizontalRule, + EuiPortal, EuiTitle } from '@elastic/eui'; import React from 'react'; -import { TransactionLink } from 'x-pack/plugins/apm/public/components/shared/TransactionLink'; import { IUrlParams } from 'x-pack/plugins/apm/public/store/urlParams'; import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; import { ActionMenu } from '../../../ActionMenu'; @@ -44,39 +43,33 @@ export function TransactionFlyout({ } return ( - - - - - -

Transaction details

-
-
+ + + + + + +

Transaction details

+
+
- - - - - - - - View transaction group details - - - -
-
- - - - - - - -
+ + + +
+
+ + + + + + + +
+ ); } diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/WaterfallItem.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/WaterfallItem.tsx index be831598aca9a..616b770944499 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/WaterfallItem.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/WaterfallContainer/Waterfall/WaterfallItem.tsx @@ -7,9 +7,12 @@ import React from 'react'; import styled from 'styled-components'; +import { EuiIcon } from '@elastic/eui'; import { colors, + fontFamily, fontFamilyCode, + fontSize, fontSizes, px, unit, @@ -17,29 +20,53 @@ import { } from '../../../../../../style/variables'; import { IWaterfallItem } from './waterfall_helpers/waterfall_helpers'; -const ItemBar = styled.div` +interface ItemBarProps { + type: 'transaction' | 'span'; + left: number; + width: number; + color: string; +} + +const ItemBar = styled('div')` + box-sizing: border-box; position: relative; - height: ${unit}px; + height: ${px(unit)}; + left: ${props => props.left}%; + width: ${props => props.width}%; + min-width: '2px'; + background-color: ${props => props.color}; `; -const ItemLabel = styled.div` + +const SpanLabel = styled<{ left: number }, any>('div')` white-space: nowrap; position: relative; - direction: rtl; + left: ${props => `${props.left}%`}; + width: ${props => `${100 - props.left}%`}; text-align: left; margin: ${px(units.quarter)} 0 0; font-family: ${fontFamilyCode}; font-size: ${fontSizes.small}; `; -const Container = styled< - { timelineMargins: TimelineMargins; isSelected: boolean }, - 'div' ->('div')` +const TransactionLabel = styled(SpanLabel)` + font-weight: 600; + font-family: ${fontFamily}; + font-size: ${fontSize}; +`; + +interface IContainerProps { + item: IWaterfallItem; + timelineMargins: ITimelineMargins; + isSelected: boolean; +} + +const Container = styled('div')` position: relative; display: block; user-select: none; padding: ${px(units.half)} ${props => px(props.timelineMargins.right)} - ${px(units.eighth)} ${props => px(props.timelineMargins.left)}; + ${props => px(props.item.docType === 'span' ? units.half : units.quarter)} + ${props => px(props.timelineMargins.left)}; border-top: 1px solid ${colors.gray4}; background-color: ${props => (props.isSelected ? colors.gray5 : 'initial')}; cursor: pointer; @@ -48,15 +75,15 @@ const Container = styled< } `; -interface TimelineMargins { +interface ITimelineMargins { right: number; left: number; top: number; bottom: number; } -interface Props { - timelineMargins: TimelineMargins; +interface IWaterfallItemProps { + timelineMargins: ITimelineMargins; totalDuration: number; item: IWaterfallItem; color: string; @@ -64,6 +91,18 @@ interface Props { onClick: () => any; } +function Prefix({ item }: { item: IWaterfallItem }) { + if (item.docType !== 'transaction') { + return null; + } + + return ( + + {' '} + + ); +} + export function WaterfallItem({ timelineMargins, totalDuration, @@ -71,34 +110,23 @@ export function WaterfallItem({ color, isSelected, onClick -}: Props) { +}: IWaterfallItemProps) { const width = (item.duration / totalDuration) * 100; const left = (item.offset / totalDuration) * 100; + const Label = item.docType === 'transaction' ? TransactionLabel : SpanLabel; return ( - - - ‎ + + + ); } diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx index 42eadcab397c4..76c8eada51a0d 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Transaction/index.tsx @@ -78,11 +78,11 @@ export const Transaction: React.SFC = ({ const root = waterfallRoot || transaction; return ( - + - Transaction sample +
Transaction sample
diff --git a/x-pack/plugins/apm/public/components/shared/StickyProperties/StickyProperties.test.js b/x-pack/plugins/apm/public/components/shared/StickyProperties/StickyProperties.test.js index b35069a44f219..4cc7d9b978a40 100644 --- a/x-pack/plugins/apm/public/components/shared/StickyProperties/StickyProperties.test.js +++ b/x-pack/plugins/apm/public/components/shared/StickyProperties/StickyProperties.test.js @@ -6,9 +6,9 @@ import React from 'react'; import { StickyProperties } from './index'; -import { mount } from 'enzyme'; +import { shallow } from 'enzyme'; import { USER_ID, REQUEST_URL_FULL } from '../../../../common/constants'; -import { toJson, mockMoment } from '../../../utils/testHelpers'; +import { mockMoment } from '../../../utils/testHelpers'; describe('StickyProperties', () => { beforeEach(mockMoment); @@ -43,10 +43,10 @@ describe('StickyProperties', () => { } ]; - const wrapper = mount( + const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/plugins/apm/public/components/shared/StickyProperties/__snapshots__/StickyProperties.test.js.snap b/x-pack/plugins/apm/public/components/shared/StickyProperties/__snapshots__/StickyProperties.test.js.snap index daefa91bbd80f..0788f6fd759a5 100644 --- a/x-pack/plugins/apm/public/components/shared/StickyProperties/__snapshots__/StickyProperties.test.js.snap +++ b/x-pack/plugins/apm/public/components/shared/StickyProperties/__snapshots__/StickyProperties.test.js.snap @@ -1,45 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`StickyProperties should render 1`] = ` -.c0 { - margin-bottom: 8px; - font-size: 12px; - color: #999999; -} - -.c0 span { - cursor: help; -} - -.c2 { - color: #999999; -} - -.c1 { - display: inline-block; - line-height: 16px; -} - -.c3 { - display: inline-block; - line-height: 16px; - max-width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -
-
-
- - Timestamp - -
-
- 1337 minutes ago (mocking 1536405447) - - + + @timestamp + + } + delay="regular" + position="top" > - ( - 1st of January (mocking 1536405447) - ) - -
-
-
+ Timestamp + + + + + + -
- + + context.request.url.full + + } + delay="regular" + position="top" > - URL - -
- + URL + + + + - https://www.elastic.co/test - -
-
+ https://www.elastic.co/test + + + + -
- + + context.request.method + + } + delay="regular" + position="top" > - Request method - -
-
+ + Request method + + + + GET -
-
-
+ + -
- + + error.exception.handled + + } + delay="regular" + position="top" > - Handled - -
-
+ + Handled + + + + true -
-
-
+ + -
- + + context.user.id + + } + delay="regular" + position="top" > - User ID - -
-
+ + User ID + + + + 1337 -
-
-
+ + +
`; diff --git a/x-pack/plugins/apm/public/components/shared/StickyProperties/index.js b/x-pack/plugins/apm/public/components/shared/StickyProperties/index.tsx similarity index 80% rename from x-pack/plugins/apm/public/components/shared/StickyProperties/index.js rename to x-pack/plugins/apm/public/components/shared/StickyProperties/index.tsx index 5a51ba338ad5a..c0ef09d8ae338 100644 --- a/x-pack/plugins/apm/public/components/shared/StickyProperties/index.js +++ b/x-pack/plugins/apm/public/components/shared/StickyProperties/index.tsx @@ -5,20 +5,28 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiToolTip } from '@elastic/eui'; +import moment from 'moment'; import React from 'react'; import styled from 'styled-components'; -import moment from 'moment'; -import TooltipOverlay from '../../shared/TooltipOverlay'; import { - unit, - units, - px, + colors, fontFamilyCode, fontSizes, - colors, - truncate + px, + truncate, + unit, + units } from '../../../style/variables'; +export interface IStickyProperty { + val: any; + label: string; + fieldName?: string; + width?: 0 | string; + truncated?: boolean; +} + const TooltipFieldName = styled.span` font-family: ${fontFamilyCode}; `; @@ -48,16 +56,7 @@ const PropertyValueTruncated = styled.span` ${truncate('100%')}; `; -function fieldNameHelper(name) { - return ( - - Field name:
- {name} -
- ); -} - -function TimestampValue({ timestamp }) { +function TimestampValue({ timestamp }: { timestamp: Date }) { const time = moment(timestamp); const timeAgo = timestamp ? time.fromNow() : 'N/A'; const timestampFull = timestamp @@ -71,13 +70,13 @@ function TimestampValue({ timestamp }) { ); } -function getPropertyLabel({ fieldName, label }) { +function getPropertyLabel({ fieldName, label }: Partial) { if (fieldName) { return ( - + {fieldName}}> {label} - + ); } @@ -85,23 +84,31 @@ function getPropertyLabel({ fieldName, label }) { return {label}; } -function getPropertyValue({ val, fieldName, truncated = false }) { +function getPropertyValue({ + val, + fieldName, + truncated = false +}: Partial) { if (fieldName === '@timestamp') { return ; } if (truncated) { return ( - + {String(val)} - + ); } return {val}; } -export function StickyProperties({ stickyProperties }) { +export function StickyProperties({ + stickyProperties +}: { + stickyProperties: IStickyProperty[]; +}) { /** * Note: the padding and margin styles here are strange because * EUI flex groups and items have a default "gutter" applied that diff --git a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.js b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.js index 72c77caba9a34..a4b93f6693e90 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.js +++ b/x-pack/plugins/apm/public/components/shared/charts/TransactionCharts/index.js @@ -5,11 +5,12 @@ */ import React, { Component } from 'react'; +import { EuiTitle } from '@elastic/eui'; import PropTypes from 'prop-types'; import CustomPlot from '../CustomPlot'; import { asMillis, tpmUnit, asInteger } from '../../../../utils/formatters'; import styled from 'styled-components'; -import { units, unit, px, fontSizes } from '../../../../style/variables'; +import { units, unit, px } from '../../../../style/variables'; import { timefilter } from 'ui/timefilter'; import moment from 'moment'; @@ -41,11 +42,6 @@ const ChartHeader = styled.div` margin-bottom: ${px(units.half)}; `; -const ChartTitle = styled.div` - font-weight: 600; - font-size: ${fontSizes.large}; -`; - export class Charts extends Component { state = { hoverX: null @@ -92,7 +88,9 @@ export class Charts extends Component { - {responseTimeLabel(transactionType)} + +
{responseTimeLabel(transactionType)}
+
{this.props.ChartHeaderContent}
- {tpmLabel(transactionType)} + +
{tpmLabel(transactionType)}
+
{ - if (!waterfall) { + if (!waterfall || !waterfall.hits) { return; } diff --git a/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap b/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap index c2f0bcbe6ff86..5edeb67d36828 100644 --- a/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap +++ b/x-pack/plugins/apm/public/utils/__test__/__snapshots__/url.test.js.snap @@ -1,27 +1,32 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`KibanaLinkComponent should render correct markup 1`] = ` +exports[`RelativeLinkComponent should render correct markup 1`] = ` Go to Discover `; -exports[`RelativeLinkComponent should render correct markup 1`] = ` +exports[`UnconnectedKibanaLink should render correct markup 1`] = ` Go to Discover `; exports[`ViewMLJob should render component 1`] = ` - View Job - + `; diff --git a/x-pack/plugins/apm/public/utils/__test__/url.test.js b/x-pack/plugins/apm/public/utils/__test__/url.test.js index b3617da276379..93daa86281b22 100644 --- a/x-pack/plugins/apm/public/utils/__test__/url.test.js +++ b/x-pack/plugins/apm/public/utils/__test__/url.test.js @@ -11,7 +11,7 @@ import createHistory from 'history/createMemoryHistory'; import { toQuery, fromQuery, - KibanaLinkComponent, + UnconnectedKibanaLink, RelativeLinkComponent, encodeKibanaSearchParams, decodeKibanaSearchParams, @@ -182,7 +182,7 @@ describe('RelativeLinkComponent', () => { }); }); -describe('KibanaLinkComponent', () => { +describe('UnconnectedKibanaLink', () => { let wrapper; beforeEach(() => { @@ -198,14 +198,14 @@ describe('KibanaLinkComponent', () => { }; wrapper = mount( - Go to Discover - + ); }); diff --git a/x-pack/plugins/apm/public/utils/url.tsx b/x-pack/plugins/apm/public/utils/url.tsx index f95c1298f92a0..018b5a270ae10 100644 --- a/x-pack/plugins/apm/public/utils/url.tsx +++ b/x-pack/plugins/apm/public/utils/url.tsx @@ -44,7 +44,8 @@ export function ViewMLJob({ }; return ( - = ({ location, pathname, hash, query = {}, ...props -}: KibanaLinkArgs) { +}) => { // Preserve current _g and _a const currentQuery = toQuery(location.search); const nextQuery = { @@ -186,14 +193,14 @@ export function KibanaLinkComponent({ }); return ; -} +}; const withLocation = connect( ({ location }: { location: any }) => ({ location }), {} ); export const RelativeLink = withLocation(RelativeLinkComponent); -export const KibanaLink = withLocation(KibanaLinkComponent); +export const KibanaLink = withLocation(UnconnectedKibanaLink); // This is downright horrible 😭 💔 // Angular decodes encoded url tokens like "%2F" to "/" which causes the route to change. diff --git a/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts b/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts index 78cb19c62701d..e3afa544fa0e1 100644 --- a/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts +++ b/x-pack/plugins/beats_management/server/lib/adapters/tags/elasticsearch_tags_adapter.ts @@ -106,9 +106,11 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter { } public async getTagsWithIds(user: FrameworkUser, tagIds: string[]) { + if (tagIds.length === 0) { + return []; + } const ids = tagIds.map(tag => `tag:${tag}`); - // TODO abstract to kibana adapter as the more generic getDocs const params = { _source: true, body: { @@ -142,7 +144,6 @@ export class ElasticsearchTagsAdapter implements CMTagsAdapter { }; const response = await this.database.index(user, params); - // TODO this is not something that works for TS... change this return type return get(response, 'result'); } } diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/semicircle.svg b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/semicircle.svg index 2b508adcca8a0..5538275825da7 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/semicircle.svg +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/semicircle.svg @@ -1,4 +1,4 @@ - + diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_bar.svg b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_bar.svg index 13544cbba2f10..9d9b20aa5198c 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_bar.svg +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_bar.svg @@ -1,5 +1,5 @@ - + diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_pill.svg b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_pill.svg index 20121506e48e2..f87b8a06f2ddf 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_pill.svg +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/progress/shapes/vertical_pill.svg @@ -1,5 +1,5 @@ - + diff --git a/x-pack/plugins/canvas/common/interpreter/create_error.js b/x-pack/plugins/canvas/common/interpreter/create_error.js new file mode 100644 index 0000000000000..5de9819330dbd --- /dev/null +++ b/x-pack/plugins/canvas/common/interpreter/create_error.js @@ -0,0 +1,13 @@ +/* + * 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 const createError = err => ({ + type: 'error', + error: { + stack: process.env.NODE_ENV === 'production' ? undefined : err.stack, + message: typeof err === 'string' ? err : err.message, + }, +}); diff --git a/x-pack/plugins/canvas/common/interpreter/interpret.js b/x-pack/plugins/canvas/common/interpreter/interpret.js index 2777e9d0b80ea..ff7a2547f236f 100644 --- a/x-pack/plugins/canvas/common/interpreter/interpret.js +++ b/x-pack/plugins/canvas/common/interpreter/interpret.js @@ -11,19 +11,7 @@ import { fromExpression } from '../lib/ast'; import { getByAlias } from '../lib/get_by_alias'; import { typesRegistry } from '../lib/types_registry'; import { castProvider } from './cast'; - -const createError = (err, { name, context, args }) => ({ - type: 'error', - error: { - stack: err.stack, - message: typeof err === 'string' ? err : err.message, - }, - info: { - context, - args, - functionName: name, - }, -}); +import { createError } from './create_error'; export function interpretProvider(config) { const { functions, onFunctionNotFound, types } = config; @@ -32,7 +20,7 @@ export function interpretProvider(config) { return interpret; - function interpret(node, context = null) { + async function interpret(node, context = null) { switch (getType(node)) { case 'expression': return invokeChain(node.chain, context); @@ -58,7 +46,11 @@ export function interpretProvider(config) { // in this case, it will try to execute the function in another context if (!fnDef) { chain.unshift(link); - return onFunctionNotFound({ type: 'expression', chain: chain }, context); + try { + return await onFunctionNotFound({ type: 'expression', chain: chain }, context); + } catch (e) { + return createError(e); + } } try { @@ -69,16 +61,15 @@ export function interpretProvider(config) { const newContext = await invokeFunction(fnDef, context, resolvedArgs); // if something failed, just return the failure - if (getType(newContext) === 'error') { - console.log('newContext error', newContext); - return newContext; - } + if (getType(newContext) === 'error') return newContext; // Continue re-invoking chain until it's empty return await invokeChain(chain, newContext); - } catch (err) { - console.error(`common/interpret ${fnName}: invokeChain rejected`, err); - return createError(err, { name: fnName, context, args: fnArgs }); + } catch (e) { + // Everything that throws from a function will hit this + // The interpreter should *never* fail. It should always return a `{type: error}` on failure + e.message = `[${fnName}] > ${e.message}`; + return createError(e); } } @@ -165,6 +156,7 @@ export function interpretProvider(config) { return argAsts.map(argAst => { return async (ctx = context) => { const newContext = await interpret(argAst, ctx); + // This is why when any sub-expression errors, the entire thing errors if (getType(newContext) === 'error') throw newContext.error; return cast(newContext, argDefs[argName].types); }; diff --git a/x-pack/plugins/canvas/common/interpreter/socket_interpret.js b/x-pack/plugins/canvas/common/interpreter/socket_interpret.js index a9ddb8c19c3f9..c8d5acf4fdd52 100644 --- a/x-pack/plugins/canvas/common/interpreter/socket_interpret.js +++ b/x-pack/plugins/canvas/common/interpreter/socket_interpret.js @@ -46,19 +46,14 @@ export function socketInterpreterProvider({ // set a unique message ID so the code knows what response to process const id = uuid(); - return new Promise((resolve, reject) => { + return new Promise(resolve => { const { serialize, deserialize } = serializeProvider(types); - const listener = resp => { - if (resp.error) { - // cast error strings back into error instances - const err = resp.error instanceof Error ? resp.error : new Error(resp.error); - if (resp.stack) err.stack = resp.stack; - reject(err); - } else { - resolve(deserialize(resp.value)); - } - }; + // This will receive {type: [msgSuccess || msgError] value: foo} + // However it doesn't currently do anything with it. Which means `value`, regardless + // of failure or success, needs to be something the interpreters would logically return + // er, a primative or a {type: foo} object + const listener = resp => resolve(deserialize(resp.value)); socket.once(`resp:${id}`, listener); diff --git a/x-pack/plugins/canvas/init.js b/x-pack/plugins/canvas/init.js index 1ae088ff207bc..315a1d7e7f6dd 100644 --- a/x-pack/plugins/canvas/init.js +++ b/x-pack/plugins/canvas/init.js @@ -7,13 +7,9 @@ import { routes } from './server/routes'; import { functionsRegistry } from './common/lib/functions_registry'; import { commonFunctions } from './common/functions'; -import { loadServerPlugins } from './server/lib/load_server_plugins'; +import { populateServerRegistries } from './server/lib/server_registries'; import { registerCanvasUsageCollector } from './server/usage'; -import { - ecommerceSavedObjects, - flightsSavedObjects, - webLogsSavedObjects, -} from './server/sample_data'; +import { loadSampleData } from './server/sample_data'; export default async function(server /*options*/) { server.injectUiAppVars('canvas', () => { @@ -34,30 +30,10 @@ export default async function(server /*options*/) { // There are some common functions that use private APIs, load them here commonFunctions.forEach(func => functionsRegistry.register(func)); - await loadServerPlugins(); - routes(server); registerCanvasUsageCollector(server); + loadSampleData(server); - const now = new Date(); - const nowTimestamp = now.toISOString(); - function updateCanvasWorkpadTimestamps(savedObjects) { - return savedObjects.map(savedObject => { - if (savedObject.type === 'canvas-workpad') { - savedObject.attributes['@timestamp'] = nowTimestamp; - savedObject.attributes['@created'] = nowTimestamp; - } - - return savedObject; - }); - } - - server.addSavedObjectsToSampleDataset( - 'ecommerce', - updateCanvasWorkpadTimestamps(ecommerceSavedObjects) - ); - server.addSavedObjectsToSampleDataset( - 'flights', - updateCanvasWorkpadTimestamps(flightsSavedObjects) - ); - server.addSavedObjectsToSampleDataset('logs', updateCanvasWorkpadTimestamps(webLogsSavedObjects)); + // Do not initialize the app until the registries are populated + await populateServerRegistries(['serverFunctions', 'types']); + routes(server); } diff --git a/x-pack/plugins/canvas/public/components/app/index.js b/x-pack/plugins/canvas/public/components/app/index.js index 2c4d1f6e9f808..f4ba53f096b53 100644 --- a/x-pack/plugins/canvas/public/components/app/index.js +++ b/x-pack/plugins/canvas/public/components/app/index.js @@ -8,6 +8,7 @@ import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; import { createSocket } from '../../socket'; import { initialize as initializeInterpreter } from '../../lib/interpreter'; +import { populateBrowserRegistries } from '../../lib/browser_registries'; import { getAppReady, getBasePath } from '../../state/selectors/app'; import { appReady, appError } from '../../state/actions/app'; import { trackRouteChange } from './track_route_change'; @@ -28,6 +29,7 @@ const mapDispatchToProps = dispatch => ({ setAppReady: basePath => async () => { // initialize the socket and interpreter createSocket(basePath); + await populateBrowserRegistries(); await initializeInterpreter(); // set app state to ready diff --git a/x-pack/plugins/canvas/public/components/error/error.js b/x-pack/plugins/canvas/public/components/error/error.js index c37780657ba29..bb1a895798b7d 100644 --- a/x-pack/plugins/canvas/public/components/error/error.js +++ b/x-pack/plugins/canvas/public/components/error/error.js @@ -11,7 +11,6 @@ import { get } from 'lodash'; import { ShowDebugging } from './show_debugging'; export const Error = ({ payload }) => { - const functionName = get(payload, 'info.functionName'); const message = get(payload, 'error.message'); return ( @@ -21,10 +20,7 @@ export const Error = ({ payload }) => { iconType="cross" title="Whoops! Expression failed" > -

- The function "{functionName}" failed - {message ? ' with the following message:' : '.'} -

+

{message ? 'Expression failed with the message:' : ''}

{message &&

{message}

} diff --git a/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss b/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss index dfc392163cb6a..0cfdcd29ecf94 100644 --- a/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss +++ b/x-pack/plugins/canvas/public/components/fullscreen/fullscreen.scss @@ -11,7 +11,8 @@ body.canvas-isFullscreen { } // hide all the interface parts - nav.global-nav, + nav.global-nav, // K6 global side nav + .header-global-wrapper, // K7 global top nav .canvasLayout__stageHeader, .canvasLayout__sidebar, .canvasLayout__footer, @@ -19,7 +20,13 @@ body.canvas-isFullscreen { display: none; } + // remove space for K7 global top nav + .header-global-wrapper + .app-wrapper { + top: 0; + } + .canvasLayout__stageContentOverflow { + display: block; // fixes center alignment for Safari overflow: visible; position: static; top: auto; diff --git a/x-pack/plugins/canvas/public/lib/browser_registries.js b/x-pack/plugins/canvas/public/lib/browser_registries.js new file mode 100644 index 0000000000000..efceec04d6dce --- /dev/null +++ b/x-pack/plugins/canvas/public/lib/browser_registries.js @@ -0,0 +1,74 @@ +/* + * 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 chrome from 'ui/chrome'; +import $script from 'scriptjs'; +import { typesRegistry } from '../../common/lib/types_registry'; +import { + argTypeRegistry, + datasourceRegistry, + transformRegistry, + modelRegistry, + viewRegistry, +} from '../expression_types'; +import { elementsRegistry } from './elements_registry'; +import { renderFunctionsRegistry } from './render_functions_registry'; +import { functionsRegistry as browserFunctions } from './functions_registry'; +import { loadPrivateBrowserFunctions } from './load_private_browser_functions'; + +const registries = { + browserFunctions: browserFunctions, + commonFunctions: browserFunctions, + elements: elementsRegistry, + types: typesRegistry, + renderers: renderFunctionsRegistry, + transformUIs: transformRegistry, + datasourceUIs: datasourceRegistry, + modelUIs: modelRegistry, + viewUIs: viewRegistry, + argumentUIs: argTypeRegistry, +}; + +let resolve = null; +let called = false; + +const populatePromise = new Promise(_resolve => { + resolve = _resolve; +}); + +export const getBrowserRegistries = () => { + return populatePromise; +}; + +export const populateBrowserRegistries = () => { + if (called) throw new Error('function should only be called once per process'); + called = true; + + // loadPrivateBrowserFunctions is sync. No biggie. + loadPrivateBrowserFunctions(); + + const remainingTypes = Object.keys(registries); + const populatedTypes = {}; + + function loadType() { + const type = remainingTypes.pop(); + window.canvas = window.canvas || {}; + window.canvas.register = d => registries[type].register(d); + + // Load plugins one at a time because each needs a different loader function + // $script will only load each of these once, we so can call this as many times as we need? + const pluginPath = chrome.addBasePath(`/api/canvas/plugins?type=${type}`); + $script(pluginPath, () => { + populatedTypes[type] = registries[type]; + + if (remainingTypes.length) loadType(); + else resolve(populatedTypes); + }); + } + + if (remainingTypes.length) loadType(); + return populatePromise; +}; diff --git a/x-pack/plugins/canvas/public/lib/interpreter.js b/x-pack/plugins/canvas/public/lib/interpreter.js index 0809046c8a0cb..36878871b8b15 100644 --- a/x-pack/plugins/canvas/public/lib/interpreter.js +++ b/x-pack/plugins/canvas/public/lib/interpreter.js @@ -10,10 +10,11 @@ import { getSocket } from '../socket'; import { typesRegistry } from '../../common/lib/types_registry'; import { createHandlers } from './create_handlers'; import { functionsRegistry } from './functions_registry'; -import { loadBrowserPlugins } from './load_browser_plugins'; +import { getBrowserRegistries } from './browser_registries'; let socket; -let functionList; +let resolve; +const functionList = new Promise(_resolve => (resolve = _resolve)); export async function initialize() { socket = getSocket(); @@ -29,14 +30,14 @@ export async function initialize() { // Create the function list socket.emit('getFunctionList'); - functionList = new Promise(resolve => socket.once('functionList', resolve)); + socket.once('functionList', resolve); return functionList; } // Use the above promise to seed the interpreter with the functions it can defer to export async function interpretAst(ast, context) { // Load plugins before attempting to get functions, otherwise this gets racey - return Promise.all([functionList, loadBrowserPlugins()]) + return Promise.all([functionList, getBrowserRegistries()]) .then(([serverFunctionList]) => { return socketInterpreterProvider({ types: typesRegistry.toJS(), diff --git a/x-pack/plugins/canvas/public/lib/load_browser_plugins.js b/x-pack/plugins/canvas/public/lib/load_browser_plugins.js deleted file mode 100644 index 8f1f5b2e90894..0000000000000 --- a/x-pack/plugins/canvas/public/lib/load_browser_plugins.js +++ /dev/null @@ -1,53 +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 chrome from 'ui/chrome'; -import $script from 'scriptjs'; -import { typesRegistry } from '../../common/lib/types_registry'; -import { - argTypeRegistry, - datasourceRegistry, - transformRegistry, - modelRegistry, - viewRegistry, -} from '../expression_types'; -import { elementsRegistry } from './elements_registry'; -import { renderFunctionsRegistry } from './render_functions_registry'; -import { functionsRegistry as browserFunctions } from './functions_registry'; -import { loadPrivateBrowserFunctions } from './load_private_browser_functions'; - -const types = { - browserFunctions: browserFunctions, - commonFunctions: browserFunctions, - elements: elementsRegistry, - types: typesRegistry, - renderers: renderFunctionsRegistry, - transformUIs: transformRegistry, - datasourceUIs: datasourceRegistry, - modelUIs: modelRegistry, - viewUIs: viewRegistry, - argumentUIs: argTypeRegistry, -}; - -export const loadBrowserPlugins = () => - new Promise(resolve => { - loadPrivateBrowserFunctions(); - const remainingTypes = Object.keys(types); - function loadType() { - const type = remainingTypes.pop(); - window.canvas = window.canvas || {}; - window.canvas.register = d => types[type].register(d); - // Load plugins one at a time because each needs a different loader function - // $script will only load each of these once, we so can call this as many times as we need? - const pluginPath = chrome.addBasePath(`/api/canvas/plugins?type=${type}`); - $script(pluginPath, () => { - if (remainingTypes.length) loadType(); - else resolve(true); - }); - } - - loadType(); - }); diff --git a/x-pack/plugins/canvas/public/socket.js b/x-pack/plugins/canvas/public/socket.js index a96320c8e0f7e..08cd0e017ce9f 100644 --- a/x-pack/plugins/canvas/public/socket.js +++ b/x-pack/plugins/canvas/public/socket.js @@ -6,7 +6,7 @@ import io from 'socket.io-client'; import { functionsRegistry } from '../common/lib/functions_registry'; -import { loadBrowserPlugins } from './lib/load_browser_plugins'; +import { getBrowserRegistries } from './lib/browser_registries'; let socket; @@ -14,7 +14,7 @@ export function createSocket(basePath) { socket = io(undefined, { path: `${basePath}/socket.io` }); socket.on('getFunctionList', () => { - const pluginsLoaded = loadBrowserPlugins(); + const pluginsLoaded = getBrowserRegistries(); pluginsLoaded.then(() => socket.emit('functionList', functionsRegistry.toJS())); }); diff --git a/x-pack/plugins/canvas/server/lib/get_plugin_stream.js b/x-pack/plugins/canvas/server/lib/get_plugin_stream.js index 51f3d234afdb1..6a08e2beeff8e 100644 --- a/x-pack/plugins/canvas/server/lib/get_plugin_stream.js +++ b/x-pack/plugins/canvas/server/lib/get_plugin_stream.js @@ -9,7 +9,9 @@ import ss from 'stream-stream'; import { getPluginPaths } from './get_plugin_paths'; export const getPluginStream = type => { - const stream = ss(); + const stream = ss({ + separator: '\n', + }); getPluginPaths(type).then(files => { files.forEach(file => { diff --git a/x-pack/plugins/canvas/server/lib/route_expression/browser.js b/x-pack/plugins/canvas/server/lib/route_expression/browser.js new file mode 100644 index 0000000000000..feae107873ac6 --- /dev/null +++ b/x-pack/plugins/canvas/server/lib/route_expression/browser.js @@ -0,0 +1,44 @@ +/* + * 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 uuid from 'uuid/v4'; + +export const browser = ({ socket, serialize, deserialize }) => { + // Note that we need to be careful about how many times routeExpressionProvider is called, because of the socket.once below. + // It's too bad we can't get a list of browser plugins on the server + const getClientFunctions = new Promise(resolve => { + socket.emit('getFunctionList'); + socket.once('functionList', resolve); + }); + + return getClientFunctions.then(functions => { + return { + interpret: (ast, context) => { + return new Promise((resolve, reject) => { + const id = uuid(); + const listener = resp => { + if (resp.type === 'msgError') { + const { value } = resp; + // cast error strings back into error instances + const err = value instanceof Error ? value : new Error(value); + if (value.stack) err.stack = value.stack; + // Reject's with a legit error. Check! Environments should always reject with an error when something bad happens + reject(err); + } else { + resolve(deserialize(resp.value)); + } + }; + + // {type: msgSuccess or msgError, value: foo}. Doesn't matter if it's success or error, we do the same thing for now + socket.once(`resp:${id}`, listener); + + socket.emit('run', { ast, context: serialize(context), id }); + }); + }, + getFunctions: () => Object.keys(functions), + }; + }); +}; diff --git a/x-pack/plugins/canvas/server/lib/route_expression/index.js b/x-pack/plugins/canvas/server/lib/route_expression/index.js new file mode 100644 index 0000000000000..3533b55687246 --- /dev/null +++ b/x-pack/plugins/canvas/server/lib/route_expression/index.js @@ -0,0 +1,32 @@ +/* + * 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 { createError } from '../../../common/interpreter/create_error'; + +export const routeExpressionProvider = environments => { + async function routeExpression(ast, context = null) { + // List of environments in order of preference + + return Promise.all(environments).then(environments => { + const environmentFunctions = environments.map(env => env.getFunctions()); + + // Grab name of the first function in the chain + const fnName = ast.chain[0].function.toLowerCase(); + + // Check each environment for that function + for (let i = 0; i < environmentFunctions.length; i++) { + if (environmentFunctions[i].includes(fnName)) { + // If we find it, run in that environment, and only that environment + return environments[i].interpret(ast, context).catch(e => createError(e)); + } + } + + // If the function isn't found in any environment, give up + throw new Error(`Function not found: [${fnName}]`); + }); + } + + return routeExpression; +}; diff --git a/x-pack/plugins/canvas/server/lib/route_expression/server.js b/x-pack/plugins/canvas/server/lib/route_expression/server.js new file mode 100644 index 0000000000000..94b8484ab5764 --- /dev/null +++ b/x-pack/plugins/canvas/server/lib/route_expression/server.js @@ -0,0 +1,33 @@ +/* + * 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 { getServerRegistries } from '../server_registries'; +import { interpretProvider } from '../../../common/interpreter/interpret'; +import { createHandlers } from '../create_handlers'; +import { getRequest } from '../../lib/get_request'; + +export const server = ({ onFunctionNotFound, server, socket }) => { + const pluginsReady = getServerRegistries(['serverFunctions', 'types']); + + return Promise.all([pluginsReady, getRequest(server, socket.handshake)]).then( + ([{ serverFunctions, types }, request]) => { + // 'request' is the modified hapi request object + return { + interpret: (ast, context) => { + const interpret = interpretProvider({ + types: types.toJS(), + functions: serverFunctions.toJS(), + handlers: createHandlers(request, server), + onFunctionNotFound, + }); + + return interpret(ast, context); + }, + getFunctions: () => Object.keys(serverFunctions.toJS()), + }; + } + ); +}; diff --git a/x-pack/plugins/canvas/server/lib/route_expression/thread/babeled.js b/x-pack/plugins/canvas/server/lib/route_expression/thread/babeled.js new file mode 100644 index 0000000000000..da33b0ae29f6c --- /dev/null +++ b/x-pack/plugins/canvas/server/lib/route_expression/thread/babeled.js @@ -0,0 +1,9 @@ +/* + * 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. + */ + +// The babel-register below uses .babelrc by default. +require('babel-register'); +require('./worker'); diff --git a/x-pack/plugins/canvas/server/lib/route_expression/thread/index.js b/x-pack/plugins/canvas/server/lib/route_expression/thread/index.js new file mode 100644 index 0000000000000..d3748db02f65c --- /dev/null +++ b/x-pack/plugins/canvas/server/lib/route_expression/thread/index.js @@ -0,0 +1,98 @@ +/* + * 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 { fork } from 'child_process'; +import { resolve } from 'path'; +import uuid from 'uuid/v4'; + +// If the worker doesn't response in 10s, kill it. +const WORKER_TIMEOUT = 20000; +const workerPath = resolve(__dirname, 'babeled.js'); +const heap = {}; +let worker = null; + +export function getWorker() { + if (worker) return worker; + worker = fork(workerPath, {}); + + // 'exit' happens whether we kill the worker or it just dies. + // No need to look for 'error', our worker is intended to be long lived so it isn't running, it's an issue + worker.on('exit', () => { + // Heads up: there is no worker.off + worker = null; + // Restart immediately on exit since node takes a couple seconds to spin up + worker = getWorker(); + }); + + worker.on('message', msg => { + const { type, value, id } = msg; + if (type === 'run') { + const { threadId } = msg; + const { ast, context } = value; + heap[threadId] + .onFunctionNotFound(ast, context) + .then(value => { + worker.send({ type: 'msgSuccess', id, value: value }); + }) + .catch(e => heap[threadId].reject(e)); + } + + if (type === 'msgSuccess' && heap[id]) heap[id].resolve(value); + + // TODO: I don't think it is even possible to hit this + if (type === 'msgError' && heap[id]) heap[id].reject(new Error(value)); + }); + + return worker; +} + +// All serialize/deserialize must occur in here. We should not return serialized stuff to the expressionRouter +export const thread = ({ onFunctionNotFound, serialize, deserialize }) => { + const getWorkerFunctions = new Promise(resolve => { + const worker = getWorker(); + worker.send({ type: 'getFunctions' }); + worker.on('message', msg => { + if (msg.type === 'functionList') resolve(msg.value); + }); + }); + + return getWorkerFunctions.then(functions => { + return { + interpret: (ast, context) => { + const worker = getWorker(); + const id = uuid(); + worker.send({ type: 'run', id, value: { ast, context: serialize(context) } }); + + return new Promise((resolve, reject) => { + heap[id] = { + time: new Date().getTime(), + resolve: value => { + delete heap[id]; + resolve(deserialize(value)); + }, + reject: e => { + delete heap[id]; + reject(e); + }, + onFunctionNotFound: (ast, context) => + onFunctionNotFound(ast, deserialize(context)).then(serialize), + }; + + // + setTimeout(() => { + if (!heap[id]) return; // Looks like this has already been cleared from the heap. + if (worker) worker.kill(); + + // The heap will be cleared because the reject on heap will delete its own id + heap[id].reject(new Error('Request timed out')); + }, WORKER_TIMEOUT); + }); + }, + + getFunctions: () => functions, + }; + }); +}; diff --git a/x-pack/plugins/canvas/server/lib/route_expression/thread/worker.js b/x-pack/plugins/canvas/server/lib/route_expression/thread/worker.js new file mode 100644 index 0000000000000..d81df410f7af7 --- /dev/null +++ b/x-pack/plugins/canvas/server/lib/route_expression/thread/worker.js @@ -0,0 +1,68 @@ +/* + * 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 uuid from 'uuid/v4'; +import { populateServerRegistries } from '../../server_registries'; +import { interpretProvider } from '../../../../common/interpreter/interpret'; +import { serializeProvider } from '../../../../common/lib/serialize'; + +// We actually DO need populateServerRegistries here since this is a different node process +const pluginsReady = populateServerRegistries(['commonFunctions', 'types']); +const heap = {}; + +process.on('message', msg => { + const { type, id, value } = msg; + const threadId = id; + + pluginsReady.then(({ commonFunctions, types }) => { + types = types.toJS(); + const { serialize, deserialize } = serializeProvider(types); + const interpret = interpretProvider({ + types, + functions: commonFunctions.toJS(), + handlers: { environment: 'serverThreaded' }, + onFunctionNotFound: (ast, context) => { + const id = uuid(); + // This needs to send a message to the main thread, and receive a response. Uhg. + process.send({ + type: 'run', + threadId, + id, + value: { + ast, + context: serialize(context), + }, + }); + + // Note that there is no facility to reject here. That's because this would only occur as the result of something that happens in the main thread, and we reject there + return new Promise(resolve => { + heap[id] = { resolve }; + }); + }, + }); + + if (type === 'getFunctions') + process.send({ type: 'functionList', value: Object.keys(commonFunctions.toJS()) }); + + if (type === 'msgSuccess') { + heap[id].resolve(deserialize(value)); + delete heap[id]; + } + + if (type === 'run') { + const { ast, context } = msg.value; + + interpret(ast, deserialize(context)) + .then(value => { + process.send({ type: 'msgSuccess', value: serialize(value), id }); + }) + // TODO: I don't think it is even possible to hit this + .catch(value => { + process.send({ type: 'msgError', value, id }); + }); + } + }); +}); diff --git a/x-pack/plugins/canvas/server/lib/load_server_plugins.js b/x-pack/plugins/canvas/server/lib/server_registries.js similarity index 55% rename from x-pack/plugins/canvas/server/lib/load_server_plugins.js rename to x-pack/plugins/canvas/server/lib/server_registries.js index 0373261e96067..cff63a1138ea3 100644 --- a/x-pack/plugins/canvas/server/lib/load_server_plugins.js +++ b/x-pack/plugins/canvas/server/lib/server_registries.js @@ -8,32 +8,48 @@ import { typesRegistry } from '../../common/lib/types_registry'; import { functionsRegistry as serverFunctions } from '../../common/lib/functions_registry'; import { getPluginPaths } from './get_plugin_paths'; -const types = { +const registries = { serverFunctions: serverFunctions, commonFunctions: serverFunctions, types: typesRegistry, }; -const loaded = new Promise(resolve => { - const remainingTypes = Object.keys(types); +let resolve = null; +let called = false; + +const populatePromise = new Promise(_resolve => { + resolve = _resolve; +}); + +export const getServerRegistries = () => { + return populatePromise; +}; + +export const populateServerRegistries = types => { + if (called) throw new Error('function should only be called once per process'); + called = true; + if (!types || !types.length) throw new Error('types is required'); + + const remainingTypes = types; + const populatedTypes = {}; const loadType = () => { const type = remainingTypes.pop(); getPluginPaths(type).then(paths => { global.canvas = global.canvas || {}; - global.canvas.register = d => types[type].register(d); + global.canvas.register = d => registries[type].register(d); paths.forEach(path => { require(path); }); global.canvas = undefined; + populatedTypes[type] = registries[type]; if (remainingTypes.length) loadType(); - else resolve(true); + else resolve(populatedTypes); }); }; - loadType(); -}); - -export const loadServerPlugins = () => loaded; + if (remainingTypes.length) loadType(); + return populatePromise; +}; diff --git a/x-pack/plugins/canvas/server/routes/socket.js b/x-pack/plugins/canvas/server/routes/socket.js index dd8a3f1440ea8..d0244e2a85537 100644 --- a/x-pack/plugins/canvas/server/routes/socket.js +++ b/x-pack/plugins/canvas/server/routes/socket.js @@ -5,51 +5,45 @@ */ import socket from 'socket.io'; -import { createHandlers } from '../lib/create_handlers'; -import { socketInterpreterProvider } from '../../common/interpreter/socket_interpret'; import { serializeProvider } from '../../common/lib/serialize'; -import { functionsRegistry } from '../../common/lib/functions_registry'; import { typesRegistry } from '../../common/lib/types_registry'; -import { loadServerPlugins } from '../lib/load_server_plugins'; -import { getRequest } from '../lib/get_request'; +import { getServerRegistries } from '../lib/server_registries'; +import { routeExpressionProvider } from '../lib/route_expression'; +import { browser } from '../lib/route_expression/browser'; +import { thread } from '../lib/route_expression/thread'; +import { server as serverEnv } from '../lib/route_expression/server'; export function socketApi(server) { const io = socket(server.listener, { path: '/socket.io' }); io.on('connection', socket => { - // Create the function list - socket.emit('getFunctionList'); - const getClientFunctions = new Promise(resolve => socket.once('functionList', resolve)); + const types = typesRegistry.toJS(); + const { serialize, deserialize } = serializeProvider(types); + + // I'd love to find a way to generalize all of these, but they each need a different set of things + // Note that ORDER MATTERS here. The environments will be tried in this order. Do not reorder this array. + const routeExpression = routeExpressionProvider([ + thread({ onFunctionNotFound, serialize, deserialize }), + serverEnv({ onFunctionNotFound, socket, server }), + browser({ onFunctionNotFound, socket, serialize, deserialize }), + ]); + + function onFunctionNotFound(ast, context) { + return routeExpression(ast, context); + } socket.on('getFunctionList', () => { - loadServerPlugins().then(() => socket.emit('functionList', functionsRegistry.toJS())); + getServerRegistries().then(({ serverFunctions }) => + socket.emit('functionList', serverFunctions.toJS()) + ); }); const handler = ({ ast, context, id }) => { - Promise.all([getClientFunctions, getRequest(server, socket.handshake)]).then( - ([clientFunctions, request]) => { - // request is the modified hapi request object - const types = typesRegistry.toJS(); - const interpret = socketInterpreterProvider({ - types, - functions: functionsRegistry.toJS(), - handlers: createHandlers(request, server), - referableFunctions: clientFunctions, - socket: socket, - }); - - const { serialize, deserialize } = serializeProvider(types); - return interpret(ast, deserialize(context)) - .then(value => { - socket.emit(`resp:${id}`, { value: serialize(value) }); - }) - .catch(e => { - socket.emit(`resp:${id}`, { - error: e.message, - stack: e.stack, - }); - }); - } + return ( + routeExpression(ast, deserialize(context)) + .then(value => socket.emit(`resp:${id}`, { type: 'msgSuccess', value: serialize(value) })) + // TODO: I don't think it is possible to hit this right now? Maybe ever? + .catch(e => socket.emit(`resp:${id}`, { type: 'msgError', value: e })) ); }; diff --git a/x-pack/plugins/canvas/server/sample_data/index.js b/x-pack/plugins/canvas/server/sample_data/index.js index 66438986c3108..212d9f5132831 100644 --- a/x-pack/plugins/canvas/server/sample_data/index.js +++ b/x-pack/plugins/canvas/server/sample_data/index.js @@ -7,5 +7,6 @@ import ecommerceSavedObjects from './ecommerce_saved_objects.json'; import flightsSavedObjects from './flights_saved_objects.json'; import webLogsSavedObjects from './web_logs_saved_objects.json'; +import { loadSampleData } from './load_sample_data'; -export { ecommerceSavedObjects, flightsSavedObjects, webLogsSavedObjects }; +export { loadSampleData, ecommerceSavedObjects, flightsSavedObjects, webLogsSavedObjects }; diff --git a/x-pack/plugins/canvas/server/sample_data/load_sample_data.js b/x-pack/plugins/canvas/server/sample_data/load_sample_data.js new file mode 100644 index 0000000000000..f2f462ed168d6 --- /dev/null +++ b/x-pack/plugins/canvas/server/sample_data/load_sample_data.js @@ -0,0 +1,32 @@ +/* + * 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 { ecommerceSavedObjects, flightsSavedObjects, webLogsSavedObjects } from './index'; + +export function loadSampleData(server) { + const now = new Date(); + const nowTimestamp = now.toISOString(); + function updateCanvasWorkpadTimestamps(savedObjects) { + return savedObjects.map(savedObject => { + if (savedObject.type === 'canvas-workpad') { + savedObject.attributes['@timestamp'] = nowTimestamp; + savedObject.attributes['@created'] = nowTimestamp; + } + + return savedObject; + }); + } + + server.addSavedObjectsToSampleDataset( + 'ecommerce', + updateCanvasWorkpadTimestamps(ecommerceSavedObjects) + ); + server.addSavedObjectsToSampleDataset( + 'flights', + updateCanvasWorkpadTimestamps(flightsSavedObjects) + ); + server.addSavedObjectsToSampleDataset('logs', updateCanvasWorkpadTimestamps(webLogsSavedObjects)); +} diff --git a/x-pack/plugins/ml/public/components/json_tooltip/tooltips.json b/x-pack/plugins/ml/public/components/json_tooltip/tooltips.json index 35b90c9ff073b..84a9f2cc27912 100644 --- a/x-pack/plugins/ml/public/components/json_tooltip/tooltips.json +++ b/x-pack/plugins/ml/public/components/json_tooltip/tooltips.json @@ -72,7 +72,7 @@ "text": "Character used to encapsulate values containing reserved characters." }, "new_job_enable_datafeed_job": { - "text": "Required for jobs that analyze data from Elasticsearch.\nRequires data format to be set to Elasticsearch/" + "text": "Required for jobs that analyze data from Elasticsearch." }, "new_job_data_source": { "text": "Elasticsearch versions 1.7.x and 2+ supported." diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/about_panel/welcome_content.js b/x-pack/plugins/ml/public/file_datavisualizer/components/about_panel/welcome_content.js index 78f89f951f28f..59245ac6c8df2 100644 --- a/x-pack/plugins/ml/public/file_datavisualizer/components/about_panel/welcome_content.js +++ b/x-pack/plugins/ml/public/file_datavisualizer/components/about_panel/welcome_content.js @@ -56,7 +56,7 @@ export function WelcomeContent() {

- JSON + Delimited text files, such as CSV and TSV

@@ -69,7 +69,7 @@ export function WelcomeContent() {

- Delimited text files, such as CSV and TSV + Newline-delimited JSON

diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/edit_flyout/__snapshots__/overrides.test.js.snap b/x-pack/plugins/ml/public/file_datavisualizer/components/edit_flyout/__snapshots__/overrides.test.js.snap index e1697b865ce11..c11317ccad9eb 100644 --- a/x-pack/plugins/ml/public/file_datavisualizer/components/edit_flyout/__snapshots__/overrides.test.js.snap +++ b/x-pack/plugins/ml/public/file_datavisualizer/components/edit_flyout/__snapshots__/overrides.test.js.snap @@ -14,15 +14,15 @@ exports[`Overrides render overrides 1`] = ` Array [ Object { "inputDisplay": - json + delimited , - "value": "json", + "value": "delimited", }, Object { "inputDisplay": - delimited + ndjson , - "value": "delimited", + "value": "ndjson", }, Object { "inputDisplay": diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/edit_flyout/options/option_lists.js b/x-pack/plugins/ml/public/file_datavisualizer/components/edit_flyout/options/option_lists.js index 8918850c3f974..7534790de0a84 100644 --- a/x-pack/plugins/ml/public/file_datavisualizer/components/edit_flyout/options/option_lists.js +++ b/x-pack/plugins/ml/public/file_datavisualizer/components/edit_flyout/options/option_lists.js @@ -6,8 +6,8 @@ export const FORMAT_OPTIONS = [ - 'json', 'delimited', + 'ndjson', 'semi_structured_text', 'xml', ]; diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/utils.js b/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/utils.js index cb82f6f4b1910..e97c16b629b8f 100644 --- a/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/utils.js +++ b/x-pack/plugins/ml/public/file_datavisualizer/components/file_datavisualizer_view/utils.js @@ -65,7 +65,7 @@ export function createUrlOverrides(overrides, originalSettings) { } } - if (formattedOverrides.format === 'json' || originalSettings.format === 'json') { + if (formattedOverrides.format === 'ndjson' || originalSettings.format === 'ndjson') { formattedOverrides.should_trim_fields = ''; formattedOverrides.has_header_row = ''; formattedOverrides.delimiter = ''; diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/import_view/importer/importer_factory.js b/x-pack/plugins/ml/public/file_datavisualizer/components/import_view/importer/importer_factory.js index e2bf8f5707bec..c82d94696ac5e 100644 --- a/x-pack/plugins/ml/public/file_datavisualizer/components/import_view/importer/importer_factory.js +++ b/x-pack/plugins/ml/public/file_datavisualizer/components/import_view/importer/importer_factory.js @@ -7,7 +7,7 @@ import { CsvImporter } from './csv_importer'; import { SstImporter } from './sst_importer'; -import { JsonImporter } from './json_importer'; +import { NdjsonImporter } from './ndjson_importer'; export function importerFactory(format, results, settings) { @@ -16,8 +16,8 @@ export function importerFactory(format, results, settings) { return new CsvImporter(results, settings); case 'semi_structured_text': return new SstImporter(results, settings); - case 'json': - return new JsonImporter(results, settings); + case 'ndjson': + return new NdjsonImporter(results, settings); default: return; } diff --git a/x-pack/plugins/ml/public/file_datavisualizer/components/import_view/importer/json_importer.js b/x-pack/plugins/ml/public/file_datavisualizer/components/import_view/importer/ndjson_importer.js similarity index 94% rename from x-pack/plugins/ml/public/file_datavisualizer/components/import_view/importer/json_importer.js rename to x-pack/plugins/ml/public/file_datavisualizer/components/import_view/importer/ndjson_importer.js index 4fdfbf8d8f002..5f1c834b03af7 100644 --- a/x-pack/plugins/ml/public/file_datavisualizer/components/import_view/importer/json_importer.js +++ b/x-pack/plugins/ml/public/file_datavisualizer/components/import_view/importer/ndjson_importer.js @@ -7,7 +7,7 @@ import { Importer } from './importer'; -export class JsonImporter extends Importer { +export class NdjsonImporter extends Importer { constructor(results, settings) { super(settings); } diff --git a/x-pack/plugins/security/public/components/management/users/confirm_delete.js b/x-pack/plugins/security/public/components/management/users/confirm_delete.js index 53d9bd3bed127..984c84bfafa9b 100644 --- a/x-pack/plugins/security/public/components/management/users/confirm_delete.js +++ b/x-pack/plugins/security/public/components/management/users/confirm_delete.js @@ -7,17 +7,29 @@ import React, { Component, Fragment } from 'react'; import { EuiOverlayMask, EuiConfirmModal } from '@elastic/eui'; import { toastNotifications } from 'ui/notify'; -export class ConfirmDelete extends Component { +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; + +class ConfirmDeleteUI extends Component { deleteUsers = () => { const { usersToDelete, apiClient, callback } = this.props; const errors = []; usersToDelete.forEach(async username => { try { await apiClient.deleteUser(username); - toastNotifications.addSuccess(`Deleted user ${username}`); + toastNotifications.addSuccess( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.confirmDelete.userSuccessfullyDeletedNotificationMessage", + defaultMessage: "Deleted user {username}" + }, { username }) + ); } catch (e) { errors.push(username); - toastNotifications.addDanger(`Error deleting user ${username}`); + toastNotifications.addDanger( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.confirmDelete.userDeletingErrorNotificationMessage", + defaultMessage: "Error deleting user {username}" + }, { username }) + ); } if (callback) { callback(usersToDelete, errors); @@ -25,34 +37,56 @@ export class ConfirmDelete extends Component { }); }; render() { - const { usersToDelete, onCancel } = this.props; + const { usersToDelete, onCancel, intl } = this.props; const moreThanOne = usersToDelete.length > 1; const title = moreThanOne - ? `Delete ${usersToDelete.length} users` - : `Delete user '${usersToDelete[0]}'`; + ? intl.formatMessage({ + id: "xpack.security.management.users.confirmDelete.deleteMultipleUsersTitle", + defaultMessage: "Delete {userLength} users" + }, { userLength: usersToDelete.length }) + : intl.formatMessage({ + id: "xpack.security.management.users.confirmDelete.deleteOneUserTitle", + defaultMessage: "Delete user {userLength}" + }, { userLength: usersToDelete[0] }); return (
{moreThanOne ? (

- You are about to delete these users: +

    {usersToDelete.map(username =>
  • {username}
  • )}
) : null} -

This operation cannot be undone.

+

+ +

); } } + +export const ConfirmDelete = injectI18n(ConfirmDeleteUI); diff --git a/x-pack/plugins/security/public/components/management/users/edit_user.js b/x-pack/plugins/security/public/components/management/users/edit_user.js index 5cda86dd947df..8d41a549c3772 100644 --- a/x-pack/plugins/security/public/components/management/users/edit_user.js +++ b/x-pack/plugins/security/public/components/management/users/edit_user.js @@ -32,9 +32,11 @@ import { import { toastNotifications } from 'ui/notify'; import { USERS_PATH } from '../../../views/management/management_urls'; import { ConfirmDelete } from './confirm_delete'; +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; + const validEmailRegex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; //eslint-disable-line max-len const validUsernameRegex = /[a-zA-Z_][a-zA-Z0-9_@\-\$\.]*/; -export class EditUser extends Component { +class EditUserUI extends Component { constructor(props) { super(props); this.state = { @@ -63,7 +65,10 @@ export class EditUser extends Component { currentUser = await apiClient.getCurrentUser(); } catch (err) { toastNotifications.addDanger({ - title: `Error loading user`, + title: this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.errorLoadingUserTitle", + defaultMessage: "Error loading user" + }), text: get(err, 'data.message') || err.message, }); return; @@ -75,7 +80,10 @@ export class EditUser extends Component { roles = await apiClient.getRoles(); } catch (err) { toastNotifications.addDanger({ - title: `Error loading roles`, + title: this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.errorLoadingRolesTitle", + defaultMessage: "Error loading roles" + }), text: get(err, 'data.message') || err.message, }); return; @@ -99,19 +107,28 @@ export class EditUser extends Component { passwordError = () => { const { password } = this.state; if (password !== null && password.length < 6) { - return 'Password must be at least 6 characters'; + return this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.passwordLengthErrorMessage", + defaultMessage: "Password must be at least 6 characters" + }); } }; currentPasswordError = () => { const { currentPasswordError } = this.state; if (currentPasswordError) { - return 'The current password you entered is incorrect'; + return this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.incorrectPasswordErrorMessage", + defaultMessage: "The current password you entered is incorrect" + }); } }; confirmPasswordError = () => { const { password, confirmPassword } = this.state; if (password && confirmPassword !== null && password !== confirmPassword) { - return 'Passwords do not match'; + return this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.passwordDoNotMatchErrorMessage", + defaultMessage: "Passwords do not match" + }); } }; usernameError = () => { @@ -119,19 +136,28 @@ export class EditUser extends Component { if (username !== null && !username) { return 'Username is required'; } else if (username && !username.match(validUsernameRegex)) { - return 'Username must begin with a letter or underscore and contain only letters, underscores, and numbers'; + return this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.usernameAllowedCharactersErrorMessage", + defaultMessage: "Username must begin with a letter or underscore and contain only letters, underscores, and numbers" + }); } }; fullnameError = () => { const { full_name } = this.state.user; if (full_name !== null && !full_name) { - return 'Full name is required'; + return this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.fullNameRequiredErrorMessage", + defaultMessage: "Full name is required" + }); } }; emailError = () => { const { email } = this.state.user; if (email !== null && (!email || !email.match(validEmailRegex))) { - return 'A valid email address is required'; + return this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.validEmailRequiredErrorMessage", + defaultMessage: "A valid email address is required" + }); } }; changePassword = async () => { @@ -139,12 +165,22 @@ export class EditUser extends Component { const { user, password, currentPassword } = this.state; try { await apiClient.changePassword(user.username, password, currentPassword); - toastNotifications.addSuccess('Password changed.'); + toastNotifications.addSuccess( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.passwordSuccessfullyChangedNotificationMessage", + defaultMessage: "Password changed." + }) + ); } catch (e) { if (e.status === 401) { return this.setState({ currentPasswordError: true }); } else { - toastNotifications.addDanger(`Error setting password: ${e.data.message}`); + toastNotifications.addDanger( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.settingPasswordErrorMessage", + defaultMessage: "Error setting password: {message}" + }, { message: e.data.message }) + ); } } this.clearPasswordForm(); @@ -161,10 +197,20 @@ export class EditUser extends Component { } try { await apiClient.saveUser(userToSave); - toastNotifications.addSuccess(`Saved user ${user.username}`); + toastNotifications.addSuccess( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.userSuccessfullySavedNotificationMessage", + defaultMessage: "Saved user {message}" + }, { message: user.username }) + ); changeUrl(USERS_PATH); } catch (e) { - toastNotifications.addDanger(`Error saving user: ${e.data.message}`); + toastNotifications.addDanger( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.editUser.savingUserErrorMessage", + defaultMessage: "Error saving user: {message}" + }, { message: e.data.message }) + ); } }; clearPasswordForm = () => { @@ -181,7 +227,10 @@ export class EditUser extends Component { {userIsLoggedInUser ? ( @@ -193,7 +242,15 @@ export class EditUser extends Component { ) : null} @@ -206,7 +263,10 @@ export class EditUser extends Component { /> @@ -237,10 +297,21 @@ export class EditUser extends Component { {this.passwordFields()} {username === 'kibana' ? ( - +

- After you change the password for the kibana user, you must update the kibana.yml - file and restart Kibana. +

@@ -258,7 +329,10 @@ export class EditUser extends Component { this.changePassword(password); }} > - Save password + @@ -268,7 +342,10 @@ export class EditUser extends Component { this.clearPasswordForm(); }} > - Cancel + @@ -298,7 +375,7 @@ export class EditUser extends Component { this.setState({ showDeleteConfirmation: false }); }; render() { - const { changeUrl, apiClient } = this.props; + const { changeUrl, apiClient, intl } = this.props; const { user, roles, @@ -323,7 +400,20 @@ export class EditUser extends Component { -

{isNewUser ? 'New user' : `Edit "${user.username}" user`}

+

+ {isNewUser ? + + : + + } +

{reserved && ( @@ -336,8 +426,11 @@ export class EditUser extends Component { {reserved && (

- Reserved users are built-in and cannot be removed or modified. Only the password - may be changed. +

)} @@ -362,10 +455,16 @@ export class EditUser extends Component { error={this.usernameError()} helpText={ !isNewUser && !reserved - ? "Username's cannot be changed after creation." + ? intl.formatMessage({ + id: "xpack.security.management.users.editUser.changingUserNameAfterCreationDescription", + defaultMessage: "Username's cannot be changed after creation." + }) : null } - label="Username" + label={intl.formatMessage({ + id: "xpack.security.management.users.editUser.usernameFormRowLabel", + defaultMessage: "Username" + })} > @@ -393,7 +492,10 @@ export class EditUser extends Component { @@ -420,7 +522,10 @@ export class EditUser extends Component { @@ -446,10 +551,18 @@ export class EditUser extends Component {
)} - + - Change password + + + )} {this.changePasswordForm()} @@ -470,7 +588,12 @@ export class EditUser extends Component { {reserved && ( - changeUrl(USERS_PATH)}>Return to user list + changeUrl(USERS_PATH)}> + + )} {reserved ? null : ( @@ -481,7 +604,16 @@ export class EditUser extends Component { data-test-subj="userFormSaveButton" onClick={() => this.saveUser()} > - {isNewUser ? 'Create user' : 'Update user'} + {isNewUser ? + + : + } @@ -489,7 +621,10 @@ export class EditUser extends Component { data-test-subj="userFormCancelButton" onClick={() => changeUrl(USERS_PATH)} > - Cancel + @@ -502,7 +637,10 @@ export class EditUser extends Component { data-test-subj="userFormDeleteButton" color="danger" > - Delete user + )} @@ -517,3 +655,5 @@ export class EditUser extends Component { ); } } + +export const EditUser = injectI18n(EditUserUI); diff --git a/x-pack/plugins/security/public/components/management/users/users.js b/x-pack/plugins/security/public/components/management/users/users.js index 9bcd58edb217c..da2617c48b30a 100644 --- a/x-pack/plugins/security/public/components/management/users/users.js +++ b/x-pack/plugins/security/public/components/management/users/users.js @@ -21,8 +21,9 @@ import { } from '@elastic/eui'; import { toastNotifications } from 'ui/notify'; import { ConfirmDelete } from './confirm_delete'; +import { injectI18n, FormattedMessage } from "@kbn/i18n/react"; -export class Users extends Component { +class UsersUI extends Component { constructor(props) { super(props); this.state = { @@ -53,7 +54,12 @@ export class Users extends Component { if (e.status === 403) { this.setState({ permissionDenied: true }); } else { - toastNotifications.addDanger(`Error fetching users: ${e.data.message}`); + toastNotifications.addDanger( + this.props.intl.formatMessage({ + id: "xpack.security.management.users.fetchingUsersErrorMessage", + defaultMessage: "Error fetching users: {message}" + }, { message: e.data.message }) + ); } } } @@ -69,7 +75,13 @@ export class Users extends Component { color="danger" onClick={() => this.setState({ showDeleteConfirmation: true })} > - Delete {numSelected} user{numSelected > 1 ? 's' : ''} + ); } @@ -78,7 +90,7 @@ export class Users extends Component { } render() { const { users, filter, permissionDenied, showDeleteConfirmation, selection } = this.state; - const { apiClient } = this.props; + const { apiClient, intl } = this.props; if (permissionDenied) { return ( @@ -87,8 +99,20 @@ export class Users extends Component { Permission denied} - body={

You do not have permission to manage users.

} + title={ +

+ +

} + body={ +

+ +

} /> @@ -99,7 +123,7 @@ export class Users extends Component { const columns = [ { field: 'full_name', - name: 'Full Name', + name: intl.formatMessage({ id: "xpack.security.management.users.fullNameColumnName", defaultMessage: "Full Name" }), sortable: true, truncateText: true, render: fullName => { @@ -108,7 +132,7 @@ export class Users extends Component { }, { field: 'username', - name: 'User Name', + name: intl.formatMessage({ id: "xpack.security.management.users.userNameColumnName", defaultMessage: "User Name" }), sortable: true, truncateText: true, render: username => ( @@ -119,13 +143,16 @@ export class Users extends Component { }, { field: 'email', - name: 'Email Address', + name: intl.formatMessage({ + id: "xpack.security.management.users.emailAddressColumnName", + defaultMessage: "Email Address" + }), sortable: true, truncateText: true, }, { field: 'roles', - name: 'Roles', + name: intl.formatMessage({ id: "xpack.security.management.users.rolesColumnName", defaultMessage: "Roles" }), render: rolenames => { const roleLinks = rolenames.map((rolename, index) => { return ( @@ -140,12 +167,15 @@ export class Users extends Component { }, { field: 'metadata._reserved', - name: 'Reserved', + name: intl.formatMessage({ id: "xpack.security.management.users.reservedColumnName", defaultMessage: "Reserved" }), sortable: false, width: '100px', align: 'right', description: - 'Reserved users are built-in and cannot be removed. Only the password can be changed.', + intl.formatMessage({ + id: "xpack.security.management.users.reservedColumnDescription", + defaultMessage: "Reserved users are built-in and cannot be removed. Only the password can be changed." + }), render: reserved => reserved ? ( @@ -198,7 +228,12 @@ export class Users extends Component { -

Users

+

+ +

@@ -206,7 +241,10 @@ export class Users extends Component { data-test-subj="createUserButton" href="#/management/security/users/edit" > - Create new user +
@@ -241,3 +279,5 @@ export class Users extends Component { ); } } + +export const Users = injectI18n(UsersUI); diff --git a/x-pack/plugins/security/public/views/login/components/login_page/__snapshots__/login_page.test.tsx.snap b/x-pack/plugins/security/public/views/login/components/login_page/__snapshots__/login_page.test.tsx.snap index 97fe1b7aeaee3..cf98d33862dad 100644 --- a/x-pack/plugins/security/public/views/login/components/login_page/__snapshots__/login_page.test.tsx.snap +++ b/x-pack/plugins/security/public/views/login/components/login_page/__snapshots__/login_page.test.tsx.snap @@ -29,7 +29,7 @@ exports[`LoginPage disabled form states renders as expected when a connection to

@@ -43,7 +43,7 @@ exports[`LoginPage disabled form states renders as expected when a connection to

@@ -73,14 +73,14 @@ exports[`LoginPage disabled form states renders as expected when a connection to message={ } title={ } @@ -121,7 +121,7 @@ exports[`LoginPage disabled form states renders as expected when an unknown logi

@@ -135,7 +135,7 @@ exports[`LoginPage disabled form states renders as expected when an unknown logi

@@ -165,14 +165,14 @@ exports[`LoginPage disabled form states renders as expected when an unknown logi message={ } title={ } @@ -213,7 +213,7 @@ exports[`LoginPage disabled form states renders as expected when secure cookies

@@ -227,7 +227,7 @@ exports[`LoginPage disabled form states renders as expected when secure cookies

@@ -257,14 +257,14 @@ exports[`LoginPage disabled form states renders as expected when secure cookies message={ } title={ } @@ -305,7 +305,7 @@ exports[`LoginPage disabled form states renders as expected when xpack is not av

@@ -319,7 +319,7 @@ exports[`LoginPage disabled form states renders as expected when xpack is not av

@@ -349,14 +349,14 @@ exports[`LoginPage disabled form states renders as expected when xpack is not av message={ } title={ } @@ -397,7 +397,7 @@ exports[`LoginPage enabled form state renders as expected 1`] = `

@@ -411,7 +411,7 @@ exports[`LoginPage enabled form state renders as expected 1`] = `

diff --git a/x-pack/plugins/security/public/views/login/components/login_page/login_page.tsx b/x-pack/plugins/security/public/views/login/components/login_page/login_page.tsx index 7485cebfc9dfd..29ca0b3d978b6 100644 --- a/x-pack/plugins/security/public/views/login/components/login_page/login_page.tsx +++ b/x-pack/plugins/security/public/views/login/components/login_page/login_page.tsx @@ -58,7 +58,7 @@ export class LoginPage extends Component {

@@ -66,7 +66,7 @@ export class LoginPage extends Component {

@@ -98,13 +98,13 @@ export class LoginPage extends Component { } message={ } @@ -121,13 +121,13 @@ export class LoginPage extends Component { } message={ } @@ -138,13 +138,13 @@ export class LoginPage extends Component { } message={ } @@ -155,13 +155,13 @@ export class LoginPage extends Component { } message={ } diff --git a/x-pack/plugins/security/public/views/management/edit_user.js b/x-pack/plugins/security/public/views/management/edit_user.js index 5ba229605e4c7..160315338b091 100644 --- a/x-pack/plugins/security/public/views/management/edit_user.js +++ b/x-pack/plugins/security/public/views/management/edit_user.js @@ -14,14 +14,17 @@ import { EditUser } from '../../components/management/users'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { createApiClient } from '../../lib/api'; +import { I18nProvider } from '@kbn/i18n/react'; const renderReact = (elem, httpClient, changeUrl, username) => { render( - , + + + , elem ); }; diff --git a/x-pack/plugins/security/public/views/management/users.js b/x-pack/plugins/security/public/views/management/users.js index fdbb115751907..c61e421d52b4b 100644 --- a/x-pack/plugins/security/public/views/management/users.js +++ b/x-pack/plugins/security/public/views/management/users.js @@ -12,12 +12,13 @@ import 'plugins/security/services/shield_user'; import { SECURITY_PATH, USERS_PATH } from './management_urls'; import { Users } from '../../components/management/users'; import { createApiClient } from '../../lib/api'; +import { I18nProvider } from '@kbn/i18n/react'; routes.when(SECURITY_PATH, { redirectTo: USERS_PATH, }); const renderReact = (elem, httpClient, changeUrl) => { - render(, elem); + render(, elem); }; routes.when(USERS_PATH, { diff --git a/x-pack/test/api_integration/apis/beats/get_beat.js b/x-pack/test/api_integration/apis/beats/get_beat.js index 67cb26fc8ad74..67bff4c5727af 100644 --- a/x-pack/test/api_integration/apis/beats/get_beat.js +++ b/x-pack/test/api_integration/apis/beats/get_beat.js @@ -18,6 +18,42 @@ export default function ({ getService }) { beforeEach('load beats archive', () => esArchiver.load(archive)); afterEach('unload beats archive', () => esArchiver.unload(archive)); + it('should return no configurations for the beat without tags', async () => { + await es.index({ + index: ES_INDEX_NAME, + type: ES_TYPE_NAME, + id: `beat:empty`, + body: { + type: 'beat', + beat: { + type: 'filebeat', + active: true, + host_ip: '1.2.3.4', + host_name: 'empty.com', + id: 'empty', + name: 'empty_filebeat', + access_token: + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI', // eslint-disable-line + }, + }, + }); + + const { body: apiResponse } = await supertest + .get('/api/beats/agent/empty/configuration') + .set( + 'kbn-beats-access-token', + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' + + 'eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.' + + 'SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI' + ) + .expect(200); + + const configurationBlocks = apiResponse.configuration_blocks; + + expect(configurationBlocks).to.be.an(Array); + expect(configurationBlocks.length).to.be(0); + }); + it('should return merged configuration for the beat', async () => { const { body: apiResponse } = await supertest .get('/api/beats/agent/foo/configuration') diff --git a/x-pack/test/api_integration/apis/monitoring/common/mappings_exist.js b/x-pack/test/api_integration/apis/monitoring/common/mappings_exist.js index 8d78a150f28c2..1771d1b378290 100644 --- a/x-pack/test/api_integration/apis/monitoring/common/mappings_exist.js +++ b/x-pack/test/api_integration/apis/monitoring/common/mappings_exist.js @@ -43,7 +43,7 @@ export default function ({ getService }) { }, ]; - describe('mappings', () => { + describe.skip('mappings', () => { for (const { indexTemplate, metrics, name } of metricSets) { let mappings;