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/test/scripts/jenkins_selenium.sh b/test/scripts/jenkins_selenium.sh index e95be0073fe63..a7003bc26d9bf 100755 --- a/test/scripts/jenkins_selenium.sh +++ b/test/scripts/jenkins_selenium.sh @@ -8,4 +8,5 @@ source "$(dirname $0)/../../src/dev/ci_setup/java_setup.sh" node scripts/build --release --debug --oss; export TEST_ES_FROM=${TEST_ES_FROM:-source} -xvfb-run "$(FORCE_COLOR=0 yarn bin)/grunt" jenkins:selenium --from=source; +# This should not be merged into master as we are only running tests for secops +# xvfb-run "$(FORCE_COLOR=0 yarn bin)/grunt" jenkins:selenium --from=source; diff --git a/test/scripts/jenkins_unit.sh b/test/scripts/jenkins_unit.sh index 4364facc45224..b9952cc5d6969 100755 --- a/test/scripts/jenkins_unit.sh +++ b/test/scripts/jenkins_unit.sh @@ -6,4 +6,5 @@ source "$(dirname $0)/../../src/dev/ci_setup/git_setup.sh" source "$(dirname $0)/../../src/dev/ci_setup/java_setup.sh" export TEST_ES_FROM=${TEST_ES_FROM:-source} -xvfb-run "$(FORCE_COLOR=0 yarn bin)/grunt" jenkins:unit --from=source; +# This should not merge forward into master. This disables unit tests since we are only running them for secops +# xvfb-run "$(FORCE_COLOR=0 yarn bin)/grunt" jenkins:unit --from=source; diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh index 6172db83a5de5..9a46d24a21a5b 100755 --- a/test/scripts/jenkins_xpack.sh +++ b/test/scripts/jenkins_xpack.sh @@ -12,16 +12,16 @@ export XPACK_DIR="$(cd "$(dirname "$0")/../../x-pack"; pwd)" echo "-> XPACK_DIR ${XPACK_DIR}" -echo " -> Running mocha tests" +echo " -> Skipping Running mocha tests (for secops only)" cd "$XPACK_DIR" -xvfb-run yarn test +# xvfb-run yarn test echo "" echo "" -echo " -> Running jest tests" +echo " -> Running jest tests (for secops only)" cd "$XPACK_DIR" -node scripts/jest --ci --no-cache --verbose +node scripts/jest --ci --no-cache --verbose secops echo "" echo "" diff --git a/x-pack/.gitignore b/x-pack/.gitignore index eb1aec466bbfa..f9d4d71829fa5 100644 --- a/x-pack/.gitignore +++ b/x-pack/.gitignore @@ -10,3 +10,4 @@ /.env /.kibana-plugin-helpers.dev.* !/plugins/infra/**/target +!/plugins/secops/**/target diff --git a/x-pack/index.js b/x-pack/index.js index 374cef7d26b66..5aa5eaa95c9d7 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -28,6 +28,7 @@ import { kueryAutocomplete } from './plugins/kuery_autocomplete'; import { canvas } from './plugins/canvas'; import { infra } from './plugins/infra'; import { rollup } from './plugins/rollup'; +import { secops } from './plugins/secops'; module.exports = function (kibana) { return [ @@ -55,5 +56,6 @@ module.exports = function (kibana) { kueryAutocomplete(kibana), infra(kibana), rollup(kibana), + secops(kibana), ]; }; diff --git a/x-pack/package.json b/x-pack/package.json index 636cad0bc31a4..30224ee04204e 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -54,6 +54,7 @@ "@types/react-dom": "^16.0.5", "@types/react-redux": "^6.0.6", "@types/react-router-dom": "^4.3.1", + "@types/recompose": "^0.26.0", "@types/reduce-reducers": "^0.1.3", "@types/sinon": "^5.0.1", "@types/supertest": "^2.0.5", @@ -82,9 +83,9 @@ "expect.js": "0.3.1", "fancy-log": "^1.3.2", "fetch-mock": "^5.13.1", - "graphql-code-generator": "^0.10.1", - "graphql-codegen-introspection-template": "^0.10.5", - "graphql-codegen-typescript-template": "^0.10.1", + "graphql-code-generator": "^0.12.6", + "graphql-codegen-introspection-template": "^0.12.6", + "graphql-codegen-typescript-template": "^0.12.6", "gulp": "3.9.1", "gulp-mocha": "^6.0.0", "gulp-multi-process": "^1.3.1", 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/secops/common/graphql/introspection.json b/x-pack/plugins/secops/common/graphql/introspection.json new file mode 100644 index 0000000000000..7a54bdae896c1 --- /dev/null +++ b/x-pack/plugins/secops/common/graphql/introspection.json @@ -0,0 +1,981 @@ +{ + "__schema": { + "queryType": { "name": "Query" }, + "mutationType": null, + "subscriptionType": null, + "types": [ + { + "kind": "OBJECT", + "name": "Query", + "description": "", + "fields": [ + { + "name": "source", + "description": "Get a security data source by id", + "args": [ + { + "name": "id", + "description": "The id of the source", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "Source", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "allSources", + "description": "Get a list of all security data sources", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "Source", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "ID", + "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "Source", + "description": "", + "fields": [ + { + "name": "id", + "description": "The id of the source", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "configuration", + "description": "The raw configuration of the source", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "SourceConfiguration", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SourceConfiguration", + "description": "A set of configuration options for a security data source", + "fields": [ + { + "name": "fields", + "description": "The field mapping to use for this source", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "SourceFields", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SourceFields", + "description": "A mapping of semantic fields to their document counterparts", + "fields": [ + { + "name": "container", + "description": "The field to identify a container by", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "host", + "description": "The fields to identify a host by", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "message", + "description": "The fields that may contain the log event message. The first field found win.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "pod", + "description": "The field to identify a pod by", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "tiebreaker", + "description": "The field to use as a tiebreaker for log events that have identical timestamps", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "timestamp", + "description": "The field to use as a timestamp for metrics and logs", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "String", + "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Schema", + "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", + "fields": [ + { + "name": "types", + "description": "A list of all types supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "queryType", + "description": "The type that query operations will be rooted at.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "mutationType", + "description": "If this server supports mutation, the type that mutation operations will be rooted at.", + "args": [], + "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "subscriptionType", + "description": "If this server support subscription, the type that subscription operations will be rooted at.", + "args": [], + "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "directives", + "description": "A list of all directives supported by this server.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Directive", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Type", + "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", + "fields": [ + { + "name": "kind", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "ENUM", "name": "__TypeKind", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "fields", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Field", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "interfaces", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "possibleTypes", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "enumValues", + "description": null, + "args": [ + { + "name": "includeDeprecated", + "description": null, + "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, + "defaultValue": "false" + } + ], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__EnumValue", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "inputFields", + "description": null, + "args": [], + "type": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ofType", + "description": null, + "args": [], + "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__TypeKind", + "description": "An enum describing what kind of type a given `__Type` is.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "SCALAR", + "description": "Indicates this type is a scalar.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Indicates this type is a union. `possibleTypes` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Indicates this type is an enum. `enumValues` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Indicates this type is an input object. `inputFields` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "LIST", + "description": "Indicates this type is a list. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "NON_NULL", + "description": "Indicates this type is a non-null. `ofType` is a valid field.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "SCALAR", + "name": "Boolean", + "description": "The `Boolean` scalar type represents `true` or `false`.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Field", + "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__InputValue", + "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "defaultValue", + "description": "A GraphQL-formatted string representing the default value for this input value.", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__EnumValue", + "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "isDeprecated", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "deprecationReason", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "__Directive", + "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", + "fields": [ + { + "name": "name", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "description", + "description": null, + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "locations", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "ENUM", "name": "__DirectiveLocation", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "args", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "onOperation", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onFragment", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + }, + { + "name": "onField", + "description": null, + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "isDeprecated": true, + "deprecationReason": "Use `locations`." + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "__DirectiveLocation", + "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "QUERY", + "description": "Location adjacent to a query operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "MUTATION", + "description": "Location adjacent to a mutation operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUBSCRIPTION", + "description": "Location adjacent to a subscription operation.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD", + "description": "Location adjacent to a field.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_DEFINITION", + "description": "Location adjacent to a fragment definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FRAGMENT_SPREAD", + "description": "Location adjacent to a fragment spread.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INLINE_FRAGMENT", + "description": "Location adjacent to an inline fragment.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCHEMA", + "description": "Location adjacent to a schema definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SCALAR", + "description": "Location adjacent to a scalar definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "OBJECT", + "description": "Location adjacent to an object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "FIELD_DEFINITION", + "description": "Location adjacent to a field definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ARGUMENT_DEFINITION", + "description": "Location adjacent to an argument definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INTERFACE", + "description": "Location adjacent to an interface definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "UNION", + "description": "Location adjacent to a union definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM", + "description": "Location adjacent to an enum definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ENUM_VALUE", + "description": "Location adjacent to an enum value definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_OBJECT", + "description": "Location adjacent to an input object type definition.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INPUT_FIELD_DEFINITION", + "description": "Location adjacent to an input object field definition.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + } + ], + "directives": [ + { + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", + "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], + "args": [ + { + "name": "if", + "description": "Skipped when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "defaultValue": null + } + ] + }, + { + "name": "include", + "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", + "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], + "args": [ + { + "name": "if", + "description": "Included when true.", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } + }, + "defaultValue": null + } + ] + }, + { + "name": "deprecated", + "description": "Marks an element of a GraphQL schema as no longer supported.", + "locations": ["FIELD_DEFINITION", "ENUM_VALUE"], + "args": [ + { + "name": "reason", + "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": "\"No longer supported\"" + } + ] + } + ] + } +} diff --git a/x-pack/plugins/secops/common/graphql/root/index.ts b/x-pack/plugins/secops/common/graphql/root/index.ts new file mode 100644 index 0000000000000..47417b6376307 --- /dev/null +++ b/x-pack/plugins/secops/common/graphql/root/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { rootSchema } from './schema.gql'; diff --git a/x-pack/plugins/secops/common/graphql/root/schema.gql.ts b/x-pack/plugins/secops/common/graphql/root/schema.gql.ts new file mode 100644 index 0000000000000..0819f2e2808b8 --- /dev/null +++ b/x-pack/plugins/secops/common/graphql/root/schema.gql.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import gql from 'graphql-tag'; + +export const rootSchema = gql` + schema { + query: Query + #mutation: Mutation + } + + type Query + + #type Mutation +`; diff --git a/x-pack/plugins/secops/common/graphql/typed_resolvers.ts b/x-pack/plugins/secops/common/graphql/typed_resolvers.ts new file mode 100644 index 0000000000000..b3c4274f7f1e1 --- /dev/null +++ b/x-pack/plugins/secops/common/graphql/typed_resolvers.ts @@ -0,0 +1,77 @@ +/* + * 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 { GraphQLResolveInfo } from 'graphql'; + +type BasicResolver = ( + parent: any, + args: Args, + context: any, + info: GraphQLResolveInfo +) => Promise | Result; + +type AppResolverResult = + | Promise + | Promise<{ [P in keyof R]: () => Promise }> + | { [P in keyof R]: () => Promise } + | { [P in keyof R]: () => R[P] } + | R; + +export type AppResolvedResult = Resolver extends AppResolver + ? Result + : never; + +export type SubsetResolverWithFields = R extends BasicResolver< + Array, + infer ArgsInArray +> + ? BasicResolver< + Array>>, + ArgsInArray + > + : R extends BasicResolver + ? BasicResolver>, Args> + : never; + +export type SubsetResolverWithoutFields = R extends BasicResolver< + Array, + infer ArgsInArray +> + ? BasicResolver< + Array>>, + ArgsInArray + > + : R extends BasicResolver + ? BasicResolver>, Args> + : never; + +export type AppResolver = ( + parent: Parent, + args: Args, + context: Context, + info: GraphQLResolveInfo +) => AppResolverResult; + +export type AppResolverOf = Resolver extends BasicResolver< + infer Result, + infer Args +> + ? AppResolver + : never; + +export type AppResolverWithFields< + Resolver, + Parent, + Context, + IncludedFields extends string +> = AppResolverOf, Parent, Context>; + +export type AppResolverWithoutFields< + Resolver, + Parent, + Context, + ExcludedFields extends string +> = AppResolverOf, Parent, Context>; diff --git a/x-pack/plugins/secops/common/graphql/types.ts b/x-pack/plugins/secops/common/graphql/types.ts new file mode 100644 index 0000000000000..ed44e8d5e4b53 --- /dev/null +++ b/x-pack/plugins/secops/common/graphql/types.ts @@ -0,0 +1,163 @@ +/* tslint:disable */ +/* + * 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 { GraphQLResolveInfo } from 'graphql'; + +export type Resolver = ( + parent: Parent, + args: Args, + context: Context, + info: GraphQLResolveInfo +) => Promise | Result; + +export type SubscriptionResolver = { + subscribe( + parent: P, + args: Args, + context: Context, + info: GraphQLResolveInfo + ): AsyncIterator; + resolve?( + parent: P, + args: Args, + context: Context, + info: GraphQLResolveInfo + ): R | Result | Promise; +}; + +export interface Query { + source: Source /** Get a security data source by id */; + allSources: Source[] /** Get a list of all security data sources */; +} + +export interface Source { + id: string /** The id of the source */; + configuration: SourceConfiguration /** The raw configuration of the source */; +} +/** A set of configuration options for a security data source */ +export interface SourceConfiguration { + fields: SourceFields /** The field mapping to use for this source */; +} +/** A mapping of semantic fields to their document counterparts */ +export interface SourceFields { + container: string /** The field to identify a container by */; + host: string /** The fields to identify a host by */; + message: string[] /** The fields that may contain the log event message. The first field found win. */; + pod: string /** The field to identify a pod by */; + tiebreaker: string /** The field to use as a tiebreaker for log events that have identical timestamps */; + timestamp: string /** The field to use as a timestamp for metrics and logs */; +} +export interface SourceQueryArgs { + id: string /** The id of the source */; +} + +export namespace QueryResolvers { + export interface Resolvers { + source?: SourceResolver /** Get a security data source by id */; + allSources?: AllSourcesResolver< + Source[], + any, + Context + > /** Get a list of all security data sources */; + } + + export type SourceResolver = Resolver< + R, + Parent, + Context, + SourceArgs + >; + export interface SourceArgs { + id: string /** The id of the source */; + } + + export type AllSourcesResolver = Resolver< + R, + Parent, + Context + >; +} + +export namespace SourceResolvers { + export interface Resolvers { + id?: IdResolver /** The id of the source */; + configuration?: ConfigurationResolver< + SourceConfiguration, + any, + Context + > /** The raw configuration of the source */; + } + + export type IdResolver = Resolver; + export type ConfigurationResolver< + R = SourceConfiguration, + Parent = any, + Context = any + > = Resolver; +} +/** A set of configuration options for a security data source */ +export namespace SourceConfigurationResolvers { + export interface Resolvers { + fields?: FieldsResolver< + SourceFields, + any, + Context + > /** The field mapping to use for this source */; + } + + export type FieldsResolver = Resolver< + R, + Parent, + Context + >; +} +/** A mapping of semantic fields to their document counterparts */ +export namespace SourceFieldsResolvers { + export interface Resolvers { + container?: ContainerResolver /** The field to identify a container by */; + host?: HostResolver /** The fields to identify a host by */; + message?: MessageResolver< + string[], + any, + Context + > /** The fields that may contain the log event message. The first field found win. */; + pod?: PodResolver /** The field to identify a pod by */; + tiebreaker?: TiebreakerResolver< + string, + any, + Context + > /** The field to use as a tiebreaker for log events that have identical timestamps */; + timestamp?: TimestampResolver< + string, + any, + Context + > /** The field to use as a timestamp for metrics and logs */; + } + + export type ContainerResolver = Resolver< + R, + Parent, + Context + >; + export type HostResolver = Resolver; + export type MessageResolver = Resolver< + R, + Parent, + Context + >; + export type PodResolver = Resolver; + export type TiebreakerResolver = Resolver< + R, + Parent, + Context + >; + export type TimestampResolver = Resolver< + R, + Parent, + Context + >; +} diff --git a/x-pack/plugins/secops/index.ts b/x-pack/plugins/secops/index.ts new file mode 100644 index 0000000000000..10779b6db54be --- /dev/null +++ b/x-pack/plugins/secops/index.ts @@ -0,0 +1,48 @@ +/* + * 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 JoiNamespace from 'joi'; +import { resolve } from 'path'; + +import { getConfigSchema, initServerWithKibana, KbnServer } from './server/kibana.index'; + +const APP_ID = 'secops'; + +export function secops(kibana: any) { + return new kibana.Plugin({ + id: APP_ID, + configPrefix: 'xpack.secops', + publicDir: resolve(__dirname, 'public'), + require: ['kibana', 'elasticsearch'], + uiExports: { + app: { + description: 'Explore your security operations', + main: 'plugins/secops/app', + euiIconType: 'securityApp', + title: 'Sec Ops', + listed: false, + url: `/app/${APP_ID}`, + }, + home: ['plugins/secops/register_feature'], + links: [ + { + description: 'Explore your security operations', + euiIconType: 'securityApp', + id: 'secops', + order: 9000, + title: 'Sec Ops', + url: `/app/${APP_ID}`, + }, + ], + }, + config(Joi: typeof JoiNamespace) { + return getConfigSchema(Joi); + }, + init(server: KbnServer) { + initServerWithKibana(server); + }, + }); +} diff --git a/x-pack/plugins/secops/package.json b/x-pack/plugins/secops/package.json new file mode 100644 index 0000000000000..ced3d1d97cf2c --- /dev/null +++ b/x-pack/plugins/secops/package.json @@ -0,0 +1,16 @@ +{ + "author": "Elastic", + "name": "sec-ops", + "version": "7.0.0-alpha1", + "scripts": { + "build-graphql-types": "node scripts/generate_types_from_graphql.js" + }, + "devDependencies": { + "@types/boom": "^7.2.0", + "@types/color": "^3.0.0", + "@types/lodash": "^4.14.110" + }, + "dependencies": { + "lodash": "^4.17.10" + } +} diff --git a/x-pack/plugins/secops/public/app.ts b/x-pack/plugins/secops/public/app.ts new file mode 100644 index 0000000000000..255c51c9e48ce --- /dev/null +++ b/x-pack/plugins/secops/public/app.ts @@ -0,0 +1,7 @@ +/* + * 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 './apps/kibana_app'; diff --git a/x-pack/plugins/secops/public/apps/kibana_app.ts b/x-pack/plugins/secops/public/apps/kibana_app.ts new file mode 100644 index 0000000000000..292b02c243e0e --- /dev/null +++ b/x-pack/plugins/secops/public/apps/kibana_app.ts @@ -0,0 +1,12 @@ +/* + * 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 'uiExports/autocompleteProviders'; + +import { compose } from '../lib/compose/kibana_compose'; +import { startApp } from './start_app'; + +startApp(compose()); diff --git a/x-pack/plugins/secops/public/apps/start_app.tsx b/x-pack/plugins/secops/public/apps/start_app.tsx new file mode 100644 index 0000000000000..36f0a83289f9e --- /dev/null +++ b/x-pack/plugins/secops/public/apps/start_app.tsx @@ -0,0 +1,31 @@ +/* + * 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 { createHashHistory } from 'history'; +import React from 'react'; +import { ApolloProvider } from 'react-apollo'; +import { ThemeProvider } from 'styled-components'; + +// TODO use theme provided from parentApp when kibana supports it +import { EuiErrorBoundary } from '@elastic/eui'; +import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json'; + +import { AppFrontendLibs } from '../lib/lib'; +import { PageRouter } from '../routes'; + +export const startApp = async (libs: AppFrontendLibs) => { + const history = createHashHistory(); + + libs.framework.render( + + + + + + + + ); +}; diff --git a/x-pack/plugins/secops/public/apps/testing_app.ts b/x-pack/plugins/secops/public/apps/testing_app.ts new file mode 100644 index 0000000000000..0fd944d3f45d6 --- /dev/null +++ b/x-pack/plugins/secops/public/apps/testing_app.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { compose } from '../lib/compose/testing_compose'; +import { startApp } from './start_app'; + +startApp(compose()); diff --git a/x-pack/plugins/secops/public/components/page.tsx b/x-pack/plugins/secops/public/components/page.tsx new file mode 100644 index 0000000000000..04d69fdb1d9e8 --- /dev/null +++ b/x-pack/plugins/secops/public/components/page.tsx @@ -0,0 +1,26 @@ +/* + * 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 { EuiPage } from '@elastic/eui'; +import styled from 'styled-components'; + +export const ColumnarPage = styled.div` + display: flex; + flex-direction: column; + align-items: stretch; + flex: 1 0 0; +`; + +export const PageContent = styled.div` + flex: 1 0 0; + display: flex; + flex-direction: row; + background-color: ${props => props.theme.eui.euiColorEmptyShade}; +`; + +export const FlexPage = styled(EuiPage)` + flex: 1 0 0; +`; diff --git a/x-pack/plugins/secops/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/secops/public/lib/adapters/framework/kibana_framework_adapter.ts new file mode 100644 index 0000000000000..d2e745245680b --- /dev/null +++ b/x-pack/plugins/secops/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -0,0 +1,182 @@ +/* + * 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 { IModule, IScope } from 'angular'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; + +import { UIRoutes as KibanaUIRoutes } from 'ui/routes'; + +import { + AppBufferedKibanaServiceCall, + AppFrameworkAdapter, + AppKibanaAdapterServiceRefs, + AppKibanaUIConfig, + AppTimezoneProvider, + AppUiKibanaAdapterScope, +} from '../../lib'; + +const ROOT_ELEMENT_ID = 'react-secops-root'; +const BREADCRUMBS_ELEMENT_ID = 'react-secops-breadcrumbs'; + +export class AppKibanaFrameworkAdapter implements AppFrameworkAdapter { + public dateFormat?: string; + public kbnVersion?: string; + public scaledDateFormat?: string; + public timezone?: string; + + private adapterService: KibanaAdapterServiceProvider; + private timezoneProvider: AppTimezoneProvider; + private rootComponent: React.ReactElement | null = null; + private breadcrumbsComponent: React.ReactElement | null = null; + + constructor(uiModule: IModule, uiRoutes: KibanaUIRoutes, timezoneProvider: AppTimezoneProvider) { + this.adapterService = new KibanaAdapterServiceProvider(); + this.timezoneProvider = timezoneProvider; + this.register(uiModule, uiRoutes); + } + + public setUISettings = (key: string, value: any) => { + this.adapterService.callOrBuffer(({ config }) => { + config.set(key, value); + }); + }; + + public render = (component: React.ReactElement) => { + this.adapterService.callOrBuffer(() => (this.rootComponent = component)); + }; + + public renderBreadcrumbs = (component: React.ReactElement) => { + this.adapterService.callOrBuffer(() => (this.breadcrumbsComponent = component)); + }; + + private register = (adapterModule: IModule, uiRoutes: KibanaUIRoutes) => { + adapterModule.provider('kibanaAdapter', this.adapterService); + + adapterModule.directive('appUiKibanaAdapter', () => ({ + controller: ($scope: AppUiKibanaAdapterScope, $element: JQLite) => ({ + $onDestroy: () => { + const targetRootElement = $element[0].querySelector(`#${ROOT_ELEMENT_ID}`); + const targetBreadcrumbsElement = $element[0].querySelector(`#${ROOT_ELEMENT_ID}`); + + if (targetRootElement) { + ReactDOM.unmountComponentAtNode(targetRootElement); + } + + if (targetBreadcrumbsElement) { + ReactDOM.unmountComponentAtNode(targetBreadcrumbsElement); + } + }, + $onInit: () => { + $scope.topNavMenu = []; + }, + $postLink: () => { + $scope.$watchGroup( + [ + () => this.breadcrumbsComponent, + () => $element[0].querySelector(`#${BREADCRUMBS_ELEMENT_ID}`), + ], + ([breadcrumbsComponent, targetElement]) => { + if (!targetElement) { + return; + } + + if (breadcrumbsComponent) { + ReactDOM.render(breadcrumbsComponent, targetElement); + } else { + ReactDOM.unmountComponentAtNode(targetElement); + } + } + ); + $scope.$watchGroup( + [() => this.rootComponent, () => $element[0].querySelector(`#${ROOT_ELEMENT_ID}`)], + ([rootComponent, targetElement]) => { + if (!targetElement) { + return; + } + + if (rootComponent) { + ReactDOM.render(rootComponent, targetElement); + } else { + ReactDOM.unmountComponentAtNode(targetElement); + } + } + ); + }, + }), + scope: true, + template: ` +
+ `, + })); + + adapterModule.run(( + config: AppKibanaUIConfig, + kbnVersion: string, + Private: (provider: Provider) => Provider, + // @ts-ignore: inject kibanaAdapter to force eager instaliation + kibanaAdapter: any + ) => { + this.timezone = Private(this.timezoneProvider)(); + this.kbnVersion = kbnVersion; + this.dateFormat = config.get('dateFormat'); + this.scaledDateFormat = config.get('dateFormat:scaled'); + }); + + uiRoutes.enable(); + + uiRoutes.otherwise({ + reloadOnSearch: false, + template: + '', + }); + }; +} + +// tslint:disable-next-line: max-classes-per-file +class KibanaAdapterServiceProvider { + public serviceRefs: AppKibanaAdapterServiceRefs | null = null; + public bufferedCalls: Array> = []; + + public $get($rootScope: IScope, config: AppKibanaUIConfig) { + this.serviceRefs = { + config, + rootScope: $rootScope, + }; + + this.applyBufferedCalls(this.bufferedCalls); + + return this; + } + + public callOrBuffer(serviceCall: (serviceRefs: AppKibanaAdapterServiceRefs) => void) { + if (this.serviceRefs !== null) { + this.applyBufferedCalls([serviceCall]); + } else { + this.bufferedCalls.push(serviceCall); + } + } + + public applyBufferedCalls( + bufferedCalls: Array> + ) { + if (!this.serviceRefs) { + return; + } + + this.serviceRefs.rootScope.$apply(() => { + bufferedCalls.forEach(serviceCall => { + if (!this.serviceRefs) { + return; + } + return serviceCall(this.serviceRefs); + }); + }); + } +} diff --git a/x-pack/plugins/secops/public/lib/adapters/framework/testing_framework_adapter.ts b/x-pack/plugins/secops/public/lib/adapters/framework/testing_framework_adapter.ts new file mode 100644 index 0000000000000..c829e1d26e6a4 --- /dev/null +++ b/x-pack/plugins/secops/public/lib/adapters/framework/testing_framework_adapter.ts @@ -0,0 +1,29 @@ +/* + * 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 { AppFrameworkAdapter } from '../../lib'; + +export class AppTestingFrameworkAdapter implements AppFrameworkAdapter { + public appState?: object; + public dateFormat?: string; + public kbnVersion?: string; + public scaledDateFormat?: string; + public timezone?: string; + + constructor() { + this.appState = {}; + } + + public render() { + return; + } + public renderBreadcrumbs() { + return; + } + public setUISettings() { + return; + } +} diff --git a/x-pack/plugins/secops/public/lib/adapters/observable_api/kibana_observable_api.ts b/x-pack/plugins/secops/public/lib/adapters/observable_api/kibana_observable_api.ts new file mode 100644 index 0000000000000..5a8a7c3a4e89e --- /dev/null +++ b/x-pack/plugins/secops/public/lib/adapters/observable_api/kibana_observable_api.ts @@ -0,0 +1,41 @@ +/* + * 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 { ajax } from 'rxjs/ajax'; +import { map } from 'rxjs/operators'; + +import { AppObservableApi, AppObservableApiPostParams, AppObservableApiResponse } from '../../lib'; + +export class AppKibanaObservableApiAdapter implements AppObservableApi { + private basePath: string; + private defaultHeaders: { + [headerName: string]: string; + }; + + constructor({ basePath, xsrfToken }: { basePath: string; xsrfToken: string }) { + this.basePath = basePath; + this.defaultHeaders = { + 'kbn-version': xsrfToken, + }; + } + + public post = ({ + url, + body, + }: AppObservableApiPostParams): AppObservableApiResponse => + ajax({ + body: body ? JSON.stringify(body) : undefined, + headers: { + ...this.defaultHeaders, + 'Content-Type': 'application/json', + }, + method: 'POST', + responseType: 'json', + timeout: 30000, + url: `${this.basePath}/api/${url}`, + withCredentials: true, + }).pipe(map(({ response, status }) => ({ response, status }))); +} diff --git a/x-pack/plugins/secops/public/lib/compose/kibana_compose.ts b/x-pack/plugins/secops/public/lib/compose/kibana_compose.ts new file mode 100644 index 0000000000000..5aa9c3c220666 --- /dev/null +++ b/x-pack/plugins/secops/public/lib/compose/kibana_compose.ts @@ -0,0 +1,69 @@ +/* + * 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 'ui/autoload/all'; +// @ts-ignore: path dynamic for kibana +import chrome from 'ui/chrome'; +// @ts-ignore: path dynamic for kibana +import { uiModules } from 'ui/modules'; +import uiRoutes from 'ui/routes'; +// @ts-ignore: path dynamic for kibana +import { timezoneProvider } from 'ui/vis/lib/timezone'; + +import { AppKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api'; + +import introspectionQueryResultData from '../../../common/graphql/introspection.json'; +import { AppKibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { AppFrontendLibs } from '../lib'; + +import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; +import ApolloClient from 'apollo-client'; +import { ApolloLink } from 'apollo-link'; +import { HttpLink } from 'apollo-link-http'; +import { withClientState } from 'apollo-link-state'; + +export function compose(): AppFrontendLibs { + const cache = new InMemoryCache({ + fragmentMatcher: new IntrospectionFragmentMatcher({ + introspectionQueryResultData, + }), + }); + + const observableApi = new AppKibanaObservableApiAdapter({ + basePath: chrome.getBasePath(), + xsrfToken: chrome.getXsrfToken(), + }); + + const graphQLOptions = { + cache, + link: ApolloLink.from([ + withClientState({ + cache, + resolvers: {}, + }), + new HttpLink({ + credentials: 'same-origin', + headers: { + 'kbn-xsrf': chrome.getXsrfToken(), + }, + uri: `${chrome.getBasePath()}/api/secops/graphql`, + }), + ]), + }; + + const apolloClient = new ApolloClient(graphQLOptions); + + const appModule = uiModules.get('app/secops'); + + const framework = new AppKibanaFrameworkAdapter(appModule, uiRoutes, timezoneProvider); + + const libs: AppFrontendLibs = { + apolloClient, + framework, + observableApi, + }; + return libs; +} diff --git a/x-pack/plugins/secops/public/lib/compose/testing_compose.ts b/x-pack/plugins/secops/public/lib/compose/testing_compose.ts new file mode 100644 index 0000000000000..2dd320c88cbc4 --- /dev/null +++ b/x-pack/plugins/secops/public/lib/compose/testing_compose.ts @@ -0,0 +1,59 @@ +/* + * 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 'ui/autoload/all'; +// @ts-ignore: path dynamic for kibana +import chrome from 'ui/chrome'; +// @ts-ignore: path dynamic for kibana +import { uiModules } from 'ui/modules'; +import uiRoutes from 'ui/routes'; +// @ts-ignore: path dynamic for kibana +import { timezoneProvider } from 'ui/vis/lib/timezone'; + +import { InMemoryCache } from 'apollo-cache-inmemory'; +import ApolloClient from 'apollo-client'; +import { SchemaLink } from 'apollo-link-schema'; +import { addMockFunctionsToSchema, makeExecutableSchema } from 'graphql-tools'; +import { AppKibanaFrameworkAdapter } from '../adapters/framework/kibana_framework_adapter'; +import { AppKibanaObservableApiAdapter } from '../adapters/observable_api/kibana_observable_api'; +import { AppFrontendLibs } from '../lib'; + +export function compose(): AppFrontendLibs { + const appModule = uiModules.get('app/secops'); + const observableApi = new AppKibanaObservableApiAdapter({ + basePath: chrome.getBasePath(), + xsrfToken: chrome.getXsrfToken(), + }); + const framework = new AppKibanaFrameworkAdapter(appModule, uiRoutes, timezoneProvider); + const typeDefs = ` + Query {} +`; + + const mocks = { + Mutation: () => undefined, + Query: () => undefined, + }; + + const schema = makeExecutableSchema({ typeDefs }); + addMockFunctionsToSchema({ + mocks, + schema, + }); + + const cache = new InMemoryCache((window as any).__APOLLO_CLIENT__); + + const apolloClient = new ApolloClient({ + cache, + link: new SchemaLink({ schema }), + }); + + const libs: AppFrontendLibs = { + apolloClient, + framework, + observableApi, + }; + return libs; +} diff --git a/x-pack/plugins/secops/public/lib/lib.ts b/x-pack/plugins/secops/public/lib/lib.ts new file mode 100644 index 0000000000000..5246d4c1e1f80 --- /dev/null +++ b/x-pack/plugins/secops/public/lib/lib.ts @@ -0,0 +1,70 @@ +/* + * 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 { IModule, IScope } from 'angular'; +import { NormalizedCacheObject } from 'apollo-cache-inmemory'; +import ApolloClient from 'apollo-client'; +import React from 'react'; +import { Observable } from 'rxjs'; + +export interface AppFrontendLibs { + framework: AppFrameworkAdapter; + apolloClient: AppApolloClient; + observableApi: AppObservableApi; +} + +export type AppTimezoneProvider = () => string; + +export type AppApolloClient = ApolloClient; + +export interface AppFrameworkAdapter { + appState?: object; + dateFormat?: string; + kbnVersion?: string; + scaledDateFormat?: string; + timezone?: string; + + setUISettings(key: string, value: any): void; + render(component: React.ReactElement): void; + renderBreadcrumbs(component: React.ReactElement): void; +} + +export interface AppFrameworkAdapterConstructible { + new (uiModule: IModule, timezoneProvider: AppTimezoneProvider): AppFrameworkAdapter; +} + +export interface AppObservableApiPostParams { + url: string; + body?: RequestBody; +} + +export type AppObservableApiResponse = Observable<{ + status: number; + response: BodyType; +}>; + +export interface AppObservableApi { + post( + params: AppObservableApiPostParams + ): AppObservableApiResponse; +} + +export interface AppUiKibanaAdapterScope extends IScope { + breadcrumbs: any[]; + topNavMenu: any[]; +} + +export interface AppKibanaUIConfig { + get(key: string): any; + set(key: string, value: any): Promise; +} + +export interface AppKibanaAdapterServiceRefs { + config: AppKibanaUIConfig; + rootScope: IScope; +} + +export type AppBufferedKibanaServiceCall = (serviceRefs: ServiceRefs) => void; diff --git a/x-pack/plugins/secops/public/pages/404.tsx b/x-pack/plugins/secops/public/pages/404.tsx new file mode 100644 index 0000000000000..a025f3ace88b3 --- /dev/null +++ b/x-pack/plugins/secops/public/pages/404.tsx @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { pure } from 'recompose'; + +export const NotFoundPage = pure(() =>
No content found
); diff --git a/x-pack/plugins/secops/public/pages/home/index.tsx b/x-pack/plugins/secops/public/pages/home/index.tsx new file mode 100644 index 0000000000000..9d646390b9c69 --- /dev/null +++ b/x-pack/plugins/secops/public/pages/home/index.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as React from 'react'; +import { pure } from 'recompose'; + +import { ColumnarPage } from '../../components/page'; + +export const HomePage = pure(() => ( + +

Hello Sec Ops

+
+)); diff --git a/x-pack/plugins/secops/public/register_feature.ts b/x-pack/plugins/secops/public/register_feature.ts new file mode 100644 index 0000000000000..29357a6167e04 --- /dev/null +++ b/x-pack/plugins/secops/public/register_feature.ts @@ -0,0 +1,22 @@ +/* + * 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 { + FeatureCatalogueCategory, + FeatureCatalogueRegistryProvider, +} from 'ui/registry/feature_catalogue'; + +const APP_ID = 'secops'; + +FeatureCatalogueRegistryProvider.register(() => ({ + id: 'secops', + title: 'Sec Ops', + description: 'Explore security metrics and logs for events and alerts', + icon: 'securityApp', + path: `/app/${APP_ID}`, + showOnHomePage: true, + category: FeatureCatalogueCategory.DATA, +})); diff --git a/x-pack/plugins/secops/public/routes.tsx b/x-pack/plugins/secops/public/routes.tsx new file mode 100644 index 0000000000000..ad16e1b4d6176 --- /dev/null +++ b/x-pack/plugins/secops/public/routes.tsx @@ -0,0 +1,26 @@ +/* + * 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 { History } from 'history'; +import React from 'react'; +import { Route, Router, Switch } from 'react-router-dom'; +import { pure } from 'recompose'; + +import { NotFoundPage } from './pages/404'; +import { HomePage } from './pages/home'; + +interface RouterProps { + history: History; +} + +export const PageRouter = pure(({ history }) => ( + + + + + + +)); diff --git a/x-pack/plugins/secops/scripts/combined_schema.ts b/x-pack/plugins/secops/scripts/combined_schema.ts new file mode 100644 index 0000000000000..ec3628b2632c9 --- /dev/null +++ b/x-pack/plugins/secops/scripts/combined_schema.ts @@ -0,0 +1,16 @@ +/* + * 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 { buildSchemaFromTypeDefinitions } from 'graphql-tools'; + +import { schemas as serverSchemas } from '../server/graphql'; + +export const schemas = [...serverSchemas]; + +// this default export is used to feed the combined types to the gql-gen tool +// which generates the corresponding typescript types +// tslint:disable-next-line:no-default-export +export default buildSchemaFromTypeDefinitions(schemas); diff --git a/x-pack/plugins/secops/scripts/generate_types_from_graphql.js b/x-pack/plugins/secops/scripts/generate_types_from_graphql.js new file mode 100644 index 0000000000000..f36979c159376 --- /dev/null +++ b/x-pack/plugins/secops/scripts/generate_types_from_graphql.js @@ -0,0 +1,49 @@ +/* + * 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. + */ + +const { join, resolve } = require('path'); +// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved +const { generate } = require('graphql-code-generator'); + +const GRAPHQL_GLOBS = [ + join('public', 'containers', '**', '*.gql_query.ts{,x}'), + join('public', 'store', '**', '*.gql_query.ts{,x}'), + join('common', 'graphql', '**', '*.gql_query.ts{,x}'), +]; +const CONFIG_PATH = resolve(__dirname, 'gql_gen.json'); +const OUTPUT_INTROSPECTION_PATH = resolve('common', 'graphql', 'introspection.json'); +const OUTPUT_TYPES_PATH = resolve('common', 'graphql', 'types.ts'); +const SCHEMA_PATH = resolve(__dirname, 'combined_schema.ts'); + +async function main() { + await generate( + { + args: GRAPHQL_GLOBS, + config: CONFIG_PATH, + out: OUTPUT_INTROSPECTION_PATH, + overwrite: true, + require: ['ts-node/register'], + schema: SCHEMA_PATH, + template: 'graphql-codegen-introspection-template', + }, + true + ); + await generate( + { + args: GRAPHQL_GLOBS, + config: CONFIG_PATH, + out: OUTPUT_TYPES_PATH, + overwrite: true, + schema: SCHEMA_PATH, + template: 'graphql-codegen-typescript-template', + }, + true + ); +} + +if (require.main === module) { + main(); +} diff --git a/x-pack/plugins/secops/scripts/gql_gen.json b/x-pack/plugins/secops/scripts/gql_gen.json new file mode 100644 index 0000000000000..ecfeb9f9e124b --- /dev/null +++ b/x-pack/plugins/secops/scripts/gql_gen.json @@ -0,0 +1,20 @@ +{ + "flattenTypes": true, + "generatorConfig": { + "prepend": [ + "/*", + " * 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.", + " */", + "" + ] + }, + "primitives": { + "String": "string", + "Int": "number", + "Float": "number", + "Boolean": "boolean", + "ID": "string" + } +} diff --git a/x-pack/plugins/secops/server/graphql/index.ts b/x-pack/plugins/secops/server/graphql/index.ts new file mode 100644 index 0000000000000..8cf5394a91a6c --- /dev/null +++ b/x-pack/plugins/secops/server/graphql/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { rootSchema } from '../../common/graphql/root/schema.gql'; +import { sourcesSchema } from './sources/schema.gql'; + +export const schemas = [rootSchema, sourcesSchema]; diff --git a/x-pack/plugins/secops/server/graphql/sources/index.ts b/x-pack/plugins/secops/server/graphql/sources/index.ts new file mode 100644 index 0000000000000..ee187d8c31bec --- /dev/null +++ b/x-pack/plugins/secops/server/graphql/sources/index.ts @@ -0,0 +1,8 @@ +/* + * 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 { createSourcesResolvers } from './resolvers'; +export { sourcesSchema } from './schema.gql'; diff --git a/x-pack/plugins/secops/server/graphql/sources/resolvers.ts b/x-pack/plugins/secops/server/graphql/sources/resolvers.ts new file mode 100644 index 0000000000000..1748e5f5092d2 --- /dev/null +++ b/x-pack/plugins/secops/server/graphql/sources/resolvers.ts @@ -0,0 +1,56 @@ +/* + * 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 { QueryResolvers } from '../../../common/graphql/types'; +import { AppResolverWithFields } from '../../lib/framework'; +import { Sources } from '../../lib/sources'; +import { Context } from '../../lib/types'; + +export type QuerySourceResolver = AppResolverWithFields< + QueryResolvers.SourceResolver, + null, + Context, + 'id' | 'configuration' +>; + +export type QueryAllSourcesResolver = AppResolverWithFields< + QueryResolvers.AllSourcesResolver, + null, + Context, + 'id' | 'configuration' +>; + +interface SourcesResolversDeps { + sources: Sources; +} + +export const createSourcesResolvers = ( + libs: SourcesResolversDeps +): { + Query: { + source: QuerySourceResolver; + allSources: QueryAllSourcesResolver; + }; +} => ({ + Query: { + async source(root, args) { + const requestedSourceConfiguration = await libs.sources.getConfiguration(args.id); + + return { + id: args.id, + configuration: requestedSourceConfiguration, + }; + }, + async allSources() { + const sourceConfigurations = await libs.sources.getAllConfigurations(); + + return Object.entries(sourceConfigurations).map(([sourceName, sourceConfiguration]) => ({ + id: sourceName, + configuration: sourceConfiguration, + })); + }, + }, +}); diff --git a/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts b/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts new file mode 100644 index 0000000000000..f9de01dd0ddce --- /dev/null +++ b/x-pack/plugins/secops/server/graphql/sources/schema.gql.ts @@ -0,0 +1,45 @@ +/* + * 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 gql from 'graphql-tag'; + +export const sourcesSchema = gql` + extend type Query { + "Get a security data source by id" + source("The id of the source" id: ID!): Source! + "Get a list of all security data sources" + allSources: [Source!]! + } + + type Source { + "The id of the source" + id: ID! + "The raw configuration of the source" + configuration: SourceConfiguration! + } + + "A set of configuration options for a security data source" + type SourceConfiguration { + "The field mapping to use for this source" + fields: SourceFields! + } + + "A mapping of semantic fields to their document counterparts" + type SourceFields { + "The field to identify a container by" + container: String! + "The fields to identify a host by" + host: String! + "The fields that may contain the log event message. The first field found win." + message: [String!]! + "The field to identify a pod by" + pod: String! + "The field to use as a tiebreaker for log events that have identical timestamps" + tiebreaker: String! + "The field to use as a timestamp for metrics and logs" + timestamp: String! + } +`; diff --git a/x-pack/plugins/secops/server/init_server.ts b/x-pack/plugins/secops/server/init_server.ts new file mode 100644 index 0000000000000..98107857289d4 --- /dev/null +++ b/x-pack/plugins/secops/server/init_server.ts @@ -0,0 +1,19 @@ +/* + * 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 { IResolvers, makeExecutableSchema } from 'graphql-tools'; +import { schemas } from './graphql'; +import { createSourcesResolvers } from './graphql/sources'; +import { AppBackendLibs } from './lib/types'; + +export const initServer = (libs: AppBackendLibs) => { + const schema = makeExecutableSchema({ + resolvers: [createSourcesResolvers(libs) as IResolvers], + typeDefs: schemas, + }); + + libs.framework.registerGraphQLEndpoint('/api/secops/graphql', schema); +}; diff --git a/x-pack/plugins/secops/server/kibana.index.ts b/x-pack/plugins/secops/server/kibana.index.ts new file mode 100644 index 0000000000000..645640cad3378 --- /dev/null +++ b/x-pack/plugins/secops/server/kibana.index.ts @@ -0,0 +1,39 @@ +/* + * 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 { Server } from 'hapi'; +import JoiNamespace from 'joi'; +import { initServer } from './init_server'; +import { compose } from './lib/compose/kibana'; + +export interface KbnServer extends Server { + usage: any; +} + +export const initServerWithKibana = (kbnServer: KbnServer) => { + const libs = compose(kbnServer); + initServer(libs); +}; + +export const getConfigSchema = (Joi: typeof JoiNamespace) => { + const DefaultSourceConfigSchema = Joi.object({}); + + const AppRootConfigSchema = Joi.object({ + enabled: Joi.boolean().default(true), + query: Joi.object({ + partitionSize: Joi.number(), + partitionFactor: Joi.number(), + }).default(), + sources: Joi.object() + .keys({ + default: DefaultSourceConfigSchema, + }) + .pattern(/.*/, DefaultSourceConfigSchema) + .default(), + }).default(); + + return AppRootConfigSchema; +}; diff --git a/x-pack/plugins/secops/server/lib/compose/kibana.ts b/x-pack/plugins/secops/server/lib/compose/kibana.ts new file mode 100644 index 0000000000000..6d9be1afd4dc2 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/compose/kibana.ts @@ -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 { Server } from 'hapi'; + +import { KibanaConfigurationAdapter } from '../configuration/kibana_configuration_adapter'; +import { KibanaBackendFrameworkAdapter } from '../framework/kibana_framework_adapter'; +import { Sources } from '../sources'; +import { ConfigurationSourcesAdapter } from '../sources/configuration_sources_adapter'; +import { AppBackendLibs, AppDomainLibs, Configuration } from '../types'; + +export function compose(server: Server): AppBackendLibs { + const configuration = new KibanaConfigurationAdapter(server); + const framework = new KibanaBackendFrameworkAdapter(server); + const sources = new Sources(new ConfigurationSourcesAdapter(configuration)); + + const domainLibs: AppDomainLibs = { + hello: '', + }; + + const libs: AppBackendLibs = { + configuration, + framework, + sources, + ...domainLibs, + }; + + return libs; +} diff --git a/x-pack/plugins/secops/server/lib/configuration/adapter_types.ts b/x-pack/plugins/secops/server/lib/configuration/adapter_types.ts new file mode 100644 index 0000000000000..69938e400aaef --- /dev/null +++ b/x-pack/plugins/secops/server/lib/configuration/adapter_types.ts @@ -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. + */ + +export interface ConfigurationAdapter { + get(): Promise; +} diff --git a/x-pack/plugins/secops/server/lib/configuration/index.ts b/x-pack/plugins/secops/server/lib/configuration/index.ts new file mode 100644 index 0000000000000..4e09b5d0e9e2d --- /dev/null +++ b/x-pack/plugins/secops/server/lib/configuration/index.ts @@ -0,0 +1,7 @@ +/* + * 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 * from './adapter_types'; diff --git a/x-pack/plugins/secops/server/lib/configuration/inmemory_configuration_adapter.ts b/x-pack/plugins/secops/server/lib/configuration/inmemory_configuration_adapter.ts new file mode 100644 index 0000000000000..d574b95755ebc --- /dev/null +++ b/x-pack/plugins/secops/server/lib/configuration/inmemory_configuration_adapter.ts @@ -0,0 +1,16 @@ +/* + * 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 { ConfigurationAdapter } from './adapter_types'; + +export class InmemoryConfigurationAdapter + implements ConfigurationAdapter { + constructor(private readonly configuration: Configuration) {} + + public async get() { + return this.configuration; + } +} diff --git a/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.test.ts b/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.test.ts new file mode 100644 index 0000000000000..07a3ce4bd9e7b --- /dev/null +++ b/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.test.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaConfigurationAdapter } from './kibana_configuration_adapter'; + +describe('the KibanaConfigurationAdapter', () => { + test('queries the xpack.secops configuration of the server', async () => { + const mockConfig = { + get: jest.fn(), + }; + + const configurationAdapter = new KibanaConfigurationAdapter({ + config: () => mockConfig, + }); + + await configurationAdapter.get(); + + expect(mockConfig.get).toBeCalledWith('xpack.secops'); + }); + + test('applies the query defaults', async () => { + const configurationAdapter = new KibanaConfigurationAdapter({ + config: () => ({ + get: () => ({}), + }), + }); + + const configuration = await configurationAdapter.get(); + + expect(configuration).toMatchObject({ + query: { + partitionSize: expect.any(Number), + partitionFactor: expect.any(Number), + }, + }); + }); +}); diff --git a/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.ts b/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.ts new file mode 100644 index 0000000000000..25cabfb4d0af3 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/configuration/kibana_configuration_adapter.ts @@ -0,0 +1,75 @@ +/* + * 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 Joi from 'joi'; + +import { ConfigurationAdapter } from './adapter_types'; + +export class KibanaConfigurationAdapter + implements ConfigurationAdapter { + private readonly server: ServerWithConfig; + + constructor(server: any) { + if (!isServerWithConfig(server)) { + throw new Error('Failed to find configuration on server.'); + } + + this.server = server; + } + + public async get() { + const config = this.server.config(); + + if (!isKibanaConfiguration(config)) { + throw new Error('Failed to access configuration of server.'); + } + + const configuration = config.get('xpack.secops') || {}; + const configurationWithDefaults = { + enabled: true, + query: { + partitionSize: 75, + partitionFactor: 1.2, + ...(configuration.query || {}), + }, + sources: {}, + ...configuration, + } as Configuration; + + // we assume this to be the configuration because Kibana would have already validated it + return configurationWithDefaults; + } +} + +interface ServerWithConfig { + config(): any; +} + +function isServerWithConfig(maybeServer: any): maybeServer is ServerWithConfig { + return ( + Joi.validate( + maybeServer, + Joi.object({ + config: Joi.func().required(), + }).unknown() + ).error === null + ); +} + +interface KibanaConfiguration { + get(key: string): any; +} + +function isKibanaConfiguration(maybeConfiguration: any): maybeConfiguration is KibanaConfiguration { + return ( + Joi.validate( + maybeConfiguration, + Joi.object({ + get: Joi.func().required(), + }).unknown() + ).error === null + ); +} diff --git a/x-pack/plugins/secops/server/lib/framework/adapter_types.ts b/x-pack/plugins/secops/server/lib/framework/adapter_types.ts new file mode 100644 index 0000000000000..574d54abc16f7 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/framework/adapter_types.ts @@ -0,0 +1,30 @@ +/* + * 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 { GraphQLSchema } from 'graphql'; + +export * from '../../../common/graphql/typed_resolvers'; + +export const internalFrameworkRequest = Symbol('internalFrameworkRequest'); + +export interface FrameworkAdapter { + version: string; + exposeStaticDir(urlPath: string, dir: string): void; + registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void; +} + +export interface FrameworkRequest { + [internalFrameworkRequest]: InternalRequest; + payload: InternalRequest['payload']; + params: InternalRequest['params']; + query: InternalRequest['query']; +} + +export interface WrappableRequest { + payload: Payload; + params: Params; + query: Query; +} diff --git a/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts b/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts new file mode 100644 index 0000000000000..0af06c488e26e --- /dev/null +++ b/x-pack/plugins/secops/server/lib/framework/apollo_server_hapi.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as GraphiQL from 'apollo-server-module-graphiql'; +import Boom from 'boom'; +import { Plugin, Request, ResponseToolkit, RouteOptions, Server } from 'hapi'; + +import { GraphQLOptions, runHttpQuery } from 'apollo-server-core'; + +export type HapiOptionsFunction = (req: Request) => GraphQLOptions | Promise; + +export interface HapiGraphQLPluginOptions { + path: string; + vhost?: string; + route?: RouteOptions; + graphqlOptions: GraphQLOptions | HapiOptionsFunction; +} + +export const graphqlHapi: Plugin = { + name: 'graphql-secops', + register: (server: Server, options: HapiGraphQLPluginOptions) => { + if (!options || !options.graphqlOptions) { + throw new Error('Apollo Server requires options.'); + } + + server.route({ + options: options.route || {}, + handler: async (request: Request, h: ResponseToolkit) => { + try { + const query = + request.method === 'post' + ? (request.payload as Record) + : (request.query as Record); + + const gqlResponse = await runHttpQuery([request], { + method: request.method.toUpperCase(), + options: options.graphqlOptions, + query, + }); + + return h.response(gqlResponse).type('application/json'); + } catch (error) { + if ('HttpQueryError' !== error.name) { + const queryError = Boom.boomify(error); + + queryError.output.payload.message = error.message; + + return queryError; + } + + if (error.isGraphQLError === true) { + return h + .response(error.message) + .code(error.statusCode) + .type('application/json'); + } + + const genericError = new Boom(error.message, { statusCode: error.statusCode }); + + if (error.headers) { + Object.keys(error.headers).forEach(header => { + genericError.output.headers[header] = error.headers[header]; + }); + } + + // Boom hides the error when status code is 500 + + genericError.output.payload.message = error.message; + + throw genericError; + } + }, + method: ['GET', 'POST'], + path: options.path || '/graphql', + vhost: options.vhost || undefined, + }); + }, +}; + +export type HapiGraphiQLOptionsFunction = ( + req?: Request +) => GraphiQL.GraphiQLData | Promise; + +export interface HapiGraphiQLPluginOptions { + path: string; + + route?: any; + + graphiqlOptions: GraphiQL.GraphiQLData | HapiGraphiQLOptionsFunction; +} + +export const graphiqlHapi: Plugin = { + name: 'graphiql-secops', + register: (server: Server, options: HapiGraphiQLPluginOptions) => { + if (!options || !options.graphiqlOptions) { + throw new Error('Apollo Server GraphiQL requires options.'); + } + + server.route({ + options: options.route || {}, + handler: async (request: Request, h: ResponseToolkit) => { + const graphiqlString = await GraphiQL.resolveGraphiQLString( + request.query, + options.graphiqlOptions, + request + ); + + return h.response(graphiqlString).type('text/html'); + }, + method: 'GET', + path: options.path || '/graphiql', + }); + }, +}; diff --git a/x-pack/plugins/secops/server/lib/framework/index.ts b/x-pack/plugins/secops/server/lib/framework/index.ts new file mode 100644 index 0000000000000..4e09b5d0e9e2d --- /dev/null +++ b/x-pack/plugins/secops/server/lib/framework/index.ts @@ -0,0 +1,7 @@ +/* + * 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 * from './adapter_types'; diff --git a/x-pack/plugins/secops/server/lib/framework/kibana_framework_adapter.ts b/x-pack/plugins/secops/server/lib/framework/kibana_framework_adapter.ts new file mode 100644 index 0000000000000..3a35652198a1e --- /dev/null +++ b/x-pack/plugins/secops/server/lib/framework/kibana_framework_adapter.ts @@ -0,0 +1,87 @@ +/* + * 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 { GraphQLSchema } from 'graphql'; +import { Request, Server } from 'hapi'; + +import { + FrameworkAdapter, + FrameworkRequest, + internalFrameworkRequest, + WrappableRequest, +} from './adapter_types'; +import { + graphiqlHapi, + graphqlHapi, + HapiGraphiQLPluginOptions, + HapiGraphQLPluginOptions, +} from './apollo_server_hapi'; + +declare module 'hapi' { + interface PluginProperties { + elasticsearch: any; + kibana: any; + } +} + +export class KibanaBackendFrameworkAdapter implements FrameworkAdapter { + public version: string; + private server: Server; + + constructor(hapiServer: Server) { + this.server = hapiServer; + this.version = hapiServer.plugins.kibana.status.plugin.version; + } + + public exposeStaticDir(urlPath: string, dir: string): void { + this.server.route({ + handler: { + directory: { + path: dir, + }, + }, + method: 'GET', + path: urlPath, + }); + } + + public registerGraphQLEndpoint(routePath: string, schema: GraphQLSchema): void { + this.server.register({ + options: { + graphqlOptions: (req: Request) => ({ + context: { req: wrapRequest(req) }, + schema, + }), + path: routePath, + }, + plugin: graphqlHapi, + }); + + this.server.register({ + options: { + graphiqlOptions: { + endpointURL: routePath, + passHeader: `'kbn-version': '${this.version}'`, + }, + path: `${routePath}/graphiql`, + }, + plugin: graphiqlHapi, + }); + } +} + +export function wrapRequest( + req: InternalRequest +): FrameworkRequest { + const { params, payload, query } = req; + + return { + [internalFrameworkRequest]: req, + params, + payload, + query, + }; +} diff --git a/x-pack/plugins/secops/server/lib/sources/adapter_types.ts b/x-pack/plugins/secops/server/lib/sources/adapter_types.ts new file mode 100644 index 0000000000000..39672654e8562 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/sources/adapter_types.ts @@ -0,0 +1,21 @@ +/* + * 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 { SourceConfiguration } from './index'; + +export type PartialSourceConfigurations = { + default?: PartialDefaultSourceConfiguration; +} & { + [sourceId: string]: PartialSourceConfiguration; +}; + +export type PartialDefaultSourceConfiguration = { + fields?: Partial; +} & Partial>>; + +export type PartialSourceConfiguration = { + fields?: Partial; +} & Pick>; diff --git a/x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.test.ts b/x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.test.ts new file mode 100644 index 0000000000000..76e12166cdd31 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.test.ts @@ -0,0 +1,111 @@ +/* + * 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 { InmemoryConfigurationAdapter } from '../configuration/inmemory_configuration_adapter'; +import { PartialSourceConfiguration } from './adapter_types'; +import { ConfigurationSourcesAdapter } from './configuration_sources_adapter'; + +describe('the ConfigurationSourcesAdapter', () => { + test('adds the default source when no sources are configured', async () => { + const sourcesAdapter = new ConfigurationSourcesAdapter( + new InmemoryConfigurationAdapter({ sources: {} }) + ); + + expect(await sourcesAdapter.getAll()).toMatchObject({ + default: { + metricAlias: expect.any(String), + logAlias: expect.any(String), + fields: { + container: expect.any(String), + host: expect.any(String), + message: expect.arrayContaining([expect.any(String)]), + pod: expect.any(String), + tiebreaker: expect.any(String), + timestamp: expect.any(String), + }, + }, + }); + }); + + test('adds missing aliases to default source when they are missing from the configuration', async () => { + const sourcesAdapter = new ConfigurationSourcesAdapter( + new InmemoryConfigurationAdapter({ + sources: { + default: {} as PartialSourceConfiguration, + }, + }) + ); + + expect(await sourcesAdapter.getAll()).toMatchObject({ + default: { + metricAlias: expect.any(String), + logAlias: expect.any(String), + }, + }); + }); + + test('adds missing fields to default source when they are missing from the configuration', async () => { + const sourcesAdapter = new ConfigurationSourcesAdapter( + new InmemoryConfigurationAdapter({ + sources: { + default: { + metricAlias: 'METRIC_ALIAS', + logAlias: 'LOG_ALIAS', + fields: { + container: 'DIFFERENT_CONTAINER_FIELD', + }, + } as PartialSourceConfiguration, + }, + }) + ); + + expect(await sourcesAdapter.getAll()).toMatchObject({ + default: { + metricAlias: 'METRIC_ALIAS', + logAlias: 'LOG_ALIAS', + fields: { + container: 'DIFFERENT_CONTAINER_FIELD', + host: expect.any(String), + message: expect.arrayContaining([expect.any(String)]), + pod: expect.any(String), + tiebreaker: expect.any(String), + timestamp: expect.any(String), + }, + }, + }); + }); + + test('adds missing fields to non-default sources when they are missing from the configuration', async () => { + const sourcesAdapter = new ConfigurationSourcesAdapter( + new InmemoryConfigurationAdapter({ + sources: { + sourceOne: { + metricAlias: 'METRIC_ALIAS', + logAlias: 'LOG_ALIAS', + fields: { + container: 'DIFFERENT_CONTAINER_FIELD', + }, + }, + }, + }) + ); + + expect(await sourcesAdapter.getAll()).toMatchObject({ + sourceOne: { + metricAlias: 'METRIC_ALIAS', + logAlias: 'LOG_ALIAS', + fields: { + container: 'DIFFERENT_CONTAINER_FIELD', + host: expect.any(String), + message: expect.arrayContaining([expect.any(String)]), + pod: expect.any(String), + tiebreaker: expect.any(String), + timestamp: expect.any(String), + }, + }, + }); + }); +}); diff --git a/x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.ts b/x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.ts new file mode 100644 index 0000000000000..309ecffd4db94 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/sources/configuration_sources_adapter.ts @@ -0,0 +1,63 @@ +/* + * 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 { ConfigurationAdapter } from '../configuration'; +import { PartialSourceConfigurations } from './adapter_types'; +import { SourceConfigurations, SourcesAdapter } from './index'; + +interface ConfigurationWithSources { + sources?: PartialSourceConfigurations; +} + +export class ConfigurationSourcesAdapter implements SourcesAdapter { + private readonly configuration: ConfigurationAdapter; + + constructor(configuration: ConfigurationAdapter) { + this.configuration = configuration; + } + + public async getAll() { + const sourceConfigurations = (await this.configuration.get()).sources || { + default: DEFAULT_SOURCE, + }; + const sourceConfigurationsWithDefault = { + ...sourceConfigurations, + default: { + ...DEFAULT_SOURCE, + ...(sourceConfigurations.default || {}), + }, + } as PartialSourceConfigurations; + + return Object.entries(sourceConfigurationsWithDefault).reduce( + (result, [sourceId, sourceConfiguration]) => + ({ + ...result, + [sourceId]: { + ...sourceConfiguration, + fields: { + ...DEFAULT_FIELDS, + ...(sourceConfiguration.fields || {}), + }, + }, + } as SourceConfigurations), + {} + ); + } +} + +const DEFAULT_FIELDS = { + container: 'docker.container.name', + host: 'beat.hostname', + message: ['message', '@message'], + pod: 'kubernetes.pod.name', + tiebreaker: '_doc', + timestamp: '@timestamp', +}; + +const DEFAULT_SOURCE = { + metricAlias: 'metricbeat-*', + logAlias: 'filebeat-*', + fields: DEFAULT_FIELDS, +}; diff --git a/x-pack/plugins/secops/server/lib/sources/index.ts b/x-pack/plugins/secops/server/lib/sources/index.ts new file mode 100644 index 0000000000000..d49451dde5de3 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/sources/index.ts @@ -0,0 +1,45 @@ +/* + * 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 { ConfigurationSourcesAdapter } from './configuration_sources_adapter'; + +export class Sources { + constructor(private readonly adapter: SourcesAdapter) {} + + public async getConfiguration(sourceId: string) { + const sourceConfigurations = await this.getAllConfigurations(); + const requestedSourceConfiguration = sourceConfigurations[sourceId]; + + if (!requestedSourceConfiguration) { + throw new Error(`Failed to find source '${sourceId}'`); + } + + return requestedSourceConfiguration; + } + + public getAllConfigurations() { + return this.adapter.getAll(); + } +} + +export interface SourcesAdapter { + getAll(): Promise; +} + +export interface SourceConfigurations { + [sourceId: string]: SourceConfiguration; +} + +export interface SourceConfiguration { + fields: { + container: string; + host: string; + message: string[]; + pod: string; + tiebreaker: string; + timestamp: string; + }; +} diff --git a/x-pack/plugins/secops/server/lib/types.ts b/x-pack/plugins/secops/server/lib/types.ts new file mode 100644 index 0000000000000..8f79dde4be45f --- /dev/null +++ b/x-pack/plugins/secops/server/lib/types.ts @@ -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 { ConfigurationAdapter } from './configuration'; +import { FrameworkAdapter, FrameworkRequest } from './framework'; +import { SourceConfigurations, Sources } from './sources'; + +export interface AppDomainLibs { + hello: string; +} + +export interface AppBackendLibs extends AppDomainLibs { + configuration: ConfigurationAdapter; + framework: FrameworkAdapter; + sources: Sources; +} + +export interface Configuration { + enabled: boolean; + query: { + partitionSize: number; + partitionFactor: number; + }; + sources: SourceConfigurations; +} + +export interface Context { + req: FrameworkRequest; +} diff --git a/x-pack/plugins/secops/yarn.lock b/x-pack/plugins/secops/yarn.lock new file mode 100644 index 0000000000000..d0e11d6d6d3cd --- /dev/null +++ b/x-pack/plugins/secops/yarn.lock @@ -0,0 +1,37 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/boom@^7.2.0": + version "7.2.1" + resolved "https://registry.yarnpkg.com/@types/boom/-/boom-7.2.1.tgz#a21e21ba08cc49d17b26baef98e1a77ee4d6cdb0" + integrity sha512-kOiap+kSa4DPoookJXQGQyKy1rjZ55tgfKAh9F0m1NUdukkcwVzpSnXPMH42a5L+U++ugdQlh/xFJu/WAdr1aw== + +"@types/color-convert@*": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-1.9.0.tgz#bfa8203e41e7c65471e9841d7e306a7cd8b5172d" + integrity sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg== + dependencies: + "@types/color-name" "*" + +"@types/color-name@*": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.0.tgz#926f76f7e66f49cc59ad880bb15b030abbf0b66d" + integrity sha512-gZ/Rb+MFXF0pXSEQxdRoPMm5jeO3TycjOdvbpbcpHX/B+n9AqaHFe5q6Ga9CsZ7ir/UgIWPfrBzUzn3F19VH/w== + +"@types/color@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/color/-/color-3.0.0.tgz#40f8a6bf2fd86e969876b339a837d8ff1b0a6e30" + integrity sha512-5qqtNia+m2I0/85+pd2YzAXaTyKO8j+svirO5aN+XaQJ5+eZ8nx0jPtEWZLxCi50xwYsX10xUHetFzfb1WEs4Q== + dependencies: + "@types/color-convert" "*" + +"@types/lodash@^4.14.110": + version "4.14.117" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.117.tgz#695a7f514182771a1e0f4345d189052ee33c8778" + integrity sha512-xyf2m6tRbz8qQKcxYZa7PA4SllYcay+eh25DN3jmNYY6gSTL7Htc/bttVdkqj2wfJGbeWlQiX8pIyJpKU+tubw== + +lodash@^4.17.10: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== 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/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 65fa2787c09af..901a71202561e 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -6,16 +6,17 @@ require('@kbn/plugin-helpers').babelRegister(); require('@kbn/test').runTestsCli([ - require.resolve('../test/reporting/configs/chromium_api.js'), - require.resolve('../test/reporting/configs/chromium_functional.js'), - require.resolve('../test/reporting/configs/phantom_api.js'), - require.resolve('../test/reporting/configs/phantom_functional.js'), - require.resolve('../test/functional/config.js'), +// These commented out tests are only for within the secops branch and should not be merged into master +// require.resolve('../test/reporting/configs/chromium_api.js'), +// require.resolve('../test/reporting/configs/chromium_functional.js'), +// require.resolve('../test/reporting/configs/phantom_api.js'), +// require.resolve('../test/reporting/configs/phantom_functional.js'), +// require.resolve('../test/functional/config.js'), require.resolve('../test/api_integration/config.js'), - require.resolve('../test/saml_api_integration/config.js'), - require.resolve('../test/spaces_api_integration/spaces_only/config'), - require.resolve('../test/spaces_api_integration/security_and_spaces/config'), - require.resolve('../test/saved_object_api_integration/security_and_spaces/config'), - require.resolve('../test/saved_object_api_integration/security_only/config'), - require.resolve('../test/saved_object_api_integration/spaces_only/config'), +// require.resolve('../test/saml_api_integration/config.js'), +// require.resolve('../test/spaces_api_integration/spaces_only/config'), +// require.resolve('../test/spaces_api_integration/security_and_spaces/config'), +// require.resolve('../test/saved_object_api_integration/security_and_spaces/config'), +// require.resolve('../test/saved_object_api_integration/security_only/config'), +// require.resolve('../test/saved_object_api_integration/spaces_only/config'), ]); 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/index.js b/x-pack/test/api_integration/apis/index.js index aa2273ce64212..3a515429292e7 100644 --- a/x-pack/test/api_integration/apis/index.js +++ b/x-pack/test/api_integration/apis/index.js @@ -6,13 +6,23 @@ export default function ({ loadTestFile }) { describe('apis', () => { - loadTestFile(require.resolve('./es')); - loadTestFile(require.resolve('./security')); - loadTestFile(require.resolve('./monitoring')); - loadTestFile(require.resolve('./xpack_main')); - loadTestFile(require.resolve('./logstash')); - loadTestFile(require.resolve('./kibana')); + // These commented out tests are only for within the secops branch and should not be merged into master + // loadTestFile(require.resolve('./es')); + // loadTestFile(require.resolve('./security')); + // loadTestFile(require.resolve('./monitoring')); + // loadTestFile(require.resolve('./xpack_main')); + // loadTestFile(require.resolve('./logstash')); + // loadTestFile(require.resolve('./kibana')); + // loadTestFile(require.resolve('./infra')); + + // TODO: I am only running infra at the moment + // but in reality I should not be running infra and + // should instead be running secops which still needs + // to be built. I kept this api integration test running for right now + // as an example. -- Frank H. + // See completion of issue: https://github.com/elastic/ingest-dev/issues/56 loadTestFile(require.resolve('./infra')); - loadTestFile(require.resolve('./beats')); + + // loadTestFile(require.resolve('./beats')); }); } 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; diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock index 75f308e00caa0..151f5f9baf1a3 100644 --- a/x-pack/yarn.lock +++ b/x-pack/yarn.lock @@ -67,6 +67,24 @@ resolved "https://registry.yarnpkg.com/@elastic/numeral/-/numeral-2.3.2.tgz#06c9ef22f18dd8c2b39ffe353868d4d0c13ea4f9" integrity sha1-BsnvIvGN2MKzn/41OGjU0ME+pPk= +"@graphql-modules/epoxy@0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@graphql-modules/epoxy/-/epoxy-0.1.6.tgz#f0555d72f5477f5a65bf880397137f422fb5bda5" + integrity sha512-DYq1RfTXEEaTYS2cKCTPNCdr6uCj3JgrERQjUMpn0bSBM28V2ncnx5ycDL+w2p/gnYiFD5LfHSJNnQ5u8/79Zg== + dependencies: + "@graphql-modules/logger" "0.1.6" + "@types/deepmerge" "2.1.0" + deepmerge "2.1.1" + graphql-tools "3.1.1" + +"@graphql-modules/logger@0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@graphql-modules/logger/-/logger-0.1.6.tgz#89da70baf9e5802f5dc970a501f500bdbd8efedf" + integrity sha512-gh2etdA4kHHXKiHQMhqub9eVBhr8rfi7Pe6/TLx/9Dmm7Ikqy0FvyDvg1zSstP5dRgGj5xm7z1EP8wxSxWHg2w== + dependencies: + moment "2.22.2" + winston "3.0.0" + "@kbn/datemath@link:../packages/kbn-datemath": version "0.0.0" uid "" @@ -231,6 +249,11 @@ resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-1.0.8.tgz#6c083127b330b3c2fc65cd0f3a6e9cbd9607b28c" integrity sha512-/UCphyyw97YAq4zKsuXH33R3UNB4jDSza0fLvMubWr/ONh9IePi1NbgFP222blhiCe724ebJs8U87+aDuAq/jA== +"@types/deepmerge@2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/deepmerge/-/deepmerge-2.1.0.tgz#22f175e5cb55874fe818caa6fd50a1d98fc3d748" + integrity sha512-/0Ct/q5g+SgaACZ+A0ylY3071nEBN7QDnTWiCtaB3fx24UpoAQXf25yNVloOYVUis7jytM1F1WC78+EOwXkQJQ== + "@types/delay@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/delay/-/delay-2.0.1.tgz#61bcf318a74b61e79d1658fbf054f984c90ef901" @@ -275,10 +298,10 @@ resolved "https://registry.yarnpkg.com/@types/graphql/-/graphql-0.13.1.tgz#7d39750355c9ecb921816d6f76c080405b5f6bea" integrity sha512-a6vRcP4M6+7Lqev1JeF3hGFmC3FNBIJ5cRnaSN/z1LtGVr/CaY6nqSlQbnFr2j8ucDlUAQU43LCnJrz6uLUyHg== -"@types/handlebars@4.0.38": - version "4.0.38" - resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.38.tgz#d0ebec1934c0bba97f99a0cb703fe2ef8c4a662f" - integrity sha512-oMzU0D7jDp+H2go/i0XqBHfr+HEhYD/e1TvkhHi3yrhQm/7JFR8FJMdvoH76X8G1FBpgc6Pwi+QslCJBeJ1N9g== +"@types/handlebars@4.0.39": + version "4.0.39" + resolved "https://registry.yarnpkg.com/@types/handlebars/-/handlebars-4.0.39.tgz#961fb54db68030890942e6aeffe9f93a957807bd" + integrity sha512-vjaS7Q0dVqFp85QhyPSZqDKnTTCemcSHNHFvDdalO1s0Ifz5KuE64jQD5xoUkfdWwF4WpqdJEl7LsWH8rzhKJA== "@types/hapi@^17.0.18": version "17.0.18" @@ -311,6 +334,11 @@ dependencies: "@types/node" "*" +"@types/is-glob@4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@types/is-glob/-/is-glob-4.0.0.tgz#fb8a2bff539025d4dcd6d5efe7689e03341b876d" + integrity sha512-zC/2EmD8scdsGIeE+Xg7kP7oi9VP90zgMQtm9Cr25av4V+a+k8slQyiT60qSw8KORYrOKlPXfHwoa1bQbRzskQ== + "@types/is-stream@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1" @@ -396,10 +424,10 @@ resolved "https://registry.yarnpkg.com/@types/podium/-/podium-1.0.0.tgz#bfaa2151be2b1d6109cc69f7faa9dac2cba3bb20" integrity sha1-v6ohUb4rHWEJzGn3+qnawsujuyA= -"@types/prettier@1.13.1": - version "1.13.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.13.1.tgz#5dd359398de96863a0629156f382913a06ebe013" - integrity sha512-OlcCdqLtMvl+Hq4UkAxxppKX252NXsBm6RyJZVuBZtkduu3Dl8pdx78XS4K7oPGPOxpD6T+KzK0DV11G8ykTkw== +"@types/prettier@1.13.2": + version "1.13.2" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.13.2.tgz#ffe96278e712a8d4e467e367a338b05e22872646" + integrity sha512-k6MCN8WuDiCj6O+UJsVMbrreZxkbrhQbO02oDj6yuRu8UAkp0MDdEcDKif8/gBKuJbT84kkO+VHQAqXkumEklg== "@types/prop-types@^15.5.3": version "15.5.3" @@ -455,6 +483,13 @@ dependencies: csstype "^2.2.0" +"@types/recompose@^0.26.0": + version "0.26.5" + resolved "https://registry.yarnpkg.com/@types/recompose/-/recompose-0.26.5.tgz#8496b63c535a60c3584b8b0aca54bfb86679ed70" + integrity sha512-Il5stz/Z3pVIMl48pyggl6nnhRLQ8N8YN8hi0Anm0M5UjVh2uMSY0ah2vzwZZKxnca4NzyJArloSjsJ9fL2vWw== + dependencies: + "@types/react" "*" + "@types/reduce-reducers@^0.1.3": version "0.1.3" resolved "https://registry.yarnpkg.com/@types/reduce-reducers/-/reduce-reducers-0.1.3.tgz#69f252207622ced7e063c7526ad46ec60b69f0c0" @@ -866,6 +901,14 @@ apollo-link@^1.0.0, apollo-link@^1.2.2: apollo-utilities "^1.0.0" zen-observable-ts "^0.8.9" +apollo-link@^1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.3.tgz#9bd8d5fe1d88d31dc91dae9ecc22474d451fb70d" + integrity sha512-iL9yS2OfxYhigme5bpTbmRyC+Htt6tyo2fRMHT3K1XRL/C5IQDDz37OjpPy4ndx7WInSvfSZaaOTKFja9VWqSw== + dependencies: + apollo-utilities "^1.0.0" + zen-observable-ts "^0.8.10" + apollo-server-core@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/apollo-server-core/-/apollo-server-core-1.3.6.tgz#08636243c2de56fa8c267d68dd602cb1fbd323e3" @@ -1186,18 +1229,13 @@ async@^2.1.4: dependencies: lodash "^4.14.0" -async@^2.5.0: +async@^2.5.0, async@^2.6.0: version "2.6.1" resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== dependencies: lodash "^4.17.10" -async@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9" - integrity sha1-+PwEyjoTeErenhZBr5hXjPvWR6k= - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -2491,31 +2529,72 @@ color-convert@^1.9.0: dependencies: color-name "^1.1.1" -color-name@^1.1.1: +color-convert@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3, color-name@^1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@^1.0.0: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +color-string@^1.5.2: + version "1.5.3" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" + integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + dependencies: + color-name "^1.0.0" + simple-swizzle "^0.2.2" + color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== +color@3.0.x: + version "3.0.0" + resolved "https://registry.yarnpkg.com/color/-/color-3.0.0.tgz#d920b4328d534a3ac8295d68f7bd4ba6c427be9a" + integrity sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w== + dependencies: + color-convert "^1.9.1" + color-string "^1.5.2" + +colornames@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" + integrity sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y= + colors@0.5.x: version "0.5.1" resolved "https://registry.yarnpkg.com/colors/-/colors-0.5.1.tgz#7d0023eaeb154e8ee9fce75dcb923d0ed1667774" integrity sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q= -colors@1.0.x: - version "1.0.3" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" - integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs= +colors@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.2.tgz#2df8ff573dfbf255af562f8ce7181d6b971a359b" + integrity sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ== colors@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" integrity sha1-FopHAXVran9RoSzgyXv6KMCE7WM= +colorspace@1.1.x: + version "1.1.1" + resolved "https://registry.yarnpkg.com/colorspace/-/colorspace-1.1.1.tgz#9ac2491e1bc6f8fb690e2176814f8d091636d972" + integrity sha512-pI3btWyiuz7Ken0BWh9Elzsmv2bM9AhA7psXib4anUXy/orfZ/E0MbQwhSOG/9L8hLlalqrU0UhOuqxW1YjmVw== + dependencies: + color "3.0.x" + text-hex "1.0.x" + combined-stream@1.0.6, combined-stream@~1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" @@ -2545,10 +2624,10 @@ commander@2.15.1: resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" integrity sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag== -commander@2.16.0: - version "2.16.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.16.0.tgz#f16390593996ceb4f3eeb020b31d78528f7f8a50" - integrity sha512-sVXqklSaotK9at437sFlFpyOcJonxe0yST/AG9DkQKUdIE6IqGIMv4SfAQSKaJbSdVEJYItASCrBiVQHq1HQew== +commander@2.18.0: + version "2.18.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" + integrity sha512-6CYPa+JP2ftfRU2qkDK+UTVeQYosOg/2GbcjIcKPHfinyOLPVGXu/ovN86RP49Re5ndJK1N0kuiidFFuepc4ZQ== commander@~2.17.1: version "2.17.1" @@ -2871,11 +2950,6 @@ currently-unhandled@^0.4.1: dependencies: array-find-index "^1.0.1" -cycle@1.0.x: - version "1.0.3" - resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" - integrity sha1-IegLK+hYD5i0aPN5QwZisEbDStI= - cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" @@ -3102,6 +3176,11 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= +deepmerge@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.1.1.tgz#e862b4e45ea0555072bf51e7fd0d9845170ae768" + integrity sha512-urQxA1smbLZ2cBbXbaYObM1dJ82aJ2H57A1C/Kklfh/ZN1bgH4G/n5KWhdNfOK11W98gqZfyYj7W4frJJRwA2w== + default-require-extensions@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" @@ -3224,6 +3303,15 @@ dfa@^1.0.0: dependencies: babel-runtime "^6.11.6" +diagnostics@^1.0.1, diagnostics@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a" + integrity sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ== + dependencies: + colorspace "1.1.x" + enabled "1.0.x" + kuler "1.0.x" + diff@3.5.0, diff@^3.1.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" @@ -3392,6 +3480,13 @@ emojis-list@^2.0.0: resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= +enabled@1.0.x: + version "1.0.2" + resolved "https://registry.yarnpkg.com/enabled/-/enabled-1.0.2.tgz#965f6513d2c2d1c5f4652b64a2e3396467fc2f93" + integrity sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M= + dependencies: + env-variable "0.0.x" + encoding@^0.1.11: version "0.1.12" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" @@ -3460,6 +3555,11 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" integrity sha1-blwtClYhtdra7O+AuQ7ftc13cvA= +env-variable@0.0.x: + version "0.0.5" + resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.5.tgz#913dd830bef11e96a039c038d4130604eba37f88" + integrity sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA== + enzyme-adapter-react-16@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.1.1.tgz#a8f4278b47e082fbca14f5bfb1ee50ee650717b4" @@ -3852,11 +3952,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -eyes@0.1.x: - version "0.1.8" - resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" - integrity sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A= - falafel@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/falafel/-/falafel-2.1.0.tgz#96bb17761daba94f46d001738b3cedf3a67fe06c" @@ -3896,7 +3991,12 @@ fast-levenshtein@~2.0.4: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= -fb-watchman@^2.0.0: +fast-safe-stringify@^2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz#04b26106cc56681f51a044cfc0d76cf0008ac2c2" + integrity sha512-q8BZ89jjc+mz08rSxROs8VsrBBcn1SIw1kq9NjolL509tkABRk9io01RAjSaEv1Xb2uFLt8VtRiZbGp5H8iDtg== + +fb-watchman@2.0.0, fb-watchman@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" integrity sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg= @@ -3943,6 +4043,11 @@ fd-slicer@~1.1.0: dependencies: pend "~1.2.0" +fecha@^2.3.3: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" + integrity sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg== + fetch-mock@^5.13.1: version "5.13.1" resolved "https://registry.yarnpkg.com/fetch-mock/-/fetch-mock-5.13.1.tgz#955794a77f3d972f1644b9ace65a0fdfd60f1df7" @@ -4473,6 +4578,18 @@ glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glo once "^1.3.0" path-is-absolute "^1.0.0" +glob@7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^4.3.1: version "4.5.3" resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" @@ -4646,55 +4763,63 @@ graphql-anywhere@^4.1.16: dependencies: apollo-utilities "^1.0.18" -graphql-code-generator@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/graphql-code-generator/-/graphql-code-generator-0.10.1.tgz#b1c05b726ad4c9078a609233cb364eeeb512b5ab" - integrity sha1-scBbcmrUyQeKYJIzyzZO7rUStas= +graphql-code-generator@^0.12.6: + version "0.12.6" + resolved "https://registry.yarnpkg.com/graphql-code-generator/-/graphql-code-generator-0.12.6.tgz#72835046d6c833834d616135c59ad19b7af85d24" + integrity sha512-GSU06AJ4sPsqVKpKzK2KfQM+D2WtnGoazey5iLVUfef5LSYNJqFGsCHMN6bTcCoBLUTXs5gq79yaQvJp5P2gbg== dependencies: + "@graphql-modules/epoxy" "0.1.6" "@types/babylon" "6.16.3" - "@types/prettier" "1.13.1" + "@types/is-glob" "4.0.0" + "@types/prettier" "1.13.2" "@types/valid-url" "1.0.2" babel-types "7.0.0-beta.3" babylon "7.0.0-beta.47" - commander "2.16.0" - glob "7.1.2" - graphql-codegen-compiler "0.10.1" - graphql-codegen-core "0.10.1" + commander "2.18.0" + fb-watchman "2.0.0" + glob "7.1.3" + graphql-codegen-compiler "0.12.6" + graphql-codegen-core "0.12.6" + graphql-import "0.7.1" + is-glob "4.0.0" + is-valid-path "0.1.1" mkdirp "0.5.1" - prettier "1.13.7" - request "2.87.0" + pify "4.0.0" + prettier "1.14.3" + request "2.88.0" valid-url "1.0.9" -graphql-codegen-compiler@0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/graphql-codegen-compiler/-/graphql-codegen-compiler-0.10.1.tgz#de9a6dd037a665a1ad276b3366987e8a5f58bb9f" - integrity sha1-3ppt0DemZaGtJ2szZph+il9Yu58= +graphql-codegen-compiler@0.12.6: + version "0.12.6" + resolved "https://registry.yarnpkg.com/graphql-codegen-compiler/-/graphql-codegen-compiler-0.12.6.tgz#26b8e4c0fe4003c7feb9091d6b259c2505dd837d" + integrity sha512-f/GP19sMsXggdQHZLb7uZVaiuhDX/EqadeaZI9bRfPaALK02hLIOizTYQBKOVMExSmgj3Hiz6DGgu61gRO6z1w== dependencies: - "@types/handlebars" "4.0.38" + "@types/handlebars" "4.0.39" change-case "3.0.2" common-tags "1.8.0" - graphql-codegen-core "0.10.1" - handlebars "4.0.11" + graphql-codegen-core "0.12.6" + handlebars "4.0.12" moment "2.22.2" -graphql-codegen-core@0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/graphql-codegen-core/-/graphql-codegen-core-0.10.1.tgz#f7ab790b050f14b133c5a28f2260cdd8d1e7792d" - integrity sha1-96t5CwUPFLEzxaKPImDN2NHneS0= +graphql-codegen-core@0.12.6: + version "0.12.6" + resolved "https://registry.yarnpkg.com/graphql-codegen-core/-/graphql-codegen-core-0.12.6.tgz#ab3d4d9b0bd10268de749ae215eba84f7bb98fdb" + integrity sha512-yRdLsPEd3b4bJ9aYalGVwa7XKNNK3DP85Yq0nCPcqbfj+OnR7xiVNbfd/oshQ4Mb05QOzSw7sDKBVluA0WTXog== dependencies: graphql-tag "2.9.2" - graphql-tools "3.0.4" - winston "2.4.3" + graphql-tools "4.0.0" + ts-log "2.1.3" + winston "3.1.0" -graphql-codegen-introspection-template@^0.10.5: - version "0.10.5" - resolved "https://registry.yarnpkg.com/graphql-codegen-introspection-template/-/graphql-codegen-introspection-template-0.10.5.tgz#c8c0f647a9771c4e3c6ffbe26aa8da15af9af661" - integrity sha1-yMD2R6l3HE48b/viaqjaFa+a9mE= +graphql-codegen-introspection-template@^0.12.6: + version "0.12.6" + resolved "https://registry.yarnpkg.com/graphql-codegen-introspection-template/-/graphql-codegen-introspection-template-0.12.6.tgz#57e37384d1dd7fba14a0ac4fc8c989f56a358d8c" + integrity sha512-oHgsqz17oxBC84KT/srzBMCN4fByhe0yMMXaEiUuNQyrcj26eWP2qQBygheYAlcED3cYQ/eW8PL9dwphWxT9iQ== -graphql-codegen-typescript-template@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/graphql-codegen-typescript-template/-/graphql-codegen-typescript-template-0.10.1.tgz#bb631d2ebfcd8f15117ca642a9988bd767183e02" - integrity sha1-u2MdLr/NjxURfKZCqZiL12cYPgI= +graphql-codegen-typescript-template@^0.12.6: + version "0.12.6" + resolved "https://registry.yarnpkg.com/graphql-codegen-typescript-template/-/graphql-codegen-typescript-template-0.12.6.tgz#3120089a7aea93373261377b0e0f89792c8bb5ad" + integrity sha512-Qn0EvuFwOmUy4drNEugczNdSmpNtFjJ3oJAdvLW1nnM54GNMlm+9LJLmZfifjG1uB4nvKORV9+v3dY2fKrxaKQ== graphql-extensions@^0.0.x, graphql-extensions@~0.0.9: version "0.0.10" @@ -4709,12 +4834,42 @@ graphql-fields@^1.0.2: resolved "https://registry.yarnpkg.com/graphql-fields/-/graphql-fields-1.0.2.tgz#099ee1d4445b42d0f47e06d622acebb33abc6cce" integrity sha1-CZ7h1ERbQtD0fgbWIqzrszq8bM4= +graphql-import@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/graphql-import/-/graphql-import-0.7.1.tgz#4add8d91a5f752d764b0a4a7a461fcd93136f223" + integrity sha512-YpwpaPjRUVlw2SN3OPljpWbVRWAhMAyfSba5U47qGMOSsPLi2gYeJtngGpymjm9nk57RFWEpjqwh4+dpYuFAPw== + dependencies: + lodash "^4.17.4" + resolve-from "^4.0.0" + graphql-tag@2.9.2, graphql-tag@^2.9.2: version "2.9.2" resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.9.2.tgz#2f60a5a981375f430bf1e6e95992427dc18af686" integrity sha512-qnNmof9pAqj/LUzs3lStP0Gw1qhdVCUS7Ab7+SUB6KD5aX1uqxWQRwMnOGTkhKuLvLNIs1TvNz+iS9kUGl1MhA== -graphql-tools@3.0.4, graphql-tools@^3.0.2: +graphql-tools@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-3.1.1.tgz#d593358f01e7c8b1671a17b70ddb034dea9dbc50" + integrity sha512-yHvPkweUB0+Q/GWH5wIG60bpt8CTwBklCSzQdEHmRUgAdEQKxw+9B7zB3dG7wB3Ym7M7lfrS4Ej+jtDZfA2UXg== + dependencies: + apollo-link "^1.2.2" + apollo-utilities "^1.0.1" + deprecated-decorator "^0.1.6" + iterall "^1.1.3" + uuid "^3.1.0" + +graphql-tools@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-4.0.0.tgz#6ea01937c6f947212f83567ba687e97c22fdd2a6" + integrity sha512-WokvjkanuZwY4BZBS3SlkDjrjCPu7WlCtLB2i9JiiXembVEkNos3Rl90zf7sJu72zSidGzTXU63iXRO2Fg3TtA== + dependencies: + apollo-link "^1.2.3" + apollo-utilities "^1.0.1" + deprecated-decorator "^0.1.6" + iterall "^1.1.3" + uuid "^3.1.0" + +graphql-tools@^3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-3.0.4.tgz#d08aa75db111d704cba05d92afd67ec5d1dc6b24" integrity sha512-doQeqej/1D7kowXjYaAKk9H04KZN6+Vm6/KqXk3iqq8kfeI5edHOCg+eDc4LZXb0EEue0vy76JW7TLOwf9ZMZQ== @@ -4838,18 +4993,7 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" -handlebars@4.0.11, handlebars@^4.0.3: - version "4.0.11" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" - integrity sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw= - dependencies: - async "^1.4.0" - optimist "^0.6.1" - source-map "^0.4.4" - optionalDependencies: - uglify-js "^2.6" - -handlebars@^4.0.10: +handlebars@4.0.12, handlebars@^4.0.10: version "4.0.12" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5" integrity sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA== @@ -4860,6 +5004,17 @@ handlebars@^4.0.10: optionalDependencies: uglify-js "^3.1.4" +handlebars@^4.0.3: + version "4.0.11" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" + integrity sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw= + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + hapi-auth-cookie@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/hapi-auth-cookie/-/hapi-auth-cookie-9.0.0.tgz#3b0af443334e2bd92490ddb17bed16e3e9edfd01" @@ -5444,6 +5599,11 @@ is-arrayish@^0.2.1: resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0= +is-arrayish@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.3.2.tgz#4574a2ae56f7ab206896fb431eaeed066fdf8f03" + integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== + is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -5563,6 +5723,13 @@ is-generator-fn@^1.0.0: resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-1.0.0.tgz#969d49e1bb3329f6bb7f09089be26578b2ddd46a" integrity sha1-lp1J4bszKfa7fwkIm+JleLLd1Go= +is-glob@4.0.0, is-glob@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= + dependencies: + is-extglob "^2.1.1" + is-glob@^2.0.0, is-glob@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" @@ -5577,12 +5744,12 @@ is-glob@^3.1.0: dependencies: is-extglob "^2.1.0" -is-glob@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" - integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= +is-invalid-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-invalid-path/-/is-invalid-path-0.1.0.tgz#307a855b3cf1a938b44ea70d2c61106053714f34" + integrity sha1-MHqFWzzxqTi0TqcNLGEQYFNxTzQ= dependencies: - is-extglob "^2.1.1" + is-glob "^2.0.0" is-lower-case@^1.1.0: version "1.1.3" @@ -5752,6 +5919,13 @@ is-valid-glob@^1.0.0: resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa" integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao= +is-valid-path@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-valid-path/-/is-valid-path-0.1.1.tgz#110f9ff74c37f663e1ec7915eb451f2db93ac9df" + integrity sha1-EQ+f90w39mPh7HkV60UfLbk6yd8= + dependencies: + is-invalid-path "^0.1.0" + is-windows@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.1.tgz#310db70f742d259a16a369202b51af84233310d9" @@ -5799,7 +5973,7 @@ isomorphic-fetch@2.2.1, isomorphic-fetch@^2.1.1: node-fetch "^1.0.1" whatwg-fetch ">=0.10.0" -isstream@0.1.x, isstream@~0.1.2: +isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= @@ -6499,6 +6673,13 @@ kleur@^2.0.1: resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300" integrity sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ== +kuler@1.0.x: + version "1.0.1" + resolved "https://registry.yarnpkg.com/kuler/-/kuler-1.0.1.tgz#ef7c784f36c9fb6e16dd3150d152677b2b0228a6" + integrity sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ== + dependencies: + colornames "^1.1.1" + lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" @@ -6964,6 +7145,17 @@ lodash@~1.0.1: resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" integrity sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE= +logform@^1.9.0, logform@^1.9.1: + version "1.10.0" + resolved "https://registry.yarnpkg.com/logform/-/logform-1.10.0.tgz#c9d5598714c92b546e23f4e78147c40f1e02012e" + integrity sha512-em5ojIhU18fIMOw/333mD+ZLE2fis0EzXl1ZwHx4iQzmpQi6odNiY/t+ITNr33JZhT9/KEaH+UPIipr6a9EjWg== + dependencies: + colors "^1.2.1" + fast-safe-stringify "^2.0.4" + fecha "^2.3.3" + ms "^2.1.1" + triple-beam "^1.2.0" + loglevel@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.1.tgz#e0fc95133b6ef276cdc8887cdaf24aa6f156f8fa" @@ -7899,6 +8091,11 @@ once@~1.3.0: dependencies: wrappy "1" +one-time@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/one-time/-/one-time-0.0.4.tgz#f8cdf77884826fe4dff93e3a9cc37b1e4480742e" + integrity sha1-+M33eISCb+Tf+T46nMN7HkSAdC4= + onetime@^1.0.0: version "1.1.0" resolved "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" @@ -8333,6 +8530,11 @@ pez@4.x.x: hoek "5.x.x" nigel "3.x.x" +pify@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.0.tgz#db04c982b632fd0df9090d14aaf1c8413cadb695" + integrity sha512-zrSP/KDf9DH3K3VePONoCstgPiYJy9z0SCatZuTpOc7YdnWIqwkWdXOuwlr4uDc7em8QZRsFWsT/685x5InjYg== + pify@^2.0.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" @@ -8490,10 +8692,10 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= -prettier@1.13.7: - version "1.13.7" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281" - integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w== +prettier@1.14.3: + version "1.14.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895" + integrity sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg== pretty-format@^23.6.0: version "23.6.0" @@ -9214,7 +9416,7 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -9509,31 +9711,31 @@ request@2.81.0, "request@>=2.9.0 <2.82.0": tunnel-agent "^0.6.0" uuid "^3.0.0" -request@2.87.0: - version "2.87.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" - integrity sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw== +request@2.88.0, request@^2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== dependencies: aws-sign2 "~0.7.0" - aws4 "^1.6.0" + aws4 "^1.8.0" caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" + combined-stream "~1.0.6" + extend "~3.0.2" forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" + form-data "~2.3.2" + har-validator "~5.1.0" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" + mime-types "~2.1.19" + oauth-sign "~0.9.0" performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - tough-cookie "~2.3.3" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" tunnel-agent "^0.6.0" - uuid "^3.1.0" + uuid "^3.3.2" request@^2.83.0: version "2.83.0" @@ -9591,32 +9793,6 @@ request@^2.85.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - request@~2.79.0: version "2.79.0" resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" @@ -9683,6 +9859,11 @@ resolve-from@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" integrity sha1-six699nWiBvItuZTM17rywoYh0g= +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve-options@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" @@ -10067,6 +10248,13 @@ simple-git@^1.91.0: dependencies: debug "^3.1.0" +simple-swizzle@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" + integrity sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo= + dependencies: + is-arrayish "^0.3.1" + sinon@^5.0.7: version "5.0.7" resolved "https://registry.yarnpkg.com/sinon/-/sinon-5.0.7.tgz#3bded6a73613ccc9e512e20246ced69a27c27dab" @@ -10781,6 +10969,11 @@ text-encoding@^0.6.4: resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" integrity sha1-45mpgiV6J22uQou5KEXLcb3CbRk= +text-hex@1.0.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" + integrity sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg== + throat@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" @@ -11022,6 +11215,11 @@ trim-right@^1.0.1: resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= +triple-beam@^1.2.0, triple-beam@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" + integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== + "true-case-path@^1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.2.tgz#7ec91130924766c7f573be3020c34f8fdfd00d62" @@ -11029,6 +11227,11 @@ trim-right@^1.0.1: dependencies: glob "^6.0.4" +ts-log@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.1.3.tgz#9e30aca1baffe7693a2e4142b8f07ecb01cb8340" + integrity sha512-VIk9+hzE80UjhJcSANst8LGRBpfNh32y9d3LVDMtEqcEb1x0hB71IO0aObNcLJ5VpK5tKeF9uI4pwEco03SkwA== + tslib@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" @@ -11604,17 +11807,43 @@ window-size@^0.2.0: resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" integrity sha1-tDFbtCFKPXBY6+7okuE/ok2YsHU= -winston@2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.3.tgz#7a9fdab371b6d3d9b63a592947846d856948c517" - integrity sha512-GYKuysPz2pxYAVJD2NPsDLP5Z79SDEzPm9/j4tCjkF/n89iBNGBMJcR+dMUqxgPNgoSs6fVygPi+Vl2oxIpBuw== - dependencies: - async "~1.0.0" - colors "1.0.x" - cycle "1.0.x" - eyes "0.1.x" - isstream "0.1.x" +winston-transport@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.2.0.tgz#a20be89edf2ea2ca39ba25f3e50344d73e6520e5" + integrity sha512-0R1bvFqxSlK/ZKTH86nymOuKv/cT1PQBMuDdA7k7f0S9fM44dNH6bXnuxwXPrN8lefJgtZq08BKdyZ0DZIy/rg== + dependencies: + readable-stream "^2.3.6" + triple-beam "^1.2.0" + +winston@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.0.0.tgz#1f0b24a96586798bcf0cd149fb07ed47cb01a1b2" + integrity sha512-7QyfOo1PM5zGL6qma6NIeQQMh71FBg/8fhkSAePqtf5YEi6t+UrPDcUuHhuuUasgso49ccvMEsmqr0GBG2qaMQ== + dependencies: + async "^2.6.0" + diagnostics "^1.0.1" + is-stream "^1.1.0" + logform "^1.9.0" + one-time "0.0.4" + readable-stream "^2.3.6" stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.2.0" + +winston@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/winston/-/winston-3.1.0.tgz#80724376aef164e024f316100d5b178d78ac5331" + integrity sha512-FsQfEE+8YIEeuZEYhHDk5cILo1HOcWkGwvoidLrDgPog0r4bser1lEIOco2dN9zpDJ1M88hfDgZvxe5z4xNcwg== + dependencies: + async "^2.6.0" + diagnostics "^1.1.1" + is-stream "^1.1.0" + logform "^1.9.1" + one-time "0.0.4" + readable-stream "^2.3.6" + stack-trace "0.0.x" + triple-beam "^1.3.0" + winston-transport "^4.2.0" wordwrap@0.0.2: version "0.0.2" @@ -11909,6 +12138,13 @@ yeast@0.1.2: resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" integrity sha1-AI4G2AlDIMNy28L47XagymyKxBk= +zen-observable-ts@^0.8.10: + version "0.8.10" + resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.10.tgz#18e2ce1c89fe026e9621fd83cc05168228fce829" + integrity sha512-5vqMtRggU/2GhePC9OU4sYEWOdvmayp2k3gjPf4F0mXwB3CSbbNznfDUvDJx9O2ZTa1EIXdJhPchQveFKwNXPQ== + dependencies: + zen-observable "^0.8.0" + zen-observable-ts@^0.8.6, zen-observable-ts@^0.8.9: version "0.8.9" resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.9.tgz#d3c97af08c0afdca37ebcadf7cc3ee96bda9bab1"