diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 516de74378cbca..0993876f98a6a7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -262,8 +262,31 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib # Enterprise Search # Shared -/x-pack/plugins/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/* @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/common/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/* @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/* @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/shared/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/public/applications/__mocks__/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/* @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/lib/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/__mocks__/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/lib/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/routes/enterprise_search/ @elastic/enterprise-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/enterprise_search/ @elastic/enterprise-search-frontend /x-pack/test/functional_enterprise_search/ @elastic/enterprise-search-frontend +# App Search +/x-pack/plugins/enterprise_search/public/applications/app_search/ @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/routes/app_search/ @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/app_search/ @elastic/app-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/app_search/ @elastic/app-search-frontend +# Workplace Search +/x-pack/plugins/enterprise_search/public/applications/workplace_search/ @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/routes/workplace_search/ @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/collectors/workplace_search/ @elastic/workplace-search-frontend +/x-pack/plugins/enterprise_search/server/saved_objects/workplace_search/ @elastic/workplace-search-frontend # Elasticsearch UI /src/plugins/dev_tools/ @elastic/es-ui diff --git a/docs/api/saved-objects/create.asciidoc b/docs/api/saved-objects/create.asciidoc index 50809a1bd5d4e2..d7a368034ef07f 100644 --- a/docs/api/saved-objects/create.asciidoc +++ b/docs/api/saved-objects/create.asciidoc @@ -9,11 +9,13 @@ experimental[] Create {kib} saved objects. [[saved-objects-api-create-request]] ==== Request -`POST :/api/saved_objects/` + +`POST :/api/saved_objects/` `POST :/api/saved_objects//` -`POST :/s//saved_objects/` +`POST :/s//api/saved_objects/` + +`POST :/s//api/saved_objects//` [[saved-objects-api-create-path-params]] ==== Path parameters diff --git a/packages/kbn-babel-preset/common_preset.js b/packages/kbn-babel-preset/common_preset.js index 8e2f1d207f3f41..b14dcd8971c31c 100644 --- a/packages/kbn-babel-preset/common_preset.js +++ b/packages/kbn-babel-preset/common_preset.js @@ -28,18 +28,19 @@ const plugins = [ // See https://github.com/babel/proposals/issues/12 for progress require.resolve('@babel/plugin-proposal-class-properties'), - // Optional Chaining proposal is stage 3 (https://github.com/tc39/proposal-optional-chaining) + // Optional Chaining proposal is stage 4 (https://github.com/tc39/proposal-optional-chaining) // Need this since we are using TypeScript 3.7+ require.resolve('@babel/plugin-proposal-optional-chaining'), - // Nullish coalescing proposal is stage 3 (https://github.com/tc39/proposal-nullish-coalescing) + + // Nullish coalescing proposal is stage 4 (https://github.com/tc39/proposal-nullish-coalescing) // Need this since we are using TypeScript 3.7+ require.resolve('@babel/plugin-proposal-nullish-coalescing-operator'), - // Proposal is on stage 4 (https://github.com/tc39/proposal-export-ns-from) + // Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from) // Need this since we are using TypeScript 3.8+ require.resolve('@babel/plugin-proposal-export-namespace-from'), - // Proposal is on stage 4 (https://github.com/tc39/proposal-export-ns-from) + // Proposal is on stage 4, and included in ECMA-262 (https://github.com/tc39/proposal-export-ns-from) // Need this since we are using TypeScript 3.9+ require.resolve('@babel/plugin-proposal-private-methods'), ]; diff --git a/packages/kbn-monaco/package.json b/packages/kbn-monaco/package.json index e2406a73f5342d..eef68d3a35e0c7 100644 --- a/packages/kbn-monaco/package.json +++ b/packages/kbn-monaco/package.json @@ -11,5 +11,8 @@ "devDependencies": { "@kbn/babel-preset": "link:../kbn-babel-preset", "@kbn/dev-utils": "link:../kbn-dev-utils" + }, + "dependencies": { + "@kbn/i18n": "link:../kbn-i18n" } } \ No newline at end of file diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 6af14734444d1d..15df6b34e22ffc 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -39,9 +39,9 @@ export class DocLinksService { dashboard: { guide: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/dashboard.html`, drilldowns: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html`, - drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html#url-drilldown`, + drilldownsTriggerPicker: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/drilldowns.html#url-drilldowns`, urlDrilldownTemplateSyntax: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html`, - urlDrilldownVariables: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html#variables`, + urlDrilldownVariables: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/url_templating-language.html#url-template-variables`, }, filebeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/filebeat/${DOC_LINK_VERSION}`, diff --git a/src/plugins/charts/README.md b/src/plugins/charts/README.md index 31727b7acb7a1a..dae7b9695ed60e 100644 --- a/src/plugins/charts/README.md +++ b/src/plugins/charts/README.md @@ -27,3 +27,7 @@ Truncated color mappings in `value`/`text` form ## Theme See Theme service [docs](public/services/theme/README.md) + +## Palettes + +See palette service [docs](public/services/palettes/README.md) diff --git a/src/plugins/charts/public/services/palettes/README.md b/src/plugins/charts/public/services/palettes/README.md new file mode 100644 index 00000000000000..3403d422682bdb --- /dev/null +++ b/src/plugins/charts/public/services/palettes/README.md @@ -0,0 +1,33 @@ +# Palette Service + +The `palette` service offers a collection of palettes which implement a uniform interface for assigning colors to charts. The service provides methods for switching palettes +easily. It's used by the x-pack plugins `canvas` and `lens`. + +Each palette is allowed to store some state as well which has to be handled by the consumer. + +Palettes are integrated with the expression as well using the `system_palette` and `palette` functions. + +## Using the palette service + +To consume the palette service, use `charts.palettes.getPalettes` to lazily load the async bundle implementing existing palettes. This is recommended to be called in the renderer, not as part of the `setup` or `start` phases of a plugin. + +All palette definitions can be loaded using `paletteService.getAll()`. If the id of the palette is known, it can be fetched using `paleteService.get(id)`. + +One a palette is loaded, there are two ways to request colors - either by fetching a list of colors (`getColors`) or by specifying the chart object to be colored (`getColor`). If possible, using `getColor` is recommended because it allows the palette implementation to apply custom logic to coloring (e.g. lightening up colors or syncing colors) which has to be implemented by the consumer if `getColors` is used). + +### SeriesLayer + +If `getColor` is used, an array of `SeriesLayer` objects has to be passed in. These correspond with the current series in the chart a color has to be determined for. An array is necessary as some charts are constructed hierarchically (e.g. pie charts or treemaps). The array of objects represents the current series with all ancestors up to the corresponding root series. For each layer in the series hierarchy, the number of "sibling" series and the position of the current series has to be specified along with the name of the series. + +## Custom palette + +All palettes are stateless and define their own colors except for the `custom` palette which takes a state of the form +```ts +{ colors: string[]; gradient: boolean } +``` + +This state has to be passed into the `getColors` and `getColor` function to retrieve specific colors. + +## Registering new palettes + +Currently palettes can't be extended dynamically. diff --git a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts index 37657912deb95b..60d05890028d10 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/ecommerce/saved_objects.ts @@ -294,8 +294,7 @@ export const getSavedObjects = (): SavedObject[] => [ attributes: { title: 'kibana_sample_data_ecommerce', timeFieldName: 'order_date', - fields: - '[{"name":"_id","type":"string","esTypes":["_id"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_index","type":"string","esTypes":["_index"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_score","type":"number","count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_source","type":"_source","esTypes":["_source"],"count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_type","type":"string","esTypes":["_type"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"category","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"category.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"category"}}},{"name":"currency","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_birth_date","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_first_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_first_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_first_name"}}},{"name":"customer_full_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_full_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_full_name"}}},{"name":"customer_gender","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_id","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"customer_last_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"customer_last_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"customer_last_name"}}},{"name":"customer_phone","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"day_of_week","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"day_of_week_i","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"email","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"event.dataset","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.city_name","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.continent_name","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.country_iso_code","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.location","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geoip.region_name","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"manufacturer","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"manufacturer.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"manufacturer"}}},{"name":"order_date","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"order_id","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products._id","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products._id.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products._id"}}},{"name":"products.base_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.base_unit_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.category","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products.category.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products.category"}}},{"name":"products.created_on","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.discount_amount","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.discount_percentage","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.manufacturer","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products.manufacturer.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products.manufacturer"}}},{"name":"products.min_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.product_id","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.product_name","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"products.product_name.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent":"products.product_name"}}},{"name":"products.quantity","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.sku","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.tax_amount","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.taxful_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.taxless_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"products.unit_discount_amount","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"sku","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"taxful_total_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"taxless_total_price","type":"number","esTypes":["half_float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"total_quantity","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"total_unique_products","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"type","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"user","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true}]', + fields: '[]', fieldFormatMap: '{"taxful_total_price":{"id":"number","params":{"pattern":"$0,0.[00]"}}}', }, references: [], diff --git a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts index 6f701d75e7d52d..e65b6ad40651bc 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/flights/saved_objects.ts @@ -439,7 +439,7 @@ export const getSavedObjects = (): SavedObject[] => [ title: 'kibana_sample_data_flights', timeFieldName: 'timestamp', fields: - '[{"name":"AvgTicketPrice","type":"number","esTypes":["float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"Cancelled","type":"boolean","esTypes":["boolean"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"Carrier","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"Dest","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestAirportID","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestCityName","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestCountry","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestLocation","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestRegion","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DestWeather","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DistanceKilometers","type":"number","esTypes":["float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"DistanceMiles","type":"number","esTypes":["float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightDelay","type":"boolean","esTypes":["boolean"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightDelayMin","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightDelayType","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightNum","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightTimeHour","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"FlightTimeMin","type":"number","esTypes":["float"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"Origin","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginAirportID","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginCityName","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginCountry","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginLocation","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginRegion","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"OriginWeather","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"_id","type":"string","esTypes":["_id"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_index","type":"string","esTypes":["_index"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_score","type":"number","count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_source","type":"_source","esTypes":["_source"],"count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_type","type":"string","esTypes":["_type"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"dayOfWeek","type":"number","esTypes":["integer"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"timestamp","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.hourOfDay","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', + '[{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.hourOfDay","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', fieldFormatMap: '{"hour_of_day":{"id":"number","params":{"pattern":"00"}},"AvgTicketPrice":{"id":"number","params":{"pattern":"$0,0.[00]"}}}', }, diff --git a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts index f8d39e6689fa8b..068ba66c4b0ded 100644 --- a/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts +++ b/src/plugins/home/server/services/sample_data/data_sets/logs/saved_objects.ts @@ -270,7 +270,7 @@ export const getSavedObjects = (): SavedObject[] => [ title: 'kibana_sample_data_logs', timeFieldName: 'timestamp', fields: - '[{"name":"@timestamp","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"_id","type":"string","esTypes":["_id"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_index","type":"string","esTypes":["_index"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"_score","type":"number","count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_source","type":"_source","esTypes":["_source"],"count":0,"scripted":false,"searchable":false,"aggregatable":false,"readFromDocValues":false},{"name":"_type","type":"string","esTypes":["_type"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":false},{"name":"agent","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"agent.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "agent"}}},{"name":"bytes","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"clientip","type":"ip","esTypes":["ip"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"event.dataset","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"extension","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"extension.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "extension"}}},{"name":"geo.coordinates","type":"geo_point","esTypes":["geo_point"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geo.dest","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geo.src","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"geo.srcdest","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"host","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"host.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "host"}}},{"name":"index","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"index.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "index"}}},{"name":"ip","type":"ip","esTypes":["ip"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"machine.os","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"machine.os.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "machine.os"}}},{"name":"machine.ram","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"memory","type":"number","esTypes":["double"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"message","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"message.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "message"}}},{"name":"phpmemory","type":"number","esTypes":["long"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"referer","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"request","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"request.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "request"}}},{"name":"response","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"response.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "response"}}},{"name":"tags","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"tags.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "tags"}}},{"name":"timestamp","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"url","type":"string","esTypes":["text"],"count":0,"scripted":false,"searchable":true,"aggregatable":false,"readFromDocValues":false},{"name":"url.keyword","type":"string","esTypes":["keyword"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true,"subType":{"multi":{"parent": "url"}}},{"name":"utc_time","type":"date","esTypes":["date"],"count":0,"scripted":false,"searchable":true,"aggregatable":true,"readFromDocValues":true},{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.getHour()","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', + '[{"name":"hour_of_day","type":"number","count":0,"scripted":true,"script":"doc[\'timestamp\'].value.getHour()","lang":"painless","searchable":true,"aggregatable":true,"readFromDocValues":false}]', fieldFormatMap: '{"hour_of_day":{}}', }, references: [], diff --git a/src/plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap b/src/plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap index 35349b47196761..696b74d040e0c5 100644 --- a/src/plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap +++ b/src/plugins/input_control_vis/public/__snapshots__/input_control_fn.test.ts.snap @@ -2,7 +2,7 @@ exports[`interpreter/functions#input_control_vis returns an object with the correct structure 1`] = ` Object { - "as": "visualization", + "as": "input_control_vis", "type": "render", "value": Object { "visConfig": Object { diff --git a/src/plugins/input_control_vis/public/__snapshots__/to_ast.test.ts.snap b/src/plugins/input_control_vis/public/__snapshots__/to_ast.test.ts.snap new file mode 100644 index 00000000000000..edd44d8dd03374 --- /dev/null +++ b/src/plugins/input_control_vis/public/__snapshots__/to_ast.test.ts.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`input_control_vis toExpressionAst should build an expression based on vis.params 1`] = ` +Object { + "chain": Array [ + Object { + "arguments": Object { + "visConfig": Array [ + "{\\"controls\\":[{\\"id\\":\\"1536977437774\\",\\"fieldName\\":\\"manufacturer.keyword\\",\\"parent\\":\\"\\",\\"label\\":\\"Manufacturer\\",\\"type\\":\\"list\\",\\"options\\":{\\"type\\":\\"terms\\",\\"multiselect\\":true,\\"dynamicOptions\\":true,\\"size\\":5,\\"order\\":\\"desc\\"},\\"indexPattern\\":\\"ff959d40-b880-11e8-a6d9-e546fe2bba5f\\"}],\\"updateFiltersOnChange\\":false,\\"useTimeFilter\\":true,\\"pinFilters\\":false}", + ], + }, + "function": "input_control_vis", + "type": "function", + }, + ], + "type": "expression", +} +`; diff --git a/src/plugins/input_control_vis/public/components/editor/_index.scss b/src/plugins/input_control_vis/public/components/editor/_index.scss deleted file mode 100644 index 9af8f8d6e82224..00000000000000 --- a/src/plugins/input_control_vis/public/components/editor/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './control_editor'; diff --git a/src/plugins/input_control_vis/public/components/editor/_control_editor.scss b/src/plugins/input_control_vis/public/components/editor/control_editor.scss similarity index 100% rename from src/plugins/input_control_vis/public/components/editor/_control_editor.scss rename to src/plugins/input_control_vis/public/components/editor/control_editor.scss diff --git a/src/plugins/input_control_vis/public/components/editor/control_editor.tsx b/src/plugins/input_control_vis/public/components/editor/control_editor.tsx index aa473095aaf3f1..109237f8db4ec6 100644 --- a/src/plugins/input_control_vis/public/components/editor/control_editor.tsx +++ b/src/plugins/input_control_vis/public/components/editor/control_editor.tsx @@ -36,6 +36,8 @@ import { getTitle, ControlParams, CONTROL_TYPES, ControlParamsOptions } from '.. import { IIndexPattern } from '../../../../data/public'; import { InputControlVisDependencies } from '../../plugin'; +import './control_editor.scss'; + interface ControlEditorUiProps { controlIndex: number; controlParams: ControlParams; diff --git a/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx b/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx index a85f98c7b89ba8..c05dec8fccbe1b 100644 --- a/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx +++ b/src/plugins/input_control_vis/public/components/editor/controls_tab.test.tsx @@ -21,16 +21,16 @@ import React from 'react'; import { shallowWithIntl, mountWithIntl } from '@kbn/test/jest'; import { findTestSubject } from '@elastic/eui/lib/test'; import { getDepsMock, getIndexPatternMock } from '../../test_utils'; -import { ControlsTab, ControlsTabUiProps } from './controls_tab'; +import ControlsTab, { ControlsTabProps } from './controls_tab'; import { Vis } from '../../../../visualizations/public'; const indexPatternsMock = { get: getIndexPatternMock, }; -let props: ControlsTabUiProps; +let props: ControlsTabProps; beforeEach(() => { - props = { + props = ({ deps: getDepsMock(), vis: ({ API: { @@ -78,18 +78,18 @@ beforeEach(() => { }, setValue: jest.fn(), intl: null as any, - }; + } as unknown) as ControlsTabProps; }); test('renders ControlsTab', () => { - const component = shallowWithIntl(); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); describe('behavior', () => { test('add control button', () => { - const component = mountWithIntl(); + const component = mountWithIntl(); findTestSubject(component, 'inputControlEditorAddBtn').simulate('click'); @@ -102,7 +102,7 @@ describe('behavior', () => { }); test('remove control button', () => { - const component = mountWithIntl(); + const component = mountWithIntl(); findTestSubject(component, 'inputControlEditorRemoveControl0').simulate('click'); const expectedParams = [ 'controls', @@ -125,7 +125,7 @@ describe('behavior', () => { }); test('move down control button', () => { - const component = mountWithIntl(); + const component = mountWithIntl(); findTestSubject(component, 'inputControlEditorMoveDownControl0').simulate('click'); const expectedParams = [ 'controls', @@ -162,7 +162,7 @@ describe('behavior', () => { }); test('move up control button', () => { - const component = mountWithIntl(); + const component = mountWithIntl(); findTestSubject(component, 'inputControlEditorMoveUpControl1').simulate('click'); const expectedParams = [ 'controls', diff --git a/src/plugins/input_control_vis/public/components/editor/controls_tab.tsx b/src/plugins/input_control_vis/public/components/editor/controls_tab.tsx index a9f04a86f8d03c..0e622e08c529fc 100644 --- a/src/plugins/input_control_vis/public/components/editor/controls_tab.tsx +++ b/src/plugins/input_control_vis/public/components/editor/controls_tab.tsx @@ -18,7 +18,8 @@ */ import React, { PureComponent } from 'react'; -import { injectI18n, FormattedMessage, InjectedIntlProps } from '@kbn/i18n/react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { EuiButton, @@ -44,22 +45,17 @@ import { } from '../../editor_utils'; import { getLineageMap, getParentCandidates } from '../../lineage'; import { InputControlVisDependencies } from '../../plugin'; +import { InputControlVisParams } from '../../types'; interface ControlsTabUiState { type: CONTROL_TYPES; } -interface ControlsTabUiParams { - controls: ControlParams[]; -} -type ControlsTabUiInjectedProps = InjectedIntlProps & - Pick, 'vis' | 'stateParams' | 'setValue'> & { - deps: InputControlVisDependencies; - }; +export type ControlsTabProps = VisOptionsProps & { + deps: InputControlVisDependencies; +}; -export type ControlsTabUiProps = ControlsTabUiInjectedProps; - -class ControlsTabUi extends PureComponent { +class ControlsTab extends PureComponent { state = { type: CONTROL_TYPES.LIST, }; @@ -161,8 +157,6 @@ class ControlsTabUi extends PureComponent {this.renderControls()} @@ -176,25 +170,31 @@ class ControlsTabUi extends PureComponent this.setState({ type: event.target.value as CONTROL_TYPES })} - aria-label={intl.formatMessage({ - id: 'inputControl.editor.controlsTab.select.controlTypeAriaLabel', - defaultMessage: 'Select control type', - })} + aria-label={i18n.translate( + 'inputControl.editor.controlsTab.select.controlTypeAriaLabel', + { + defaultMessage: 'Select control type', + } + )} /> @@ -205,10 +205,12 @@ class ControlsTabUi extends PureComponent ( - props: Omit -) => ; +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { ControlsTab as default }; diff --git a/src/plugins/input_control_vis/public/components/editor/index.tsx b/src/plugins/input_control_vis/public/components/editor/index.tsx new file mode 100644 index 00000000000000..11b3c2ea4ee8a8 --- /dev/null +++ b/src/plugins/input_control_vis/public/components/editor/index.tsx @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { lazy } from 'react'; +import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; +import { InputControlVisDependencies } from '../../plugin'; +import { InputControlVisParams } from '../../types'; + +const ControlsTab = lazy(() => import('./controls_tab')); +const OptionsTab = lazy(() => import('./options_tab')); + +export const getControlsTab = (deps: InputControlVisDependencies) => ( + props: VisOptionsProps +) => ; + +export const OptionsTabLazy = (props: VisOptionsProps) => ( + +); diff --git a/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx b/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx index 0f126e915a68c5..0970d1cd3c2984 100644 --- a/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx +++ b/src/plugins/input_control_vis/public/components/editor/options_tab.test.tsx @@ -22,13 +22,13 @@ import { shallow } from 'enzyme'; import { mountWithIntl } from '@kbn/test/jest'; import { Vis } from '../../../../visualizations/public'; -import { OptionsTab, OptionsTabProps } from './options_tab'; +import OptionsTab, { OptionsTabProps } from './options_tab'; describe('OptionsTab', () => { let props: OptionsTabProps; beforeEach(() => { - props = { + props = ({ vis: {} as Vis, stateParams: { updateFiltersOnChange: false, @@ -36,7 +36,7 @@ describe('OptionsTab', () => { pinFilters: false, }, setValue: jest.fn(), - }; + } as unknown) as OptionsTabProps; }); it('should renders OptionsTab', () => { diff --git a/src/plugins/input_control_vis/public/components/editor/options_tab.tsx b/src/plugins/input_control_vis/public/components/editor/options_tab.tsx index cdff6cabad8ba8..306d1141e75bd8 100644 --- a/src/plugins/input_control_vis/public/components/editor/options_tab.tsx +++ b/src/plugins/input_control_vis/public/components/editor/options_tab.tsx @@ -24,20 +24,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSwitchEvent } from '@elastic/eui'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public'; +import { InputControlVisParams } from '../../types'; -interface OptionsTabParams { - updateFiltersOnChange: boolean; - useTimeFilter: boolean; - pinFilters: boolean; -} -type OptionsTabInjectedProps = Pick< - VisOptionsProps, - 'vis' | 'setValue' | 'stateParams' ->; - -export type OptionsTabProps = OptionsTabInjectedProps; +export type OptionsTabProps = VisOptionsProps; -export class OptionsTab extends PureComponent { +class OptionsTab extends PureComponent { handleUpdateFiltersChange = (event: EuiSwitchEvent) => { this.props.setValue('updateFiltersOnChange', event.target.checked); }; @@ -98,3 +89,6 @@ export class OptionsTab extends PureComponent { ); } } +// default export required for React.Lazy +// eslint-disable-next-line import/no-default-export +export { OptionsTab as default }; diff --git a/src/plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap b/src/plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap index 5a76967c71fbb3..5e1f25993616b0 100644 --- a/src/plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap +++ b/src/plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.tsx.snap @@ -2,355 +2,371 @@ exports[`Apply and Cancel change btns enabled when there are changes 1`] = `
- - - - - - - - - - - - + + - - - - - - + + + + - - - - + + + + + + + + + + +
`; exports[`Clear btns enabled when there are values 1`] = `
- - - - - - - - - - - - + + - - - - - - + + + + - - - - + + + + + + + + + + +
`; exports[`Renders list control 1`] = `
- - - - - - - - - - - - + + - - - - - - + + + + - - - - + + + + + + + + + + +
`; exports[`Renders range control 1`] = `
- - - - - - - - - - - - + + - - - - - - + + + + - - - - + + + + + + + + + + +
`; diff --git a/src/plugins/input_control_vis/public/components/vis/_index.scss b/src/plugins/input_control_vis/public/components/vis/_index.scss deleted file mode 100644 index a428a7c1782e30..00000000000000 --- a/src/plugins/input_control_vis/public/components/vis/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './vis'; diff --git a/src/plugins/input_control_vis/public/components/vis/_vis.scss b/src/plugins/input_control_vis/public/components/vis/_vis.scss deleted file mode 100644 index d42c2c5f263c75..00000000000000 --- a/src/plugins/input_control_vis/public/components/vis/_vis.scss +++ /dev/null @@ -1,5 +0,0 @@ -.icvContainer { - width: 100%; - margin: 0 $euiSizeXS; - padding: $euiSizeS; -} diff --git a/src/plugins/input_control_vis/public/components/vis/input_control_vis.scss b/src/plugins/input_control_vis/public/components/vis/input_control_vis.scss new file mode 100644 index 00000000000000..322573446f7629 --- /dev/null +++ b/src/plugins/input_control_vis/public/components/vis/input_control_vis.scss @@ -0,0 +1,13 @@ +.icvContainer__wrapper { + @include euiScrollBar; + min-height: 0; + flex: 1 1 0; + display: flex; + overflow: auto; +} + +.icvContainer { + width: 100%; + margin: 0 $euiSizeXS; + padding: $euiSizeS; +} diff --git a/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx b/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx index 95edb4a35bc22d..058f39cb8a6d43 100644 --- a/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx +++ b/src/plugins/input_control_vis/public/components/vis/input_control_vis.tsx @@ -26,6 +26,8 @@ import { RangeControl } from '../../control/range_control_factory'; import { ListControl as ListControlComponent } from './list_control'; import { RangeControl as RangeControlComponent } from './range_control'; +import './input_control_vis.scss'; + function isListControl(control: RangeControl | ListControl): control is ListControl { return control.type === CONTROL_TYPES.LIST; } @@ -165,9 +167,11 @@ export class InputControlVis extends Component { } return ( -
- {this.renderControls()} - {stagingButtons} +
+
+ {this.renderControls()} + {stagingButtons} +
); } diff --git a/src/plugins/input_control_vis/public/index.scss b/src/plugins/input_control_vis/public/index.scss deleted file mode 100644 index 42fded23d77615..00000000000000 --- a/src/plugins/input_control_vis/public/index.scss +++ /dev/null @@ -1,9 +0,0 @@ -// Prefix all styles with "icv" to avoid conflicts. -// Examples -// icvChart -// icvChart__legend -// icvChart__legend--small -// icvChart__legend-isLoading - -@import './components/editor/index'; -@import './components/vis/index'; diff --git a/src/plugins/input_control_vis/public/index.ts b/src/plugins/input_control_vis/public/index.ts index 8edd3fd9996c3f..b6fee12f6d9cbd 100644 --- a/src/plugins/input_control_vis/public/index.ts +++ b/src/plugins/input_control_vis/public/index.ts @@ -17,8 +17,6 @@ * under the License. */ -import './index.scss'; - import { PluginInitializerContext } from '../../../core/public'; import { InputControlVisPlugin as Plugin } from './plugin'; diff --git a/src/plugins/input_control_vis/public/input_control_fn.ts b/src/plugins/input_control_vis/public/input_control_fn.ts index 1664555b916b66..46fba66264bcb2 100644 --- a/src/plugins/input_control_vis/public/input_control_fn.ts +++ b/src/plugins/input_control_vis/public/input_control_fn.ts @@ -20,24 +20,25 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public'; +import { InputControlVisParams } from './types'; interface Arguments { visConfig: string; } -type VisParams = Required; - -interface RenderValue { +export interface InputControlRenderValue { visType: 'input_control_vis'; - visConfig: VisParams; + visConfig: InputControlVisParams; } -export const createInputControlVisFn = (): ExpressionFunctionDefinition< +export type InputControlExpressionFunctionDefinition = ExpressionFunctionDefinition< 'input_control_vis', Datatable, Arguments, - Render -> => ({ + Render +>; + +export const createInputControlVisFn = (): InputControlExpressionFunctionDefinition => ({ name: 'input_control_vis', type: 'render', inputTypes: [], @@ -52,10 +53,10 @@ export const createInputControlVisFn = (): ExpressionFunctionDefinition< }, }, fn(input, args) { - const params = JSON.parse(args.visConfig); + const params: InputControlVisParams = JSON.parse(args.visConfig); return { type: 'render', - as: 'visualization', + as: 'input_control_vis', value: { visType: 'input_control_vis', visConfig: params, diff --git a/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx b/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx new file mode 100644 index 00000000000000..6431ed6ebed1ee --- /dev/null +++ b/src/plugins/input_control_vis/public/input_control_vis_renderer.tsx @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ExpressionRenderDefinition } from 'src/plugins/expressions'; +import { InputControlVisDependencies } from './plugin'; +import { InputControlRenderValue } from './input_control_fn'; +import type { InputControlVisControllerType } from './vis_controller'; + +const inputControlVisRegistry = new Map(); + +export const getInputControlVisRenderer: ( + deps: InputControlVisDependencies +) => ExpressionRenderDefinition = (deps) => ({ + name: 'input_control_vis', + reuseDomNode: true, + render: async (domNode, { visConfig }, handlers) => { + let registeredController = inputControlVisRegistry.get(domNode); + + if (!registeredController) { + const { createInputControlVisController } = await import('./vis_controller'); + + const Controller = createInputControlVisController(deps, handlers); + registeredController = new Controller(domNode); + inputControlVisRegistry.set(domNode, registeredController); + + handlers.onDestroy(() => { + registeredController?.destroy(); + inputControlVisRegistry.delete(domNode); + }); + } + + await registeredController.render(visConfig); + handlers.done(); + }, +}); diff --git a/src/plugins/input_control_vis/public/input_control_vis_type.ts b/src/plugins/input_control_vis/public/input_control_vis_type.ts index 6e33e18c1603b0..686327a1ba774a 100644 --- a/src/plugins/input_control_vis/public/input_control_vis_type.ts +++ b/src/plugins/input_control_vis/public/input_control_vis_type.ts @@ -19,15 +19,14 @@ import { i18n } from '@kbn/i18n'; import { VisGroups, BaseVisTypeOptions } from '../../visualizations/public'; -import { createInputControlVisController } from './vis_controller'; -import { getControlsTab } from './components/editor/controls_tab'; -import { OptionsTab } from './components/editor/options_tab'; +import { getControlsTab, OptionsTabLazy } from './components/editor'; import { InputControlVisDependencies } from './plugin'; +import { toExpressionAst } from './to_ast'; +import { InputControlVisParams } from './types'; export function createInputControlVisTypeDefinition( deps: InputControlVisDependencies -): BaseVisTypeOptions { - const InputControlVisController = createInputControlVisController(deps); +): BaseVisTypeOptions { const ControlsTab = getControlsTab(deps); return { @@ -41,7 +40,6 @@ export function createInputControlVisTypeDefinition( defaultMessage: 'Add dropdown menus and range sliders to your dashboard.', }), stage: 'experimental', - visualization: InputControlVisController, visConfig: { defaults: { controls: [], @@ -64,12 +62,12 @@ export function createInputControlVisTypeDefinition( title: i18n.translate('inputControl.register.tabs.optionsTitle', { defaultMessage: 'Options', }), - editor: OptionsTab, + editor: OptionsTabLazy, }, ], }, inspectorAdapters: {}, requestHandler: 'none', - responseHandler: 'none', + toExpressionAst, }; } diff --git a/src/plugins/input_control_vis/public/plugin.ts b/src/plugins/input_control_vis/public/plugin.ts index 2c93a529c25b19..afaaa27d74c82e 100644 --- a/src/plugins/input_control_vis/public/plugin.ts +++ b/src/plugins/input_control_vis/public/plugin.ts @@ -22,6 +22,7 @@ import { DataPublicPluginSetup, DataPublicPluginStart } from 'src/plugins/data/p import { Plugin as ExpressionsPublicPlugin } from '../../expressions/public'; import { VisualizationsSetup, VisualizationsStart } from '../../visualizations/public'; import { createInputControlVisFn } from './input_control_fn'; +import { getInputControlVisRenderer } from './input_control_vis_renderer'; import { createInputControlVisTypeDefinition } from './input_control_vis_type'; type InputControlVisCoreSetup = CoreSetup; @@ -76,6 +77,7 @@ export class InputControlVisPlugin implements Plugin { }; expressions.registerFunction(createInputControlVisFn); + expressions.registerRenderer(getInputControlVisRenderer(visualizationDependencies)); visualizations.createBaseVisualization( createInputControlVisTypeDefinition(visualizationDependencies) ); diff --git a/src/plugins/input_control_vis/public/to_ast.test.ts b/src/plugins/input_control_vis/public/to_ast.test.ts new file mode 100644 index 00000000000000..fbeb78ee93a1ed --- /dev/null +++ b/src/plugins/input_control_vis/public/to_ast.test.ts @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Vis } from '../../visualizations/public'; +import { InputControlVisParams } from './types'; +import { toExpressionAst } from './to_ast'; + +describe('input_control_vis toExpressionAst', () => { + const vis = { + params: { + controls: [ + { + id: '1536977437774', + fieldName: 'manufacturer.keyword', + parent: '', + label: 'Manufacturer', + type: 'list', + options: { + type: 'terms', + multiselect: true, + dynamicOptions: true, + size: 5, + order: 'desc', + }, + indexPattern: 'ff959d40-b880-11e8-a6d9-e546fe2bba5f', + }, + ], + updateFiltersOnChange: false, + useTimeFilter: true, + pinFilters: false, + }, + } as Vis; + + it('should build an expression based on vis.params', () => { + const expression = toExpressionAst(vis); + expect(expression).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/input_control_vis/public/to_ast.ts b/src/plugins/input_control_vis/public/to_ast.ts new file mode 100644 index 00000000000000..93c0b4a87cfe6c --- /dev/null +++ b/src/plugins/input_control_vis/public/to_ast.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { buildExpression, buildExpressionFunction } from '../../expressions/public'; +import { Vis } from '../../visualizations/public'; +import { InputControlExpressionFunctionDefinition } from './input_control_fn'; +import { InputControlVisParams } from './types'; + +export const toExpressionAst = (vis: Vis) => { + const inputControl = buildExpressionFunction( + 'input_control_vis', + { + visConfig: JSON.stringify(vis.params), + } + ); + + const ast = buildExpression([inputControl]); + + return ast.toAst(); +}; diff --git a/src/plugins/input_control_vis/public/types.ts b/src/plugins/input_control_vis/public/types.ts new file mode 100644 index 00000000000000..2898ab49590ed4 --- /dev/null +++ b/src/plugins/input_control_vis/public/types.ts @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ControlParams } from './editor_utils'; + +export interface InputControlVisParams { + controls: ControlParams[]; + pinFilters: boolean; + updateFiltersOnChange: boolean; + useTimeFilter: boolean; +} diff --git a/src/plugins/input_control_vis/public/vis_controller.tsx b/src/plugins/input_control_vis/public/vis_controller.tsx index 6f35e17866120b..8e762a38671e9c 100644 --- a/src/plugins/input_control_vis/public/vis_controller.tsx +++ b/src/plugins/input_control_vis/public/vis_controller.tsx @@ -20,20 +20,29 @@ import React from 'react'; import { isEqual } from 'lodash'; import { render, unmountComponentAtNode } from 'react-dom'; - import { Subscription } from 'rxjs'; + import { I18nStart } from 'kibana/public'; +import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { VisualizationContainer } from '../../visualizations/public'; +import { FilterManager, Filter } from '../../data/public'; + import { InputControlVis } from './components/vis/input_control_vis'; import { getControlFactory } from './control/control_factory'; import { getLineageMap } from './lineage'; -import { ControlParams } from './editor_utils'; import { RangeControl } from './control/range_control_factory'; import { ListControl } from './control/list_control_factory'; import { InputControlVisDependencies } from './plugin'; -import { FilterManager, Filter } from '../../data/public'; -import { VisParams, ExprVis } from '../../visualizations/public'; +import { InputControlVisParams } from './types'; -export const createInputControlVisController = (deps: InputControlVisDependencies) => { +export type InputControlVisControllerType = InstanceType< + ReturnType +>; + +export const createInputControlVisController = ( + deps: InputControlVisDependencies, + handlers: IInterpreterRenderHandlers +) => { return class InputControlVisController { private I18nContext?: I18nStart['Context']; private _isLoaded = false; @@ -43,9 +52,9 @@ export const createInputControlVisController = (deps: InputControlVisDependencie filterManager: FilterManager; updateSubsciption: any; timeFilterSubscription: Subscription; - visParams?: VisParams; + visParams?: InputControlVisParams; - constructor(public el: Element, public vis: ExprVis) { + constructor(public el: Element) { this.controls = []; this.queryBarUpdateHandler = this.updateControlsFromKbn.bind(this); @@ -63,7 +72,7 @@ export const createInputControlVisController = (deps: InputControlVisDependencie }); } - async render(visData: any, visParams: VisParams) { + async render(visParams: InputControlVisParams) { if (!this.I18nContext) { const [{ i18n }] = await deps.core.getStartServices(); this.I18nContext = i18n.Context; @@ -71,7 +80,7 @@ export const createInputControlVisController = (deps: InputControlVisDependencie if (!this._isLoaded || !isEqual(visParams, this.visParams)) { this.visParams = visParams; this.controls = []; - this.controls = await this.initControls(); + this.controls = await this.initControls(visParams); this._isLoaded = true; } this.drawVis(); @@ -91,34 +100,34 @@ export const createInputControlVisController = (deps: InputControlVisDependencie render( - + + + , this.el ); }; - async initControls() { - const controlParamsList = (this.visParams?.controls as ControlParams[])?.filter( - (controlParams) => { - // ignore controls that do not have indexPattern or field - return controlParams.indexPattern && controlParams.fieldName; - } - ); + async initControls(visParams: InputControlVisParams) { + const controlParamsList = visParams.controls.filter((controlParams) => { + // ignore controls that do not have indexPattern or field + return controlParams.indexPattern && controlParams.fieldName; + }); const controlFactoryPromises = controlParamsList.map((controlParams) => { const factory = getControlFactory(controlParams); - return factory(controlParams, this.visParams?.useTimeFilter, deps); + return factory(controlParams, visParams.useTimeFilter, deps); }); const controls = await Promise.all(controlFactoryPromises); diff --git a/src/plugins/telemetry/server/fetcher.ts b/src/plugins/telemetry/server/fetcher.ts index a3649f51577ac3..820f2c7c4c4af0 100644 --- a/src/plugins/telemetry/server/fetcher.ts +++ b/src/plugins/telemetry/server/fetcher.ts @@ -17,7 +17,6 @@ * under the License. */ -import moment from 'moment'; import { Observable, Subscription, timer } from 'rxjs'; import { take } from 'rxjs/operators'; // @ts-ignore @@ -213,7 +212,6 @@ export class FetcherTask { private async fetchTelemetry() { return await this.telemetryCollectionManager!.getStats({ unencrypted: false, - timestamp: moment().valueOf(), }); } diff --git a/src/plugins/telemetry/server/index.ts b/src/plugins/telemetry/server/index.ts index e9887456e2f365..326c87a75b0ea5 100644 --- a/src/plugins/telemetry/server/index.ts +++ b/src/plugins/telemetry/server/index.ts @@ -44,7 +44,6 @@ export const plugin = (initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get(); @@ -109,12 +105,7 @@ export class TelemetryPlugin implements Plugin this.elasticsearchClient, - () => this.savedObjectsService - ); + registerCollection(telemetryCollectionManager); const router = http.createRouter(); registerRoutes({ @@ -138,11 +129,9 @@ export class TelemetryPlugin implements Plugin; * @param {Object} config contains the usageCollection, callCluster (deprecated), the esClient and Saved Objects client scoped to the request or the internal repository, and the kibana request * @param {Object} StatsCollectionContext contains logger and version (string) */ -export const getLocalStats: StatsGetter<{}, TelemetryLocalStats> = async ( +export const getLocalStats: StatsGetter = async ( clustersDetails, config, context diff --git a/src/plugins/telemetry/server/telemetry_collection/index.ts b/src/plugins/telemetry/server/telemetry_collection/index.ts index 40cbf0e4caa1d9..77894091f61330 100644 --- a/src/plugins/telemetry/server/telemetry_collection/index.ts +++ b/src/plugins/telemetry/server/telemetry_collection/index.ts @@ -24,6 +24,5 @@ export { buildDataTelemetryPayload, } from './get_data_telemetry'; export { getLocalStats, TelemetryLocalStats } from './get_local_stats'; -export { getLocalLicense } from './get_local_license'; export { getClusterUuids } from './get_cluster_stats'; export { registerCollection } from './register_collection'; diff --git a/src/plugins/telemetry/server/telemetry_collection/register_collection.ts b/src/plugins/telemetry/server/telemetry_collection/register_collection.ts index 27ca5ae7465123..fac315b01493eb 100644 --- a/src/plugins/telemetry/server/telemetry_collection/register_collection.ts +++ b/src/plugins/telemetry/server/telemetry_collection/register_collection.ts @@ -36,27 +36,17 @@ * under the License. */ -import { ILegacyClusterClient, SavedObjectsServiceStart } from 'kibana/server'; import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server'; -import { IClusterClient } from '../../../../../src/core/server'; import { getLocalStats } from './get_local_stats'; import { getClusterUuids } from './get_cluster_stats'; -import { getLocalLicense } from './get_local_license'; export function registerCollection( - telemetryCollectionManager: TelemetryCollectionManagerPluginSetup, - esCluster: ILegacyClusterClient, - esClientGetter: () => IClusterClient | undefined, - soServiceGetter: () => SavedObjectsServiceStart | undefined + telemetryCollectionManager: TelemetryCollectionManagerPluginSetup ) { - telemetryCollectionManager.setCollection({ - esCluster, - esClientGetter, - soServiceGetter, + telemetryCollectionManager.setCollectionStrategy({ title: 'local', priority: 0, statsGetter: getLocalStats, clusterDetailsGetter: getClusterUuids, - licenseGetter: getLocalLicense, }); } diff --git a/src/plugins/telemetry_collection_manager/server/index.ts b/src/plugins/telemetry_collection_manager/server/index.ts index 36ab64731fe582..de2080059c80b7 100644 --- a/src/plugins/telemetry_collection_manager/server/index.ts +++ b/src/plugins/telemetry_collection_manager/server/index.ts @@ -30,13 +30,11 @@ export function plugin(initializerContext: PluginInitializerContext) { export { TelemetryCollectionManagerPluginSetup, TelemetryCollectionManagerPluginStart, - ESLicense, StatsCollectionConfig, StatsGetter, StatsGetterConfig, StatsCollectionContext, ClusterDetails, ClusterDetailsGetter, - LicenseGetter, UsageStatsPayload, } from './types'; diff --git a/src/plugins/telemetry_collection_manager/server/plugin.ts b/src/plugins/telemetry_collection_manager/server/plugin.ts index bc33e9fbc82c5b..a135f4b115b215 100644 --- a/src/plugins/telemetry_collection_manager/server/plugin.ts +++ b/src/plugins/telemetry_collection_manager/server/plugin.ts @@ -26,14 +26,15 @@ import { Logger, IClusterClient, SavedObjectsServiceStart, -} from '../../../core/server'; + ILegacyClusterClient, +} from 'src/core/server'; import { TelemetryCollectionManagerPluginSetup, TelemetryCollectionManagerPluginStart, BasicStatsPayload, - CollectionConfig, - Collection, + CollectionStrategyConfig, + CollectionStrategy, StatsGetterConfig, StatsCollectionConfig, UsageStatsPayload, @@ -49,9 +50,12 @@ interface TelemetryCollectionPluginsDepsSetup { export class TelemetryCollectionManagerPlugin implements Plugin { private readonly logger: Logger; - private readonly collections: Array> = []; + private collectionStrategy: CollectionStrategy | undefined; private usageGetterMethodPriority = -1; private usageCollection?: UsageCollectionSetup; + private legacyElasticsearchClient?: ILegacyClusterClient; + private elasticsearchClient?: IClusterClient; + private savedObjectsService?: SavedObjectsServiceStart; private readonly isDistributable: boolean; private readonly version: string; @@ -65,7 +69,7 @@ export class TelemetryCollectionManagerPlugin this.usageCollection = usageCollection; return { - setCollection: this.setCollection.bind(this), + setCollectionStrategy: this.setCollectionStrategy.bind(this), getOptInStats: this.getOptInStats.bind(this), getStats: this.getStats.bind(this), areAllCollectorsReady: this.areAllCollectorsReady.bind(this), @@ -73,8 +77,11 @@ export class TelemetryCollectionManagerPlugin } public start(core: CoreStart) { + this.legacyElasticsearchClient = core.elasticsearch.legacy.client; // TODO: Remove when all the collectors have migrated + this.elasticsearchClient = core.elasticsearch.client; + this.savedObjectsService = core.savedObjects; + return { - setCollection: this.setCollection.bind(this), getOptInStats: this.getOptInStats.bind(this), getStats: this.getStats.bind(this), areAllCollectorsReady: this.areAllCollectorsReady.bind(this), @@ -83,19 +90,10 @@ export class TelemetryCollectionManagerPlugin public stop() {} - private setCollection, T extends BasicStatsPayload>( - collectionConfig: CollectionConfig + private setCollectionStrategy( + collectionConfig: CollectionStrategyConfig ) { - const { - title, - priority, - esCluster, - esClientGetter, - soServiceGetter, - statsGetter, - clusterDetailsGetter, - licenseGetter, - } = collectionConfig; + const { title, priority, statsGetter, clusterDetailsGetter } = collectionConfig; if (typeof priority !== 'number') { throw new Error('priority must be set.'); @@ -108,78 +106,58 @@ export class TelemetryCollectionManagerPlugin if (!statsGetter) { throw Error('Stats getter method not set.'); } - if (!esCluster) { - throw Error('esCluster name must be set for the getCluster method.'); - } - if (!esClientGetter) { - throw Error('esClientGetter method not set.'); - } - if (!soServiceGetter) { - throw Error('soServiceGetter method not set.'); - } if (!clusterDetailsGetter) { throw Error('Cluster UUIds method is not set.'); } - if (!licenseGetter) { - throw Error('License getter method not set.'); - } - this.collections.unshift({ - licenseGetter, - statsGetter, - clusterDetailsGetter, - esCluster, - title, - esClientGetter, - soServiceGetter, - }); + this.logger.debug(`Setting ${title} as the telemetry collection strategy`); + + // Overwrite the collection strategy + this.collectionStrategy = collectionConfig; this.usageGetterMethodPriority = priority; } } + /** + * Returns the context to provide to the Collection Strategies. + * It may return undefined if the ES and SO clients are not initialised yet. + * @param config {@link StatsGetterConfig} + * @param usageCollection {@link UsageCollectionSetup} + * @private + */ private getStatsCollectionConfig( config: StatsGetterConfig, - collection: Collection, - collectionEsClient: IClusterClient, - collectionSoService: SavedObjectsServiceStart, usageCollection: UsageCollectionSetup - ): StatsCollectionConfig { - const { request } = config; - + ): StatsCollectionConfig | undefined { const callCluster = config.unencrypted - ? collection.esCluster.asScoped(request).callAsCurrentUser - : collection.esCluster.callAsInternalUser; + ? this.legacyElasticsearchClient?.asScoped(config.request).callAsCurrentUser + : this.legacyElasticsearchClient?.callAsInternalUser; // Scope the new elasticsearch Client appropriately and pass to the stats collection config const esClient = config.unencrypted - ? collectionEsClient.asScoped(config.request).asCurrentUser - : collectionEsClient.asInternalUser; + ? this.elasticsearchClient?.asScoped(config.request).asCurrentUser + : this.elasticsearchClient?.asInternalUser; // Scope the saved objects client appropriately and pass to the stats collection config const soClient = config.unencrypted - ? collectionSoService.getScopedClient(config.request) - : collectionSoService.createInternalRepository(); + ? this.savedObjectsService?.getScopedClient(config.request) + : this.savedObjectsService?.createInternalRepository(); // Provide the kibanaRequest so opted-in plugins can scope their custom clients only if the request is not encrypted - const kibanaRequest = config.unencrypted ? request : void 0; + const kibanaRequest = config.unencrypted ? config.request : void 0; - return { callCluster, usageCollection, esClient, soClient, kibanaRequest }; + if (callCluster && esClient && soClient) { + return { callCluster, usageCollection, esClient, soClient, kibanaRequest }; + } } private async getOptInStats(optInStatus: boolean, config: StatsGetterConfig) { if (!this.usageCollection) { return []; } - for (const collection of this.collections) { - // first fetch the client and make sure it's not undefined. - const collectionEsClient = collection.esClientGetter(); - const collectionSoService = collection.soServiceGetter(); - if (collectionEsClient !== undefined && collectionSoService !== undefined) { - const statsCollectionConfig = this.getStatsCollectionConfig( - config, - collection, - collectionEsClient, - collectionSoService, - this.usageCollection - ); + const collection = this.collectionStrategy; + if (collection) { + // Build the context (clients and others) to send to the CollectionStrategies + const statsCollectionConfig = this.getStatsCollectionConfig(config, this.usageCollection); + if (statsCollectionConfig) { try { const optInStats = await this.getOptInStatsForCollection( collection, @@ -194,8 +172,9 @@ export class TelemetryCollectionManagerPlugin return encryptTelemetry(optInStats, { useProdKey: this.isDistributable }); } } catch (err) { - this.logger.debug(`Failed to collect any opt in stats with registered collections.`); - // swallow error to try next collection; + this.logger.debug( + `Failed to collect any opt in stats with collection ${collection.title}.` + ); } } } @@ -203,19 +182,18 @@ export class TelemetryCollectionManagerPlugin return []; } - private areAllCollectorsReady = async () => { + private async areAllCollectorsReady() { return await this.usageCollection?.areAllCollectorsReady(); - }; + } private getOptInStatsForCollection = async ( - collection: Collection, + collection: CollectionStrategy, optInStatus: boolean, statsCollectionConfig: StatsCollectionConfig ) => { const context: StatsCollectionContext = { logger: this.logger.get(collection.title), version: this.version, - ...collection.customContext, }; const clustersDetails = await collection.clusterDetailsGetter(statsCollectionConfig, context); @@ -229,17 +207,11 @@ export class TelemetryCollectionManagerPlugin if (!this.usageCollection) { return []; } - for (const collection of this.collections) { - const collectionEsClient = collection.esClientGetter(); - const collectionSavedObjectsService = collection.soServiceGetter(); - if (collectionEsClient !== undefined && collectionSavedObjectsService !== undefined) { - const statsCollectionConfig = this.getStatsCollectionConfig( - config, - collection, - collectionEsClient, - collectionSavedObjectsService, - this.usageCollection - ); + const collection = this.collectionStrategy; + if (collection) { + // Build the context (clients and others) to send to the CollectionStrategies + const statsCollectionConfig = this.getStatsCollectionConfig(config, this.usageCollection); + if (statsCollectionConfig) { try { const usageData = await this.getUsageForCollection(collection, statsCollectionConfig); if (usageData.length) { @@ -256,7 +228,6 @@ export class TelemetryCollectionManagerPlugin this.logger.debug( `Failed to collect any usage with registered collection ${collection.title}.` ); - // swallow error to try next collection; } } } @@ -265,34 +236,24 @@ export class TelemetryCollectionManagerPlugin } private async getUsageForCollection( - collection: Collection, + collection: CollectionStrategy, statsCollectionConfig: StatsCollectionConfig ): Promise { const context: StatsCollectionContext = { logger: this.logger.get(collection.title), version: this.version, - ...collection.customContext, }; const clustersDetails = await collection.clusterDetailsGetter(statsCollectionConfig, context); if (clustersDetails.length === 0) { - // don't bother doing a further lookup, try next collection. + // don't bother doing a further lookup. return []; } - const [stats, licenses] = await Promise.all([ - collection.statsGetter(clustersDetails, statsCollectionConfig, context), - collection.licenseGetter(clustersDetails, statsCollectionConfig, context), - ]); + const stats = await collection.statsGetter(clustersDetails, statsCollectionConfig, context); - return stats.map((stat) => { - const license = licenses[stat.cluster_uuid]; - return { - collectionSource: collection.title, - ...(license ? { license } : {}), - ...stat, - }; - }); + // Add the `collectionSource` to the resulting payload + return stats.map((stat) => ({ collectionSource: collection.title, ...stat })); } } diff --git a/src/plugins/telemetry_collection_manager/server/types.ts b/src/plugins/telemetry_collection_manager/server/types.ts index a6cf1a9e5aaf95..05641d50645931 100644 --- a/src/plugins/telemetry_collection_manager/server/types.ts +++ b/src/plugins/telemetry_collection_manager/server/types.ts @@ -19,21 +19,18 @@ import { LegacyAPICaller, + ElasticsearchClient, Logger, KibanaRequest, - ILegacyClusterClient, - IClusterClient, - SavedObjectsServiceStart, SavedObjectsClientContract, ISavedObjectsRepository, -} from 'kibana/server'; +} from 'src/core/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { ElasticsearchClient } from '../../../../src/core/server'; import { TelemetryCollectionManagerPlugin } from './plugin'; export interface TelemetryCollectionManagerPluginSetup { - setCollection: , T extends BasicStatsPayload>( - collectionConfig: CollectionConfig + setCollectionStrategy: ( + collectionConfig: CollectionStrategyConfig ) => void; getOptInStats: TelemetryCollectionManagerPlugin['getOptInStats']; getStats: TelemetryCollectionManagerPlugin['getStats']; @@ -41,9 +38,6 @@ export interface TelemetryCollectionManagerPluginSetup { } export interface TelemetryCollectionManagerPluginStart { - setCollection: , T extends BasicStatsPayload>( - collectionConfig: CollectionConfig - ) => void; getOptInStats: TelemetryCollectionManagerPlugin['getOptInStats']; getStats: TelemetryCollectionManagerPlugin['getStats']; areAllCollectorsReady: TelemetryCollectionManagerPlugin['areAllCollectorsReady']; @@ -91,74 +85,34 @@ export interface BasicStatsPayload { } export interface UsageStatsPayload extends BasicStatsPayload { - license?: ESLicense; collectionSource: string; } -// From https://www.elastic.co/guide/en/elasticsearch/reference/current/get-license.html -export interface ESLicense { - status: string; - uid: string; - type: string; - issue_date: string; - issue_date_in_millis: number; - expiry_date: string; - expirty_date_in_millis: number; - max_nodes: number; - issued_to: string; - issuer: string; - start_date_in_millis: number; -} - export interface StatsCollectionContext { logger: Logger | Console; version: string; } export type StatsGetterConfig = UnencryptedStatsGetterConfig | EncryptedStatsGetterConfig; -export type ClusterDetailsGetter = {}> = ( +export type ClusterDetailsGetter = ( config: StatsCollectionConfig, - context: StatsCollectionContext & CustomContext + context: StatsCollectionContext ) => Promise; -export type StatsGetter< - CustomContext extends Record = {}, - T extends BasicStatsPayload = BasicStatsPayload -> = ( +export type StatsGetter = ( clustersDetails: ClusterDetails[], config: StatsCollectionConfig, - context: StatsCollectionContext & CustomContext + context: StatsCollectionContext ) => Promise; -export type LicenseGetter = {}> = ( - clustersDetails: ClusterDetails[], - config: StatsCollectionConfig, - context: StatsCollectionContext & CustomContext -) => Promise<{ [clusterUuid: string]: ESLicense | undefined }>; -export interface CollectionConfig< - CustomContext extends Record = {}, - T extends BasicStatsPayload = BasicStatsPayload -> { +export interface CollectionStrategyConfig { title: string; priority: number; - esCluster: ILegacyClusterClient; - esClientGetter: () => IClusterClient | undefined; // --> by now we know that the client getter will return the IClusterClient but we assure that through a code check - soServiceGetter: () => SavedObjectsServiceStart | undefined; // --> by now we know that the service getter will return the SavedObjectsServiceStart but we assure that through a code check - statsGetter: StatsGetter; - clusterDetailsGetter: ClusterDetailsGetter; - licenseGetter: LicenseGetter; - customContext?: CustomContext; + statsGetter: StatsGetter; + clusterDetailsGetter: ClusterDetailsGetter; } -export interface Collection< - CustomContext extends Record = {}, - T extends BasicStatsPayload = BasicStatsPayload -> { - customContext?: CustomContext; - statsGetter: StatsGetter; - licenseGetter: LicenseGetter; - clusterDetailsGetter: ClusterDetailsGetter; - esCluster: ILegacyClusterClient; - esClientGetter: () => IClusterClient | undefined; // the collection could still return undefined for the es client getter. - soServiceGetter: () => SavedObjectsServiceStart | undefined; // the collection could still return undefined for the Saved Objects Service getter. +export interface CollectionStrategy { + statsGetter: StatsGetter; + clusterDetailsGetter: ClusterDetailsGetter; title: string; } diff --git a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap index 03a355c604c4d7..3ff0c83961e2a5 100644 --- a/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap +++ b/src/plugins/visualizations/public/legacy/__snapshots__/build_pipeline.test.ts.snap @@ -2,8 +2,6 @@ exports[`visualize loader pipeline helpers: build pipeline buildPipeline calls toExpression on vis_type if it exists 1`] = `"kibana | kibana_context | test"`; -exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles input_control_vis function 1`] = `"input_control_vis visConfig='{\\"some\\":\\"nested\\",\\"data\\":{\\"here\\":true}}' "`; - exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function with buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"},\\"bucket\\":1}' "`; exports[`visualize loader pipeline helpers: build pipeline buildPipelineVisFunction handles region_map function without buckets 1`] = `"regionmap visConfig='{\\"metric\\":{\\"accessor\\":0,\\"label\\":\\"\\",\\"format\\":{},\\"params\\":{},\\"aggType\\":\\"\\"}}' "`; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts index 653542bd8837de..57c58a99f09ea6 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.test.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.test.ts @@ -92,15 +92,6 @@ describe('visualize loader pipeline helpers: build pipeline', () => { uiState = {}; }); - it('handles input_control_vis function', () => { - const params = { - some: 'nested', - data: { here: true }, - }; - const actual = buildPipelineVisFunction.input_control_vis(params, schemasDef, uiState); - expect(actual).toMatchSnapshot(); - }); - describe('handles region_map function', () => { it('without buckets', () => { const params = { metric: {} }; diff --git a/src/plugins/visualizations/public/legacy/build_pipeline.ts b/src/plugins/visualizations/public/legacy/build_pipeline.ts index 0c244876ca6a36..29f6ec9b069a7e 100644 --- a/src/plugins/visualizations/public/legacy/build_pipeline.ts +++ b/src/plugins/visualizations/public/legacy/build_pipeline.ts @@ -219,9 +219,6 @@ export const prepareDimension = (variable: string, data: any) => { }; export const buildPipelineVisFunction: BuildPipelineVisFunction = { - input_control_vis: (params) => { - return `input_control_vis ${prepareJson('visConfig', params)}`; - }, region_map: (params, schemas) => { const visConfig = { ...params, diff --git a/test/api_integration/apis/saved_objects/migrations.ts b/test/api_integration/apis/saved_objects/migrations.ts index b5fa16558514a5..fa9c2fd1a2d7fe 100644 --- a/test/api_integration/apis/saved_objects/migrations.ts +++ b/test/api_integration/apis/saved_objects/migrations.ts @@ -54,8 +54,7 @@ function getLogMock() { export default ({ getService }: FtrProviderContext) => { const esClient = getService('es'); - // FLAKY: https://github.com/elastic/kibana/issues/84445 - describe.skip('Kibana index migration', () => { + describe('Kibana index migration', () => { before(() => esClient.indices.delete({ index: '.migrate-*' })); it('Migrates an existing index that has never been migrated before', async () => { @@ -313,7 +312,10 @@ export default ({ getService }: FtrProviderContext) => { result // @ts-expect-error destIndex exists only on MigrationResult status: 'migrated'; .map(({ status, destIndex }) => ({ status, destIndex })) - .sort((a) => (a.destIndex ? 0 : 1)) + .sort(({ destIndex: a }, { destIndex: b }) => + // sort by destIndex in ascending order, keeping falsy values at the end + (a && !b) || a < b ? -1 : (!a && b) || a > b ? 1 : 0 + ) ).to.eql([ { status: 'migrated', destIndex: '.migration-c_2' }, { status: 'skipped', destIndex: undefined }, diff --git a/test/api_integration/apis/telemetry/telemetry_local.js b/test/api_integration/apis/telemetry/telemetry_local.js index b3d34d5910fc3b..a025a65779e9c2 100644 --- a/test/api_integration/apis/telemetry/telemetry_local.js +++ b/test/api_integration/apis/telemetry/telemetry_local.js @@ -62,6 +62,8 @@ export default function ({ getService }) { expect(body.length).to.be(1); const stats = body[0]; expect(stats.collection).to.be('local'); + expect(stats.collectionSource).to.be('local'); + expect(stats.license).to.be.undefined; // OSS cannot get the license expect(stats.stack_stats.kibana.count).to.be.a('number'); expect(stats.stack_stats.kibana.indices).to.be.a('number'); expect(stats.stack_stats.kibana.os.platforms[0].platform).to.be.a('string'); diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx index b90f606d276eb1..399a24a8bc1650 100644 --- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { render, fireEvent, waitFor } from '@testing-library/react'; import { MissingJobsAlert } from './anomaly_detection_setup_link'; -import * as hooks from '../../hooks/useFetcher'; +import * as hooks from '../../hooks/use_fetcher'; async function renderTooltipAnchor({ jobs, diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx index a06d6f357c524b..8a1d73c8189445 100644 --- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx @@ -16,10 +16,10 @@ import { getEnvironmentLabel, } from '../../../common/environment_filter_values'; import { getAPMHref } from '../../components/shared/Links/apm/APMLink'; -import { useApmPluginContext } from '../../hooks/useApmPluginContext'; -import { FETCH_STATUS, useFetcher } from '../../hooks/useFetcher'; -import { useLicense } from '../../hooks/useLicense'; -import { useUrlParams } from '../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS, useFetcher } from '../../hooks/use_fetcher'; +import { useLicenseContext } from '../../context/license/use_license_context'; +import { useUrlParams } from '../../context/url_params_context/use_url_params'; import { APIReturnType } from '../../services/rest/createCallApmApi'; import { units } from '../../style/variables'; @@ -32,7 +32,7 @@ export function AnomalyDetectionSetupLink() { const environment = uiFilters.environment; const { core } = useApmPluginContext(); const canGetJobs = !!core.application.capabilities.ml?.canGetJobs; - const license = useLicense(); + const license = useLicenseContext(); const hasValidLicense = license?.isActive && license?.hasAtLeast('platinum'); const { basePath } = core.http; diff --git a/x-pack/plugins/apm/public/application/action_menu/index.tsx b/x-pack/plugins/apm/public/application/action_menu/index.tsx index 1713ef61fac1ee..438eb2bca7f243 100644 --- a/x-pack/plugins/apm/public/application/action_menu/index.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/index.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { useParams } from 'react-router-dom'; import { getAlertingCapabilities } from '../../components/alerting/get_alert_capabilities'; import { getAPMHref } from '../../components/shared/Links/apm/APMLink'; -import { useApmPluginContext } from '../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../context/apm_plugin/use_apm_plugin_context'; import { AlertingPopoverAndFlyout } from './alerting_popover_flyout'; import { AnomalyDetectionSetupLink } from './anomaly_detection_setup_link'; diff --git a/x-pack/plugins/apm/public/application/application.test.tsx b/x-pack/plugins/apm/public/application/application.test.tsx index 75b7835c13151b..c5091b1b554cc9 100644 --- a/x-pack/plugins/apm/public/application/application.test.tsx +++ b/x-pack/plugins/apm/public/application/application.test.tsx @@ -8,7 +8,7 @@ import { act } from '@testing-library/react'; import { createMemoryHistory } from 'history'; import { Observable } from 'rxjs'; import { AppMountParameters, CoreStart, HttpSetup } from 'src/core/public'; -import { mockApmPluginContextValue } from '../context/ApmPluginContext/MockApmPluginContext'; +import { mockApmPluginContextValue } from '../context/apm_plugin/mock_apm_plugin_context'; import { ApmPluginSetupDeps } from '../plugin'; import { createCallApmApi } from '../services/rest/createCallApmApi'; import { renderApp } from './'; diff --git a/x-pack/plugins/apm/public/application/csmApp.tsx b/x-pack/plugins/apm/public/application/csmApp.tsx index 7fcbe7c518cd0b..4d16643a83fe98 100644 --- a/x-pack/plugins/apm/public/application/csmApp.tsx +++ b/x-pack/plugins/apm/public/application/csmApp.tsx @@ -20,8 +20,8 @@ import { APMRouteDefinition } from '../application/routes'; import { renderAsRedirectTo } from '../components/app/Main/route_config'; import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPathChange'; import { RumHome, UX_LABEL } from '../components/app/RumDashboard/RumHome'; -import { ApmPluginContext } from '../context/ApmPluginContext'; -import { UrlParamsProvider } from '../context/UrlParamsContext'; +import { ApmPluginContext } from '../context/apm_plugin/apm_plugin_context'; +import { UrlParamsProvider } from '../context/url_params_context/url_params_context'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { ConfigSchema } from '../index'; import { ApmPluginSetupDeps, ApmPluginStartDeps } from '../plugin'; diff --git a/x-pack/plugins/apm/public/application/index.tsx b/x-pack/plugins/apm/public/application/index.tsx index 79c29867cb8e31..9c4413765a5006 100644 --- a/x-pack/plugins/apm/public/application/index.tsx +++ b/x-pack/plugins/apm/public/application/index.tsx @@ -25,9 +25,9 @@ import { ScrollToTopOnPathChange } from '../components/app/Main/ScrollToTopOnPat import { ApmPluginContext, ApmPluginContextValue, -} from '../context/ApmPluginContext'; -import { LicenseProvider } from '../context/LicenseContext'; -import { UrlParamsProvider } from '../context/UrlParamsContext'; +} from '../context/apm_plugin/apm_plugin_context'; +import { LicenseProvider } from '../context/license/license_context'; +import { UrlParamsProvider } from '../context/url_params_context/url_params_context'; import { useBreadcrumbs } from '../hooks/use_breadcrumbs'; import { ApmPluginSetupDeps } from '../plugin'; import { createCallApmApi } from '../services/rest/createCallApmApi'; diff --git a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx index 1a565ab8708bc5..f4f2be0a6e8897 100644 --- a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.stories.tsx @@ -7,11 +7,11 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ErrorCountAlertTrigger } from '.'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; export default { title: 'app/ErrorCountAlertTrigger', diff --git a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx index a465b90e7bf05f..efa792ff442734 100644 --- a/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/ErrorCountAlertTrigger/index.tsx @@ -10,8 +10,8 @@ import { useParams } from 'react-router-dom'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG, AlertType } from '../../../../common/alert_types'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { EnvironmentField, ServiceField, IsAboveField } from '../fields'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; @@ -34,7 +34,11 @@ export function ErrorCountAlertTrigger(props: Props) { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); const { start, end } = urlParams; - const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const { environmentOptions } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); const defaults = { threshold: 25, diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx index d20aae29fb8ce5..8b2d4e235ac256 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.stories.tsx @@ -8,12 +8,12 @@ import { cloneDeep, merge } from 'lodash'; import React, { ComponentType } from 'react'; import { MemoryRouter, Route } from 'react-router-dom'; import { TransactionDurationAlertTrigger } from '.'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; export default { title: 'app/TransactionDurationAlertTrigger', diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx index b7220de8079c98..3566850aa24c46 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAlertTrigger/index.tsx @@ -10,8 +10,8 @@ import { map } from 'lodash'; import React from 'react'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; @@ -21,7 +21,7 @@ import { TransactionTypeField, IsAboveField, } from '../fields'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface AlertParams { windowSize: number; @@ -63,10 +63,14 @@ interface Props { export function TransactionDurationAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes } = useApmService(); + const { transactionTypes } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); const { start, end, transactionType } = urlParams; - const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const { environmentOptions } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); if (!transactionTypes.length || !serviceName) { return null; diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx index e13ed6c1bcd6f3..ff5939c6013758 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionDurationAnomalyAlertTrigger/index.tsx @@ -9,8 +9,8 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { ANOMALY_SEVERITY } from '../../../../../ml/common'; import { ALERT_TYPES_CONFIG } from '../../../../common/alert_types'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; import { PopoverExpression } from '../ServiceAlertTrigger/PopoverExpression'; import { @@ -23,7 +23,7 @@ import { ServiceField, TransactionTypeField, } from '../fields'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface Params { windowSize: number; @@ -47,10 +47,14 @@ interface Props { export function TransactionDurationAnomalyAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes } = useApmService(); + const { transactionTypes } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); const { start, end, transactionType } = urlParams; - const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const { environmentOptions } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); if (serviceName && !transactionTypes.length) { return null; diff --git a/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx index 464409ed332e8e..f723febde389d6 100644 --- a/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/TransactionErrorRateAlertTrigger/index.tsx @@ -7,8 +7,8 @@ import { useParams } from 'react-router-dom'; import React from 'react'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ALERT_TYPES_CONFIG, AlertType } from '../../../../common/alert_types'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ServiceAlertTrigger } from '../ServiceAlertTrigger'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; @@ -18,7 +18,7 @@ import { EnvironmentField, IsAboveField, } from '../fields'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface AlertParams { windowSize: number; @@ -38,10 +38,14 @@ interface Props { export function TransactionErrorRateAlertTrigger(props: Props) { const { setAlertParams, alertParams, setAlertProperty } = props; const { urlParams } = useUrlParams(); - const { transactionTypes } = useApmService(); + const { transactionTypes } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); const { start, end, transactionType } = urlParams; - const { environmentOptions } = useEnvironments({ serviceName, start, end }); + const { environmentOptions } = useEnvironmentsFetcher({ + serviceName, + start, + end, + }); if (serviceName && !transactionTypes.length) { return null; diff --git a/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx b/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx index 3ad71b52b6037d..07ab89afd41088 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx @@ -17,8 +17,8 @@ import { import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; import { EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { APIReturnType, callApmApi, diff --git a/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx b/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx index 4364731501b898..30659cf3f93191 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx @@ -19,8 +19,8 @@ import React, { useState } from 'react'; import { useParams } from 'react-router-dom'; import { EuiTitle, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { getDurationFormatter } from '../../../../common/utils/formatters'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { APIReturnType, callApmApi, diff --git a/x-pack/plugins/apm/public/components/app/Correlations/SignificantTermsTable.tsx b/x-pack/plugins/apm/public/components/app/Correlations/SignificantTermsTable.tsx index b74517902f89be..350f64367b766f 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/SignificantTermsTable.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/SignificantTermsTable.tsx @@ -10,7 +10,7 @@ import { useHistory } from 'react-router-dom'; import { EuiBasicTable } from '@elastic/eui'; import { asPercent, asInteger } from '../../../../common/utils/formatters'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { createHref } from '../../shared/Links/url_helpers'; type CorrelationsApiResponse = diff --git a/x-pack/plugins/apm/public/components/app/Correlations/index.tsx b/x-pack/plugins/apm/public/components/app/Correlations/index.tsx index b0f6b83485e39a..16a21e28fc08d7 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/index.tsx @@ -19,10 +19,10 @@ import { } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { enableCorrelations } from '../../../../common/ui_settings_keys'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { LatencyCorrelations } from './LatencyCorrelations'; import { ErrorCorrelations } from './ErrorCorrelations'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { createHref } from '../../shared/Links/url_helpers'; export function Correlations() { diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx index 643064b2f31764..c0ce2ed388a12b 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/DetailView/index.tsx @@ -22,7 +22,7 @@ import { useHistory } from 'react-router-dom'; import styled from 'styled-components'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; import { px, unit, units } from '../../../../style/variables'; import { TransactionDetailLink } from '../../../shared/Links/apm/TransactionDetailLink'; import { DiscoverErrorLink } from '../../../shared/Links/DiscoverLinks/DiscoverErrorLink'; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx index 159f111bee04c3..ab99c6ffa8da03 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/Distribution/index.tsx @@ -20,7 +20,7 @@ import d3 from 'd3'; import React from 'react'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { asRelativeDateTimeRange } from '../../../../../common/utils/formatters'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; type ErrorDistributionAPIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/errors/distribution'>; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx index dc97642dec357c..95ebd5d4036de2 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx @@ -21,14 +21,15 @@ import { RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; import { useTrackPageview } from '../../../../../observability/public'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { fontFamilyCode, fontSizes, px, units } from '../../../style/variables'; import { ApmHeader } from '../../shared/ApmHeader'; import { SearchBar } from '../../shared/search_bar'; import { DetailView } from './DetailView'; import { ErrorDistribution } from './Distribution'; +import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher'; const Titles = styled.div` margin-bottom: ${px(units.plus)}; @@ -88,24 +89,10 @@ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { } }, [serviceName, start, end, groupId, uiFilters]); - const { data: errorDistributionData } = useFetcher(() => { - if (start && end) { - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', - params: { - path: { - serviceName, - }, - query: { - start, - end, - groupId, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, groupId, uiFilters]); + const { errorDistributionData } = useErrorGroupDistributionFetcher({ + serviceName, + groupId, + }); useTrackPageview({ app: 'apm', path: 'error_group_details' }); useTrackPageview({ app: 'apm', path: 'error_group_details', delay: 15000 }); diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx index 84b72b62248b0f..4022caedadaabc 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/__test__/List.test.tsx @@ -6,8 +6,8 @@ import { mount } from 'enzyme'; import React from 'react'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../../../context/url_params_context/mock_url_params_context_provider'; import { mockMoment, toJson } from '../../../../../utils/testHelpers'; import { ErrorGroupList } from '../index'; import props from './props.json'; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx index be1078ea860c30..200a5f467491bd 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/List/index.tsx @@ -19,7 +19,7 @@ import { truncate, unit, } from '../../../../style/variables'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { ManagedTable } from '../../../shared/ManagedTable'; import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; import { TimestampTooltip } from '../../../shared/TimestampTooltip'; diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx index e2a02a2f3e7aec..71cb8e0e016022 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx @@ -16,13 +16,14 @@ import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; import { ErrorDistribution } from '../ErrorGroupDetails/Distribution'; import { ErrorGroupList } from './List'; +import { useErrorGroupDistributionFetcher } from '../../../hooks/use_error_group_distribution_fetcher'; interface ErrorGroupOverviewProps { serviceName: string; @@ -30,26 +31,11 @@ interface ErrorGroupOverviewProps { function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { const { urlParams, uiFilters } = useUrlParams(); - const { start, end, sortField, sortDirection } = urlParams; - - const { data: errorDistributionData } = useFetcher(() => { - if (start && end) { - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', - params: { - path: { - serviceName, - }, - query: { - start, - end, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, uiFilters]); + const { errorDistributionData } = useErrorGroupDistributionFetcher({ + serviceName, + groupId: undefined, + }); const { data: errorGroupListData } = useFetcher(() => { const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc'; diff --git a/x-pack/plugins/apm/public/components/app/Home/Home.test.tsx b/x-pack/plugins/apm/public/components/app/Home/Home.test.tsx index ab4ca1dfbb49d5..148e0733b93ca2 100644 --- a/x-pack/plugins/apm/public/components/app/Home/Home.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Home/Home.test.tsx @@ -7,7 +7,7 @@ import { shallow } from 'enzyme'; import React from 'react'; import { Home } from '../Home'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; describe('Home component', () => { it('should render services', () => { diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx index ce8f2b0ba611a3..839c087305bd85 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/index.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; -import { ApmServiceContextProvider } from '../../../../context/apm_service_context'; +import { ApmServiceContextProvider } from '../../../../context/apm_service/apm_service_context'; import { UNIDENTIFIED_SERVICE_NODES_LABEL } from '../../../../../common/i18n'; import { SERVICE_NODE_NAME_MISSING } from '../../../../../common/service_nodes'; import { APMRouteDefinition } from '../../../../application/routes'; diff --git a/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx b/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx index ac1668a54ab952..10c8417223c773 100644 --- a/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx +++ b/x-pack/plugins/apm/public/components/app/Main/route_config/route_handlers/agent_configuration.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { useFetcher } from '../../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../../hooks/use_fetcher'; import { toQuery } from '../../../../shared/Links/url_helpers'; import { Settings } from '../../../Settings'; import { AgentConfigurationCreateEdit } from '../../../Settings/AgentConfigurations/AgentConfigurationCreateEdit'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx index 3787202f5dee4c..6a56dbf40b33b3 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Charts/PageViewsChart.tsx @@ -27,7 +27,7 @@ import moment from 'moment'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { useUiSetting$ } from '../../../../../../../../src/plugins/kibana_react/public'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { ChartWrapper } from '../ChartWrapper'; import { I18LABELS } from '../translations'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx index 237d33a6a89a38..9fdb34935fee5e 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ClientMetrics/index.tsx @@ -14,7 +14,7 @@ import { EuiToolTip, EuiIconTip, } from '@elastic/eui'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { useUxQuery } from '../hooks/useUxQuery'; import { formatToSec } from '../UXMetrics/KeyUXMetrics'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx index 4c4f7110cafb90..2e6c5c8e23ee52 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ImpactfulMetrics/JSErrors.tsx @@ -16,8 +16,8 @@ import { } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { CsmSharedContext } from '../CsmSharedContext'; import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx index 4b94b98704da76..d7bc94e6564f19 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/index.tsx @@ -6,8 +6,8 @@ import React, { useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../Breakdowns/BreakdownFilter'; import { PageLoadDistChart } from '../Charts/PageLoadDistChart'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts index c3f4ab44179fe3..5c545a63d6d056 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageLoadDistribution/use_breakdowns.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { PercentileRange } from './index'; interface Props { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx index 84668f4b06d772..b339cc7774d754 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/PageViewsTrend/index.tsx @@ -6,8 +6,8 @@ import React, { useState } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { I18LABELS } from '../translations'; import { BreakdownFilter } from '../Breakdowns/BreakdownFilter'; import { PageViewsChart } from '../Charts/PageViewsChart'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx index 6c7e2e22a9893b..8d759d80352d79 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/Panels/MainFilters.tsx @@ -8,9 +8,9 @@ import React from 'react'; import { EuiFlexItem } from '@elastic/eui'; import { EnvironmentFilter } from '../../../shared/EnvironmentFilter'; import { ServiceNameFilter } from '../URLFilter/ServiceNameFilter'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { RUM_AGENT_NAMES } from '../../../../../common/agent_name'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { UserPercentile } from '../UserPercentile'; export function MainFilters() { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx index e4e9109f007e75..c810bd3e7c4893 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/RumDashboard.tsx @@ -20,7 +20,7 @@ import { PageLoadAndViews } from './Panels/PageLoadAndViews'; import { VisitorBreakdownsPanel } from './Panels/VisitorBreakdowns'; import { useBreakPoints } from './hooks/useBreakPoints'; import { getPercentileLabel } from './UXMetrics/translations'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; export function RumDashboard() { const { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx index b70621b1e4cbc4..756014004cc9bb 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/ServiceNameFilter/index.tsx @@ -8,7 +8,7 @@ import { EuiSelect } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; interface Props { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx index abafdf089748bf..a492938deffab6 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/__tests__/SelectableUrlList.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { createMemoryHistory } from 'history'; -import * as fetcherHook from '../../../../../../hooks/useFetcher'; +import * as fetcherHook from '../../../../../../hooks/use_fetcher'; import { SelectableUrlList } from '../SelectableUrlList'; import { render } from '../../../utils/test_helper'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx index 67692a9a8554b8..61f75a430706c1 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/URLSearch/index.tsx @@ -8,8 +8,8 @@ import useDebounce from 'react-use/lib/useDebounce'; import React, { useEffect, useState, FormEvent, useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { EuiTitle } from '@elastic/eui'; -import { useUrlParams } from '../../../../../hooks/useUrlParams'; -import { useFetcher } from '../../../../../hooks/useFetcher'; +import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../../../hooks/use_fetcher'; import { I18LABELS } from '../../translations'; import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; import { formatToSec } from '../../UXMetrics/KeyUXMetrics'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx index ef829ebf7f0cf5..655cdaaca933b0 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/URLFilter/index.tsx @@ -10,9 +10,9 @@ import { useHistory } from 'react-router-dom'; import { omit } from 'lodash'; import { URLSearch } from './URLSearch'; import { UrlList } from './UrlList'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; -import { removeUndefinedProps } from '../../../../context/UrlParamsContext/helpers'; +import { removeUndefinedProps } from '../../../../context/url_params_context/helpers'; import { LocalUIFilterName } from '../../../../../common/ui_filter'; export function URLFilter() { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx index 2ded35deb58f2d..690595caa6c0e0 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/KeyUXMetrics.tsx @@ -20,7 +20,7 @@ import { TBT_LABEL, TBT_TOOLTIP, } from './translations'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { useUxQuery } from '../hooks/useUxQuery'; import { UXMetrics } from '../../../../../../observability/public'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx index 3a6323a747a702..baa9cb7dd74f9f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/__tests__/KeyUXMetrics.test.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { render } from '@testing-library/react'; -import * as fetcherHook from '../../../../../hooks/useFetcher'; +import * as fetcherHook from '../../../../../hooks/use_fetcher'; import { KeyUXMetrics } from '../KeyUXMetrics'; describe('KeyUXMetrics', () => { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx index 95a42ce3018f12..392b42cba12e57 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UXMetrics/index.tsx @@ -15,11 +15,11 @@ import { } from '@elastic/eui'; import { I18LABELS } from '../translations'; import { KeyUXMetrics } from './KeyUXMetrics'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { useUxQuery } from '../hooks/useUxQuery'; import { CoreVitals } from '../../../../../../observability/public'; import { CsmSharedContext } from '../CsmSharedContext'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { getPercentileLabel } from './translations'; export function UXMetrics() { diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx index 04c7e3cc00287f..260c775c7129b5 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/UserPercentile/index.tsx @@ -8,7 +8,7 @@ import React, { useCallback, useEffect } from 'react'; import { EuiSelect } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { I18LABELS } from '../translations'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx index ce9485690b9301..77d5697c317504 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdown/index.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiTitle, EuiSpacer } from '@elastic/eui'; import { VisitorBreakdownChart } from '../Charts/VisitorBreakdownChart'; import { I18LABELS, VisitorBreakdownLabel } from '../translations'; -import { useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; export function VisitorBreakdown() { const { urlParams, uiFilters } = useUrlParams(); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx index 3a5c3d80ca7d19..eff03c58e99912 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/EmbeddedMap.tsx @@ -21,7 +21,7 @@ import { isErrorEmbeddable, } from '../../../../../../../../src/plugins/embeddable/public'; import { useLayerList } from './useLayerList'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { RenderTooltipContentParams } from '../../../../../../maps/public'; import { MapToolTip } from './MapToolTip'; import { useMapFilters } from './useMapFilters'; diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts index a1cdf7bb646e5d..54bfa81f26add3 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useLayerList.ts @@ -22,7 +22,7 @@ import { } from '../../../../../../maps/common/constants'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../../../../src/plugins/apm_oss/public'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { SERVICE_NAME, TRANSACTION_TYPE, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts index 774ac23d231966..c5cf081311f66a 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/VisitorBreakdownMap/useMapFilters.ts @@ -5,7 +5,7 @@ */ import { useMemo } from 'react'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FieldFilter as Filter } from '../../../../../../../../src/plugins/data/common'; import { CLIENT_GEO_COUNTRY_ISO_CODE, diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts index 16396dc9fc15bd..c8cd2c2c64da8f 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/hooks/useUxQuery.ts @@ -5,7 +5,7 @@ */ import { useMemo } from 'react'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; export function useUxQuery() { const { urlParams, uiFilters } = useUrlParams(); diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx b/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx index 5522cad5690bc3..d5b8cd83d437c4 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/utils/test_helper.tsx @@ -13,7 +13,7 @@ import { Router } from 'react-router-dom'; import { MemoryHistory } from 'history'; import { EuiThemeProvider } from '../../../../../../observability/public'; import { KibanaContextProvider } from '../../../../../../../../src/plugins/kibana_react/public'; -import { UrlParamsProvider } from '../../../../context/UrlParamsContext'; +import { UrlParamsProvider } from '../../../../context/url_params_context/url_params_context'; export const core = ({ http: { diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx index 1187b71dff8256..659f9f63d0cfa0 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.test.tsx @@ -9,7 +9,7 @@ import { render } from '@testing-library/react'; import cytoscape from 'cytoscape'; import React, { ReactNode } from 'react'; import { ThemeContext } from 'styled-components'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { Controls } from './Controls'; import { CytoscapeContext } from './Cytoscape'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx index b4408e20c04d2f..a23fa72314aed8 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Controls.tsx @@ -8,9 +8,9 @@ import { EuiButtonIcon, EuiPanel, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext, useEffect, useState } from 'react'; import styled from 'styled-components'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useTheme } from '../../../hooks/useTheme'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useTheme } from '../../../hooks/use_theme'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { getAPMHref } from '../../shared/Links/apm/APMLink'; import { APMQueryParams } from '../../shared/Links/url_helpers'; import { CytoscapeContext } from './Cytoscape'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index 8a76c5f7bd8f1a..1dea95d369966b 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -16,7 +16,7 @@ import React, { useRef, useState, } from 'react'; -import { useTheme } from '../../../hooks/useTheme'; +import { useTheme } from '../../../hooks/use_theme'; import { getCytoscapeOptions } from './cytoscape_options'; import { useCytoscapeEventHandlers } from './use_cytoscape_event_handlers'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx index 63a9cf985959f5..07e88294caadbc 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/EmptyBanner.tsx @@ -10,7 +10,7 @@ import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { CytoscapeContext } from './Cytoscape'; -import { useTheme } from '../../../hooks/useTheme'; +import { useTheme } from '../../../hooks/use_theme'; const EmptyBannerContainer = styled.div` margin: ${({ theme }) => theme.eui.gutterTypes.gutterSmall}; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx index 788e5f25b6310a..36c0c9f37f9c71 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/AnomalyDetection.tsx @@ -18,7 +18,7 @@ import { getServiceHealthStatus, getServiceHealthStatusColor, } from '../../../../../common/service_health_status'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { fontSize, px } from '../../../../style/variables'; import { asInteger, asDuration } from '../../../../../common/utils/formatters'; import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx index f98a7a1b33dd88..d9f33b8fca4cd4 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.test.tsx @@ -7,7 +7,7 @@ import React, { ReactNode } from 'react'; import { Buttons } from './Buttons'; import { render } from '@testing-library/react'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; function Wrapper({ children }: { children?: ReactNode }) { return {children}; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx index 8670cf623c2532..56110a89ed888c 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Buttons.tsx @@ -9,8 +9,8 @@ import { EuiButton, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { MouseEvent } from 'react'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { getAPMHref } from '../../../shared/Links/apm/APMLink'; import { APMQueryParams } from '../../../shared/Links/url_helpers'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx index 70eb5eaf8e576c..313b262508c613 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/Popover.stories.tsx @@ -8,8 +8,8 @@ import cytoscape from 'cytoscape'; import { HttpSetup } from 'kibana/public'; import React, { ComponentType } from 'react'; import { EuiThemeProvider } from '../../../../../../observability/public'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider'; import { createCallApmApi } from '../../../../services/rest/createCallApmApi'; import { CytoscapeContext } from '../Cytoscape'; import { Popover } from './'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx index be8c5cf8cd4355..3b737c6fa4170f 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/ServiceStatsFetcher.tsx @@ -15,8 +15,8 @@ import { i18n } from '@kbn/i18n'; import { isNumber } from 'lodash'; import { ServiceNodeStats } from '../../../../../common/service_map'; import { ServiceStatsList } from './ServiceStatsList'; -import { useFetcher, FETCH_STATUS } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { AnomalyDetection } from './AnomalyDetection'; import { ServiceAnomalyStats } from '../../../../../common/anomaly_detection'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx index 7b7e3b46bb317a..036d02531f7944 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/Popover/index.tsx @@ -15,7 +15,7 @@ import React, { } from 'react'; import { EuiPopover } from '@elastic/eui'; import cytoscape from 'cytoscape'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { SERVICE_NAME } from '../../../../../common/elasticsearch_fieldnames'; import { CytoscapeContext } from '../Cytoscape'; import { getAnimationOptions } from '../cytoscape_options'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts index d8a8a3c8e9ab4d..e2a54f60486826 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/cytoscape_options.ts @@ -15,7 +15,7 @@ import { getServiceHealthStatusColor, ServiceHealthStatus, } from '../../../../common/service_health_status'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { defaultIcon, iconForNode } from './icons'; export const popoverWidth = 280; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/empty_banner.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/empty_banner.test.tsx index ae27d4d3baf759..a1fb7e7077add6 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/empty_banner.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/empty_banner.test.tsx @@ -7,7 +7,7 @@ import { act, waitFor } from '@testing-library/react'; import cytoscape from 'cytoscape'; import React, { ReactNode } from 'react'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { renderWithTheme } from '../../../utils/testHelpers'; import { CytoscapeContext } from './Cytoscape'; import { EmptyBanner } from './EmptyBanner'; diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx index 2a5b4ce44ff467..97e507d7cc8718 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.test.tsx @@ -11,9 +11,9 @@ import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; import { License } from '../../../../../licensing/common/license'; import { EuiThemeProvider } from '../../../../../observability/public'; import { FETCH_STATUS } from '../../../../../observability/public/hooks/use_fetcher'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { LicenseContext } from '../../../context/LicenseContext'; -import * as useFetcherModule from '../../../hooks/useFetcher'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { LicenseContext } from '../../../context/license/license_context'; +import * as useFetcherModule from '../../../hooks/use_fetcher'; import { ServiceMap } from './'; const KibanaReactContext = createKibanaReactContext({ diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx index 1731d3f9430d42..48a7f8f77ab840 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -13,10 +13,10 @@ import { isActivePlatinumLicense, SERVICE_MAP_TIMEOUT_ERROR, } from '../../../../common/service_map'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; -import { useLicense } from '../../../hooks/useLicense'; -import { useTheme } from '../../../hooks/useTheme'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useLicenseContext } from '../../../context/license/use_license_context'; +import { useTheme } from '../../../hooks/use_theme'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { DatePicker } from '../../shared/DatePicker'; import { LicensePrompt } from '../../shared/LicensePrompt'; @@ -70,7 +70,7 @@ export function ServiceMap({ serviceName, }: PropsWithChildren) { const theme = useTheme(); - const license = useLicense(); + const license = useLicenseContext(); const { urlParams } = useUrlParams(); const { data = { elements: [] }, status, error } = useFetcher(() => { diff --git a/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx index 74e7b652d0ebe0..c4c227deb69184 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceNodeOverview/index.tsx @@ -21,8 +21,8 @@ import { asInteger, asPercent, } from '../../../../common/utils/formatters'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { px, truncate, unit } from '../../../style/variables'; import { ServiceNodeMetricOverviewLink } from '../../shared/Links/apm/ServiceNodeMetricOverviewLink'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx index 7c0869afe0cd1a..18067a43861bdb 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/ServicePage/ServicePage.tsx @@ -21,7 +21,7 @@ import { omitAllOption, getOptionLabel, } from '../../../../../../../common/agent_configuration/all_option'; -import { useFetcher, FETCH_STATUS } from '../../../../../../hooks/useFetcher'; +import { useFetcher, FETCH_STATUS } from '../../../../../../hooks/use_fetcher'; import { FormRowSelect } from './FormRowSelect'; import { APMLink } from '../../../../../shared/Links/apm/APMLink'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx index 54440559070ad8..7e1146596dd877 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/SettingsPage.tsx @@ -32,8 +32,8 @@ import { validateSetting, } from '../../../../../../../common/agent_configuration/setting_definitions'; import { AgentName } from '../../../../../../../typings/es_schemas/ui/fields/agent'; -import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; -import { FETCH_STATUS } from '../../../../../../hooks/useFetcher'; +import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS } from '../../../../../../hooks/use_fetcher'; import { saveConfig } from './saveConfig'; import { SettingFormRow } from './SettingFormRow'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx index db3f2c374a1ae6..5ca643428e49c1 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.stories.tsx @@ -14,13 +14,13 @@ import { storiesOf } from '@storybook/react'; import React from 'react'; import { HttpSetup } from 'kibana/public'; import { AgentConfiguration } from '../../../../../../common/agent_configuration/configuration_types'; -import { FETCH_STATUS } from '../../../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../../../hooks/use_fetcher'; import { createCallApmApi } from '../../../../../services/rest/createCallApmApi'; import { AgentConfigurationCreateEdit } from './index'; import { ApmPluginContext, ApmPluginContextValue, -} from '../../../../../context/ApmPluginContext'; +} from '../../../../../context/apm_plugin/apm_plugin_context'; import { EuiThemeProvider } from '../../../../../../../observability/public'; storiesOf( diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx index 4f94f255a4e4c2..998175c895557b 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/index.tsx @@ -14,7 +14,7 @@ import { AgentConfiguration, AgentConfigurationIntake, } from '../../../../../../common/agent_configuration/configuration_types'; -import { FetcherResult } from '../../../../../hooks/useFetcher'; +import { FetcherResult } from '../../../../../hooks/use_fetcher'; import { fromQuery, toQuery } from '../../../../shared/Links/url_helpers'; import { ServicePage } from './ServicePage/ServicePage'; import { SettingsPage } from './SettingsPage/SettingsPage'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx index adae50db85ada1..958aafa8159df7 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx @@ -13,7 +13,7 @@ import { APIReturnType, callApmApi, } from '../../../../../services/rest/createCallApmApi'; -import { useApmPluginContext } from '../../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; type Config = APIReturnType<'GET /api/apm/settings/agent-configuration'>[0]; diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx index 81079d78a148a5..be4edbe2ea270f 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/index.tsx @@ -18,9 +18,9 @@ import React, { useState } from 'react'; import { useLocation } from 'react-router-dom'; import { APIReturnType } from '../../../../../services/rest/createCallApmApi'; import { getOptionLabel } from '../../../../../../common/agent_configuration/all_option'; -import { useApmPluginContext } from '../../../../../hooks/useApmPluginContext'; -import { FETCH_STATUS } from '../../../../../hooks/useFetcher'; -import { useTheme } from '../../../../../hooks/useTheme'; +import { useApmPluginContext } from '../../../../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS } from '../../../../../hooks/use_fetcher'; +import { useTheme } from '../../../../../hooks/use_theme'; import { px, units } from '../../../../../style/variables'; import { createAgentConfigurationHref, diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx index 12c63f8702f256..c408d5e960cf3b 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/index.tsx @@ -16,8 +16,8 @@ import { isEmpty } from 'lodash'; import React from 'react'; import { useLocation } from 'react-router-dom'; import { useTrackPageview } from '../../../../../../observability/public'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { createAgentConfigurationHref } from '../../../shared/Links/apm/agentConfigurationLinks'; import { AgentConfigurationList } from './List'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx index 53794ca9965ff8..2adf85181886c8 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.test.tsx @@ -7,8 +7,8 @@ import { render } from '@testing-library/react'; import React from 'react'; import { ApmIndices } from '.'; -import * as hooks from '../../../../hooks/useFetcher'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import * as hooks from '../../../../hooks/use_fetcher'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; describe('ApmIndices', () => { it('should not get stuck in infinite loop', () => { diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx index a1ef9ddd87271a..5a5d20cde9ade4 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx @@ -19,10 +19,10 @@ import { EuiButton, EuiButtonEmpty, } from '@elastic/eui'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { clearCache } from '../../../../services/rest/callApi'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; const APM_INDEX_LABELS = [ { diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx index 5014584c3928af..ffcb85384642ac 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx @@ -10,7 +10,7 @@ import { NotificationsStart } from 'kibana/public'; import React, { useState } from 'react'; import { px, unit } from '../../../../../../style/variables'; import { callApmApi } from '../../../../../../services/rest/createCallApmApi'; -import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; interface Props { onDelete: () => void; diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx index c6566af3a8b61b..f9c5aa17e411a8 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/index.tsx @@ -15,7 +15,7 @@ import { import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import { Filter } from '../../../../../../../common/custom_link/custom_link_types'; -import { useApmPluginContext } from '../../../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context'; import { FiltersSection } from './FiltersSection'; import { FlyoutFooter } from './FlyoutFooter'; import { LinkSection } from './LinkSection'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx index 96a634828f6696..1da7d415b56609 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.test.tsx @@ -14,15 +14,15 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import * as apmApi from '../../../../../services/rest/createCallApmApi'; import { License } from '../../../../../../../licensing/common/license'; -import * as hooks from '../../../../../hooks/useFetcher'; -import { LicenseContext } from '../../../../../context/LicenseContext'; +import * as hooks from '../../../../../hooks/use_fetcher'; +import { LicenseContext } from '../../../../../context/license/license_context'; import { CustomLinkOverview } from '.'; import { expectTextsInDocument, expectTextsNotInDocument, } from '../../../../../utils/testHelpers'; import * as saveCustomLink from './CreateEditCustomLinkFlyout/saveCustomLink'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; const data = [ { diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx index 771a8c6154dc04..6b5c7d583ee8a8 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/index.tsx @@ -17,8 +17,8 @@ import { isEmpty } from 'lodash'; import React, { useEffect, useState } from 'react'; import { INVALID_LICENSE } from '../../../../../../common/custom_link'; import { CustomLink } from '../../../../../../common/custom_link/custom_link_types'; -import { FETCH_STATUS, useFetcher } from '../../../../../hooks/useFetcher'; -import { useLicense } from '../../../../../hooks/useLicense'; +import { FETCH_STATUS, useFetcher } from '../../../../../hooks/use_fetcher'; +import { useLicenseContext } from '../../../../../context/license/use_license_context'; import { LicensePrompt } from '../../../../shared/LicensePrompt'; import { CreateCustomLinkButton } from './CreateCustomLinkButton'; import { CreateEditCustomLinkFlyout } from './CreateEditCustomLinkFlyout'; @@ -26,7 +26,7 @@ import { CustomLinkTable } from './CustomLinkTable'; import { EmptyPrompt } from './EmptyPrompt'; export function CustomLinkOverview() { - const license = useLicense(); + const license = useLicenseContext(); const hasValidLicense = license?.isActive && license?.hasAtLeast('gold'); const [isFlyoutOpen, setIsFlyoutOpen] = useState(false); diff --git a/x-pack/plugins/apm/public/components/app/Settings/Settings.test.tsx b/x-pack/plugins/apm/public/components/app/Settings/Settings.test.tsx index 21da12477b0246..cfef7ca937f66e 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/Settings.test.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/Settings.test.tsx @@ -5,7 +5,7 @@ */ import { render } from '@testing-library/react'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import React, { ReactNode } from 'react'; import { Settings } from './'; import { createMemoryHistory } from 'history'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx index ccc1778e9fbdea..e709c7e104472a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/add_environments.tsx @@ -21,8 +21,8 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ML_ERRORS } from '../../../../../common/anomaly_detection'; -import { useFetcher, FETCH_STATUS } from '../../../../hooks/useFetcher'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useFetcher, FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { createJobs } from './create_jobs'; import { getEnvironmentLabel } from '../../../../../common/environment_filter_values'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx index 9f43f4aa3df913..addfd64a9ef624 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/index.tsx @@ -9,12 +9,12 @@ import { EuiTitle, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { EuiPanel, EuiEmptyPrompt } from '@elastic/eui'; import { ML_ERRORS } from '../../../../../common/anomaly_detection'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { JobsList } from './jobs_list'; import { AddEnvironments } from './add_environments'; -import { useFetcher } from '../../../../hooks/useFetcher'; +import { useFetcher } from '../../../../hooks/use_fetcher'; import { LicensePrompt } from '../../../shared/LicensePrompt'; -import { useLicense } from '../../../../hooks/useLicense'; +import { useLicenseContext } from '../../../../context/license/use_license_context'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; export type AnomalyDetectionApiResponse = APIReturnType<'GET /api/apm/settings/anomaly-detection/jobs'>; @@ -27,7 +27,7 @@ const DEFAULT_VALUE: AnomalyDetectionApiResponse = { export function AnomalyDetection() { const plugin = useApmPluginContext(); const canGetJobs = !!plugin.core.application.capabilities.ml?.canGetJobs; - const license = useLicense(); + const license = useLicenseContext(); const hasValidLicense = license?.isActive && license?.hasAtLeast('platinum'); const [viewAddEnvironments, setViewAddEnvironments] = useState(false); diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx index 137dcfcdbb4f07..8d6a0740a8a080 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/jobs_list.tsx @@ -16,7 +16,7 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { ITableColumn, ManagedTable } from '../../../shared/ManagedTable'; import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; import { MLJobLink } from '../../../shared/Links/MachineLearningLinks/MLJobLink'; diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx index 1844e5754cfba5..9e21eb2ffc870a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/legacy_jobs_callout.tsx @@ -7,7 +7,7 @@ import { EuiCallOut, EuiButton } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useMlHref } from '../../../../../../ml/public'; export function LegacyJobsCallout() { diff --git a/x-pack/plugins/apm/public/components/app/Settings/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/index.tsx index c9c577285ee807..e974f05fbe9941 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/index.tsx @@ -16,7 +16,7 @@ import React, { ReactNode } from 'react'; import { RouteComponentProps } from 'react-router-dom'; import { HeaderMenuPortal } from '../../../../../observability/public'; import { ActionMenu } from '../../../application/action_menu'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { getAPMHref } from '../../shared/Links/apm/APMLink'; import { HomeLink } from '../../shared/Links/apm/HomeLink'; diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx index c1c9edb01eb8e3..3f325f17af82d0 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/index.tsx @@ -8,8 +8,8 @@ import { EuiEmptyPrompt } from '@elastic/eui'; import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { getRedirectToTransactionDetailPageUrl } from './get_redirect_to_transaction_detail_page_url'; import { getRedirectToTracePageUrl } from './get_redirect_to_trace_page_url'; diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx index e7c0400290dcb7..c07e00ef387c9c 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx @@ -8,13 +8,13 @@ import { shallow } from 'enzyme'; import React, { ReactNode } from 'react'; import { MemoryRouter, RouteComponentProps } from 'react-router-dom'; import { TraceLink } from './'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; -import * as hooks from '../../../hooks/useFetcher'; -import * as urlParamsHooks from '../../../hooks/useUrlParams'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import * as hooks from '../../../hooks/use_fetcher'; +import * as urlParamsHooks from '../../../context/url_params_context/use_url_params'; function Wrapper({ children }: { children?: ReactNode }) { return ( diff --git a/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx b/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx index cbab2c44132f31..ab10d6b4f46a02 100644 --- a/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceOverview/index.tsx @@ -8,8 +8,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiPanel } from '@elastic/eui'; import React, { useMemo } from 'react'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx index 003f2ed05b09ed..bebd5bdabbae37 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/Distribution/index.tsx @@ -27,8 +27,8 @@ import { ValuesType } from 'utility-types'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { useTheme } from '../../../../../../observability/public'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { unit } from '../../../../style/variables'; import { ChartContainer } from '../../../shared/charts/chart_container'; import { EmptyMessage } from '../../../shared/EmptyMessage'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx index 48413d6207ee3e..43732c23aea647 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx @@ -10,7 +10,7 @@ import { Location } from 'history'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { TransactionMetadata } from '../../../shared/MetadataTable/TransactionMetadata'; import { WaterfallContainer } from './WaterfallContainer'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx index e3ba02ce42c2e1..5217c2abb11dc4 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx @@ -9,7 +9,7 @@ import { MemoryRouter } from 'react-router-dom'; import { EuiThemeProvider } from '../../../../../../../observability/public'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { TraceAPIResponse } from '../../../../../../server/lib/traces/get_trace'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { WaterfallContainer } from './index'; import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers'; import { diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx index 501ca6d33d5af1..806350679df55c 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx @@ -6,7 +6,7 @@ import { Location } from 'history'; import React from 'react'; -import { IUrlParams } from '../../../../../context/UrlParamsContext/types'; +import { IUrlParams } from '../../../../../context/url_params_context/types'; import { ServiceLegends } from './ServiceLegends'; import { IWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers'; import { Waterfall } from './Waterfall'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts index f78fe39120d8d1..0f6bebd9037cce 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/waterfallContainer.stories.data.ts @@ -5,7 +5,7 @@ */ import { Location } from 'history'; -import { IUrlParams } from '../../../../../context/UrlParamsContext/types'; +import { IUrlParams } from '../../../../../context/url_params_context/types'; export const location = { pathname: '/services/opbeans-go/transactions/view', diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx index c9420dbb81cb99..d90fe393c94a45 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx @@ -18,7 +18,7 @@ import { Location } from 'history'; import React, { useEffect, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; import { TransactionSummary } from '../../../shared/Summary/TransactionSummary'; diff --git a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx index 8f335ddc71c726..c491b9f0e1eff9 100644 --- a/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/index.tsx @@ -17,19 +17,19 @@ import React, { useMemo } from 'react'; import { isEmpty, flatten } from 'lodash'; import { useHistory } from 'react-router-dom'; import { RouteComponentProps } from 'react-router-dom'; -import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; -import { useTransactionDistribution } from '../../../hooks/useTransactionDistribution'; -import { useWaterfall } from '../../../hooks/useWaterfall'; +import { useTransactionChartsFetcher } from '../../../hooks/use_transaction_charts_fetcher'; +import { useTransactionDistributionFetcher } from '../../../hooks/use_transaction_distribution_fetcher'; +import { useWaterfallFetcher } from './use_waterfall_fetcher'; import { ApmHeader } from '../../shared/ApmHeader'; import { TransactionCharts } from '../../shared/charts/transaction_charts'; import { TransactionDistribution } from './Distribution'; import { WaterfallWithSummmary } from './WaterfallWithSummmary'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; -import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { HeightRetainer } from '../../shared/HeightRetainer'; import { Correlations } from '../Correlations'; @@ -50,18 +50,20 @@ export function TransactionDetails({ const { urlParams } = useUrlParams(); const history = useHistory(); const { - data: distributionData, - status: distributionStatus, - } = useTransactionDistribution(urlParams); + distributionData, + distributionStatus, + } = useTransactionDistributionFetcher(); const { - data: transactionChartsData, - status: transactionChartsStatus, - } = useTransactionCharts(); + transactionChartsData, + transactionChartsStatus, + } = useTransactionChartsFetcher(); - const { waterfall, exceedsMax, status: waterfallStatus } = useWaterfall( - urlParams - ); + const { + waterfall, + exceedsMax, + status: waterfallStatus, + } = useWaterfallFetcher(); const { transactionName, transactionType } = urlParams; useTrackPageview({ app: 'apm', path: 'transaction_details' }); diff --git a/x-pack/plugins/apm/public/hooks/useWaterfall.ts b/x-pack/plugins/apm/public/components/app/TransactionDetails/use_waterfall_fetcher.ts similarity index 75% rename from x-pack/plugins/apm/public/hooks/useWaterfall.ts rename to x-pack/plugins/apm/public/components/app/TransactionDetails/use_waterfall_fetcher.ts index 6264ec45088a25..7458fa79bd1f3f 100644 --- a/x-pack/plugins/apm/public/hooks/useWaterfall.ts +++ b/x-pack/plugins/apm/public/components/app/TransactionDetails/use_waterfall_fetcher.ts @@ -5,9 +5,9 @@ */ import { useMemo } from 'react'; -import { IUrlParams } from '../context/UrlParamsContext/types'; -import { useFetcher } from './useFetcher'; -import { getWaterfall } from '../components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { getWaterfall } from './WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; const INITIAL_DATA = { root: undefined, @@ -15,7 +15,8 @@ const INITIAL_DATA = { errorsPerTransaction: {}, }; -export function useWaterfall(urlParams: IUrlParams) { +export function useWaterfallFetcher() { + const { urlParams } = useUrlParams(); const { traceId, start, end, transactionId } = urlParams; const { data = INITIAL_DATA, status, error } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx b/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx index 003bd6ba4c122b..ae0dd85b6a8b55 100644 --- a/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx +++ b/x-pack/plugins/apm/public/components/app/service_details/service_detail_tabs.tsx @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import React, { ReactNode } from 'react'; import { isJavaAgentName, isRumAgentName } from '../../../../common/agent_name'; import { enableServiceOverview } from '../../../../common/ui_settings_keys'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { useErrorOverviewHref } from '../../shared/Links/apm/ErrorOverviewLink'; import { useMetricOverviewHref } from '../../shared/Links/apm/MetricOverviewLink'; import { useServiceMapHref } from '../../shared/Links/apm/ServiceMapLink'; @@ -23,7 +23,7 @@ import { ServiceMetrics } from '../service_metrics'; import { ServiceNodeOverview } from '../ServiceNodeOverview'; import { ServiceOverview } from '../service_overview'; import { TransactionOverview } from '../transaction_overview'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; interface Tab { key: string; @@ -44,7 +44,7 @@ interface Props { } export function ServiceDetailTabs({ serviceName, tab }: Props) { - const { agentName } = useApmService(); + const { agentName } = useApmServiceContext(); const { uiSettings } = useApmPluginContext().core; const overviewTab = { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx index e8ad3e65b1a47e..3b97849b077902 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/HealthBadge.tsx @@ -10,7 +10,7 @@ import { getServiceHealthStatusLabel, ServiceHealthStatus, } from '../../../../../common/service_health_status'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; export function HealthBadge({ healthStatus, diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx index 39cb73d2a0dd9b..1c6fa9fe0447e1 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/ServiceList/service_list.test.tsx @@ -7,7 +7,7 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ServiceHealthStatus } from '../../../../../common/service_health_status'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { mockMoment, renderWithTheme } from '../../../../utils/testHelpers'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { ServiceList, SERVICE_COLUMNS } from './'; diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx index 3c84b3982642d4..b1d725bba0ca90 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/index.tsx @@ -17,17 +17,17 @@ import url from 'url'; import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; -import { useAnomalyDetectionJobs } from '../../../hooks/useAnomalyDetectionJobs'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { useLocalStorage } from '../../../hooks/useLocalStorage'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; import { Correlations } from '../Correlations'; import { NoServicesMessage } from './no_services_message'; import { ServiceList } from './ServiceList'; import { MLCallout } from './ServiceList/MLCallout'; +import { useAnomalyDetectionJobsFetcher } from './use_anomaly_detection_jobs_fetcher'; const initialData = { items: [], @@ -37,12 +37,10 @@ const initialData = { let hasDisplayedToast = false; -export function ServiceInventory() { +function useServicesFetcher() { + const { urlParams, uiFilters } = useUrlParams(); const { core } = useApmPluginContext(); - const { - urlParams: { start, end }, - uiFilters, - } = useUrlParams(); + const { start, end } = urlParams; const { data = initialData, status } = useFetcher( (callApmApi) => { if (start && end) { @@ -92,6 +90,13 @@ export function ServiceInventory() { } }, [data.hasLegacyData, core.http.basePath, core.notifications.toasts]); + return { servicesData: data, servicesStatus: status }; +} + +export function ServiceInventory() { + const { core } = useApmPluginContext(); + const { servicesData, servicesStatus } = useServicesFetcher(); + // The page is called "service inventory" to avoid confusion with the // "service overview", but this is tracked in some dashboards because it's the // initial landing page for APM, so it stays as "services_overview" (plural.) @@ -110,9 +115,9 @@ export function ServiceInventory() { ); const { - data: anomalyDetectionJobsData, - status: anomalyDetectionJobsStatus, - } = useAnomalyDetectionJobs(); + anomalyDetectionJobsData, + anomalyDetectionJobsStatus, + } = useAnomalyDetectionJobsFetcher(); const [userHasDismissedCallout, setUserHasDismissedCallout] = useLocalStorage( 'apm.userHasDismissedServiceInventoryMlCallout', @@ -148,11 +153,11 @@ export function ServiceInventory() { } /> diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx index 0fc2a2b4cdcef0..cf1ccfbd36aaf3 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.test.tsx @@ -6,8 +6,8 @@ import { render } from '@testing-library/react'; import React, { ReactNode } from 'react'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { NoServicesMessage } from './no_services_message'; function Wrapper({ children }: { children?: ReactNode }) { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx index d2763c6632c652..b20efc440312c8 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/no_services_message.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { KibanaLink } from '../../shared/Links/KibanaLink'; import { SetupInstructionsLink } from '../../shared/Links/SetupInstructionsLink'; import { LoadingStatePrompt } from '../../shared/LoadingStatePrompt'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { ErrorStatePrompt } from '../../shared/ErrorStatePrompt'; interface Props { diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx index de5e92664a769c..1c838a01d05c74 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_inventory/service_inventory.test.tsx @@ -13,17 +13,17 @@ import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; import { ServiceHealthStatus } from '../../../../common/service_health_status'; import { ServiceInventory } from '.'; import { EuiThemeProvider } from '../../../../../observability/public'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; -import * as useAnomalyDetectionJobs from '../../../hooks/useAnomalyDetectionJobs'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import * as useLocalUIFilters from '../../../hooks/useLocalUIFilters'; -import * as useDynamicIndexPatternHooks from '../../../hooks/useDynamicIndexPattern'; +import * as useDynamicIndexPatternHooks from '../../../hooks/use_dynamic_index_pattern'; import { SessionStorageMock } from '../../../services/__test__/SessionStorageMock'; -import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; +import * as hook from './use_anomaly_detection_jobs_fetcher'; const KibanaReactContext = createKibanaReactContext({ usageCollection: { reportUiStats: () => {} }, @@ -80,19 +80,13 @@ describe('ServiceInventory', () => { status: FETCH_STATUS.SUCCESS, }); - jest - .spyOn(useAnomalyDetectionJobs, 'useAnomalyDetectionJobs') - .mockReturnValue({ - status: FETCH_STATUS.SUCCESS, - data: { - jobs: [], - hasLegacyJobs: false, - }, - refetch: () => undefined, - }); + jest.spyOn(hook, 'useAnomalyDetectionJobsFetcher').mockReturnValue({ + anomalyDetectionJobsStatus: FETCH_STATUS.SUCCESS, + anomalyDetectionJobsData: { jobs: [], hasLegacyJobs: false }, + }); jest - .spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPattern') + .spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPatternFetcher') .mockReturnValue({ indexPattern: undefined, status: FETCH_STATUS.SUCCESS, diff --git a/x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts b/x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts similarity index 50% rename from x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts rename to x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts index bef016e5bedd14..901841ac4d593f 100644 --- a/x-pack/plugins/apm/public/hooks/useAnomalyDetectionJobs.ts +++ b/x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts @@ -3,16 +3,15 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { useFetcher } from '../../../hooks/use_fetcher'; -import { useFetcher } from './useFetcher'; - -export function useAnomalyDetectionJobs() { - return useFetcher( +export function useAnomalyDetectionJobsFetcher() { + const { data, status } = useFetcher( (callApmApi) => - callApmApi({ - endpoint: `GET /api/apm/settings/anomaly-detection/jobs`, - }), + callApmApi({ endpoint: `GET /api/apm/settings/anomaly-detection/jobs` }), [], { showToastOnError: false } ); + + return { anomalyDetectionJobsData: data, anomalyDetectionJobsStatus: status }; } diff --git a/x-pack/plugins/apm/public/components/app/service_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/service_metrics/index.tsx index d0f8fc1e613323..bf99f5c87fa6a8 100644 --- a/x-pack/plugins/apm/public/components/app/service_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_metrics/index.tsx @@ -13,10 +13,10 @@ import { EuiFlexGroup, } from '@elastic/eui'; import React, { useMemo } from 'react'; -import { useServiceMetricCharts } from '../../../hooks/useServiceMetricCharts'; +import { useServiceMetricChartsFetcher } from '../../../hooks/use_service_metric_charts_fetcher'; import { MetricsChart } from '../../shared/charts/metrics_chart'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { Projection } from '../../../../common/projections'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; @@ -31,7 +31,9 @@ export function ServiceMetrics({ serviceName, }: ServiceMetricsProps) { const { urlParams } = useUrlParams(); - const { data, status } = useServiceMetricCharts(urlParams, agentName); + const { data, status } = useServiceMetricChartsFetcher({ + serviceNodeName: undefined, + }); const { start, end } = urlParams; const localFiltersConfig: React.ComponentProps< diff --git a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx index c6f7e68e4f4d06..0ba45fae15fef6 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.test.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import { ServiceNodeMetrics } from '.'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { RouteComponentProps } from 'react-router-dom'; describe('ServiceNodeMetrics', () => { diff --git a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx index 11de40b47ff861..aa1d9cccbdfa62 100644 --- a/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_node_metrics/index.tsx @@ -22,11 +22,11 @@ import React from 'react'; import { RouteComponentProps } from 'react-router-dom'; import styled from 'styled-components'; import { SERVICE_NODE_NAME_MISSING } from '../../../../common/service_nodes'; -import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context'; -import { FETCH_STATUS, useFetcher } from '../../../hooks/useFetcher'; -import { useServiceMetricCharts } from '../../../hooks/useServiceMetricCharts'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; +import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; +import { useServiceMetricChartsFetcher } from '../../../hooks/use_service_metric_charts_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { px, truncate, unit } from '../../../style/variables'; import { ApmHeader } from '../../shared/ApmHeader'; import { MetricsChart } from '../../shared/charts/metrics_chart'; @@ -58,12 +58,8 @@ type ServiceNodeMetricsProps = RouteComponentProps<{ export function ServiceNodeMetrics({ match }: ServiceNodeMetricsProps) { const { urlParams, uiFilters } = useUrlParams(); const { serviceName, serviceNodeName } = match.params; - const { agentName } = useApmService(); - const { data } = useServiceMetricCharts( - urlParams, - agentName, - serviceNodeName - ); + const { agentName } = useApmServiceContext(); + const { data } = useServiceMetricChartsFetcher({ serviceNodeName }); const { start, end } = urlParams; const { data: { host, containerId } = INITIAL_DATA, status } = useFetcher( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx index 15125128d97816..dcb407d27e6901 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/index.tsx @@ -15,7 +15,7 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useTrackPageview } from '../../../../../observability/public'; import { isRumAgentName } from '../../../../common/agent_name'; -import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event_context'; +import { ChartPointerEventContextProvider } from '../../../context/chart_pointer_event/chart_pointer_event_context'; import { TransactionBreakdownChart } from '../../shared/charts/transaction_breakdown_chart'; import { TransactionErrorRateChart } from '../../shared/charts/transaction_error_rate_chart'; import { ServiceMapLink } from '../../shared/Links/apm/ServiceMapLink'; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx index b364f027538a6d..949f5cce0a64f9 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview.test.tsx @@ -8,17 +8,17 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { CoreStart } from 'src/core/public'; import { createKibanaReactContext } from '../../../../../../../src/plugins/kibana_react/public'; -import { ApmPluginContextValue } from '../../../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../../../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider'; -import * as useDynamicIndexPatternHooks from '../../../hooks/useDynamicIndexPattern'; -import * as useFetcherHooks from '../../../hooks/useFetcher'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; -import * as useAnnotationsHooks from '../../../hooks/use_annotations'; -import * as useTransactionBreakdownHooks from '../../../hooks/use_transaction_breakdown'; +} from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; +import * as useDynamicIndexPatternHooks from '../../../hooks/use_dynamic_index_pattern'; +import * as useFetcherHooks from '../../../hooks/use_fetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import * as useAnnotationsHooks from '../../../context/annotations/use_annotations_context'; +import * as useTransactionBreakdownHooks from '../../shared/charts/transaction_breakdown_chart/use_transaction_breakdown'; import { renderWithTheme } from '../../../utils/testHelpers'; import { ServiceOverview } from './'; @@ -56,10 +56,10 @@ function Wrapper({ children }: { children?: ReactNode }) { describe('ServiceOverview', () => { it('renders', () => { jest - .spyOn(useAnnotationsHooks, 'useAnnotations') + .spyOn(useAnnotationsHooks, 'useAnnotationsContext') .mockReturnValue({ annotations: [] }); jest - .spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPattern') + .spyOn(useDynamicIndexPatternHooks, 'useDynamicIndexPatternFetcher') .mockReturnValue({ indexPattern: undefined, status: FETCH_STATUS.SUCCESS, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index b4228878dd9f5b..6e183924a80a73 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -14,8 +14,8 @@ import { i18n } from '@kbn/i18n'; import React, { useState } from 'react'; import styled from 'styled-components'; import { asInteger } from '../../../../../common/utils/formatters'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { px, truncate, unit } from '../../../../style/variables'; import { SparkPlotWithValueLabel } from '../../../shared/charts/spark_plot/spark_plot_with_value_label'; diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx index 94d92bfbe89ddc..1662f44d1e4216 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx @@ -9,10 +9,10 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useParams } from 'react-router-dom'; import { asTransactionRate } from '../../../../common/utils/formatters'; -import { useFetcher } from '../../../hooks/useFetcher'; -import { useTheme } from '../../../hooks/useTheme'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useTheme } from '../../../hooks/use_theme'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { callApmApi } from '../../../services/rest/createCallApmApi'; import { TimeseriesChart } from '../../shared/charts/timeseries_chart'; @@ -24,7 +24,7 @@ export function ServiceOverviewThroughputChart({ const theme = useTheme(); const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); - const { transactionType } = useApmService(); + const { transactionType } = useApmServiceContext(); const { start, end } = urlParams; const { data, status } = useFetcher(() => { diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index e241bc2fed05a4..6b02a44dcc2f46 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -21,8 +21,8 @@ import { asTransactionRate, } from '../../../../../common/utils/formatters'; import { px, truncate, unit } from '../../../../style/variables'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/useFetcher'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APIReturnType, callApmApi, diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx index 953397b9f3d5fb..c14c31afe0445e 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/TransactionList/TransactionList.stories.tsx @@ -7,7 +7,7 @@ import React, { ComponentType } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { APIReturnType } from '../../../../services/rest/createCallApmApi'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { TransactionList } from './'; type TransactionGroup = APIReturnType<'GET /api/apm/services/{serviceName}/transaction_groups'>['items'][0]; diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx index 28a27c034265a9..9ff4ad916b174c 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/index.tsx @@ -23,10 +23,10 @@ import { useLocation } from 'react-router-dom'; import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; import { TRANSACTION_PAGE_LOAD } from '../../../../common/transaction_types'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; -import { useTransactionCharts } from '../../../hooks/useTransactionCharts'; -import { useTransactionList } from '../../../hooks/useTransactionList'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { IUrlParams } from '../../../context/url_params_context/types'; +import { useTransactionChartsFetcher } from '../../../hooks/use_transaction_charts_fetcher'; +import { useTransactionListFetcher } from './use_transaction_list'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { TransactionCharts } from '../../shared/charts/transaction_charts'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { fromQuery, toQuery } from '../../shared/Links/url_helpers'; @@ -37,7 +37,7 @@ import { Correlations } from '../Correlations'; import { TransactionList } from './TransactionList'; import { useRedirect } from './useRedirect'; import { UserExperienceCallout } from './user_experience_callout'; -import { useApmService } from '../../../hooks/use_apm_service'; +import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; function getRedirectLocation({ location, @@ -68,22 +68,22 @@ interface TransactionOverviewProps { export function TransactionOverview({ serviceName }: TransactionOverviewProps) { const location = useLocation(); const { urlParams } = useUrlParams(); - const { transactionType, transactionTypes } = useApmService(); + const { transactionType, transactionTypes } = useApmServiceContext(); // redirect to first transaction type useRedirect(getRedirectLocation({ location, transactionType, urlParams })); const { - data: transactionCharts, - status: transactionChartsStatus, - } = useTransactionCharts(); + transactionChartsData, + transactionChartsStatus, + } = useTransactionChartsFetcher(); useTrackPageview({ app: 'apm', path: 'transaction_overview' }); useTrackPageview({ app: 'apm', path: 'transaction_overview', delay: 15000 }); const { - data: transactionListData, - status: transactionListStatus, - } = useTransactionList(urlParams); + transactionListData, + transactionListStatus, + } = useTransactionListFetcher(); const localFiltersConfig: React.ComponentProps< typeof LocalUIFilters @@ -134,7 +134,7 @@ export function TransactionOverview({ serviceName }: TransactionOverviewProps) { )} diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx index d4a8b3a46991c0..93d56ea19024e0 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/transaction_overview.test.tsx @@ -10,13 +10,13 @@ import { CoreStart } from 'kibana/public'; import React from 'react'; import { Router } from 'react-router-dom'; import { createKibanaReactContext } from 'src/plugins/kibana_react/public'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { ApmServiceContextProvider } from '../../../context/apm_service_context'; -import { UrlParamsProvider } from '../../../context/UrlParamsContext'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; -import * as useFetcherHook from '../../../hooks/useFetcher'; -import * as useServiceTransactionTypesHook from '../../../hooks/use_service_transaction_types'; -import * as useServiceAgentNameHook from '../../../hooks/use_service_agent_name'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { ApmServiceContextProvider } from '../../../context/apm_service/apm_service_context'; +import { UrlParamsProvider } from '../../../context/url_params_context/url_params_context'; +import { IUrlParams } from '../../../context/url_params_context/types'; +import * as useFetcherHook from '../../../hooks/use_fetcher'; +import * as useServiceTransactionTypesHook from '../../../context/apm_service/use_service_transaction_types_fetcher'; +import * as useServiceAgentNameHook from '../../../context/apm_service/use_service_agent_name_fetcher'; import { disableConsoleWarning, renderWithTheme, @@ -46,15 +46,17 @@ function setup({ // mock transaction types jest - .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypes') + .spyOn(useServiceTransactionTypesHook, 'useServiceTransactionTypesFetcher') .mockReturnValue(serviceTransactionTypes); // mock agent - jest.spyOn(useServiceAgentNameHook, 'useServiceAgentName').mockReturnValue({ - agentName: 'nodejs', - error: undefined, - status: useFetcherHook.FETCH_STATUS.SUCCESS, - }); + jest + .spyOn(useServiceAgentNameHook, 'useServiceAgentNameFetcher') + .mockReturnValue({ + agentName: 'nodejs', + error: undefined, + status: useFetcherHook.FETCH_STATUS.SUCCESS, + }); jest.spyOn(useFetcherHook, 'useFetcher').mockReturnValue({} as any); diff --git a/x-pack/plugins/apm/public/hooks/useTransactionList.ts b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts similarity index 75% rename from x-pack/plugins/apm/public/hooks/useTransactionList.ts rename to x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts index 92b54beb715db7..78883ec2cf0d39 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionList.ts +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/use_transaction_list.ts @@ -5,10 +5,9 @@ */ import { useParams } from 'react-router-dom'; -import { useUiFilters } from '../context/UrlParamsContext'; -import { IUrlParams } from '../context/UrlParamsContext/types'; -import { APIReturnType } from '../services/rest/createCallApmApi'; -import { useFetcher } from './useFetcher'; +import { APIReturnType } from '../../../services/rest/createCallApmApi'; +import { useFetcher } from '../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; type TransactionsAPIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transaction_groups'>; @@ -18,10 +17,10 @@ const DEFAULT_RESPONSE: Partial = { bucketSize: 0, }; -export function useTransactionList(urlParams: IUrlParams) { +export function useTransactionListFetcher() { + const { urlParams, uiFilters } = useUrlParams(); const { serviceName } = useParams<{ serviceName?: string }>(); const { transactionType, start, end } = urlParams; - const uiFilters = useUiFilters(urlParams); const { data = DEFAULT_RESPONSE, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && transactionType) { @@ -43,8 +42,8 @@ export function useTransactionList(urlParams: IUrlParams) { ); return { - data, - status, - error, + transactionListData: data, + transactionListStatus: status, + transactionListError: error, }; } diff --git a/x-pack/plugins/apm/public/components/app/transaction_overview/user_experience_callout.tsx b/x-pack/plugins/apm/public/components/app/transaction_overview/user_experience_callout.tsx index 41e84d4acfba5b..6e1154a458d6e9 100644 --- a/x-pack/plugins/apm/public/components/app/transaction_overview/user_experience_callout.tsx +++ b/x-pack/plugins/apm/public/components/app/transaction_overview/user_experience_callout.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { EuiButton, EuiCallOut, EuiSpacer, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; export function UserExperienceCallout() { const { core } = useApmPluginContext(); diff --git a/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx b/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx index 56501d8c916f47..dd88b1ea7eb734 100644 --- a/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/ApmHeader/apm_header.stories.tsx @@ -8,8 +8,8 @@ import { EuiTitle } from '@elastic/eui'; import React, { ComponentType } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { HttpSetup } from '../../../../../../../src/core/public'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; -import { MockUrlParamsContextProvider } from '../../../context/UrlParamsContext/MockUrlParamsContextProvider'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; +import { MockUrlParamsContextProvider } from '../../../context/url_params_context/mock_url_params_context_provider'; import { createCallApmApi } from '../../../services/rest/createCallApmApi'; import { ApmHeader } from './'; diff --git a/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx b/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx index a806a3ea601541..04e03cda6a61e8 100644 --- a/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ApmHeader/index.tsx @@ -9,7 +9,7 @@ import React, { ReactNode } from 'react'; import styled from 'styled-components'; import { HeaderMenuPortal } from '../../../../../observability/public'; import { ActionMenu } from '../../../application/action_menu'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { EnvironmentFilter } from '../EnvironmentFilter'; const HeaderFlexGroup = styled(EuiFlexGroup)` diff --git a/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx b/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx index 520cc2f423ddde..222c27cc7ed6d8 100644 --- a/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx @@ -10,12 +10,12 @@ import { mount } from 'enzyme'; import { createMemoryHistory } from 'history'; import React, { ReactNode } from 'react'; import { Router } from 'react-router-dom'; -import { MockApmPluginContextWrapper } from '../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../context/apm_plugin/mock_apm_plugin_context'; import { UrlParamsContext, useUiFilters, -} from '../../../context/UrlParamsContext'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; +} from '../../../context/url_params_context/url_params_context'; +import { IUrlParams } from '../../../context/url_params_context/types'; import { DatePicker } from './'; const history = createMemoryHistory(); diff --git a/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx b/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx index f35cc067489118..f847ce0b6e96fa 100644 --- a/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/DatePicker/index.tsx @@ -8,8 +8,8 @@ import { EuiSuperDatePicker } from '@elastic/eui'; import React, { useEffect } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; import { UI_SETTINGS } from '../../../../../../../src/plugins/data/common'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { clearCache } from '../../../services/rest/callApi'; import { fromQuery, toQuery } from '../Links/url_helpers'; import { TimePickerQuickRange, TimePickerTimeDefaults } from './typings'; diff --git a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx index cace4c2770f370..4522cfa7195fd4 100644 --- a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx @@ -13,8 +13,8 @@ import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, } from '../../../../common/environment_filter_values'; -import { useEnvironments } from '../../../hooks/useEnvironments'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../Links/url_helpers'; function updateEnvironmentUrl( @@ -67,7 +67,7 @@ export function EnvironmentFilter() { const { environment } = uiFilters; const { start, end } = urlParams; - const { environments, status = 'loading' } = useEnvironments({ + const { environments, status = 'loading' } = useEnvironmentsFetcher({ serviceName, start, end, diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts index e7dd03db6b63cf..2276704edc342e 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/get_bool_filter.ts @@ -13,7 +13,7 @@ import { TRANSACTION_TYPE, } from '../../../../common/elasticsearch_fieldnames'; import { UIProcessorEvent } from '../../../../common/processor_event'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; +import { IUrlParams } from '../../../context/url_params_context/types'; export function getBoolFilter({ groupId, diff --git a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx index 2ef93fc32200e7..5284e3f6aa0113 100644 --- a/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/KueryBar/index.tsx @@ -14,9 +14,9 @@ import { IIndexPattern, QuerySuggestion, } from '../../../../../../../src/plugins/data/public'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useDynamicIndexPattern } from '../../../hooks/useDynamicIndexPattern'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useDynamicIndexPatternFetcher } from '../../../hooks/use_dynamic_index_pattern'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../Links/url_helpers'; import { getBoolFilter } from './get_bool_filter'; // @ts-expect-error @@ -65,7 +65,7 @@ export function KueryBar() { const example = examples[processorEvent || 'defaults']; - const { indexPattern } = useDynamicIndexPattern(processorEvent); + const { indexPattern } = useDynamicIndexPatternFetcher(processorEvent); const placeholder = i18n.translate('xpack.apm.kueryBar.placeholder', { defaultMessage: `Search {event, select, diff --git a/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx b/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx index 1819e71a49753a..bd68e7db777145 100644 --- a/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx +++ b/x-pack/plugins/apm/public/components/shared/LicensePrompt/LicensePrompt.stories.tsx @@ -9,7 +9,7 @@ import { LicensePrompt } from '.'; import { ApmPluginContext, ApmPluginContextValue, -} from '../../../context/ApmPluginContext'; +} from '../../../context/apm_plugin/apm_plugin_context'; const contextMock = ({ core: { http: { basePath: { prepend: () => {} } } }, diff --git a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx index 93b5672aa54f98..70286655bba882 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/DiscoverLinks/DiscoverLink.tsx @@ -12,7 +12,7 @@ import { useLocation } from 'react-router-dom'; import rison, { RisonValue } from 'rison-node'; import url from 'url'; import { APM_STATIC_INDEX_PATTERN_ID } from '../../../../../../../../src/plugins/apm_oss/public'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { getTimepickerRisonData } from '../rison_helpers'; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx index 8c2829a515f831..e2447cc7a67a53 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/ElasticDocsLink.tsx @@ -6,7 +6,7 @@ import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; import React from 'react'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; // union type constisting of valid guide sections that we link to type DocsSection = '/apm/get-started' | '/x-pack' | '/apm/server' | '/kibana'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/InfraLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/InfraLink.tsx index 630235e54c9fa5..6d4bbbbfc2f804 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/InfraLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/InfraLink.tsx @@ -9,7 +9,7 @@ import { IBasePath } from 'kibana/public'; import React from 'react'; import url from 'url'; import { InfraAppId } from '../../../../../infra/public'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; import { fromQuery } from './url_helpers'; interface InfraQueryParams { diff --git a/x-pack/plugins/apm/public/components/shared/Links/KibanaLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/KibanaLink.tsx index 8aa0d4f5a33543..ab44374f48167f 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/KibanaLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/KibanaLink.tsx @@ -7,7 +7,7 @@ import { EuiLink, EuiLinkAnchorProps } from '@elastic/eui'; import React from 'react'; import url from 'url'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; interface Props extends EuiLinkAnchorProps { path?: string; diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx index 5fbcd475cb47ba..7bf017fb239e3c 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/MLLink.tsx @@ -6,9 +6,9 @@ import { EuiLink } from '@elastic/eui'; import React from 'react'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useMlHref, ML_PAGES } from '../../../../../../ml/public'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; interface MlRisonData { ml?: { diff --git a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts index 0f671fd363c75a..eabef034bf3d99 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts +++ b/x-pack/plugins/apm/public/components/shared/Links/MachineLearningLinks/useTimeSeriesExplorerHref.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; import { useMlHref } from '../../../../../../ml/public'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; export function useTimeSeriesExplorerHref({ jobId, diff --git a/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx index 0ff73d91d7c5b0..68bee36dbe2832 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/SetupInstructionsLink.tsx @@ -7,7 +7,7 @@ import { EuiButton, EuiButtonEmpty, EuiLink } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; const SETUP_INSTRUCTIONS_LABEL = i18n.translate( 'xpack.apm.setupInstructionsButtonLabel', diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx index 41c932bf9c9f55..98046193e3807e 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/APMLink.tsx @@ -11,8 +11,8 @@ import React from 'react'; import { useLocation } from 'react-router-dom'; import url from 'url'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useApmPluginContext } from '../../../../hooks/useApmPluginContext'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams, fromQuery, toQuery } from '../url_helpers'; interface Props extends EuiLinkAnchorProps { diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx index 30b91fe2564f16..dcf21de7dca8df 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/ErrorOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx index fbae80203f03bb..de7130e8786081 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/MetricOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx index 2553ec4353194d..afdb177e467d82 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeMetricOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { APMLink, APMLinkExtendProps } from './APMLink'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { pickKeys } from '../../../../../common/utils/pick_keys'; interface Props extends APMLinkExtendProps { diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx index 0a9553bcbfe6ca..c107b436717c2d 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/ServiceNodeOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { pickKeys } from '../../../../../common/utils/pick_keys'; import { APMQueryParams } from '../url_helpers'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/TraceOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/TraceOverviewLink.tsx index 6aa362707800fd..caa1498e6df87a 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/TraceOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/TraceOverviewLink.tsx @@ -11,7 +11,7 @@ */ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx index c9b26b557512ca..ee798e0208c2ba 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionDetailLink.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { APMLink, APMLinkExtendProps } from './APMLink'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { pickKeys } from '../../../../../common/utils/pick_keys'; interface Props extends APMLinkExtendProps { diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx index 23e795b026d0cf..92ff1b8a68ac02 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/TransactionOverviewLink.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { pickKeys } from '../../../../../common/utils/pick_keys'; import { APMQueryParams } from '../url_helpers'; diff --git a/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx b/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx index 039d9dcb1c0ed9..318a1590be77c3 100644 --- a/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx +++ b/x-pack/plugins/apm/public/components/shared/Links/apm/service_inventory_link.tsx @@ -11,7 +11,7 @@ */ import React from 'react'; import { pickKeys } from '../../../../../common/utils/pick_keys'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { APMQueryParams } from '../url_helpers'; import { APMLink, APMLinkExtendProps, useAPMHref } from './APMLink'; diff --git a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/TransactionTypeFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/LocalUIFilters/TransactionTypeFilter/index.tsx index e6d266091ae521..43f7b089a2965d 100644 --- a/x-pack/plugins/apm/public/components/shared/LocalUIFilters/TransactionTypeFilter/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/LocalUIFilters/TransactionTypeFilter/index.tsx @@ -13,7 +13,7 @@ import { import { i18n } from '@kbn/i18n'; import React from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../../Links/url_helpers'; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx index 9db563a0f6ba87..6f62fd24e71ea9 100644 --- a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx @@ -8,7 +8,7 @@ import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; import { orderBy } from 'lodash'; import React, { ReactNode, useCallback, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { fromQuery, toQuery } from '../Links/url_helpers'; // TODO: this should really be imported from EUI diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx index e95122f54aff1e..8f44d98cecdf7b 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/ErrorMetadata/__test__/ErrorMetadata.test.tsx @@ -9,7 +9,7 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { ErrorMetadata } from '..'; import { APMError } from '../../../../../../typings/es_schemas/ui/apm_error'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx index 1f10d923e351e3..c97e5061873478 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx @@ -9,7 +9,7 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { SpanMetadata } from '..'; import { Span } from '../../../../../../typings/es_schemas/ui/span'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx index 8359716fc69666..4080a300ba17f0 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx @@ -9,7 +9,7 @@ import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { TransactionMetadata } from '..'; import { Transaction } from '../../../../../../typings/es_schemas/ui/transaction'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx index 8e53aa4aa10896..8a4cd588c82600 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/__test__/MetadataTable.test.tsx @@ -8,7 +8,7 @@ import { render } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { MetadataTable } from '..'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument } from '../../../../utils/testHelpers'; import { SectionsWithRows } from '../helper'; diff --git a/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx b/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx index 1d2ac4d18a2a7b..283433fa37bf91 100644 --- a/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/MetadataTable/index.tsx @@ -17,7 +17,7 @@ import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; import React, { useCallback } from 'react'; import { useHistory, useLocation } from 'react-router-dom'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { ElasticDocsLink } from '../../shared/Links/ElasticDocsLink'; import { HeightRetainer } from '../HeightRetainer'; import { fromQuery, toQuery } from '../Links/url_helpers'; diff --git a/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx b/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx index ed33c59af36f4d..83c2acb57e3c73 100644 --- a/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx +++ b/x-pack/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import styled from 'styled-components'; import { EuiBadge } from '@elastic/eui'; -import { useTheme } from '../../../hooks/useTheme'; +import { useTheme } from '../../../hooks/use_theme'; import { px } from '../../../../public/style/variables'; import { units } from '../../../style/variables'; diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx index 0241167aba1fb0..777200099976ec 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/CustomLinkToolbar.test.tsx @@ -7,7 +7,7 @@ import { act, fireEvent, render } from '@testing-library/react'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx index db7a284f6adff6..c4547595645a26 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.test.tsx @@ -10,8 +10,8 @@ import { MemoryRouter } from 'react-router-dom'; import { CustomLinkMenuSection } from '.'; import { CustomLink as CustomLinkType } from '../../../../../common/custom_link/custom_link_types'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; -import * as useFetcher from '../../../../hooks/useFetcher'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import * as useFetcher from '../../../../hooks/use_fetcher'; import { expectTextsInDocument, expectTextsNotInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx index 2825363b101976..0a67db0f15b329 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/CustomLinkMenuSection/index.tsx @@ -22,7 +22,7 @@ import { import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; import { CustomLinkList } from './CustomLinkList'; import { CustomLinkToolbar } from './CustomLinkToolbar'; -import { FETCH_STATUS, useFetcher } from '../../../../hooks/useFetcher'; +import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; import { LoadingStatePrompt } from '../../LoadingStatePrompt'; import { px } from '../../../../style/variables'; import { CreateEditCustomLinkFlyout } from '../../../app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout'; diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx index 15a85113406e15..3f74b80bab064b 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/TransactionActionMenu.tsx @@ -18,9 +18,9 @@ import { SectionTitle, } from '../../../../../observability/public'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; -import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; -import { useLicense } from '../../../hooks/useLicense'; -import { useUrlParams } from '../../../hooks/useUrlParams'; +import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context'; +import { useLicenseContext } from '../../../context/license/use_license_context'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { CustomLinkMenuSection } from './CustomLinkMenuSection'; import { getSections } from './sections'; @@ -39,7 +39,7 @@ function ActionMenuButton({ onClick }: { onClick: () => void }) { } export function TransactionActionMenu({ transaction }: Props) { - const license = useLicense(); + const license = useLicenseContext(); const hasGoldLicense = license?.isActive && license?.hasAtLeast('gold'); const { core } = useApmPluginContext(); diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx index 9b5f00f76eeb21..8cb863c8fc3852 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/__test__/TransactionActionMenu.test.tsx @@ -9,9 +9,9 @@ import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { License } from '../../../../../../licensing/common/license'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; -import { MockApmPluginContextWrapper } from '../../../../context/ApmPluginContext/MockApmPluginContext'; -import { LicenseContext } from '../../../../context/LicenseContext'; -import * as hooks from '../../../../hooks/useFetcher'; +import { MockApmPluginContextWrapper } from '../../../../context/apm_plugin/mock_apm_plugin_context'; +import { LicenseContext } from '../../../../context/license/license_context'; +import * as hooks from '../../../../hooks/use_fetcher'; import * as apmApi from '../../../../services/rest/createCallApmApi'; import { expectTextsInDocument, diff --git a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts index 4433865b44991e..c77de875dc84f7 100644 --- a/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts +++ b/x-pack/plugins/apm/public/components/shared/TransactionActionMenu/sections.ts @@ -10,7 +10,7 @@ import { isEmpty, pickBy } from 'lodash'; import moment from 'moment'; import url from 'url'; import { Transaction } from '../../../../typings/es_schemas/ui/transaction'; -import { IUrlParams } from '../../../context/UrlParamsContext/types'; +import { IUrlParams } from '../../../context/url_params_context/types'; import { getDiscoverHref } from '../Links/DiscoverLinks/DiscoverLink'; import { getDiscoverQuery } from '../Links/DiscoverLinks/DiscoverTransactionLink'; import { getInfraHref } from '../Links/InfraLink'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx index 1a2a90c9fb3c30..eebb9e8d23d98e 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Legend/index.tsx @@ -6,7 +6,7 @@ import React from 'react'; import styled from 'styled-components'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { fontSizes, px, units } from '../../../../style/variables'; export enum Shape { diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx index 37d3664e98acda..a6b46f4a64691d 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { EuiToolTip } from '@elastic/eui'; import styled from 'styled-components'; import { asDuration } from '../../../../../../common/utils/formatters'; -import { useTheme } from '../../../../../hooks/useTheme'; +import { useTheme } from '../../../../../hooks/use_theme'; import { px, units } from '../../../../../style/variables'; import { Legend } from '../../Legend'; import { AgentMark } from '../../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx index abe81185635b50..e69b23cf5f0086 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.test.tsx @@ -8,7 +8,7 @@ import { fireEvent } from '@testing-library/react'; import { act } from '@testing-library/react-hooks'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { MockApmPluginContextWrapper } from '../../../../../context/ApmPluginContext/MockApmPluginContext'; +import { MockApmPluginContextWrapper } from '../../../../../context/apm_plugin/mock_apm_plugin_context'; import { expectTextsInDocument, renderWithTheme, diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx index de63e2323ddac6..c6847bd5e674dc 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx @@ -8,12 +8,12 @@ import React, { useState } from 'react'; import { EuiPopover, EuiText } from '@elastic/eui'; import styled from 'styled-components'; import { asDuration } from '../../../../../../common/utils/formatters'; -import { useTheme } from '../../../../../hooks/useTheme'; +import { useTheme } from '../../../../../hooks/use_theme'; import { TRACE_ID, TRANSACTION_ID, } from '../../../../../../common/elasticsearch_fieldnames'; -import { useUrlParams } from '../../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../../context/url_params_context/use_url_params'; import { px, unit, units } from '../../../../../style/variables'; import { ErrorMark } from '../../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks'; import { ErrorDetailLink } from '../../../Links/apm/ErrorDetailLink'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx index cb5a44432dcbc4..dcdfee22e3cfce 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/TimelineAxis.tsx @@ -9,7 +9,7 @@ import { inRange } from 'lodash'; import { Sticky } from 'react-sticky'; import { XAxis, XYPlot } from 'react-vis'; import { getDurationFormatter } from '../../../../../common/utils/formatters'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { px } from '../../../../style/variables'; import { Mark } from './'; import { LastTickValue } from './LastTickValue'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx b/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx index 5ea2e4cfedf18c..ee1c899123994c 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/Timeline/VerticalLines.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { VerticalGridLines, XYPlot } from 'react-vis'; -import { useTheme } from '../../../../hooks/useTheme'; +import { useTheme } from '../../../../hooks/use_theme'; import { Mark } from '../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks'; import { PlotValues } from './plotUtils'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx index c0e8f869ce647e..359eadfc55cff5 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.test.tsx @@ -5,7 +5,7 @@ */ import { render } from '@testing-library/react'; import React from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { ChartContainer } from './chart_container'; describe('ChartContainer', () => { diff --git a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx index b4486f1e9b94ad..ef58430e1e31e4 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/chart_container.tsx @@ -7,7 +7,7 @@ import { EuiLoadingChart, EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; interface Props { hasData: boolean; diff --git a/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx index 9a561571df5a7c..506c27051b5116 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/metrics_chart/index.tsx @@ -15,7 +15,7 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { GenericMetricsChart } from '../../../../../server/lib/metrics/transform_metrics_chart'; import { Maybe } from '../../../../../typings/common'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { isValidCoordinateValue } from '../../../../utils/isValidCoordinateValue'; import { TimeseriesChart } from '../timeseries_chart'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/spark_plot_with_value_label/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/spark_plot_with_value_label/index.tsx index 3819ed30d104a0..3bfcba63685b65 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/spark_plot/spark_plot_with_value_label/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/spark_plot/spark_plot_with_value_label/index.tsx @@ -7,7 +7,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import React from 'react'; import { px, unit } from '../../../../../style/variables'; -import { useTheme } from '../../../../../hooks/useTheme'; +import { useTheme } from '../../../../../hooks/use_theme'; import { SparkPlot } from '../'; type Color = diff --git a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx index a857707ca0c750..689f80e01247ed 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/timeseries_chart.tsx @@ -29,11 +29,11 @@ import { useHistory } from 'react-router-dom'; import { useChartTheme } from '../../../../../observability/public'; import { asAbsoluteDateTime } from '../../../../common/utils/formatters'; import { RectCoordinate, TimeSeries } from '../../../../typings/timeseries'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; -import { useTheme } from '../../../hooks/useTheme'; -import { useUrlParams } from '../../../hooks/useUrlParams'; -import { useAnnotations } from '../../../hooks/use_annotations'; -import { useChartPointerEvent } from '../../../hooks/use_chart_pointer_event'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; +import { useTheme } from '../../../hooks/use_theme'; +import { useUrlParams } from '../../../context/url_params_context/use_url_params'; +import { useAnnotationsContext } from '../../../context/annotations/use_annotations_context'; +import { useChartPointerEventContext } from '../../../context/chart_pointer_event/use_chart_pointer_event_context'; import { AnomalySeries } from '../../../selectors/chart_selectors'; import { unit } from '../../../style/variables'; import { ChartContainer } from './chart_container'; @@ -72,9 +72,9 @@ export function TimeseriesChart({ }: Props) { const history = useHistory(); const chartRef = React.createRef(); - const { annotations } = useAnnotations(); + const { annotations } = useAnnotationsContext(); const chartTheme = useChartTheme(); - const { pointerEvent, setPointerEvent } = useChartPointerEvent(); + const { pointerEvent, setPointerEvent } = useChartPointerEventContext(); const { urlParams } = useUrlParams(); const theme = useTheme(); diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx index 4d9a1637bea760..38a980fbcd90a8 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/index.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { useTransactionBreakdown } from '../../../../hooks/use_transaction_breakdown'; +import { useTransactionBreakdown } from './use_transaction_breakdown'; import { TransactionBreakdownChartContents } from './transaction_breakdown_chart_contents'; export function TransactionBreakdownChart({ diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx index 20056a6831adf4..0eda922519f852 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/transaction_breakdown_chart_contents.tsx @@ -28,11 +28,11 @@ import { asPercent, } from '../../../../../common/utils/formatters'; import { TimeSeries } from '../../../../../typings/timeseries'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; -import { useTheme } from '../../../../hooks/useTheme'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; -import { useAnnotations } from '../../../../hooks/use_annotations'; -import { useChartPointerEvent } from '../../../../hooks/use_chart_pointer_event'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; +import { useTheme } from '../../../../hooks/use_theme'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useAnnotationsContext } from '../../../../context/annotations/use_annotations_context'; +import { useChartPointerEventContext } from '../../../../context/chart_pointer_event/use_chart_pointer_event_context'; import { unit } from '../../../../style/variables'; import { ChartContainer } from '../../charts/chart_container'; import { onBrushEnd } from '../../charts/helper/helper'; @@ -52,9 +52,9 @@ export function TransactionBreakdownChartContents({ }: Props) { const history = useHistory(); const chartRef = React.createRef(); - const { annotations } = useAnnotations(); + const { annotations } = useAnnotationsContext(); const chartTheme = useChartTheme(); - const { pointerEvent, setPointerEvent } = useChartPointerEvent(); + const { pointerEvent, setPointerEvent } = useChartPointerEventContext(); const { urlParams } = useUrlParams(); const theme = useTheme(); const { start, end } = urlParams; diff --git a/x-pack/plugins/apm/public/hooks/use_transaction_breakdown.ts b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts similarity index 80% rename from x-pack/plugins/apm/public/hooks/use_transaction_breakdown.ts rename to x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts index f1671ed7aa6d90..ff744d763ecaed 100644 --- a/x-pack/plugins/apm/public/hooks/use_transaction_breakdown.ts +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_breakdown_chart/use_transaction_breakdown.ts @@ -5,15 +5,15 @@ */ import { useParams } from 'react-router-dom'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; -import { useApmService } from './use_apm_service'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; export function useTransactionBreakdown() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams, uiFilters } = useUrlParams(); const { start, end, transactionName } = urlParams; - const { transactionType } = useApmService(); + const { transactionType } = useApmServiceContext(); const { data = { timeseries: undefined }, error, status } = useFetcher( (callApmApi) => { diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx index ac117bbbd922a4..bb7c0a9104fc7b 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/index.tsx @@ -20,11 +20,11 @@ import { TRANSACTION_ROUTE_CHANGE, } from '../../../../../common/transaction_types'; import { asTransactionRate } from '../../../../../common/utils/formatters'; -import { AnnotationsContextProvider } from '../../../../context/annotations_context'; -import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event_context'; -import { LicenseContext } from '../../../../context/LicenseContext'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; -import { FETCH_STATUS } from '../../../../hooks/useFetcher'; +import { AnnotationsContextProvider } from '../../../../context/annotations/annotations_context'; +import { ChartPointerEventContextProvider } from '../../../../context/chart_pointer_event/chart_pointer_event_context'; +import { LicenseContext } from '../../../../context/license/license_context'; +import type { IUrlParams } from '../../../../context/url_params_context/types'; +import { FETCH_STATUS } from '../../../../hooks/use_fetcher'; import { ITransactionChartData } from '../../../../selectors/chart_selectors'; import { TimeseriesChart } from '../timeseries_chart'; import { TransactionBreakdownChart } from '../transaction_breakdown_chart'; diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx index ee5cd8960d3357..f0569ea1a07521 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_charts/ml_header.tsx @@ -10,7 +10,7 @@ import { isEmpty } from 'lodash'; import React from 'react'; import { useParams } from 'react-router-dom'; import styled from 'styled-components'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { MLJobLink } from '../../Links/MachineLearningLinks/MLJobLink'; interface Props { diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx index 00472df95c4b1c..06a5e7baef79b9 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx @@ -9,9 +9,9 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import { useParams } from 'react-router-dom'; import { asPercent } from '../../../../../common/utils/formatters'; -import { useFetcher } from '../../../../hooks/useFetcher'; -import { useTheme } from '../../../../hooks/useTheme'; -import { useUrlParams } from '../../../../hooks/useUrlParams'; +import { useFetcher } from '../../../../hooks/use_fetcher'; +import { useTheme } from '../../../../hooks/use_theme'; +import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { TimeseriesChart } from '../timeseries_chart'; diff --git a/x-pack/plugins/apm/public/components/shared/table_fetch_wrapper/index.tsx b/x-pack/plugins/apm/public/components/shared/table_fetch_wrapper/index.tsx index e8d62cd8bd85b1..9538d46960fc94 100644 --- a/x-pack/plugins/apm/public/components/shared/table_fetch_wrapper/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/table_fetch_wrapper/index.tsx @@ -5,7 +5,7 @@ */ import React, { ReactNode } from 'react'; -import { FETCH_STATUS } from '../../../hooks/useFetcher'; +import { FETCH_STATUS } from '../../../hooks/use_fetcher'; import { ErrorStatePrompt } from '../ErrorStatePrompt'; export function TableFetchWrapper({ diff --git a/x-pack/plugins/apm/public/context/annotations_context.tsx b/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx similarity index 83% rename from x-pack/plugins/apm/public/context/annotations_context.tsx rename to x-pack/plugins/apm/public/context/annotations/annotations_context.tsx index 4e09a3d227b113..77285f976d8508 100644 --- a/x-pack/plugins/apm/public/context/annotations_context.tsx +++ b/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx @@ -6,10 +6,10 @@ import React, { createContext } from 'react'; import { useParams } from 'react-router-dom'; -import { Annotation } from '../../common/annotations'; -import { useFetcher } from '../hooks/useFetcher'; -import { useUrlParams } from '../hooks/useUrlParams'; -import { callApmApi } from '../services/rest/createCallApmApi'; +import { Annotation } from '../../../common/annotations'; +import { useFetcher } from '../../hooks/use_fetcher'; +import { useUrlParams } from '../url_params_context/use_url_params'; +import { callApmApi } from '../../services/rest/createCallApmApi'; export const AnnotationsContext = createContext({ annotations: [] } as { annotations: Annotation[]; diff --git a/x-pack/plugins/apm/public/hooks/use_annotations.ts b/x-pack/plugins/apm/public/context/annotations/use_annotations_context.ts similarity index 80% rename from x-pack/plugins/apm/public/hooks/use_annotations.ts rename to x-pack/plugins/apm/public/context/annotations/use_annotations_context.ts index 1cd9a7e65dda27..7fdc602b1916e4 100644 --- a/x-pack/plugins/apm/public/hooks/use_annotations.ts +++ b/x-pack/plugins/apm/public/context/annotations/use_annotations_context.ts @@ -5,9 +5,9 @@ */ import { useContext } from 'react'; -import { AnnotationsContext } from '../context/annotations_context'; +import { AnnotationsContext } from './annotations_context'; -export function useAnnotations() { +export function useAnnotationsContext() { const context = useContext(AnnotationsContext); if (!context) { diff --git a/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx b/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx similarity index 94% rename from x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx rename to x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx index 44952e64db59c6..0e26db4820ea1a 100644 --- a/x-pack/plugins/apm/public/context/ApmPluginContext/index.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/apm_plugin_context.tsx @@ -6,7 +6,7 @@ import { AppMountParameters, CoreStart } from 'kibana/public'; import { createContext } from 'react'; -import { ConfigSchema } from '../../'; +import { ConfigSchema } from '../..'; import { ApmPluginSetupDeps } from '../../plugin'; export interface ApmPluginContextValue { diff --git a/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx similarity index 97% rename from x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx rename to x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx index 25e7f23a00125a..7ab46c65c90d9c 100644 --- a/x-pack/plugins/apm/public/context/ApmPluginContext/MockApmPluginContext.tsx +++ b/x-pack/plugins/apm/public/context/apm_plugin/mock_apm_plugin_context.tsx @@ -5,7 +5,7 @@ */ import React, { ReactNode } from 'react'; import { Observable, of } from 'rxjs'; -import { ApmPluginContext, ApmPluginContextValue } from '.'; +import { ApmPluginContext, ApmPluginContextValue } from './apm_plugin_context'; import { ConfigSchema } from '../..'; import { UI_SETTINGS } from '../../../../../../src/plugins/data/common'; import { createCallApmApi } from '../../services/rest/createCallApmApi'; diff --git a/x-pack/plugins/apm/public/hooks/useApmPluginContext.ts b/x-pack/plugins/apm/public/context/apm_plugin/use_apm_plugin_context.ts similarity index 84% rename from x-pack/plugins/apm/public/hooks/useApmPluginContext.ts rename to x-pack/plugins/apm/public/context/apm_plugin/use_apm_plugin_context.ts index 80a04edbca8584..7c480ea3da2755 100644 --- a/x-pack/plugins/apm/public/hooks/useApmPluginContext.ts +++ b/x-pack/plugins/apm/public/context/apm_plugin/use_apm_plugin_context.ts @@ -5,7 +5,7 @@ */ import { useContext } from 'react'; -import { ApmPluginContext } from '../context/ApmPluginContext'; +import { ApmPluginContext } from './apm_plugin_context'; export function useApmPluginContext() { return useContext(ApmPluginContext); diff --git a/x-pack/plugins/apm/public/context/apm_service_context.test.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.test.tsx similarity index 100% rename from x-pack/plugins/apm/public/context/apm_service_context.test.tsx rename to x-pack/plugins/apm/public/context/apm_service/apm_service_context.test.tsx diff --git a/x-pack/plugins/apm/public/context/apm_service_context.tsx b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx similarity index 75% rename from x-pack/plugins/apm/public/context/apm_service_context.tsx rename to x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx index 2f1b33dea5aa67..b07763aed7b001 100644 --- a/x-pack/plugins/apm/public/context/apm_service_context.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/apm_service_context.tsx @@ -5,15 +5,15 @@ */ import React, { createContext, ReactNode } from 'react'; -import { isRumAgentName } from '../../common/agent_name'; +import { isRumAgentName } from '../../../common/agent_name'; import { TRANSACTION_PAGE_LOAD, TRANSACTION_REQUEST, -} from '../../common/transaction_types'; -import { useServiceTransactionTypes } from '../hooks/use_service_transaction_types'; -import { useUrlParams } from '../hooks/useUrlParams'; -import { useServiceAgentName } from '../hooks/use_service_agent_name'; -import { IUrlParams } from './UrlParamsContext/types'; +} from '../../../common/transaction_types'; +import { useServiceTransactionTypesFetcher } from './use_service_transaction_types_fetcher'; +import { useUrlParams } from '../url_params_context/use_url_params'; +import { useServiceAgentNameFetcher } from './use_service_agent_name_fetcher'; +import { IUrlParams } from '../url_params_context/types'; export const APMServiceContext = createContext<{ agentName?: string; @@ -27,8 +27,8 @@ export function ApmServiceContextProvider({ children: ReactNode; }) { const { urlParams } = useUrlParams(); - const { agentName } = useServiceAgentName(); - const transactionTypes = useServiceTransactionTypes(); + const { agentName } = useServiceAgentNameFetcher(); + const transactionTypes = useServiceTransactionTypesFetcher(); const transactionType = getTransactionType({ urlParams, transactionTypes, diff --git a/x-pack/plugins/apm/public/hooks/use_apm_service.ts b/x-pack/plugins/apm/public/context/apm_service/use_apm_service_context.ts similarity index 75% rename from x-pack/plugins/apm/public/hooks/use_apm_service.ts rename to x-pack/plugins/apm/public/context/apm_service/use_apm_service_context.ts index bc80c3771c39d8..85c135f36719fa 100644 --- a/x-pack/plugins/apm/public/hooks/use_apm_service.ts +++ b/x-pack/plugins/apm/public/context/apm_service/use_apm_service_context.ts @@ -5,8 +5,8 @@ */ import { useContext } from 'react'; -import { APMServiceContext } from '../context/apm_service_context'; +import { APMServiceContext } from './apm_service_context'; -export function useApmService() { +export function useApmServiceContext() { return useContext(APMServiceContext); } diff --git a/x-pack/plugins/apm/public/hooks/use_service_agent_name.ts b/x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts similarity index 83% rename from x-pack/plugins/apm/public/hooks/use_service_agent_name.ts rename to x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts index 199f14532f7b45..9a1d969ec2c33a 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_agent_name.ts +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_agent_name_fetcher.ts @@ -5,10 +5,10 @@ */ import { useParams } from 'react-router-dom'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; +import { useFetcher } from '../../hooks/use_fetcher'; +import { useUrlParams } from '../url_params_context/use_url_params'; -export function useServiceAgentName() { +export function useServiceAgentNameFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); const { start, end } = urlParams; diff --git a/x-pack/plugins/apm/public/hooks/use_service_transaction_types.tsx b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx similarity index 83% rename from x-pack/plugins/apm/public/hooks/use_service_transaction_types.tsx rename to x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx index 9d8892ac79b7d7..85a10cc273bac8 100644 --- a/x-pack/plugins/apm/public/hooks/use_service_transaction_types.tsx +++ b/x-pack/plugins/apm/public/context/apm_service/use_service_transaction_types_fetcher.tsx @@ -5,12 +5,12 @@ */ import { useParams } from 'react-router-dom'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; +import { useFetcher } from '../../hooks/use_fetcher'; +import { useUrlParams } from '../url_params_context/use_url_params'; const INITIAL_DATA = { transactionTypes: [] }; -export function useServiceTransactionTypes() { +export function useServiceTransactionTypesFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams } = useUrlParams(); const { start, end } = urlParams; diff --git a/x-pack/plugins/apm/public/context/chart_pointer_event_context.tsx b/x-pack/plugins/apm/public/context/chart_pointer_event/chart_pointer_event_context.tsx similarity index 100% rename from x-pack/plugins/apm/public/context/chart_pointer_event_context.tsx rename to x-pack/plugins/apm/public/context/chart_pointer_event/chart_pointer_event_context.tsx diff --git a/x-pack/plugins/apm/public/hooks/use_chart_pointer_event.tsx b/x-pack/plugins/apm/public/context/chart_pointer_event/use_chart_pointer_event_context.tsx similarity index 78% rename from x-pack/plugins/apm/public/hooks/use_chart_pointer_event.tsx rename to x-pack/plugins/apm/public/context/chart_pointer_event/use_chart_pointer_event_context.tsx index 058ec594e2d228..bf53273104d600 100644 --- a/x-pack/plugins/apm/public/hooks/use_chart_pointer_event.tsx +++ b/x-pack/plugins/apm/public/context/chart_pointer_event/use_chart_pointer_event_context.tsx @@ -5,9 +5,9 @@ */ import { useContext } from 'react'; -import { ChartPointerEventContext } from '../context/chart_pointer_event_context'; +import { ChartPointerEventContext } from './chart_pointer_event_context'; -export function useChartPointerEvent() { +export function useChartPointerEventContext() { const context = useContext(ChartPointerEventContext); if (!context) { diff --git a/x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx b/x-pack/plugins/apm/public/context/license/Invalid_license_notification.tsx similarity index 100% rename from x-pack/plugins/apm/public/context/LicenseContext/InvalidLicenseNotification.tsx rename to x-pack/plugins/apm/public/context/license/Invalid_license_notification.tsx diff --git a/x-pack/plugins/apm/public/context/LicenseContext/index.tsx b/x-pack/plugins/apm/public/context/license/license_context.tsx similarity index 87% rename from x-pack/plugins/apm/public/context/LicenseContext/index.tsx rename to x-pack/plugins/apm/public/context/license/license_context.tsx index e6615a2fc98bf0..557f135fa4c0e9 100644 --- a/x-pack/plugins/apm/public/context/LicenseContext/index.tsx +++ b/x-pack/plugins/apm/public/context/license/license_context.tsx @@ -7,8 +7,8 @@ import React from 'react'; import useObservable from 'react-use/lib/useObservable'; import { ILicense } from '../../../../licensing/public'; -import { useApmPluginContext } from '../../hooks/useApmPluginContext'; -import { InvalidLicenseNotification } from './InvalidLicenseNotification'; +import { useApmPluginContext } from '../apm_plugin/use_apm_plugin_context'; +import { InvalidLicenseNotification } from './Invalid_license_notification'; export const LicenseContext = React.createContext( undefined diff --git a/x-pack/plugins/apm/public/hooks/useLicense.ts b/x-pack/plugins/apm/public/context/license/use_license_context.ts similarity index 77% rename from x-pack/plugins/apm/public/hooks/useLicense.ts rename to x-pack/plugins/apm/public/context/license/use_license_context.ts index ca828e49706a82..e86bb78d127abc 100644 --- a/x-pack/plugins/apm/public/hooks/useLicense.ts +++ b/x-pack/plugins/apm/public/context/license/use_license_context.ts @@ -5,8 +5,8 @@ */ import { useContext } from 'react'; -import { LicenseContext } from '../context/LicenseContext'; +import { LicenseContext } from './license_context'; -export function useLicense() { +export function useLicenseContext() { return useContext(LicenseContext); } diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/constants.ts b/x-pack/plugins/apm/public/context/url_params_context/constants.ts similarity index 100% rename from x-pack/plugins/apm/public/context/UrlParamsContext/constants.ts rename to x-pack/plugins/apm/public/context/url_params_context/constants.ts diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts b/x-pack/plugins/apm/public/context/url_params_context/helpers.ts similarity index 100% rename from x-pack/plugins/apm/public/context/UrlParamsContext/helpers.ts rename to x-pack/plugins/apm/public/context/url_params_context/helpers.ts diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/MockUrlParamsContextProvider.tsx b/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx similarity index 93% rename from x-pack/plugins/apm/public/context/UrlParamsContext/MockUrlParamsContextProvider.tsx rename to x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx index fd01e057ac3ded..b593cbd57a9a9a 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/MockUrlParamsContextProvider.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx @@ -5,7 +5,7 @@ */ import React from 'react'; import { IUrlParams } from './types'; -import { UrlParamsContext, useUiFilters } from '.'; +import { UrlParamsContext, useUiFilters } from './url_params_context'; const defaultUrlParams = { page: 0, diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts b/x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts similarity index 100% rename from x-pack/plugins/apm/public/context/UrlParamsContext/resolveUrlParams.ts rename to x-pack/plugins/apm/public/context/url_params_context/resolve_url_params.ts diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/types.ts b/x-pack/plugins/apm/public/context/url_params_context/types.ts similarity index 100% rename from x-pack/plugins/apm/public/context/UrlParamsContext/types.ts rename to x-pack/plugins/apm/public/context/url_params_context/types.ts diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/url_params_context.test.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx similarity index 98% rename from x-pack/plugins/apm/public/context/UrlParamsContext/url_params_context.test.tsx rename to x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx index 3a6ccce178cd4c..6b57039678e0ae 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/url_params_context.test.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.test.tsx @@ -5,7 +5,7 @@ */ import * as React from 'react'; -import { UrlParamsContext, UrlParamsProvider } from './'; +import { UrlParamsContext, UrlParamsProvider } from './url_params_context'; import { mount } from 'enzyme'; import { Location, History } from 'history'; import { MemoryRouter, Router } from 'react-router-dom'; diff --git a/x-pack/plugins/apm/public/context/UrlParamsContext/index.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx similarity index 98% rename from x-pack/plugins/apm/public/context/UrlParamsContext/index.tsx rename to x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx index 5682009019d7f8..0a3f8459ff0023 100644 --- a/x-pack/plugins/apm/public/context/UrlParamsContext/index.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx @@ -15,7 +15,7 @@ import { withRouter } from 'react-router-dom'; import { uniqueId, mapValues } from 'lodash'; import { IUrlParams } from './types'; import { getParsedDate } from './helpers'; -import { resolveUrlParams } from './resolveUrlParams'; +import { resolveUrlParams } from './resolve_url_params'; import { UIFilters } from '../../../typings/ui_filters'; import { localUIFilterNames, diff --git a/x-pack/plugins/apm/public/hooks/useUrlParams.tsx b/x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx similarity index 84% rename from x-pack/plugins/apm/public/hooks/useUrlParams.tsx rename to x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx index b9f47046812be4..1bf071d9db35e1 100644 --- a/x-pack/plugins/apm/public/hooks/useUrlParams.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/use_url_params.tsx @@ -5,7 +5,7 @@ */ import { useContext } from 'react'; -import { UrlParamsContext } from '../context/UrlParamsContext'; +import { UrlParamsContext } from './url_params_context'; export function useUrlParams() { return useContext(UrlParamsContext); diff --git a/x-pack/plugins/apm/public/hooks/useCallApi.ts b/x-pack/plugins/apm/public/hooks/useCallApi.ts index 3fec36e7fb24ba..79e439c3f7e7a5 100644 --- a/x-pack/plugins/apm/public/hooks/useCallApi.ts +++ b/x-pack/plugins/apm/public/hooks/useCallApi.ts @@ -6,7 +6,7 @@ import { useMemo } from 'react'; import { callApi } from '../services/rest/callApi'; -import { useApmPluginContext } from './useApmPluginContext'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; import { FetchOptions } from '../../common/fetch_options'; export function useCallApi() { diff --git a/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts b/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts index b4a354c2316339..66edb84378a457 100644 --- a/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts +++ b/x-pack/plugins/apm/public/hooks/useKibanaUrl.ts @@ -5,7 +5,7 @@ */ import url from 'url'; -import { useApmPluginContext } from './useApmPluginContext'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; export function useKibanaUrl( /** The path to the plugin */ path: string, diff --git a/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts b/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts index da1797fd712b15..551e92f8ba0340 100644 --- a/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts +++ b/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts @@ -15,10 +15,10 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths } from '../../server/lib/ui_filters/local_ui_filters/config'; import { fromQuery, toQuery } from '../components/shared/Links/url_helpers'; -import { removeUndefinedProps } from '../context/UrlParamsContext/helpers'; +import { removeUndefinedProps } from '../context/url_params_context/helpers'; import { useCallApi } from './useCallApi'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; +import { useFetcher } from './use_fetcher'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; import { LocalUIFilterName } from '../../common/ui_filter'; const getInitialData = ( diff --git a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx index dcd6ed0ba49347..9127bd3adc69ec 100644 --- a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.test.tsx @@ -9,11 +9,11 @@ import produce from 'immer'; import React, { ReactNode } from 'react'; import { MemoryRouter } from 'react-router-dom'; import { routes } from '../components/app/Main/route_config'; -import { ApmPluginContextValue } from '../context/ApmPluginContext'; +import { ApmPluginContextValue } from '../context/apm_plugin/apm_plugin_context'; import { mockApmPluginContextValue, MockApmPluginContextWrapper, -} from '../context/ApmPluginContext/MockApmPluginContext'; +} from '../context/apm_plugin/mock_apm_plugin_context'; import { useBreadcrumbs } from './use_breadcrumbs'; function createWrapper(path: string) { diff --git a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts index 640170bf3bff27..089381cbe05b5b 100644 --- a/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts +++ b/x-pack/plugins/apm/public/hooks/use_breadcrumbs.ts @@ -16,7 +16,7 @@ import { } from 'react-router-dom'; import { APMRouteDefinition, BreadcrumbTitle } from '../application/routes'; import { getAPMHref } from '../components/shared/Links/apm/APMLink'; -import { useApmPluginContext } from './useApmPluginContext'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; interface BreadcrumbWithoutLink extends ChromeBreadcrumb { match: Match>; diff --git a/x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts b/x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts similarity index 89% rename from x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts rename to x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts index d0e12d8537846a..becdf1f9ecc5e4 100644 --- a/x-pack/plugins/apm/public/hooks/useDynamicIndexPattern.ts +++ b/x-pack/plugins/apm/public/hooks/use_dynamic_index_pattern.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useFetcher } from './useFetcher'; +import { useFetcher } from './use_fetcher'; import { UIProcessorEvent } from '../../common/processor_event'; -export function useDynamicIndexPattern( +export function useDynamicIndexPatternFetcher( processorEvent: UIProcessorEvent | undefined ) { const { data, status } = useFetcher( diff --git a/x-pack/plugins/apm/public/hooks/useEnvironments.tsx b/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx similarity index 94% rename from x-pack/plugins/apm/public/hooks/useEnvironments.tsx rename to x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx index 05ac780aefbde8..1ad151b8c7e907 100644 --- a/x-pack/plugins/apm/public/hooks/useEnvironments.tsx +++ b/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx @@ -5,7 +5,7 @@ */ import { useMemo } from 'react'; -import { useFetcher } from './useFetcher'; +import { useFetcher } from './use_fetcher'; import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, @@ -23,7 +23,7 @@ function getEnvironmentOptions(environments: string[]) { return [ENVIRONMENT_ALL, ...environmentOptions]; } -export function useEnvironments({ +export function useEnvironmentsFetcher({ serviceName, start, end, diff --git a/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx new file mode 100644 index 00000000000000..1c17be884ebf56 --- /dev/null +++ b/x-pack/plugins/apm/public/hooks/use_error_group_distribution_fetcher.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useUrlParams } from '../context/url_params_context/use_url_params'; +import { useFetcher } from './use_fetcher'; + +export function useErrorGroupDistributionFetcher({ + serviceName, + groupId, +}: { + serviceName: string; + groupId: string | undefined; +}) { + const { urlParams, uiFilters } = useUrlParams(); + const { start, end } = urlParams; + const { data } = useFetcher( + (callApmApi) => { + if (start && end) { + return callApmApi({ + endpoint: 'GET /api/apm/services/{serviceName}/errors/distribution', + params: { + path: { serviceName }, + query: { + start, + end, + groupId, + uiFilters: JSON.stringify(uiFilters), + }, + }, + }); + } + }, + [serviceName, start, end, groupId, uiFilters] + ); + + return { errorDistributionData: data }; +} diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx index e837851828d94f..e6f3b71af8a859 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.integration.test.tsx @@ -7,8 +7,8 @@ import { render, waitFor } from '@testing-library/react'; import React from 'react'; import { delay } from '../utils/testHelpers'; -import { useFetcher } from './useFetcher'; -import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext'; +import { useFetcher } from './use_fetcher'; +import { MockApmPluginContextWrapper } from '../context/apm_plugin/mock_apm_plugin_context'; const wrapper = MockApmPluginContextWrapper; diff --git a/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx similarity index 96% rename from x-pack/plugins/apm/public/hooks/useFetcher.test.tsx rename to x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx index 59dd9455c724c9..9b4ad6bc9bb517 100644 --- a/x-pack/plugins/apm/public/hooks/useFetcher.test.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.test.tsx @@ -6,9 +6,9 @@ import { renderHook, RenderHookResult } from '@testing-library/react-hooks'; import { delay } from '../utils/testHelpers'; -import { FetcherResult, useFetcher } from './useFetcher'; -import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext'; -import { ApmPluginContextValue } from '../context/ApmPluginContext'; +import { FetcherResult, useFetcher } from './use_fetcher'; +import { MockApmPluginContextWrapper } from '../context/apm_plugin/mock_apm_plugin_context'; +import { ApmPluginContextValue } from '../context/apm_plugin/apm_plugin_context'; // Wrap the hook with a provider so it can useApmPluginContext const wrapper = MockApmPluginContextWrapper; diff --git a/x-pack/plugins/apm/public/hooks/useFetcher.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx similarity index 98% rename from x-pack/plugins/apm/public/hooks/useFetcher.tsx rename to x-pack/plugins/apm/public/hooks/use_fetcher.tsx index 6add0e8a2b4802..a9a4871dc87071 100644 --- a/x-pack/plugins/apm/public/hooks/useFetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx @@ -9,7 +9,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { IHttpFetchError } from 'src/core/public'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; import { APMClient, callApmApi } from '../services/rest/createCallApmApi'; -import { useApmPluginContext } from './useApmPluginContext'; +import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; export enum FETCH_STATUS { LOADING = 'loading', diff --git a/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts similarity index 75% rename from x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts rename to x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts index 7a54c6ffc6dbeb..c888c515895631 100644 --- a/x-pack/plugins/apm/public/hooks/useServiceMetricCharts.ts +++ b/x-pack/plugins/apm/public/hooks/use_service_metric_charts_fetcher.ts @@ -7,22 +7,23 @@ import { useParams } from 'react-router-dom'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { MetricsChartsByAgentAPIResponse } from '../../server/lib/metrics/get_metrics_chart_data_by_agent'; -import { useUiFilters } from '../context/UrlParamsContext'; -import { IUrlParams } from '../context/UrlParamsContext/types'; -import { useFetcher } from './useFetcher'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; +import { useApmServiceContext } from '../context/apm_service/use_apm_service_context'; +import { useFetcher } from './use_fetcher'; const INITIAL_DATA: MetricsChartsByAgentAPIResponse = { charts: [], }; -export function useServiceMetricCharts( - urlParams: IUrlParams, - agentName?: string, - serviceNodeName?: string -) { +export function useServiceMetricChartsFetcher({ + serviceNodeName, +}: { + serviceNodeName: string | undefined; +}) { + const { urlParams, uiFilters } = useUrlParams(); + const { agentName } = useApmServiceContext(); const { serviceName } = useParams<{ serviceName?: string }>(); const { start, end } = urlParams; - const uiFilters = useUiFilters(urlParams); const { data = INITIAL_DATA, error, status } = useFetcher( (callApmApi) => { if (serviceName && start && end && agentName) { diff --git a/x-pack/plugins/apm/public/hooks/useTheme.tsx b/x-pack/plugins/apm/public/hooks/use_theme.tsx similarity index 100% rename from x-pack/plugins/apm/public/hooks/useTheme.tsx rename to x-pack/plugins/apm/public/hooks/use_theme.tsx diff --git a/x-pack/plugins/apm/public/hooks/useTransactionCharts.ts b/x-pack/plugins/apm/public/hooks/use_transaction_charts_fetcher.ts similarity index 82% rename from x-pack/plugins/apm/public/hooks/useTransactionCharts.ts rename to x-pack/plugins/apm/public/hooks/use_transaction_charts_fetcher.ts index c790ac57edc3bf..f5105e38b985e2 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionCharts.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_charts_fetcher.ts @@ -7,10 +7,10 @@ import { useMemo } from 'react'; import { useParams } from 'react-router-dom'; import { getTransactionCharts } from '../selectors/chart_selectors'; -import { useFetcher } from './useFetcher'; -import { useUrlParams } from './useUrlParams'; +import { useFetcher } from './use_fetcher'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; -export function useTransactionCharts() { +export function useTransactionChartsFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); const { urlParams: { transactionType, start, end, transactionName }, @@ -45,8 +45,8 @@ export function useTransactionCharts() { ); return { - data: memoizedData, - status, - error, + transactionChartsData: memoizedData, + transactionChartsStatus: status, + transactionChartsError: error, }; } diff --git a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts similarity index 89% rename from x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts rename to x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts index 9cbfee37d12530..74222e8ffe038c 100644 --- a/x-pack/plugins/apm/public/hooks/useTransactionDistribution.ts +++ b/x-pack/plugins/apm/public/hooks/use_transaction_distribution_fetcher.ts @@ -6,12 +6,11 @@ import { flatten, omit, isEmpty } from 'lodash'; import { useHistory, useParams } from 'react-router-dom'; -import { IUrlParams } from '../context/UrlParamsContext/types'; -import { useFetcher } from './useFetcher'; -import { useUiFilters } from '../context/UrlParamsContext'; +import { useFetcher } from './use_fetcher'; import { toQuery, fromQuery } from '../components/shared/Links/url_helpers'; import { maybe } from '../../common/utils/maybe'; import { APIReturnType } from '../services/rest/createCallApmApi'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; type APIResponse = APIReturnType<'GET /api/apm/services/{serviceName}/transaction_groups/distribution'>; @@ -21,8 +20,9 @@ const INITIAL_DATA = { bucketSize: 0, }; -export function useTransactionDistribution(urlParams: IUrlParams) { +export function useTransactionDistributionFetcher() { const { serviceName } = useParams<{ serviceName?: string }>(); + const { urlParams, uiFilters } = useUrlParams(); const { start, end, @@ -31,10 +31,8 @@ export function useTransactionDistribution(urlParams: IUrlParams) { traceId, transactionName, } = urlParams; - const uiFilters = useUiFilters(urlParams); const history = useHistory(); - const { data = INITIAL_DATA, status, error } = useFetcher( async (callApmApi) => { if (serviceName && start && end && transactionType && transactionName) { @@ -96,5 +94,9 @@ export function useTransactionDistribution(urlParams: IUrlParams) { [serviceName, start, end, transactionType, transactionName, uiFilters] ); - return { data, status, error }; + return { + distributionData: data, + distributionStatus: status, + distributionError: error, + }; } diff --git a/x-pack/plugins/apm/public/selectors/chart_selectors.ts b/x-pack/plugins/apm/public/selectors/chart_selectors.ts index 2fdcaf9e4e6751..37bd04e5d99800 100644 --- a/x-pack/plugins/apm/public/selectors/chart_selectors.ts +++ b/x-pack/plugins/apm/public/selectors/chart_selectors.ts @@ -17,7 +17,7 @@ import { RectCoordinate, TimeSeries, } from '../../typings/timeseries'; -import { IUrlParams } from '../context/UrlParamsContext/types'; +import { IUrlParams } from '../context/url_params_context/types'; import { getEmptySeries } from '../components/shared/charts/helper/get_empty_series'; import { httpStatusCodeToColor } from '../utils/httpStatusCodeToColor'; import { asDuration, asTransactionRate } from '../../common/utils/formatters'; diff --git a/x-pack/plugins/apm/public/utils/testHelpers.tsx b/x-pack/plugins/apm/public/utils/testHelpers.tsx index 87dfeb95b63263..21c87c18be363b 100644 --- a/x-pack/plugins/apm/public/utils/testHelpers.tsx +++ b/x-pack/plugins/apm/public/utils/testHelpers.tsx @@ -24,8 +24,8 @@ import { PromiseReturnType } from '../../../observability/typings/common'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { APMConfig } from '../../server'; import { UIFilters } from '../../typings/ui_filters'; -import { MockApmPluginContextWrapper } from '../context/ApmPluginContext/MockApmPluginContext'; -import { UrlParamsProvider } from '../context/UrlParamsContext'; +import { MockApmPluginContextWrapper } from '../context/apm_plugin/mock_apm_plugin_context'; +import { UrlParamsProvider } from '../context/url_params_context/url_params_context'; const originalConsoleWarn = console.warn; // eslint-disable-line no-console /** diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_actions_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_actions_provider.js new file mode 100644 index 00000000000000..df3017ebf92a4a --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_actions_provider.js @@ -0,0 +1,34 @@ +/* + * 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 { FollowerIndexPauseProvider } from './follower_index_pause_provider'; +import { FollowerIndexResumeProvider } from './follower_index_resume_provider'; +import { FollowerIndexUnfollowProvider } from './follower_index_unfollow_provider'; + +export const FollowerIndexActionsProvider = (props) => { + return ( + + {(pauseFollowerIndex) => ( + + {(resumeFollowerIndex) => ( + + {(unfollowLeaderIndex) => { + const { children } = props; + return children(() => ({ + pauseFollowerIndex, + resumeFollowerIndex, + unfollowLeaderIndex, + })); + }} + + )} + + )} + + ); +}; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js similarity index 95% rename from x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js index 9c1e8255d069c2..7d1168d8316316 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_pause_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_pause_provider.js @@ -11,9 +11,9 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; -import { pauseFollowerIndex } from '../store/actions'; -import { arrify } from '../../../common/services/utils'; -import { areAllSettingsDefault } from '../services/follower_index_default_settings'; +import { pauseFollowerIndex } from '../../store/actions'; +import { arrify } from '../../../../common/services/utils'; +import { areAllSettingsDefault } from '../../services/follower_index_default_settings'; class FollowerIndexPauseProviderUi extends PureComponent { static propTypes = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js similarity index 95% rename from x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js index 101e3df6bf710c..86f8c0447e7342 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_resume_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_resume_provider.js @@ -10,10 +10,10 @@ import { connect } from 'react-redux'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, EuiLink, EuiOverlayMask } from '@elastic/eui'; -import { reactRouterNavigate } from '../../../../../../src/plugins/kibana_react/public'; -import { routing } from '../services/routing'; -import { resumeFollowerIndex } from '../store/actions'; -import { arrify } from '../../../common/services/utils'; +import { reactRouterNavigate } from '../../../../../../../src/plugins/kibana_react/public'; +import { routing } from '../../services/routing'; +import { resumeFollowerIndex } from '../../store/actions'; +import { arrify } from '../../../../common/services/utils'; class FollowerIndexResumeProviderUi extends PureComponent { static propTypes = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js similarity index 97% rename from x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js rename to x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js index 68b6b970ad90ba..f9644aa20c2c2a 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_unfollow_provider.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/follower_index_unfollow_provider.js @@ -11,8 +11,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; -import { unfollowLeaderIndex } from '../store/actions'; -import { arrify } from '../../../common/services/utils'; +import { unfollowLeaderIndex } from '../../store/actions'; +import { arrify } from '../../../../common/services/utils'; class FollowerIndexUnfollowProviderUi extends PureComponent { static propTypes = { diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/index.js b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/index.js new file mode 100644 index 00000000000000..fe1a7d82a56a19 --- /dev/null +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/follower_index_actions_providers/index.js @@ -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. + */ + +export { FollowerIndexActionsProvider } from './follower_index_actions_provider'; +export { FollowerIndexPauseProvider } from './follower_index_pause_provider'; +export { FollowerIndexResumeProvider } from './follower_index_resume_provider'; +export { FollowerIndexUnfollowProvider } from './follower_index_unfollow_provider'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/components/index.js b/x-pack/plugins/cross_cluster_replication/public/app/components/index.js index 6d971bff03981f..55609fa85fb11b 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/components/index.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/components/index.js @@ -12,9 +12,10 @@ export { AutoFollowPatternForm } from './auto_follow_pattern_form'; export { AutoFollowPatternDeleteProvider } from './auto_follow_pattern_delete_provider'; export { AutoFollowPatternPageTitle } from './auto_follow_pattern_page_title'; export { AutoFollowPatternIndicesPreview } from './auto_follow_pattern_indices_preview'; -export { FollowerIndexPauseProvider } from './follower_index_pause_provider'; -export { FollowerIndexResumeProvider } from './follower_index_resume_provider'; -export { FollowerIndexUnfollowProvider } from './follower_index_unfollow_provider'; +export { FollowerIndexPauseProvider } from './follower_index_actions_providers'; +export { FollowerIndexResumeProvider } from './follower_index_actions_providers'; +export { FollowerIndexUnfollowProvider } from './follower_index_actions_providers'; +export { FollowerIndexActionsProvider } from './follower_index_actions_providers'; export { FollowerIndexForm } from './follower_index_form'; export { FollowerIndexPageTitle } from './follower_index_page_title'; export { FormEntryRow } from './form_entry_row'; diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js index 2309dece3f92be..dd5fe6f2128083 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/auto_follow_pattern_list/components/auto_follow_pattern_table/auto_follow_pattern_table.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { PureComponent, Fragment } from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { i18n } from '@kbn/i18n'; import { @@ -13,7 +13,6 @@ import { EuiLoadingKibana, EuiOverlayMask, EuiHealth, - EuiIcon, } from '@elastic/eui'; import { API_STATUS, UIM_AUTO_FOLLOW_PATTERN_SHOW_DETAILS_CLICK } from '../../../../../constants'; import { @@ -23,6 +22,33 @@ import { import { routing } from '../../../../../services/routing'; import { trackUiMetric } from '../../../../../services/track_ui_metric'; +const actionI18nTexts = { + pause: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionPauseDescription', + { + defaultMessage: 'Pause replication', + } + ), + resume: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionResumeDescription', + { + defaultMessage: 'Resume replication', + } + ), + edit: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionEditDescription', + { + defaultMessage: 'Edit auto-follow pattern', + } + ), + delete: i18n.translate( + 'xpack.crossClusterReplication.autoFollowPatternList.table.actionDeleteDescription', + { + defaultMessage: 'Delete auto-follow pattern', + } + ), +}; + const getFilteredPatterns = (autoFollowPatterns, queryText) => { if (queryText) { const normalizedSearchText = queryText.toLowerCase(); @@ -93,7 +119,7 @@ export class AutoFollowPatternTable extends PureComponent { }); }; - getTableColumns() { + getTableColumns(deleteAutoFollowPattern) { const { selectAutoFollowPattern } = this.props; return [ @@ -200,88 +226,34 @@ export class AutoFollowPatternTable extends PureComponent { ), actions: [ { - render: ({ name, active }) => { - const label = active - ? i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternList.table.actionPauseDescription', - { - defaultMessage: 'Pause replication', - } - ) - : i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternList.table.actionResumeDescription', - { - defaultMessage: 'Resume replication', - } - ); - - return ( - { - if (event.stopPropagation) { - event.stopPropagation(); - } - if (active) { - this.props.pauseAutoFollowPattern(name); - } else { - this.props.resumeAutoFollowPattern(name); - } - }} - data-test-subj={active ? 'contextMenuPauseButton' : 'contextMenuResumeButton'} - > - - {label} - - ); - }, + name: actionI18nTexts.pause, + description: actionI18nTexts.pause, + icon: 'pause', + onClick: (item) => this.props.pauseAutoFollowPattern(item.name), + available: (item) => item.active, + 'data-test-subj': 'contextMenuPauseButton', }, { - render: ({ name }) => { - const label = i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternList.table.actionEditDescription', - { - defaultMessage: 'Edit auto-follow pattern', - } - ); - - return ( - routing.navigate(routing.getAutoFollowPatternPath(name))} - data-test-subj="contextMenuEditButton" - > - - {label} - - ); - }, + name: actionI18nTexts.resume, + description: actionI18nTexts.resume, + icon: 'play', + onClick: (item) => this.props.resumeAutoFollowPattern(item.name), + available: (item) => !item.active, + 'data-test-subj': 'contextMenuResumeButton', }, { - render: ({ name }) => { - const label = i18n.translate( - 'xpack.crossClusterReplication.autoFollowPatternList.table.actionDeleteDescription', - { - defaultMessage: 'Delete auto-follow pattern', - } - ); - - return ( - - {(deleteAutoFollowPattern) => ( - deleteAutoFollowPattern(name)} - data-test-subj="contextMenuDeleteButton" - > - - {label} - - )} - - ); - }, + name: actionI18nTexts.edit, + description: actionI18nTexts.edit, + icon: 'pencil', + onClick: (item) => routing.navigate(routing.getAutoFollowPatternPath(item.name)), + 'data-test-subj': 'contextMenuEditButton', + }, + { + name: actionI18nTexts.delete, + description: actionI18nTexts.delete, + icon: 'trash', + onClick: (item) => deleteAutoFollowPattern(item.name), + 'data-test-subj': 'contextMenuDeleteButton', }, ], width: '100px', @@ -339,26 +311,30 @@ export class AutoFollowPatternTable extends PureComponent { }; return ( - - ({ - 'data-test-subj': 'row', - })} - cellProps={(item, column) => ({ - 'data-test-subj': `cell_${column.field}`, - })} - data-test-subj="autoFollowPatternListTable" - /> - {this.renderLoading()} - + + {(deleteAutoFollowPattern) => ( + <> + ({ + 'data-test-subj': 'row', + })} + cellProps={(item, column) => ({ + 'data-test-subj': `cell_${column.field}`, + })} + data-test-subj="autoFollowPatternListTable" + /> + {this.renderLoading()} + + )} + ); } } diff --git a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js index 0c57b3f7330cfd..2ea73e272b24e6 100644 --- a/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js +++ b/x-pack/plugins/cross_cluster_replication/public/app/sections/home/follower_indices_list/components/follower_indices_table/follower_indices_table.js @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { PureComponent, Fragment } from 'react'; +import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { - EuiIcon, EuiHealth, EuiInMemoryTable, EuiLink, @@ -17,15 +16,38 @@ import { EuiOverlayMask, } from '@elastic/eui'; import { API_STATUS, UIM_FOLLOWER_INDEX_SHOW_DETAILS_CLICK } from '../../../../../constants'; -import { - FollowerIndexPauseProvider, - FollowerIndexResumeProvider, - FollowerIndexUnfollowProvider, -} from '../../../../../components'; +import { FollowerIndexActionsProvider } from '../../../../../components'; import { routing } from '../../../../../services/routing'; import { trackUiMetric } from '../../../../../services/track_ui_metric'; import { ContextMenu } from '../context_menu'; +const actionI18nTexts = { + pause: i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionPauseDescription', + { + defaultMessage: 'Pause replication', + } + ), + resume: i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionResumeDescription', + { + defaultMessage: 'Resume replication', + } + ), + edit: i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionEditDescription', + { + defaultMessage: 'Edit follower index', + } + ), + unfollow: i18n.translate( + 'xpack.crossClusterReplication.followerIndexList.table.actionUnfollowDescription', + { + defaultMessage: 'Unfollow leader index', + } + ), +}; + const getFilteredIndices = (followerIndices, queryText) => { if (queryText) { const normalizedSearchText = queryText.toLowerCase(); @@ -101,91 +123,43 @@ export class FollowerIndicesTable extends PureComponent { routing.navigate(uri); }; - getTableColumns() { + getTableColumns(actionHandlers) { const { selectFollowerIndex } = this.props; const actions = [ - /* Pause or resume follower index */ + /* Pause follower index */ { - render: (followerIndex) => { - const { name, isPaused } = followerIndex; - const label = isPaused - ? i18n.translate( - 'xpack.crossClusterReplication.followerIndexList.table.actionResumeDescription', - { - defaultMessage: 'Resume replication', - } - ) - : i18n.translate( - 'xpack.crossClusterReplication.followerIndexList.table.actionPauseDescription', - { - defaultMessage: 'Pause replication', - } - ); - - return isPaused ? ( - - {(resumeFollowerIndex) => ( - resumeFollowerIndex(name)} data-test-subj="resumeButton"> - - {label} - - )} - - ) : ( - - {(pauseFollowerIndex) => ( - pauseFollowerIndex(followerIndex)} - data-test-subj="pauseButton" - > - - {label} - - )} - - ); - }, + name: actionI18nTexts.pause, + description: actionI18nTexts.pause, + icon: 'pause', + onClick: (item) => actionHandlers.pauseFollowerIndex(item), + available: (item) => !item.isPaused, + 'data-test-subj': 'pauseButton', + }, + /* Resume follower index */ + { + name: actionI18nTexts.resume, + description: actionI18nTexts.resume, + icon: 'play', + onClick: (item) => actionHandlers.resumeFollowerIndex(item.name), + available: (item) => item.isPaused, + 'data-test-subj': 'resumeButton', }, /* Edit follower index */ { - render: ({ name }) => { - const label = i18n.translate( - 'xpack.crossClusterReplication.followerIndexList.table.actionEditDescription', - { - defaultMessage: 'Edit follower index', - } - ); - - return ( - this.editFollowerIndex(name)} data-test-subj="editButton"> - - {label} - - ); - }, + name: actionI18nTexts.edit, + description: actionI18nTexts.edit, + onClick: (item) => this.editFollowerIndex(item.name), + icon: 'pencil', + 'data-test-subj': 'editButton', }, /* Unfollow leader index */ { - render: ({ name }) => { - const label = i18n.translate( - 'xpack.crossClusterReplication.followerIndexList.table.actionUnfollowDescription', - { - defaultMessage: 'Unfollow leader index', - } - ); - - return ( - - {(unfollowLeaderIndex) => ( - unfollowLeaderIndex(name)} data-test-subj="unfollowButton"> - - {label} - - )} - - ); - }, + name: actionI18nTexts.unfollow, + description: actionI18nTexts.unfollow, + onClick: (item) => actionHandlers.unfollowLeaderIndex(item.name), + icon: 'indexFlush', + 'data-test-subj': 'unfollowButton', }, ]; @@ -321,26 +295,33 @@ export class FollowerIndicesTable extends PureComponent { }; return ( - - ({ - 'data-test-subj': 'row', - })} - cellProps={(item, column) => ({ - 'data-test-subj': `cell-${column.field}`, - })} - data-test-subj="followerIndexListTable" - /> - {this.renderLoading()} - + + {(getActionHandlers) => { + const actionHandlers = getActionHandlers(); + return ( + <> + ({ + 'data-test-subj': 'row', + })} + cellProps={(item, column) => ({ + 'data-test-subj': `cell-${column.field}`, + })} + data-test-subj="followerIndexListTable" + /> + {this.renderLoading()} + + ); + }} + ); } } diff --git a/x-pack/plugins/data_enhanced/public/plugin.ts b/x-pack/plugins/data_enhanced/public/plugin.ts index 393a28c289e82f..a3b37e47287e50 100644 --- a/x-pack/plugins/data_enhanced/public/plugin.ts +++ b/x-pack/plugins/data_enhanced/public/plugin.ts @@ -69,6 +69,7 @@ export class DataEnhancedPlugin createConnectedBackgroundSessionIndicator({ sessionService: plugins.data.search.session, application: core.application, + timeFilter: plugins.data.query.timefilter.timefilter, }) ) ), diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx index c7195ea486e2f7..4a6a852be755b5 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.stories.tsx @@ -26,5 +26,14 @@ storiesOf('components/BackgroundSessionIndicator', module).add('default', () =>
+
+ +
)); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx index f401a460113c76..b7d342300f3119 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.test.tsx @@ -100,3 +100,13 @@ test('Canceled state', async () => { expect(onRefresh).toBeCalled(); }); + +test('Disabled state', async () => { + render( + + + + ); + + expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled(); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx index 674ce392aa2d0d..ce77686c4f3c10 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/background_session_indicator/background_session_indicator.tsx @@ -30,6 +30,8 @@ export interface BackgroundSessionIndicatorProps { viewBackgroundSessionsLink?: string; onSaveResults?: () => void; onRefresh?: () => void; + disabled?: boolean; + disabledReasonText?: string; } type ActionButtonProps = BackgroundSessionIndicatorProps & { buttonProps: EuiButtonEmptyProps }; @@ -285,12 +287,13 @@ export const BackgroundSessionIndicator: React.FC + } diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx index 4c9fd50dc8c4c5..e08773c6a8a760 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.test.tsx @@ -5,21 +5,36 @@ */ import React from 'react'; -import { render, waitFor } from '@testing-library/react'; +import { render, waitFor, screen, act } from '@testing-library/react'; import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks'; import { createConnectedBackgroundSessionIndicator } from './connected_background_session_indicator'; import { BehaviorSubject } from 'rxjs'; -import { ISessionService, SessionState } from '../../../../../../../src/plugins/data/public'; +import { + ISessionService, + RefreshInterval, + SessionState, + TimefilterContract, +} from '../../../../../../../src/plugins/data/public'; import { coreMock } from '../../../../../../../src/core/public/mocks'; const coreStart = coreMock.createStart(); -const sessionService = dataPluginMock.createStartContract().search - .session as jest.Mocked; +const dataStart = dataPluginMock.createStartContract(); +const sessionService = dataStart.search.session as jest.Mocked; + +const refreshInterval$ = new BehaviorSubject({ value: 0, pause: true }); +const timeFilter = dataStart.query.timefilter.timefilter as jest.Mocked; +timeFilter.getRefreshIntervalUpdate$.mockImplementation(() => refreshInterval$); +timeFilter.getRefreshInterval.mockImplementation(() => refreshInterval$.getValue()); + +beforeEach(() => { + refreshInterval$.next({ value: 0, pause: true }); +}); test("shouldn't show indicator in case no active search session", async () => { const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ sessionService, application: coreStart.application, + timeFilter, }); const { getByTestId, container } = render(); @@ -35,8 +50,32 @@ test('should show indicator in case there is an active search session', async () const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ sessionService: { ...sessionService, state$ }, application: coreStart.application, + timeFilter, }); const { getByTestId } = render(); await waitFor(() => getByTestId('backgroundSessionIndicator')); }); + +test('should be disabled during auto-refresh', async () => { + const state$ = new BehaviorSubject(SessionState.Loading); + const BackgroundSessionIndicator = createConnectedBackgroundSessionIndicator({ + sessionService: { ...sessionService, state$ }, + application: coreStart.application, + timeFilter, + }); + + render(); + + await waitFor(() => screen.getByTestId('backgroundSessionIndicator')); + + expect( + screen.getByTestId('backgroundSessionIndicator').querySelector('button') + ).not.toBeDisabled(); + + act(() => { + refreshInterval$.next({ value: 0, pause: false }); + }); + + expect(screen.getByTestId('backgroundSessionIndicator').querySelector('button')).toBeDisabled(); +}); diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx index 1cc2fffcea8c53..b80295d87d2023 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_background_session_indicator/connected_background_session_indicator.tsx @@ -5,24 +5,46 @@ */ import React from 'react'; -import { debounceTime } from 'rxjs/operators'; +import { debounceTime, distinctUntilChanged, map } from 'rxjs/operators'; import useObservable from 'react-use/lib/useObservable'; +import { i18n } from '@kbn/i18n'; import { BackgroundSessionIndicator } from '../background_session_indicator'; -import { ISessionService } from '../../../../../../../src/plugins/data/public/'; +import { ISessionService, TimefilterContract } from '../../../../../../../src/plugins/data/public/'; import { RedirectAppLinks } from '../../../../../../../src/plugins/kibana_react/public'; import { ApplicationStart } from '../../../../../../../src/core/public'; export interface BackgroundSessionIndicatorDeps { sessionService: ISessionService; + timeFilter: TimefilterContract; application: ApplicationStart; } export const createConnectedBackgroundSessionIndicator = ({ sessionService, application, + timeFilter, }: BackgroundSessionIndicatorDeps): React.FC => { + const isAutoRefreshEnabled = () => !timeFilter.getRefreshInterval().pause; + const isAutoRefreshEnabled$ = timeFilter + .getRefreshIntervalUpdate$() + .pipe(map(isAutoRefreshEnabled), distinctUntilChanged()); + return () => { const state = useObservable(sessionService.state$.pipe(debounceTime(500))); + const autoRefreshEnabled = useObservable(isAutoRefreshEnabled$, isAutoRefreshEnabled()); + let disabled = false; + let disabledReasonText: string = ''; + + if (autoRefreshEnabled) { + disabled = true; + disabledReasonText = i18n.translate( + 'xpack.data.backgroundSessionIndicator.disabledDueToAutoRefreshMessage', + { + defaultMessage: 'Send to background is not available when auto refresh is enabled.', + } + ); + } + if (!state) return null; return ( @@ -40,6 +62,8 @@ export const createConnectedBackgroundSessionIndicator = ({ onCancel={() => { sessionService.cancel(); }} + disabled={disabled} + disabledReasonText={disabledReasonText} /> ); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/constants.ts b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/constants.ts new file mode 100644 index 00000000000000..b2b76d5b987b99 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/constants.ts @@ -0,0 +1,28 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export const INDEXING_STATUS_PROGRESS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.indexingStatus.progress.title', + { + defaultMessage: 'Indexing progress', + } +); + +export const INDEXING_STATUS_HAS_ERRORS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.indexingStatus.hasErrors.title', + { + defaultMessage: 'Several documents have field conversion errors.', + } +); + +export const INDEXING_STATUS_HAS_ERRORS_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.indexingStatus.hasErrors.button', + { + defaultMessage: 'View errors', + } +); diff --git a/x-pack/plugins/telemetry_collection_xpack/common/index.ts b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/index.ts similarity index 68% rename from x-pack/plugins/telemetry_collection_xpack/common/index.ts rename to x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/index.ts index 2b08ebe2e7bbf2..4a97f11e8f0ee3 100644 --- a/x-pack/plugins/telemetry_collection_xpack/common/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/index.ts @@ -4,5 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export const PLUGIN_ID = 'telemetryCollectionXpack'; -export const PLUGIN_NAME = 'telemetry_collection_xpack'; +export { IndexingStatus } from './indexing_status'; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.test.tsx new file mode 100644 index 00000000000000..097c3bbc8e9ff6 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.test.tsx @@ -0,0 +1,51 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { EuiPanel } from '@elastic/eui'; + +import { IndexingStatusContent } from './indexing_status_content'; +import { IndexingStatusErrors } from './indexing_status_errors'; +import { IndexingStatusFetcher } from './indexing_status_fetcher'; +import { IndexingStatus } from './indexing_status'; + +describe('IndexingStatus', () => { + const getItemDetailPath = jest.fn(); + const getStatusPath = jest.fn(); + const onComplete = jest.fn(); + const setGlobalIndexingStatus = jest.fn(); + + const props = { + percentageComplete: 50, + numDocumentsWithErrors: 1, + activeReindexJobId: 12, + viewLinkPath: '/path', + itemId: '1', + getItemDetailPath, + getStatusPath, + onComplete, + setGlobalIndexingStatus, + }; + + it('renders', () => { + const wrapper = shallow(); + const fetcher = wrapper.find(IndexingStatusFetcher).prop('children')( + props.percentageComplete, + props.numDocumentsWithErrors + ); + + expect(shallow(fetcher).find(EuiPanel)).toHaveLength(1); + expect(shallow(fetcher).find(IndexingStatusContent)).toHaveLength(1); + }); + + it('renders errors', () => { + const wrapper = shallow(); + const fetcher = wrapper.find(IndexingStatusFetcher).prop('children')(100, 1); + expect(shallow(fetcher).find(IndexingStatusErrors)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.tsx new file mode 100644 index 00000000000000..beec0babea5903 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status.tsx @@ -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 React from 'react'; + +import { EuiPanel, EuiSpacer } from '@elastic/eui'; + +import { IndexingStatusContent } from './indexing_status_content'; +import { IndexingStatusErrors } from './indexing_status_errors'; +import { IndexingStatusFetcher } from './indexing_status_fetcher'; + +import { IIndexingStatus } from '../types'; + +export interface IIndexingStatusProps extends IIndexingStatus { + viewLinkPath: string; + itemId: string; + getItemDetailPath?(itemId: string): string; + getStatusPath(itemId: string, activeReindexJobId: number): string; + onComplete(numDocumentsWithErrors: number): void; + setGlobalIndexingStatus?(activeReindexJob: IIndexingStatus): void; +} + +export const IndexingStatus: React.FC = (props) => ( + + {(percentageComplete, numDocumentsWithErrors) => ( +
+ {percentageComplete < 100 && ( + + + + )} + {percentageComplete === 100 && numDocumentsWithErrors > 0 && ( + <> + + + + )} +
+ )} +
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.test.tsx new file mode 100644 index 00000000000000..9fe0e890e6943e --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.test.tsx @@ -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 React from 'react'; +import { shallow } from 'enzyme'; + +import { EuiProgress, EuiTitle } from '@elastic/eui'; + +import { IndexingStatusContent } from './indexing_status_content'; + +describe('IndexingStatusContent', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiTitle)).toHaveLength(1); + expect(wrapper.find(EuiProgress)).toHaveLength(1); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.tsx new file mode 100644 index 00000000000000..a0c67388621a82 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_content.tsx @@ -0,0 +1,27 @@ +/* + * 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 { EuiProgress, EuiSpacer, EuiTitle } from '@elastic/eui'; + +import { INDEXING_STATUS_PROGRESS_TITLE } from './constants'; + +interface IIndexingStatusContentProps { + percentageComplete: number; +} + +export const IndexingStatusContent: React.FC = ({ + percentageComplete, +}) => ( +
+ +

{INDEXING_STATUS_PROGRESS_TITLE}

+
+ + +
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.test.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.test.tsx new file mode 100644 index 00000000000000..fc706aee659a5a --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.test.tsx @@ -0,0 +1,25 @@ +/* + * 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 { shallow } from 'enzyme'; + +import { EuiCallOut, EuiButton } from '@elastic/eui'; + +import { EuiLinkTo } from '../react_router_helpers'; + +import { IndexingStatusErrors } from './indexing_status_errors'; + +describe('IndexingStatusErrors', () => { + it('renders', () => { + const wrapper = shallow(); + + expect(wrapper.find(EuiButton)).toHaveLength(1); + expect(wrapper.find(EuiCallOut)).toHaveLength(1); + expect(wrapper.find(EuiLinkTo)).toHaveLength(1); + expect(wrapper.find(EuiLinkTo).prop('to')).toEqual('/path'); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.tsx new file mode 100644 index 00000000000000..a928400b2338ca --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_errors.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 React from 'react'; + +import { EuiButton, EuiCallOut } from '@elastic/eui'; + +import { EuiLinkTo } from '../react_router_helpers'; + +import { INDEXING_STATUS_HAS_ERRORS_TITLE, INDEXING_STATUS_HAS_ERRORS_BUTTON } from './constants'; + +interface IIndexingStatusErrorsProps { + viewLinkPath: string; +} + +export const IndexingStatusErrors: React.FC = ({ viewLinkPath }) => ( + +

{INDEXING_STATUS_HAS_ERRORS_TITLE}

+ + {INDEXING_STATUS_HAS_ERRORS_BUTTON} + +
+); diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_fetcher.tsx b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_fetcher.tsx new file mode 100644 index 00000000000000..cb7c82f91ed618 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/indexing_status/indexing_status_fetcher.tsx @@ -0,0 +1,64 @@ +/* + * 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, { useEffect, useState, useRef } from 'react'; + +import { HttpLogic } from '../http'; +import { flashAPIErrors } from '../flash_messages'; + +interface IIndexingStatusFetcherProps { + activeReindexJobId: number; + itemId: string; + percentageComplete: number; + numDocumentsWithErrors: number; + onComplete?(numDocumentsWithErrors: number): void; + getStatusPath(itemId: string, activeReindexJobId: number): string; + children(percentageComplete: number, numDocumentsWithErrors: number): JSX.Element; +} + +export const IndexingStatusFetcher: React.FC = ({ + activeReindexJobId, + children, + getStatusPath, + itemId, + numDocumentsWithErrors, + onComplete, + percentageComplete = 0, +}) => { + const [indexingStatus, setIndexingStatus] = useState({ + numDocumentsWithErrors, + percentageComplete, + }); + const pollingInterval = useRef(); + + useEffect(() => { + pollingInterval.current = window.setInterval(async () => { + try { + const response = await HttpLogic.values.http.get(getStatusPath(itemId, activeReindexJobId)); + if (response.percentageComplete >= 100) { + clearInterval(pollingInterval.current); + } + setIndexingStatus({ + percentageComplete: response.percentageComplete, + numDocumentsWithErrors: response.numDocumentsWithErrors, + }); + if (response.percentageComplete >= 100 && onComplete) { + onComplete(response.numDocumentsWithErrors); + } + } catch (e) { + flashAPIErrors(e); + } + }, 3000); + + return () => { + if (pollingInterval.current) { + clearInterval(pollingInterval.current); + } + }; + }, []); + + return children(indexingStatus.percentageComplete, indexingStatus.numDocumentsWithErrors); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/shared/types.ts b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts new file mode 100644 index 00000000000000..3866d1a7199e40 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/shared/types.ts @@ -0,0 +1,11 @@ +/* + * 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 IIndexingStatus { + percentageComplete: number; + numDocumentsWithErrors: number; + activeReindexJobId: number; +} diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts index 62f4dceeac363a..d97a587e57ff25 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts @@ -19,6 +19,9 @@ import { registerAccountPrepareSourcesRoute, registerAccountSourceSearchableRoute, registerAccountSourceDisplaySettingsConfig, + registerAccountSourceSchemasRoute, + registerAccountSourceReindexJobRoute, + registerAccountSourceReindexJobStatusRoute, registerOrgSourcesRoute, registerOrgSourcesStatusRoute, registerOrgSourceRoute, @@ -31,6 +34,9 @@ import { registerOrgPrepareSourcesRoute, registerOrgSourceSearchableRoute, registerOrgSourceDisplaySettingsConfig, + registerOrgSourceSchemasRoute, + registerOrgSourceReindexJobRoute, + registerOrgSourceReindexJobStatusRoute, registerOrgSourceOauthConfigurationsRoute, registerOrgSourceOauthConfigurationRoute, } from './sources'; @@ -523,6 +529,139 @@ describe('sources routes', () => { }); }); + describe('GET /api/workplace_search/account/sources/{id}/schemas', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/account/sources/{id}/schemas', + payload: 'params', + }); + + registerAccountSourceSchemasRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + id: '123', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/123/schemas', + }); + }); + }); + + describe('POST /api/workplace_search/account/sources/{id}/schemas', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/workplace_search/account/sources/{id}/schemas', + payload: 'body', + }); + + registerAccountSourceSchemasRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + const mockRequest = { + params: { id: '123' }, + body: {}, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/123/schemas', + body: mockRequest.body, + }); + }); + }); + + describe('GET /api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}', + payload: 'params', + }); + + registerAccountSourceReindexJobRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + source_id: '123', + job_id: '345', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/123/reindex_job/345', + }); + }); + }); + + describe('GET /api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}/status', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}/status', + payload: 'params', + }); + + registerAccountSourceReindexJobStatusRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + source_id: '123', + job_id: '345', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/123/reindex_job/345/status', + }); + }); + }); + describe('GET /api/workplace_search/org/sources', () => { let mockRouter: MockRouter; @@ -1000,6 +1139,139 @@ describe('sources routes', () => { }); }); + describe('GET /api/workplace_search/org/sources/{id}/schemas', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/org/sources/{id}/schemas', + payload: 'params', + }); + + registerOrgSourceSchemasRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + id: '123', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/123/schemas', + }); + }); + }); + + describe('POST /api/workplace_search/org/sources/{id}/schemas', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/workplace_search/org/sources/{id}/schemas', + payload: 'body', + }); + + registerOrgSourceSchemasRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + const mockRequest = { + params: { id: '123' }, + body: {}, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/123/schemas', + body: mockRequest.body, + }); + }); + }); + + describe('GET /api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}', + payload: 'params', + }); + + registerOrgSourceReindexJobRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + source_id: '123', + job_id: '345', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/123/reindex_job/345', + }); + }); + }); + + describe('GET /api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}/status', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('creates a request handler', () => { + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}/status', + payload: 'params', + }); + + registerOrgSourceReindexJobStatusRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + + const mockRequest = { + params: { + source_id: '123', + job_id: '345', + }, + }; + + mockRouter.callRoute(mockRequest); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/sources/123/reindex_job/345/status', + }); + }); + }); + describe('GET /api/workplace_search/org/settings/connectors', () => { let mockRouter: MockRouter; diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index d43a4252c7e1f8..9beac109be5106 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -339,6 +339,89 @@ export function registerAccountSourceDisplaySettingsConfig({ ); } +export function registerAccountSourceSchemasRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/account/sources/{id}/schemas', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/sources/${request.params.id}/schemas`, + })(context, request, response); + } + ); + + router.post( + { + path: '/api/workplace_search/account/sources/{id}/schemas', + validate: { + body: schema.object({}), + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/sources/${request.params.id}/schemas`, + body: request.body, + })(context, request, response); + } + ); +} + +export function registerAccountSourceReindexJobRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}', + validate: { + params: schema.object({ + source_id: schema.string(), + job_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/sources/${request.params.source_id}/reindex_job/${request.params.job_id}`, + })(context, request, response); + } + ); +} + +export function registerAccountSourceReindexJobStatusRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/account/sources/{source_id}/reindex_job/{job_id}/status', + validate: { + params: schema.object({ + source_id: schema.string(), + job_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/sources/${request.params.source_id}/reindex_job/${request.params.job_id}/status`, + })(context, request, response); + } + ); +} + export function registerOrgSourcesRoute({ router, enterpriseSearchRequestHandler, @@ -638,6 +721,89 @@ export function registerOrgSourceDisplaySettingsConfig({ ); } +export function registerOrgSourceSchemasRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/org/sources/{id}/schemas', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/org/sources/${request.params.id}/schemas`, + })(context, request, response); + } + ); + + router.post( + { + path: '/api/workplace_search/org/sources/{id}/schemas', + validate: { + body: schema.object({}), + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/org/sources/${request.params.id}/schemas`, + body: request.body, + })(context, request, response); + } + ); +} + +export function registerOrgSourceReindexJobRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}', + validate: { + params: schema.object({ + source_id: schema.string(), + job_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/org/sources/${request.params.source_id}/reindex_job/${request.params.job_id}`, + })(context, request, response); + } + ); +} + +export function registerOrgSourceReindexJobStatusRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/org/sources/{source_id}/reindex_job/{job_id}/status', + validate: { + params: schema.object({ + source_id: schema.string(), + job_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + return enterpriseSearchRequestHandler.createRequest({ + path: `/ws/org/sources/${request.params.source_id}/reindex_job/${request.params.job_id}/status`, + })(context, request, response); + } + ); +} + export function registerOrgSourceOauthConfigurationsRoute({ router, enterpriseSearchRequestHandler, @@ -741,6 +907,9 @@ export const registerSourcesRoutes = (dependencies: RouteDependencies) => { registerAccountPrepareSourcesRoute(dependencies); registerAccountSourceSearchableRoute(dependencies); registerAccountSourceDisplaySettingsConfig(dependencies); + registerAccountSourceSchemasRoute(dependencies); + registerAccountSourceReindexJobRoute(dependencies); + registerAccountSourceReindexJobStatusRoute(dependencies); registerOrgSourcesRoute(dependencies); registerOrgSourcesStatusRoute(dependencies); registerOrgSourceRoute(dependencies); @@ -753,6 +922,9 @@ export const registerSourcesRoutes = (dependencies: RouteDependencies) => { registerOrgPrepareSourcesRoute(dependencies); registerOrgSourceSearchableRoute(dependencies); registerOrgSourceDisplaySettingsConfig(dependencies); + registerOrgSourceSchemasRoute(dependencies); + registerOrgSourceReindexJobRoute(dependencies); + registerOrgSourceReindexJobStatusRoute(dependencies); registerOrgSourceOauthConfigurationsRoute(dependencies); registerOrgSourceOauthConfigurationRoute(dependencies); }; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx index ed607e361bd6e5..d9aeba23726729 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/enrollment_token_list_page/components/new_enrollment_key_flyout.tsx @@ -46,6 +46,9 @@ function useCreateApiKeyForm( policy_id: policyIdInput.value, }), }); + if (res.error) { + throw res.error; + } policyIdInput.clear(); apiKeyNameInput.clear(); setIsLoading(false); diff --git a/x-pack/plugins/fleet/server/errors/index.ts b/x-pack/plugins/fleet/server/errors/index.ts index e3ca6a9b48dcfe..d6fa79a2baeba0 100644 --- a/x-pack/plugins/fleet/server/errors/index.ts +++ b/x-pack/plugins/fleet/server/errors/index.ts @@ -5,7 +5,11 @@ */ /* eslint-disable max-classes-per-file */ -export { defaultIngestErrorHandler, ingestErrorToResponseOptions } from './handlers'; +export { + defaultIngestErrorHandler, + ingestErrorToResponseOptions, + isLegacyESClientError, +} from './handlers'; export class IngestManagerError extends Error { constructor(message?: string) { @@ -24,3 +28,4 @@ export class PackageUnsupportedMediaTypeError extends IngestManagerError {} export class PackageInvalidArchiveError extends IngestManagerError {} export class PackageCacheError extends IngestManagerError {} export class PackageOperationNotSupportedError extends IngestManagerError {} +export class FleetAdminUserInvalidError extends IngestManagerError {} diff --git a/x-pack/plugins/fleet/server/services/api_keys/security.ts b/x-pack/plugins/fleet/server/services/api_keys/security.ts index dfd53d55fbbf5d..5fdf8626a9fb22 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/security.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/security.ts @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest, FakeRequest, SavedObjectsClientContract } from 'src/core/server'; +import type { Request } from '@hapi/hapi'; +import { KibanaRequest, SavedObjectsClientContract } from '../../../../../../src/core/server'; +import { FleetAdminUserInvalidError, isLegacyESClientError } from '../../errors'; import { CallESAsCurrentUser } from '../../types'; import { appContextService } from '../app_context'; import { outputService } from '../output'; @@ -18,22 +20,38 @@ export async function createAPIKey( if (!adminUser) { throw new Error('No admin user configured'); } - const request: FakeRequest = { + const request = KibanaRequest.from(({ + path: '/', + route: { settings: {} }, + url: { href: '/' }, + raw: { req: { url: '/' } }, headers: { authorization: `Basic ${Buffer.from(`${adminUser.username}:${adminUser.password}`).toString( 'base64' )}`, }, - }; + } as unknown) as Request); const security = appContextService.getSecurity(); if (!security) { throw new Error('Missing security plugin'); } - return security.authc.createAPIKey(request as KibanaRequest, { - name, - role_descriptors: roleDescriptors, - }); + try { + const key = await security.authc.createAPIKey(request, { + name, + role_descriptors: roleDescriptors, + }); + + return key; + } catch (err) { + if (isLegacyESClientError(err) && err.statusCode === 401) { + // Clear Fleet admin user cache as the user is probably not valid anymore + outputService.invalidateCache(); + throw new FleetAdminUserInvalidError(`Fleet Admin user is invalid: ${err.message}`); + } + + throw err; + } } export async function authenticate(callCluster: CallESAsCurrentUser) { try { @@ -51,20 +69,36 @@ export async function invalidateAPIKey(soClient: SavedObjectsClientContract, id: if (!adminUser) { throw new Error('No admin user configured'); } - const request: FakeRequest = { + const request = KibanaRequest.from(({ + path: '/', + route: { settings: {} }, + url: { href: '/' }, + raw: { req: { url: '/' } }, headers: { authorization: `Basic ${Buffer.from(`${adminUser.username}:${adminUser.password}`).toString( 'base64' )}`, }, - }; + } as unknown) as Request); const security = appContextService.getSecurity(); if (!security) { throw new Error('Missing security plugin'); } - return security.authc.invalidateAPIKey(request as KibanaRequest, { - id, - }); + try { + const res = await security.authc.invalidateAPIKey(request, { + id, + }); + + return res; + } catch (err) { + if (isLegacyESClientError(err) && err.statusCode === 401) { + // Clear Fleet admin user cache as the user is probably not valid anymore + outputService.invalidateCache(); + throw new FleetAdminUserInvalidError(`Fleet Admin user is invalid: ${err.message}`); + } + + throw err; + } } diff --git a/x-pack/plugins/fleet/server/services/epm/archive/validation.ts b/x-pack/plugins/fleet/server/services/epm/archive/validation.ts index dc7a91e08799cb..b93a9119cd4df8 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/validation.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/validation.ts @@ -5,7 +5,7 @@ */ import yaml from 'js-yaml'; -import { uniq } from 'lodash'; +import { pick, uniq } from 'lodash'; import { ArchivePackage, RegistryPolicyTemplate, @@ -21,6 +21,42 @@ import { pkgToPkgKey } from '../registry'; const MANIFESTS: Record = {}; const MANIFEST_NAME = 'manifest.yml'; +// not sure these are 100% correct but they do the job here +// keeping them local until others need them +type OptionalPropertyOf = Exclude< + { + [K in keyof T]: T extends Record ? never : K; + }[keyof T], + undefined +>; +type RequiredPropertyOf = Exclude>; + +type RequiredPackageProp = RequiredPropertyOf; +type OptionalPackageProp = OptionalPropertyOf; +// pro: guarantee only supplying known values. these keys must be in ArchivePackage. no typos or new values +// pro: any values added to these lists will be passed through by default +// pro & con: values do need to be shadowed / repeated from ArchivePackage, but perhaps we want to limit values +const requiredArchivePackageProps: readonly RequiredPackageProp[] = [ + 'name', + 'version', + 'description', + 'type', + 'categories', + 'format_version', +] as const; + +const optionalArchivePackageProps: readonly OptionalPackageProp[] = [ + 'title', + 'release', + 'readme', + 'screenshots', + 'icons', + 'assets', + 'internal', + 'data_streams', + 'policy_templates', +] as const; + // TODO: everything below performs verification of manifest.yml files, and hence duplicates functionality already implemented in the // package registry. At some point this should probably be replaced (or enhanced) with verification based on // https://github.com/elastic/package-spec/ @@ -58,43 +94,43 @@ function parseAndVerifyArchive(paths: string[]): ArchivePackage { } // ... which must be valid YAML - let manifest; + let manifest: ArchivePackage; try { manifest = yaml.load(manifestBuffer.toString()); } catch (error) { throw new PackageInvalidArchiveError(`Could not parse top-level package manifest: ${error}.`); } - // Package name and version from the manifest must match those from the toplevel directory - const pkgKey = pkgToPkgKey({ name: manifest.name, version: manifest.version }); - if (toplevelDir !== pkgKey) { + // must have mandatory fields + const reqGiven = pick(manifest, requiredArchivePackageProps); + const requiredKeysMatch = + Object.keys(reqGiven).toString() === requiredArchivePackageProps.toString(); + if (!requiredKeysMatch) { + const list = requiredArchivePackageProps.join(', '); throw new PackageInvalidArchiveError( - `Name ${manifest.name} and version ${manifest.version} do not match top-level directory ${toplevelDir}` + `Invalid top-level package manifest: one or more fields missing of ${list}` ); } - const { name, version, description, type, categories, format_version: formatVersion } = manifest; - // check for mandatory fields - if (!(name && version && description && type && categories && formatVersion)) { + // at least have all required properties + // get optional values and combine into one object for the remaining operations + const optGiven = pick(manifest, optionalArchivePackageProps); + const parsed: ArchivePackage = { ...reqGiven, ...optGiven }; + + // Package name and version from the manifest must match those from the toplevel directory + const pkgKey = pkgToPkgKey({ name: parsed.name, version: parsed.version }); + if (toplevelDir !== pkgKey) { throw new PackageInvalidArchiveError( - 'Invalid top-level package manifest: one or more fields missing of name, version, description, type, categories, format_version' + `Name ${parsed.name} and version ${parsed.version} do not match top-level directory ${toplevelDir}` ); } - const dataStreams = parseAndVerifyDataStreams(paths, name, version); - const policyTemplates = parseAndVerifyPolicyTemplates(manifest); + parsed.data_streams = parseAndVerifyDataStreams(paths, parsed.name, parsed.version); + parsed.policy_templates = parseAndVerifyPolicyTemplates(manifest); - return { - name, - version, - description, - type, - categories, - format_version: formatVersion, - data_streams: dataStreams, - policy_templates: policyTemplates, - }; + return parsed; } + function parseAndVerifyDataStreams( paths: string[], pkgName: string, diff --git a/x-pack/plugins/graph/public/angular/graph_client_workspace.js b/x-pack/plugins/graph/public/angular/graph_client_workspace.js index 5cc06bad4c4238..785e221b798650 100644 --- a/x-pack/plugins/graph/public/angular/graph_client_workspace.js +++ b/x-pack/plugins/graph/public/angular/graph_client_workspace.js @@ -1187,7 +1187,7 @@ function GraphWorkspace(options) { // Search for connections between the selected nodes. searcher(self.options.indexName, searchReq, function (data) { - const numDocsMatched = data.hits.total; + const numDocsMatched = data.hits.total.value; const buckets = data.aggregations.matrix.buckets; const vertices = nodesForLinking.map(function (existingNode) { return { diff --git a/x-pack/plugins/graph/server/routes/search.ts b/x-pack/plugins/graph/server/routes/search.ts index 1bd28616872816..7d05f9ab6888c2 100644 --- a/x-pack/plugins/graph/server/routes/search.ts +++ b/x-pack/plugins/graph/server/routes/search.ts @@ -47,7 +47,7 @@ export function registerSearchRoute({ await esClient.asCurrentUser.search({ index: request.body.index, body: request.body.body, - rest_total_hits_as_int: true, + track_total_hits: true, ignore_throttled: !includeFrozen, }) ).body, diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx index ad5509dd88bc9d..5121714050c680 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/datapanel.tsx @@ -563,7 +563,8 @@ export const InnerIndexPatternDataPanel = function InnerIndexPatternDataPanel({ '{availableFields} available {availableFields, plural, one {field} other {fields}}. {emptyFields} empty {emptyFields, plural, one {field} other {fields}}. {metaFields} meta {metaFields, plural, one {field} other {fields}}.', values: { availableFields: fieldGroups.AvailableFields.fields.length, - emptyFields: fieldGroups.EmptyFields.fields.length, + // empty fields can be undefined if there is no existence information to be fetched + emptyFields: fieldGroups.EmptyFields?.fields.length || 0, metaFields: fieldGroups.MetaFields.fields.length, }, })} diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx index eb7730677d52ab..9e89468200e2cb 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/field_list.tsx @@ -14,13 +14,6 @@ import { IndexPatternField } from './types'; import { FieldItemSharedProps, FieldsAccordion } from './fields_accordion'; const PAGINATION_SIZE = 50; -export interface FieldsGroup { - specialFields: IndexPatternField[]; - availableFields: IndexPatternField[]; - emptyFields: IndexPatternField[]; - metaFields: IndexPatternField[]; -} - export type FieldGroups = Record< string, { diff --git a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts index 68c47e11acfc0a..e5764eaf0e8c0a 100644 --- a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts +++ b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts @@ -5,9 +5,11 @@ */ import { uniq, mapValues } from 'lodash'; -import { PaletteOutput } from 'src/plugins/charts/public'; +import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; import { Datatable } from 'src/plugins/expressions'; -import { FormatFactory } from '../types'; +import { AccessorConfig, FormatFactory, FramePublicAPI } from '../types'; +import { getColumnToLabelMap } from './state_helpers'; +import { LayerConfig } from './types'; const isPrimitive = (value: unknown): boolean => value != null && typeof value !== 'object'; @@ -87,3 +89,48 @@ export function getColorAssignments( }; }); } + +export function getAccessorColorConfig( + colorAssignments: ColorAssignments, + frame: FramePublicAPI, + layer: LayerConfig, + sortedAccessors: string[], + paletteService: PaletteRegistry +): AccessorConfig[] { + const layerContainsSplits = Boolean(layer.splitAccessor); + const currentPalette: PaletteOutput = layer.palette || { type: 'palette', name: 'default' }; + const totalSeriesCount = colorAssignments[currentPalette.name].totalSeriesCount; + return sortedAccessors.map((accessor) => { + const currentYConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); + if (layerContainsSplits) { + return { + columnId: accessor as string, + triggerIcon: 'disabled', + }; + } + const columnToLabel = getColumnToLabelMap(layer, frame.datasourceLayers[layer.layerId]); + const rank = colorAssignments[currentPalette.name].getRank( + layer, + columnToLabel[accessor] || accessor, + accessor + ); + const customColor = + currentYConfig?.color || + paletteService.get(currentPalette.name).getColor( + [ + { + name: columnToLabel[accessor] || accessor, + rankAtDepth: rank, + totalSeriesAtDepth: totalSeriesCount, + }, + ], + { maxDepth: 1, totalSeries: totalSeriesCount }, + currentPalette.params + ); + return { + columnId: accessor as string, + triggerIcon: customColor ? 'color' : 'disabled', + color: customColor ? customColor : undefined, + }; + }); +} diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index 872eb179e6a5c9..f0dcaf589b1c43 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -10,24 +10,18 @@ import { render } from 'react-dom'; import { Position } from '@elastic/charts'; import { I18nProvider } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; +import { PaletteRegistry } from 'src/plugins/charts/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; import { getSuggestions } from './xy_suggestions'; import { LayerContextMenu, XyToolbar, DimensionEditor } from './xy_config_panel'; -import { - Visualization, - OperationMetadata, - VisualizationType, - AccessorConfig, - FramePublicAPI, -} from '../types'; +import { Visualization, OperationMetadata, VisualizationType, AccessorConfig } from '../types'; import { State, SeriesType, visualizationTypes, LayerConfig } from './types'; -import { getColumnToLabelMap, isHorizontalChart } from './state_helpers'; +import { isHorizontalChart } from './state_helpers'; import { toExpression, toPreviewExpression, getSortedAccessors } from './to_expression'; import { LensIconChartBarStacked } from '../assets/chart_bar_stacked'; import { LensIconChartMixedXy } from '../assets/chart_mixed_xy'; import { LensIconChartBarHorizontal } from '../assets/chart_bar_horizontal'; -import { ColorAssignments, getColorAssignments } from './color_assignment'; +import { getAccessorColorConfig, getColorAssignments } from './color_assignment'; const defaultIcon = LensIconChartBarStacked; const defaultSeriesType = 'bar_stacked'; @@ -328,7 +322,11 @@ export const getXyVisualization = ({ renderDimensionEditor(domElement, props) { render( - + , domElement ); @@ -375,51 +373,6 @@ export const getXyVisualization = ({ }, }); -function getAccessorColorConfig( - colorAssignments: ColorAssignments, - frame: FramePublicAPI, - layer: LayerConfig, - sortedAccessors: string[], - paletteService: PaletteRegistry -): AccessorConfig[] { - const layerContainsSplits = Boolean(layer.splitAccessor); - const currentPalette: PaletteOutput = layer.palette || { type: 'palette', name: 'default' }; - const totalSeriesCount = colorAssignments[currentPalette.name].totalSeriesCount; - return sortedAccessors.map((accessor) => { - const currentYConfig = layer.yConfig?.find((yConfig) => yConfig.forAccessor === accessor); - if (layerContainsSplits) { - return { - columnId: accessor as string, - triggerIcon: 'disabled', - }; - } - const columnToLabel = getColumnToLabelMap(layer, frame.datasourceLayers[layer.layerId]); - const rank = colorAssignments[currentPalette.name].getRank( - layer, - columnToLabel[accessor] || accessor, - accessor - ); - const customColor = - currentYConfig?.color || - paletteService.get(currentPalette.name).getColor( - [ - { - name: columnToLabel[accessor] || accessor, - rankAtDepth: rank, - totalSeriesAtDepth: totalSeriesCount, - }, - ], - { maxDepth: 1, totalSeries: totalSeriesCount }, - currentPalette.params - ); - return { - columnId: accessor as string, - triggerIcon: customColor ? 'color' : 'disabled', - color: customColor ? customColor : undefined, - }; - }); -} - function validateLayersForDimension( dimension: string, layers: LayerConfig[], diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx index 99fbfa058a2de4..7b84b990f963a2 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.test.tsx @@ -14,6 +14,8 @@ import { FramePublicAPI } from '../types'; import { State } from './types'; import { Position } from '@elastic/charts'; import { createMockFramePublicAPI, createMockDatasource } from '../editor_frame_service/mocks'; +import { chartPluginMock } from 'src/plugins/charts/public/mocks'; +import { EuiColorPicker } from '@elastic/eui'; describe('XY Config panels', () => { let frame: FramePublicAPI; @@ -322,6 +324,8 @@ describe('XY Config panels', () => { accessor="bar" groupId="left" state={{ ...state, layers: [{ ...state.layers[0], seriesType: 'bar_horizontal' }] }} + formatFactory={jest.fn()} + paletteService={chartPluginMock.createPaletteRegistry()} /> ); @@ -343,6 +347,8 @@ describe('XY Config panels', () => { accessor="bar" groupId="left" state={state} + formatFactory={jest.fn()} + paletteService={chartPluginMock.createPaletteRegistry()} /> ); @@ -353,5 +359,82 @@ describe('XY Config panels', () => { expect(options!.map(({ label }) => label)).toEqual(['Auto', 'Left', 'Right']); }); + + test('sets the color of a dimension to the color from palette service if not set explicitly', () => { + const state = testState(); + const component = mount( + + ); + + expect(component.find(EuiColorPicker).prop('color')).toEqual('black'); + }); + + test('uses the overwrite color if set', () => { + const state = testState(); + const component = mount( + + ); + + expect(component.find(EuiColorPicker).prop('color')).toEqual('red'); + }); }); }); diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx index a22530c5743b44..cd8a5993d3ecb6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/xy_config_panel.tsx @@ -5,7 +5,7 @@ */ import './xy_config_panel.scss'; -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { Position } from '@elastic/charts'; import { debounce } from 'lodash'; @@ -22,10 +22,12 @@ import { EuiToolTip, EuiIcon, } from '@elastic/eui'; +import { PaletteRegistry } from 'src/plugins/charts/public'; import { VisualizationLayerWidgetProps, VisualizationToolbarProps, VisualizationDimensionEditorProps, + FormatFactory, } from '../types'; import { State, @@ -48,6 +50,7 @@ import { AxisSettingsPopover } from './axis_settings_popover'; import { TooltipWrapper } from './tooltip_wrapper'; import { getAxesConfiguration } from './axes_configuration'; import { PalettePicker } from '../shared_components'; +import { getAccessorColorConfig, getColorAssignments } from './color_assignment'; type UnwrapArray = T extends Array ? P : T; type AxesSettingsConfigKeys = keyof AxesSettingsConfig; @@ -445,7 +448,12 @@ export function XyToolbar(props: VisualizationToolbarProps) { } const idPrefix = htmlIdGenerator()(); -export function DimensionEditor(props: VisualizationDimensionEditorProps) { +export function DimensionEditor( + props: VisualizationDimensionEditorProps & { + formatFactory: FormatFactory; + paletteService: PaletteRegistry; + } +) { const { state, setState, layerId, accessor } = props; const index = state.layers.findIndex((l) => l.layerId === layerId); const layer = state.layers[index]; @@ -556,12 +564,37 @@ const ColorPicker = ({ setState, layerId, accessor, -}: VisualizationDimensionEditorProps) => { + frame, + formatFactory, + paletteService, +}: VisualizationDimensionEditorProps & { + formatFactory: FormatFactory; + paletteService: PaletteRegistry; +}) => { const index = state.layers.findIndex((l) => l.layerId === layerId); const layer = state.layers[index]; const disabled = !!layer.splitAccessor; - const [color, setColor] = useState(getSeriesColor(layer, accessor)); + const overwriteColor = getSeriesColor(layer, accessor); + const currentColor = useMemo(() => { + if (overwriteColor || !frame.activeData) return overwriteColor; + + const colorAssignments = getColorAssignments( + state.layers, + { tables: frame.activeData }, + formatFactory + ); + const mappedAccessors = getAccessorColorConfig( + colorAssignments, + frame, + layer, + [accessor], + paletteService + ); + return mappedAccessors[0].color; + }, [overwriteColor, frame, paletteService, state.layers, accessor, formatFactory, layer]); + + const [color, setColor] = useState(currentColor); const handleColor: EuiColorPickerProps['onChange'] = (text, output) => { setColor(text); @@ -596,9 +629,9 @@ const ColorPicker = ({ { diff --git a/x-pack/plugins/lens/server/routes/existing_fields.test.ts b/x-pack/plugins/lens/server/routes/existing_fields.test.ts index c877e69d7b0dd7..0a3e669ba8538c 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.test.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.test.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { IndexPattern } from 'src/plugins/data/common'; import { existingFields, Field, buildFieldList } from './existing_fields'; describe('existingFields', () => { @@ -71,25 +72,20 @@ describe('existingFields', () => { describe('buildFieldList', () => { const indexPattern = { - id: '', - type: 'indexpattern', - attributes: { - title: 'testpattern', - type: 'type', - typeMeta: 'typemeta', - fields: JSON.stringify([ - { name: 'foo', scripted: true, lang: 'painless', script: '2+2' }, - { name: 'bar' }, - { name: '@bar' }, - { name: 'baz' }, - { name: '_mymeta' }, - ]), - }, - references: [], + title: 'testpattern', + type: 'type', + typeMeta: 'typemeta', + fields: [ + { name: 'foo', scripted: true, lang: 'painless', script: '2+2' }, + { name: 'bar' }, + { name: '@bar' }, + { name: 'baz' }, + { name: '_mymeta' }, + ], }; it('supports scripted fields', () => { - const fields = buildFieldList(indexPattern, []); + const fields = buildFieldList((indexPattern as unknown) as IndexPattern, []); expect(fields.find((f) => f.isScript)).toMatchObject({ isScript: true, name: 'foo', @@ -99,7 +95,7 @@ describe('buildFieldList', () => { }); it('supports meta fields', () => { - const fields = buildFieldList(indexPattern, ['_mymeta']); + const fields = buildFieldList((indexPattern as unknown) as IndexPattern, ['_mymeta']); expect(fields.find((f) => f.isMeta)).toMatchObject({ isScript: false, isMeta: true, diff --git a/x-pack/plugins/lens/server/routes/existing_fields.ts b/x-pack/plugins/lens/server/routes/existing_fields.ts index 844c7b16e1eaad..aef8b1b3d7076d 100644 --- a/x-pack/plugins/lens/server/routes/existing_fields.ts +++ b/x-pack/plugins/lens/server/routes/existing_fields.ts @@ -6,10 +6,12 @@ import Boom from '@hapi/boom'; import { schema } from '@kbn/config-schema'; -import { ILegacyScopedClusterClient, SavedObject, RequestHandlerContext } from 'src/core/server'; +import { ILegacyScopedClusterClient, RequestHandlerContext } from 'src/core/server'; import { CoreSetup, Logger } from 'src/core/server'; +import { IndexPattern, IndexPatternsService } from 'src/plugins/data/common'; import { BASE_API_URL } from '../../common'; -import { IndexPatternAttributes, UI_SETTINGS } from '../../../../../src/plugins/data/server'; +import { UI_SETTINGS } from '../../../../../src/plugins/data/server'; +import { PluginStartContract } from '../plugin'; export function isBoomError(error: { isBoom?: boolean }): error is Boom { return error.isBoom === true; @@ -28,7 +30,7 @@ export interface Field { script?: string; } -export async function existingFieldsRoute(setup: CoreSetup, logger: Logger) { +export async function existingFieldsRoute(setup: CoreSetup, logger: Logger) { const router = setup.http.createRouter(); router.post( @@ -47,11 +49,18 @@ export async function existingFieldsRoute(setup: CoreSetup, logger: Logger) { }, }, async (context, req, res) => { + const [{ savedObjects, elasticsearch }, { data }] = await setup.getStartServices(); + const savedObjectsClient = savedObjects.getScopedClient(req); + const esClient = elasticsearch.client.asScoped(req).asCurrentUser; try { return res.ok({ body: await fetchFieldExistence({ ...req.params, ...req.body, + indexPatternsService: await data.indexPatterns.indexPatternsServiceFactory( + savedObjectsClient, + esClient + ), context, }), }); @@ -80,6 +89,7 @@ export async function existingFieldsRoute(setup: CoreSetup, logger: Logger) { async function fetchFieldExistence({ context, indexPatternId, + indexPatternsService, dslQuery = { match_all: {} }, fromDate, toDate, @@ -87,16 +97,14 @@ async function fetchFieldExistence({ }: { indexPatternId: string; context: RequestHandlerContext; + indexPatternsService: IndexPatternsService; dslQuery: object; fromDate?: string; toDate?: string; timeFieldName?: string; }) { const metaFields: string[] = await context.core.uiSettings.client.get(UI_SETTINGS.META_FIELDS); - const { indexPattern, indexPatternTitle } = await fetchIndexPatternDefinition( - indexPatternId, - context - ); + const indexPattern = await indexPatternsService.get(indexPatternId); const fields = buildFieldList(indexPattern, metaFields); const docs = await fetchIndexPatternStats({ @@ -104,51 +112,32 @@ async function fetchFieldExistence({ toDate, dslQuery, client: context.core.elasticsearch.legacy.client, - index: indexPatternTitle, - timeFieldName: timeFieldName || indexPattern.attributes.timeFieldName, + index: indexPattern.title, + timeFieldName: timeFieldName || indexPattern.timeFieldName, fields, }); return { - indexPatternTitle, + indexPatternTitle: indexPattern.title, existingFieldNames: existingFields(docs, fields), }; } -async function fetchIndexPatternDefinition(indexPatternId: string, context: RequestHandlerContext) { - const savedObjectsClient = context.core.savedObjects.client; - const indexPattern = await savedObjectsClient.get( - 'index-pattern', - indexPatternId - ); - const indexPatternTitle = indexPattern.attributes.title; - - return { - indexPattern, - indexPatternTitle, - }; -} - /** * Exported only for unit tests. */ -export function buildFieldList( - indexPattern: SavedObject, - metaFields: string[] -): Field[] { - return JSON.parse(indexPattern.attributes.fields).map( - (field: { name: string; lang: string; scripted?: boolean; script?: string }) => { - return { - name: field.name, - isScript: !!field.scripted, - lang: field.lang, - script: field.script, - // id is a special case - it doesn't show up in the meta field list, - // but as it's not part of source, it has to be handled separately. - isMeta: metaFields.includes(field.name) || field.name === '_id', - }; - } - ); +export function buildFieldList(indexPattern: IndexPattern, metaFields: string[]): Field[] { + return indexPattern.fields.map((field) => { + return { + name: field.name, + isScript: !!field.scripted, + lang: field.lang, + script: field.script, + // id is a special case - it doesn't show up in the meta field list, + // but as it's not part of source, it has to be handled separately. + isMeta: metaFields.includes(field.name) || field.name === '_id', + }; + }); } async function fetchIndexPatternStats({ diff --git a/x-pack/plugins/lens/server/routes/field_stats.ts b/x-pack/plugins/lens/server/routes/field_stats.ts index 29e2416b74618b..e0f1e05ed970d9 100644 --- a/x-pack/plugins/lens/server/routes/field_stats.ts +++ b/x-pack/plugins/lens/server/routes/field_stats.ts @@ -11,10 +11,11 @@ import { CoreSetup } from 'src/core/server'; import { IFieldType } from 'src/plugins/data/common'; import { ESSearchResponse } from '../../../../typings/elasticsearch'; import { FieldStatsResponse, BASE_API_URL } from '../../common'; +import { PluginStartContract } from '../plugin'; const SHARD_SIZE = 5000; -export async function initFieldsRoute(setup: CoreSetup) { +export async function initFieldsRoute(setup: CoreSetup) { const router = setup.http.createRouter(); router.post( { diff --git a/x-pack/plugins/lens/server/routes/index.ts b/x-pack/plugins/lens/server/routes/index.ts index 01018d8cd7fe53..b1d7e7ca8bc170 100644 --- a/x-pack/plugins/lens/server/routes/index.ts +++ b/x-pack/plugins/lens/server/routes/index.ts @@ -5,11 +5,12 @@ */ import { CoreSetup, Logger } from 'src/core/server'; +import { PluginStartContract } from '../plugin'; import { existingFieldsRoute } from './existing_fields'; import { initFieldsRoute } from './field_stats'; import { initLensUsageRoute } from './telemetry'; -export function setupRoutes(setup: CoreSetup, logger: Logger) { +export function setupRoutes(setup: CoreSetup, logger: Logger) { existingFieldsRoute(setup, logger); initFieldsRoute(setup); initLensUsageRoute(setup); diff --git a/x-pack/plugins/lens/server/routes/telemetry.ts b/x-pack/plugins/lens/server/routes/telemetry.ts index 306c631cd78a7a..820e32509923e3 100644 --- a/x-pack/plugins/lens/server/routes/telemetry.ts +++ b/x-pack/plugins/lens/server/routes/telemetry.ts @@ -8,10 +8,11 @@ import Boom from '@hapi/boom'; import { CoreSetup } from 'src/core/server'; import { schema } from '@kbn/config-schema'; import { BASE_API_URL } from '../../common'; +import { PluginStartContract } from '../plugin'; // This route is responsible for taking a batch of click events from the browser // and writing them to saved objects -export async function initLensUsageRoute(setup: CoreSetup) { +export async function initLensUsageRoute(setup: CoreSetup) { const router = setup.http.createRouter(); router.post( { diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index d31f6ee626245b..1c0645ae797ece 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -487,8 +487,14 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye return {}; } + const { docValueFields } = getDocValueAndSourceFields( + indexPattern, + this._getTooltipPropertyNames() + ); + + const initialSearchContext = { docvalue_fields: docValueFields }; // Request fields in docvalue_fields insted of _source const searchService = getSearchService(); - const searchSource = searchService.searchSource.createEmpty(); + const searchSource = await searchService.searchSource.create(initialSearchContext as object); searchSource.setField('index', indexPattern); searchSource.setField('size', 1); diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts b/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts index b7b6aa15ffe444..543e6898193a48 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts @@ -30,7 +30,7 @@ export function getDefaultDatafeedQuery() { export function createSearchItems( kibanaConfig: IUiSettingsClient, - indexPattern: IIndexPattern, + indexPattern: IIndexPattern | undefined, savedSearch: SavedSearchSavedObject | null ) { // query is only used by the data visualizer as it needs diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx index e436ff468ccf04..771123532dcbf8 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx @@ -38,7 +38,7 @@ export const analyticsJobExplorationRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver('', undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); const [globalState] = useUrlState('_g'); diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx index 80706a82121d51..3b68c5078e99e0 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_jobs_list.tsx @@ -34,7 +34,7 @@ export const analyticsJobsListRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver('', undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx index 18002648cfaa6c..3acd12402932fd 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_map.tsx @@ -34,7 +34,7 @@ export const analyticsMapRouteFactory = ( }); const PageWrapper: FC = ({ deps }) => { - const { context } = useResolver('', undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx index b1fd6e93a744c8..2f58ef756e51c5 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/data_frame_analytics/models_list.tsx @@ -34,7 +34,7 @@ export const modelsListRouteFactory = ( }); const PageWrapper: FC = ({ location, deps }) => { - const { context } = useResolver('', undefined, deps.config, basicResolvers(deps)); + const { context } = useResolver(undefined, undefined, deps.config, basicResolvers(deps)); return ( diff --git a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx index 837616a8a76d2a..f651d17e02de4e 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/datavisualizer/file_based.tsx @@ -45,7 +45,7 @@ export const fileBasedRouteFactory = ( const PageWrapper: FC = ({ location, deps }) => { const { redirectToMlAccessDeniedPage } = deps; - const { context } = useResolver('', undefined, deps.config, { + const { context } = useResolver(undefined, undefined, deps.config, { checkBasicLicense, loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns), checkFindFileStructurePrivilege: () => diff --git a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index df92c772525659..7de59cba495af8 100644 --- a/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -63,7 +63,7 @@ export const timeSeriesExplorerRouteFactory = ( }); const PageWrapper: FC = ({ deps }) => { - const { context, results } = useResolver('', undefined, deps.config, { + const { context, results } = useResolver(undefined, undefined, deps.config, { ...basicResolvers(deps), jobs: mlJobService.loadJobsWrapper, jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts new file mode 100644 index 00000000000000..07cc0385387451 --- /dev/null +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.test.ts @@ -0,0 +1,89 @@ +/* + * 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 { renderHook, act } from '@testing-library/react-hooks'; + +import { IUiSettingsClient } from 'kibana/public'; + +import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url'; +import { useNotifications } from '../contexts/kibana'; + +import { useResolver } from './use_resolver'; + +jest.mock('../contexts/kibana/use_create_url', () => { + return { + useCreateAndNavigateToMlLink: jest.fn(), + }; +}); + +jest.mock('../contexts/kibana', () => { + return { + useMlUrlGenerator: () => ({ + createUrl: jest.fn(), + }), + useNavigateToPath: () => jest.fn(), + useNotifications: jest.fn(), + }; +}); + +const addError = jest.fn(); +(useNotifications as jest.Mock).mockImplementation(() => ({ + toasts: { addSuccess: jest.fn(), addDanger: jest.fn(), addError }, +})); + +const redirectToJobsManagementPage = jest.fn(() => Promise.resolve()); +(useCreateAndNavigateToMlLink as jest.Mock).mockImplementation(() => redirectToJobsManagementPage); + +describe('useResolver', () => { + afterEach(() => { + jest.useFakeTimers(); + }); + afterEach(() => { + jest.advanceTimersByTime(0); + jest.useRealTimers(); + }); + + it('should accept undefined as indexPatternId and savedSearchId.', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useResolver(undefined, undefined, {} as IUiSettingsClient, {}) + ); + + await act(async () => { + await waitForNextUpdate(); + }); + + expect(result.current).toStrictEqual({ + context: { + combinedQuery: { + bool: { + must: [ + { + match_all: {}, + }, + ], + }, + }, + currentIndexPattern: null, + currentSavedSearch: null, + indexPatterns: null, + kibanaConfig: {}, + }, + results: {}, + }); + expect(addError).toHaveBeenCalledTimes(0); + expect(redirectToJobsManagementPage).toHaveBeenCalledTimes(0); + }); + + it('should add an error toast and redirect if indexPatternId is an empty string.', async () => { + const { result } = renderHook(() => useResolver('', undefined, {} as IUiSettingsClient, {})); + + await act(async () => {}); + + expect(result.current).toStrictEqual({ context: null, results: {} }); + expect(addError).toHaveBeenCalledTimes(1); + expect(redirectToJobsManagementPage).toHaveBeenCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/ml/public/application/routing/use_resolver.ts b/x-pack/plugins/ml/public/application/routing/use_resolver.ts index e4cd90145bee40..3ce23f8b8a19c7 100644 --- a/x-pack/plugins/ml/public/application/routing/use_resolver.ts +++ b/x-pack/plugins/ml/public/application/routing/use_resolver.ts @@ -11,6 +11,7 @@ import { getIndexPatternById, getIndexPatternsContract, getIndexPatternAndSavedSearch, + IndexPatternAndSavedSearch, } from '../util/index_utils'; import { createSearchItems } from '../jobs/new_job/utils/new_job_utils'; import { ResolverResults, Resolvers } from './resolvers'; @@ -19,6 +20,14 @@ import { useNotifications } from '../contexts/kibana'; import { useCreateAndNavigateToMlLink } from '../contexts/kibana/use_create_url'; import { ML_PAGES } from '../../../common/constants/ml_url_generator'; +/** + * Hook to resolve route specific requirements + * @param indexPatternId optional Kibana index pattern id, used for wizards + * @param savedSearchId optional Kibana saved search id, used for wizards + * @param config Kibana UI Settings + * @param resolvers an array of resolvers to be executed for the route + * @return { context, results } returns the ML context and resolver results + */ export const useResolver = ( indexPatternId: string | undefined, savedSearchId: string | undefined, @@ -52,36 +61,49 @@ export const useResolver = ( return; } - if (indexPatternId !== undefined || savedSearchId !== undefined) { - try { - // note, currently we're using our own kibana context that requires a current index pattern to be set - // this means, if the page uses this context, useResolver must be passed a string for the index pattern id - // and loadIndexPatterns must be part of the resolvers. - const { indexPattern, savedSearch } = - savedSearchId !== undefined - ? await getIndexPatternAndSavedSearch(savedSearchId) - : { savedSearch: null, indexPattern: await getIndexPatternById(indexPatternId!) }; + try { + if (indexPatternId === '') { + throw new Error( + i18n.translate('xpack.ml.useResolver.errorIndexPatternIdEmptyString', { + defaultMessage: 'indexPatternId must not be empty string.', + }) + ); + } - const { combinedQuery } = createSearchItems(config, indexPattern!, savedSearch); + let indexPatternAndSavedSearch: IndexPatternAndSavedSearch = { + savedSearch: null, + indexPattern: null, + }; - setContext({ - combinedQuery, - currentIndexPattern: indexPattern, - currentSavedSearch: savedSearch, - indexPatterns: getIndexPatternsContract()!, - kibanaConfig: config, - }); - } catch (error) { - // an unexpected error has occurred. This could be caused by an incorrect index pattern or saved search ID - notifications.toasts.addError(new Error(error), { - title: i18n.translate('xpack.ml.useResolver.errorTitle', { - defaultMessage: 'An error has occurred', - }), - }); - await redirectToJobsManagementPage(); + if (savedSearchId !== undefined) { + indexPatternAndSavedSearch = await getIndexPatternAndSavedSearch(savedSearchId); + } else if (indexPatternId !== undefined) { + indexPatternAndSavedSearch.indexPattern = await getIndexPatternById(indexPatternId); } - } else { - setContext({}); + + const { savedSearch, indexPattern } = indexPatternAndSavedSearch; + + const { combinedQuery } = createSearchItems( + config, + indexPattern !== null ? indexPattern : undefined, + savedSearch + ); + + setContext({ + combinedQuery, + currentIndexPattern: indexPattern, + currentSavedSearch: savedSearch, + indexPatterns: getIndexPatternsContract(), + kibanaConfig: config, + }); + } catch (error) { + // an unexpected error has occurred. This could be caused by an incorrect index pattern or saved search ID + notifications.toasts.addError(new Error(error), { + title: i18n.translate('xpack.ml.useResolver.errorTitle', { + defaultMessage: 'An error has occurred', + }), + }); + await redirectToJobsManagementPage(); } })(); }, []); diff --git a/x-pack/plugins/ml/public/application/util/index_utils.ts b/x-pack/plugins/ml/public/application/util/index_utils.ts index 42be3dd8252f95..de08553af9906c 100644 --- a/x-pack/plugins/ml/public/application/util/index_utils.ts +++ b/x-pack/plugins/ml/public/application/util/index_utils.ts @@ -73,9 +73,12 @@ export function getIndexPatternIdFromName(name: string) { } return null; } - +export interface IndexPatternAndSavedSearch { + savedSearch: SavedSearchSavedObject | null; + indexPattern: IIndexPattern | null; +} export async function getIndexPatternAndSavedSearch(savedSearchId: string) { - const resp: { savedSearch: SavedSearchSavedObject | null; indexPattern: IIndexPattern | null } = { + const resp: IndexPatternAndSavedSearch = { savedSearch: null, indexPattern: null, }; diff --git a/x-pack/plugins/monitoring/public/views/base_controller.js b/x-pack/plugins/monitoring/public/views/base_controller.js index 0eb40c8dd5963d..62c15f0913569d 100644 --- a/x-pack/plugins/monitoring/public/views/base_controller.js +++ b/x-pack/plugins/monitoring/public/views/base_controller.js @@ -163,12 +163,12 @@ export class MonitoringViewBaseController { if (isSetupModeFeatureEnabled(SetupModeFeature.MetricbeatMigration)) { promises.push(updateSetupModeData()); } - this.updateDataPromise = new PromiseWithCancel(Promise.all(promises)); + this.updateDataPromise = new PromiseWithCancel(Promise.allSettled(promises)); return this.updateDataPromise.promise().then(([pageData, alerts]) => { $scope.$apply(() => { this._isDataInitialized = true; // render will replace loading screen with the react component - $scope.pageData = this.data = pageData; // update the view's data with the fetch result - $scope.alerts = this.alerts = alerts; + $scope.pageData = this.data = pageData.value; // update the view's data with the fetch result + $scope.alerts = this.alerts = alerts.value || {}; }); }); }; diff --git a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js index ddc33a4b937300..b676abd3de2dd6 100644 --- a/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js +++ b/x-pack/plugins/monitoring/server/lib/cluster/get_clusters_from_request.js @@ -151,20 +151,32 @@ export async function getClustersFromRequest( 'production' ); if (prodLicenseInfo.clusterAlerts.enabled) { - cluster.alerts = { - list: await fetchStatus( - alertsClient, - req.server.plugins.monitoring.info, - undefined, - cluster.cluster_uuid, - start, - end, - [] - ), - alertsMeta: { - enabled: true, - }, - }; + try { + cluster.alerts = { + list: await fetchStatus( + alertsClient, + req.server.plugins.monitoring.info, + undefined, + cluster.cluster_uuid, + start, + end, + [] + ), + alertsMeta: { + enabled: true, + }, + }; + } catch (err) { + req.logger.warn( + `Unable to fetch alert status because '${err.message}'. Alerts may not properly show up in the UI.` + ); + cluster.alerts = { + list: {}, + alertsMeta: { + enabled: true, + }, + }; + } continue; } diff --git a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts index 7b1b877c51278d..792389485164da 100644 --- a/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts +++ b/x-pack/plugins/monitoring/server/telemetry_collection/get_licenses.ts @@ -5,8 +5,8 @@ */ import { SearchResponse } from 'elasticsearch'; -import { ESLicense } from 'src/plugins/telemetry_collection_manager/server'; import { LegacyAPICaller } from 'kibana/server'; +import { ESLicense } from '../../../telemetry_collection_xpack/server'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../common/constants'; /** diff --git a/x-pack/plugins/security/common/constants.ts b/x-pack/plugins/security/common/constants.ts index 07e6ab6c72cb94..f53b5ca6d56ca0 100644 --- a/x-pack/plugins/security/common/constants.ts +++ b/x-pack/plugins/security/common/constants.ts @@ -19,3 +19,6 @@ export const APPLICATION_PREFIX = 'kibana-'; export const RESERVED_PRIVILEGES_APPLICATION_WILDCARD = 'kibana-*'; export const AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER = 'auth_provider_hint'; +export const LOGOUT_PROVIDER_QUERY_STRING_PARAMETER = 'provider'; +export const LOGOUT_REASON_QUERY_STRING_PARAMETER = 'msg'; +export const NEXT_URL_QUERY_STRING_PARAMETER = 'next'; diff --git a/x-pack/plugins/security/common/model/authenticated_user.ts b/x-pack/plugins/security/common/model/authenticated_user.ts index c22c5fc4ef0dad..491ceb6845e28a 100644 --- a/x-pack/plugins/security/common/model/authenticated_user.ts +++ b/x-pack/plugins/security/common/model/authenticated_user.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import type { AuthenticationProvider } from '../types'; -import { User } from './user'; +import type { AuthenticationProvider, User } from '.'; const REALMS_ELIGIBLE_FOR_PASSWORD_CHANGE = ['reserved', 'native']; diff --git a/x-pack/plugins/security/common/model/authentication_provider.test.ts b/x-pack/plugins/security/common/model/authentication_provider.test.ts new file mode 100644 index 00000000000000..fc32d3108be085 --- /dev/null +++ b/x-pack/plugins/security/common/model/authentication_provider.test.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 { shouldProviderUseLoginForm } from './authentication_provider'; + +describe('#shouldProviderUseLoginForm', () => { + ['basic', 'token'].forEach((providerType) => { + it(`returns "true" for "${providerType}" provider`, () => { + expect(shouldProviderUseLoginForm(providerType)).toEqual(true); + }); + }); + + ['anonymous', 'http', 'kerberos', 'oidc', 'pki', 'saml'].forEach((providerType) => { + it(`returns "false" for "${providerType}" provider`, () => { + expect(shouldProviderUseLoginForm(providerType)).toEqual(false); + }); + }); +}); diff --git a/x-pack/plugins/security/common/model/authentication_provider.ts b/x-pack/plugins/security/common/model/authentication_provider.ts new file mode 100644 index 00000000000000..1b34fbc9da29a9 --- /dev/null +++ b/x-pack/plugins/security/common/model/authentication_provider.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. + */ + +/** + * Type and name tuple to identify provider used to authenticate user. + */ +export interface AuthenticationProvider { + type: string; + name: string; +} + +/** + * Checks whether authentication provider with the specified type uses Kibana's native login form. + * @param providerType Type of the authentication provider. + */ +export function shouldProviderUseLoginForm(providerType: string) { + return providerType === 'basic' || providerType === 'token'; +} diff --git a/x-pack/plugins/security/common/model/index.ts b/x-pack/plugins/security/common/model/index.ts index 59d4908c67ffb1..ee1dcffd4a7943 100644 --- a/x-pack/plugins/security/common/model/index.ts +++ b/x-pack/plugins/security/common/model/index.ts @@ -7,6 +7,7 @@ export { ApiKey, ApiKeyToInvalidate } from './api_key'; export { User, EditUser, getUserDisplayName } from './user'; export { AuthenticatedUser, canUserChangePassword } from './authenticated_user'; +export { AuthenticationProvider, shouldProviderUseLoginForm } from './authentication_provider'; export { BuiltinESPrivileges } from './builtin_es_privileges'; export { RawKibanaPrivileges, RawKibanaFeaturePrivileges } from './raw_kibana_privileges'; export { FeaturesPrivileges } from './features_privileges'; diff --git a/x-pack/plugins/security/common/parse_next.ts b/x-pack/plugins/security/common/parse_next.ts index 7ce0de05ad526d..68903baeb05b82 100644 --- a/x-pack/plugins/security/common/parse_next.ts +++ b/x-pack/plugins/security/common/parse_next.ts @@ -5,19 +5,21 @@ */ import { parse } from 'url'; +import { NEXT_URL_QUERY_STRING_PARAMETER } from './constants'; import { isInternalURL } from './is_internal_url'; export function parseNext(href: string, basePath = '') { const { query, hash } = parse(href, true); - if (!query.next) { + + let next = query[NEXT_URL_QUERY_STRING_PARAMETER]; + if (!next) { return `${basePath}/`; } - let next: string; - if (Array.isArray(query.next) && query.next.length > 0) { - next = query.next[0]; + if (Array.isArray(next) && next.length > 0) { + next = next[0]; } else { - next = query.next as string; + next = next as string; } // validate that `next` is not attempting a redirect to somewhere diff --git a/x-pack/plugins/security/common/types.ts b/x-pack/plugins/security/common/types.ts index c668c6ccf71d16..33e2875acefefa 100644 --- a/x-pack/plugins/security/common/types.ts +++ b/x-pack/plugins/security/common/types.ts @@ -4,13 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -/** - * Type and name tuple to identify provider used to authenticate user. - */ -export interface AuthenticationProvider { - type: string; - name: string; -} +import type { AuthenticationProvider } from './model'; export interface SessionInfo { now: number; diff --git a/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.test.tsx b/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.test.tsx new file mode 100644 index 00000000000000..89d622e086b380 --- /dev/null +++ b/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.test.tsx @@ -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 React from 'react'; +import { EuiButton } from '@elastic/eui'; +import { mountWithIntl } from '@kbn/test/jest'; +import { LoggedOutPage } from './logged_out_page'; + +import { coreMock } from '../../../../../../src/core/public/mocks'; + +describe('LoggedOutPage', () => { + beforeAll(() => { + Object.defineProperty(window, 'location', { + value: { href: 'https://some-host' }, + writable: true, + }); + }); + + it('points to a base path if `next` parameter is not provided', async () => { + const basePathMock = coreMock.createStart({ basePath: '/mock-base-path' }).http.basePath; + const wrapper = mountWithIntl(); + + expect(wrapper.find(EuiButton).prop('href')).toBe('/mock-base-path/'); + }); + + it('properly parses `next` parameter', async () => { + window.location.href = `https://host.com/mock-base-path/security/logged_out?next=${encodeURIComponent( + '/mock-base-path/app/home#/?_g=()' + )}`; + + const basePathMock = coreMock.createStart({ basePath: '/mock-base-path' }).http.basePath; + const wrapper = mountWithIntl(); + + expect(wrapper.find(EuiButton).prop('href')).toBe('/mock-base-path/app/home#/?_g=()'); + }); +}); diff --git a/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.tsx b/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.tsx index a708931c3fa95e..5498b8ef3644c3 100644 --- a/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.tsx +++ b/x-pack/plugins/security/public/authentication/logged_out/logged_out_page.tsx @@ -9,6 +9,7 @@ import ReactDOM from 'react-dom'; import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { CoreStart, IBasePath } from 'src/core/public'; +import { parseNext } from '../../../common/parse_next'; import { AuthenticationStatePage } from '../components'; interface Props { @@ -25,7 +26,7 @@ export function LoggedOutPage({ basePath }: Props) { /> } > - + diff --git a/x-pack/plugins/security/public/authentication/login/login_page.tsx b/x-pack/plugins/security/public/authentication/login/login_page.tsx index 06469626842848..35703212762fd2 100644 --- a/x-pack/plugins/security/public/authentication/login/login_page.tsx +++ b/x-pack/plugins/security/public/authentication/login/login_page.tsx @@ -15,7 +15,10 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiSpacer, EuiTitle } from '@elasti import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { CoreStart, FatalErrorsStart, HttpStart, NotificationsStart } from 'src/core/public'; -import { AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER } from '../../../common/constants'; +import { + AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, + LOGOUT_REASON_QUERY_STRING_PARAMETER, +} from '../../../common/constants'; import { LoginState } from '../../../common/login_state'; import { LoginForm, DisabledLoginForm } from './components'; @@ -219,7 +222,7 @@ export class LoginPage extends Component { http={this.props.http} notifications={this.props.notifications} selector={selector} - infoMessage={infoMessageMap.get(query.msg?.toString())} + infoMessage={infoMessageMap.get(query[LOGOUT_REASON_QUERY_STRING_PARAMETER]?.toString())} loginAssistanceMessage={this.props.loginAssistanceMessage} loginHelp={loginHelp} authProviderHint={query[AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER]?.toString()} diff --git a/x-pack/plugins/security/public/session/session_expired.ts b/x-pack/plugins/security/public/session/session_expired.ts index 5866526b8851ef..52ba37c242d09e 100644 --- a/x-pack/plugins/security/public/session/session_expired.ts +++ b/x-pack/plugins/security/public/session/session_expired.ts @@ -4,6 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + LOGOUT_PROVIDER_QUERY_STRING_PARAMETER, + LOGOUT_REASON_QUERY_STRING_PARAMETER, + NEXT_URL_QUERY_STRING_PARAMETER, +} from '../../common/constants'; + export interface ISessionExpired { logout(): void; } @@ -11,13 +17,15 @@ export interface ISessionExpired { const getNextParameter = () => { const { location } = window; const next = encodeURIComponent(`${location.pathname}${location.search}${location.hash}`); - return `&next=${next}`; + return `&${NEXT_URL_QUERY_STRING_PARAMETER}=${next}`; }; const getProviderParameter = (tenant: string) => { const key = `${tenant}/session_provider`; const providerName = sessionStorage.getItem(key); - return providerName ? `&provider=${encodeURIComponent(providerName)}` : ''; + return providerName + ? `&${LOGOUT_PROVIDER_QUERY_STRING_PARAMETER}=${encodeURIComponent(providerName)}` + : ''; }; export class SessionExpired { @@ -26,6 +34,8 @@ export class SessionExpired { logout() { const next = getNextParameter(); const provider = getProviderParameter(this.tenant); - window.location.assign(`${this.logoutUrl}?msg=SESSION_EXPIRED${next}${provider}`); + window.location.assign( + `${this.logoutUrl}?${LOGOUT_REASON_QUERY_STRING_PARAMETER}=SESSION_EXPIRED${next}${provider}` + ); } } diff --git a/x-pack/plugins/security/server/audit/security_audit_logger.ts b/x-pack/plugins/security/server/audit/security_audit_logger.ts index ee81f5f330f440..c0d431b3c2fa2d 100644 --- a/x-pack/plugins/security/server/audit/security_audit_logger.ts +++ b/x-pack/plugins/security/server/audit/security_audit_logger.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AuthenticationProvider } from '../../common/types'; +import type { AuthenticationProvider } from '../../common/model'; import { LegacyAuditLogger } from './audit_service'; /** diff --git a/x-pack/plugins/security/server/authentication/authenticator.test.ts b/x-pack/plugins/security/server/authentication/authenticator.test.ts index 16cb51cbfccf5b..ed5d05dbcf619c 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.test.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.test.ts @@ -111,31 +111,78 @@ describe('Authenticator', () => { ).toThrowError('Provider name "__http__" is reserved.'); }); - it('properly sets `loggedOut` URL.', () => { - const basicAuthenticationProviderMock = jest.requireMock('./providers/basic') - .BasicAuthenticationProvider; + describe('#options.urls.loggedOut', () => { + it('points to /login if provider requires login form', () => { + const authenticationProviderMock = jest.requireMock(`./providers/basic`) + .BasicAuthenticationProvider; + authenticationProviderMock.mockClear(); + new Authenticator(getMockOptions()); + const getLoggedOutURL = authenticationProviderMock.mock.calls[0][0].urls.loggedOut; - basicAuthenticationProviderMock.mockClear(); - new Authenticator(getMockOptions()); - expect(basicAuthenticationProviderMock).toHaveBeenCalledWith( - expect.objectContaining({ - urls: { - loggedOut: '/mock-server-basepath/security/logged_out', - }, - }), - expect.anything() - ); + expect(getLoggedOutURL(httpServerMock.createKibanaRequest())).toBe( + '/mock-server-basepath/login?msg=LOGGED_OUT' + ); - basicAuthenticationProviderMock.mockClear(); - new Authenticator(getMockOptions({ selector: { enabled: true } })); - expect(basicAuthenticationProviderMock).toHaveBeenCalledWith( - expect.objectContaining({ - urls: { - loggedOut: `/mock-server-basepath/login?msg=LOGGED_OUT`, - }, - }), - expect.anything() - ); + expect( + getLoggedOutURL( + httpServerMock.createKibanaRequest({ + query: { next: '/app/ml/encode me', msg: 'SESSION_EXPIRED' }, + }) + ) + ).toBe('/mock-server-basepath/login?next=%2Fapp%2Fml%2Fencode+me&msg=SESSION_EXPIRED'); + }); + + it('points to /login if login selector is enabled', () => { + const authenticationProviderMock = jest.requireMock(`./providers/saml`) + .SAMLAuthenticationProvider; + authenticationProviderMock.mockClear(); + new Authenticator( + getMockOptions({ + selector: { enabled: true }, + providers: { saml: { saml1: { order: 0, realm: 'realm' } } }, + }) + ); + const getLoggedOutURL = authenticationProviderMock.mock.calls[0][0].urls.loggedOut; + + expect(getLoggedOutURL(httpServerMock.createKibanaRequest())).toBe( + '/mock-server-basepath/login?msg=LOGGED_OUT' + ); + + expect( + getLoggedOutURL( + httpServerMock.createKibanaRequest({ + query: { next: '/app/ml/encode me', msg: 'SESSION_EXPIRED' }, + }) + ) + ).toBe('/mock-server-basepath/login?next=%2Fapp%2Fml%2Fencode+me&msg=SESSION_EXPIRED'); + }); + + it('points to /security/logged_out if login selector is NOT enabled', () => { + const authenticationProviderMock = jest.requireMock(`./providers/saml`) + .SAMLAuthenticationProvider; + authenticationProviderMock.mockClear(); + new Authenticator( + getMockOptions({ + selector: { enabled: false }, + providers: { saml: { saml1: { order: 0, realm: 'realm' } } }, + }) + ); + const getLoggedOutURL = authenticationProviderMock.mock.calls[0][0].urls.loggedOut; + + expect(getLoggedOutURL(httpServerMock.createKibanaRequest())).toBe( + '/mock-server-basepath/security/logged_out?msg=LOGGED_OUT' + ); + + expect( + getLoggedOutURL( + httpServerMock.createKibanaRequest({ + query: { next: '/app/ml/encode me', msg: 'SESSION_EXPIRED' }, + }) + ) + ).toBe( + '/mock-server-basepath/security/logged_out?next=%2Fapp%2Fml%2Fencode+me&msg=SESSION_EXPIRED' + ); + }); }); describe('HTTP authentication provider', () => { @@ -1769,7 +1816,9 @@ describe('Authenticator', () => { }); it('if session does not exist but provider name is valid, returns whatever authentication provider returns.', async () => { - const request = httpServerMock.createKibanaRequest({ query: { provider: 'basic1' } }); + const request = httpServerMock.createKibanaRequest({ + query: { provider: 'basic1' }, + }); mockOptions.session.get.mockResolvedValue(null); mockBasicAuthenticationProvider.logout.mockResolvedValue( @@ -1782,7 +1831,7 @@ describe('Authenticator', () => { expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledTimes(1); expect(mockBasicAuthenticationProvider.logout).toHaveBeenCalledWith(request, null); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.clear).toHaveBeenCalled(); }); it('if session does not exist and provider name is not available, returns whatever authentication provider returns.', async () => { @@ -1811,7 +1860,7 @@ describe('Authenticator', () => { ); expect(mockBasicAuthenticationProvider.logout).not.toHaveBeenCalled(); - expect(mockOptions.session.clear).not.toHaveBeenCalled(); + expect(mockOptions.session.clear).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/security/server/authentication/authenticator.ts b/x-pack/plugins/security/server/authentication/authenticator.ts index 718415e4857251..f175f47d303518 100644 --- a/x-pack/plugins/security/server/authentication/authenticator.ts +++ b/x-pack/plugins/security/server/authentication/authenticator.ts @@ -10,10 +10,15 @@ import { ILegacyClusterClient, IBasePath, } from '../../../../../src/core/server'; -import { AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER } from '../../common/constants'; +import { + AUTH_PROVIDER_HINT_QUERY_STRING_PARAMETER, + LOGOUT_PROVIDER_QUERY_STRING_PARAMETER, + LOGOUT_REASON_QUERY_STRING_PARAMETER, + NEXT_URL_QUERY_STRING_PARAMETER, +} from '../../common/constants'; import type { SecurityLicense } from '../../common/licensing'; -import type { AuthenticatedUser } from '../../common/model'; -import type { AuthenticationProvider } from '../../common/types'; +import type { AuthenticatedUser, AuthenticationProvider } from '../../common/model'; +import { shouldProviderUseLoginForm } from '../../common/model'; import { SecurityAuditLogger, AuditServiceSetup, userLoginEvent } from '../audit'; import type { ConfigType } from '../config'; import { getErrorStatusCode } from '../errors'; @@ -199,11 +204,6 @@ export class Authenticator { client: this.options.clusterClient, logger: this.options.loggers.get('tokens'), }), - urls: { - loggedOut: options.config.authc.selector.enabled - ? `${options.basePath.serverBasePath}/login?msg=LOGGED_OUT` - : `${options.basePath.serverBasePath}/security/logged_out`, - }, }; this.providers = new Map( @@ -218,6 +218,7 @@ export class Authenticator { ...providerCommonOptions, name, logger: options.loggers.get(type, name), + urls: { loggedOut: (request) => this.getLoggedOutURL(request, type) }, }), this.options.config.authc.providers[type]?.[name] ), @@ -232,6 +233,9 @@ export class Authenticator { ...providerCommonOptions, name: '__http__', logger: options.loggers.get(HTTPAuthenticationProvider.type), + urls: { + loggedOut: (request) => this.getLoggedOutURL(request, HTTPAuthenticationProvider.type), + }, }) ); } @@ -338,7 +342,9 @@ export class Authenticator { if (this.shouldRedirectToLoginSelector(request, existingSessionValue)) { this.logger.debug('Redirecting request to Login Selector.'); return AuthenticationResult.redirectTo( - `${this.options.basePath.serverBasePath}/login?next=${encodeURIComponent( + `${ + this.options.basePath.serverBasePath + }/login?${NEXT_URL_QUERY_STRING_PARAMETER}=${encodeURIComponent( `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` )}${ suggestedProviderName && !existingSessionValue @@ -385,20 +391,17 @@ export class Authenticator { assertRequest(request); const sessionValue = await this.getSessionValue(request); - if (sessionValue) { + const suggestedProviderName = + sessionValue?.provider.name ?? + request.url.searchParams.get(LOGOUT_PROVIDER_QUERY_STRING_PARAMETER); + if (suggestedProviderName) { await this.session.clear(request); - return this.providers - .get(sessionValue.provider.name)! - .logout(request, sessionValue.state ?? null); - } - const queryStringProviderName = (request.query as Record)?.provider; - if (queryStringProviderName) { - // provider name is passed in a query param and sourced from the browser's local storage; - // hence, we can't assume that this provider exists, so we have to check it - const provider = this.providers.get(queryStringProviderName); + // Provider name may be passed in a query param and sourced from the browser's local storage; + // hence, we can't assume that this provider exists, so we have to check it. + const provider = this.providers.get(suggestedProviderName); if (provider) { - return provider.logout(request, null); + return provider.logout(request, sessionValue?.state ?? null); } } else { // In case logout is called and we cannot figure out what provider is supposed to handle it, @@ -737,7 +740,7 @@ export class Authenticator { // redirect URL in the `next` parameter. Redirect URL provided in authentication result, if any, // always takes precedence over what is specified in `redirectURL` parameter. if (preAccessRedirectURL) { - preAccessRedirectURL = `${preAccessRedirectURL}?next=${encodeURIComponent( + preAccessRedirectURL = `${preAccessRedirectURL}?${NEXT_URL_QUERY_STRING_PARAMETER}=${encodeURIComponent( authenticationResult.redirectURL || redirectURL || `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` @@ -754,4 +757,30 @@ export class Authenticator { }) : authenticationResult; } + + /** + * Creates a logged out URL for the specified request and provider. + * @param request Request that initiated logout. + * @param providerType Type of the provider that handles logout. + */ + private getLoggedOutURL(request: KibanaRequest, providerType: string) { + // The app that handles logout needs to know the reason of the logout and the URL we may need to + // redirect user to once they log in again (e.g. when session expires). + const searchParams = new URLSearchParams(); + for (const [key, defaultValue] of [ + [NEXT_URL_QUERY_STRING_PARAMETER, null], + [LOGOUT_REASON_QUERY_STRING_PARAMETER, 'LOGGED_OUT'], + ] as Array<[string, string | null]>) { + const value = request.url.searchParams.get(key) || defaultValue; + if (value) { + searchParams.append(key, value); + } + } + + // Query string may contain the path where logout has been called or + // logout reason that login page may need to know. + return this.options.config.authc.selector.enabled || shouldProviderUseLoginForm(providerType) + ? `${this.options.basePath.serverBasePath}/login?${searchParams.toString()}` + : `${this.options.basePath.serverBasePath}/security/logged_out?${searchParams.toString()}`; + } } diff --git a/x-pack/plugins/security/server/authentication/providers/anonymous.test.ts b/x-pack/plugins/security/server/authentication/providers/anonymous.test.ts index c296cb9c8e94d5..9674181e187501 100644 --- a/x-pack/plugins/security/server/authentication/providers/anonymous.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/anonymous.test.ts @@ -29,32 +29,48 @@ function expectAuthenticateCall( expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.authenticate'); } +enum CredentialsType { + Basic = 'Basic', + ApiKey = 'ApiKey', + None = 'ES native anonymous', +} + describe('AnonymousAuthenticationProvider', () => { const user = mockAuthenticatedUser({ authentication_provider: { type: 'anonymous', name: 'anonymous1' }, }); - for (const useBasicCredentials of [true, false]) { - describe(`with ${useBasicCredentials ? '`Basic`' : '`ApiKey`'} credentials`, () => { + for (const credentialsType of [ + CredentialsType.Basic, + CredentialsType.ApiKey, + CredentialsType.None, + ]) { + describe(`with ${credentialsType} credentials`, () => { let provider: AnonymousAuthenticationProvider; let mockOptions: ReturnType; let authorization: string; beforeEach(() => { mockOptions = mockAuthenticationProviderOptions({ name: 'anonymous1' }); - provider = useBasicCredentials - ? new AnonymousAuthenticationProvider(mockOptions, { - credentials: { username: 'user', password: 'pass' }, - }) - : new AnonymousAuthenticationProvider(mockOptions, { - credentials: { apiKey: 'some-apiKey' }, - }); - authorization = useBasicCredentials - ? new HTTPAuthorizationHeader( + let credentials; + switch (credentialsType) { + case CredentialsType.Basic: + credentials = { username: 'user', password: 'pass' }; + authorization = new HTTPAuthorizationHeader( 'Basic', new BasicHTTPAuthorizationHeaderCredentials('user', 'pass').toString() - ).toString() - : new HTTPAuthorizationHeader('ApiKey', 'some-apiKey').toString(); + ).toString(); + break; + case CredentialsType.ApiKey: + credentials = { apiKey: 'some-apiKey' }; + authorization = new HTTPAuthorizationHeader('ApiKey', 'some-apiKey').toString(); + break; + default: + credentials = 'elasticsearch_anonymous_user' as 'elasticsearch_anonymous_user'; + break; + } + + provider = new AnonymousAuthenticationProvider(mockOptions, { credentials }); }); describe('`login` method', () => { @@ -111,23 +127,29 @@ describe('AnonymousAuthenticationProvider', () => { }); it('does not handle authentication via `authorization` header.', async () => { - const request = httpServerMock.createKibanaRequest({ headers: { authorization } }); + const originalAuthorizationHeader = 'Basic credentials'; + const request = httpServerMock.createKibanaRequest({ + headers: { authorization: originalAuthorizationHeader }, + }); await expect(provider.authenticate(request)).resolves.toEqual( AuthenticationResult.notHandled() ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(request.headers.authorization).toBe(authorization); + expect(request.headers.authorization).toBe(originalAuthorizationHeader); }); it('does not handle authentication via `authorization` header even if state exists.', async () => { - const request = httpServerMock.createKibanaRequest({ headers: { authorization } }); + const originalAuthorizationHeader = 'Basic credentials'; + const request = httpServerMock.createKibanaRequest({ + headers: { authorization: originalAuthorizationHeader }, + }); await expect(provider.authenticate(request, {})).resolves.toEqual( AuthenticationResult.notHandled() ); expect(mockOptions.client.asScoped).not.toHaveBeenCalled(); - expect(request.headers.authorization).toBe(authorization); + expect(request.headers.authorization).toBe(originalAuthorizationHeader); }); it('succeeds for non-AJAX requests if state is available.', async () => { @@ -191,7 +213,7 @@ describe('AnonymousAuthenticationProvider', () => { expect(request.headers).not.toHaveProperty('authorization'); }); - if (!useBasicCredentials) { + if (credentialsType === CredentialsType.ApiKey) { it('properly handles extended format for the ApiKey credentials', async () => { provider = new AnonymousAuthenticationProvider(mockOptions, { credentials: { apiKey: { id: 'some-id', key: 'some-key' } }, @@ -237,9 +259,19 @@ describe('AnonymousAuthenticationProvider', () => { }); it('`getHTTPAuthenticationScheme` method', () => { - expect(provider.getHTTPAuthenticationScheme()).toBe( - useBasicCredentials ? 'basic' : 'apikey' - ); + let expectedAuthenticationScheme; + switch (credentialsType) { + case CredentialsType.Basic: + expectedAuthenticationScheme = 'basic'; + break; + case CredentialsType.ApiKey: + expectedAuthenticationScheme = 'apikey'; + break; + default: + expectedAuthenticationScheme = null; + break; + } + expect(provider.getHTTPAuthenticationScheme()).toBe(expectedAuthenticationScheme); }); }); } diff --git a/x-pack/plugins/security/server/authentication/providers/anonymous.ts b/x-pack/plugins/security/server/authentication/providers/anonymous.ts index 6f02cce371a413..4d1b5f4a74b2ff 100644 --- a/x-pack/plugins/security/server/authentication/providers/anonymous.ts +++ b/x-pack/plugins/security/server/authentication/providers/anonymous.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { KibanaRequest } from '../../../../../../src/core/server'; +import { KibanaRequest, LegacyElasticsearchErrorHelpers } from '../../../../../../src/core/server'; import { AuthenticationResult } from '../authentication_result'; import { canRedirectRequest } from '../can_redirect_request'; import { DeauthenticationResult } from '../deauthentication_result'; @@ -29,6 +29,11 @@ interface APIKeyCredentials { apiKey: { id: string; key: string } | string; } +/** + * Credentials that imply authentication based on the Elasticsearch native anonymous user. + */ +type ElasticsearchAnonymousUserCredentials = 'elasticsearch_anonymous_user'; + /** * Checks whether current request can initiate a new session. * @param request Request instance. @@ -44,7 +49,10 @@ function canStartNewSession(request: KibanaRequest) { * @param credentials */ function isAPIKeyCredentials( - credentials: UsernameAndPasswordCredentials | APIKeyCredentials + credentials: + | ElasticsearchAnonymousUserCredentials + | APIKeyCredentials + | UsernameAndPasswordCredentials ): credentials is APIKeyCredentials { return !!(credentials as APIKeyCredentials).apiKey; } @@ -59,14 +67,17 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider static readonly type = 'anonymous'; /** - * Defines HTTP authorization header that should be used to authenticate request. + * Defines HTTP authorization header that should be used to authenticate request. It isn't defined + * if provider should rely on Elasticsearch native anonymous access. */ - private readonly httpAuthorizationHeader: HTTPAuthorizationHeader; + private readonly httpAuthorizationHeader?: HTTPAuthorizationHeader; constructor( protected readonly options: Readonly, anonymousOptions?: Readonly<{ - credentials?: Readonly; + credentials?: Readonly< + ElasticsearchAnonymousUserCredentials | UsernameAndPasswordCredentials | APIKeyCredentials + >; }> ) { super(options); @@ -76,7 +87,11 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider throw new Error('Credentials must be specified'); } - if (isAPIKeyCredentials(credentials)) { + if (credentials === 'elasticsearch_anonymous_user') { + this.logger.debug( + 'Anonymous requests will be authenticated using Elasticsearch native anonymous user.' + ); + } else if (isAPIKeyCredentials(credentials)) { this.logger.debug('Anonymous requests will be authenticated via API key.'); this.httpAuthorizationHeader = new HTTPAuthorizationHeader( 'ApiKey', @@ -147,7 +162,7 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider return DeauthenticationResult.notHandled(); } - return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** @@ -155,7 +170,7 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider * HTTP header that provider attaches to all successfully authenticated requests to Elasticsearch. */ public getHTTPAuthenticationScheme() { - return this.httpAuthorizationHeader.scheme.toLowerCase(); + return this.httpAuthorizationHeader?.scheme.toLowerCase() ?? null; } /** @@ -164,7 +179,9 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider * @param state State value previously stored by the provider. */ private async authenticateViaAuthorizationHeader(request: KibanaRequest, state?: unknown) { - const authHeaders = { authorization: this.httpAuthorizationHeader.toString() }; + const authHeaders = this.httpAuthorizationHeader + ? { authorization: this.httpAuthorizationHeader.toString() } + : ({} as Record); try { const user = await this.getUser(request, authHeaders); this.logger.debug( @@ -173,7 +190,23 @@ export class AnonymousAuthenticationProvider extends BaseAuthenticationProvider // Create session only if it doesn't exist yet, otherwise keep it unchanged. return AuthenticationResult.succeeded(user, { authHeaders, state: state ? undefined : {} }); } catch (err) { - this.logger.debug(`Failed to authenticate request : ${err.message}`); + if (LegacyElasticsearchErrorHelpers.isNotAuthorizedError(err)) { + if (!this.httpAuthorizationHeader) { + this.logger.error( + `Failed to authenticate anonymous request using Elasticsearch reserved anonymous user. Anonymous access may not be properly configured in Elasticsearch: ${err.message}` + ); + } else if (this.httpAuthorizationHeader.scheme.toLowerCase() === 'basic') { + this.logger.error( + `Failed to authenticate anonymous request using provided username/password credentials. The user with the provided username may not exist or the password is wrong: ${err.message}` + ); + } else { + this.logger.error( + `Failed to authenticate anonymous request using provided API key. The key may not exist or expired: ${err.message}` + ); + } + } else { + this.logger.error(`Failed to authenticate request : ${err.message}`); + } return AuthenticationResult.failed(err); } } diff --git a/x-pack/plugins/security/server/authentication/providers/base.mock.ts b/x-pack/plugins/security/server/authentication/providers/base.mock.ts index 1b574e6e44c100..47d961bc8faf8e 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.mock.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.mock.ts @@ -22,7 +22,7 @@ export function mockAuthenticationProviderOptions(options?: { name: string }) { tokens: { refresh: jest.fn(), invalidate: jest.fn() }, name: options?.name ?? 'basic1', urls: { - loggedOut: '/mock-server-basepath/security/logged_out', + loggedOut: jest.fn().mockReturnValue('/mock-server-basepath/security/logged_out'), }, }; } diff --git a/x-pack/plugins/security/server/authentication/providers/base.ts b/x-pack/plugins/security/server/authentication/providers/base.ts index a5a68f2a493157..f1845617c87a4e 100644 --- a/x-pack/plugins/security/server/authentication/providers/base.ts +++ b/x-pack/plugins/security/server/authentication/providers/base.ts @@ -29,7 +29,7 @@ export interface AuthenticationProviderOptions { logger: Logger; tokens: PublicMethodsOf; urls: { - loggedOut: string; + loggedOut: (request: KibanaRequest) => string; }; } diff --git a/x-pack/plugins/security/server/authentication/providers/basic.test.ts b/x-pack/plugins/security/server/authentication/providers/basic.test.ts index 87002ebed56725..4f93e2327da061 100644 --- a/x-pack/plugins/security/server/authentication/providers/basic.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/basic.test.ts @@ -34,6 +34,8 @@ describe('BasicAuthenticationProvider', () => { let mockOptions: ReturnType; beforeEach(() => { mockOptions = mockAuthenticationProviderOptions(); + mockOptions.urls.loggedOut.mockReturnValue('/some-logged-out-page'); + provider = new BasicAuthenticationProvider(mockOptions); }); @@ -184,30 +186,13 @@ describe('BasicAuthenticationProvider', () => { ); }); - it('redirects to login view if state is `null`.', async () => { - await expect(provider.logout(httpServerMock.createKibanaRequest(), null)).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/login?msg=LOGGED_OUT') - ); - }); - - it('always redirects to the login page.', async () => { + it('redirects to the logged out URL.', async () => { await expect(provider.logout(httpServerMock.createKibanaRequest(), {})).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/login?msg=LOGGED_OUT') + DeauthenticationResult.redirectTo('/some-logged-out-page') ); - }); - it('passes query string parameters to the login page.', async () => { - await expect( - provider.logout( - httpServerMock.createKibanaRequest({ - query: { next: '/app/ml', msg: 'SESSION_EXPIRED' }, - }), - {} - ) - ).resolves.toEqual( - DeauthenticationResult.redirectTo( - '/mock-server-basepath/login?next=%2Fapp%2Fml&msg=SESSION_EXPIRED' - ) + await expect(provider.logout(httpServerMock.createKibanaRequest(), null)).resolves.toEqual( + DeauthenticationResult.redirectTo('/some-logged-out-page') ); }); }); diff --git a/x-pack/plugins/security/server/authentication/providers/basic.ts b/x-pack/plugins/security/server/authentication/providers/basic.ts index 28b671346ee7f6..6a5ae29dfd8321 100644 --- a/x-pack/plugins/security/server/authentication/providers/basic.ts +++ b/x-pack/plugins/security/server/authentication/providers/basic.ts @@ -5,6 +5,7 @@ */ import { KibanaRequest } from '../../../../../../src/core/server'; +import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../../common/constants'; import { canRedirectRequest } from '../can_redirect_request'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; @@ -108,7 +109,7 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider { this.logger.debug('Redirecting request to Login page.'); const basePath = this.options.basePath.get(request); return AuthenticationResult.redirectTo( - `${basePath}/login?next=${encodeURIComponent( + `${basePath}/login?${NEXT_URL_QUERY_STRING_PARAMETER}=${encodeURIComponent( `${basePath}${request.url.pathname}${request.url.search}` )}` ); @@ -131,12 +132,7 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider { return DeauthenticationResult.notHandled(); } - // Query string may contain the path where logout has been called or - // logout reason that login page may need to know. - const queryString = request.url.search || `?msg=LOGGED_OUT`; - return DeauthenticationResult.redirectTo( - `${this.options.basePath.get(request)}/login${queryString}` - ); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts index eb4ac8f4dcbed1..d368bf90cf360e 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.test.ts @@ -470,7 +470,7 @@ describe('KerberosAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); await expect(provider.logout(request, null)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled(); @@ -501,7 +501,7 @@ describe('KerberosAuthenticationProvider', () => { mockOptions.tokens.invalidate.mockResolvedValue(undefined); await expect(provider.logout(request, tokenPair)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/security/server/authentication/providers/kerberos.ts b/x-pack/plugins/security/server/authentication/providers/kerberos.ts index 2e15893d0845f2..9bf419c7dacaae 100644 --- a/x-pack/plugins/security/server/authentication/providers/kerberos.ts +++ b/x-pack/plugins/security/server/authentication/providers/kerberos.ts @@ -124,7 +124,7 @@ export class KerberosAuthenticationProvider extends BaseAuthenticationProvider { } } - return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index 126306c885e53e..9988ddd99c3953 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -611,10 +611,10 @@ describe('OIDCAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); await expect(provider.logout(request, null)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); await expect(provider.logout(request, { nonce: 'x', realm: 'oidc1' })).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); @@ -647,7 +647,7 @@ describe('OIDCAuthenticationProvider', () => { await expect( provider.logout(request, { accessToken, refreshToken, realm: 'oidc1' }) - ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.oidcLogout', { diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.ts b/x-pack/plugins/security/server/authentication/providers/oidc.ts index 250641d1cf1744..c46ea37f144e9f 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.ts @@ -7,6 +7,7 @@ import Boom from '@hapi/boom'; import type from 'type-detect'; import { KibanaRequest } from '../../../../../../src/core/server'; +import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../../common/constants'; import type { AuthenticationInfo } from '../../elasticsearch'; import { AuthenticationResult } from '../authentication_result'; import { canRedirectRequest } from '../can_redirect_request'; @@ -434,7 +435,7 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { } } - return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** @@ -450,14 +451,18 @@ export class OIDCAuthenticationProvider extends BaseAuthenticationProvider { * @param request Request instance. */ private captureRedirectURL(request: KibanaRequest) { + const searchParams = new URLSearchParams([ + [ + NEXT_URL_QUERY_STRING_PARAMETER, + `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}`, + ], + ['providerType', this.type], + ['providerName', this.options.name], + ]); return AuthenticationResult.redirectTo( `${ this.options.basePath.serverBasePath - }/internal/security/capture-url?next=${encodeURIComponent( - `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` - )}&providerType=${encodeURIComponent(this.type)}&providerName=${encodeURIComponent( - this.options.name - )}`, + }/internal/security/capture-url?${searchParams.toString()}`, // Here we indicate that current session, if any, should be invalidated. It is a no-op for the // initial handshake, but is essential when both access and refresh tokens are expired. { state: null } diff --git a/x-pack/plugins/security/server/authentication/providers/pki.test.ts b/x-pack/plugins/security/server/authentication/providers/pki.test.ts index aa85b8a43af4d7..763231f7fd0dfa 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.test.ts @@ -544,7 +544,7 @@ describe('PKIAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); await expect(provider.logout(request, null)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled(); @@ -572,7 +572,7 @@ describe('PKIAuthenticationProvider', () => { mockOptions.tokens.invalidate.mockResolvedValue(undefined); await expect(provider.logout(request, state)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/security/server/authentication/providers/pki.ts b/x-pack/plugins/security/server/authentication/providers/pki.ts index 974a838127e1d6..4bb0ddaa4ee65b 100644 --- a/x-pack/plugins/security/server/authentication/providers/pki.ts +++ b/x-pack/plugins/security/server/authentication/providers/pki.ts @@ -128,7 +128,7 @@ export class PKIAuthenticationProvider extends BaseAuthenticationProvider { } } - return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** diff --git a/x-pack/plugins/security/server/authentication/providers/saml.test.ts b/x-pack/plugins/security/server/authentication/providers/saml.test.ts index 03c0b7404da398..5cba017e4916b2 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.test.ts @@ -1022,10 +1022,10 @@ describe('SAMLAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest(); await expect(provider.logout(request, null)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); await expect(provider.logout(request, { somethingElse: 'x' } as any)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); @@ -1082,7 +1082,7 @@ describe('SAMLAuthenticationProvider', () => { refreshToken, realm: 'test-realm', }) - ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { @@ -1103,7 +1103,7 @@ describe('SAMLAuthenticationProvider', () => { refreshToken, realm: 'test-realm', }) - ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { @@ -1126,7 +1126,7 @@ describe('SAMLAuthenticationProvider', () => { refreshToken, realm: 'test-realm', }) - ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlLogout', { @@ -1145,7 +1145,7 @@ describe('SAMLAuthenticationProvider', () => { refreshToken: 'x-saml-refresh-token', realm: 'test-realm', }) - ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut)); + ).resolves.toEqual(DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request))); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledWith('shield.samlInvalidate', { @@ -1159,7 +1159,7 @@ describe('SAMLAuthenticationProvider', () => { mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: null }); await expect(provider.logout(request)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); @@ -1174,7 +1174,7 @@ describe('SAMLAuthenticationProvider', () => { mockOptions.client.callAsInternalUser.mockResolvedValue({ redirect: undefined }); await expect(provider.logout(request)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.client.callAsInternalUser).toHaveBeenCalledTimes(1); @@ -1187,7 +1187,7 @@ describe('SAMLAuthenticationProvider', () => { const request = httpServerMock.createKibanaRequest({ query: { SAMLResponse: 'xxx yyy' } }); await expect(provider.logout(request)).resolves.toEqual( - DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut) + DeauthenticationResult.redirectTo(mockOptions.urls.loggedOut(request)) ); expect(mockOptions.client.callAsInternalUser).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/security/server/authentication/providers/saml.ts b/x-pack/plugins/security/server/authentication/providers/saml.ts index 8f31968e5f639f..34639a849d354d 100644 --- a/x-pack/plugins/security/server/authentication/providers/saml.ts +++ b/x-pack/plugins/security/server/authentication/providers/saml.ts @@ -7,6 +7,7 @@ import Boom from '@hapi/boom'; import { KibanaRequest } from '../../../../../../src/core/server'; import { isInternalURL } from '../../../common/is_internal_url'; +import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../../common/constants'; import type { AuthenticationInfo } from '../../elasticsearch'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; @@ -282,7 +283,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { } } - return DeauthenticationResult.redirectTo(this.options.urls.loggedOut); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** @@ -606,14 +607,18 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider { * @param request Request instance. */ private captureRedirectURL(request: KibanaRequest) { + const searchParams = new URLSearchParams([ + [ + NEXT_URL_QUERY_STRING_PARAMETER, + `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}`, + ], + ['providerType', this.type], + ['providerName', this.options.name], + ]); return AuthenticationResult.redirectTo( `${ this.options.basePath.serverBasePath - }/internal/security/capture-url?next=${encodeURIComponent( - `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` - )}&providerType=${encodeURIComponent(this.type)}&providerName=${encodeURIComponent( - this.options.name - )}`, + }/internal/security/capture-url?${searchParams.toString()}`, // Here we indicate that current session, if any, should be invalidated. It is a no-op for the // initial handshake, but is essential when both access and refresh tokens are expired. { state: null } diff --git a/x-pack/plugins/security/server/authentication/providers/token.test.ts b/x-pack/plugins/security/server/authentication/providers/token.test.ts index e09400e9bb44aa..5a600461ef4675 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.test.ts @@ -37,6 +37,8 @@ describe('TokenAuthenticationProvider', () => { let mockOptions: MockAuthenticationProviderOptions; beforeEach(() => { mockOptions = mockAuthenticationProviderOptions({ name: 'token' }); + mockOptions.urls.loggedOut.mockReturnValue('/some-logged-out-page'); + provider = new TokenAuthenticationProvider(mockOptions); }); @@ -347,11 +349,9 @@ describe('TokenAuthenticationProvider', () => { expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled(); }); - it('redirects to login view if state is `null`.', async () => { - const request = httpServerMock.createKibanaRequest(); - - await expect(provider.logout(request, null)).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/login?msg=LOGGED_OUT') + it('redirects to the logged out URL if state is `null`.', async () => { + await expect(provider.logout(httpServerMock.createKibanaRequest(), null)).resolves.toEqual( + DeauthenticationResult.redirectTo('/some-logged-out-page') ); expect(mockOptions.tokens.invalidate).not.toHaveBeenCalled(); @@ -372,28 +372,14 @@ describe('TokenAuthenticationProvider', () => { expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith(tokenPair); }); - it('redirects to /login if tokens are invalidated successfully', async () => { + it('redirects to the logged out URL if tokens are invalidated successfully.', async () => { const request = httpServerMock.createKibanaRequest(); const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; mockOptions.tokens.invalidate.mockResolvedValue(undefined); await expect(provider.logout(request, tokenPair)).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/login?msg=LOGGED_OUT') - ); - - expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); - expect(mockOptions.tokens.invalidate).toHaveBeenCalledWith(tokenPair); - }); - - it('redirects to /login with optional search parameters if tokens are invalidated successfully', async () => { - const request = httpServerMock.createKibanaRequest({ query: { yep: 'nope' } }); - const tokenPair = { accessToken: 'foo', refreshToken: 'bar' }; - - mockOptions.tokens.invalidate.mockResolvedValue(undefined); - - await expect(provider.logout(request, tokenPair)).resolves.toEqual( - DeauthenticationResult.redirectTo('/mock-server-basepath/login?yep=nope') + DeauthenticationResult.redirectTo('/some-logged-out-page') ); expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/security/server/authentication/providers/token.ts b/x-pack/plugins/security/server/authentication/providers/token.ts index 2032db4b0a8f29..67c2d244e75a29 100644 --- a/x-pack/plugins/security/server/authentication/providers/token.ts +++ b/x-pack/plugins/security/server/authentication/providers/token.ts @@ -6,6 +6,7 @@ import Boom from '@hapi/boom'; import { KibanaRequest } from '../../../../../../src/core/server'; +import { NEXT_URL_QUERY_STRING_PARAMETER } from '../../../common/constants'; import { AuthenticationResult } from '../authentication_result'; import { DeauthenticationResult } from '../deauthentication_result'; import { canRedirectRequest } from '../can_redirect_request'; @@ -145,10 +146,7 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { } } - const queryString = request.url.search || `?msg=LOGGED_OUT`; - return DeauthenticationResult.redirectTo( - `${this.options.basePath.get(request)}/login${queryString}` - ); + return DeauthenticationResult.redirectTo(this.options.urls.loggedOut(request)); } /** @@ -235,6 +233,8 @@ export class TokenAuthenticationProvider extends BaseAuthenticationProvider { const nextURL = encodeURIComponent( `${this.options.basePath.get(request)}${request.url.pathname}${request.url.search}` ); - return `${this.options.basePath.get(request)}/login?next=${nextURL}`; + return `${this.options.basePath.get( + request + )}/login?${NEXT_URL_QUERY_STRING_PARAMETER}=${nextURL}`; } } diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index a306e701e4e8d2..f41e721db33a2e 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -902,8 +902,9 @@ describe('config schema', () => { "[authc.providers]: types that failed validation: - [authc.providers.0]: expected value of type [array] but got [Object] - [authc.providers.1.anonymous.anonymous1.credentials]: types that failed validation: - - [credentials.0.password]: expected value of type [string] but got [undefined] - - [credentials.1.apiKey]: expected at least one defined value but got [undefined]" + - [credentials.0]: expected value to equal [elasticsearch_anonymous_user] + - [credentials.1.password]: expected value of type [string] but got [undefined] + - [credentials.2.apiKey]: expected at least one defined value but got [undefined]" `); expect(() => @@ -918,8 +919,9 @@ describe('config schema', () => { "[authc.providers]: types that failed validation: - [authc.providers.0]: expected value of type [array] but got [Object] - [authc.providers.1.anonymous.anonymous1.credentials]: types that failed validation: - - [credentials.0.username]: expected value of type [string] but got [undefined] - - [credentials.1.apiKey]: expected at least one defined value but got [undefined]" + - [credentials.0]: expected value to equal [elasticsearch_anonymous_user] + - [credentials.1.username]: expected value of type [string] but got [undefined] + - [credentials.2.apiKey]: expected at least one defined value but got [undefined]" `); }); @@ -973,8 +975,9 @@ describe('config schema', () => { "[authc.providers]: types that failed validation: - [authc.providers.0]: expected value of type [array] but got [Object] - [authc.providers.1.anonymous.anonymous1.credentials]: types that failed validation: - - [credentials.0.username]: expected value of type [string] but got [undefined] - - [credentials.1.apiKey]: types that failed validation: + - [credentials.0]: expected value to equal [elasticsearch_anonymous_user] + - [credentials.1.username]: expected value of type [string] but got [undefined] + - [credentials.2.apiKey]: types that failed validation: - [credentials.apiKey.0.key]: expected value of type [string] but got [undefined] - [credentials.apiKey.1]: expected value of type [string] but got [Object]" `); @@ -993,8 +996,9 @@ describe('config schema', () => { "[authc.providers]: types that failed validation: - [authc.providers.0]: expected value of type [array] but got [Object] - [authc.providers.1.anonymous.anonymous1.credentials]: types that failed validation: - - [credentials.0.username]: expected value of type [string] but got [undefined] - - [credentials.1.apiKey]: types that failed validation: + - [credentials.0]: expected value to equal [elasticsearch_anonymous_user] + - [credentials.1.username]: expected value of type [string] but got [undefined] + - [credentials.2.apiKey]: types that failed validation: - [credentials.apiKey.0.id]: expected value of type [string] but got [undefined] - [credentials.apiKey.1]: expected value of type [string] but got [Object]" `); @@ -1073,6 +1077,40 @@ describe('config schema', () => { `); }); + it('can be successfully validated with `elasticsearch_anonymous_user` credentials', () => { + expect( + ConfigSchema.validate({ + authc: { + providers: { + anonymous: { + anonymous1: { + order: 0, + credentials: 'elasticsearch_anonymous_user', + }, + }, + }, + }, + }).authc.providers + ).toMatchInlineSnapshot(` + Object { + "anonymous": Object { + "anonymous1": Object { + "credentials": "elasticsearch_anonymous_user", + "description": "Continue as Guest", + "enabled": true, + "hint": "For anonymous users", + "icon": "globe", + "order": 0, + "session": Object { + "idleTimeout": null, + }, + "showInSelector": true, + }, + }, + } + `); + }); + it('can be successfully validated with session config overrides', () => { expect( ConfigSchema.validate({ diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index b46c8dc2178a40..8d1415e1574d46 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -9,7 +9,7 @@ import type { Duration } from 'moment'; import { schema, Type, TypeOf } from '@kbn/config-schema'; import { i18n } from '@kbn/i18n'; import { Logger, config as coreConfig } from '../../../../src/core/server'; -import type { AuthenticationProvider } from '../common/types'; +import type { AuthenticationProvider } from '../common/model'; export type ConfigType = ReturnType; type RawConfigType = TypeOf; @@ -150,6 +150,7 @@ const providersConfigSchema = schema.object( }, { credentials: schema.oneOf([ + schema.literal('elasticsearch_anonymous_user'), schema.object({ username: schema.string(), password: schema.string(), diff --git a/x-pack/plugins/security/server/elasticsearch/elasticsearch_client_plugin.ts b/x-pack/plugins/security/server/elasticsearch/elasticsearch_client_plugin.ts index 529e8a8aa6e9cc..0a43d8dd6973af 100644 --- a/x-pack/plugins/security/server/elasticsearch/elasticsearch_client_plugin.ts +++ b/x-pack/plugins/security/server/elasticsearch/elasticsearch_client_plugin.ts @@ -22,133 +22,6 @@ export function elasticsearchClientPlugin(Client: any, config: unknown, componen }, }); - /** - * Perform a [shield.changePassword](Change the password of a user) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {Boolean} params.refresh - Refresh the index after performing the operation - * @param {String} params.username - The username of the user to change the password for - */ - shield.changePassword = ca({ - params: { - refresh: { - type: 'boolean', - }, - }, - urls: [ - { - fmt: '/_security/user/<%=username%>/_password', - req: { - username: { - type: 'string', - required: false, - }, - }, - }, - { - fmt: '/_security/user/_password', - }, - ], - needBody: true, - method: 'POST', - }); - - /** - * Perform a [shield.clearCachedRealms](Clears the internal user caches for specified realms) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {String} params.usernames - Comma-separated list of usernames to clear from the cache - * @param {String} params.realms - Comma-separated list of realms to clear - */ - shield.clearCachedRealms = ca({ - params: { - usernames: { - type: 'string', - required: false, - }, - }, - url: { - fmt: '/_security/realm/<%=realms%>/_clear_cache', - req: { - realms: { - type: 'string', - required: true, - }, - }, - }, - method: 'POST', - }); - - /** - * Perform a [shield.clearCachedRoles](Clears the internal caches for specified roles) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {String} params.name - Role name - */ - shield.clearCachedRoles = ca({ - params: {}, - url: { - fmt: '/_security/role/<%=name%>/_clear_cache', - req: { - name: { - type: 'string', - required: true, - }, - }, - }, - method: 'POST', - }); - - /** - * Perform a [shield.deleteRole](Remove a role from the native shield realm) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {Boolean} params.refresh - Refresh the index after performing the operation - * @param {String} params.name - Role name - */ - shield.deleteRole = ca({ - params: { - refresh: { - type: 'boolean', - }, - }, - url: { - fmt: '/_security/role/<%=name%>', - req: { - name: { - type: 'string', - required: true, - }, - }, - }, - method: 'DELETE', - }); - - /** - * Perform a [shield.deleteUser](Remove a user from the native shield realm) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {Boolean} params.refresh - Refresh the index after performing the operation - * @param {String} params.username - username - */ - shield.deleteUser = ca({ - params: { - refresh: { - type: 'boolean', - }, - }, - url: { - fmt: '/_security/user/<%=username%>', - req: { - username: { - type: 'string', - required: true, - }, - }, - }, - method: 'DELETE', - }); - /** * Perform a [shield.getRole](Retrieve one or more roles from the native shield realm) request * @@ -173,30 +46,6 @@ export function elasticsearchClientPlugin(Client: any, config: unknown, componen ], }); - /** - * Perform a [shield.getUser](Retrieve one or more users from the native shield realm) request - * - * @param {Object} params - An object with parameters used to carry out this action - * @param {String, String[], Boolean} params.username - A comma-separated list of usernames - */ - shield.getUser = ca({ - params: {}, - urls: [ - { - fmt: '/_security/user/<%=username%>', - req: { - username: { - type: 'list', - required: false, - }, - }, - }, - { - fmt: '/_security/user', - }, - ], - }); - /** * Perform a [shield.putRole](Update or create a role for the native shield realm) request * @@ -249,19 +98,6 @@ export function elasticsearchClientPlugin(Client: any, config: unknown, componen method: 'PUT', }); - /** - * Perform a [shield.getUserPrivileges](Retrieve a user's list of privileges) request - * - */ - shield.getUserPrivileges = ca({ - params: {}, - urls: [ - { - fmt: '/_security/user/_privileges', - }, - ], - }); - /** * Asks Elasticsearch to prepare SAML authentication request to be sent to * the 3rd-party SAML identity provider. @@ -489,36 +325,6 @@ export function elasticsearchClientPlugin(Client: any, config: unknown, componen }, }); - shield.getBuiltinPrivileges = ca({ - params: {}, - urls: [ - { - fmt: '/_security/privilege/_builtin', - }, - ], - }); - - /** - * Gets API keys in Elasticsearch - * @param {boolean} owner A boolean flag that can be used to query API keys owned by the currently authenticated user. - * Defaults to false. The realm_name or username parameters cannot be specified when this parameter is set to true as - * they are assumed to be the currently authenticated ones. - */ - shield.getAPIKeys = ca({ - method: 'GET', - urls: [ - { - fmt: `/_security/api_key?owner=<%=owner%>`, - req: { - owner: { - type: 'boolean', - required: true, - }, - }, - }, - ], - }); - /** * Creates an API key in Elasticsearch for the current user. * @@ -591,64 +397,4 @@ export function elasticsearchClientPlugin(Client: any, config: unknown, componen fmt: '/_security/delegate_pki', }, }); - - /** - * Retrieves all configured role mappings. - * - * @returns {{ [roleMappingName]: { enabled: boolean; roles: string[]; rules: Record} }} - */ - shield.getRoleMappings = ca({ - method: 'GET', - urls: [ - { - fmt: '/_security/role_mapping', - }, - { - fmt: '/_security/role_mapping/<%=name%>', - req: { - name: { - type: 'string', - required: true, - }, - }, - }, - ], - }); - - /** - * Saves the specified role mapping. - */ - shield.saveRoleMapping = ca({ - method: 'POST', - needBody: true, - urls: [ - { - fmt: '/_security/role_mapping/<%=name%>', - req: { - name: { - type: 'string', - required: true, - }, - }, - }, - ], - }); - - /** - * Deletes the specified role mapping. - */ - shield.deleteRoleMapping = ca({ - method: 'DELETE', - urls: [ - { - fmt: '/_security/role_mapping/<%=name%>', - req: { - name: { - type: 'string', - required: true, - }, - }, - }, - ], - }); } diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 17f2480026cc72..d6fe1356ce145e 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -230,7 +230,6 @@ export class Plugin { basePath: core.http.basePath, httpResources: core.http.resources, logger: this.initializerContext.logger.get('routes'), - clusterClient, config, authc: this.authc, authz, diff --git a/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts b/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts index 7968402cd21765..3a22b9fe003a1a 100644 --- a/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/enabled.test.ts @@ -4,115 +4,96 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; -import { LicenseCheck } from '../../../../licensing/server'; +import Boom from '@hapi/boom'; +import { + kibanaResponseFactory, + RequestHandler, + RequestHandlerContext, +} from '../../../../../../src/core/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; -import Boom from '@hapi/boom'; -import { defineEnabledApiKeysRoutes } from './enabled'; -import { APIKeys } from '../../authentication/api_keys'; -interface TestOptions { - licenseCheckResult?: LicenseCheck; - apiResponse?: () => Promise; - asserts: { statusCode: number; result?: Record }; -} +import { defineEnabledApiKeysRoutes } from './enabled'; +import { Authentication } from '../../authentication'; describe('API keys enabled', () => { - const enabledApiKeysTest = ( - description: string, - { licenseCheckResult = { state: 'valid' }, apiResponse, asserts }: TestOptions - ) => { - test(description, async () => { - const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const apiKeys = new APIKeys({ - logger: mockRouteDefinitionParams.logger, - clusterClient: mockRouteDefinitionParams.clusterClient, - license: mockRouteDefinitionParams.license, - }); - - mockRouteDefinitionParams.authc.areAPIKeysEnabled.mockImplementation(() => - apiKeys.areAPIKeysEnabled() + function getMockContext( + licenseCheckResult: { state: string; message?: string } = { state: 'valid' } + ) { + return ({ + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, + } as unknown) as RequestHandlerContext; + } + + let routeHandler: RequestHandler; + let authc: jest.Mocked; + beforeEach(() => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + authc = mockRouteDefinitionParams.authc; + + defineEnabledApiKeysRoutes(mockRouteDefinitionParams); + + const [, apiKeyRouteHandler] = mockRouteDefinitionParams.router.get.mock.calls.find( + ([{ path }]) => path === '/internal/security/api_key/_enabled' + )!; + routeHandler = apiKeyRouteHandler; + }); + + describe('failure', () => { + test('returns result of license checker', async () => { + const mockContext = getMockContext({ state: 'invalid', message: 'test forbidden message' }); + const response = await routeHandler( + mockContext, + httpServerMock.createKibanaRequest(), + kibanaResponseFactory ); - if (apiResponse) { - mockRouteDefinitionParams.clusterClient.callAsInternalUser.mockImplementation(apiResponse); - } - - defineEnabledApiKeysRoutes(mockRouteDefinitionParams); - const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; - - const headers = { authorization: 'foo' }; - const mockRequest = httpServerMock.createKibanaRequest({ - method: 'get', - path: '/internal/security/api_key/_enabled', - headers, - }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; - - const response = await handler(mockContext, mockRequest, kibanaResponseFactory); - expect(response.status).toBe(asserts.statusCode); - expect(response.payload).toEqual(asserts.result); - - if (apiResponse) { - expect(mockRouteDefinitionParams.clusterClient.callAsInternalUser).toHaveBeenCalledWith( - 'shield.invalidateAPIKey', - { - body: { - id: expect.any(String), - }, - } - ); - } else { - expect(mockRouteDefinitionParams.clusterClient.asScoped).not.toHaveBeenCalled(); - } + expect(response.status).toBe(403); + expect(response.payload).toEqual({ message: 'test forbidden message' }); expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); }); - }; - describe('failure', () => { - enabledApiKeysTest('returns result of license checker', { - licenseCheckResult: { state: 'invalid', message: 'test forbidden message' }, - asserts: { statusCode: 403, result: { message: 'test forbidden message' } }, - }); + test('returns error from cluster client', async () => { + const error = Boom.notAcceptable('test not acceptable message'); + authc.areAPIKeysEnabled.mockRejectedValue(error); + + const response = await routeHandler( + getMockContext(), + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ); - const error = Boom.notAcceptable('test not acceptable message'); - enabledApiKeysTest('returns error from cluster client', { - apiResponse: async () => { - throw error; - }, - asserts: { statusCode: 406, result: error }, + expect(response.status).toBe(406); + expect(response.payload).toEqual(error); }); }); describe('success', () => { - enabledApiKeysTest('returns true if API Keys are enabled', { - apiResponse: async () => ({}), - asserts: { - statusCode: 200, - result: { - apiKeysEnabled: true, - }, - }, + test('returns true if API Keys are enabled', async () => { + authc.areAPIKeysEnabled.mockResolvedValue(true); + + const response = await routeHandler( + getMockContext(), + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ); + + expect(response.status).toBe(200); + expect(response.payload).toEqual({ apiKeysEnabled: true }); }); - enabledApiKeysTest('returns false if API Keys are disabled', { - apiResponse: async () => { - const error = new Error(); - (error as any).body = { - error: { 'disabled.feature': 'api_keys' }, - }; - throw error; - }, - asserts: { - statusCode: 200, - result: { - apiKeysEnabled: false, - }, - }, + + test('returns false if API Keys are disabled', async () => { + authc.areAPIKeysEnabled.mockResolvedValue(false); + + const response = await routeHandler( + getMockContext(), + httpServerMock.createKibanaRequest(), + kibanaResponseFactory + ); + + expect(response.status).toBe(200); + expect(response.payload).toEqual({ apiKeysEnabled: false }); }); }); }); diff --git a/x-pack/plugins/security/server/routes/api_keys/get.test.ts b/x-pack/plugins/security/server/routes/api_keys/get.test.ts index cb991fb2f5aac4..cc9e9a68e6e360 100644 --- a/x-pack/plugins/security/server/routes/api_keys/get.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/get.test.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { LicenseCheck } from '../../../../licensing/server'; import { defineGetApiKeysRoutes } from './get'; -import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { httpServerMock, coreMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; import Boom from '@hapi/boom'; @@ -26,11 +26,15 @@ describe('Get API keys', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); if (apiResponse) { - mockScopedClusterClient.callAsCurrentUser.mockImplementation(apiResponse); + mockContext.core.elasticsearch.client.asCurrentUser.security.getApiKey.mockImplementation( + (async () => ({ body: await apiResponse() })) as any + ); } defineGetApiKeysRoutes(mockRouteDefinitionParams); @@ -43,22 +47,15 @@ describe('Get API keys', () => { query: { isAdmin: isAdmin.toString() }, headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); expect(response.payload).toEqual(asserts.result); if (apiResponse) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( - 'shield.getAPIKeys', - { owner: !isAdmin } - ); - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getApiKey + ).toHaveBeenCalledWith({ owner: !isAdmin }); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); }); diff --git a/x-pack/plugins/security/server/routes/api_keys/get.ts b/x-pack/plugins/security/server/routes/api_keys/get.ts index 6e98b4b098405b..b0c6ec090a0e5d 100644 --- a/x-pack/plugins/security/server/routes/api_keys/get.ts +++ b/x-pack/plugins/security/server/routes/api_keys/get.ts @@ -10,7 +10,7 @@ import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { RouteDefinitionParams } from '..'; -export function defineGetApiKeysRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineGetApiKeysRoutes({ router }: RouteDefinitionParams) { router.get( { path: '/internal/security/api_key', @@ -28,11 +28,11 @@ export function defineGetApiKeysRoutes({ router, clusterClient }: RouteDefinitio createLicensedRouteHandler(async (context, request, response) => { try { const isAdmin = request.query.isAdmin === 'true'; - const { api_keys: apiKeys } = (await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getAPIKeys', { owner: !isAdmin })) as { api_keys: ApiKey[] }; + const apiResponse = await context.core.elasticsearch.client.asCurrentUser.security.getApiKey<{ + api_keys: ApiKey[]; + }>({ owner: !isAdmin }); - const validKeys = apiKeys.filter(({ invalidated }) => !invalidated); + const validKeys = apiResponse.body.api_keys.filter(({ invalidated }) => !invalidated); return response.ok({ body: { apiKeys: validKeys } }); } catch (error) { diff --git a/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts b/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts index 88e52f735395de..9ac41fdfa74837 100644 --- a/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/invalidate.test.ts @@ -6,18 +6,18 @@ import Boom from '@hapi/boom'; import { Type } from '@kbn/config-schema'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { LicenseCheck } from '../../../../licensing/server'; import { defineInvalidateApiKeysRoutes } from './invalidate'; -import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; interface TestOptions { licenseCheckResult?: LicenseCheck; apiResponses?: Array<() => Promise>; payload?: Record; - asserts: { statusCode: number; result?: Record; apiArguments?: unknown[][] }; + asserts: { statusCode: number; result?: Record; apiArguments?: unknown[] }; } describe('Invalidate API keys', () => { @@ -27,10 +27,15 @@ describe('Invalidate API keys', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; + for (const apiResponse of apiResponses) { - mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); + mockContext.core.elasticsearch.client.asCurrentUser.security.invalidateApiKey.mockImplementationOnce( + (async () => ({ body: await apiResponse() })) as any + ); } defineInvalidateApiKeysRoutes(mockRouteDefinitionParams); @@ -43,9 +48,6 @@ describe('Invalidate API keys', () => { body: payload !== undefined ? (validate as any).body.validate(payload) : undefined, headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); @@ -53,13 +55,10 @@ describe('Invalidate API keys', () => { if (Array.isArray(asserts.apiArguments)) { for (const apiArguments of asserts.apiArguments) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith( - mockRequest - ); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith(...apiArguments); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.invalidateApiKey + ).toHaveBeenCalledWith(apiArguments); } - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); }); @@ -128,7 +127,7 @@ describe('Invalidate API keys', () => { isAdmin: true, }, asserts: { - apiArguments: [['shield.invalidateAPIKey', { body: { id: 'si8If24B1bKsmSLTAhJV' } }]], + apiArguments: [{ body: { id: 'si8If24B1bKsmSLTAhJV' } }], statusCode: 200, result: { itemsInvalidated: [], @@ -152,7 +151,7 @@ describe('Invalidate API keys', () => { isAdmin: true, }, asserts: { - apiArguments: [['shield.invalidateAPIKey', { body: { id: 'si8If24B1bKsmSLTAhJV' } }]], + apiArguments: [{ body: { id: 'si8If24B1bKsmSLTAhJV' } }], statusCode: 200, result: { itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], @@ -168,9 +167,7 @@ describe('Invalidate API keys', () => { isAdmin: false, }, asserts: { - apiArguments: [ - ['shield.invalidateAPIKey', { body: { id: 'si8If24B1bKsmSLTAhJV', owner: true } }], - ], + apiArguments: [{ body: { id: 'si8If24B1bKsmSLTAhJV', owner: true } }], statusCode: 200, result: { itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }], @@ -195,8 +192,8 @@ describe('Invalidate API keys', () => { }, asserts: { apiArguments: [ - ['shield.invalidateAPIKey', { body: { id: 'si8If24B1bKsmSLTAhJV' } }], - ['shield.invalidateAPIKey', { body: { id: 'ab8If24B1bKsmSLTAhNC' } }], + { body: { id: 'si8If24B1bKsmSLTAhJV' } }, + { body: { id: 'ab8If24B1bKsmSLTAhNC' } }, ], statusCode: 200, result: { diff --git a/x-pack/plugins/security/server/routes/api_keys/invalidate.ts b/x-pack/plugins/security/server/routes/api_keys/invalidate.ts index dd472c0b60cbc1..39779541970074 100644 --- a/x-pack/plugins/security/server/routes/api_keys/invalidate.ts +++ b/x-pack/plugins/security/server/routes/api_keys/invalidate.ts @@ -15,7 +15,7 @@ interface ResponseType { errors: Array & { error: Error }>; } -export function defineInvalidateApiKeysRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineInvalidateApiKeysRoutes({ router }: RouteDefinitionParams) { router.post( { path: '/internal/security/api_key/invalidate', @@ -28,8 +28,6 @@ export function defineInvalidateApiKeysRoutes({ router, clusterClient }: RouteDe }, createLicensedRouteHandler(async (context, request, response) => { try { - const scopedClusterClient = clusterClient.asScoped(request); - // Invalidate all API keys in parallel. const invalidationResult = ( await Promise.all( @@ -41,7 +39,9 @@ export function defineInvalidateApiKeysRoutes({ router, clusterClient }: RouteDe } // Send the request to invalidate the API key and return an error if it could not be deleted. - await scopedClusterClient.callAsCurrentUser('shield.invalidateAPIKey', { body }); + await context.core.elasticsearch.client.asCurrentUser.security.invalidateApiKey({ + body, + }); return { key, error: undefined }; } catch (error) { return { key, error: wrapError(error) }; diff --git a/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts b/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts index ecc3d32e20aecc..b06d1329dc1dbb 100644 --- a/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/privileges.test.ts @@ -6,23 +6,17 @@ import Boom from '@hapi/boom'; import { LicenseCheck } from '../../../../licensing/server'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; -import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../index.mock'; import { defineCheckPrivilegesRoutes } from './privileges'; -import { APIKeys } from '../../authentication/api_keys'; interface TestOptions { licenseCheckResult?: LicenseCheck; - callAsInternalUserResponses?: Array<() => Promise>; - callAsCurrentUserResponses?: Array<() => Promise>; - asserts: { - statusCode: number; - result?: Record; - callAsInternalUserAPIArguments?: unknown[][]; - callAsCurrentUserAPIArguments?: unknown[][]; - }; + areAPIKeysEnabled?: boolean; + apiResponse?: () => Promise; + asserts: { statusCode: number; result?: Record; apiArguments?: unknown }; } describe('Check API keys privileges', () => { @@ -30,32 +24,23 @@ describe('Check API keys privileges', () => { description: string, { licenseCheckResult = { state: 'valid' }, - callAsInternalUserResponses = [], - callAsCurrentUserResponses = [], + areAPIKeysEnabled = true, + apiResponse, asserts, }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; - const apiKeys = new APIKeys({ - logger: mockRouteDefinitionParams.logger, - clusterClient: mockRouteDefinitionParams.clusterClient, - license: mockRouteDefinitionParams.license, - }); - - mockRouteDefinitionParams.authc.areAPIKeysEnabled.mockImplementation(() => - apiKeys.areAPIKeysEnabled() - ); + mockRouteDefinitionParams.authc.areAPIKeysEnabled.mockResolvedValue(areAPIKeysEnabled); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - for (const apiResponse of callAsCurrentUserResponses) { - mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); - } - for (const apiResponse of callAsInternalUserResponses) { - mockRouteDefinitionParams.clusterClient.callAsInternalUser.mockImplementationOnce( - apiResponse + if (apiResponse) { + mockContext.core.elasticsearch.client.asCurrentUser.security.hasPrivileges.mockImplementation( + (async () => ({ body: await apiResponse() })) as any ); } @@ -68,33 +53,15 @@ describe('Check API keys privileges', () => { path: '/internal/security/api_key/privileges', headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); expect(response.payload).toEqual(asserts.result); - if (Array.isArray(asserts.callAsCurrentUserAPIArguments)) { - for (const apiArguments of asserts.callAsCurrentUserAPIArguments) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith( - mockRequest - ); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith(...apiArguments); - } - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); - } - - if (Array.isArray(asserts.callAsInternalUserAPIArguments)) { - for (const apiArguments of asserts.callAsInternalUserAPIArguments) { - expect(mockRouteDefinitionParams.clusterClient.callAsInternalUser).toHaveBeenCalledWith( - ...apiArguments - ); - } - } else { - expect(mockRouteDefinitionParams.clusterClient.callAsInternalUser).not.toHaveBeenCalled(); + if (asserts.apiArguments) { + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.hasPrivileges + ).toHaveBeenCalledWith(asserts.apiArguments); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); @@ -109,22 +76,13 @@ describe('Check API keys privileges', () => { const error = Boom.notAcceptable('test not acceptable message'); getPrivilegesTest('returns error from cluster client', { - callAsCurrentUserResponses: [ - async () => { - throw error; - }, - ], - callAsInternalUserResponses: [async () => {}], + apiResponse: async () => { + throw error; + }, asserts: { - callAsCurrentUserAPIArguments: [ - [ - 'shield.hasPrivileges', - { body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] } }, - ], - ], - callAsInternalUserAPIArguments: [ - ['shield.invalidateAPIKey', { body: { id: expect.any(String) } }], - ], + apiArguments: { + body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] }, + }, statusCode: 406, result: error, }, @@ -133,40 +91,17 @@ describe('Check API keys privileges', () => { describe('success', () => { getPrivilegesTest('returns areApiKeysEnabled and isAdmin', { - callAsCurrentUserResponses: [ - async () => ({ - username: 'elastic', - has_all_requested: true, - cluster: { manage_api_key: true, manage_security: true, manage_own_api_key: false }, - index: {}, - application: {}, - }), - ], - callAsInternalUserResponses: [ - async () => ({ - api_keys: [ - { - id: 'si8If24B1bKsmSLTAhJV', - name: 'my-api-key', - creation: 1574089261632, - expiration: 1574175661632, - invalidated: false, - username: 'elastic', - realm: 'reserved', - }, - ], - }), - ], + apiResponse: async () => ({ + username: 'elastic', + has_all_requested: true, + cluster: { manage_api_key: true, manage_security: true, manage_own_api_key: false }, + index: {}, + application: {}, + }), asserts: { - callAsCurrentUserAPIArguments: [ - [ - 'shield.hasPrivileges', - { body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] } }, - ], - ], - callAsInternalUserAPIArguments: [ - ['shield.invalidateAPIKey', { body: { id: expect.any(String) } }], - ], + apiArguments: { + body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] }, + }, statusCode: 200, result: { areApiKeysEnabled: true, isAdmin: true, canManage: true }, }, @@ -175,36 +110,18 @@ describe('Check API keys privileges', () => { getPrivilegesTest( 'returns areApiKeysEnabled=false when API Keys are disabled in Elasticsearch', { - callAsCurrentUserResponses: [ - async () => ({ - username: 'elastic', - has_all_requested: true, - cluster: { manage_api_key: true, manage_security: true, manage_own_api_key: true }, - index: {}, - application: {}, - }), - ], - callAsInternalUserResponses: [ - async () => { - const error = new Error(); - (error as any).body = { - error: { - 'disabled.feature': 'api_keys', - }, - }; - throw error; - }, - ], + apiResponse: async () => ({ + username: 'elastic', + has_all_requested: true, + cluster: { manage_api_key: true, manage_security: true, manage_own_api_key: true }, + index: {}, + application: {}, + }), + areAPIKeysEnabled: false, asserts: { - callAsCurrentUserAPIArguments: [ - [ - 'shield.hasPrivileges', - { body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] } }, - ], - ], - callAsInternalUserAPIArguments: [ - ['shield.invalidateAPIKey', { body: { id: expect.any(String) } }], - ], + apiArguments: { + body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] }, + }, statusCode: 200, result: { areApiKeysEnabled: false, isAdmin: true, canManage: true }, }, @@ -212,52 +129,34 @@ describe('Check API keys privileges', () => { ); getPrivilegesTest('returns isAdmin=false when user has insufficient privileges', { - callAsCurrentUserResponses: [ - async () => ({ - username: 'elastic', - has_all_requested: true, - cluster: { manage_api_key: false, manage_security: false, manage_own_api_key: false }, - index: {}, - application: {}, - }), - ], - callAsInternalUserResponses: [async () => ({})], + apiResponse: async () => ({ + username: 'elastic', + has_all_requested: true, + cluster: { manage_api_key: false, manage_security: false, manage_own_api_key: false }, + index: {}, + application: {}, + }), asserts: { - callAsCurrentUserAPIArguments: [ - [ - 'shield.hasPrivileges', - { body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] } }, - ], - ], - callAsInternalUserAPIArguments: [ - ['shield.invalidateAPIKey', { body: { id: expect.any(String) } }], - ], + apiArguments: { + body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] }, + }, statusCode: 200, result: { areApiKeysEnabled: true, isAdmin: false, canManage: false }, }, }); getPrivilegesTest('returns canManage=true when user can manage their own API Keys', { - callAsCurrentUserResponses: [ - async () => ({ - username: 'elastic', - has_all_requested: true, - cluster: { manage_api_key: false, manage_security: false, manage_own_api_key: true }, - index: {}, - application: {}, - }), - ], - callAsInternalUserResponses: [async () => ({})], + apiResponse: async () => ({ + username: 'elastic', + has_all_requested: true, + cluster: { manage_api_key: false, manage_security: false, manage_own_api_key: true }, + index: {}, + application: {}, + }), asserts: { - callAsCurrentUserAPIArguments: [ - [ - 'shield.hasPrivileges', - { body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] } }, - ], - ], - callAsInternalUserAPIArguments: [ - ['shield.invalidateAPIKey', { body: { id: expect.any(String) } }], - ], + apiArguments: { + body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] }, + }, statusCode: 200, result: { areApiKeysEnabled: true, isAdmin: false, canManage: true }, }, diff --git a/x-pack/plugins/security/server/routes/api_keys/privileges.ts b/x-pack/plugins/security/server/routes/api_keys/privileges.ts index 9cccb967527729..dd5d81060c7e53 100644 --- a/x-pack/plugins/security/server/routes/api_keys/privileges.ts +++ b/x-pack/plugins/security/server/routes/api_keys/privileges.ts @@ -8,11 +8,7 @@ import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { RouteDefinitionParams } from '..'; -export function defineCheckPrivilegesRoutes({ - router, - clusterClient, - authc, -}: RouteDefinitionParams) { +export function defineCheckPrivilegesRoutes({ router, authc }: RouteDefinitionParams) { router.get( { path: '/internal/security/api_key/privileges', @@ -20,19 +16,25 @@ export function defineCheckPrivilegesRoutes({ }, createLicensedRouteHandler(async (context, request, response) => { try { - const scopedClusterClient = clusterClient.asScoped(request); - const [ { - cluster: { - manage_security: manageSecurity, - manage_api_key: manageApiKey, - manage_own_api_key: manageOwnApiKey, + body: { + cluster: { + manage_security: manageSecurity, + manage_api_key: manageApiKey, + manage_own_api_key: manageOwnApiKey, + }, }, }, areApiKeysEnabled, ] = await Promise.all([ - scopedClusterClient.callAsCurrentUser('shield.hasPrivileges', { + context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges<{ + cluster: { + manage_security: boolean; + manage_api_key: boolean; + manage_own_api_key: boolean; + }; + }>({ body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] }, }), authc.areAPIKeysEnabled(), diff --git a/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts b/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts index 08cd3ba487b0b5..39e6a4838d34d4 100644 --- a/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts +++ b/x-pack/plugins/security/server/routes/authorization/privileges/get_builtin.ts @@ -7,13 +7,13 @@ import { BuiltinESPrivileges } from '../../../../common/model'; import { RouteDefinitionParams } from '../..'; -export function defineGetBuiltinPrivilegesRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineGetBuiltinPrivilegesRoutes({ router }: RouteDefinitionParams) { router.get( { path: '/internal/security/esPrivileges/builtin', validate: false }, async (context, request, response) => { - const privileges: BuiltinESPrivileges = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getBuiltinPrivileges'); + const { + body: privileges, + } = await context.core.elasticsearch.client.asCurrentUser.security.getBuiltinPrivileges(); // Exclude the `none` privilege, as it doesn't make sense as an option within the Kibana UI privileges.cluster = privileges.cluster.filter((privilege) => privilege !== 'none'); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts index 9f5ec635f56cd1..5143b727fcb4ea 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/delete.test.ts @@ -5,14 +5,11 @@ */ import Boom from '@hapi/boom'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../../src/core/server'; import { LicenseCheck } from '../../../../../licensing/server'; import { defineDeleteRolesRoutes } from './delete'; -import { - elasticsearchServiceMock, - httpServerMock, -} from '../../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../../index.mock'; interface TestOptions { @@ -29,11 +26,15 @@ describe('DELETE role', () => { ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); if (apiResponse) { - mockScopedClusterClient.callAsCurrentUser.mockImplementation(apiResponse); + mockContext.core.elasticsearch.client.asCurrentUser.security.deleteRole.mockImplementation( + (async () => ({ body: await apiResponse() })) as any + ); } defineDeleteRolesRoutes(mockRouteDefinitionParams); @@ -46,22 +47,15 @@ describe('DELETE role', () => { params: { name }, headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); expect(response.payload).toEqual(asserts.result); if (apiResponse) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( - 'shield.deleteRole', - { name } - ); - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.deleteRole + ).toHaveBeenCalledWith({ name }); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/delete.ts b/x-pack/plugins/security/server/routes/authorization/roles/delete.ts index eb56143288747c..b877aaf6abd770 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/delete.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/delete.ts @@ -9,7 +9,7 @@ import { RouteDefinitionParams } from '../../index'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; import { wrapIntoCustomErrorResponse } from '../../../errors'; -export function defineDeleteRolesRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineDeleteRolesRoutes({ router }: RouteDefinitionParams) { router.delete( { path: '/api/security/role/{name}', @@ -19,7 +19,7 @@ export function defineDeleteRolesRoutes({ router, clusterClient }: RouteDefiniti }, createLicensedRouteHandler(async (context, request, response) => { try { - await clusterClient.asScoped(request).callAsCurrentUser('shield.deleteRole', { + await context.core.elasticsearch.client.asCurrentUser.security.deleteRole({ name: request.params.name, }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts index b25b13b9fc04aa..a6090ee405329b 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.test.ts @@ -4,14 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import Boom from '@hapi/boom'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../../src/core/server'; import { LicenseCheck } from '../../../../../licensing/server'; import { defineGetRolesRoutes } from './get'; -import { - elasticsearchServiceMock, - httpServerMock, -} from '../../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../../index.mock'; const application = 'kibana-.kibana'; @@ -32,11 +29,15 @@ describe('GET role', () => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); mockRouteDefinitionParams.authz.applicationName = application; + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); if (apiResponse) { - mockScopedClusterClient.callAsCurrentUser.mockImplementation(apiResponse); + mockContext.core.elasticsearch.client.asCurrentUser.security.getRole.mockImplementation( + (async () => ({ body: await apiResponse() })) as any + ); } defineGetRolesRoutes(mockRouteDefinitionParams); @@ -49,22 +50,17 @@ describe('GET role', () => { params: { name }, headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); expect(response.payload).toEqual(asserts.result); if (apiResponse) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.getRole', { - name, - }); - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getRole + ).toHaveBeenCalledWith({ name }); } + expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); }); }; diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get.ts b/x-pack/plugins/security/server/routes/authorization/roles/get.ts index bf1140e2e6fd10..ce4a622d30e614 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get.ts @@ -8,9 +8,9 @@ import { schema } from '@kbn/config-schema'; import { RouteDefinitionParams } from '../..'; import { createLicensedRouteHandler } from '../../licensed_route_handler'; import { wrapIntoCustomErrorResponse } from '../../../errors'; -import { transformElasticsearchRoleToRole } from './model'; +import { ElasticsearchRole, transformElasticsearchRoleToRole } from './model'; -export function defineGetRolesRoutes({ router, authz, clusterClient }: RouteDefinitionParams) { +export function defineGetRolesRoutes({ router, authz }: RouteDefinitionParams) { router.get( { path: '/api/security/role/{name}', @@ -20,9 +20,11 @@ export function defineGetRolesRoutes({ router, authz, clusterClient }: RouteDefi }, createLicensedRouteHandler(async (context, request, response) => { try { - const elasticsearchRoles = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getRole', { name: request.params.name }); + const { + body: elasticsearchRoles, + } = await context.core.elasticsearch.client.asCurrentUser.security.getRole< + Record + >({ name: request.params.name }); const elasticsearchRole = elasticsearchRoles[request.params.name]; if (elasticsearchRole) { diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts index 30e0c52c4c443b..b3a855b2e0ae78 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.test.ts @@ -4,14 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import Boom from '@hapi/boom'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../../src/core/server'; import { LicenseCheck } from '../../../../../licensing/server'; import { defineGetAllRolesRoutes } from './get_all'; -import { - elasticsearchServiceMock, - httpServerMock, -} from '../../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../../index.mock'; const application = 'kibana-.kibana'; @@ -32,11 +29,15 @@ describe('GET all roles', () => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); mockRouteDefinitionParams.authz.applicationName = application; + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); if (apiResponse) { - mockScopedClusterClient.callAsCurrentUser.mockImplementation(apiResponse); + mockContext.core.elasticsearch.client.asCurrentUser.security.getRole.mockImplementation( + (async () => ({ body: await apiResponse() })) as any + ); } defineGetAllRolesRoutes(mockRouteDefinitionParams); @@ -48,19 +49,15 @@ describe('GET all roles', () => { path: '/api/security/role', headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); expect(response.payload).toEqual(asserts.result); if (apiResponse) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith('shield.getRole'); - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getRole + ).toHaveBeenCalled(); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); }); diff --git a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts index 24be6c60e4b129..21521dd6dbae3c 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/get_all.ts @@ -9,14 +9,16 @@ import { createLicensedRouteHandler } from '../../licensed_route_handler'; import { wrapIntoCustomErrorResponse } from '../../../errors'; import { ElasticsearchRole, transformElasticsearchRoleToRole } from './model'; -export function defineGetAllRolesRoutes({ router, authz, clusterClient }: RouteDefinitionParams) { +export function defineGetAllRolesRoutes({ router, authz }: RouteDefinitionParams) { router.get( { path: '/api/security/role', validate: false }, createLicensedRouteHandler(async (context, request, response) => { try { - const elasticsearchRoles = (await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getRole')) as Record; + const { + body: elasticsearchRoles, + } = await context.core.elasticsearch.client.asCurrentUser.security.getRole< + Record + >(); // Transform elasticsearch roles into Kibana roles and return in a list sorted by the role name. return response.ok({ diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts index 811ea080b43166..779e1a7fab177e 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.test.ts @@ -5,15 +5,12 @@ */ import { Type } from '@kbn/config-schema'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../../src/core/server'; import { LicenseCheck } from '../../../../../licensing/server'; import { GLOBAL_RESOURCE } from '../../../../common/constants'; import { definePutRolesRoutes } from './put'; -import { - elasticsearchServiceMock, - httpServerMock, -} from '../../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../../src/core/server/mocks'; import { routeDefinitionParamsMock } from '../../index.mock'; import { KibanaFeature } from '../../../../../features/server'; import { securityFeatureUsageServiceMock } from '../../../feature_usage/index.mock'; @@ -47,35 +44,43 @@ const privilegeMap = { interface TestOptions { name: string; licenseCheckResult?: LicenseCheck; - apiResponses?: Array<() => Promise>; + apiResponses?: { + get: () => Promise; + put: () => Promise; + }; payload?: Record; asserts: { statusCode: number; result?: Record; - apiArguments?: unknown[][]; + apiArguments?: { get: unknown[]; put: unknown[] }; recordSubFeaturePrivilegeUsage?: boolean; }; } const putRoleTest = ( description: string, - { - name, - payload, - licenseCheckResult = { state: 'valid' }, - apiResponses = [], - asserts, - }: TestOptions + { name, payload, licenseCheckResult = { state: 'valid' }, apiResponses, asserts }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); mockRouteDefinitionParams.authz.applicationName = application; mockRouteDefinitionParams.authz.privileges.get.mockReturnValue(privilegeMap); - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - for (const apiResponse of apiResponses) { - mockScopedClusterClient.callAsCurrentUser.mockImplementationOnce(apiResponse); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; + + if (apiResponses?.get) { + mockContext.core.elasticsearch.client.asCurrentUser.security.getRole.mockImplementationOnce( + (async () => ({ body: await apiResponses?.get() })) as any + ); + } + + if (apiResponses?.put) { + mockContext.core.elasticsearch.client.asCurrentUser.security.putRole.mockImplementationOnce( + (async () => ({ body: await apiResponses?.put() })) as any + ); } mockRouteDefinitionParams.getFeatureUsageService.mockReturnValue( @@ -131,21 +136,20 @@ const putRoleTest = ( body: payload !== undefined ? (validate as any).body.validate(payload) : undefined, headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); expect(response.payload).toEqual(asserts.result); - if (Array.isArray(asserts.apiArguments)) { - for (const apiArguments of asserts.apiArguments) { - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith(...apiArguments); - } - } else { - expect(mockScopedClusterClient.callAsCurrentUser).not.toHaveBeenCalled(); + if (asserts.apiArguments?.get) { + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getRole + ).toHaveBeenCalledWith(...asserts.apiArguments?.get); + } + if (asserts.apiArguments?.put) { + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.putRole + ).toHaveBeenCalledWith(...asserts.apiArguments?.put); } expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); @@ -208,12 +212,11 @@ describe('PUT role', () => { putRoleTest(`creates empty role`, { name: 'foo-role', payload: {}, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -224,7 +227,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -239,12 +242,11 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -261,7 +263,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -279,12 +281,11 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -301,7 +302,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -317,12 +318,11 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -339,7 +339,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -383,12 +383,11 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -426,7 +425,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -473,8 +472,8 @@ describe('PUT role', () => { }, ], }, - apiResponses: [ - async () => ({ + apiResponses: { + get: async () => ({ 'foo-role': { metadata: { bar: 'old-metadata', @@ -504,13 +503,12 @@ describe('PUT role', () => { ], }, }), - async () => {}, - ], + put: async () => {}, + }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -548,7 +546,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -577,8 +575,8 @@ describe('PUT role', () => { }, ], }, - apiResponses: [ - async () => ({ + apiResponses: { + get: async () => ({ 'foo-role': { metadata: { bar: 'old-metadata', @@ -613,13 +611,12 @@ describe('PUT role', () => { ], }, }), - async () => {}, - ], + put: async () => {}, + }, asserts: { - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -652,7 +649,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -670,13 +667,12 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { recordSubFeaturePrivilegeUsage: true, - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -694,7 +690,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -712,13 +708,12 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { recordSubFeaturePrivilegeUsage: false, - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -736,7 +731,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, @@ -754,13 +749,12 @@ describe('PUT role', () => { }, ], }, - apiResponses: [async () => ({}), async () => {}], + apiResponses: { get: async () => ({}), put: async () => {} }, asserts: { recordSubFeaturePrivilegeUsage: false, - apiArguments: [ - ['shield.getRole', { name: 'foo-role', ignore: [404] }], - [ - 'shield.putRole', + apiArguments: { + get: [{ name: 'foo-role' }, { ignore: [404] }], + put: [ { name: 'foo-role', body: { @@ -778,7 +772,7 @@ describe('PUT role', () => { }, }, ], - ], + }, statusCode: 204, result: undefined, }, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/put.ts b/x-pack/plugins/security/server/routes/authorization/roles/put.ts index cdedc9ac8a5eb2..26c61b4ced15f3 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/put.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/put.ts @@ -42,7 +42,6 @@ const roleGrantsSubFeaturePrivileges = ( export function definePutRolesRoutes({ router, authz, - clusterClient, getFeatures, getFeatureUsageService, }: RouteDefinitionParams) { @@ -64,12 +63,11 @@ export function definePutRolesRoutes({ const { name } = request.params; try { - const rawRoles: Record = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getRole', { - name: request.params.name, - ignore: [404], - }); + const { + body: rawRoles, + } = await context.core.elasticsearch.client.asCurrentUser.security.getRole< + Record + >({ name: request.params.name }, { ignore: [404] }); const body = transformPutPayloadToElasticsearchRole( request.body, @@ -77,11 +75,12 @@ export function definePutRolesRoutes({ rawRoles[name] ? rawRoles[name].applications : [] ); - const [features] = await Promise.all([ + const [features] = await Promise.all([ getFeatures(), - clusterClient - .asScoped(request) - .callAsCurrentUser('shield.putRole', { name: request.params.name, body }), + context.core.elasticsearch.client.asCurrentUser.security.putRole({ + name: request.params.name, + body, + }), ]); if (roleGrantsSubFeaturePrivileges(features, request.body)) { diff --git a/x-pack/plugins/security/server/routes/index.mock.ts b/x-pack/plugins/security/server/routes/index.mock.ts index fab4a71df0cb05..1df499d9816323 100644 --- a/x-pack/plugins/security/server/routes/index.mock.ts +++ b/x-pack/plugins/security/server/routes/index.mock.ts @@ -5,7 +5,6 @@ */ import { - elasticsearchServiceMock, httpServiceMock, loggingSystemMock, httpResourcesMock, @@ -25,7 +24,6 @@ export const routeDefinitionParamsMock = { basePath: httpServiceMock.createBasePath(), csp: httpServiceMock.createSetupContract().csp, logger: loggingSystemMock.create().get(), - clusterClient: elasticsearchServiceMock.createLegacyClusterClient(), config: createConfig(ConfigSchema.validate(config), loggingSystemMock.create().get(), { isTLSEnabled: false, }), diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index 079c9e8ab9ce70..db71b04b3e6f01 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -5,13 +5,7 @@ */ import type { PublicMethodsOf } from '@kbn/utility-types'; import { KibanaFeature } from '../../../features/server'; -import { - HttpResources, - IBasePath, - ILegacyClusterClient, - IRouter, - Logger, -} from '../../../../../src/core/server'; +import { HttpResources, IBasePath, IRouter, Logger } from '../../../../../src/core/server'; import { SecurityLicense } from '../../common/licensing'; import { Authentication } from '../authentication'; import { AuthorizationServiceSetup } from '../authorization'; @@ -36,7 +30,6 @@ export interface RouteDefinitionParams { basePath: IBasePath; httpResources: HttpResources; logger: Logger; - clusterClient: ILegacyClusterClient; config: ConfigType; authc: Authentication; authz: AuthorizationServiceSetup; diff --git a/x-pack/plugins/security/server/routes/indices/get_fields.test.ts b/x-pack/plugins/security/server/routes/indices/get_fields.test.ts index 4c6182e99431d5..6d3de11249a169 100644 --- a/x-pack/plugins/security/server/routes/indices/get_fields.test.ts +++ b/x-pack/plugins/security/server/routes/indices/get_fields.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { httpServerMock, elasticsearchServiceMock } from '../../../../../../src/core/server/mocks'; +import { httpServerMock, coreMock } from '../../../../../../src/core/server/mocks'; import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { routeDefinitionParamsMock } from '../index.mock'; @@ -36,10 +36,12 @@ const mockFieldMappingResponse = { describe('GET /internal/security/fields/{query}', () => { it('returns a list of deduplicated fields, omitting empty and runtime fields', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const scopedClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - scopedClient.callAsCurrentUser.mockResolvedValue(mockFieldMappingResponse); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(scopedClient); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + }; + mockContext.core.elasticsearch.client.asCurrentUser.indices.getFieldMapping.mockImplementation( + (async () => ({ body: mockFieldMappingResponse })) as any + ); defineGetFieldsRoutes(mockRouteDefinitionParams); @@ -51,7 +53,7 @@ describe('GET /internal/security/fields/{query}', () => { path: `/internal/security/fields/foo`, headers, }); - const response = await handler({} as any, mockRequest, kibanaResponseFactory); + const response = await handler(mockContext as any, mockRequest, kibanaResponseFactory); expect(response.status).toBe(200); expect(response.payload).toEqual(['fooField', 'commonField', 'barField']); }); diff --git a/x-pack/plugins/security/server/routes/indices/get_fields.ts b/x-pack/plugins/security/server/routes/indices/get_fields.ts index 44b8804ed8d6e1..304e121f7fee11 100644 --- a/x-pack/plugins/security/server/routes/indices/get_fields.ts +++ b/x-pack/plugins/security/server/routes/indices/get_fields.ts @@ -22,7 +22,7 @@ interface FieldMappingResponse { }; } -export function defineGetFieldsRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineGetFieldsRoutes({ router }: RouteDefinitionParams) { router.get( { path: '/internal/security/fields/{query}', @@ -30,14 +30,16 @@ export function defineGetFieldsRoutes({ router, clusterClient }: RouteDefinition }, async (context, request, response) => { try { - const indexMappings = (await clusterClient - .asScoped(request) - .callAsCurrentUser('indices.getFieldMapping', { + const { + body: indexMappings, + } = await context.core.elasticsearch.client.asCurrentUser.indices.getFieldMapping( + { index: request.params.query, fields: '*', - allowNoIndices: false, - includeDefaults: true, - })) as FieldMappingResponse; + allow_no_indices: false, + include_defaults: true, + } + ); // The flow is the following (see response format at https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html): // 1. Iterate over all matched indices. diff --git a/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts b/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts index aec0310129f6ee..33fd66f9e929d4 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts @@ -5,24 +5,26 @@ */ import { routeDefinitionParamsMock } from '../index.mock'; -import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { coreMock, httpServerMock } from 'src/core/server/mocks'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { defineRoleMappingDeleteRoutes } from './delete'; describe('DELETE role mappings', () => { it('allows a role mapping to be deleted', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue({ acknowledged: true }); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue({ state: 'valid' }) } } as any, + }; + mockContext.core.elasticsearch.client.asCurrentUser.security.deleteRoleMapping.mockResolvedValue( + { body: { acknowledged: true } } as any + ); defineRoleMappingDeleteRoutes(mockRouteDefinitionParams); const [[, handler]] = mockRouteDefinitionParams.router.delete.mock.calls; const name = 'mapping1'; - const headers = { authorization: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ method: 'delete', @@ -30,31 +32,35 @@ describe('DELETE role mappings', () => { params: { name }, headers, }); - const mockContext = ({ - licensing: { - license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(200); expect(response.payload).toEqual({ acknowledged: true }); - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); expect( - mockScopedClusterClient.callAsCurrentUser - ).toHaveBeenCalledWith('shield.deleteRoleMapping', { name }); + mockContext.core.elasticsearch.client.asCurrentUser.security.deleteRoleMapping + ).toHaveBeenCalledWith({ name }); }); describe('failure', () => { it('returns result of license check', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { + license: { + check: jest.fn().mockReturnValue({ + state: 'invalid', + message: 'test forbidden message', + }), + }, + } as any, + }; defineRoleMappingDeleteRoutes(mockRouteDefinitionParams); const [[, handler]] = mockRouteDefinitionParams.router.delete.mock.calls; const name = 'mapping1'; - const headers = { authorization: 'foo' }; const mockRequest = httpServerMock.createKibanaRequest({ method: 'delete', @@ -62,21 +68,13 @@ describe('DELETE role mappings', () => { params: { name }, headers, }); - const mockContext = ({ - licensing: { - license: { - check: jest.fn().mockReturnValue({ - state: 'invalid', - message: 'test forbidden message', - }), - }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(403); expect(response.payload).toEqual({ message: 'test forbidden message' }); - expect(mockRouteDefinitionParams.clusterClient.asScoped).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.deleteRoleMapping + ).not.toHaveBeenCalled(); }); }); }); diff --git a/x-pack/plugins/security/server/routes/role_mapping/delete.ts b/x-pack/plugins/security/server/routes/role_mapping/delete.ts index dc11bcd914b35b..dbe9c5662a1f10 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/delete.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/delete.ts @@ -8,9 +8,7 @@ import { createLicensedRouteHandler } from '../licensed_route_handler'; import { wrapError } from '../../errors'; import { RouteDefinitionParams } from '..'; -export function defineRoleMappingDeleteRoutes(params: RouteDefinitionParams) { - const { clusterClient, router } = params; - +export function defineRoleMappingDeleteRoutes({ router }: RouteDefinitionParams) { router.delete( { path: '/internal/security/role_mapping/{name}', @@ -22,11 +20,11 @@ export function defineRoleMappingDeleteRoutes(params: RouteDefinitionParams) { }, createLicensedRouteHandler(async (context, request, response) => { try { - const deleteResponse = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.deleteRoleMapping', { - name: request.params.name, - }); + const { + body: deleteResponse, + } = await context.core.elasticsearch.client.asCurrentUser.security.deleteRoleMapping({ + name: request.params.name, + }); return response.ok({ body: deleteResponse }); } catch (error) { const wrappedError = wrapError(error); diff --git a/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts b/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts index ee1d550bbe24d5..8bd9f095b0f68a 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts @@ -5,21 +5,16 @@ */ import { routeDefinitionParamsMock } from '../index.mock'; -import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; -import { - kibanaResponseFactory, - RequestHandlerContext, - ILegacyClusterClient, -} from '../../../../../../src/core/server'; +import { coreMock, httpServerMock } from 'src/core/server/mocks'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { LicenseCheck } from '../../../../licensing/server'; import { defineRoleMappingFeatureCheckRoute } from './feature_check'; interface TestOptions { licenseCheckResult?: LicenseCheck; canManageRoleMappings?: boolean; - nodeSettingsResponse?: Record; - xpackUsageResponse?: Record; - internalUserClusterClientImpl?: ILegacyClusterClient['callAsInternalUser']; + nodeSettingsResponse?: () => Record; + xpackUsageResponse?: () => Record; asserts: { statusCode: number; result?: Record }; } @@ -38,57 +33,34 @@ const defaultXpackUsageResponse = { }, }; -const getDefaultInternalUserClusterClientImpl = ( - nodeSettingsResponse: TestOptions['nodeSettingsResponse'], - xpackUsageResponse: TestOptions['xpackUsageResponse'] -) => - ((async (endpoint: string, clientParams: Record) => { - if (!clientParams) throw new TypeError('expected clientParams'); - - if (endpoint === 'nodes.info') { - return nodeSettingsResponse; - } - - if (endpoint === 'transport.request') { - if (clientParams.path === '/_xpack/usage') { - return xpackUsageResponse; - } - } - - throw new Error(`unexpected endpoint: ${endpoint}`); - }) as unknown) as TestOptions['internalUserClusterClientImpl']; - describe('GET role mappings feature check', () => { const getFeatureCheckTest = ( description: string, { licenseCheckResult = { state: 'valid' }, canManageRoleMappings = true, - nodeSettingsResponse = {}, - xpackUsageResponse = defaultXpackUsageResponse, - internalUserClusterClientImpl = getDefaultInternalUserClusterClientImpl( - nodeSettingsResponse, - xpackUsageResponse - ), + nodeSettingsResponse = async () => ({}), + xpackUsageResponse = async () => defaultXpackUsageResponse, asserts, }: TestOptions ) => { test(description, async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - mockRouteDefinitionParams.clusterClient.callAsInternalUser.mockImplementation( - internalUserClusterClientImpl + mockContext.core.elasticsearch.client.asInternalUser.nodes.info.mockImplementation( + (async () => ({ body: await nodeSettingsResponse() })) as any + ); + mockContext.core.elasticsearch.client.asInternalUser.transport.request.mockImplementation( + (async () => ({ body: await xpackUsageResponse() })) as any ); - mockScopedClusterClient.callAsCurrentUser.mockImplementation(async (method, payload) => { - if (method === 'shield.hasPrivileges') { - return { - has_all_requested: canManageRoleMappings, - }; - } - }); + mockContext.core.elasticsearch.client.asCurrentUser.security.hasPrivileges.mockResolvedValue({ + body: { has_all_requested: canManageRoleMappings }, + } as any); defineRoleMappingFeatureCheckRoute(mockRouteDefinitionParams); const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; @@ -99,9 +71,6 @@ describe('GET role mappings feature check', () => { path: `/internal/security/_check_role_mapping_features`, headers, }); - const mockContext = ({ - licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(asserts.statusCode); @@ -124,7 +93,7 @@ describe('GET role mappings feature check', () => { }); getFeatureCheckTest('allows both script types when explicitly enabled', { - nodeSettingsResponse: { + nodeSettingsResponse: async () => ({ nodes: { someNodeId: { settings: { @@ -134,7 +103,7 @@ describe('GET role mappings feature check', () => { }, }, }, - }, + }), asserts: { statusCode: 200, result: { @@ -147,7 +116,7 @@ describe('GET role mappings feature check', () => { }); getFeatureCheckTest('disallows stored scripts when disabled', { - nodeSettingsResponse: { + nodeSettingsResponse: async () => ({ nodes: { someNodeId: { settings: { @@ -157,7 +126,7 @@ describe('GET role mappings feature check', () => { }, }, }, - }, + }), asserts: { statusCode: 200, result: { @@ -170,7 +139,7 @@ describe('GET role mappings feature check', () => { }); getFeatureCheckTest('disallows inline scripts when disabled', { - nodeSettingsResponse: { + nodeSettingsResponse: async () => ({ nodes: { someNodeId: { settings: { @@ -180,7 +149,7 @@ describe('GET role mappings feature check', () => { }, }, }, - }, + }), asserts: { statusCode: 200, result: { @@ -193,7 +162,7 @@ describe('GET role mappings feature check', () => { }); getFeatureCheckTest('indicates incompatible realms when only native and file are enabled', { - xpackUsageResponse: { + xpackUsageResponse: async () => ({ security: { realms: { native: { @@ -206,7 +175,7 @@ describe('GET role mappings feature check', () => { }, }, }, - }, + }), asserts: { statusCode: 200, result: { @@ -231,9 +200,12 @@ describe('GET role mappings feature check', () => { getFeatureCheckTest( 'falls back to allowing both script types if there is an error retrieving node settings', { - internalUserClusterClientImpl: (() => { - return Promise.reject(new Error('something bad happened')); - }) as TestOptions['internalUserClusterClientImpl'], + nodeSettingsResponse: async () => { + throw new Error('something bad happened'); + }, + xpackUsageResponse: async () => { + throw new Error('something bad happened'); + }, asserts: { statusCode: 200, result: { diff --git a/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts b/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts index 88c7f193cea34b..470039b8ae92b2 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, ILegacyClusterClient } from 'src/core/server'; +import { ElasticsearchClient, Logger } from 'src/core/server'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { RouteDefinitionParams } from '..'; @@ -34,24 +34,18 @@ interface XPackUsageResponse { const INCOMPATIBLE_REALMS = ['file', 'native']; -export function defineRoleMappingFeatureCheckRoute({ - router, - clusterClient, - logger, -}: RouteDefinitionParams) { +export function defineRoleMappingFeatureCheckRoute({ router, logger }: RouteDefinitionParams) { router.get( { path: '/internal/security/_check_role_mapping_features', validate: false, }, createLicensedRouteHandler(async (context, request, response) => { - const { has_all_requested: canManageRoleMappings } = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.hasPrivileges', { - body: { - cluster: ['manage_security'], - }, - }); + const { + body: { has_all_requested: canManageRoleMappings }, + } = await context.core.elasticsearch.client.asCurrentUser.security.hasPrivileges<{ + has_all_requested: boolean; + }>({ body: { cluster: ['manage_security'] } }); if (!canManageRoleMappings) { return response.ok({ @@ -61,7 +55,10 @@ export function defineRoleMappingFeatureCheckRoute({ }); } - const enabledFeatures = await getEnabledRoleMappingsFeatures(clusterClient, logger); + const enabledFeatures = await getEnabledRoleMappingsFeatures( + context.core.elasticsearch.client.asInternalUser, + logger + ); return response.ok({ body: { @@ -73,13 +70,12 @@ export function defineRoleMappingFeatureCheckRoute({ ); } -async function getEnabledRoleMappingsFeatures(clusterClient: ILegacyClusterClient, logger: Logger) { +async function getEnabledRoleMappingsFeatures(esClient: ElasticsearchClient, logger: Logger) { logger.debug(`Retrieving role mappings features`); - const nodeScriptSettingsPromise: Promise = clusterClient - .callAsInternalUser('nodes.info', { - filterPath: 'nodes.*.settings.script', - }) + const nodeScriptSettingsPromise = esClient.nodes + .info({ filter_path: 'nodes.*.settings.script' }) + .then(({ body }) => body) .catch((error) => { // fall back to assuming that node settings are unset/at their default values. // this will allow the role mappings UI to permit both role template script types, @@ -88,13 +84,11 @@ async function getEnabledRoleMappingsFeatures(clusterClient: ILegacyClusterClien return {}; }); - const xpackUsagePromise: Promise = clusterClient - // `transport.request` is potentially unsafe when combined with untrusted user input. - // Do not augment with such input. - .callAsInternalUser('transport.request', { - method: 'GET', - path: '/_xpack/usage', - }) + // `transport.request` is potentially unsafe when combined with untrusted user input. + // Do not augment with such input. + const xpackUsagePromise = esClient.transport + .request({ method: 'GET', path: '/_xpack/usage' }) + .then(({ body }) => body as XPackUsageResponse) .catch((error) => { // fall back to no external realms configured. // this will cause a warning in the UI about no compatible realms being enabled, but will otherwise allow diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.test.ts b/x-pack/plugins/security/server/routes/role_mapping/get.test.ts index 2519034b386bfa..625aae42a3907b 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/get.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/get.test.ts @@ -6,9 +6,9 @@ import Boom from '@hapi/boom'; import { routeDefinitionParamsMock } from '../index.mock'; -import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { coreMock, httpServerMock } from 'src/core/server/mocks'; import { defineRoleMappingGetRoutes } from './get'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; const mockRoleMappingResponse = { mapping1: { @@ -49,13 +49,22 @@ const mockRoleMappingResponse = { }, }; +function getMockContext( + licenseCheckResult: { state: string; message?: string } = { state: 'valid' } +) { + return { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } } as any, + }; +} + describe('GET role mappings', () => { it('returns all role mappings', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(mockRoleMappingResponse); + const mockContext = getMockContext(); + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping.mockResolvedValue({ + body: mockRoleMappingResponse, + } as any); defineRoleMappingGetRoutes(mockRouteDefinitionParams); @@ -67,11 +76,6 @@ describe('GET role mappings', () => { path: `/internal/security/role_mapping`, headers, }); - const mockContext = ({ - licensing: { - license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(200); @@ -118,29 +122,27 @@ describe('GET role mappings', () => { }, ]); - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( - 'shield.getRoleMappings', - { name: undefined } - ); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping + ).toHaveBeenCalledWith({ name: undefined }); }); it('returns role mapping by name', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue({ - mapping1: { - enabled: true, - roles: ['foo', 'bar'], - rules: { - field: { - dn: 'CN=bob,OU=example,O=com', + const mockContext = getMockContext(); + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping.mockResolvedValue({ + body: { + mapping1: { + enabled: true, + roles: ['foo', 'bar'], + rules: { + field: { + dn: 'CN=bob,OU=example,O=com', + }, }, }, }, - }); + } as any); defineRoleMappingGetRoutes(mockRouteDefinitionParams); @@ -155,11 +157,6 @@ describe('GET role mappings', () => { params: { name }, headers, }); - const mockContext = ({ - licensing: { - license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(200); @@ -175,16 +172,15 @@ describe('GET role mappings', () => { }, }); - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( - 'shield.getRoleMappings', - { name } - ); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping + ).toHaveBeenCalledWith({ name }); }); describe('failure', () => { it('returns result of license check', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = getMockContext({ state: 'invalid', message: 'test forbidden message' }); defineRoleMappingGetRoutes(mockRouteDefinitionParams); @@ -196,29 +192,19 @@ describe('GET role mappings', () => { path: `/internal/security/role_mapping`, headers, }); - const mockContext = ({ - licensing: { - license: { - check: jest.fn().mockReturnValue({ - state: 'invalid', - message: 'test forbidden message', - }), - }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(403); expect(response.payload).toEqual({ message: 'test forbidden message' }); - expect(mockRouteDefinitionParams.clusterClient.asScoped).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping + ).not.toHaveBeenCalled(); }); it('returns a 404 when the role mapping is not found', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( + const mockContext = getMockContext(); + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping.mockRejectedValue( Boom.notFound('role mapping not found!') ); @@ -235,18 +221,12 @@ describe('GET role mappings', () => { params: { name }, headers, }); - const mockContext = ({ - licensing: { - license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(404); - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); expect( - mockScopedClusterClient.callAsCurrentUser - ).toHaveBeenCalledWith('shield.getRoleMappings', { name }); + mockContext.core.elasticsearch.client.asCurrentUser.security.getRoleMapping + ).toHaveBeenCalledWith({ name }); }); }); }); diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.ts b/x-pack/plugins/security/server/routes/role_mapping/get.ts index 63598584b5d1b5..5ab9b1f6b4a243 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/get.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/get.ts @@ -14,7 +14,7 @@ interface RoleMappingsResponse { } export function defineRoleMappingGetRoutes(params: RouteDefinitionParams) { - const { clusterClient, logger, router } = params; + const { logger, router } = params; router.get( { @@ -29,13 +29,11 @@ export function defineRoleMappingGetRoutes(params: RouteDefinitionParams) { const expectSingleEntity = typeof request.params.name === 'string'; try { - const roleMappingsResponse: RoleMappingsResponse = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getRoleMappings', { - name: request.params.name, - }); + const roleMappingsResponse = await context.core.elasticsearch.client.asCurrentUser.security.getRoleMapping( + { name: request.params.name } + ); - const mappings = Object.entries(roleMappingsResponse).map(([name, mapping]) => { + const mappings = Object.entries(roleMappingsResponse.body).map(([name, mapping]) => { return { name, ...mapping, diff --git a/x-pack/plugins/security/server/routes/role_mapping/post.test.ts b/x-pack/plugins/security/server/routes/role_mapping/post.test.ts index 8f61d2a122f0c1..5dc7a21a02c6f0 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/post.test.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/post.test.ts @@ -5,17 +5,20 @@ */ import { routeDefinitionParamsMock } from '../index.mock'; -import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; -import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { coreMock, httpServerMock } from 'src/core/server/mocks'; +import { kibanaResponseFactory } from '../../../../../../src/core/server'; import { defineRoleMappingPostRoutes } from './post'; describe('POST role mappings', () => { it('allows a role mapping to be created', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - mockScopedClusterClient.callAsCurrentUser.mockResolvedValue({ created: true }); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue({ state: 'valid' }) } } as any, + }; + mockContext.core.elasticsearch.client.asCurrentUser.security.putRoleMapping.mockResolvedValue({ + body: { created: true }, + } as any); defineRoleMappingPostRoutes(mockRouteDefinitionParams); @@ -39,37 +42,41 @@ describe('POST role mappings', () => { }, headers, }); - const mockContext = ({ - licensing: { - license: { check: jest.fn().mockReturnValue({ state: 'valid' }) }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(200); expect(response.payload).toEqual({ created: true }); - expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( - 'shield.saveRoleMapping', - { - name, - body: { - enabled: true, - roles: ['foo', 'bar'], - rules: { - field: { - dn: 'CN=bob,OU=example,O=com', - }, + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.putRoleMapping + ).toHaveBeenCalledWith({ + name, + body: { + enabled: true, + roles: ['foo', 'bar'], + rules: { + field: { + dn: 'CN=bob,OU=example,O=com', }, }, - } - ); + }, + }); }); describe('failure', () => { it('returns result of license check', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { + license: { + check: jest.fn().mockReturnValue({ + state: 'invalid', + message: 'test forbidden message', + }), + }, + } as any, + }; defineRoleMappingPostRoutes(mockRouteDefinitionParams); @@ -81,22 +88,14 @@ describe('POST role mappings', () => { path: `/internal/security/role_mapping`, headers, }); - const mockContext = ({ - licensing: { - license: { - check: jest.fn().mockReturnValue({ - state: 'invalid', - message: 'test forbidden message', - }), - }, - }, - } as unknown) as RequestHandlerContext; const response = await handler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(403); expect(response.payload).toEqual({ message: 'test forbidden message' }); - expect(mockRouteDefinitionParams.clusterClient.asScoped).not.toHaveBeenCalled(); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.putRoleMapping + ).not.toHaveBeenCalled(); }); }); }); diff --git a/x-pack/plugins/security/server/routes/role_mapping/post.ts b/x-pack/plugins/security/server/routes/role_mapping/post.ts index 11149f38069a7f..6c1b19dacb6015 100644 --- a/x-pack/plugins/security/server/routes/role_mapping/post.ts +++ b/x-pack/plugins/security/server/routes/role_mapping/post.ts @@ -8,9 +8,7 @@ import { createLicensedRouteHandler } from '../licensed_route_handler'; import { wrapError } from '../../errors'; import { RouteDefinitionParams } from '..'; -export function defineRoleMappingPostRoutes(params: RouteDefinitionParams) { - const { clusterClient, router } = params; - +export function defineRoleMappingPostRoutes({ router }: RouteDefinitionParams) { router.post( { path: '/internal/security/role_mapping/{name}', @@ -43,13 +41,10 @@ export function defineRoleMappingPostRoutes(params: RouteDefinitionParams) { }, createLicensedRouteHandler(async (context, request, response) => { try { - const saveResponse = await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.saveRoleMapping', { - name: request.params.name, - body: request.body, - }); - return response.ok({ body: saveResponse }); + const saveResponse = await context.core.elasticsearch.client.asCurrentUser.security.putRoleMapping( + { name: request.params.name, body: request.body } + ); + return response.ok({ body: saveResponse.body }); } catch (error) { const wrappedError = wrapError(error); return response.customError({ diff --git a/x-pack/plugins/security/server/routes/users/change_password.test.ts b/x-pack/plugins/security/server/routes/users/change_password.test.ts index c66b5f985cb33f..d98c0acb7d86d0 100644 --- a/x-pack/plugins/security/server/routes/users/change_password.test.ts +++ b/x-pack/plugins/security/server/routes/users/change_password.test.ts @@ -7,21 +7,20 @@ import { errors } from 'elasticsearch'; import { ObjectType } from '@kbn/config-schema'; import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; import { - ILegacyClusterClient, + Headers, IRouter, - ILegacyScopedClusterClient, kibanaResponseFactory, RequestHandler, RequestHandlerContext, RouteConfig, - ScopeableRequest, } from '../../../../../../src/core/server'; import { Authentication, AuthenticationResult } from '../../authentication'; import { Session } from '../../session_management'; import { defineChangeUserPasswordRoutes } from './change_password'; -import { elasticsearchServiceMock, httpServerMock } from '../../../../../../src/core/server/mocks'; +import { coreMock, httpServerMock } from '../../../../../../src/core/server/mocks'; import { mockAuthenticatedUser } from '../../../common/model/authenticated_user.mock'; import { sessionMock } from '../../session_management/session.mock'; import { routeDefinitionParamsMock } from '../index.mock'; @@ -30,19 +29,19 @@ describe('Change password', () => { let router: jest.Mocked; let authc: jest.Mocked; let session: jest.Mocked>; - let mockClusterClient: jest.Mocked; - let mockScopedClusterClient: jest.Mocked; let routeHandler: RequestHandler; let routeConfig: RouteConfig; - let mockContext: RequestHandlerContext; - - function checkPasswordChangeAPICall(username: string, request: ScopeableRequest) { - expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1); - expect(mockClusterClient.asScoped).toHaveBeenCalledWith(request); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( - 'shield.changePassword', - { username, body: { password: 'new-password' } } + let mockContext: DeeplyMockedKeys; + + function checkPasswordChangeAPICall(username: string, headers?: Headers) { + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.changePassword + ).toHaveBeenCalledTimes(1); + expect( + mockContext.core.elasticsearch.client.asCurrentUser.security.changePassword + ).toHaveBeenCalledWith( + { username, body: { password: 'new-password' } }, + headers && { headers } ); } @@ -56,15 +55,10 @@ describe('Change password', () => { authc.login.mockResolvedValue(AuthenticationResult.succeeded(mockAuthenticatedUser())); session.get.mockResolvedValue(sessionMock.createValue()); - mockScopedClusterClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - mockClusterClient = routeParamsMock.clusterClient; - mockClusterClient.asScoped.mockReturnValue(mockScopedClusterClient); - - mockContext = ({ - licensing: { - license: { check: jest.fn().mockReturnValue({ check: 'valid' }) }, - }, - } as unknown) as RequestHandlerContext; + mockContext = { + core: coreMock.createRequestHandlerContext(), + licensing: { license: { check: jest.fn().mockReturnValue({ state: 'valid' }) } }, + } as any; defineChangeUserPasswordRoutes(routeParamsMock); @@ -114,20 +108,18 @@ describe('Change password', () => { const changePasswordFailure = new (errors.AuthenticationException as any)('Unauthorized', { body: { error: { header: { 'WWW-Authenticate': 'Negotiate' } } }, }); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(changePasswordFailure); + mockContext.core.elasticsearch.client.asCurrentUser.security.changePassword.mockRejectedValue( + changePasswordFailure + ); const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); expect(response.status).toBe(403); expect(response.payload).toEqual(changePasswordFailure); - expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockClusterClient.asScoped).toHaveBeenCalledTimes(1); - expect(mockClusterClient.asScoped).toHaveBeenCalledWith({ - headers: { - ...mockRequest.headers, - authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, - }, + checkPasswordChangeAPICall(username, { + ...mockRequest.headers, + authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, }); }); @@ -148,16 +140,16 @@ describe('Change password', () => { expect(response.payload).toEqual(loginFailureReason); checkPasswordChangeAPICall(username, { - headers: { - ...mockRequest.headers, - authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, - }, + ...mockRequest.headers, + authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, }); }); it('returns 500 if password update request fails with non-401 error.', async () => { const failureReason = new Error('Request failed.'); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + mockContext.core.elasticsearch.client.asCurrentUser.security.changePassword.mockRejectedValue( + failureReason + ); const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); @@ -165,10 +157,8 @@ describe('Change password', () => { expect(response.payload).toEqual(failureReason); checkPasswordChangeAPICall(username, { - headers: { - ...mockRequest.headers, - authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, - }, + ...mockRequest.headers, + authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, }); }); @@ -179,10 +169,8 @@ describe('Change password', () => { expect(response.payload).toBeUndefined(); checkPasswordChangeAPICall(username, { - headers: { - ...mockRequest.headers, - authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, - }, + ...mockRequest.headers, + authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, }); expect(authc.login).toHaveBeenCalledTimes(1); @@ -209,10 +197,8 @@ describe('Change password', () => { expect(response.payload).toBeUndefined(); checkPasswordChangeAPICall(username, { - headers: { - ...mockRequest.headers, - authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, - }, + ...mockRequest.headers, + authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, }); expect(authc.login).toHaveBeenCalledTimes(1); @@ -230,10 +216,8 @@ describe('Change password', () => { expect(response.payload).toBeUndefined(); checkPasswordChangeAPICall(username, { - headers: { - ...mockRequest.headers, - authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, - }, + ...mockRequest.headers, + authorization: `Basic ${Buffer.from(`${username}:old-password`).toString('base64')}`, }); expect(authc.login).not.toHaveBeenCalled(); @@ -249,7 +233,9 @@ describe('Change password', () => { it('returns 500 if password update request fails.', async () => { const failureReason = new Error('Request failed.'); - mockScopedClusterClient.callAsCurrentUser.mockRejectedValue(failureReason); + mockContext.core.elasticsearch.client.asCurrentUser.security.changePassword.mockRejectedValue( + failureReason + ); const response = await routeHandler(mockContext, mockRequest, kibanaResponseFactory); @@ -257,7 +243,7 @@ describe('Change password', () => { expect(response.payload).toEqual(failureReason); expect(authc.login).not.toHaveBeenCalled(); - checkPasswordChangeAPICall(username, mockRequest); + checkPasswordChangeAPICall(username); }); it('successfully changes user password.', async () => { @@ -267,7 +253,7 @@ describe('Change password', () => { expect(response.payload).toBeUndefined(); expect(authc.login).not.toHaveBeenCalled(); - checkPasswordChangeAPICall(username, mockRequest); + checkPasswordChangeAPICall(username); }); }); }); diff --git a/x-pack/plugins/security/server/routes/users/change_password.ts b/x-pack/plugins/security/server/routes/users/change_password.ts index be868f841eeebe..66d36b4294883e 100644 --- a/x-pack/plugins/security/server/routes/users/change_password.ts +++ b/x-pack/plugins/security/server/routes/users/change_password.ts @@ -14,12 +14,7 @@ import { } from '../../authentication'; import { RouteDefinitionParams } from '..'; -export function defineChangeUserPasswordRoutes({ - authc, - session, - router, - clusterClient, -}: RouteDefinitionParams) { +export function defineChangeUserPasswordRoutes({ authc, session, router }: RouteDefinitionParams) { router.post( { path: '/internal/security/users/{username}/password', @@ -43,28 +38,26 @@ export function defineChangeUserPasswordRoutes({ // If user is changing their own password they should provide a proof of knowledge their // current password via sending it in `Authorization: Basic base64(username:current password)` // HTTP header no matter how they logged in to Kibana. - const scopedClusterClient = clusterClient.asScoped( - isUserChangingOwnPassword - ? { - headers: { - ...request.headers, - authorization: new HTTPAuthorizationHeader( - 'Basic', - new BasicHTTPAuthorizationHeaderCredentials( - username, - currentPassword || '' - ).toString() - ).toString(), - }, - } - : request - ); + const options = isUserChangingOwnPassword + ? { + headers: { + ...request.headers, + authorization: new HTTPAuthorizationHeader( + 'Basic', + new BasicHTTPAuthorizationHeaderCredentials( + username, + currentPassword || '' + ).toString() + ).toString(), + }, + } + : undefined; try { - await scopedClusterClient.callAsCurrentUser('shield.changePassword', { - username, - body: { password: newPassword }, - }); + await context.core.elasticsearch.client.asCurrentUser.security.changePassword( + { username, body: { password: newPassword } }, + options + ); } catch (error) { // This may happen only if user's credentials are rejected meaning that current password // isn't correct. diff --git a/x-pack/plugins/security/server/routes/users/create_or_update.ts b/x-pack/plugins/security/server/routes/users/create_or_update.ts index 5a3e50bb11d5c2..a98848a583500e 100644 --- a/x-pack/plugins/security/server/routes/users/create_or_update.ts +++ b/x-pack/plugins/security/server/routes/users/create_or_update.ts @@ -9,7 +9,7 @@ import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { RouteDefinitionParams } from '..'; -export function defineCreateOrUpdateUserRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineCreateOrUpdateUserRoutes({ router }: RouteDefinitionParams) { router.post( { path: '/internal/security/users/{username}', @@ -28,7 +28,7 @@ export function defineCreateOrUpdateUserRoutes({ router, clusterClient }: RouteD }, createLicensedRouteHandler(async (context, request, response) => { try { - await clusterClient.asScoped(request).callAsCurrentUser('shield.putUser', { + await context.core.elasticsearch.client.asCurrentUser.security.putUser({ username: request.params.username, // Omit `username`, `enabled` and all fields with `null` value. body: Object.fromEntries( diff --git a/x-pack/plugins/security/server/routes/users/delete.ts b/x-pack/plugins/security/server/routes/users/delete.ts index 99a8d5c18ab3dd..26a1765b4fbdf9 100644 --- a/x-pack/plugins/security/server/routes/users/delete.ts +++ b/x-pack/plugins/security/server/routes/users/delete.ts @@ -9,7 +9,7 @@ import { RouteDefinitionParams } from '../index'; import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; -export function defineDeleteUserRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineDeleteUserRoutes({ router }: RouteDefinitionParams) { router.delete( { path: '/internal/security/users/{username}', @@ -19,9 +19,9 @@ export function defineDeleteUserRoutes({ router, clusterClient }: RouteDefinitio }, createLicensedRouteHandler(async (context, request, response) => { try { - await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.deleteUser', { username: request.params.username }); + await context.core.elasticsearch.client.asCurrentUser.security.deleteUser({ + username: request.params.username, + }); return response.noContent(); } catch (error) { diff --git a/x-pack/plugins/security/server/routes/users/get.ts b/x-pack/plugins/security/server/routes/users/get.ts index 08679103725465..aa6a4f6be8bad7 100644 --- a/x-pack/plugins/security/server/routes/users/get.ts +++ b/x-pack/plugins/security/server/routes/users/get.ts @@ -9,7 +9,7 @@ import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { RouteDefinitionParams } from '..'; -export function defineGetUserRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineGetUserRoutes({ router }: RouteDefinitionParams) { router.get( { path: '/internal/security/users/{username}', @@ -20,9 +20,13 @@ export function defineGetUserRoutes({ router, clusterClient }: RouteDefinitionPa createLicensedRouteHandler(async (context, request, response) => { try { const username = request.params.username; - const users = (await clusterClient - .asScoped(request) - .callAsCurrentUser('shield.getUser', { username })) as Record; + const { + body: users, + } = await context.core.elasticsearch.client.asCurrentUser.security.getUser< + Record + >({ + username, + }); if (!users[username]) { return response.notFound(); diff --git a/x-pack/plugins/security/server/routes/users/get_all.ts b/x-pack/plugins/security/server/routes/users/get_all.ts index 492ab27ab27adc..3c5ca184f747c2 100644 --- a/x-pack/plugins/security/server/routes/users/get_all.ts +++ b/x-pack/plugins/security/server/routes/users/get_all.ts @@ -8,7 +8,7 @@ import { RouteDefinitionParams } from '../index'; import { wrapIntoCustomErrorResponse } from '../../errors'; import { createLicensedRouteHandler } from '../licensed_route_handler'; -export function defineGetAllUsersRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineGetAllUsersRoutes({ router }: RouteDefinitionParams) { router.get( { path: '/internal/security/users', validate: false }, createLicensedRouteHandler(async (context, request, response) => { @@ -16,7 +16,7 @@ export function defineGetAllUsersRoutes({ router, clusterClient }: RouteDefiniti return response.ok({ // Return only values since keys (user names) are already duplicated there. body: Object.values( - await clusterClient.asScoped(request).callAsCurrentUser('shield.getUser') + (await context.core.elasticsearch.client.asCurrentUser.security.getUser()).body ), }); } catch (error) { diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts index b513230b3ba6f0..dfe5faa95ae15b 100644 --- a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts +++ b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts @@ -14,7 +14,7 @@ import { RequestHandlerContext, } from '../../../../../../src/core/server'; import { SecurityLicense, SecurityLicenseFeatures } from '../../../common/licensing'; -import { AuthenticationProvider } from '../../../common/types'; +import type { AuthenticationProvider } from '../../../common/model'; import { ConfigType } from '../../config'; import { Session } from '../../session_management'; import { defineAccessAgreementRoutes } from './access_agreement'; diff --git a/x-pack/plugins/security/server/routes/views/login.ts b/x-pack/plugins/security/server/routes/views/login.ts index 93d43d04a86ca3..68becb48f596ee 100644 --- a/x-pack/plugins/security/server/routes/views/login.ts +++ b/x-pack/plugins/security/server/routes/views/login.ts @@ -7,6 +7,11 @@ import { schema } from '@kbn/config-schema'; import { parseNext } from '../../../common/parse_next'; import { LoginState } from '../../../common/login_state'; +import { shouldProviderUseLoginForm } from '../../../common/model'; +import { + LOGOUT_REASON_QUERY_STRING_PARAMETER, + NEXT_URL_QUERY_STRING_PARAMETER, +} from '../../../common/constants'; import { RouteDefinitionParams } from '..'; /** @@ -26,8 +31,8 @@ export function defineLoginRoutes({ validate: { query: schema.object( { - next: schema.maybe(schema.string()), - msg: schema.maybe(schema.string()), + [NEXT_URL_QUERY_STRING_PARAMETER]: schema.maybe(schema.string()), + [LOGOUT_REASON_QUERY_STRING_PARAMETER]: schema.maybe(schema.string()), }, { unknowns: 'allow' } ), @@ -59,7 +64,7 @@ export function defineLoginRoutes({ // Since `config.authc.sortedProviders` is based on `config.authc.providers` config we can // be sure that config is present for every provider in `config.authc.sortedProviders`. const { showInSelector, description, hint, icon } = config.authc.providers[type]?.[name]!; - const usesLoginForm = type === 'basic' || type === 'token'; + const usesLoginForm = shouldProviderUseLoginForm(type); return { type, name, diff --git a/x-pack/plugins/security/server/session_management/session.ts b/x-pack/plugins/security/server/session_management/session.ts index 757b1aaeddcbcf..4dc83a1abe4af6 100644 --- a/x-pack/plugins/security/server/session_management/session.ts +++ b/x-pack/plugins/security/server/session_management/session.ts @@ -9,7 +9,7 @@ import { randomBytes, createHash } from 'crypto'; import nodeCrypto, { Crypto } from '@elastic/node-crypto'; import type { PublicMethodsOf } from '@kbn/utility-types'; import type { KibanaRequest, Logger } from '../../../../../src/core/server'; -import type { AuthenticationProvider } from '../../common/types'; +import type { AuthenticationProvider } from '../../common/model'; import type { ConfigType } from '../config'; import type { SessionIndex, SessionIndexValue } from './session_index'; import type { SessionCookie } from './session_cookie'; diff --git a/x-pack/plugins/security/server/session_management/session_index.ts b/x-pack/plugins/security/server/session_management/session_index.ts index 96fff41d575034..45b2f4489c195d 100644 --- a/x-pack/plugins/security/server/session_management/session_index.ts +++ b/x-pack/plugins/security/server/session_management/session_index.ts @@ -5,7 +5,7 @@ */ import type { ILegacyClusterClient, Logger } from '../../../../../src/core/server'; -import type { AuthenticationProvider } from '../../common/types'; +import type { AuthenticationProvider } from '../../common/model'; import type { ConfigType } from '../config'; export interface SessionIndexOptions { diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index e58aed15a8a10a..c47ec703418453 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -160,6 +160,7 @@ export const ML_GROUP_IDS = [ML_GROUP_ID, LEGACY_ML_GROUP_ID]; /* Rule notifications options */ +export const ENABLE_CASE_CONNECTOR = false; export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [ '.email', '.slack', @@ -169,6 +170,11 @@ export const NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS = [ '.jira', '.resilient', ]; + +if (ENABLE_CASE_CONNECTOR) { + NOTIFICATION_SUPPORTED_ACTION_TYPES_IDS.push('.case'); +} + export const NOTIFICATION_THROTTLE_NO_ACTIONS = 'no_actions'; export const NOTIFICATION_THROTTLE_RULE = 'rule'; diff --git a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts index 82b803c62a9406..ff76a0fcb5593f 100644 --- a/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts +++ b/x-pack/plugins/security_solution/common/detection_engine/schemas/common/schemas.ts @@ -320,7 +320,12 @@ export type SeverityMappingOrUndefined = t.TypeOf; -export const job_status = t.keyof({ succeeded: null, failed: null, 'going to run': null }); +export const job_status = t.keyof({ + succeeded: null, + failed: null, + 'going to run': null, + 'partial failure': null, +}); export type JobStatus = t.TypeOf; export const conflicts = t.keyof({ abort: null, proceed: null }); diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx index 2c8051f902b172..50e139bcd215f9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.test.tsx @@ -6,42 +6,24 @@ import React from 'react'; import { mount } from 'enzyme'; +import { waitFor, act } from '@testing-library/react'; import { AddComment, AddCommentRefObject } from '.'; import { TestProviders } from '../../../common/mock'; -import { getFormMock } from '../__mock__/form'; import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; import { CommentRequest, CommentType } from '../../../../../case/common/api'; -import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; +import { useInsertTimeline } from '../use_insert_timeline'; import { usePostComment } from '../../containers/use_post_comment'; -import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; -import { useFormData } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data'; -import { waitFor } from '@testing-library/react'; - -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' -); - -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data' -); - -jest.mock('../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'); jest.mock('../../containers/use_post_comment'); +jest.mock('../use_insert_timeline'); -const useFormMock = useForm as jest.Mock; -const useFormDataMock = useFormData as jest.Mock; - -const useInsertTimelineMock = useInsertTimeline as jest.Mock; const usePostCommentMock = usePostComment as jest.Mock; - +const useInsertTimelineMock = useInsertTimeline as jest.Mock; const onCommentSaving = jest.fn(); const onCommentPosted = jest.fn(); const postComment = jest.fn(); -const handleCursorChange = jest.fn(); -const handleOnTimelineChange = jest.fn(); const addCommentProps = { caseId: '1234', @@ -52,15 +34,6 @@ const addCommentProps = { showLoading: false, }; -const defaultInsertTimeline = { - cursorPosition: { - start: 0, - end: 0, - }, - handleCursorChange, - handleOnTimelineChange, -}; - const defaultPostCommment = { isLoading: false, isError: false, @@ -73,14 +46,9 @@ const sampleData: CommentRequest = { }; describe('AddComment ', () => { - const formHookMock = getFormMock(sampleData); - beforeEach(() => { jest.resetAllMocks(); - useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline); usePostCommentMock.mockImplementation(() => defaultPostCommment); - useFormMock.mockImplementation(() => ({ form: formHookMock })); - useFormDataMock.mockImplementation(() => [{ comment: sampleData.comment }]); jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); }); @@ -92,14 +60,25 @@ describe('AddComment ', () => { ); + + await act(async () => { + wrapper + .find(`[data-test-subj="add-comment"] textarea`) + .first() + .simulate('change', { target: { value: sampleData.comment } }); + }); + expect(wrapper.find(`[data-test-subj="add-comment"]`).exists()).toBeTruthy(); expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeFalsy(); - wrapper.find(`[data-test-subj="submit-comment"]`).first().simulate('click'); + await act(async () => { + wrapper.find(`[data-test-subj="submit-comment"]`).first().simulate('click'); + }); + await waitFor(() => { expect(onCommentSaving).toBeCalled(); expect(postComment).toBeCalledWith(sampleData, onCommentPosted); - expect(formHookMock.reset).toBeCalled(); + expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe(''); }); }); @@ -112,6 +91,7 @@ describe('AddComment ', () => { ); + expect(wrapper.find(`[data-test-subj="loading-spinner"]`).exists()).toBeTruthy(); expect( wrapper.find(`[data-test-subj="submit-comment"]`).first().prop('isDisabled') @@ -127,15 +107,16 @@ describe('AddComment ', () => { ); + expect( wrapper.find(`[data-test-subj="submit-comment"]`).first().prop('isDisabled') ).toBeTruthy(); }); - it('should insert a quote', () => { + it('should insert a quote', async () => { const sampleQuote = 'what a cool quote'; const ref = React.createRef(); - mount( + const wrapper = mount( @@ -143,10 +124,37 @@ describe('AddComment ', () => { ); - ref.current!.addQuote(sampleQuote); - expect(formHookMock.setFieldValue).toBeCalledWith( - 'comment', + await act(async () => { + wrapper + .find(`[data-test-subj="add-comment"] textarea`) + .first() + .simulate('change', { target: { value: sampleData.comment } }); + }); + + await act(async () => { + ref.current!.addQuote(sampleQuote); + }); + + expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe( `${sampleData.comment}\n\n${sampleQuote}` ); }); + + it('it should insert a timeline', async () => { + useInsertTimelineMock.mockImplementation((comment, onTimelineAttached) => { + onTimelineAttached(`[title](url)`); + }); + + const wrapper = mount( + + + + + + ); + + await waitFor(() => { + expect(wrapper.find(`[data-test-subj="add-comment"] textarea`).text()).toBe('[title](url)'); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx index 859ba3d1a0951d..daa7c24858b942 100644 --- a/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/add_comment/index.tsx @@ -12,12 +12,11 @@ import { CommentType } from '../../../../../case/common/api'; import { usePostComment } from '../../containers/use_post_comment'; import { Case } from '../../containers/types'; import { MarkdownEditorForm } from '../../../common/components/markdown_editor/eui_form'; -import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; import { Form, useForm, UseField, useFormData } from '../../../shared_imports'; import * as i18n from './translations'; import { schema, AddCommentFormSchema } from './schema'; -import { useTimelineClick } from '../../../common/utils/timeline/use_timeline_click'; +import { useInsertTimeline } from '../use_insert_timeline'; const MySpinner = styled(EuiLoadingSpinner)` position: absolute; @@ -56,12 +55,6 @@ export const AddComment = React.memo( const { setFieldValue, reset, submit } = form; const [{ comment }] = useFormData<{ comment: string }>({ form, watch: [fieldName] }); - const onCommentChange = useCallback((newValue) => setFieldValue(fieldName, newValue), [ - setFieldValue, - ]); - - const { handleCursorChange } = useInsertTimeline(comment, onCommentChange); - const addQuote = useCallback( (quote) => { setFieldValue(fieldName, `${comment}${comment.length > 0 ? '\n\n' : ''}${quote}`); @@ -73,7 +66,12 @@ export const AddComment = React.memo( addQuote, })); - const handleTimelineClick = useTimelineClick(); + const onTimelineAttached = useCallback( + (newValue: string) => setFieldValue(fieldName, newValue), + [setFieldValue] + ); + + useInsertTimeline(comment ?? '', onTimelineAttached); const onSubmit = useCallback(async () => { const { isValid, data } = await submit(); @@ -98,8 +96,6 @@ export const AddComment = React.memo( isDisabled: isLoading, dataTestSubj: 'add-comment', placeholder: i18n.ADD_COMMENT_HELP_TEXT, - onCursorPositionUpdate: handleCursorChange, - onClickTimeline: handleTimelineClick, bottomRightContent: ( { + const formHookMock = getFormMock({ connectorId: connectorsMock[0].id }); + + beforeEach(() => { + jest.resetAllMocks(); + useFormMock.mockImplementation(() => ({ form: formHookMock })); + }); + + it('it should render', async () => { + const wrapper = mount( +
+ + + ); + + expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeTruthy(); + }); + + it('it should not render when is not in edit mode', async () => { + const wrapper = mount( +
+ + + ); + + expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx b/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx index 7de7b3d6b2a969..9017365eea02be 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/connector_selector/form.tsx @@ -4,8 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; +import { isEmpty } from 'lodash/fp'; import { EuiFormRow } from '@elastic/eui'; -import React, { useCallback, useEffect } from 'react'; import { FieldHook, getFieldValidityAndErrorMessage } from '../../../shared_imports'; import { ConnectorsDropdown } from '../configure_cases/connectors_dropdown'; @@ -14,9 +15,8 @@ import { ActionConnector } from '../../../../../case/common/api/cases'; interface ConnectorSelectorProps { connectors: ActionConnector[]; dataTestSubj: string; - defaultValue?: ActionConnector; disabled: boolean; - field: FieldHook; + field: FieldHook; idAria: string; isEdit: boolean; isLoading: boolean; @@ -24,7 +24,6 @@ interface ConnectorSelectorProps { export const ConnectorSelector = ({ connectors, dataTestSubj, - defaultValue, disabled = false, field, idAria, @@ -32,19 +31,6 @@ export const ConnectorSelector = ({ isLoading = false, }: ConnectorSelectorProps) => { const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - - useEffect(() => { - field.setValue(defaultValue); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [defaultValue]); - - const handleContentChange = useCallback( - (newConnector: string) => { - field.setValue(newConnector); - }, - [field] - ); - return isEdit ? ( ) : null; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/cases_dropdown.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/case/cases_dropdown.tsx new file mode 100644 index 00000000000000..931e23e811b1b1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/cases_dropdown.tsx @@ -0,0 +1,72 @@ +/* + * 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 { EuiFormRow, EuiSuperSelect, EuiSuperSelectOption } from '@elastic/eui'; +import React, { memo, useMemo, useCallback } from 'react'; +import { Case } from '../../../containers/types'; + +import * as i18n from './translations'; + +interface CaseDropdownProps { + isLoading: boolean; + cases: Case[]; + selectedCase?: string; + onCaseChanged: (id: string) => void; +} + +export const ADD_CASE_BUTTON_ID = 'add-case'; + +const addNewCase = { + value: ADD_CASE_BUTTON_ID, + inputDisplay: ( + + {i18n.CASE_CONNECTOR_ADD_NEW_CASE} + + ), + 'data-test-subj': 'dropdown-connector-add-connector', +}; + +const CasesDropdownComponent: React.FC = ({ + isLoading, + cases, + selectedCase, + onCaseChanged, +}) => { + const caseOptions: Array> = useMemo( + () => + cases.reduce>>( + (acc, theCase) => [ + ...acc, + { + value: theCase.id, + inputDisplay: {theCase.title}, + 'data-test-subj': `case-connector-cases-dropdown-${theCase.id}`, + }, + ], + [] + ), + [cases] + ); + + const options = useMemo(() => [...caseOptions, addNewCase], [caseOptions]); + const onChange = useCallback((id: string) => onCaseChanged(id), [onCaseChanged]); + + return ( + + + + ); +}; + +export const CasesDropdown = memo(CasesDropdownComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx new file mode 100644 index 00000000000000..28e051a713bf41 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/existing_case.tsx @@ -0,0 +1,54 @@ +/* + * 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, { memo, useMemo, useCallback } from 'react'; +import { useGetCases } from '../../../containers/use_get_cases'; +import { useCreateCaseModal } from '../../use_create_case_modal'; +import { CasesDropdown, ADD_CASE_BUTTON_ID } from './cases_dropdown'; + +interface ExistingCaseProps { + selectedCase: string | null; + onCaseChanged: (id: string) => void; +} + +const ExistingCaseComponent: React.FC = ({ onCaseChanged, selectedCase }) => { + const { data: cases, loading: isLoadingCases, refetchCases } = useGetCases(); + + const onCaseCreated = useCallback(() => refetchCases(), [refetchCases]); + + const { Modal: CreateCaseModal, openModal } = useCreateCaseModal({ onCaseCreated }); + + const onChange = useCallback( + (id: string) => { + if (id === ADD_CASE_BUTTON_ID) { + openModal(); + return; + } + + onCaseChanged(id); + }, + [onCaseChanged, openModal] + ); + + const isCasesLoading = useMemo( + () => isLoadingCases.includes('cases') || isLoadingCases.includes('caseUpdate'), + [isLoadingCases] + ); + + return ( + <> + + + + ); +}; + +export const ExistingCase = memo(ExistingCaseComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/fields.tsx b/x-pack/plugins/security_solution/public/cases/components/connectors/case/fields.tsx new file mode 100644 index 00000000000000..91087138e52d58 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/fields.tsx @@ -0,0 +1,101 @@ +/* + * 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. + */ + +/* eslint-disable @kbn/eslint/no-restricted-paths */ + +import React, { useCallback, useEffect, useState } from 'react'; +import styled from 'styled-components'; +import { EuiCallOut } from '@elastic/eui'; + +import { ActionParamsProps } from '../../../../../../triggers_actions_ui/public/types'; +import { CommentType } from '../../../../../../case/common/api'; + +import { CaseActionParams } from './types'; +import { ExistingCase } from './existing_case'; + +import * as i18n from './translations'; + +const Container = styled.div` + ${({ theme }) => ` + margin-top: ${theme.eui?.euiSize ?? '16px'}; + `} +`; + +const defaultAlertComment = { + type: CommentType.alert, + alertId: '{{context.rule.id}}', + index: '{{context.rule.output_index}}', +}; + +const CaseParamsFields: React.FunctionComponent> = ({ + actionParams, + editAction, + index, + errors, + messageVariables, + actionConnector, +}) => { + const { caseId = null, comment = defaultAlertComment } = actionParams.subActionParams ?? {}; + + const [selectedCase, setSelectedCase] = useState(null); + + const editSubActionProperty = useCallback( + (key: string, value: unknown) => { + const newProps = { ...actionParams.subActionParams, [key]: value }; + editAction('subActionParams', newProps, index); + }, + [actionParams.subActionParams, editAction, index] + ); + + const onCaseChanged = useCallback( + (id: string) => { + setSelectedCase(id); + editSubActionProperty('caseId', id); + }, + [editSubActionProperty] + ); + + useEffect(() => { + if (!actionParams.subAction) { + editAction('subAction', 'addComment', index); + } + + if (!actionParams.subActionParams?.caseId) { + editSubActionProperty('caseId', caseId); + } + + if (!actionParams.subActionParams?.comment) { + editSubActionProperty('comment', comment); + } + + if (caseId != null) { + setSelectedCase((prevCaseId) => (prevCaseId !== caseId ? caseId : prevCaseId)); + } + + // editAction creates an infinity loop. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + actionConnector, + index, + actionParams.subActionParams?.caseId, + actionParams.subActionParams?.comment, + caseId, + comment, + actionParams.subAction, + ]); + + return ( + <> + + + + + + ); +}; + +// eslint-disable-next-line import/no-default-export +export { CaseParamsFields as default }; diff --git a/x-pack/plugins/security_solution/public/common/lib/connectors/case/index.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/case/index.ts similarity index 57% rename from x-pack/plugins/security_solution/public/common/lib/connectors/case/index.ts rename to x-pack/plugins/security_solution/public/cases/components/connectors/case/index.ts index 271b1bfd2e3dea..0aacd619917719 100644 --- a/x-pack/plugins/security_solution/public/common/lib/connectors/case/index.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/index.ts @@ -3,11 +3,29 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { lazy } from 'react'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { ActionTypeModel } from '../../../../../../triggers_actions_ui/public/types'; +import { CaseActionParams } from './types'; import * as i18n from './translations'; +interface ValidationResult { + errors: { + caseId: string[]; + }; +} + +const validateParams = (actionParams: CaseActionParams) => { + const validationResult: ValidationResult = { errors: { caseId: [] } }; + + if (actionParams.subActionParams && !actionParams.subActionParams.caseId) { + validationResult.errors.caseId.push(i18n.CASE_CONNECTOR_CASE_REQUIRED); + } + + return validationResult; +}; + export function getActionType(): ActionTypeModel { return { id: '.case', @@ -15,8 +33,8 @@ export function getActionType(): ActionTypeModel { selectMessage: i18n.CASE_CONNECTOR_DESC, actionTypeTitle: i18n.CASE_CONNECTOR_TITLE, validateConnector: () => ({ errors: {} }), - validateParams: () => ({ errors: {} }), + validateParams, actionConnectorFields: null, - actionParamsFields: null, + actionParamsFields: lazy(() => import('./fields')), }; } diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts new file mode 100644 index 00000000000000..8cfcf2b9a073b1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/translations.ts @@ -0,0 +1,86 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export * from '../../../translations'; + +export const CASE_CONNECTOR_DESC = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.selectMessageText', + { + defaultMessage: 'Create or update a case.', + } +); + +export const CASE_CONNECTOR_TITLE = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.actionTypeTitle', + { + defaultMessage: 'Cases', + } +); + +export const CASE_CONNECTOR_COMMENT_LABEL = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.commentLabel', + { + defaultMessage: 'Comment', + } +); + +export const CASE_CONNECTOR_COMMENT_REQUIRED = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.commentRequired', + { + defaultMessage: 'Comment is required.', + } +); + +export const CASE_CONNECTOR_CASES_DROPDOWN_ROW_LABEL = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.casesDropdownRowLabel', + { + defaultMessage: 'Case', + } +); + +export const CASE_CONNECTOR_CASES_DROPDOWN_PLACEHOLDER = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.casesDropdownPlaceholder', + { + defaultMessage: 'Select case', + } +); + +export const CASE_CONNECTOR_CASES_OPTION_NEW_CASE = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.optionAddNewCase', + { + defaultMessage: 'Add to a new case', + } +); + +export const CASE_CONNECTOR_CASES_OPTION_EXISTING_CASE = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.optionAddToExistingCase', + { + defaultMessage: 'Add to existing case', + } +); + +export const CASE_CONNECTOR_CASE_REQUIRED = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.caseRequired', + { + defaultMessage: 'You must select a case.', + } +); + +export const CASE_CONNECTOR_CALL_OUT_INFO = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.callOutInfo', + { + defaultMessage: 'All alerts after rule creation will be appended to the selected case.', + } +); + +export const CASE_CONNECTOR_ADD_NEW_CASE = i18n.translate( + 'xpack.securitySolution.case.components.connectors.case.addNewCaseOption', + { + defaultMessage: 'Add new case', + } +); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/case/types.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/case/types.ts new file mode 100644 index 00000000000000..8173a814c2d890 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/case/types.ts @@ -0,0 +1,17 @@ +/* + * 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 CaseActionParams { + subAction: string; + subActionParams: { + caseId: string; + comment: { + alertId: string; + index: string; + type: 'alert'; + }; + }; +} diff --git a/x-pack/plugins/security_solution/public/common/lib/connectors/config.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/config.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/lib/connectors/config.ts rename to x-pack/plugins/security_solution/public/cases/components/connectors/config.ts diff --git a/x-pack/plugins/security_solution/public/common/lib/connectors/index.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/index.ts similarity index 79% rename from x-pack/plugins/security_solution/public/common/lib/connectors/index.ts rename to x-pack/plugins/security_solution/public/cases/components/connectors/index.ts index 58d7e89e080e75..e77aa9bdd84b1e 100644 --- a/x-pack/plugins/security_solution/public/common/lib/connectors/index.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/index.ts @@ -5,3 +5,7 @@ */ export { getActionType as getCaseConnectorUI } from './case'; + +export * from './config'; +export * from './types'; +export * from './utils'; diff --git a/x-pack/plugins/security_solution/public/common/lib/connectors/types.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/types.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/lib/connectors/types.ts rename to x-pack/plugins/security_solution/public/cases/components/connectors/types.ts diff --git a/x-pack/plugins/security_solution/public/common/lib/connectors/utils.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/utils.ts similarity index 100% rename from x-pack/plugins/security_solution/public/common/lib/connectors/utils.ts rename to x-pack/plugins/security_solution/public/cases/components/connectors/utils.ts diff --git a/x-pack/plugins/security_solution/public/cases/components/create/connector.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/connector.test.tsx new file mode 100644 index 00000000000000..89b9e3b30ede1d --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/connector.test.tsx @@ -0,0 +1,186 @@ +/* + * 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 { mount } from 'enzyme'; +import { act, waitFor } from '@testing-library/react'; +import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; + +import { useForm, Form, FormHook } from '../../../shared_imports'; +import { connectorsMock } from '../../containers/mock'; +import { Connector } from './connector'; +import { useConnectors } from '../../containers/configure/use_connectors'; +import { useGetIncidentTypes } from '../settings/resilient/use_get_incident_types'; +import { useGetSeverity } from '../settings/resilient/use_get_severity'; + +jest.mock('../../../common/lib/kibana', () => { + return { + useKibana: () => ({ + services: { + notifications: {}, + http: {}, + }, + }), + }; +}); +jest.mock('../../containers/configure/use_connectors'); +jest.mock('../settings/resilient/use_get_incident_types'); +jest.mock('../settings/resilient/use_get_severity'); + +const useConnectorsMock = useConnectors as jest.Mock; +const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock; +const useGetSeverityMock = useGetSeverity as jest.Mock; + +const useGetIncidentTypesResponse = { + isLoading: false, + incidentTypes: [ + { + id: 19, + name: 'Malware', + }, + { + id: 21, + name: 'Denial of Service', + }, + ], +}; + +const useGetSeverityResponse = { + isLoading: false, + severity: [ + { + id: 4, + name: 'Low', + }, + { + id: 5, + name: 'Medium', + }, + { + id: 6, + name: 'High', + }, + ], +}; + +describe('Connector', () => { + let globalForm: FormHook; + + const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm<{ connectorId: string; fields: Record | null }>({ + defaultValue: { connectorId: connectorsMock[0].id, fields: null }, + }); + + globalForm = form; + + return
{children}
; + }; + + beforeEach(() => { + jest.resetAllMocks(); + useConnectorsMock.mockReturnValue({ loading: false, connectors: connectorsMock }); + useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse); + useGetSeverityMock.mockReturnValue(useGetSeverityResponse); + }); + + it('it renders', async () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="caseConnectors"]`).exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="connector-settings"]`).exists()).toBeTruthy(); + + waitFor(() => { + expect(wrapper.find(`[data-test-subj="connector-settings-sn"]`).exists()).toBeTruthy(); + }); + }); + + it('it is loading when fetching connectors', async () => { + useConnectorsMock.mockReturnValue({ loading: true, connectors: connectorsMock }); + const wrapper = mount( + + + + ); + + expect( + wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('isLoading') + ).toEqual(true); + }); + + it('it is disabled when fetching connectors', async () => { + useConnectorsMock.mockReturnValue({ loading: true, connectors: connectorsMock }); + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('disabled')).toEqual( + true + ); + }); + + it('it is disabled and loading when passing loading as true', async () => { + const wrapper = mount( + + + + ); + + expect( + wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('isLoading') + ).toEqual(true); + expect(wrapper.find('[data-test-subj="dropdown-connectors"]').first().prop('disabled')).toEqual( + true + ); + }); + + it(`it should change connector`, async () => { + const wrapper = mount( + + + + ); + + await waitFor(() => { + expect(wrapper.find(`[data-test-subj="connector-settings-resilient"]`).exists()).toBeFalsy(); + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find(`button[data-test-subj="dropdown-connector-resilient-2"]`).simulate('click'); + wrapper.update(); + }); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find(`[data-test-subj="connector-settings-resilient"]`).exists()).toBeTruthy(); + }); + + act(() => { + ((wrapper.find(EuiComboBox).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange([{ value: '19', label: 'Denial of Service' }]); + }); + + act(() => { + wrapper + .find('select[data-test-subj="severitySelect"]') + .first() + .simulate('change', { + target: { value: '4' }, + }); + }); + + await waitFor(() => { + expect(globalForm.getFormData()).toEqual({ + connectorId: 'resilient-2', + fields: { incidentTypes: ['19'], severityCode: '4' }, + }); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx b/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx new file mode 100644 index 00000000000000..b2a0f3c351552b --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/connector.tsx @@ -0,0 +1,83 @@ +/* + * 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, { memo, useEffect } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { ConnectorTypeFields } from '../../../../../case/common/api/connectors'; +import { UseField, useFormData, FieldHook } from '../../../shared_imports'; +import { useConnectors } from '../../containers/configure/use_connectors'; +import { ConnectorSelector } from '../connector_selector/form'; +import { SettingFieldsForm } from '../settings/fields_form'; +import { ActionConnector } from '../../containers/types'; +import { getConnectorById } from '../configure_cases/utils'; + +interface Props { + isLoading: boolean; +} + +interface SettingsFieldProps { + connectors: ActionConnector[]; + field: FieldHook; + isEdit: boolean; +} + +const SettingsField = ({ connectors, isEdit, field }: SettingsFieldProps) => { + const [{ connectorId }] = useFormData({ watch: ['connectorId'] }); + const { setValue } = field; + const connector = getConnectorById(connectorId, connectors) ?? null; + + useEffect(() => { + if (connectorId) { + setValue(null); + } + }, [setValue, connectorId]); + + return ( + + ); +}; + +const ConnectorComponent: React.FC = ({ isLoading }) => { + const { loading: isLoadingConnectors, connectors } = useConnectors(); + + return ( + + + + + + + + + ); +}; + +ConnectorComponent.displayName = 'ConnectorComponent'; + +export const Connector = memo(ConnectorComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/description.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/description.test.tsx new file mode 100644 index 00000000000000..201a61febc6287 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/description.test.tsx @@ -0,0 +1,57 @@ +/* + * 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 { mount } from 'enzyme'; +import { act } from '@testing-library/react'; + +import { useForm, Form, FormHook } from '../../../shared_imports'; +import { Description } from './description'; + +describe('Description', () => { + let globalForm: FormHook; + + const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm<{ description: string }>({ + defaultValue: { description: 'My description' }, + }); + + globalForm = form; + + return
{children}
; + }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('it renders', async () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="caseDescription"]`).exists()).toBeTruthy(); + }); + + it('it changes the description', async () => { + const wrapper = mount( + + + + ); + + await act(async () => { + wrapper + .find(`[data-test-subj="caseDescription"] textarea`) + .first() + .simulate('change', { target: { value: 'My new description' } }); + }); + + expect(globalForm.getFormData()).toEqual({ description: 'My new description' }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/description.tsx b/x-pack/plugins/security_solution/public/cases/components/create/description.tsx new file mode 100644 index 00000000000000..f130bd14644f83 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/description.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 React, { memo } from 'react'; +import { MarkdownEditorForm } from '../../../common/components/markdown_editor/eui_form'; +import { UseField } from '../../../shared_imports'; + +interface Props { + isLoading: boolean; +} + +export const fieldName = 'description'; + +const DescriptionComponent: React.FC = ({ isLoading }) => ( + +); + +DescriptionComponent.displayName = 'DescriptionComponent'; + +export const Description = memo(DescriptionComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx new file mode 100644 index 00000000000000..e64b2b3a050801 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/form.test.tsx @@ -0,0 +1,66 @@ +/* + * 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 { mount } from 'enzyme'; + +import { useForm, Form } from '../../../shared_imports'; +import { useGetTags } from '../../containers/use_get_tags'; +import { useConnectors } from '../../containers/configure/use_connectors'; +import { connectorsMock } from '../../containers/mock'; +import { schema, FormProps } from './schema'; +import { CreateCaseForm } from './form'; + +jest.mock('../../containers/use_get_tags'); +jest.mock('../../containers/configure/use_connectors'); +const useGetTagsMock = useGetTags as jest.Mock; +const useConnectorsMock = useConnectors as jest.Mock; + +const initialCaseValue: FormProps = { + description: '', + tags: [], + title: '', + connectorId: 'none', + fields: null, +}; + +describe('CreateCaseForm', () => { + const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm({ + defaultValue: initialCaseValue, + options: { stripEmptyFields: false }, + schema, + }); + + return
{children}
; + }; + + beforeEach(() => { + jest.resetAllMocks(); + useGetTagsMock.mockReturnValue({ tags: ['test'] }); + useConnectorsMock.mockReturnValue({ loading: false, connectors: connectorsMock }); + }); + + it('it renders with steps', async () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="case-creation-form-steps"]`).exists()).toBeTruthy(); + }); + + it('it renders without steps', async () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find(`[data-test-subj="case-creation-form-steps"]`).exists()).toBeFalsy(); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form.tsx new file mode 100644 index 00000000000000..40db4d792c1c8f --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/form.tsx @@ -0,0 +1,94 @@ +/* + * 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, { useMemo } from 'react'; +import { EuiLoadingSpinner, EuiSteps } from '@elastic/eui'; +import styled, { css } from 'styled-components'; + +import { useFormContext } from '../../../shared_imports'; + +import { Title } from './title'; +import { Description } from './description'; +import { Tags } from './tags'; +import { Connector } from './connector'; +import * as i18n from './translations'; + +interface ContainerProps { + big?: boolean; +} + +const Container = styled.div.attrs((props) => props)` + ${({ big, theme }) => css` + margin-top: ${big ? theme.eui?.euiSizeXL ?? '32px' : theme.eui?.euiSize ?? '16px'}; + `} +`; + +const MySpinner = styled(EuiLoadingSpinner)` + position: absolute; + top: 50%; + left: 50%; + z-index: 99; +`; + +interface Props { + withSteps?: boolean; +} + +export const CreateCaseForm: React.FC = React.memo(({ withSteps = true }) => { + const { isSubmitting } = useFormContext(); + + const firstStep = useMemo( + () => ({ + title: i18n.STEP_ONE_TITLE, + children: ( + <> + + <Container> + <Tags isLoading={isSubmitting} /> + </Container> + <Container big> + <Description isLoading={isSubmitting} /> + </Container> + </> + ), + }), + [isSubmitting] + ); + + const secondStep = useMemo( + () => ({ + title: i18n.STEP_TWO_TITLE, + children: ( + <Container> + <Connector isLoading={isSubmitting} /> + </Container> + ), + }), + [isSubmitting] + ); + + const allSteps = useMemo(() => [firstStep, secondStep], [firstStep, secondStep]); + + return ( + <> + {isSubmitting && <MySpinner data-test-subj="create-case-loading-spinner" size="xl" />} + {withSteps ? ( + <EuiSteps + headingElement="h2" + steps={allSteps} + data-test-subj={'case-creation-form-steps'} + /> + ) : ( + <> + {firstStep.children} + {secondStep.children} + </> + )} + </> + ); +}); + +CreateCaseForm.displayName = 'CreateCaseForm'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx b/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx new file mode 100644 index 00000000000000..e11e508b60ebf0 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/form_context.tsx @@ -0,0 +1,66 @@ +/* + * 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, { useCallback, useEffect } from 'react'; + +import { schema, FormProps } from './schema'; +import { Form, useForm } from '../../../shared_imports'; +import { + getConnectorById, + getNoneConnector, + normalizeActionConnector, +} from '../configure_cases/utils'; +import { usePostCase } from '../../containers/use_post_case'; +import { useConnectors } from '../../containers/configure/use_connectors'; +import { Case } from '../../containers/types'; + +const initialCaseValue: FormProps = { + description: '', + tags: [], + title: '', + connectorId: 'none', + fields: null, +}; + +interface Props { + onSuccess?: (theCase: Case) => void; +} + +export const FormContext: React.FC<Props> = ({ children, onSuccess }) => { + const { connectors } = useConnectors(); + const { caseData, postCase } = usePostCase(); + + const submitCase = useCallback( + async ({ connectorId: dataConnectorId, fields, ...dataWithoutConnectorId }, isValid) => { + if (isValid) { + const caseConnector = getConnectorById(dataConnectorId, connectors); + const connectorToUpdate = caseConnector + ? normalizeActionConnector(caseConnector, fields) + : getNoneConnector(); + + await postCase({ ...dataWithoutConnectorId, connector: connectorToUpdate }); + } + }, + [postCase, connectors] + ); + + const { form } = useForm<FormProps>({ + defaultValue: initialCaseValue, + options: { stripEmptyFields: false }, + schema, + onSubmit: submitCase, + }); + + useEffect(() => { + if (caseData && onSuccess) { + onSuccess(caseData); + } + }, [caseData, onSuccess]); + + return <Form form={form}>{children}</Form>; +}; + +FormContext.displayName = 'FormContext'; diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx index 7902c7065d9a35..29073e7774158f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx @@ -5,71 +5,40 @@ */ import React from 'react'; -import { mount } from 'enzyme'; -import { Create } from '.'; +import { mount, ReactWrapper } from 'enzyme'; +import { act, waitFor } from '@testing-library/react'; +import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; import { TestProviders } from '../../../common/mock'; -import { getFormMock } from '../__mock__/form'; -import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; -import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; import { usePostCase } from '../../containers/use_post_case'; import { useGetTags } from '../../containers/use_get_tags'; - -import { useForm } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form'; -import { useFormData } from '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data'; - -import { waitFor } from '@testing-library/react'; import { useConnectors } from '../../containers/configure/use_connectors'; import { connectorsMock } from '../../containers/configure/mock'; import { ConnectorTypes } from '../../../../../case/common/api/connectors'; +import { Router, routeData, mockHistory, mockLocation } from '../__mock__/router'; +import { useGetIncidentTypes } from '../settings/resilient/use_get_incident_types'; +import { useGetSeverity } from '../settings/resilient/use_get_severity'; +import { useGetIssueTypes } from '../settings/jira/use_get_issue_types'; +import { useGetFieldsByIssueType } from '../settings/jira/use_get_fields_by_issue_type'; +import { Create } from '.'; -jest.mock('@elastic/eui', () => { - const original = jest.requireActual('@elastic/eui'); - return { - ...original, - // eslint-disable-next-line react/display-name - EuiFieldText: () => <input />, - }; -}); -jest.mock('../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'); jest.mock('../../containers/use_post_case'); - -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form' -); - -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data' -); - jest.mock('../../containers/use_get_tags'); jest.mock('../../containers/configure/use_connectors'); -jest.mock( - '../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider', - () => ({ - FormDataProvider: ({ children }: { children: ({ tags }: { tags: string[] }) => void }) => - children({ tags: ['rad', 'dude'] }), - }) -); -const useConnectorsMock = useConnectors as jest.Mock; -const useFormMock = useForm as jest.Mock; -const useFormDataMock = useFormData as jest.Mock; +jest.mock('../settings/resilient/use_get_incident_types'); +jest.mock('../settings/resilient/use_get_severity'); +jest.mock('../settings/jira/use_get_issue_types'); +jest.mock('../settings/jira/use_get_fields_by_issue_type'); +jest.mock('../settings/jira/use_get_single_issue'); +jest.mock('../settings/jira/use_get_issues'); -const useInsertTimelineMock = useInsertTimeline as jest.Mock; +const useConnectorsMock = useConnectors as jest.Mock; const usePostCaseMock = usePostCase as jest.Mock; - +const useGetIncidentTypesMock = useGetIncidentTypes as jest.Mock; +const useGetSeverityMock = useGetSeverity as jest.Mock; +const useGetIssueTypesMock = useGetIssueTypes as jest.Mock; +const useGetFieldsByIssueTypeMock = useGetFieldsByIssueType as jest.Mock; const postCase = jest.fn(); -const handleCursorChange = jest.fn(); -const handleOnTimelineChange = jest.fn(); - -const defaultInsertTimeline = { - cursorPosition: { - start: 0, - end: 0, - }, - handleCursorChange, - handleOnTimelineChange, -}; const sampleTags = ['coke', 'pepsi']; const sampleData = { @@ -83,27 +52,117 @@ const sampleData = { type: ConnectorTypes.none, }, }; + const defaultPostCase = { isLoading: false, isError: false, caseData: null, postCase, }; + const sampleConnectorData = { loading: false, connectors: [] }; + +const useGetIncidentTypesResponse = { + isLoading: false, + incidentTypes: [ + { + id: 19, + name: 'Malware', + }, + { + id: 21, + name: 'Denial of Service', + }, + ], +}; + +const useGetSeverityResponse = { + isLoading: false, + severity: [ + { + id: 4, + name: 'Low', + }, + { + id: 5, + name: 'Medium', + }, + { + id: 6, + name: 'High', + }, + ], +}; + +const useGetIssueTypesResponse = { + isLoading: false, + issueTypes: [ + { + id: '10006', + name: 'Task', + }, + { + id: '10007', + name: 'Bug', + }, + ], +}; + +const useGetFieldsByIssueTypeResponse = { + isLoading: false, + fields: { + summary: { allowedValues: [], defaultValue: {} }, + labels: { allowedValues: [], defaultValue: {} }, + description: { allowedValues: [], defaultValue: {} }, + priority: { + allowedValues: [ + { + name: 'Medium', + id: '3', + }, + { + name: 'Low', + id: '2', + }, + ], + defaultValue: { name: 'Medium', id: '3' }, + }, + }, +}; + +const fillForm = async (wrapper: ReactWrapper) => { + await act(async () => { + wrapper + .find(`[data-test-subj="caseTitle"] input`) + .first() + .simulate('change', { target: { value: sampleData.title } }); + }); + + await act(async () => { + wrapper + .find(`[data-test-subj="caseDescription"] textarea`) + .first() + .simulate('change', { target: { value: sampleData.description } }); + }); + + await waitFor(() => { + ((wrapper.find(EuiComboBox).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange(sampleTags.map((tag) => ({ label: tag }))); + }); +}; + describe('Create case', () => { const fetchTags = jest.fn(); - const formHookMock = getFormMock(sampleData); beforeEach(() => { jest.resetAllMocks(); - useInsertTimelineMock.mockImplementation(() => defaultInsertTimeline); usePostCaseMock.mockImplementation(() => defaultPostCase); - useFormMock.mockImplementation(() => ({ form: formHookMock })); - useFormDataMock.mockImplementation(() => [ - { - description: sampleData.description, - }, - ]); useConnectorsMock.mockReturnValue(sampleConnectorData); + useGetIncidentTypesMock.mockReturnValue(useGetIncidentTypesResponse); + useGetSeverityMock.mockReturnValue(useGetSeverityResponse); + useGetIssueTypesMock.mockReturnValue(useGetIssueTypesResponse); + useGetFieldsByIssueTypeMock.mockReturnValue(useGetFieldsByIssueTypeResponse); + jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); (useGetTags as jest.Mock).mockImplementation(() => ({ tags: sampleTags, @@ -112,7 +171,32 @@ describe('Create case', () => { }); describe('Step 1 - Case Fields', () => { + it('it renders', async () => { + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <Create /> + </Router> + </TestProviders> + ); + + expect(wrapper.find(`[data-test-subj="caseTitle"]`).first().exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="caseDescription"]`).first().exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="caseTags"]`).first().exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="caseConnectors"]`).first().exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="create-case-submit"]`).first().exists()).toBeTruthy(); + expect(wrapper.find(`[data-test-subj="create-case-cancel"]`).first().exists()).toBeTruthy(); + expect( + wrapper.find(`[data-test-subj="case-creation-form-steps"]`).first().exists() + ).toBeTruthy(); + }); + it('should post case on submit click', async () => { + useConnectorsMock.mockReturnValue({ + ...sampleConnectorData, + connectors: connectorsMock, + }); + const wrapper = mount( <TestProviders> <Router history={mockHistory}> @@ -120,7 +204,13 @@ describe('Create case', () => { </Router> </TestProviders> ); - wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + + await fillForm(wrapper); + wrapper.update(); + + await act(async () => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + }); await waitFor(() => expect(postCase).toBeCalledWith(sampleData)); }); @@ -132,15 +222,18 @@ describe('Create case', () => { </Router> </TestProviders> ); + wrapper.find(`[data-test-subj="create-case-cancel"]`).first().simulate('click'); await waitFor(() => expect(mockHistory.push).toHaveBeenCalledWith('/')); }); + it('should redirect to new case when caseData is there', async () => { - const sampleId = '777777'; + const sampleId = 'case-id'; usePostCaseMock.mockImplementation(() => ({ ...defaultPostCase, caseData: { id: sampleId }, })); + mount( <TestProviders> <Router history={mockHistory}> @@ -148,11 +241,11 @@ describe('Create case', () => { </Router> </TestProviders> ); - await waitFor(() => expect(mockHistory.push).toHaveBeenNthCalledWith(1, '/777777')); + + await waitFor(() => expect(mockHistory.push).toHaveBeenNthCalledWith(1, '/case-id')); }); it('should render spinner when loading', async () => { - usePostCaseMock.mockImplementation(() => ({ ...defaultPostCase, isLoading: true })); const wrapper = mount( <TestProviders> <Router history={mockHistory}> @@ -160,11 +253,87 @@ describe('Create case', () => { </Router> </TestProviders> ); + + await fillForm(wrapper); + await act(async () => { + await wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + wrapper.update(); + expect( + wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists() + ).toBeTruthy(); + }); + }); + }); + + describe('Step 2 - Connector Fields', () => { + it(`it should submit a Jira connector`, async () => { + useConnectorsMock.mockReturnValue({ + ...sampleConnectorData, + connectors: connectorsMock, + }); + + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <Create /> + </Router> + </TestProviders> + ); + + await fillForm(wrapper); + await waitFor(() => { + expect(wrapper.find(`[data-test-subj="connector-settings-jira"]`).exists()).toBeFalsy(); + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find(`button[data-test-subj="dropdown-connector-jira-1"]`).simulate('click'); + wrapper.update(); + }); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find(`[data-test-subj="connector-settings-jira"]`).exists()).toBeTruthy(); + }); + + act(() => { + wrapper + .find('select[data-test-subj="issueTypeSelect"]') + .first() + .simulate('change', { + target: { value: '10007' }, + }); + }); + + act(() => { + wrapper + .find('select[data-test-subj="prioritySelect"]') + .first() + .simulate('change', { + target: { value: '2' }, + }); + }); + + await act(async () => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + }); + await waitFor(() => - expect(wrapper.find(`[data-test-subj="create-case-loading-spinner"]`).exists()).toBeTruthy() + expect(postCase).toBeCalledWith({ + ...sampleData, + connector: { + id: 'jira-1', + name: 'Jira', + type: '.jira', + fields: { issueType: '10007', parent: null, priority: '2' }, + }, + }) ); }); - it('Tag options render with new tags added', async () => { + + it(`it should submit a resilient connector`, async () => { + useConnectorsMock.mockReturnValue({ + ...sampleConnectorData, + connectors: connectorsMock, + }); + const wrapper = mount( <TestProviders> <Router history={mockHistory}> @@ -172,63 +341,109 @@ describe('Create case', () => { </Router> </TestProviders> ); - await waitFor(() => + + await fillForm(wrapper); + await waitFor(() => { expect( - wrapper - .find(`[data-test-subj="caseTags"] [data-test-subj="input"]`) - .first() - .prop('options') - ).toEqual([{ label: 'coke' }, { label: 'pepsi' }, { label: 'rad' }, { label: 'dude' }]) + wrapper.find(`[data-test-subj="connector-settings-resilient"]`).exists() + ).toBeFalsy(); + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find(`button[data-test-subj="dropdown-connector-resilient-2"]`).simulate('click'); + wrapper.update(); + }); + + await waitFor(() => { + wrapper.update(); + expect( + wrapper.find(`[data-test-subj="connector-settings-resilient"]`).exists() + ).toBeTruthy(); + }); + + act(() => { + ((wrapper.find(EuiComboBox).at(1).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange([{ value: '19', label: 'Denial of Service' }]); + }); + + act(() => { + wrapper + .find('select[data-test-subj="severitySelect"]') + .first() + .simulate('change', { + target: { value: '4' }, + }); + }); + + await act(async () => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + }); + + await waitFor(() => + expect(postCase).toBeCalledWith({ + ...sampleData, + connector: { + id: 'resilient-2', + name: 'My Connector 2', + type: '.resilient', + fields: { incidentTypes: ['19'], severityCode: '4' }, + }, + }) ); }); - }); - // FAILED ES PROMOTION: https://github.com/elastic/kibana/issues/84145 - describe.skip('Step 2 - Connector Fields', () => { - const connectorTypes = [ - { - label: 'Jira', - testId: 'jira-1', - dataTestSubj: 'connector-settings-jira', - }, - { - label: 'Resilient', - testId: 'resilient-2', - dataTestSubj: 'connector-settings-resilient', - }, - { - label: 'ServiceNow', - testId: 'servicenow-1', - dataTestSubj: 'connector-settings-sn', - }, - ]; - connectorTypes.forEach(({ label, testId, dataTestSubj }) => { - it(`should change from none to ${label} connector fields`, async () => { - useConnectorsMock.mockReturnValue({ - ...sampleConnectorData, - connectors: connectorsMock, - }); + it(`it should submit a servicenow connector`, async () => { + useConnectorsMock.mockReturnValue({ + ...sampleConnectorData, + connectors: connectorsMock, + }); - const wrapper = mount( - <TestProviders> - <Router history={mockHistory}> - <Create /> - </Router> - </TestProviders> - ); - - await waitFor(() => { - expect(wrapper.find(`[data-test-subj="${dataTestSubj}"]`).exists()).toBeFalsy(); - wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); - wrapper.find(`button[data-test-subj="dropdown-connector-${testId}"]`).simulate('click'); - wrapper.update(); - }); + const wrapper = mount( + <TestProviders> + <Router history={mockHistory}> + <Create /> + </Router> + </TestProviders> + ); + + await fillForm(wrapper); + await waitFor(() => { + expect(wrapper.find(`[data-test-subj="connector-settings-sn"]`).exists()).toBeFalsy(); + wrapper.find('button[data-test-subj="dropdown-connectors"]').simulate('click'); + wrapper.find(`button[data-test-subj="dropdown-connector-servicenow-1"]`).simulate('click'); + wrapper.update(); + }); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find(`[data-test-subj="connector-settings-sn"]`).exists()).toBeTruthy(); + }); - await waitFor(() => { - wrapper.update(); - expect(wrapper.find(`[data-test-subj="${dataTestSubj}"]`).exists()).toBeTruthy(); + ['severitySelect', 'urgencySelect', 'impactSelect'].forEach((subj) => { + act(() => { + wrapper + .find(`select[data-test-subj="${subj}"]`) + .first() + .simulate('change', { + target: { value: '2' }, + }); }); }); + + await act(async () => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + }); + + await waitFor(() => + expect(postCase).toBeCalledWith({ + ...sampleData, + connector: { + id: 'servicenow-1', + name: 'My Connector', + type: '.servicenow', + fields: { impact: '2', severity: '2', urgency: '2' }, + }, + }) + ); }); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx index 42633c5d2ccf83..5c50c37723083a 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx @@ -3,319 +3,81 @@ * 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, { useCallback, useEffect, useMemo, useState } from 'react'; -import { - EuiButton, - EuiButtonEmpty, - EuiFlexGroup, - EuiFlexItem, - EuiLoadingSpinner, - EuiPanel, - EuiSteps, -} from '@elastic/eui'; -import styled, { css } from 'styled-components'; + +import React, { useCallback } from 'react'; +import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; +import styled from 'styled-components'; import { useHistory } from 'react-router-dom'; -import { isEqual } from 'lodash/fp'; -import { - Field, - Form, - FormDataProvider, - getUseField, - UseField, - useForm, - useFormData, -} from '../../../shared_imports'; -import { usePostCase } from '../../containers/use_post_case'; -import { schema, FormProps } from './schema'; -import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline'; -import { MarkdownEditorForm } from '../../../common/components/markdown_editor/eui_form'; -import { useGetTags } from '../../containers/use_get_tags'; +import { Field, getUseField, useFormContext } from '../../../shared_imports'; import { getCaseDetailsUrl } from '../../../common/components/link_to'; -import { useTimelineClick } from '../../../common/utils/timeline/use_timeline_click'; -import { SettingFieldsForm } from '../settings/fields_form'; -import { useConnectors } from '../../containers/configure/use_connectors'; -import { ConnectorSelector } from '../connector_selector/form'; -import { useCaseConfigure } from '../../containers/configure/use_configure'; -import { - normalizeCaseConnector, - getConnectorById, - getNoneConnector, - normalizeActionConnector, -} from '../configure_cases/utils'; -import { ActionConnector } from '../../containers/types'; -import { ConnectorFields } from '../../../../../case/common/api/connectors'; import * as i18n from './translations'; +import { CreateCaseForm } from './form'; +import { FormContext } from './form_context'; +import { useInsertTimeline } from '../use_insert_timeline'; +import { fieldName as descriptionFieldName } from './description'; +import { SubmitCaseButton } from './submit_button'; export const CommonUseField = getUseField({ component: Field }); -interface ContainerProps { - big?: boolean; -} - -const Container = styled.div.attrs((props) => props)<ContainerProps>` - ${({ big, theme }) => css` - margin-top: ${big ? theme.eui.euiSizeXL : theme.eui.euiSize}; +const Container = styled.div` + ${({ theme }) => ` + margin-top: ${theme.eui.euiSize}; `} `; -const MySpinner = styled(EuiLoadingSpinner)` - position: absolute; - top: 50%; - left: 50%; - z-index: 99; -`; - -const initialCaseValue: FormProps = { - description: '', - tags: [], - title: '', - connectorId: 'none', +const InsertTimeline = () => { + const { setFieldValue, getFormData } = useFormContext(); + const formData = getFormData(); + const onTimelineAttached = useCallback( + (newValue: string) => setFieldValue(descriptionFieldName, newValue), + [setFieldValue] + ); + useInsertTimeline(formData[descriptionFieldName] ?? '', onTimelineAttached); + return null; }; export const Create = React.memo(() => { const history = useHistory(); - const { caseData, isLoading, postCase } = usePostCase(); - const { loading: isLoadingConnectors, connectors } = useConnectors(); - const { connector: configureConnector, loading: isLoadingCaseConfigure } = useCaseConfigure(); - const { tags: tagOptions } = useGetTags(); - - const [connector, setConnector] = useState<ActionConnector | null>(null); - const [options, setOptions] = useState( - tagOptions.map((label) => ({ - label, - })) - ); - - // This values uses useEffect to update, not useMemo, - // because we need to setState on it from the jsx - useEffect( - () => - setOptions( - tagOptions.map((label) => ({ - label, - })) - ), - [tagOptions] - ); - - const [fields, setFields] = useState<ConnectorFields>(null); - - const { form } = useForm<FormProps>({ - defaultValue: initialCaseValue, - options: { stripEmptyFields: false }, - schema, - }); - const currentConnectorId = useMemo( - () => - !isLoadingCaseConfigure - ? normalizeCaseConnector(connectors, configureConnector)?.id ?? 'none' - : null, - [configureConnector, connectors, isLoadingCaseConfigure] - ); - const { submit, setFieldValue } = form; - const [{ description }] = useFormData<{ - description: string; - }>({ - form, - watch: ['description'], - }); - const onChangeConnector = useCallback( - (newConnectorId) => { - if (connector == null || connector.id !== newConnectorId) { - setConnector(getConnectorById(newConnectorId, connectors) ?? null); - // Reset setting fields when changing connector - setFields(null); - } + const onSuccess = useCallback( + ({ id }) => { + history.push(getCaseDetailsUrl({ id })); }, - [connector, connectors] + [history] ); - const onDescriptionChange = useCallback((newValue) => setFieldValue('description', newValue), [ - setFieldValue, - ]); - - const { handleCursorChange } = useInsertTimeline(description, onDescriptionChange); - - const handleTimelineClick = useTimelineClick(); - - const onSubmit = useCallback(async () => { - const { isValid, data } = await submit(); - if (isValid) { - const { connectorId: dataConnectorId, ...dataWithoutConnectorId } = data; - const caseConnector = getConnectorById(dataConnectorId, connectors); - const connectorToUpdate = caseConnector - ? normalizeActionConnector(caseConnector, fields) - : getNoneConnector(); - - await postCase({ ...dataWithoutConnectorId, connector: connectorToUpdate }); - } - }, [submit, postCase, fields, connectors]); - const handleSetIsCancel = useCallback(() => { history.push('/'); }, [history]); - const firstStep = useMemo( - () => ({ - title: i18n.STEP_ONE_TITLE, - children: ( - <> - <CommonUseField - path="title" - componentProps={{ - idAria: 'caseTitle', - 'data-test-subj': 'caseTitle', - euiFieldProps: { - fullWidth: false, - disabled: isLoading, - }, - }} - /> - <Container> - <CommonUseField - path="tags" - componentProps={{ - idAria: 'caseTags', - 'data-test-subj': 'caseTags', - euiFieldProps: { - fullWidth: true, - placeholder: '', - disabled: isLoading, - options, - noSuggestions: false, - }, - }} - /> - <FormDataProvider pathsToWatch="tags"> - {({ tags: anotherTags }) => { - const current: string[] = options.map((opt) => opt.label); - const newOptions = anotherTags.reduce((acc: string[], item: string) => { - if (!acc.includes(item)) { - return [...acc, item]; - } - return acc; - }, current); - if (!isEqual(current, newOptions)) { - setOptions( - newOptions.map((label: string) => ({ - label, - })) - ); - } - return null; - }} - </FormDataProvider> - </Container> - <Container big> - <UseField - path={'description'} - component={MarkdownEditorForm} - componentProps={{ - dataTestSubj: 'caseDescription', - idAria: 'caseDescription', - isDisabled: isLoading, - onClickTimeline: handleTimelineClick, - onCursorPositionUpdate: handleCursorChange, - }} - /> - </Container> - </> - ), - }), - [isLoading, options, handleCursorChange, handleTimelineClick] - ); - - const secondStep = useMemo( - () => ({ - title: i18n.STEP_TWO_TITLE, - children: ( - <EuiFlexGroup> - <EuiFlexItem> - <Container> - <UseField - path="connectorId" - component={ConnectorSelector} - componentProps={{ - connectors, - dataTestSubj: 'caseConnectors', - defaultValue: currentConnectorId, - disabled: isLoadingConnectors, - idAria: 'caseConnectors', - isLoading, - }} - onChange={onChangeConnector} - /> - </Container> - </EuiFlexItem> - <EuiFlexItem> - <Container> - <SettingFieldsForm - connector={connector} - fields={fields} - isEdit={true} - onChange={setFields} - /> - </Container> - </EuiFlexItem> - </EuiFlexGroup> - ), - }), - [ - connector, - connectors, - currentConnectorId, - fields, - isLoading, - isLoadingConnectors, - onChangeConnector, - ] - ); - - const allSteps = useMemo(() => [firstStep, secondStep], [firstStep, secondStep]); - - if (caseData != null && caseData.id) { - history.push(getCaseDetailsUrl({ id: caseData.id })); - return null; - } - return ( <EuiPanel> - {isLoading && <MySpinner data-test-subj="create-case-loading-spinner" size="xl" />} - <Form form={form}> - <EuiSteps headingElement="h2" steps={allSteps} /> - </Form> - <Container> - <EuiFlexGroup - alignItems="center" - justifyContent="flexEnd" - gutterSize="xs" - responsive={false} - > - <EuiFlexItem grow={false}> - <EuiButtonEmpty - data-test-subj="create-case-cancel" - size="s" - onClick={handleSetIsCancel} - iconType="cross" - > - {i18n.CANCEL} - </EuiButtonEmpty> - </EuiFlexItem> - <EuiFlexItem grow={false}> - <EuiButton - data-test-subj="create-case-submit" - fill - iconType="plusInCircle" - isDisabled={isLoading} - isLoading={isLoading} - onClick={onSubmit} - > - {i18n.CREATE_CASE} - </EuiButton> - </EuiFlexItem> - </EuiFlexGroup> - </Container> + <FormContext onSuccess={onSuccess}> + <CreateCaseForm /> + <Container> + <EuiFlexGroup + alignItems="center" + justifyContent="flexEnd" + gutterSize="xs" + responsive={false} + > + <EuiFlexItem grow={false}> + <EuiButtonEmpty + data-test-subj="create-case-cancel" + size="s" + onClick={handleSetIsCancel} + iconType="cross" + > + {i18n.CANCEL} + </EuiButtonEmpty> + </EuiFlexItem> + <EuiFlexItem grow={false}> + <SubmitCaseButton /> + </EuiFlexItem> + </EuiFlexGroup> + </Container> + <InsertTimeline /> + </FormContext> </EuiPanel> ); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.test.tsx new file mode 100644 index 00000000000000..3bbdb1eafd47c2 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.test.tsx @@ -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 { mount } from 'enzyme'; + +import { OptionalFieldLabel } from '.'; + +describe('OptionalFieldLabel', () => { + it('it renders correctly', async () => { + const wrapper = mount(OptionalFieldLabel); + expect(wrapper.find('[data-test-subj="form-optional-field-label"]').first().text()).toBe( + 'Optional' + ); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx index b86198e09ceac4..4a491eac35d90d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/optional_field_label/index.tsx @@ -10,7 +10,7 @@ import React from 'react'; import * as i18n from '../../../translations'; export const OptionalFieldLabel = ( - <EuiText color="subdued" size="xs"> + <EuiText color="subdued" size="xs" data-test-subj="form-optional-field-label"> {i18n.OPTIONAL} </EuiText> ); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx b/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx index 5abac04d6ef379..a336860121c94e 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/schema.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CasePostRequest } from '../../../../../case/common/api'; +import { CasePostRequest, ConnectorTypeFields } from '../../../../../case/common/api'; import { FIELD_TYPES, fieldValidators, FormSchema } from '../../../shared_imports'; import * as i18n from '../../translations'; @@ -18,7 +18,10 @@ export const schemaTags = { labelAppend: OptionalFieldLabel, }; -export type FormProps = Omit<CasePostRequest, 'connector'> & { connectorId: string }; +export type FormProps = Omit<CasePostRequest, 'connector'> & { + connectorId: string; + fields: ConnectorTypeFields['fields']; +}; export const schema: FormSchema<FormProps> = { title: { diff --git a/x-pack/plugins/security_solution/public/cases/components/create/submit_button.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/submit_button.test.tsx new file mode 100644 index 00000000000000..c8f6ebc05582f1 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/submit_button.test.tsx @@ -0,0 +1,83 @@ +/* + * 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 { mount } from 'enzyme'; +import { act, waitFor } from '@testing-library/react'; + +import { useForm, Form } from '../../../shared_imports'; +import { SubmitCaseButton } from './submit_button'; + +describe('SubmitCaseButton', () => { + const onSubmit = jest.fn(); + + const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm<{ title: string }>({ + defaultValue: { title: 'My title' }, + onSubmit, + }); + + return <Form form={form}>{children}</Form>; + }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('it renders', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <SubmitCaseButton /> + </MockHookWrapperComponent> + ); + + expect(wrapper.find(`[data-test-subj="create-case-submit"]`).exists()).toBeTruthy(); + }); + + it('it submits', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <SubmitCaseButton /> + </MockHookWrapperComponent> + ); + + await act(async () => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + }); + + await waitFor(() => expect(onSubmit).toBeCalled()); + }); + + it('it disables when submitting', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <SubmitCaseButton /> + </MockHookWrapperComponent> + ); + + await waitFor(() => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + expect( + wrapper.find(`[data-test-subj="create-case-submit"]`).first().prop('isDisabled') + ).toBeTruthy(); + }); + }); + + it('it is loading when submitting', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <SubmitCaseButton /> + </MockHookWrapperComponent> + ); + + await waitFor(() => { + wrapper.find(`[data-test-subj="create-case-submit"]`).first().simulate('click'); + expect( + wrapper.find(`[data-test-subj="create-case-submit"]`).first().prop('isLoading') + ).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/submit_button.tsx b/x-pack/plugins/security_solution/public/cases/components/create/submit_button.tsx new file mode 100644 index 00000000000000..8cffce290ff110 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/submit_button.tsx @@ -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 React, { memo } from 'react'; +import { EuiButton } from '@elastic/eui'; + +import { useFormContext } from '../../../shared_imports'; +import * as i18n from './translations'; + +const SubmitCaseButtonComponent: React.FC = () => { + const { submit, isSubmitting } = useFormContext(); + + return ( + <EuiButton + data-test-subj="create-case-submit" + fill + iconType="plusInCircle" + isDisabled={isSubmitting} + isLoading={isSubmitting} + onClick={submit} + > + {i18n.CREATE_CASE} + </EuiButton> + ); +}; + +export const SubmitCaseButton = memo(SubmitCaseButtonComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/tags.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/tags.test.tsx new file mode 100644 index 00000000000000..c06ac011a035b9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/tags.test.tsx @@ -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 React from 'react'; +import { mount } from 'enzyme'; +import { EuiComboBox, EuiComboBoxOptionOption } from '@elastic/eui'; +import { waitFor } from '@testing-library/react'; + +import { useForm, Form, FormHook, FIELD_TYPES } from '../../../shared_imports'; +import { useGetTags } from '../../containers/use_get_tags'; +import { Tags } from './tags'; + +jest.mock('../../containers/use_get_tags'); +const useGetTagsMock = useGetTags as jest.Mock; + +describe('Tags', () => { + let globalForm: FormHook; + + const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm<{ tags: string[] }>({ + defaultValue: { tags: [] }, + schema: { + tags: { type: FIELD_TYPES.COMBO_BOX }, + }, + }); + + globalForm = form; + + return <Form form={form}>{children}</Form>; + }; + + beforeEach(() => { + jest.resetAllMocks(); + useGetTagsMock.mockReturnValue({ tags: ['test'] }); + }); + + it('it renders', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <Tags isLoading={false} /> + </MockHookWrapperComponent> + ); + + await waitFor(() => { + expect(wrapper.find(`[data-test-subj="caseTags"]`).exists()).toBeTruthy(); + }); + }); + + it('it disables the input when loading', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <Tags isLoading={true} /> + </MockHookWrapperComponent> + ); + + expect(wrapper.find(EuiComboBox).prop('disabled')).toBeTruthy(); + }); + + it('it changes the tags', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <Tags isLoading={false} /> + </MockHookWrapperComponent> + ); + + await waitFor(() => { + ((wrapper.find(EuiComboBox).props() as unknown) as { + onChange: (a: EuiComboBoxOptionOption[]) => void; + }).onChange(['test', 'case'].map((tag) => ({ label: tag }))); + }); + + expect(globalForm.getFormData()).toEqual({ tags: ['test', 'case'] }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/tags.tsx b/x-pack/plugins/security_solution/public/cases/components/create/tags.tsx new file mode 100644 index 00000000000000..8a7b4a6e5f7606 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/tags.tsx @@ -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 React, { memo, useMemo } from 'react'; + +import { Field, getUseField } from '../../../shared_imports'; +import { useGetTags } from '../../containers/use_get_tags'; + +const CommonUseField = getUseField({ component: Field }); + +interface Props { + isLoading: boolean; +} + +const TagsComponent: React.FC<Props> = ({ isLoading }) => { + const { tags: tagOptions, isLoading: isLoadingTags } = useGetTags(); + const options = useMemo( + () => + tagOptions.map((label) => ({ + label, + })), + [tagOptions] + ); + + return ( + <CommonUseField + path="tags" + componentProps={{ + idAria: 'caseTags', + 'data-test-subj': 'caseTags', + euiFieldProps: { + fullWidth: true, + placeholder: '', + disabled: isLoading || isLoadingTags, + options, + noSuggestions: false, + }, + }} + /> + ); +}; + +TagsComponent.displayName = 'TagsComponent'; + +export const Tags = memo(TagsComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/title.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/title.test.tsx new file mode 100644 index 00000000000000..54a4e665a56e54 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/title.test.tsx @@ -0,0 +1,67 @@ +/* + * 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 { mount } from 'enzyme'; +import { act } from '@testing-library/react'; + +import { useForm, Form, FormHook } from '../../../shared_imports'; +import { Title } from './title'; + +describe('Title', () => { + let globalForm: FormHook; + + const MockHookWrapperComponent: React.FC = ({ children }) => { + const { form } = useForm<{ title: string }>({ + defaultValue: { title: 'My title' }, + }); + + globalForm = form; + + return <Form form={form}>{children}</Form>; + }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('it renders', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <Title isLoading={false} /> + </MockHookWrapperComponent> + ); + + expect(wrapper.find(`[data-test-subj="caseTitle"]`).exists()).toBeTruthy(); + }); + + it('it disables the input when loading', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <Title isLoading={true} /> + </MockHookWrapperComponent> + ); + + expect(wrapper.find(`[data-test-subj="caseTitle"] input`).prop('disabled')).toBeTruthy(); + }); + + it('it changes the title', async () => { + const wrapper = mount( + <MockHookWrapperComponent> + <Title isLoading={false} /> + </MockHookWrapperComponent> + ); + + await act(async () => { + wrapper + .find(`[data-test-subj="caseTitle"] input`) + .first() + .simulate('change', { target: { value: 'My new title' } }); + }); + + expect(globalForm.getFormData()).toEqual({ title: 'My new title' }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/title.tsx b/x-pack/plugins/security_solution/public/cases/components/create/title.tsx new file mode 100644 index 00000000000000..2daeb9b738e233 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/create/title.tsx @@ -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 React, { memo } from 'react'; +import { Field, getUseField } from '../../../shared_imports'; + +const CommonUseField = getUseField({ component: Field }); + +interface Props { + isLoading: boolean; +} + +const TitleComponent: React.FC<Props> = ({ isLoading }) => ( + <CommonUseField + path="title" + componentProps={{ + idAria: 'caseTitle', + 'data-test-subj': 'caseTitle', + euiFieldProps: { + fullWidth: true, + disabled: isLoading, + }, + }} + /> +); + +TitleComponent.displayName = 'TitleComponent'; + +export const Title = memo(TitleComponent); diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/card.tsx b/x-pack/plugins/security_solution/public/cases/components/settings/card.tsx index 344ca88f5ab37a..f5be9740bc4f18 100644 --- a/x-pack/plugins/security_solution/public/cases/components/settings/card.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/settings/card.tsx @@ -8,7 +8,7 @@ import React, { memo, useMemo } from 'react'; import { EuiCard, EuiIcon, EuiLoadingSpinner } from '@elastic/eui'; import styled from 'styled-components'; -import { connectorsConfiguration } from '../../../common/lib/connectors/config'; +import { connectorsConfiguration } from '../connectors'; import { ConnectorTypes } from '../../../../../case/common/api/connectors'; interface ConnectorCardProps { diff --git a/x-pack/plugins/security_solution/public/cases/components/settings/fields_form.tsx b/x-pack/plugins/security_solution/public/cases/components/settings/fields_form.tsx index 87536b62747e80..79ae87355b5fb5 100644 --- a/x-pack/plugins/security_solution/public/cases/components/settings/fields_form.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/settings/fields_form.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { memo, Suspense, useCallback } from 'react'; +import React, { memo, Suspense } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; import { CaseSettingsConnector, SettingFieldsProps } from './types'; @@ -18,13 +18,6 @@ interface Props extends Omit<SettingFieldsProps<ConnectorTypeFields['fields']>, const SettingFieldsFormComponent: React.FC<Props> = ({ connector, isEdit, onChange, fields }) => { const { caseSettingsRegistry } = getCaseSettings(); - const onFieldsChange = useCallback( - (newFields) => { - onChange(newFields); - }, - [onChange] - ); - if (connector == null || connector.actionTypeId == null || connector.actionTypeId === '.none') { return null; } @@ -45,12 +38,14 @@ const SettingFieldsFormComponent: React.FC<Props> = ({ connector, isEdit, onChan </EuiFlexGroup> } > - <FieldsComponent - isEdit={isEdit} - fields={fields} - connector={connector} - onChange={onFieldsChange} - /> + <div data-test-subj={'connector-settings'}> + <FieldsComponent + isEdit={isEdit} + fields={fields} + connector={connector} + onChange={onChange} + /> + </div> </Suspense> ) : null} </> diff --git a/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx b/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx index a04450b3c41988..83afa4c4f5ed33 100644 --- a/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/tag_list/index.tsx @@ -18,13 +18,14 @@ import { import styled, { css } from 'styled-components'; import { isEqual } from 'lodash/fp'; import * as i18n from './translations'; -import { Form, FormDataProvider, useForm } from '../../../shared_imports'; +import { Form, FormDataProvider, useForm, getUseField, Field } from '../../../shared_imports'; import { schema } from './schema'; -import { CommonUseField } from '../create'; import { useGetTags } from '../../containers/use_get_tags'; import { Tags } from './tags'; +const CommonUseField = getUseField({ component: Field }); + interface TagListProps { disabled?: boolean; isLoading: boolean; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx index 7a12f9211e969a..b5885b330a822d 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_all_cases_modal/all_cases_modal.tsx @@ -22,10 +22,7 @@ export interface AllCasesModalProps { onRowClick: (id?: string) => void; } -const AllCasesModalComponent: React.FC<AllCasesModalProps> = ({ - onCloseCaseModal, - onRowClick, -}: AllCasesModalProps) => { +const AllCasesModalComponent: React.FC<AllCasesModalProps> = ({ onCloseCaseModal, onRowClick }) => { const userPermissions = useGetUserSavedObjectPermissions(); const userCanCrud = userPermissions?.crud ?? false; return ( diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx new file mode 100644 index 00000000000000..68446fc5b31713 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/create_case_modal.tsx @@ -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 React, { memo, useCallback } from 'react'; +import styled from 'styled-components'; +import { + EuiModal, + EuiModalBody, + EuiModalHeader, + EuiModalHeaderTitle, + EuiOverlayMask, +} from '@elastic/eui'; + +import { FormContext } from '../create/form_context'; +import { CreateCaseForm } from '../create/form'; +import { SubmitCaseButton } from '../create/submit_button'; +import { Case } from '../../containers/types'; +import * as i18n from '../../translations'; + +export interface CreateCaseModalProps { + onCloseCaseModal: () => void; + onCaseCreated: (theCase: Case) => void; +} + +const Container = styled.div` + ${({ theme }) => ` + margin-top: ${theme.eui.euiSize}; + text-align: right; + `} +`; + +const CreateModalComponent: React.FC<CreateCaseModalProps> = ({ + onCloseCaseModal, + onCaseCreated, +}) => { + const onSuccess = useCallback( + (theCase) => { + onCaseCreated(theCase); + onCloseCaseModal(); + }, + [onCaseCreated, onCloseCaseModal] + ); + + return ( + <EuiOverlayMask data-test-subj="all-cases-modal"> + <EuiModal onClose={onCloseCaseModal}> + <EuiModalHeader> + <EuiModalHeaderTitle>{i18n.CREATE_TITLE}</EuiModalHeaderTitle> + </EuiModalHeader> + <EuiModalBody> + <FormContext onSuccess={onSuccess}> + <CreateCaseForm withSteps={false} /> + <Container> + <SubmitCaseButton /> + </Container> + </FormContext> + </EuiModalBody> + </EuiModal> + </EuiOverlayMask> + ); +}; + +export const CreateCaseModal = memo(CreateModalComponent); + +CreateCaseModal.displayName = 'CreateCaseModal'; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx new file mode 100644 index 00000000000000..f07be3cc608216 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/use_create_case_modal/index.tsx @@ -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 React, { useState, useCallback, useMemo } from 'react'; +import { Case } from '../../containers/types'; +import { CreateCaseModal } from './create_case_modal'; + +interface Props { + onCaseCreated: (theCase: Case) => void; +} +export interface UseAllCasesModalReturnedValues { + Modal: React.FC; + isModalOpen: boolean; + closeModal: () => void; + openModal: () => void; +} + +export const useCreateCaseModal = ({ onCaseCreated }: Props) => { + const [isModalOpen, setIsModalOpen] = useState<boolean>(false); + const closeModal = useCallback(() => setIsModalOpen(false), []); + const openModal = useCallback(() => setIsModalOpen(true), []); + + const Modal: React.FC = useCallback( + () => + isModalOpen ? ( + <CreateCaseModal onCloseCaseModal={closeModal} onCaseCreated={onCaseCreated} /> + ) : null, + [closeModal, isModalOpen, onCaseCreated] + ); + + const state = useMemo( + () => ({ + Modal, + isModalOpen, + closeModal, + openModal, + }), + [isModalOpen, closeModal, openModal, Modal] + ); + + return state; +}; diff --git a/x-pack/plugins/security_solution/public/cases/components/use_insert_timeline/index.tsx b/x-pack/plugins/security_solution/public/cases/components/use_insert_timeline/index.tsx new file mode 100644 index 00000000000000..c44193dc363a42 --- /dev/null +++ b/x-pack/plugins/security_solution/public/cases/components/use_insert_timeline/index.tsx @@ -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 { useCallback, useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import { isEmpty } from 'lodash/fp'; + +import { getTimelineUrl, useFormatUrl } from '../../../common/components/link_to'; +import { useShallowEqualSelector } from '../../../common/hooks/use_selector'; +import { timelineSelectors, timelineActions } from '../../../timelines/store/timeline'; +import { SecurityPageName } from '../../../app/types'; +import { setInsertTimeline } from '../../../timelines/store/timeline/actions'; + +interface UseInsertTimelineReturn { + handleOnTimelineChange: (title: string, id: string | null, graphEventId?: string) => void; +} + +export const useInsertTimeline = ( + value: string, + onChange: (newValue: string) => void +): UseInsertTimelineReturn => { + const dispatch = useDispatch(); + const { formatUrl } = useFormatUrl(SecurityPageName.timelines); + + const insertTimeline = useShallowEqualSelector(timelineSelectors.selectInsertTimeline); + + const handleOnTimelineChange = useCallback( + (title: string, id: string | null, graphEventId?: string) => { + const url = formatUrl(getTimelineUrl(id ?? '', graphEventId), { + absolute: true, + skipSearch: true, + }); + + let newValue = `[${title}](${url})`; + // Leave a space between the previous value and the timeline url if the value is not empty. + if (!isEmpty(value)) { + newValue = `${value} ${newValue}`; + } + + onChange(newValue); + }, + [value, onChange, formatUrl] + ); + + useEffect(() => { + if (insertTimeline != null && value != null) { + dispatch(timelineActions.showTimeline({ id: insertTimeline.timelineId, show: false })); + handleOnTimelineChange( + insertTimeline.timelineTitle, + insertTimeline.timelineSavedObjectId, + insertTimeline.graphEventId + ); + dispatch(setInsertTimeline(null)); + } + }, [insertTimeline, dispatch, handleOnTimelineChange, value]); + + return { + handleOnTimelineChange, + }; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/item_details_card/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/item_details_card/__snapshots__/index.test.tsx.snap index d95e0300fe140e..c7841f6d6bbcc2 100644 --- a/x-pack/plugins/security_solution/public/common/components/item_details_card/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/item_details_card/__snapshots__/index.test.tsx.snap @@ -182,7 +182,9 @@ exports[`item_details_card ItemDetailsPropertySummary should render correctly 1` <Styled(EuiDescriptionListTitle)> name 1 </Styled(EuiDescriptionListTitle)> - <Styled(EuiDescriptionListDescription)> + <Styled(EuiDescriptionListDescription) + className="eui-textBreakWord" + > value 1 </Styled(EuiDescriptionListDescription)> </Fragment> diff --git a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.tsx b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.tsx index 829d8db5a5a0f0..8f32224f860e3f 100644 --- a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.tsx @@ -72,7 +72,7 @@ export const ItemDetailsPropertySummary = memo<ItemDetailsPropertySummaryProps>( ({ name, value }) => ( <> <DescriptionListTitle>{name}</DescriptionListTitle> - <DescriptionListDescription>{value}</DescriptionListDescription> + <DescriptionListDescription className="eui-textBreakWord">{value}</DescriptionListDescription> </> ) ); diff --git a/x-pack/plugins/security_solution/public/common/lib/connectors/case/translations.ts b/x-pack/plugins/security_solution/public/common/lib/connectors/case/translations.ts deleted file mode 100644 index a39e04acc1bf3d..00000000000000 --- a/x-pack/plugins/security_solution/public/common/lib/connectors/case/translations.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const CASE_CONNECTOR_DESC = i18n.translate( - 'xpack.securitySolution.case.components.case.selectMessageText', - { - defaultMessage: 'Create or update a case.', - } -); - -export const CASE_CONNECTOR_TITLE = i18n.translate( - 'xpack.securitySolution.case.components.case.actionTypeTitle', - { - defaultMessage: 'Cases', - } -); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts index e99894afeb63c4..e6482577f89edc 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_status/helpers.ts @@ -13,6 +13,6 @@ export const getStatusColor = (status: RuleStatusType | string | null) => ? 'success' : status === 'failed' ? 'danger' - : status === 'executing' || status === 'going to run' + : status === 'executing' || status === 'going to run' || status === 'partial failure' ? 'warning' : 'subdued'; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts index e9c89130736c05..d7908a6780ebb2 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/types.ts @@ -70,6 +70,13 @@ const MetaRule = t.intersection([ }), ]); +const StatusTypes = t.union([ + t.literal('succeeded'), + t.literal('failed'), + t.literal('going to run'), + t.literal('partial failure'), +]); + export const RuleSchema = t.intersection([ t.type({ author, @@ -108,13 +115,15 @@ export const RuleSchema = t.intersection([ license, last_failure_at: t.string, last_failure_message: t.string, + last_success_message: t.string, + last_success_at: t.string, meta: MetaRule, machine_learning_job_id: t.string, output_index: t.string, query: t.string, rule_name_override, saved_id: t.string, - status: t.string, + status: StatusTypes, status_date: t.string, threshold, threat_query, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index ba676835d60f11..4866824f882cfc 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -275,18 +275,33 @@ export const RuleDetailsPageComponent: FC<PropsFromRedux> = ({ ), [ruleDetailTabs, ruleDetailTab, setRuleDetailTab] ); - const ruleError = useMemo( - () => + const ruleError = useMemo(() => { + if ( rule?.status === 'failed' && ruleDetailTab === RuleDetailTabs.alerts && - rule?.last_failure_at != null ? ( + rule?.last_failure_at != null + ) { + return ( <RuleStatusFailedCallOut message={rule?.last_failure_message ?? ''} date={rule?.last_failure_at} /> - ) : null, - [rule, ruleDetailTab] - ); + ); + } else if ( + rule?.status === 'partial failure' && + ruleDetailTab === RuleDetailTabs.alerts && + rule?.last_success_at != null + ) { + return ( + <RuleStatusFailedCallOut + message={rule?.last_success_message ?? ''} + date={rule?.last_success_at} + color="warning" + /> + ); + } + return null; + }, [rule, ruleDetailTab]); const updateDateRangeCallback = useCallback<UpdateDateRange>( ({ x }) => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.test.tsx index 3394b0fc8c5c05..c7436234020635 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.test.tsx @@ -13,6 +13,13 @@ describe('RuleStatusFailedCallOut', () => { it('renders correctly', () => { const wrapper = shallow(<RuleStatusFailedCallOut date="date" message="message" />); + expect(wrapper.find('EuiCallOut')).toHaveLength(1); + }); + it('renders correctly with optional params', () => { + const wrapper = shallow( + <RuleStatusFailedCallOut date="date" message="message" color="warning" /> + ); + expect(wrapper.find('EuiCallOut')).toHaveLength(1); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.tsx index 5b5b96ace8670c..121ff6b8686c34 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/status_failed_callout.tsx @@ -13,22 +13,26 @@ import * as i18n from './translations'; interface RuleStatusFailedCallOutComponentProps { date: string; message: string; + color?: 'danger' | 'primary' | 'success' | 'warning'; } const RuleStatusFailedCallOutComponent: React.FC<RuleStatusFailedCallOutComponentProps> = ({ date, message, + color, }) => ( <EuiCallOut title={ <EuiFlexGroup gutterSize="xs" alignItems="center" justifyContent="flexStart"> - <EuiFlexItem grow={false}>{i18n.ERROR_CALLOUT_TITLE}</EuiFlexItem> + <EuiFlexItem grow={false}> + {color === 'warning' ? i18n.PARTIAL_FAILURE_CALLOUT_TITLE : i18n.ERROR_CALLOUT_TITLE} + </EuiFlexItem> <EuiFlexItem grow={true}> <FormattedDate value={date} fieldName="last_failure_at" /> </EuiFlexItem> </EuiFlexGroup> } - color="danger" + color={color ? color : 'danger'} iconType="alert" > <p>{message}</p> diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts index 94dfdc3e9daa02..5fbe0a5b786713 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/translations.ts @@ -48,6 +48,13 @@ export const ERROR_CALLOUT_TITLE = i18n.translate( } ); +export const PARTIAL_FAILURE_CALLOUT_TITLE = i18n.translate( + 'xpack.securitySolution.detectionEngine.ruleDetails.partialErrorCalloutTitle', + { + defaultMessage: 'Partial rule failure at', + } +); + export const FAILURE_HISTORY_TAB = i18n.translate( 'xpack.securitySolution.detectionEngine.ruleDetails.failureHistoryTab', { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/antivirus_registration/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx similarity index 60% rename from x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/antivirus_registration/index.tsx rename to x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx index 21fe14df81dd21..072f588663c5a0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/antivirus_registration/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/antivirus_registration_form/index.tsx @@ -14,6 +14,29 @@ import { isAntivirusRegistrationEnabled } from '../../../store/policy_details/se import { usePolicyDetailsSelector } from '../../policy_hooks'; import { ConfigForm } from '../../components/config_form'; +const TRANSLATIONS: Readonly<{ [K in 'title' | 'description' | 'label']: string }> = { + title: i18n.translate( + 'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.type', + { + defaultMessage: 'Register as anti-virus', + } + ), + description: i18n.translate( + 'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.explanation', + { + defaultMessage: + 'Toggle on to register Elastic as an official Anti-Virus solution for Windows OS. ' + + 'This will also disable Windows Defender.', + } + ), + label: i18n.translate( + 'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.toggle', + { + defaultMessage: 'Register as anti-virus', + } + ), +}; + export const AntivirusRegistrationForm = memo(() => { const antivirusRegistrationEnabled = usePolicyDetailsSelector(isAntivirusRegistrationEnabled); const dispatch = useDispatch(); @@ -30,31 +53,11 @@ export const AntivirusRegistrationForm = memo(() => { ); return ( - <ConfigForm - type={i18n.translate( - 'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.type', - { - defaultMessage: 'Register as anti-virus', - } - )} - supportedOss={[OperatingSystem.WINDOWS]} - > - <EuiText size="s"> - {i18n.translate( - 'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.explanation', - { - defaultMessage: 'Switch the toggle to on to register Elastic anti-virus', - } - )} - </EuiText> + <ConfigForm type={TRANSLATIONS.title} supportedOss={[OperatingSystem.WINDOWS]}> + <EuiText size="s">{TRANSLATIONS.description}</EuiText> <EuiSpacer size="s" /> <EuiSwitch - label={i18n.translate( - 'xpack.securitySolution.endpoint.policy.details.antivirusRegistration.toggle', - { - defaultMessage: 'Register as anti-virus', - } - )} + label={TRANSLATIONS.label} checked={antivirusRegistrationEnabled} onChange={handleSwitchChange} /> diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx new file mode 100644 index 00000000000000..7cdf54316e4e20 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/events_form/index.tsx @@ -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 React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiCheckbox, EuiSpacer, EuiText, htmlIdGenerator } from '@elastic/eui'; +import { OperatingSystem, UIPolicyConfig } from '../../../../../../../common/endpoint/types'; +import { OS } from '../../../types'; +import { ConfigForm, ConfigFormHeading } from '../../components/config_form'; + +const OPERATING_SYSTEM_TO_TEST_SUBJ: { [K in OperatingSystem]: string } = { + [OperatingSystem.WINDOWS]: 'Windows', + [OperatingSystem.LINUX]: 'Linux', + [OperatingSystem.MAC]: 'Mac', +}; + +interface OperatingSystemToOsMap { + [OperatingSystem.WINDOWS]: OS.windows; + [OperatingSystem.LINUX]: OS.linux; + [OperatingSystem.MAC]: OS.mac; +} + +export type ProtectionField< + T extends OperatingSystem +> = keyof UIPolicyConfig[OperatingSystemToOsMap[T]]['events']; + +export type EventFormSelection<T extends OperatingSystem> = { [K in ProtectionField<T>]: boolean }; + +export interface EventFormOption<T extends OperatingSystem> { + name: string; + protectionField: ProtectionField<T>; +} + +export interface EventsFormProps<T extends OperatingSystem> { + os: T; + options: ReadonlyArray<EventFormOption<T>>; + selection: EventFormSelection<T>; + onValueSelection: (value: ProtectionField<T>, selected: boolean) => void; +} + +const countSelected = <T extends OperatingSystem>(selection: EventFormSelection<T>) => { + return Object.values(selection).filter((value) => value).length; +}; + +export const EventsForm = <T extends OperatingSystem>({ + os, + options, + selection, + onValueSelection, +}: EventsFormProps<T>) => ( + <ConfigForm + type={i18n.translate('xpack.securitySolution.endpoint.policy.details.eventCollection', { + defaultMessage: 'Event Collection', + })} + supportedOss={[os]} + rightCorner={ + <EuiText size="s" color="subdued"> + {i18n.translate('xpack.securitySolution.endpoint.policy.details.eventCollectionsEnabled', { + defaultMessage: '{selected} / {total} event collections enabled', + values: { selected: countSelected(selection), total: options.length }, + })} + </EuiText> + } + > + <ConfigFormHeading> + {i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.eventingEvents', { + defaultMessage: 'Events', + })} + </ConfigFormHeading> + <EuiSpacer size="s" /> + {options.map(({ name, protectionField }) => ( + <EuiCheckbox + key={String(protectionField)} + id={htmlIdGenerator()()} + label={name} + data-test-subj={`policy${OPERATING_SYSTEM_TO_TEST_SUBJ[os]}Event_${protectionField}`} + checked={selection[protectionField]} + onChange={(event) => onValueSelection(protectionField, event.target.checked)} + /> + ))} + </ConfigForm> +); + +EventsForm.displayName = 'EventsForm'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx index 9c11bc6f5a4d13..1ce099c494cf0c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx @@ -36,7 +36,7 @@ import { AgentsSummary } from './agents_summary'; import { VerticalDivider } from './vertical_divider'; import { WindowsEvents, MacEvents, LinuxEvents } from './policy_forms/events'; import { MalwareProtections } from './policy_forms/protections/malware'; -import { AntivirusRegistrationForm } from './policy_forms/antivirus_registration'; +import { AntivirusRegistrationForm } from './components/antivirus_registration_form'; import { useToasts } from '../../../../common/lib/kibana'; import { AppAction } from '../../../../common/store/actions'; import { SpyRoute } from '../../../../common/utils/route/spy_routes'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/checkbox.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/checkbox.tsx deleted file mode 100644 index 76077831c670b4..00000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/checkbox.tsx +++ /dev/null @@ -1,55 +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 React, { useCallback, useMemo } from 'react'; -import { EuiCheckbox, EuiCheckboxProps, htmlIdGenerator } from '@elastic/eui'; -import { useDispatch } from 'react-redux'; -import { usePolicyDetailsSelector } from '../../policy_hooks'; -import { policyConfig } from '../../../store/policy_details/selectors'; -import { PolicyDetailsAction } from '../../../store/policy_details'; -import { UIPolicyConfig } from '../../../../../../../common/endpoint/types'; - -type EventsCheckboxProps = Omit<EuiCheckboxProps, 'id' | 'label' | 'checked' | 'onChange'> & { - name: string; - setter: (config: UIPolicyConfig, checked: boolean) => UIPolicyConfig; - getter: (config: UIPolicyConfig) => boolean; -}; - -export const EventsCheckbox = React.memo(function ({ - name, - setter, - getter, - ...otherProps -}: EventsCheckboxProps) { - const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); - const selected = getter(policyDetailsConfig); - const dispatch = useDispatch<(action: PolicyDetailsAction) => void>(); - const checkboxId = useMemo(() => htmlIdGenerator()(), []); - - const handleCheckboxChange = useCallback( - (event: React.ChangeEvent<HTMLInputElement>) => { - if (policyDetailsConfig) { - dispatch({ - type: 'userChangedPolicyConfig', - payload: { policyConfig: setter(policyDetailsConfig, event.target.checked) }, - }); - } - }, - [dispatch, policyDetailsConfig, setter] - ); - - return ( - <EuiCheckbox - id={checkboxId} - label={name} - checked={selected} - onChange={handleCheckboxChange} - {...otherProps} - /> - ); -}); - -EventsCheckbox.displayName = 'EventsCheckbox'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx index 999e3bac5653a0..f9532eaecf7013 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/linux.tsx @@ -4,96 +4,59 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React, { memo } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSpacer, EuiText } from '@elastic/eui'; -import { EventsCheckbox } from './checkbox'; -import { OS } from '../../../types'; +import { useDispatch } from 'react-redux'; +import { OperatingSystem } from '../../../../../../../common/endpoint/types'; +import { policyConfig } from '../../../store/policy_details/selectors'; +import { setIn } from '../../../models/policy_details_config'; import { usePolicyDetailsSelector } from '../../policy_hooks'; -import { selectedLinuxEvents, totalLinuxEvents } from '../../../store/policy_details/selectors'; -import { ConfigForm, ConfigFormHeading } from '../../components/config_form'; -import { getIn, setIn } from '../../../models/policy_details_config'; -import { OperatingSystem, UIPolicyConfig } from '../../../../../../../common/endpoint/types'; -import { - COLLECTIONS_ENABLED_MESSAGE, - EVENTS_FORM_TYPE_LABEL, - EVENTS_HEADING, -} from './translations'; +import { EventFormOption, EventsForm } from '../../components/events_form'; -export const LinuxEvents = React.memo(() => { - const selected = usePolicyDetailsSelector(selectedLinuxEvents); - const total = usePolicyDetailsSelector(totalLinuxEvents); - - const checkboxes = useMemo(() => { - const items: Array<{ - name: string; - os: 'linux'; - protectionField: keyof UIPolicyConfig['linux']['events']; - }> = [ - { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.file', - { - defaultMessage: 'File', - } - ), - os: OS.linux, - protectionField: 'file', - }, +const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.LINUX>> = [ + { + name: i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.file', { + defaultMessage: 'File', + }), + protectionField: 'file', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.process', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.process', - { - defaultMessage: 'Process', - } - ), - os: OS.linux, - protectionField: 'process', - }, + defaultMessage: 'Process', + } + ), + protectionField: 'process', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.network', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.network', - { - defaultMessage: 'Network', - } - ), - os: OS.linux, - protectionField: 'network', - }, - ]; - return ( - <> - <ConfigFormHeading>{EVENTS_HEADING}</ConfigFormHeading> - <EuiSpacer size="s" /> - {items.map((item, index) => { - return ( - <EventsCheckbox - name={item.name} - key={index} - data-test-subj={`policyLinuxEvent_${item.protectionField}`} - setter={(config, checked) => - setIn(config)(item.os)('events')(item.protectionField)(checked) - } - getter={(config) => getIn(config)(item.os)('events')(item.protectionField)} - /> - ); - })} - </> - ); - }, []); + defaultMessage: 'Network', + } + ), + protectionField: 'network', + }, +]; + +export const LinuxEvents = memo(() => { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const dispatch = useDispatch(); return ( - <ConfigForm - type={EVENTS_FORM_TYPE_LABEL} - supportedOss={[OperatingSystem.LINUX]} - dataTestSubj="linuxEventingForm" - rightCorner={ - <EuiText size="s" color="subdued"> - {COLLECTIONS_ENABLED_MESSAGE(selected, total)} - </EuiText> + <EventsForm<OperatingSystem.LINUX> + os={OperatingSystem.LINUX} + selection={policyDetailsConfig.linux.events} + options={OPTIONS} + onValueSelection={(value, selected) => + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: setIn(policyDetailsConfig)('linux')('events')(value)(selected) }, + }) } - > - {checkboxes} - </ConfigForm> + /> ); }); + +LinuxEvents.displayName = 'LinuxEvents'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/mac.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/mac.tsx index 6e15a3c4cd43b8..ac6ae531ba1724 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/mac.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/mac.tsx @@ -4,96 +4,53 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React, { memo } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSpacer, EuiText } from '@elastic/eui'; -import { EventsCheckbox } from './checkbox'; -import { OS } from '../../../types'; +import { useDispatch } from 'react-redux'; +import { OperatingSystem } from '../../../../../../../common/endpoint/types'; +import { policyConfig } from '../../../store/policy_details/selectors'; +import { setIn } from '../../../models/policy_details_config'; import { usePolicyDetailsSelector } from '../../policy_hooks'; -import { selectedMacEvents, totalMacEvents } from '../../../store/policy_details/selectors'; -import { ConfigForm, ConfigFormHeading } from '../../components/config_form'; -import { getIn, setIn } from '../../../models/policy_details_config'; -import { OperatingSystem, UIPolicyConfig } from '../../../../../../../common/endpoint/types'; -import { - COLLECTIONS_ENABLED_MESSAGE, - EVENTS_FORM_TYPE_LABEL, - EVENTS_HEADING, -} from './translations'; +import { EventFormOption, EventsForm } from '../../components/events_form'; -export const MacEvents = React.memo(() => { - const selected = usePolicyDetailsSelector(selectedMacEvents); - const total = usePolicyDetailsSelector(totalMacEvents); +const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.MAC>> = [ + { + name: i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.file', { + defaultMessage: 'File', + }), + protectionField: 'file', + }, + { + name: i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.process', { + defaultMessage: 'Process', + }), + protectionField: 'process', + }, + { + name: i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.network', { + defaultMessage: 'Network', + }), + protectionField: 'network', + }, +]; - const checkboxes = useMemo(() => { - const items: Array<{ - name: string; - os: 'mac'; - protectionField: keyof UIPolicyConfig['mac']['events']; - }> = [ - { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.file', - { - defaultMessage: 'File', - } - ), - os: OS.mac, - protectionField: 'file', - }, - { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.process', - { - defaultMessage: 'Process', - } - ), - os: OS.mac, - protectionField: 'process', - }, - { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.mac.events.network', - { - defaultMessage: 'Network', - } - ), - os: OS.mac, - protectionField: 'network', - }, - ]; - return ( - <> - <ConfigFormHeading>{EVENTS_HEADING}</ConfigFormHeading> - <EuiSpacer size="s" /> - {items.map((item, index) => { - return ( - <EventsCheckbox - name={item.name} - key={index} - data-test-subj={`policyMacEvent_${item.protectionField}`} - setter={(config, checked) => - setIn(config)(item.os)('events')(item.protectionField)(checked) - } - getter={(config) => getIn(config)(item.os)('events')(item.protectionField)} - /> - ); - })} - </> - ); - }, []); +export const MacEvents = memo(() => { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const dispatch = useDispatch(); return ( - <ConfigForm - type={EVENTS_FORM_TYPE_LABEL} - supportedOss={[OperatingSystem.MAC]} - dataTestSubj="macEventingForm" - rightCorner={ - <EuiText size="s" color="subdued"> - {COLLECTIONS_ENABLED_MESSAGE(selected, total)} - </EuiText> + <EventsForm<OperatingSystem.MAC> + os={OperatingSystem.MAC} + selection={policyDetailsConfig.mac.events} + options={OPTIONS} + onValueSelection={(value, selected) => + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: setIn(policyDetailsConfig)('mac')('events')(value)(selected) }, + }) } - > - {checkboxes} - </ConfigForm> + /> ); }); + +MacEvents.displayName = 'MacEvents'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/translations.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/translations.ts deleted file mode 100644 index 3b48b7969a8ced..00000000000000 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/translations.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; - -export const EVENTS_HEADING = i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.eventingEvents', - { - defaultMessage: 'Events', - } -); - -export const EVENTS_FORM_TYPE_LABEL = i18n.translate( - 'xpack.securitySolution.endpoint.policy.details.eventCollection', - { - defaultMessage: 'Event Collection', - } -); - -export const COLLECTIONS_ENABLED_MESSAGE = (selected: number, total: number) => { - return i18n.translate('xpack.securitySolution.endpoint.policy.details.eventCollectionsEnabled', { - defaultMessage: '{selected} / {total} event collections enabled', - values: { selected, total }, - }); -}; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/windows.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/windows.tsx index c381249cf24b92..c99f2a6b72ac3d 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/windows.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/events/windows.tsx @@ -4,142 +4,97 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo } from 'react'; +import React, { memo } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiSpacer, EuiText } from '@elastic/eui'; -import { EventsCheckbox } from './checkbox'; -import { OS } from '../../../types'; +import { useDispatch } from 'react-redux'; +import { OperatingSystem } from '../../../../../../../common/endpoint/types'; +import { policyConfig } from '../../../store/policy_details/selectors'; +import { setIn } from '../../../models/policy_details_config'; import { usePolicyDetailsSelector } from '../../policy_hooks'; -import { selectedWindowsEvents, totalWindowsEvents } from '../../../store/policy_details/selectors'; -import { ConfigForm, ConfigFormHeading } from '../../components/config_form'; -import { getIn, setIn } from '../../../models/policy_details_config'; -import { - Immutable, - OperatingSystem, - UIPolicyConfig, -} from '../../../../../../../common/endpoint/types'; -import { - COLLECTIONS_ENABLED_MESSAGE, - EVENTS_FORM_TYPE_LABEL, - EVENTS_HEADING, -} from './translations'; +import { EventFormOption, EventsForm } from '../../components/events_form'; -export const WindowsEvents = React.memo(() => { - const selected = usePolicyDetailsSelector(selectedWindowsEvents); - const total = usePolicyDetailsSelector(totalWindowsEvents); - - const checkboxes = useMemo(() => { - const items: Immutable< - Array<{ - name: string; - os: 'windows'; - protectionField: keyof UIPolicyConfig['windows']['events']; - }> - > = [ - { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.dllDriverLoad', - { - defaultMessage: 'DLL and Driver Load', - } - ), - os: OS.windows, - protectionField: 'dll_and_driver_load', - }, +const OPTIONS: ReadonlyArray<EventFormOption<OperatingSystem.WINDOWS>> = [ + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.dllDriverLoad', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.dns', - { - defaultMessage: 'DNS', - } - ), - os: OS.windows, - protectionField: 'dns', - }, + defaultMessage: 'DLL and Driver Load', + } + ), + protectionField: 'dll_and_driver_load', + }, + { + name: i18n.translate('xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.dns', { + defaultMessage: 'DNS', + }), + protectionField: 'dns', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.file', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.file', - { - defaultMessage: 'File', - } - ), - os: OS.windows, - protectionField: 'file', - }, + defaultMessage: 'File', + } + ), + protectionField: 'file', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.network', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.network', - { - defaultMessage: 'Network', - } - ), - os: OS.windows, - protectionField: 'network', - }, + defaultMessage: 'Network', + } + ), + protectionField: 'network', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.process', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.process', - { - defaultMessage: 'Process', - } - ), - os: OS.windows, - protectionField: 'process', - }, + defaultMessage: 'Process', + } + ), + protectionField: 'process', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.registry', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.registry', - { - defaultMessage: 'Registry', - } - ), - os: OS.windows, - protectionField: 'registry', - }, + defaultMessage: 'Registry', + } + ), + protectionField: 'registry', + }, + { + name: i18n.translate( + 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.security', { - name: i18n.translate( - 'xpack.securitySolution.endpoint.policyDetailsConfig.windows.events.security', - { - defaultMessage: 'Security', - } - ), - os: OS.windows, - protectionField: 'security', - }, - ]; - return ( - <> - <ConfigFormHeading>{EVENTS_HEADING}</ConfigFormHeading> - <EuiSpacer size="s" /> - {items.map((item, index) => { - return ( - <EventsCheckbox - name={item.name} - key={index} - data-test-subj={`policyWindowsEvent_${item.protectionField}`} - setter={(config, checked) => - setIn(config)(item.os)('events')(item.protectionField)(checked) - } - getter={(config) => getIn(config)(item.os)('events')(item.protectionField)} - /> - ); - })} - </> - ); - }, []); + defaultMessage: 'Security', + } + ), + protectionField: 'security', + }, +]; + +export const WindowsEvents = memo(() => { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const dispatch = useDispatch(); return ( - <ConfigForm - type={EVENTS_FORM_TYPE_LABEL} - supportedOss={[OperatingSystem.WINDOWS]} - dataTestSubj="windowsEventingForm" - rightCorner={ - <EuiText size="s" color="subdued"> - {COLLECTIONS_ENABLED_MESSAGE(selected, total)} - </EuiText> + <EventsForm<OperatingSystem.WINDOWS> + os={OperatingSystem.WINDOWS} + selection={policyDetailsConfig.windows.events} + options={OPTIONS} + onValueSelection={(value, selected) => + dispatch({ + type: 'userChangedPolicyConfig', + payload: { + policyConfig: setIn(policyDetailsConfig)('windows')('events')(value)(selected), + }, + }) } - > - {checkboxes} - </ConfigForm> + /> ); }); + +WindowsEvents.displayName = 'WindowsEvents'; diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap index 71b103949a80a0..eb3a8f2132e7f0 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/__snapshots__/index.test.tsx.snap @@ -138,7 +138,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -154,7 +154,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -170,7 +170,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -180,7 +180,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -196,7 +196,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -390,7 +390,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -406,7 +406,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -422,7 +422,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -432,7 +432,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -448,7 +448,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -642,7 +642,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -658,7 +658,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -674,7 +674,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -684,7 +684,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -700,7 +700,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -894,7 +894,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -910,7 +910,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -926,7 +926,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -936,7 +936,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -952,7 +952,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1146,7 +1146,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1162,7 +1162,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1178,7 +1178,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -1188,7 +1188,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1204,7 +1204,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1398,7 +1398,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1414,7 +1414,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1430,7 +1430,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -1440,7 +1440,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1456,7 +1456,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1650,7 +1650,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1666,7 +1666,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1682,7 +1682,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -1692,7 +1692,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1708,7 +1708,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1902,7 +1902,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1918,7 +1918,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1934,7 +1934,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -1944,7 +1944,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -1960,7 +1960,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2154,7 +2154,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2170,7 +2170,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2186,7 +2186,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -2196,7 +2196,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2212,7 +2212,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2406,7 +2406,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2422,7 +2422,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2438,7 +2438,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -2448,7 +2448,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2464,7 +2464,7 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2948,7 +2948,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2964,7 +2964,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -2980,7 +2980,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -2990,7 +2990,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3006,7 +3006,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3200,7 +3200,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3216,7 +3216,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3232,7 +3232,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -3242,7 +3242,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3258,7 +3258,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3452,7 +3452,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3468,7 +3468,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3484,7 +3484,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -3494,7 +3494,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3510,7 +3510,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3704,7 +3704,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3720,7 +3720,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3736,7 +3736,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -3746,7 +3746,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3762,7 +3762,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3956,7 +3956,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3972,7 +3972,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -3988,7 +3988,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -3998,7 +3998,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4014,7 +4014,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4208,7 +4208,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4224,7 +4224,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4240,7 +4240,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -4250,7 +4250,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4266,7 +4266,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4460,7 +4460,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4476,7 +4476,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4492,7 +4492,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -4502,7 +4502,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4518,7 +4518,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4712,7 +4712,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4728,7 +4728,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4744,7 +4744,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -4754,7 +4754,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4770,7 +4770,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4964,7 +4964,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4980,7 +4980,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -4996,7 +4996,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -5006,7 +5006,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5022,7 +5022,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5216,7 +5216,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5232,7 +5232,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5248,7 +5248,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -5258,7 +5258,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5274,7 +5274,7 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5716,7 +5716,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5732,7 +5732,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5748,7 +5748,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -5758,7 +5758,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5774,7 +5774,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5968,7 +5968,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -5984,7 +5984,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6000,7 +6000,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -6010,7 +6010,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6026,7 +6026,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6220,7 +6220,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6236,7 +6236,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6252,7 +6252,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -6262,7 +6262,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6278,7 +6278,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6472,7 +6472,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6488,7 +6488,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6504,7 +6504,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -6514,7 +6514,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6530,7 +6530,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6724,7 +6724,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6740,7 +6740,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6756,7 +6756,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -6766,7 +6766,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6782,7 +6782,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6976,7 +6976,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -6992,7 +6992,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7008,7 +7008,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -7018,7 +7018,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7034,7 +7034,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7228,7 +7228,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7244,7 +7244,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7260,7 +7260,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -7270,7 +7270,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7286,7 +7286,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7480,7 +7480,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7496,7 +7496,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7512,7 +7512,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -7522,7 +7522,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7538,7 +7538,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7732,7 +7732,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7748,7 +7748,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7764,7 +7764,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -7774,7 +7774,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7790,7 +7790,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -7984,7 +7984,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -8000,7 +8000,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -8016,7 +8016,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -8026,7 +8026,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -8042,7 +8042,7 @@ exports[`TrustedAppsGrid renders correctly when new page and page size set (not Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap index 4a8f984b59a016..8f70c61ba4afc3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_list/__snapshots__/index.test.tsx.snap @@ -793,7 +793,7 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = ` Name </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -809,7 +809,7 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = ` OS </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -825,7 +825,7 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = ` Date Created </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > 1 minute ago </dd> @@ -835,7 +835,7 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = ` Created By </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" @@ -851,7 +851,7 @@ exports[`TrustedAppsList renders correctly when item details expanded 1`] = ` Description </dt> <dd - class="euiDescriptionList__description c2" + class="euiDescriptionList__description c2 eui-textBreakWord" > <span class="euiToolTipAnchor" diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index f97bec65d269a9..b81be3249953e2 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -59,7 +59,7 @@ import { IndexFieldsStrategyResponse, } from '../common/search_strategy/index_fields'; import { SecurityAppStore } from './common/store/store'; -import { getCaseConnectorUI } from './common/lib/connectors'; +import { getCaseConnectorUI } from './cases/components/connectors'; import { licenseService } from './common/hooks/use_license'; import { LazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension'; import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx deleted file mode 100644 index 6a65819a764eba..00000000000000 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/insert_timeline_popover/use_insert_timeline.tsx +++ /dev/null @@ -1,70 +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 { useCallback, useState, useEffect } from 'react'; -import { useDispatch } from 'react-redux'; - -import { useShallowEqualSelector } from '../../../../common/hooks/use_selector'; -import { SecurityPageName } from '../../../../../common/constants'; -import { getTimelineUrl, useFormatUrl } from '../../../../common/components/link_to'; -import { CursorPosition } from '../../../../common/components/markdown_editor'; -import { timelineActions, timelineSelectors } from '../../../../timelines/store/timeline'; -import { setInsertTimeline } from '../../../store/timeline/actions'; - -export const useInsertTimeline = (value: string, onChange: (newValue: string) => void) => { - const dispatch = useDispatch(); - const { formatUrl } = useFormatUrl(SecurityPageName.timelines); - const [cursorPosition, setCursorPosition] = useState<CursorPosition>({ - start: 0, - end: 0, - }); - - const insertTimeline = useShallowEqualSelector(timelineSelectors.selectInsertTimeline); - - const handleOnTimelineChange = useCallback( - (title: string, id: string | null, graphEventId?: string) => { - const url = formatUrl(getTimelineUrl(id ?? '', graphEventId), { - absolute: true, - skipSearch: true, - }); - - const newValue: string = [ - value.slice(0, cursorPosition.start), - cursorPosition.start === cursorPosition.end - ? `[${title}](${url})` - : `[${value.slice(cursorPosition.start, cursorPosition.end)}](${url})`, - value.slice(cursorPosition.end), - ].join(''); - - onChange(newValue); - }, - [value, onChange, cursorPosition, formatUrl] - ); - - const handleCursorChange = useCallback((cp: CursorPosition) => { - setCursorPosition(cp); - }, []); - - // insertTimeline selector is defined to attached a timeline to a case outside of the case page. - // FYI, if you are in the case page we only use handleOnTimelineChange to attach a timeline to a case. - useEffect(() => { - if (insertTimeline != null && value != null) { - dispatch(timelineActions.showTimeline({ id: insertTimeline.timelineId, show: false })); - handleOnTimelineChange( - insertTimeline.timelineTitle, - insertTimeline.timelineSavedObjectId, - insertTimeline.graphEventId - ); - dispatch(setInsertTimeline(null)); - } - }, [insertTimeline, dispatch, handleOnTimelineChange, value]); - - return { - cursorPosition, - handleCursorChange, - handleOnTimelineChange, - }; -}; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts index 1d6a8227ebc13e..f7b1790e127d94 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.mock.ts @@ -22,6 +22,8 @@ export const ruleStatusServiceFactoryMock = async ({ success: jest.fn(), + partialFailure: jest.fn(), + error: jest.fn(), }; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts index cde6a506c657d2..449ecd11257d74 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.test.ts @@ -53,6 +53,21 @@ describe('buildRuleStatusAttributes', () => { expect(result.statusDate).toEqual(result.lastSuccessAt); }); + it('returns partial failure fields if "partial failure"', () => { + const result = buildRuleStatusAttributes( + 'partial failure', + 'some indices missing timestamp override field' + ); + expect(result).toEqual({ + status: 'partial failure', + statusDate: expectIsoDateString, + lastSuccessAt: expectIsoDateString, + lastSuccessMessage: 'some indices missing timestamp override field', + }); + + expect(result.statusDate).toEqual(result.lastSuccessAt); + }); + it('returns failure fields if "failed"', () => { const result = buildRuleStatusAttributes('failed', 'failure message'); expect(result).toEqual({ diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts index 433ad4e2affea4..debc329bf40d77 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/rule_status_service.ts @@ -23,6 +23,7 @@ interface Attributes { export interface RuleStatusService { goingToRun: () => Promise<void>; success: (message: string, attributes?: Attributes) => Promise<void>; + partialFailure: (message: string, attributes?: Attributes) => Promise<void>; error: (message: string, attributes?: Attributes) => Promise<void>; } @@ -46,6 +47,13 @@ export const buildRuleStatusAttributes: ( lastSuccessMessage: message, }; } + case 'partial failure': { + return { + ...baseAttributes, + lastSuccessAt: now, + lastSuccessMessage: message, + }; + } case 'failed': { return { ...baseAttributes, @@ -93,6 +101,18 @@ export const ruleStatusServiceFactory = async ({ }); }, + partialFailure: async (message, attributes) => { + const [currentStatus] = await getOrCreateRuleStatuses({ + alertId, + ruleStatusClient, + }); + + await ruleStatusClient.update(currentStatus.id, { + ...currentStatus.attributes, + ...buildRuleStatusAttributes('partial failure', message, attributes), + }); + }, + error: async (message, attributes) => { const ruleStatuses = await getOrCreateRuleStatuses({ alertId, diff --git a/x-pack/plugins/telemetry_collection_xpack/server/index.ts b/x-pack/plugins/telemetry_collection_xpack/server/index.ts index 249d16c331c397..de39089fe0e034 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/index.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/index.ts @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'kibana/server'; import { TelemetryCollectionXpackPlugin } from './plugin'; +export { ESLicense } from './telemetry_collection'; + // This exports static code and TypeScript types, // as well as, Kibana Platform `plugin()` initializer. -export function plugin(initializerContext: PluginInitializerContext) { - return new TelemetryCollectionXpackPlugin(initializerContext); +export function plugin() { + return new TelemetryCollectionXpackPlugin(); } diff --git a/x-pack/plugins/telemetry_collection_xpack/server/plugin.ts b/x-pack/plugins/telemetry_collection_xpack/server/plugin.ts index 524b4c5616c73a..e6d72f58131638 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/plugin.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/plugin.ts @@ -4,16 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - PluginInitializerContext, - CoreSetup, - CoreStart, - Plugin, - IClusterClient, - SavedObjectsServiceStart, -} from 'kibana/server'; +import { CoreSetup, CoreStart, Plugin } from 'src/core/server'; import { TelemetryCollectionManagerPluginSetup } from 'src/plugins/telemetry_collection_manager/server'; -import { getClusterUuids, getLocalLicense } from '../../../../src/plugins/telemetry/server'; +import { getClusterUuids } from '../../../../src/plugins/telemetry/server'; import { getStatsWithXpack } from './telemetry_collection'; interface TelemetryCollectionXpackDepsSetup { @@ -21,25 +14,16 @@ interface TelemetryCollectionXpackDepsSetup { } export class TelemetryCollectionXpackPlugin implements Plugin { - private elasticsearchClient?: IClusterClient; - private savedObjectsService?: SavedObjectsServiceStart; - constructor(initializerContext: PluginInitializerContext) {} + constructor() {} public setup(core: CoreSetup, { telemetryCollectionManager }: TelemetryCollectionXpackDepsSetup) { - telemetryCollectionManager.setCollection({ - esCluster: core.elasticsearch.legacy.client, - esClientGetter: () => this.elasticsearchClient, - soServiceGetter: () => this.savedObjectsService, + telemetryCollectionManager.setCollectionStrategy({ title: 'local_xpack', priority: 1, statsGetter: getStatsWithXpack, clusterDetailsGetter: getClusterUuids, - licenseGetter: getLocalLicense, }); } - public start(core: CoreStart) { - this.elasticsearchClient = core.elasticsearch.client; - this.savedObjectsService = core.savedObjects; - } + public start(core: CoreStart) {} } diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap index b68186c0c343d0..836b5276615efd 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/__snapshots__/get_stats_with_xpack.test.ts.snap @@ -104,6 +104,9 @@ Object { }, "cluster_uuid": "test", "collection": "local", + "license": Object { + "type": "basic", + }, "stack_stats": Object { "data": Array [], "kibana": Object { @@ -183,6 +186,9 @@ Object { }, "cluster_uuid": "test", "collection": "local", + "license": Object { + "type": "basic", + }, "stack_stats": Object { "data": Array [], "kibana": Object { diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.test.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.test.ts new file mode 100644 index 00000000000000..c5c6832aa84ac9 --- /dev/null +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.test.ts @@ -0,0 +1,82 @@ +/* + * 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 { elasticsearchServiceMock } from 'src/core/server/mocks'; +import { getLicenseFromLocalOrMaster } from './get_license'; + +describe('getLicenseFromLocalOrMaster', () => { + test('return an undefined license if it fails to get the license on the first attempt and it does not have a cached license yet', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + // The local fetch fails + esClient.license.get.mockRejectedValue(new Error('Something went terribly wrong')); + + const license = await getLicenseFromLocalOrMaster(esClient); + + expect(license).toBeUndefined(); + expect(esClient.license.get).toHaveBeenCalledWith({ local: true, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledTimes(1); + }); + + test('returns the license it fetches from Elasticsearch', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + // The local fetch succeeds + esClient.license.get.mockResolvedValue({ body: { license: { type: 'basic' } } } as any); + + const license = await getLicenseFromLocalOrMaster(esClient); + + expect(license).toStrictEqual({ type: 'basic' }); + expect(esClient.license.get).toHaveBeenCalledWith({ local: true, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledTimes(1); + }); + + test('after the first successful attempt, if the local request fails, it will try with the master request (failed case)', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + const error = new Error('Something went terribly wrong'); + // The requests fail with an error + esClient.license.get.mockRejectedValue(error); + + await expect(getLicenseFromLocalOrMaster(esClient)).rejects.toStrictEqual(error); + + expect(esClient.license.get).toHaveBeenCalledWith({ local: true, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledWith({ local: false, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledTimes(2); + }); + + test('after the first successful attempt, if the local request fails, it will try with the master request (success case)', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + // The local fetch fails + esClient.license.get.mockRejectedValueOnce(new Error('Something went terribly wrong')); + // The master fetch succeeds + esClient.license.get.mockResolvedValue({ body: { license: { type: 'basic' } } } as any); + + const license = await getLicenseFromLocalOrMaster(esClient); + + expect(license).toStrictEqual({ type: 'basic' }); + expect(esClient.license.get).toHaveBeenCalledWith({ local: true, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledWith({ local: false, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledTimes(2); + }); + + test('after the first successful attempt, if the local request fails, it will try with the master request (clearing cached license)', async () => { + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + // The requests fail with 400 + esClient.license.get.mockRejectedValue({ statusCode: 400 }); + + // First attempt goes through 2 requests: local and master + const license = await getLicenseFromLocalOrMaster(esClient); + + expect(license).toBeUndefined(); + expect(esClient.license.get).toHaveBeenCalledWith({ local: true, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledWith({ local: false, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledTimes(2); + + // Now the cached license is cleared, next request only goes for local and gives up when failed + esClient.license.get.mockClear(); + await expect(getLicenseFromLocalOrMaster(esClient)).resolves.toBeUndefined(); + expect(esClient.license.get).toHaveBeenCalledWith({ local: true, accept_enterprise: true }); + expect(esClient.license.get).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/plugins/telemetry/server/telemetry_collection/get_local_license.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts similarity index 50% rename from src/plugins/telemetry/server/telemetry_collection/get_local_license.ts rename to x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts index 879416cda62fc4..9ffbf5d1bf6d71 100644 --- a/src/plugins/telemetry/server/telemetry_collection/get_local_license.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_license.ts @@ -1,29 +1,30 @@ /* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. + * 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 { ESLicense, LicenseGetter } from 'src/plugins/telemetry_collection_manager/server'; import { ElasticsearchClient } from 'src/core/server'; +// From https://www.elastic.co/guide/en/elasticsearch/reference/current/get-license.html +export interface ESLicense { + status: string; + uid: string; + type: string; + issue_date: string; + issue_date_in_millis: number; + expiry_date: string; + expirty_date_in_millis: number; + max_nodes: number; + issued_to: string; + issuer: string; + start_date_in_millis: number; +} + let cachedLicense: ESLicense | undefined; async function fetchLicense(esClient: ElasticsearchClient, local: boolean) { - const { body } = await esClient.license.get({ + const { body } = await esClient.license.get<{ license: ESLicense }>({ local, // For versions >= 7.6 and < 8.0, this flag is needed otherwise 'platinum' is returned for 'enterprise' license. accept_enterprise: true, @@ -39,7 +40,7 @@ async function fetchLicense(esClient: ElasticsearchClient, local: boolean) { * * In OSS we'll get a 400 response using the new elasticsearch client. */ -async function getLicenseFromLocalOrMaster(esClient: ElasticsearchClient) { +export async function getLicenseFromLocalOrMaster(esClient: ElasticsearchClient) { // Fetching the local license is cheaper than getting it from the master node and good enough const { license } = await fetchLicense(esClient, true).catch(async (err) => { if (cachedLicense) { @@ -64,9 +65,3 @@ async function getLicenseFromLocalOrMaster(esClient: ElasticsearchClient) { } return license; } - -export const getLocalLicense: LicenseGetter = async (clustersDetails, { esClient }) => { - const license = await getLicenseFromLocalOrMaster(esClient); - // It should be called only with 1 cluster element in the clustersDetails array, but doing reduce just in case. - return clustersDetails.reduce((acc, { clusterUuid }) => ({ ...acc, [clusterUuid]: license }), {}); -}; diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts index c0e55274b08df6..bf1e7c3aaae17d 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/get_stats_with_xpack.ts @@ -4,33 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ +import { StatsGetter } from 'src/plugins/telemetry_collection_manager/server'; import { TelemetryLocalStats, getLocalStats } from '../../../../../src/plugins/telemetry/server'; -import { StatsGetter } from '../../../../../src/plugins/telemetry_collection_manager/server'; import { getXPackUsage } from './get_xpack'; +import { ESLicense, getLicenseFromLocalOrMaster } from './get_license'; export type TelemetryAggregatedStats = TelemetryLocalStats & { stack_stats: { xpack?: object }; + license?: ESLicense; }; -export const getStatsWithXpack: StatsGetter<{}, TelemetryAggregatedStats> = async function ( +export const getStatsWithXpack: StatsGetter<TelemetryAggregatedStats> = async function ( clustersDetails, config, context ) { const { esClient } = config; - const clustersLocalStats = await getLocalStats(clustersDetails, config, context); - const xpack = await getXPackUsage(esClient).catch(() => undefined); // We want to still report something (and do not lose the license) even when this method fails. + const [clustersLocalStats, license, xpack] = await Promise.all([ + getLocalStats(clustersDetails, config, context), + getLicenseFromLocalOrMaster(esClient), + getXPackUsage(esClient).catch(() => undefined), // We want to still report something (and do not lose the license) even when this method fails. + ]); return clustersLocalStats .map((localStats) => { + const localStatsWithLicense: TelemetryAggregatedStats = { + ...localStats, + ...(license && { license }), + }; if (xpack) { return { - ...localStats, - stack_stats: { ...localStats.stack_stats, xpack }, + ...localStatsWithLicense, + stack_stats: { ...localStatsWithLicense.stack_stats, xpack }, }; } - return localStats; + return localStatsWithLicense; }) .reduce((acc, stats) => { // Concatenate the telemetry reported via monitoring as additional payloads instead of reporting it inside of stack_stats.kibana.plugins.monitoringTelemetry diff --git a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/index.ts b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/index.ts index 553f8dc0c41880..bcd011ae750a66 100644 --- a/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/index.ts +++ b/x-pack/plugins/telemetry_collection_xpack/server/telemetry_collection/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ +export { ESLicense } from './get_license'; export { getStatsWithXpack } from './get_stats_with_xpack'; diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 490bf56d76062a..4991c4d16099cb 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -19600,7 +19600,6 @@ "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredDescriptionTextField": "説明が必要です。", "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredEmailTextField": "電子メールアドレスまたはユーザー名が必要です", "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredProjectKeyTextField": "プロジェクトキーが必要です", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredTitleTextField": "タイトルが必要です。", "xpack.triggersActionsUI.components.builtinActionTypes.jira.savedObjectIdFieldHelp": "JIRAは、このアクションを、Kibanaの保存されたオブジェクトのIDに関連付けます。", "xpack.triggersActionsUI.components.builtinActionTypes.jira.savedObjectIdFieldLabel": "オブジェクトID(任意)", "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxAriaLabel": "親問題を選択", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 15699c3f47dd5f..59ab890cfd9db0 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -19619,7 +19619,6 @@ "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredDescriptionTextField": "“描述”必填。", "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredEmailTextField": "“电子邮件”或“用户名”必填", "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredProjectKeyTextField": "“项目键”必填", - "xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredTitleTextField": "“标题”必填。", "xpack.triggersActionsUI.components.builtinActionTypes.jira.savedObjectIdFieldHelp": "JIRA 将此操作与 Kibana 已保存对象的 ID 关联。", "xpack.triggersActionsUI.components.builtinActionTypes.jira.savedObjectIdFieldLabel": "对象 ID(可选)", "xpack.triggersActionsUI.components.builtinActionTypes.jira.searchIssuesComboBoxAriaLabel": "选择父问题", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx index f476522c2bf5ac..b10341fa00f1ba 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.test.tsx @@ -93,7 +93,7 @@ describe('jira action params validation', () => { expect(actionTypeModel.validateParams(actionParams)).toEqual({ errors: { - title: ['Title is required.'], + title: ['Summary is required.'], }, }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx index 81f0bbfe8a02fc..20374cfbe3a3b7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/jira.tsx @@ -64,8 +64,8 @@ export function getActionType(): ActionTypeModel<JiraConfig, JiraSecrets, JiraAc title: new Array<string>(), }; validationResult.errors = errors; - if (actionParams.subActionParams && !actionParams.subActionParams.title?.length) { - errors.title.push(i18n.TITLE_REQUIRED); + if (!actionParams.subActionParams?.title?.length) { + errors.title.push(i18n.SUMMARY_REQUIRED); } return validationResult; }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts index 6f45316ff44334..c9642da9ba440e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/translations.ts @@ -127,10 +127,10 @@ export const DESCRIPTION_REQUIRED = i18n.translate( } ); -export const TITLE_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredTitleTextField', +export const SUMMARY_REQUIRED = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.jira.requiredSummaryTextField', { - defaultMessage: 'Title is required.', + defaultMessage: 'Summary is required.', } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx index 937fe61e887ea6..17e9b42e7878e7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.test.tsx @@ -93,7 +93,7 @@ describe('resilient action params validation', () => { expect(actionTypeModel.validateParams(actionParams)).toEqual({ errors: { - title: ['Title is required.'], + title: ['Name is required.'], }, }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx index 6d57fc98fe20fc..251274a08ba6c7 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/resilient.tsx @@ -72,8 +72,9 @@ export function getActionType(): ActionTypeModel< title: new Array<string>(), }; validationResult.errors = errors; - if (actionParams.subActionParams && !actionParams.subActionParams.title?.length) { - errors.title.push(i18n.TITLE_REQUIRED); + + if (!actionParams.subActionParams?.title?.length) { + errors.title.push(i18n.NAME_REQUIRED); } return validationResult; }, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts index 65d08c9f7de68b..7483ba2f461df4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/translations.ts @@ -134,16 +134,16 @@ export const MAPPING_FIELD_COMMENTS = i18n.translate( ); export const DESCRIPTION_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.common.requiredDescriptionTextField', + 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.requiredDescriptionTextField', { defaultMessage: 'Description is required.', } ); -export const TITLE_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.common.requiredTitleTextField', +export const NAME_REQUIRED = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.resilient.requiredNameTextField', { - defaultMessage: 'Title is required.', + defaultMessage: 'Name is required.', } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx index 5e70bc20f5c514..c29ddbf385de69 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx @@ -90,7 +90,7 @@ describe('servicenow action params validation', () => { expect(actionTypeModel.validateParams(actionParams)).toEqual({ errors: { - title: ['Title is required.'], + title: ['Short description is required.'], }, }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx index 9cc689d8f48b16..8eca7f3ef31201 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx @@ -67,7 +67,7 @@ export function getActionType(): ActionTypeModel< title: new Array<string>(), }; validationResult.errors = errors; - if (actionParams.subActionParams && !actionParams.subActionParams.title?.length) { + if (!actionParams.subActionParams?.title?.length) { errors.title.push(i18n.TITLE_REQUIRED); } return validationResult; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts index 312cb9844bd754..91a5c0a54397b9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts @@ -157,6 +157,6 @@ export const DESCRIPTION_REQUIRED = i18n.translate( export const TITLE_REQUIRED = i18n.translate( 'xpack.triggersActionsUI.components.builtinActionTypes.common.requiredTitleTextField', { - defaultMessage: 'Title is required.', + defaultMessage: 'Short description is required.', } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.test.ts index c1f7cbb9fafedc..57cc45786b2da2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ResolvedActionGroup } from '../../../../alerts/common'; +import { RecoveredActionGroup } from '../../../../alerts/common'; import { AlertProvidedActionVariables } from './action_variables'; import { getDefaultsForActionParams } from './get_defaults_for_action_params'; @@ -16,8 +16,8 @@ describe('getDefaultsForActionParams', () => { }); }); - test('pagerduty defaults for resolved action group', async () => { - expect(getDefaultsForActionParams('.pagerduty', ResolvedActionGroup.id)).toEqual({ + test('pagerduty defaults for recovered action group', async () => { + expect(getDefaultsForActionParams('.pagerduty', RecoveredActionGroup.id)).toEqual({ dedupKey: `{{${AlertProvidedActionVariables.alertId}}}:{{${AlertProvidedActionVariables.alertInstanceId}}}`, eventAction: 'resolve', }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts index c2143553e63c69..36c054977ac307 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/get_defaults_for_action_params.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertActionParam, ResolvedActionGroup } from '../../../../alerts/common'; +import { AlertActionParam, RecoveredActionGroup } from '../../../../alerts/common'; import { AlertProvidedActionVariables } from './action_variables'; export const getDefaultsForActionParams = ( @@ -17,7 +17,7 @@ export const getDefaultsForActionParams = ( dedupKey: `{{${AlertProvidedActionVariables.alertId}}}:{{${AlertProvidedActionVariables.alertInstanceId}}}`, eventAction: 'trigger', }; - if (actionGroupId === ResolvedActionGroup.id) { + if (actionGroupId === RecoveredActionGroup.id) { pagerDutyDefaults.eventAction = 'resolve'; } return pagerDutyDefaults; diff --git a/x-pack/plugins/ui_actions_enhanced/README.md b/x-pack/plugins/ui_actions_enhanced/README.md index a4a37b559ff8de..cd2a34a2f75368 100644 --- a/x-pack/plugins/ui_actions_enhanced/README.md +++ b/x-pack/plugins/ui_actions_enhanced/README.md @@ -3,3 +3,66 @@ Registers commercially licensed generic actions like per panel time range and contains some code that supports drilldown work. - [__Dashboard drilldown user docs__](https://www.elastic.co/guide/en/kibana/master/drilldowns.html) + +## Dynamic Actions Telemetry + +Dynamic actions (drilldowns) report telemetry. Below is the summary of dynamic action metrics that are reported using telemetry. + +### Dynamic action count + +Total count of dynamic actions (drilldowns) on a saved object. + +``` +dynamicActions.count +``` + +### Count by factory ID + +Count of active dynamic actions (drilldowns) on a saved object by factory ID (drilldown type). + +``` +dynamicActions.actions.<factory_id>.count +``` + +For example: + +``` +dynamicActions.actions.DASHBOARD_TO_DASHBOARD_DRILLDOWN.count +dynamicActions.actions.URL_DRILLDOWN.count +``` + +### Count by trigger + +Count of active dynamic actions (drilldowns) on a saved object by a trigger to which they are attached. + +``` +dynamicActions.triggers.<trigger>.count +``` + +For example: + +``` +dynamicActions.triggers.VALUE_CLICK_TRIGGER.count +dynamicActions.triggers.RANGE_SELECT_TRIGGER.count +``` + +### Count by factory and trigger + +Count of active dynamic actions (drilldowns) on a saved object by a factory ID and trigger ID. + +``` +dynamicActions.action_triggers.<factory_id>_<trigger>.count +``` + +For example: + +``` +dynamicActions.action_triggers.DASHBOARD_TO_DASHBOARD_DRILLDOWN_VALUE_CLICK_TRIGGER.count +dynamicActions.action_triggers.DASHBOARD_TO_DASHBOARD_DRILLDOWN_RANGE_SELECT_TRIGGER.count +dynamicActions.action_triggers.URL_DRILLDOWN_VALUE_CLICK_TRIGGER.count +``` + +### Factory metrics + +Each dynamic action factory (drilldown type) can report its own stats, which is +done using the `.telemetry()` method on dynamic action factories. diff --git a/x-pack/plugins/ui_actions_enhanced/server/dynamic_action_enhancement.ts b/x-pack/plugins/ui_actions_enhanced/server/dynamic_action_enhancement.ts index 4cea7ddf4854ae..16e7e7967838d4 100644 --- a/x-pack/plugins/ui_actions_enhanced/server/dynamic_action_enhancement.ts +++ b/x-pack/plugins/ui_actions_enhanced/server/dynamic_action_enhancement.ts @@ -8,19 +8,20 @@ import { EnhancementRegistryDefinition } from '../../../../src/plugins/embeddabl import { SavedObjectReference } from '../../../../src/core/types'; import { ActionFactory, DynamicActionsState, SerializedEvent } from './types'; import { SerializableState } from '../../../../src/plugins/kibana_utils/common'; +import { dynamicActionsCollector } from './telemetry/dynamic_actions_collector'; +import { dynamicActionFactoriesCollector } from './telemetry/dynamic_action_factories_collector'; export const dynamicActionEnhancement = ( getActionFactory: (id: string) => undefined | ActionFactory ): EnhancementRegistryDefinition => { return { id: 'dynamicActions', - telemetry: (state: SerializableState, telemetry: Record<string, any>) => { - let telemetryData = telemetry; - (state as DynamicActionsState).events.forEach((event: SerializedEvent) => { - const factory = getActionFactory(event.action.factoryId); - if (factory) telemetryData = factory.telemetry(event, telemetryData); - }); - return telemetryData; + telemetry: (serializableState: SerializableState, stats: Record<string, any>) => { + const state = serializableState as DynamicActionsState; + stats = dynamicActionsCollector(state, stats); + stats = dynamicActionFactoriesCollector(getActionFactory, state, stats); + + return stats; }, extract: (state: SerializableState) => { const references: SavedObjectReference[] = []; diff --git a/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.test.ts b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.test.ts new file mode 100644 index 00000000000000..9d38fd9d302a45 --- /dev/null +++ b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.test.ts @@ -0,0 +1,124 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ + +import { dynamicActionFactoriesCollector } from './dynamic_action_factories_collector'; +import { DynamicActionsState } from '../../common'; +import { ActionFactory } from '../types'; + +type GetActionFactory = (id: string) => undefined | ActionFactory; + +const factories: Record<string, ActionFactory> = { + FACTORY_ID_1: ({ + id: 'FACTORY_ID_1', + telemetry: jest.fn((state: DynamicActionsState, stats: Record<string, any>) => { + stats.myStat_1 = 1; + stats.myStat_2 = 123; + return stats; + }), + } as unknown) as ActionFactory, + FACTORY_ID_2: ({ + id: 'FACTORY_ID_2', + telemetry: jest.fn((state: DynamicActionsState, stats: Record<string, any>) => stats), + } as unknown) as ActionFactory, + FACTORY_ID_3: ({ + id: 'FACTORY_ID_3', + telemetry: jest.fn((state: DynamicActionsState, stats: Record<string, any>) => { + stats.myStat_1 = 2; + stats.stringStat = 'abc'; + return stats; + }), + } as unknown) as ActionFactory, +}; + +const getActionFactory: GetActionFactory = (id: string) => factories[id]; + +const state: DynamicActionsState = { + events: [ + { + eventId: 'eventId-1', + triggers: ['TRIGGER_1'], + action: { + factoryId: 'FACTORY_ID_1', + name: 'Click me!', + config: {}, + }, + }, + { + eventId: 'eventId-2', + triggers: ['TRIGGER_2', 'TRIGGER_3'], + action: { + factoryId: 'FACTORY_ID_2', + name: 'Click me, too!', + config: { + doCleanup: true, + }, + }, + }, + { + eventId: 'eventId-3', + triggers: ['TRIGGER_4', 'TRIGGER_1'], + action: { + factoryId: 'FACTORY_ID_3', + name: 'Go to documentation', + config: { + url: 'http://google.com', + iamFeelingLucky: true, + }, + }, + }, + ], +}; + +beforeEach(() => { + Object.values(factories).forEach((factory) => { + ((factory.telemetry as unknown) as jest.SpyInstance).mockClear(); + }); +}); + +describe('dynamicActionFactoriesCollector', () => { + test('returns empty stats when there are not dynamic actions', () => { + const stats = dynamicActionFactoriesCollector( + getActionFactory, + { + events: [], + }, + {} + ); + + expect(stats).toEqual({}); + }); + + test('calls .telemetry() method of a supplied factory', () => { + const currentState = { + events: [state.events[0]], + }; + dynamicActionFactoriesCollector(getActionFactory, currentState, {}); + + const spy1 = (factories.FACTORY_ID_1.telemetry as unknown) as jest.SpyInstance; + const spy2 = (factories.FACTORY_ID_2.telemetry as unknown) as jest.SpyInstance; + + expect(spy1).toHaveBeenCalledTimes(1); + expect(spy2).toHaveBeenCalledTimes(0); + + expect(spy1.mock.calls[0][0]).toEqual(currentState.events[0]); + expect(typeof spy1.mock.calls[0][1]).toBe('object'); + expect(!!spy1.mock.calls[0][1]).toBe(true); + }); + + test('returns stats received from factory', () => { + const currentState = { + events: [state.events[0]], + }; + const stats = dynamicActionFactoriesCollector(getActionFactory, currentState, {}); + + expect(stats).toEqual({ + myStat_1: 1, + myStat_2: 123, + }); + }); +}); diff --git a/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.ts b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.ts new file mode 100644 index 00000000000000..2ece6102c27a4c --- /dev/null +++ b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_action_factories_collector.ts @@ -0,0 +1,24 @@ +/* + * 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 { DynamicActionsState } from '../../common'; +import { ActionFactory } from '../types'; + +export const dynamicActionFactoriesCollector = ( + getActionFactory: (id: string) => undefined | ActionFactory, + state: DynamicActionsState, + stats: Record<string, any> +): Record<string, any> => { + for (const event of state.events) { + const factory = getActionFactory(event.action.factoryId); + + if (factory) { + stats = factory.telemetry(event, stats); + } + } + + return stats; +}; diff --git a/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.test.ts b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.test.ts new file mode 100644 index 00000000000000..99217cd98fa01a --- /dev/null +++ b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.test.ts @@ -0,0 +1,271 @@ +/* + * 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. + */ + +/* eslint-disable @typescript-eslint/naming-convention */ + +import { dynamicActionsCollector } from './dynamic_actions_collector'; +import { DynamicActionsState } from '../../common'; + +const state: DynamicActionsState = { + events: [ + { + eventId: 'eventId-1', + triggers: ['TRIGGER_1'], + action: { + factoryId: 'FACTORY_ID_1', + name: 'Click me!', + config: {}, + }, + }, + { + eventId: 'eventId-2', + triggers: ['TRIGGER_2', 'TRIGGER_3'], + action: { + factoryId: 'FACTORY_ID_2', + name: 'Click me, too!', + config: { + doCleanup: true, + }, + }, + }, + { + eventId: 'eventId-3', + triggers: ['TRIGGER_4', 'TRIGGER_1'], + action: { + factoryId: 'FACTORY_ID_1', + name: 'Go to documentation', + config: { + url: 'http://google.com', + iamFeelingLucky: true, + }, + }, + }, + ], +}; + +describe('dynamicActionsCollector', () => { + describe('dynamic action count', () => { + test('equal to zero when there are no dynamic actions', () => { + const stats = dynamicActionsCollector( + { + events: [], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.count': 0, + }); + }); + + test('does not update existing count if there are no dynamic actions', () => { + const stats = dynamicActionsCollector( + { + events: [], + }, + { + 'dynamicActions.count': 25, + } + ); + + expect(stats).toMatchObject({ + 'dynamicActions.count': 25, + }); + }); + + test('equal to one when there is one dynamic action', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.count': 1, + }); + }); + + test('adds one to the current dynamic action count', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + { + 'dynamicActions.count': 2, + } + ); + + expect(stats).toMatchObject({ + 'dynamicActions.count': 3, + }); + }); + + test('equal to three when there are three dynamic action', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0], state.events[1], state.events[2]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.count': 3, + }); + }); + }); + + describe('registered action counts', () => { + test('for single action sets count to one', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.actions.FACTORY_ID_1.count': 1, + }); + }); + + test('adds count to existing action counts', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + { + 'dynamicActions.actions.FACTORY_ID_1.count': 5, + 'dynamicActions.actions.FACTORY_ID_2.count': 1, + } + ); + + expect(stats).toMatchObject({ + 'dynamicActions.actions.FACTORY_ID_1.count': 6, + 'dynamicActions.actions.FACTORY_ID_2.count': 1, + }); + }); + + test('aggregates count factory count', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0], state.events[2]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.actions.FACTORY_ID_1.count': 2, + }); + }); + + test('returns counts for every factory type', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0], state.events[2], state.events[1]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.actions.FACTORY_ID_1.count': 2, + 'dynamicActions.actions.FACTORY_ID_2.count': 1, + }); + }); + }); + + describe('action trigger counts', () => { + test('for single action sets count to one', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.triggers.TRIGGER_1.count': 1, + }); + }); + + test('adds count to existing stats', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + { + 'dynamicActions.triggers.TRIGGER_1.count': 123, + } + ); + + expect(stats).toMatchObject({ + 'dynamicActions.triggers.TRIGGER_1.count': 124, + }); + }); + + test('aggregates trigger counts from all dynamic actions', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0], state.events[2], state.events[1]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.triggers.TRIGGER_1.count': 2, + 'dynamicActions.triggers.TRIGGER_2.count': 1, + 'dynamicActions.triggers.TRIGGER_3.count': 1, + 'dynamicActions.triggers.TRIGGER_4.count': 1, + }); + }); + }); + + describe('action x trigger counts', () => { + test('returns single action (factoryId x trigger) stat', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.action_triggers.FACTORY_ID_1_TRIGGER_1.count': 1, + }); + }); + + test('adds count to existing stats', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0]], + }, + { + 'dynamicActions.action_triggers.FACTORY_ID_1_TRIGGER_1.count': 3, + } + ); + + expect(stats).toMatchObject({ + 'dynamicActions.action_triggers.FACTORY_ID_1_TRIGGER_1.count': 4, + }); + }); + + test('aggregates actions x triggers counts for all events', () => { + const stats = dynamicActionsCollector( + { + events: [state.events[0], state.events[2], state.events[1]], + }, + {} + ); + + expect(stats).toMatchObject({ + 'dynamicActions.action_triggers.FACTORY_ID_1_TRIGGER_1.count': 2, + 'dynamicActions.action_triggers.FACTORY_ID_2_TRIGGER_2.count': 1, + 'dynamicActions.action_triggers.FACTORY_ID_2_TRIGGER_3.count': 1, + 'dynamicActions.action_triggers.FACTORY_ID_1_TRIGGER_4.count': 1, + }); + }); + }); +}); diff --git a/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.ts b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.ts new file mode 100644 index 00000000000000..ae595776fda583 --- /dev/null +++ b/x-pack/plugins/ui_actions_enhanced/server/telemetry/dynamic_actions_collector.ts @@ -0,0 +1,36 @@ +/* + * 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 { DynamicActionsState } from '../../common'; +import { getMetricKey } from './get_metric_key'; + +export const dynamicActionsCollector = ( + state: DynamicActionsState, + stats: Record<string, any> +): Record<string, any> => { + const countMetricKey = getMetricKey('count'); + + stats[countMetricKey] = state.events.length + (stats[countMetricKey] || 0); + + for (const event of state.events) { + const factoryId = event.action.factoryId; + const actionCountMetric = getMetricKey(`actions.${factoryId}.count`); + + stats[actionCountMetric] = 1 + (stats[actionCountMetric] || 0); + + for (const trigger of event.triggers) { + const triggerCountMetric = getMetricKey(`triggers.${trigger}.count`); + const actionXTriggerCountMetric = getMetricKey( + `action_triggers.${factoryId}_${trigger}.count` + ); + + stats[triggerCountMetric] = 1 + (stats[triggerCountMetric] || 0); + stats[actionXTriggerCountMetric] = 1 + (stats[actionXTriggerCountMetric] || 0); + } + } + + return stats; +}; diff --git a/x-pack/plugins/ui_actions_enhanced/server/telemetry/get_metric_key.ts b/x-pack/plugins/ui_actions_enhanced/server/telemetry/get_metric_key.ts new file mode 100644 index 00000000000000..6d3ae370c52004 --- /dev/null +++ b/x-pack/plugins/ui_actions_enhanced/server/telemetry/get_metric_key.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. + */ + +const prefix = 'dynamicActions.'; + +/** Returns prefixed telemetry metric key for all dynamic action metrics. */ +export const getMetricKey = (path: string) => `${prefix}${path}`; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_journey.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_journey.tsx index 9a3e045017f9a2..0c47e4c73e9769 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_journey.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/executed_journey.tsx @@ -57,29 +57,31 @@ interface ExecutedJourneyProps { journey: JourneyState; } -export const ExecutedJourney: FC<ExecutedJourneyProps> = ({ journey }) => ( - <div> - <EuiText> - <h3> - <FormattedMessage - id="xpack.uptime.synthetics.executedJourney.heading" - defaultMessage="Summary information" - /> - </h3> - <p> - {statusMessage( - journey.steps - .filter(isStepEnd) - .reduce(reduceStepStatus, { failed: 0, skipped: 0, succeeded: 0 }) - )} - </p> - </EuiText> - <EuiSpacer /> - <EuiFlexGroup direction="column"> - {journey.steps.filter(isStepEnd).map((step, index) => ( - <ExecutedStep key={index} index={index} step={step} /> - ))} - <EuiSpacer size="s" /> - </EuiFlexGroup> - </div> -); +export const ExecutedJourney: FC<ExecutedJourneyProps> = ({ journey }) => { + return ( + <div> + <EuiText> + <h3> + <FormattedMessage + id="xpack.uptime.synthetics.executedJourney.heading" + defaultMessage="Summary information" + /> + </h3> + <p> + {statusMessage( + journey.steps + .filter(isStepEnd) + .reduce(reduceStepStatus, { failed: 0, skipped: 0, succeeded: 0 }) + )} + </p> + </EuiText> + <EuiSpacer /> + <EuiFlexGroup direction="column"> + {journey.steps.filter(isStepEnd).map((step, index) => ( + <ExecutedStep key={index} index={index} step={step} /> + ))} + <EuiSpacer size="s" /> + </EuiFlexGroup> + </div> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/README.md b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/README.md new file mode 100644 index 00000000000000..cf8d3b5345eaab --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/README.md @@ -0,0 +1,123 @@ +# Waterfall chart + +## Introduction + +The waterfall chart component aims to be agnostic in it's approach, so that a variety of consumers / solutions can use it. Some of Elastic Chart's features are used in a non-standard way to facilitate this flexibility, this README aims to cover some of the things that might be less obvious, and also provides a high level overview of implementation. + +## Requirements for usage + +The waterfall chart component asssumes that the consumer is making use of `KibanaReactContext`, and as such things like `useKibana` can be called. + +Consumers are also expected to be using the `<EuiThemeProvider />` so that the waterfall chart can apply styled-component styles based on the EUI theme. + +These are the two hard requirements, but almost all plugins will be using these. + +## Rendering + +At it's core the watefall chart is a stacked bar chart that has been rotated through 90 degrees. As such it's important to understand that `x` is now represented as `y` and vice versa. + +## Flexibility + +This section aims to cover some things that are non-standard. + +### Tooltip + +By default the formatting of tooltip values is very basic, but for a waterfall chart there needs to be a great deal of flexibility to represent whatever breakdown you're trying to show. + +As such a custom tooltip component is used. This custom component would usually only have access to some basic props that pertain to the values of the hovered bar. The waterfall chart component extends this by making us of a waterfall chart context. + +The custom tooltip component can use the context to access the full set of chart data, find the relevant items (those with the same `x` value) and call a custom `renderTooltipItem` for each item, `renderTooltipItem` will be passed `item.config.tooltipProps`. Every consumer can choose what they use for their `tooltipProps`. + +Some consumers might need colours, some might need iconography and so on. The waterfall chart doesn't make assumptions, and will render out the React content returned by `renderTooltipItem`. + +IMPORTANT: `renderTooltipItem` is provided via context and not as a direct prop due to the fact the custom tooltip component would usually only have access to the props provided directly to it from Elastic Charts. + +### Colours + +The easiest way to facilitate specific colours for each stack (let's say your colours are mapped to a constraint like mime type) is to assign the colour directly on your datum `config` property, and then access this directly in the `barStyleAccessor` function, e.g. + +``` +barStyleAccessor={(datum) => { + return datum.datum.config.colour; +}) +``` + +### Config + +The notion of `config` has been mentioned already. But this is a place that consumers can store their solution specific properties. `renderTooltipItem` will make use of `config.tooltipProps`, and `barStyleAccessor` can make use of anything on `config`. + +### Sticky top axis + +By default there is no "sticky" axis functionality in Elastic Charts, therefore a second chart is rendered, this contains a replica of the top axis, and renders one empty data point (as a chart can't only have an axis). This second chart is then positioned in such a way that it covers the top of the real axis, and remains fixed. + +## Data + +The waterfall chart expects data in a relatively simple format, there are the usual plot properties (`x`, `y0`, and `y`) and then `config`. E.g. + +``` +const series = [ + {x: 0, y: 0, y: 100, config: { tooltipProps: { type: 'dns' }}}, + {x: 0, y0: 300, y: 500, config: { tooltipProps: { type: 'ssl' }}}, + {x: 1, y0: 250, y: 300, config: { tooltipProps: { propA: 'somethingBreakdownRelated' }}}, + {x: 1, y0: 500, y: 600, config: { tooltipProps: { propA: 'anotherBreakdown' }}}, +] +``` + +Gaps in bars are fine, and to be expected for certain solutions. + +## Sidebar items + +The waterfall chart component again doesn't make assumptions about consumer's sidebar items' content, but the waterfall chart does handle the rendering so the sidebar can be aligned and rendered properly alongside the chart itself. + +`sidebarItems` should be provided to the context, and a `renderSidebarItem` prop should be provided to the chart. + +A sidebar is optional. + +There is a great deal of flexibility here so that solutions can make use of this in the way they need. For example, if you'd like to add a toggle functionality, so that clicking an item shows / hides it's children, this would involve rendering your toggle in `renderSidebarItem` and then when clicked you can handle adjusting your data as necessary. + +IMPORTANT: It is important to understand that the chart itself makes use of a fixed height. The sidebar will create a space that has a matching height. Each item is assigned equal space vertically via Flexbox, so that the items align with the relevant bar to the right (these are two totally different rendering contexts, with the chart itself sitting within a `canvas` element). So it's important that whatever content you choose to render here doesn't exceed the available height available to each item. The chart's height is calculated as `numberOfBars * 32`, so content should be kept within that `32px` threshold. + +## Legend items + +Much the same as with the sidebar items, no assumptions are made here, solutions will have different aims. + +`legendItems` should be provided to the context, and a `renderLegendItem` prop should be provided to the chart. + +A legend is optional. + +## Overall usage + +Pulling all of this together, things look like this (for a specific solution): + +``` +const renderSidebarItem: RenderItem<SidebarItem> = (item, index) => { + return <MiddleTruncatedText text={`${index + 1}. ${item.url}`} />; +}; + +const renderLegendItem: RenderItem<LegendItem> = (item) => { + return <EuiHealth color={item.colour}>{item.name}</EuiHealth>; +}; + +<WaterfallProvider + data={series} + sidebarItems={sidebarItems} + legendItems={legendItems} + renderTooltipItem={(tooltipProps) => { + return <EuiHealth color={String(tooltipProps.colour)}>{tooltipProps.value}</EuiHealth>; + }} +> + <WaterfallChart + tickFormat={(d: number) => `${Number(d).toFixed(0)} ms`} + domain={{ min: domain.min, max: domain.max }} + barStyleAccessor={(datum) => { + return datum.datum.config.colour; + }} + renderSidebarItem={renderSidebarItem} + renderLegendItem={renderLegendItem} + /> +</WaterfallProvider> +``` + +A solution could easily forego a sidebar and legend for a more minimalistic view, e.g. maybe a mini waterfall within a table column. + + diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.ts new file mode 100644 index 00000000000000..ac650c5ef0dddb --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/constants.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. + */ + +// Pixel value +export const BAR_HEIGHT = 32; +// Flex grow value +export const MAIN_GROW_SIZE = 8; +// Flex grow value +export const SIDEBAR_GROW_SIZE = 2; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/legend.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/legend.tsx new file mode 100644 index 00000000000000..85a205a7256f31 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/legend.tsx @@ -0,0 +1,28 @@ +/* + * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { IWaterfallContext } from '../context/waterfall_chart'; +import { WaterfallChartLegendContainer } from './styles'; +import { WaterfallChartProps } from './waterfall_chart'; + +interface LegendProps { + items: Required<IWaterfallContext>['legendItems']; + render: Required<WaterfallChartProps>['renderLegendItem']; +} + +export const Legend: React.FC<LegendProps> = ({ items, render }) => { + return ( + <WaterfallChartLegendContainer> + <EuiFlexGroup gutterSize="none"> + {items.map((item, index) => { + return <EuiFlexItem key={index}>{render(item, index)}</EuiFlexItem>; + })} + </EuiFlexGroup> + </WaterfallChartLegendContainer> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.test.tsx new file mode 100644 index 00000000000000..4f54a347d22d23 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.test.tsx @@ -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 { getChunks, MiddleTruncatedText } from './middle_truncated_text'; +import { shallowWithIntl } from '@kbn/test/jest'; +import React from 'react'; + +const longString = + 'this-is-a-really-really-really-really-really-really-really-really-long-string.madeup.extension'; + +describe('getChunks', () => { + it('Calculates chunks correctly', () => { + const result = getChunks(longString); + expect(result).toEqual({ + first: 'this-is-a-really-really-really-really-really-really-really-really-long-string.made', + last: 'up.extension', + }); + }); +}); + +describe('Component', () => { + it('Renders correctly', () => { + expect(shallowWithIntl(<MiddleTruncatedText text={longString} />)).toMatchInlineSnapshot(` + <styled.div> + <styled.div> + <styled.span> + this-is-a-really-really-really-really-really-really-really-really-long-string.made + </styled.span> + <styled.span> + up.extension + </styled.span> + </styled.div> + </styled.div> + `); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx new file mode 100644 index 00000000000000..519927d7db28bf --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/middle_truncated_text.tsx @@ -0,0 +1,61 @@ +/* + * 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, { useMemo } from 'react'; +import styled from 'styled-components'; + +const OuterContainer = styled.div` + width: 100%; + height: 100%; + position: relative; +`; + +const InnerContainer = styled.div` + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + overflow: hidden; + display: flex; + min-width: 0; +`; // NOTE: min-width: 0 ensures flexbox and no-wrap children can co-exist + +const FirstChunk = styled.span` + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +`; + +const LastChunk = styled.span` + flex-shrink: 0; +`; + +export const getChunks = (text: string) => { + const END_CHARS = 12; + const chars = text.split(''); + const splitPoint = chars.length - END_CHARS > 0 ? chars.length - END_CHARS : null; + const endChars = splitPoint ? chars.splice(splitPoint) : []; + return { first: chars.join(''), last: endChars.join('') }; +}; + +// Helper component for adding middle text truncation, e.g. +// really-really-really-long....ompressed.js +// Can be used to accomodate content in sidebar item rendering. +export const MiddleTruncatedText = ({ text }: { text: string }) => { + const chunks = useMemo(() => { + return getChunks(text); + }, [text]); + + return ( + <OuterContainer> + <InnerContainer> + <FirstChunk>{chunks.first}</FirstChunk> + <LastChunk>{chunks.last}</LastChunk> + </InnerContainer> + </OuterContainer> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/sidebar.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/sidebar.tsx new file mode 100644 index 00000000000000..9ff544fc1946b9 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/sidebar.tsx @@ -0,0 +1,43 @@ +/* + * 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 { EuiFlexItem } from '@elastic/eui'; +import { SIDEBAR_GROW_SIZE } from './constants'; +import { IWaterfallContext } from '../context/waterfall_chart'; +import { + WaterfallChartSidebarContainer, + WaterfallChartSidebarContainerInnerPanel, + WaterfallChartSidebarContainerFlexGroup, + WaterfallChartSidebarFlexItem, +} from './styles'; +import { WaterfallChartProps } from './waterfall_chart'; + +interface SidebarProps { + items: Required<IWaterfallContext>['sidebarItems']; + height: number; + render: Required<WaterfallChartProps>['renderSidebarItem']; +} + +export const Sidebar: React.FC<SidebarProps> = ({ items, height, render }) => { + return ( + <EuiFlexItem grow={SIDEBAR_GROW_SIZE}> + <WaterfallChartSidebarContainer height={height}> + <WaterfallChartSidebarContainerInnerPanel paddingSize="none"> + <WaterfallChartSidebarContainerFlexGroup direction="column" gutterSize="none"> + {items.map((item, index) => { + return ( + <WaterfallChartSidebarFlexItem key={index}> + {render(item, index)} + </WaterfallChartSidebarFlexItem> + ); + })} + </WaterfallChartSidebarContainerFlexGroup> + </WaterfallChartSidebarContainerInnerPanel> + </WaterfallChartSidebarContainer> + </EuiFlexItem> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts new file mode 100644 index 00000000000000..25f5e5f8f5cc9b --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/styles.ts @@ -0,0 +1,89 @@ +/* + * 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 { EuiPanel, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { euiStyled } from '../../../../../../../observability/public'; + +// NOTE: This isn't a perfect solution - changes in font size etc within charts could change the ideal height here. +const FIXED_AXIS_HEIGHT = 33; + +interface WaterfallChartOuterContainerProps { + height?: number; +} + +export const WaterfallChartOuterContainer = euiStyled.div<WaterfallChartOuterContainerProps>` + height: ${(props) => (props.height ? `${props.height}px` : 'auto')}; + overflow-y: ${(props) => (props.height ? 'scroll' : 'visible')}; + overflow-x: hidden; +`; + +export const WaterfallChartFixedTopContainer = euiStyled.div` + position: sticky; + top: 0; + z-index: ${(props) => props.theme.eui.euiZLevel4}; +`; + +export const WaterfallChartFixedTopContainerSidebarCover = euiStyled(EuiPanel)` + height: 100%; + border-radius: 0 !important; + border: none; +`; // NOTE: border-radius !important is here as the "border" prop isn't working + +export const WaterfallChartFixedAxisContainer = euiStyled.div` + height: ${FIXED_AXIS_HEIGHT}px; +`; + +interface WaterfallChartSidebarContainer { + height: number; +} + +export const WaterfallChartSidebarContainer = euiStyled.div<WaterfallChartSidebarContainer>` + height: ${(props) => `${props.height - FIXED_AXIS_HEIGHT}px`}; + overflow-y: hidden; +`; + +export const WaterfallChartSidebarContainerInnerPanel = euiStyled(EuiPanel)` + border: 0; + height: 100%; +`; + +export const WaterfallChartSidebarContainerFlexGroup = euiStyled(EuiFlexGroup)` + height: 100%; +`; + +// Ensures flex items honour no-wrap of children, rather than trying to extend to the full width of children. +export const WaterfallChartSidebarFlexItem = euiStyled(EuiFlexItem)` + min-width: 0; + padding-left: ${(props) => props.theme.eui.paddingSizes.m}; + padding-right: ${(props) => props.theme.eui.paddingSizes.m}; +`; + +interface WaterfallChartChartContainer { + height: number; +} + +export const WaterfallChartChartContainer = euiStyled.div<WaterfallChartChartContainer>` + width: 100%; + height: ${(props) => `${props.height}px`}; + margin-top: -${FIXED_AXIS_HEIGHT}px; +`; + +export const WaterfallChartLegendContainer = euiStyled.div` + position: sticky; + bottom: 0; + z-index: ${(props) => props.theme.eui.euiZLevel4}; + background-color: ${(props) => props.theme.eui.euiColorLightestShade}; + padding: ${(props) => props.theme.eui.paddingSizes.xs}; + font-size: ${(props) => props.theme.eui.euiFontSizeXS}; + box-shadow: 0px -1px 4px 0px ${(props) => props.theme.eui.euiColorLightShade}; +`; // NOTE: EuiShadowColor is a little too dark to work with the background-color + +export const WaterfallChartTooltip = euiStyled.div` + background-color: ${(props) => props.theme.eui.euiColorDarkestShade}; + border-radius: ${(props) => props.theme.eui.euiBorderRadius}; + color: ${(props) => props.theme.eui.euiColorLightestShade}; + padding: ${(props) => props.theme.eui.paddingSizes.s}; +`; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx new file mode 100644 index 00000000000000..de4be0ea34b2c4 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/components/waterfall_chart.tsx @@ -0,0 +1,194 @@ +/* + * 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, { useMemo } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { + Axis, + BarSeries, + Chart, + Position, + ScaleType, + Settings, + TickFormatter, + DomainRange, + BarStyleAccessor, + TooltipInfo, + TooltipType, +} from '@elastic/charts'; +import { EUI_CHARTS_THEME_DARK, EUI_CHARTS_THEME_LIGHT } from '@elastic/eui/dist/eui_charts_theme'; +// NOTE: The WaterfallChart has a hard requirement that consumers / solutions are making use of KibanaReactContext, and useKibana etc +// can therefore be accessed. +import { useUiSetting$ } from '../../../../../../../../../src/plugins/kibana_react/public'; +import { useWaterfallContext } from '../context/waterfall_chart'; +import { + WaterfallChartOuterContainer, + WaterfallChartFixedTopContainer, + WaterfallChartFixedTopContainerSidebarCover, + WaterfallChartFixedAxisContainer, + WaterfallChartChartContainer, + WaterfallChartTooltip, +} from './styles'; +import { WaterfallData } from '../types'; +import { BAR_HEIGHT, MAIN_GROW_SIZE, SIDEBAR_GROW_SIZE } from './constants'; +import { Sidebar } from './sidebar'; +import { Legend } from './legend'; + +const Tooltip = ({ header }: TooltipInfo) => { + const { data, renderTooltipItem } = useWaterfallContext(); + const relevantItems = data.filter((item) => { + return item.x === header?.value; + }); + return ( + <WaterfallChartTooltip> + <EuiFlexGroup direction="column" gutterSize="none"> + {relevantItems.map((item, index) => { + return ( + <EuiFlexItem key={index}>{renderTooltipItem(item.config.tooltipProps)}</EuiFlexItem> + ); + })} + </EuiFlexGroup> + </WaterfallChartTooltip> + ); +}; + +export type RenderItem<I = any> = (item: I, index: number) => JSX.Element; + +export interface WaterfallChartProps { + tickFormat: TickFormatter; + domain: DomainRange; + barStyleAccessor: BarStyleAccessor; + renderSidebarItem?: RenderItem; + renderLegendItem?: RenderItem; + maxHeight?: number; +} + +const getUniqueBars = (data: WaterfallData) => { + return data.reduce<Set<number>>((acc, item) => { + if (!acc.has(item.x)) { + acc.add(item.x); + return acc; + } else { + return acc; + } + }, new Set()); +}; + +const getChartHeight = (data: WaterfallData): number => getUniqueBars(data).size * BAR_HEIGHT; + +export const WaterfallChart = ({ + tickFormat, + domain, + barStyleAccessor, + renderSidebarItem, + renderLegendItem, + maxHeight = 600, +}: WaterfallChartProps) => { + const { data, sidebarItems, legendItems } = useWaterfallContext(); + + const generatedHeight = useMemo(() => { + return getChartHeight(data); + }, [data]); + + const [darkMode] = useUiSetting$<boolean>('theme:darkMode'); + + const theme = useMemo(() => { + return darkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme; + }, [darkMode]); + + const shouldRenderSidebar = + sidebarItems && sidebarItems.length > 0 && renderSidebarItem ? true : false; + const shouldRenderLegend = + legendItems && legendItems.length > 0 && renderLegendItem ? true : false; + + return ( + <WaterfallChartOuterContainer height={maxHeight}> + <> + <WaterfallChartFixedTopContainer> + <EuiFlexGroup gutterSize="none"> + {shouldRenderSidebar && ( + <EuiFlexItem grow={SIDEBAR_GROW_SIZE}> + <WaterfallChartFixedTopContainerSidebarCover paddingSize="none" /> + </EuiFlexItem> + )} + <EuiFlexItem grow={shouldRenderSidebar ? MAIN_GROW_SIZE : true}> + <WaterfallChartFixedAxisContainer> + <Chart className="axis-only-chart"> + <Settings + showLegend={false} + rotation={90} + tooltip={{ type: TooltipType.None }} + theme={theme} + /> + + <Axis + id="time" + position={Position.Top} + tickFormat={tickFormat} + domain={domain} + showGridLines={true} + /> + + <Axis id="values" position={Position.Left} tickFormat={() => ''} /> + + <BarSeries + id="waterfallItems" + xScaleType={ScaleType.Linear} + yScaleType={ScaleType.Linear} + xAccessor="x" + yAccessors={['y']} + y0Accessors={['y0']} + styleAccessor={barStyleAccessor} + data={[{ x: 0, y0: 0, y1: 0 }]} + /> + </Chart> + </WaterfallChartFixedAxisContainer> + </EuiFlexItem> + </EuiFlexGroup> + </WaterfallChartFixedTopContainer> + <EuiFlexGroup gutterSize="none"> + {shouldRenderSidebar && ( + <Sidebar items={sidebarItems!} height={generatedHeight} render={renderSidebarItem!} /> + )} + <EuiFlexItem grow={shouldRenderSidebar ? MAIN_GROW_SIZE : true}> + <WaterfallChartChartContainer height={generatedHeight}> + <Chart className="data-chart"> + <Settings + showLegend={false} + rotation={90} + tooltip={{ customTooltip: Tooltip }} + theme={theme} + /> + + <Axis + id="time" + position={Position.Top} + tickFormat={tickFormat} + domain={domain} + showGridLines={true} + /> + + <Axis id="values" position={Position.Left} tickFormat={() => ''} /> + + <BarSeries + id="waterfallItems" + xScaleType={ScaleType.Linear} + yScaleType={ScaleType.Linear} + xAccessor="x" + yAccessors={['y']} + y0Accessors={['y0']} + styleAccessor={barStyleAccessor} + data={data} + /> + </Chart> + </WaterfallChartChartContainer> + </EuiFlexItem> + </EuiFlexGroup> + {shouldRenderLegend && <Legend items={legendItems!} render={renderLegendItem!} />} + </> + </WaterfallChartOuterContainer> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.test.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.test.ts new file mode 100644 index 00000000000000..698e6b4be0c4c4 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.test.ts @@ -0,0 +1,687 @@ +/* + * 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 { colourPalette } from './data_formatting'; + +// const TEST_DATA = [ +// { +// '@timestamp': '2020-10-29T14:55:01.055Z', +// ecs: { +// version: '1.6.0', +// }, +// agent: { +// type: 'heartbeat', +// version: '7.10.0', +// hostname: 'docker-desktop', +// ephemeral_id: '34179df8-f97c-46a2-9e73-33976d4ac58d', +// id: '5a03ad5f-cc18-43e8-8f82-6b08b9ceb36a', +// name: 'docker-desktop', +// }, +// synthetics: { +// index: 7, +// payload: { +// request: { +// url: 'https://unpkg.com/director@1.2.8/build/director.js', +// method: 'GET', +// headers: { +// referer: '', +// user_agent: +// 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4287.0 Safari/537.36', +// }, +// mixed_content_type: 'none', +// initial_priority: 'High', +// referrer_policy: 'no-referrer-when-downgrade', +// }, +// status: 200, +// method: 'GET', +// end: 13902.944973, +// url: 'https://unpkg.com/director@1.2.8/build/director.js', +// type: 'Script', +// is_navigation_request: false, +// start: 13902.752946, +// response: { +// encoded_data_length: 179, +// protocol: 'h2', +// headers: { +// content_encoding: 'br', +// server: 'cloudflare', +// age: '94838', +// cf_cache_status: 'HIT', +// x_content_type_options: 'nosniff', +// last_modified: 'Wed, 04 Feb 2015 03:25:28 GMT', +// cf_ray: '5e9dbc2bdda2e5a7-MAN', +// content_type: 'application/javascript; charset=utf-8', +// x_cloud_trace_context: 'eec7acc7a6f96b5353ef0d648bf437ac', +// expect_ct: +// 'max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"', +// access_control_allow_origin: '*', +// vary: 'Accept-Encoding', +// cache_control: 'public, max-age=31536000', +// date: 'Thu, 29 Oct 2020 14:55:00 GMT', +// cf_request_id: '061673ef6b0000e5a7cd07a000000001', +// etag: 'W/"4f70-NHpXdyWxnckEaeiXalAnXQ+oh4Q"', +// strict_transport_security: 'max-age=31536000; includeSubDomains; preload', +// }, +// remote_i_p_address: '104.16.125.175', +// connection_reused: true, +// timing: { +// dns_start: -1, +// push_end: 0, +// worker_fetch_start: -1, +// worker_respond_with_settled: -1, +// proxy_end: -1, +// worker_start: -1, +// worker_ready: -1, +// send_end: 158.391, +// connect_end: -1, +// connect_start: -1, +// send_start: 157.876, +// proxy_start: -1, +// push_start: 0, +// ssl_end: -1, +// receive_headers_end: 186.885, +// ssl_start: -1, +// request_time: 13902.757525, +// dns_end: -1, +// }, +// connection_id: 17, +// status_text: '', +// remote_port: 443, +// status: 200, +// security_details: { +// valid_to: 1627905600, +// certificate_id: 0, +// key_exchange_group: 'X25519', +// valid_from: 1596326400, +// protocol: 'TLS 1.3', +// issuer: 'Cloudflare Inc ECC CA-3', +// key_exchange: '', +// san_list: ['unpkg.com', '*.unpkg.com', 'sni.cloudflaressl.com'], +// signed_certificate_timestamp_list: [], +// certificate_transparency_compliance: 'unknown', +// cipher: 'AES_128_GCM', +// subject_name: 'sni.cloudflaressl.com', +// }, +// mime_type: 'application/javascript', +// url: 'https://unpkg.com/director@1.2.8/build/director.js', +// from_prefetch_cache: false, +// from_disk_cache: false, +// security_state: 'secure', +// response_time: 1.603983300513211e12, +// from_service_worker: false, +// }, +// }, +// journey: { +// name: 'check that title is present', +// id: 'check that title is present', +// }, +// type: 'journey/network_info', +// package_version: '0.0.1', +// }, +// monitor: { +// status: 'up', +// duration: { +// us: 24, +// }, +// id: 'check that title is present', +// name: 'check that title is present', +// type: 'browser', +// timespan: { +// gte: '2020-10-29T14:55:01.055Z', +// lt: '2020-10-29T14:56:01.055Z', +// }, +// check_group: '948d3b6b-19f6-11eb-b237-025000000001', +// }, +// event: { +// dataset: 'uptime', +// }, +// }, +// { +// '@timestamp': '2020-10-29T14:55:01.055Z', +// ecs: { +// version: '1.6.0', +// }, +// agent: { +// version: '7.10.0', +// hostname: 'docker-desktop', +// ephemeral_id: '34179df8-f97c-46a2-9e73-33976d4ac58d', +// id: '5a03ad5f-cc18-43e8-8f82-6b08b9ceb36a', +// name: 'docker-desktop', +// type: 'heartbeat', +// }, +// monitor: { +// check_group: '948d3b6b-19f6-11eb-b237-025000000001', +// status: 'up', +// duration: { +// us: 13, +// }, +// id: 'check that title is present', +// name: 'check that title is present', +// type: 'browser', +// timespan: { +// gte: '2020-10-29T14:55:01.055Z', +// lt: '2020-10-29T14:56:01.055Z', +// }, +// }, +// synthetics: { +// journey: { +// name: 'check that title is present', +// id: 'check that title is present', +// }, +// type: 'journey/network_info', +// package_version: '0.0.1', +// index: 9, +// payload: { +// start: 13902.76168, +// url: 'file:///opt/examples/todos/app/app.js', +// method: 'GET', +// is_navigation_request: false, +// end: 13902.770133, +// request: { +// headers: { +// referer: '', +// user_agent: +// 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4287.0 Safari/537.36', +// }, +// mixed_content_type: 'none', +// initial_priority: 'High', +// referrer_policy: 'no-referrer-when-downgrade', +// url: 'file:///opt/examples/todos/app/app.js', +// method: 'GET', +// }, +// status: 0, +// type: 'Script', +// response: { +// protocol: 'file', +// connection_reused: false, +// mime_type: 'text/javascript', +// security_state: 'secure', +// from_disk_cache: false, +// url: 'file:///opt/examples/todos/app/app.js', +// status_text: '', +// connection_id: 0, +// from_prefetch_cache: false, +// encoded_data_length: -1, +// headers: {}, +// status: 0, +// from_service_worker: false, +// }, +// }, +// }, +// event: { +// dataset: 'uptime', +// }, +// }, +// { +// '@timestamp': '2020-10-29T14:55:01.000Z', +// monitor: { +// timespan: { +// lt: '2020-10-29T14:56:01.000Z', +// gte: '2020-10-29T14:55:01.000Z', +// }, +// id: 'check that title is present', +// name: 'check that title is present', +// check_group: '948d3b6b-19f6-11eb-b237-025000000001', +// status: 'up', +// duration: { +// us: 44365, +// }, +// type: 'browser', +// }, +// synthetics: { +// journey: { +// id: 'check that title is present', +// name: 'check that title is present', +// }, +// type: 'journey/network_info', +// package_version: '0.0.1', +// index: 5, +// payload: { +// status: 0, +// url: 'file:///opt/examples/todos/app/index.html', +// end: 13902.730261, +// request: { +// method: 'GET', +// headers: {}, +// mixed_content_type: 'none', +// initial_priority: 'VeryHigh', +// referrer_policy: 'no-referrer-when-downgrade', +// url: 'file:///opt/examples/todos/app/index.html', +// }, +// method: 'GET', +// response: { +// status: 0, +// connection_id: 0, +// from_disk_cache: false, +// headers: {}, +// encoded_data_length: -1, +// status_text: '', +// from_service_worker: false, +// connection_reused: false, +// url: 'file:///opt/examples/todos/app/index.html', +// remote_port: 0, +// security_state: 'secure', +// protocol: 'file', +// mime_type: 'text/html', +// remote_i_p_address: '', +// from_prefetch_cache: false, +// }, +// start: 13902.726626, +// type: 'Document', +// is_navigation_request: true, +// }, +// }, +// event: { +// dataset: 'uptime', +// }, +// ecs: { +// version: '1.6.0', +// }, +// agent: { +// ephemeral_id: '34179df8-f97c-46a2-9e73-33976d4ac58d', +// id: '5a03ad5f-cc18-43e8-8f82-6b08b9ceb36a', +// name: 'docker-desktop', +// type: 'heartbeat', +// version: '7.10.0', +// hostname: 'docker-desktop', +// }, +// }, +// { +// '@timestamp': '2020-10-29T14:55:01.044Z', +// monitor: { +// type: 'browser', +// timespan: { +// lt: '2020-10-29T14:56:01.044Z', +// gte: '2020-10-29T14:55:01.044Z', +// }, +// check_group: '948d3b6b-19f6-11eb-b237-025000000001', +// status: 'up', +// duration: { +// us: 10524, +// }, +// id: 'check that title is present', +// name: 'check that title is present', +// }, +// synthetics: { +// package_version: '0.0.1', +// index: 6, +// payload: { +// status: 200, +// type: 'Stylesheet', +// url: 'https://unpkg.com/todomvc-app-css@2.0.4/index.css', +// method: 'GET', +// start: 13902.75266, +// is_navigation_request: false, +// end: 13902.943835, +// response: { +// remote_i_p_address: '104.16.125.175', +// response_time: 1.603983300511892e12, +// url: 'https://unpkg.com/todomvc-app-css@2.0.4/index.css', +// mime_type: 'text/css', +// protocol: 'h2', +// security_state: 'secure', +// encoded_data_length: 414, +// remote_port: 443, +// status_text: '', +// timing: { +// proxy_start: -1, +// worker_ready: -1, +// worker_fetch_start: -1, +// receive_headers_end: 189.169, +// worker_respond_with_settled: -1, +// connect_end: 160.311, +// worker_start: -1, +// send_start: 161.275, +// dns_start: 0.528, +// send_end: 161.924, +// ssl_end: 160.267, +// proxy_end: -1, +// ssl_start: 29.726, +// request_time: 13902.753988, +// dns_end: 5.212, +// push_end: 0, +// push_start: 0, +// connect_start: 5.212, +// }, +// connection_reused: false, +// from_service_worker: false, +// security_details: { +// san_list: ['unpkg.com', '*.unpkg.com', 'sni.cloudflaressl.com'], +// valid_from: 1596326400, +// cipher: 'AES_128_GCM', +// protocol: 'TLS 1.3', +// issuer: 'Cloudflare Inc ECC CA-3', +// valid_to: 1627905600, +// certificate_id: 0, +// key_exchange_group: 'X25519', +// certificate_transparency_compliance: 'unknown', +// key_exchange: '', +// subject_name: 'sni.cloudflaressl.com', +// signed_certificate_timestamp_list: [], +// }, +// connection_id: 17, +// status: 200, +// from_disk_cache: false, +// from_prefetch_cache: false, +// headers: { +// date: 'Thu, 29 Oct 2020 14:55:00 GMT', +// x_cloud_trace_context: '76a4f7b8be185f2ac9aa839de3d6f893', +// cache_control: 'public, max-age=31536000', +// expect_ct: +// 'max-age=604800, report-uri="https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct"', +// content_type: 'text/css; charset=utf-8', +// age: '627638', +// x_content_type_options: 'nosniff', +// last_modified: 'Sat, 09 Jan 2016 00:57:37 GMT', +// access_control_allow_origin: '*', +// cf_request_id: '061673ef6a0000e5a75a309000000001', +// vary: 'Accept-Encoding', +// strict_transport_security: 'max-age=31536000; includeSubDomains; preload', +// cf_ray: '5e9dbc2bdda1e5a7-MAN', +// content_encoding: 'br', +// etag: 'W/"1921-kYwbQVnRAA2V/L9Gr4SCtUE5LHQ"', +// server: 'cloudflare', +// cf_cache_status: 'HIT', +// }, +// }, +// request: { +// headers: { +// referer: '', +// user_agent: +// 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4287.0 Safari/537.36', +// }, +// mixed_content_type: 'none', +// initial_priority: 'VeryHigh', +// referrer_policy: 'no-referrer-when-downgrade', +// url: 'https://unpkg.com/todomvc-app-css@2.0.4/index.css', +// method: 'GET', +// }, +// }, +// journey: { +// id: 'check that title is present', +// name: 'check that title is present', +// }, +// type: 'journey/network_info', +// }, +// event: { +// dataset: 'uptime', +// }, +// ecs: { +// version: '1.6.0', +// }, +// agent: { +// version: '7.10.0', +// hostname: 'docker-desktop', +// ephemeral_id: '34179df8-f97c-46a2-9e73-33976d4ac58d', +// id: '5a03ad5f-cc18-43e8-8f82-6b08b9ceb36a', +// name: 'docker-desktop', +// type: 'heartbeat', +// }, +// }, +// { +// '@timestamp': '2020-10-29T14:55:01.055Z', +// agent: { +// ephemeral_id: '34179df8-f97c-46a2-9e73-33976d4ac58d', +// id: '5a03ad5f-cc18-43e8-8f82-6b08b9ceb36a', +// name: 'docker-desktop', +// type: 'heartbeat', +// version: '7.10.0', +// hostname: 'docker-desktop', +// }, +// synthetics: { +// index: 8, +// payload: { +// method: 'GET', +// type: 'Script', +// response: { +// url: 'file:///opt/examples/todos/app/vue.min.js', +// protocol: 'file', +// connection_id: 0, +// headers: {}, +// mime_type: 'text/javascript', +// from_service_worker: false, +// status_text: '', +// connection_reused: false, +// encoded_data_length: -1, +// from_disk_cache: false, +// security_state: 'secure', +// from_prefetch_cache: false, +// status: 0, +// }, +// is_navigation_request: false, +// request: { +// mixed_content_type: 'none', +// initial_priority: 'High', +// referrer_policy: 'no-referrer-when-downgrade', +// url: 'file:///opt/examples/todos/app/vue.min.js', +// method: 'GET', +// headers: { +// referer: '', +// user_agent: +// 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/88.0.4287.0 Safari/537.36', +// }, +// }, +// end: 13902.772783, +// status: 0, +// start: 13902.760644, +// url: 'file:///opt/examples/todos/app/vue.min.js', +// }, +// journey: { +// name: 'check that title is present', +// id: 'check that title is present', +// }, +// type: 'journey/network_info', +// package_version: '0.0.1', +// }, +// monitor: { +// status: 'up', +// duration: { +// us: 82, +// }, +// name: 'check that title is present', +// type: 'browser', +// timespan: { +// gte: '2020-10-29T14:55:01.055Z', +// lt: '2020-10-29T14:56:01.055Z', +// }, +// id: 'check that title is present', +// check_group: '948d3b6b-19f6-11eb-b237-025000000001', +// }, +// event: { +// dataset: 'uptime', +// }, +// ecs: { +// version: '1.6.0', +// }, +// }, +// ]; + +// const toMillis = (seconds: number) => seconds * 1000; + +// describe('getTimings', () => { +// it('Calculates timings for network events correctly', () => { +// // NOTE: Uses these timings as the file protocol events don't have timing information +// const eventOneTimings = getTimings( +// TEST_DATA[0].synthetics.payload.response.timing!, +// toMillis(TEST_DATA[0].synthetics.payload.start), +// toMillis(TEST_DATA[0].synthetics.payload.end) +// ); +// expect(eventOneTimings).toEqual({ +// blocked: 162.4549999999106, +// connect: -1, +// dns: -1, +// receive: 0.5629999989271255, +// send: 0.5149999999999864, +// ssl: undefined, +// wait: 28.494, +// }); + +// const eventFourTimings = getTimings( +// TEST_DATA[3].synthetics.payload.response.timing!, +// toMillis(TEST_DATA[3].synthetics.payload.start), +// toMillis(TEST_DATA[3].synthetics.payload.end) +// ); +// expect(eventFourTimings).toEqual({ +// blocked: 1.8559999997466803, +// connect: 25.52200000000002, +// dns: 4.683999999999999, +// receive: 0.6780000009983667, +// send: 0.6490000000000009, +// ssl: 130.541, +// wait: 27.245000000000005, +// }); +// }); +// }); + +// describe('getSeriesAndDomain', () => { +// let seriesAndDomain: any; +// let NetworkItems: any; + +// beforeAll(() => { +// NetworkItems = extractItems(TEST_DATA); +// seriesAndDomain = getSeriesAndDomain(NetworkItems); +// }); + +// it('Correctly calculates the domain', () => { +// expect(seriesAndDomain.domain).toEqual({ max: 218.34699999913573, min: 0 }); +// }); + +// it('Correctly calculates the series', () => { +// expect(seriesAndDomain.series).toEqual([ +// { +// config: { colour: '#f3b3a6', tooltipProps: { colour: '#f3b3a6', value: '3.635ms' } }, +// x: 0, +// y: 3.6349999997764826, +// y0: 0, +// }, +// { +// config: { +// colour: '#b9a888', +// tooltipProps: { colour: '#b9a888', value: 'Queued / Blocked: 1.856ms' }, +// }, +// x: 1, +// y: 27.889999999731778, +// y0: 26.0339999999851, +// }, +// { +// config: { colour: '#54b399', tooltipProps: { colour: '#54b399', value: 'DNS: 4.684ms' } }, +// x: 1, +// y: 32.573999999731775, +// y0: 27.889999999731778, +// }, +// { +// config: { +// colour: '#da8b45', +// tooltipProps: { colour: '#da8b45', value: 'Connecting: 25.522ms' }, +// }, +// x: 1, +// y: 58.095999999731795, +// y0: 32.573999999731775, +// }, +// { +// config: { colour: '#edc5a2', tooltipProps: { colour: '#edc5a2', value: 'SSL: 130.541ms' } }, +// x: 1, +// y: 188.63699999973178, +// y0: 58.095999999731795, +// }, +// { +// config: { +// colour: '#d36086', +// tooltipProps: { colour: '#d36086', value: 'Sending request: 0.649ms' }, +// }, +// x: 1, +// y: 189.28599999973179, +// y0: 188.63699999973178, +// }, +// { +// config: { +// colour: '#b0c9e0', +// tooltipProps: { colour: '#b0c9e0', value: 'Waiting (TTFB): 27.245ms' }, +// }, +// x: 1, +// y: 216.5309999997318, +// y0: 189.28599999973179, +// }, +// { +// config: { +// colour: '#ca8eae', +// tooltipProps: { colour: '#ca8eae', value: 'Content downloading: 0.678ms' }, +// }, +// x: 1, +// y: 217.20900000073016, +// y0: 216.5309999997318, +// }, +// { +// config: { +// colour: '#b9a888', +// tooltipProps: { colour: '#b9a888', value: 'Queued / Blocked: 162.455ms' }, +// }, +// x: 2, +// y: 188.77500000020862, +// y0: 26.320000000298023, +// }, +// { +// config: { +// colour: '#d36086', +// tooltipProps: { colour: '#d36086', value: 'Sending request: 0.515ms' }, +// }, +// x: 2, +// y: 189.2900000002086, +// y0: 188.77500000020862, +// }, +// { +// config: { +// colour: '#b0c9e0', +// tooltipProps: { colour: '#b0c9e0', value: 'Waiting (TTFB): 28.494ms' }, +// }, +// x: 2, +// y: 217.7840000002086, +// y0: 189.2900000002086, +// }, +// { +// config: { +// colour: '#9170b8', +// tooltipProps: { colour: '#9170b8', value: 'Content downloading: 0.563ms' }, +// }, +// x: 2, +// y: 218.34699999913573, +// y0: 217.7840000002086, +// }, +// { +// config: { colour: '#9170b8', tooltipProps: { colour: '#9170b8', value: '12.139ms' } }, +// x: 3, +// y: 46.15699999965727, +// y0: 34.01799999922514, +// }, +// { +// config: { colour: '#9170b8', tooltipProps: { colour: '#9170b8', value: '8.453ms' } }, +// x: 4, +// y: 43.506999999284744, +// y0: 35.053999999538064, +// }, +// ]); +// }); +// }); + +describe('Palettes', () => { + it('A colour palette comprising timing and mime type colours is correctly generated', () => { + expect(colourPalette).toEqual({ + blocked: '#b9a888', + connect: '#da8b45', + dns: '#54b399', + font: '#aa6556', + html: '#f3b3a6', + media: '#d6bf57', + other: '#e7664c', + receive: '#54b399', + script: '#9170b8', + send: '#d36086', + ssl: '#edc5a2', + stylesheet: '#ca8eae', + wait: '#b0c9e0', + }); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.ts new file mode 100644 index 00000000000000..9c66ea638c942c --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/data_formatting.ts @@ -0,0 +1,336 @@ +/* + * 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 { euiPaletteColorBlind } from '@elastic/eui'; + +import { + PayloadTimings, + CalculatedTimings, + NetworkItems, + FriendlyTimingLabels, + FriendlyMimetypeLabels, + MimeType, + MimeTypesMap, + Timings, + TIMING_ORDER, + SidebarItems, + LegendItems, +} from './types'; +import { WaterfallData } from '../../../waterfall'; + +const microToMillis = (micro: number): number => (micro === -1 ? -1 : micro * 1000); + +// The timing calculations here are based off several sources: +// https://github.com/ChromeDevTools/devtools-frontend/blob/2fe91adefb2921b4deb2b4b125370ef9ccdb8d1b/front_end/sdk/HARLog.js#L307 +// and +// https://chromium.googlesource.com/chromium/blink.git/+/master/Source/devtools/front_end/sdk/HAREntry.js#131 +// and +// https://github.com/cyrus-and/chrome-har-capturer/blob/master/lib/har.js#L195 +// Order of events: request_start = 0, [proxy], [dns], [connect [ssl]], [send], receive_headers_end + +export const getTimings = ( + timings: PayloadTimings, + requestSentTime: number, + responseReceivedTime: number +): CalculatedTimings => { + if (!timings) return { blocked: -1, dns: -1, connect: -1, send: 0, wait: 0, receive: 0, ssl: -1 }; + + const getLeastNonNegative = (values: number[]) => + values.reduce<number>((best, value) => (value >= 0 && value < best ? value : best), Infinity); + const getOptionalTiming = (_timings: PayloadTimings, key: keyof PayloadTimings) => + _timings[key] >= 0 ? _timings[key] : -1; + + // NOTE: Request sent and request start can differ due to queue times + const requestStartTime = microToMillis(timings.request_time); + + // Queued + const queuedTime = requestSentTime < requestStartTime ? requestStartTime - requestSentTime : -1; + + // Blocked + // "blocked" represents both queued time + blocked/stalled time + proxy time (ie: anything before the request was actually started). + let blocked = queuedTime; + + const blockedStart = getLeastNonNegative([ + timings.dns_start, + timings.connect_start, + timings.send_start, + ]); + + if (blockedStart !== Infinity) { + blocked += blockedStart; + } + + // Proxy + // Proxy is part of blocked, but it can be quirky in that blocked can be -1 even though there are proxy timings. This can happen with + // protocols like Quic. + if (timings.proxy_end !== -1) { + const blockedProxy = timings.proxy_end - timings.proxy_start; + + if (blockedProxy && blockedProxy > blocked) { + blocked = blockedProxy; + } + } + + // DNS + const dnsStart = timings.dns_end >= 0 ? blockedStart : 0; + const dnsEnd = getOptionalTiming(timings, 'dns_end'); + const dns = dnsEnd - dnsStart; + + // SSL + const sslStart = getOptionalTiming(timings, 'ssl_start'); + const sslEnd = getOptionalTiming(timings, 'ssl_end'); + let ssl; + + if (sslStart >= 0 && sslEnd >= 0) { + ssl = timings.ssl_end - timings.ssl_start; + } + + // Connect + let connect = -1; + if (timings.connect_start >= 0) { + connect = timings.send_start - timings.connect_start; + } + + // Send + const send = timings.send_end - timings.send_start; + + // Wait + const wait = timings.receive_headers_end - timings.send_end; + + // Receive + const receive = responseReceivedTime - (requestStartTime + timings.receive_headers_end); + + // SSL connection is a part of the overall connection time + if (connect && ssl) { + connect = connect - ssl; + } + + return { blocked, dns, connect, send, wait, receive, ssl }; +}; + +// TODO: Switch to real API data, and type data as the payload response (if server response isn't preformatted) +export const extractItems = (data: any): NetworkItems => { + const items = data + .map((entry: any) => { + const requestSentTime = microToMillis(entry.synthetics.payload.start); + const responseReceivedTime = microToMillis(entry.synthetics.payload.end); + const requestStartTime = + entry.synthetics.payload.response && entry.synthetics.payload.response.timing + ? microToMillis(entry.synthetics.payload.response.timing.request_time) + : null; + + return { + timestamp: entry['@timestamp'], + method: entry.synthetics.payload.method, + url: entry.synthetics.payload.url, + status: entry.synthetics.payload.status, + mimeType: entry.synthetics.payload?.response?.mime_type, + requestSentTime, + responseReceivedTime, + earliestRequestTime: requestStartTime + ? Math.min(requestSentTime, requestStartTime) + : requestSentTime, + timings: + entry.synthetics.payload.response && entry.synthetics.payload.response.timing + ? getTimings( + entry.synthetics.payload.response.timing, + requestSentTime, + responseReceivedTime + ) + : null, + }; + }) + .sort((a: any, b: any) => { + return a.earliestRequestTime - b.earliestRequestTime; + }); + + return items; +}; + +const formatValueForDisplay = (value: number, points: number = 3) => { + return Number(value).toFixed(points); +}; + +const getColourForMimeType = (mimeType?: string) => { + const key = mimeType && MimeTypesMap[mimeType] ? MimeTypesMap[mimeType] : MimeType.Other; + return colourPalette[key]; +}; + +export const getSeriesAndDomain = (items: NetworkItems) => { + // The earliest point in time a request is sent or started. This will become our notion of "0". + const zeroOffset = items.reduce<number>((acc, item) => { + const { earliestRequestTime } = item; + return earliestRequestTime < acc ? earliestRequestTime : acc; + }, Infinity); + + const series = items.reduce<WaterfallData>((acc, item, index) => { + const { earliestRequestTime } = item; + + // Entries without timings should be handled differently: + // https://github.com/ChromeDevTools/devtools-frontend/blob/ed2a064ac194bfae4e25c4748a9fa3513b3e9f7d/front_end/network/RequestTimingView.js#L140 + // If there are no concrete timings just plot one block via start and end + if (!item.timings || item.timings === null) { + const duration = item.responseReceivedTime - item.earliestRequestTime; + const colour = getColourForMimeType(item.mimeType); + return [ + ...acc, + { + x: index, + y0: item.earliestRequestTime - zeroOffset, + y: item.responseReceivedTime - zeroOffset, + config: { + colour, + tooltipProps: { + value: `${formatValueForDisplay(duration)}ms`, + colour, + }, + }, + }, + ]; + } + + let currentOffset = earliestRequestTime - zeroOffset; + + TIMING_ORDER.forEach((timing) => { + const value = item.timings![timing]; + const colour = + timing === Timings.Receive ? getColourForMimeType(item.mimeType) : colourPalette[timing]; + if (value && value >= 0) { + const y = currentOffset + value; + + acc.push({ + x: index, + y0: currentOffset, + y, + config: { + colour, + tooltipProps: { + value: `${FriendlyTimingLabels[timing]}: ${formatValueForDisplay( + y - currentOffset + )}ms`, + colour, + }, + }, + }); + currentOffset = y; + } + }); + return acc; + }, []); + + const yValues = series.map((serie) => serie.y); + const domain = { min: 0, max: Math.max(...yValues) }; + return { series, domain }; +}; + +export const getSidebarItems = (items: NetworkItems): SidebarItems => { + return items.map((item) => { + const { url, status, method } = item; + return { url, status, method }; + }); +}; + +export const getLegendItems = (): LegendItems => { + let timingItems: LegendItems = []; + Object.values(Timings).forEach((timing) => { + // The "receive" timing is mapped to a mime type colour, so we don't need to show this in the legend + if (timing === Timings.Receive) { + return; + } + timingItems = [ + ...timingItems, + { name: FriendlyTimingLabels[timing], colour: TIMING_PALETTE[timing] }, + ]; + }); + + let mimeTypeItems: LegendItems = []; + Object.values(MimeType).forEach((mimeType) => { + mimeTypeItems = [ + ...mimeTypeItems, + { name: FriendlyMimetypeLabels[mimeType], colour: MIME_TYPE_PALETTE[mimeType] }, + ]; + }); + return [...timingItems, ...mimeTypeItems]; +}; + +// Timing colour palette +type TimingColourPalette = { + [K in Timings]: string; +}; + +const SAFE_PALETTE = euiPaletteColorBlind({ rotations: 2 }); + +const buildTimingPalette = (): TimingColourPalette => { + const palette = Object.values(Timings).reduce<Partial<TimingColourPalette>>((acc, value) => { + switch (value) { + case Timings.Blocked: + acc[value] = SAFE_PALETTE[6]; + break; + case Timings.Dns: + acc[value] = SAFE_PALETTE[0]; + break; + case Timings.Connect: + acc[value] = SAFE_PALETTE[7]; + break; + case Timings.Ssl: + acc[value] = SAFE_PALETTE[17]; + break; + case Timings.Send: + acc[value] = SAFE_PALETTE[2]; + break; + case Timings.Wait: + acc[value] = SAFE_PALETTE[11]; + break; + case Timings.Receive: + acc[value] = SAFE_PALETTE[0]; + break; + } + return acc; + }, {}); + + return palette as TimingColourPalette; +}; + +const TIMING_PALETTE = buildTimingPalette(); + +// MimeType colour palette +type MimeTypeColourPalette = { + [K in MimeType]: string; +}; + +const buildMimeTypePalette = (): MimeTypeColourPalette => { + const palette = Object.values(MimeType).reduce<Partial<MimeTypeColourPalette>>((acc, value) => { + switch (value) { + case MimeType.Html: + acc[value] = SAFE_PALETTE[19]; + break; + case MimeType.Script: + acc[value] = SAFE_PALETTE[3]; + break; + case MimeType.Stylesheet: + acc[value] = SAFE_PALETTE[4]; + break; + case MimeType.Media: + acc[value] = SAFE_PALETTE[5]; + break; + case MimeType.Font: + acc[value] = SAFE_PALETTE[8]; + break; + case MimeType.Other: + acc[value] = SAFE_PALETTE[9]; + break; + } + return acc; + }, {}); + + return palette as MimeTypeColourPalette; +}; + +const MIME_TYPE_PALETTE = buildMimeTypePalette(); + +type ColourPalette = TimingColourPalette & MimeTypeColourPalette; + +export const colourPalette: ColourPalette = { ...TIMING_PALETTE, ...MIME_TYPE_PALETTE }; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/types.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/types.ts new file mode 100644 index 00000000000000..1dd58b4f86db3e --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/types.ts @@ -0,0 +1,197 @@ +/* + * 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 { i18n } from '@kbn/i18n'; + +export enum Timings { + Blocked = 'blocked', + Dns = 'dns', + Connect = 'connect', + Ssl = 'ssl', + Send = 'send', + Wait = 'wait', + Receive = 'receive', +} + +export const FriendlyTimingLabels = { + [Timings.Blocked]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.timings.blocked', + { + defaultMessage: 'Queued / Blocked', + } + ), + [Timings.Dns]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.timings.dns', { + defaultMessage: 'DNS', + }), + [Timings.Connect]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.timings.connect', + { + defaultMessage: 'Connecting', + } + ), + [Timings.Ssl]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.timings.ssl', { + defaultMessage: 'SSL', + }), + [Timings.Send]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.timings.send', { + defaultMessage: 'Sending request', + }), + [Timings.Wait]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.timings.wait', { + defaultMessage: 'Waiting (TTFB)', + }), + [Timings.Receive]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.timings.receive', + { + defaultMessage: 'Content downloading', + } + ), +}; + +export const TIMING_ORDER = [ + Timings.Blocked, + Timings.Dns, + Timings.Connect, + Timings.Ssl, + Timings.Send, + Timings.Wait, + Timings.Receive, +] as const; + +export type CalculatedTimings = { + [K in Timings]?: number; +}; + +export enum MimeType { + Html = 'html', + Script = 'script', + Stylesheet = 'stylesheet', + Media = 'media', + Font = 'font', + Other = 'other', +} + +export const FriendlyMimetypeLabels = { + [MimeType.Html]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.html', { + defaultMessage: 'HTML', + }), + [MimeType.Script]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.script', + { + defaultMessage: 'JS', + } + ), + [MimeType.Stylesheet]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.stylesheet', + { + defaultMessage: 'CSS', + } + ), + [MimeType.Media]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.media', + { + defaultMessage: 'Media', + } + ), + [MimeType.Font]: i18n.translate('xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.font', { + defaultMessage: 'Font', + }), + [MimeType.Other]: i18n.translate( + 'xpack.uptime.synthetics.waterfallChart.labels.mimeTypes.other', + { + defaultMessage: 'Other', + } + ), +}; + +// NOTE: This list tries to cover the standard spec compliant mime types, +// and a few popular non-standard ones, but it isn't exhaustive. +export const MimeTypesMap: Record<string, MimeType> = { + 'text/html': MimeType.Html, + 'application/javascript': MimeType.Script, + 'text/javascript': MimeType.Script, + 'text/css': MimeType.Stylesheet, + // Images + 'image/apng': MimeType.Media, + 'image/bmp': MimeType.Media, + 'image/gif': MimeType.Media, + 'image/x-icon': MimeType.Media, + 'image/jpeg': MimeType.Media, + 'image/png': MimeType.Media, + 'image/svg+xml': MimeType.Media, + 'image/tiff': MimeType.Media, + 'image/webp': MimeType.Media, + // Common audio / video formats + 'audio/wave': MimeType.Media, + 'audio/wav': MimeType.Media, + 'audio/x-wav': MimeType.Media, + 'audio/x-pn-wav': MimeType.Media, + 'audio/webm': MimeType.Media, + 'video/webm': MimeType.Media, + 'audio/ogg': MimeType.Media, + 'video/ogg': MimeType.Media, + 'application/ogg': MimeType.Media, + // Fonts + 'font/otf': MimeType.Font, + 'font/ttf': MimeType.Font, + 'font/woff': MimeType.Font, + 'font/woff2': MimeType.Font, + 'application/x-font-opentype': MimeType.Font, + 'application/font-woff': MimeType.Font, + 'application/font-woff2': MimeType.Font, + 'application/vnd.ms-fontobject': MimeType.Font, + 'application/font-sfnt': MimeType.Font, +}; + +export interface NetworkItem { + timestamp: string; + method: string; + url: string; + status: number; + mimeType?: string; + // NOTE: This is the time the request was actually issued. timing.request_time might be later if the request was queued. + requestSentTime: number; + responseReceivedTime: number; + // NOTE: Denotes the earlier figure out of request sent time and request start time (part of timings). This can vary based on queue times, and + // also whether an entry actually has timings available. + // Ref: https://github.com/ChromeDevTools/devtools-frontend/blob/ed2a064ac194bfae4e25c4748a9fa3513b3e9f7d/front_end/network/RequestTimingView.js#L154 + earliestRequestTime: number; + timings: CalculatedTimings | null; +} +export type NetworkItems = NetworkItem[]; + +// NOTE: A number will always be present if the property exists, but that number might be -1, which represents no value. +export interface PayloadTimings { + dns_start: number; + push_end: number; + worker_fetch_start: number; + worker_respond_with_settled: number; + proxy_end: number; + worker_start: number; + worker_ready: number; + send_end: number; + connect_end: number; + connect_start: number; + send_start: number; + proxy_start: number; + push_start: number; + ssl_end: number; + receive_headers_end: number; + ssl_start: number; + request_time: number; + dns_end: number; +} + +export interface ExtraSeriesConfig { + colour: string; +} + +export type SidebarItem = Pick<NetworkItem, 'url' | 'status' | 'method'>; +export type SidebarItems = SidebarItem[]; + +export interface LegendItem { + name: string; + colour: string; +} +export type LegendItems = LegendItem[]; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/waterfall_chart_wrapper.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/waterfall_chart_wrapper.tsx new file mode 100644 index 00000000000000..434b44a94b79fe --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/consumers/synthetics/waterfall_chart_wrapper.tsx @@ -0,0 +1,84 @@ +/* + * 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, { useMemo, useState } from 'react'; +import { EuiHealth, EuiFlexGroup, EuiFlexItem, EuiBadge } from '@elastic/eui'; +import { getSeriesAndDomain, getSidebarItems, getLegendItems } from './data_formatting'; +import { SidebarItem, LegendItem, NetworkItems } from './types'; +import { + WaterfallProvider, + WaterfallChart, + MiddleTruncatedText, + RenderItem, +} from '../../../waterfall'; + +const renderSidebarItem: RenderItem<SidebarItem> = (item, index) => { + const { status } = item; + + const isErrorStatusCode = (statusCode: number) => { + const is400 = statusCode >= 400 && statusCode <= 499; + const is500 = statusCode >= 500 && statusCode <= 599; + const isSpecific300 = statusCode === 301 || statusCode === 307 || statusCode === 308; + return is400 || is500 || isSpecific300; + }; + + return ( + <> + {!isErrorStatusCode(status) ? ( + <MiddleTruncatedText text={`${index + 1}. ${item.url}`} /> + ) : ( + <EuiFlexGroup justifyContent="spaceBetween"> + <EuiFlexItem> + <MiddleTruncatedText text={`${index + 1}. ${item.url}`} /> + </EuiFlexItem> + <EuiFlexItem component="span" grow={false}> + <EuiBadge color="danger">{status}</EuiBadge> + </EuiFlexItem> + </EuiFlexGroup> + )} + </> + ); +}; + +const renderLegendItem: RenderItem<LegendItem> = (item) => { + return <EuiHealth color={item.colour}>{item.name}</EuiHealth>; +}; + +export const WaterfallChartWrapper = () => { + // TODO: Will be sourced via an API + const [networkData] = useState<NetworkItems>([]); + + const { series, domain } = useMemo(() => { + return getSeriesAndDomain(networkData); + }, [networkData]); + + const sidebarItems = useMemo(() => { + return getSidebarItems(networkData); + }, [networkData]); + + const legendItems = getLegendItems(); + + return ( + <WaterfallProvider + data={series} + sidebarItems={sidebarItems} + legendItems={legendItems} + renderTooltipItem={(tooltipProps) => { + return <EuiHealth color={String(tooltipProps.colour)}>{tooltipProps.value}</EuiHealth>; + }} + > + <WaterfallChart + tickFormat={(d: number) => `${Number(d).toFixed(0)} ms`} + domain={domain} + barStyleAccessor={(datum) => { + return datum.datum.config.colour; + }} + renderSidebarItem={renderSidebarItem} + renderLegendItem={renderLegendItem} + /> + </WaterfallProvider> + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx new file mode 100644 index 00000000000000..ccee9d7994c80a --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/context/waterfall_chart.tsx @@ -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 React, { createContext, useContext, Context } from 'react'; +import { WaterfallData, WaterfallDataEntry } from '../types'; + +export interface IWaterfallContext { + data: WaterfallData; + sidebarItems?: unknown[]; + legendItems?: unknown[]; + renderTooltipItem: ( + item: WaterfallDataEntry['config']['tooltipProps'], + index?: number + ) => JSX.Element; +} + +export const WaterfallContext = createContext<Partial<IWaterfallContext>>({}); + +interface ProviderProps { + data: IWaterfallContext['data']; + sidebarItems?: IWaterfallContext['sidebarItems']; + legendItems?: IWaterfallContext['legendItems']; + renderTooltipItem: IWaterfallContext['renderTooltipItem']; +} + +export const WaterfallProvider: React.FC<ProviderProps> = ({ + children, + data, + sidebarItems, + legendItems, + renderTooltipItem, +}) => { + return ( + <WaterfallContext.Provider value={{ data, sidebarItems, legendItems, renderTooltipItem }}> + {children} + </WaterfallContext.Provider> + ); +}; + +export const useWaterfallContext = () => + useContext((WaterfallContext as unknown) as Context<IWaterfallContext>); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/index.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/index.tsx new file mode 100644 index 00000000000000..c3ea39a9ace6e9 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/index.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. + */ + +export { WaterfallChart, RenderItem, WaterfallChartProps } from './components/waterfall_chart'; +export { WaterfallProvider, useWaterfallContext } from './context/waterfall_chart'; +export { MiddleTruncatedText } from './components/middle_truncated_text'; +export { WaterfallData, WaterfallDataEntry } from './types'; diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/types.ts b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/types.ts new file mode 100644 index 00000000000000..d6901fb482599c --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/waterfall/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. + */ + +interface PlotProperties { + x: number; + y: number; + y0: number; +} + +export interface WaterfallDataSeriesConfigProperties { + tooltipProps: Record<string, string | number>; +} + +export type WaterfallDataEntry = PlotProperties & { + config: WaterfallDataSeriesConfigProperties & Record<string, unknown>; +}; + +export type WaterfallData = WaterfallDataEntry[]; diff --git a/x-pack/test/api_integration/apis/lens/existing_fields.ts b/x-pack/test/api_integration/apis/lens/existing_fields.ts index 08806df380f38a..6eddaac50fda5b 100644 --- a/x-pack/test/api_integration/apis/lens/existing_fields.ts +++ b/x-pack/test/api_integration/apis/lens/existing_fields.ts @@ -102,26 +102,46 @@ const metricBeatData = [ '_id', '_index', 'agent.ephemeral_id', + 'agent.ephemeral_id.keyword', 'agent.hostname', + 'agent.hostname.keyword', 'agent.id', + 'agent.id.keyword', 'agent.type', + 'agent.type.keyword', 'agent.version', + 'agent.version.keyword', 'ecs.version', + 'ecs.version.keyword', 'event.dataset', + 'event.dataset.keyword', 'event.duration', 'event.module', + 'event.module.keyword', 'host.architecture', + 'host.architecture.keyword', 'host.hostname', + 'host.hostname.keyword', 'host.id', + 'host.id.keyword', 'host.name', + 'host.name.keyword', 'host.os.build', + 'host.os.build.keyword', 'host.os.family', + 'host.os.family.keyword', 'host.os.kernel', + 'host.os.kernel.keyword', 'host.os.name', + 'host.os.name.keyword', 'host.os.platform', + 'host.os.platform.keyword', 'host.os.version', + 'host.os.version.keyword', 'metricset.name', + 'metricset.name.keyword', 'service.type', + 'service.type.keyword', 'system.cpu.cores', 'system.cpu.idle.pct', 'system.cpu.iowait.pct', diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_local.js b/x-pack/test/api_integration/apis/telemetry/telemetry_local.js index 6a5a7db4d2560e..e7014ff92d9b93 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_local.js +++ b/x-pack/test/api_integration/apis/telemetry/telemetry_local.js @@ -55,6 +55,9 @@ export default function ({ getService }) { const stats = body[0]; expect(stats.collection).to.be('local'); + expect(stats.collectionSource).to.be('local_xpack'); + + // License should exist in X-Pack expect(stats.license.issuer).to.be.a('string'); expect(stats.license.status).to.be('active'); diff --git a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts index 3a526fac2f08a1..dd72016476526d 100644 --- a/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts +++ b/x-pack/test/fleet_api_integration/apis/enrollment_api_keys/crud.ts @@ -92,12 +92,12 @@ export default function (providerContext: FtrProviderContext) { .expect(400); }); - it('should not allow to create an enrollment api key for a non existing agent policy', async () => { + it('should return a 400 if the fleet admin user is modifed outside of Fleet', async () => { await supertest .post(`/api/fleet/enrollment-api-keys`) .set('kbn-xsrf', 'xxx') .send({ - policy_id: 'idonotexistspolicy', + raoul: 'raoul', }) .expect(400); }); @@ -161,6 +161,33 @@ export default function (providerContext: FtrProviderContext) { }, }); }); + + describe('It should handle error when the Fleet user is invalid', () => { + before(async () => {}); + after(async () => { + await getService('supertest') + .post(`/api/fleet/agents/setup`) + .set('kbn-xsrf', 'xxx') + .send({ forceRecreate: true }); + }); + + it('should not allow to create an enrollment api key if the Fleet admin user is invalid', async () => { + await es.security.changePassword({ + username: 'fleet_enroll', + body: { + password: Buffer.from((Math.random() * 10000000).toString()).toString('base64'), + }, + }); + const res = await supertest + .post(`/api/fleet/enrollment-api-keys`) + .set('kbn-xsrf', 'xxx') + .send({ + policy_id: 'policy1', + }) + .expect(400); + expect(res.body.message).match(/Fleet Admin user is invalid/); + }); + }); }); }); } diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/data.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/data.json new file mode 100644 index 00000000000000..9b6804beabfe56 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/data.json @@ -0,0 +1,530 @@ +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:5460518c-10c7-4c25-b2ec-3f63eafb7d47", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:26:37.093Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "MngOEjmt4OWFSCvya8AWgDF9p0nPqiCZLpNrqntWdjcGl+vPcbVs+un3ilKC3GQKtKP6KLtMziLR/60teHpAJ0Ls1f+mbCP1PjjAfFL1ZBnGHsvkR099iRJ9q4rCxzmZtifGZQ/s2+t99DRUe8GkJhIj3VR1uN/EKPXmXDWZo0f+bTUDT7vGZVY=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:58.866Z", + "default_api_key_id": "ieriwHQBXUUrssdI83FW", + "last_checkin": "2020-09-24T16:26:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "E-riwHQBXUUrssdIvHEw", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::4de:9ad6:320f:79f5/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.116/22", + "fdbb:cb5c:fb4:68:1cfe:7be7:f700:8810/64", + "fdbb:cb5c:fb4:68:257d:7303:389d:f335/64", + "fdbb:cb5c:fb4:68:7470:3bec:14b5:2caf/64", + "fdbb:cb5c:fb4:68:9c5f:eab7:8345:f711/64", + "fdbb:cb5c:fb4:68:dc96:8bac:67e0:99dd/64", + "fdbb:cb5c:fb4:68:60c6:73b6:1540:602/64", + "fdbb:cb5c:fb4:68:144:6a1b:1aae:a57d/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "mac": [ + "00:50:56:b1:7e:49" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "5460518c-10c7-4c25-b2ec-3f63eafb7d47" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:296c368b-35d3-4241-905f-75a24f52ec13", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:26:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "PEF8+bfiv21Yn5yj8I2/vIaQWMrUQK4PeBBwXsrvmVTsbuFejXM0IQtYVKXShBJAoY9CUEKPCRR4rIIdXWZc51i1ZneLoFw+yBw8BsSwhHfbQXvAVQowH7UqKHp0CiA5J9uGSgmw3Q55a4dv4IHih+sBKji7Qf2durs5gCWUJExrRCpMiU3OHSg=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:56.620Z", + "default_api_key_id": "xOrjwHQBXUUrssdIDnHH", + "last_checkin": "2020-09-24T16:26:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "S67iwHQBEiA0_Dvks-Cm", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.7.158/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:371f/64", + "fe80::250:56ff:feb1:371f/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "mac": [ + "00:50:56:b1:37:1f" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-38-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "296c368b-35d3-4241-905f-75a24f52ec13" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:26:36.352Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "sdv6996k+S1BiZ/12K3Wi6rb8Lsoh/+shwzKNqujwcmhdbeQ92ygLoO+tudJaJOnL129WT+hhanEf6OgH5PpQBezc03hl9v2AI+BlU+hssfce5OfgFRGLYg8S+ryNHwFhK6EJeN1aivoie+YholNpcpt2l/t+lQpevMI4QYGaMfUzofuivs5JM4=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:54.037Z", + "default_api_key_id": "lq7iwHQBEiA0_Dvk8-Fb", + "last_checkin": "2020-09-24T16:26:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "geriwHQBXUUrssdIqXB2", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-195.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::107d:2365:5a7c:8da/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.195/22", + "fdbb:cb5c:fb4:68:d4ef:63a5:8ffc:f933/64", + "fdbb:cb5c:fb4:68:b082:8681:cf85:27d0/64", + "fdbb:cb5c:fb4:68:7c3d:13f3:5339:be7b/64", + "fdbb:cb5c:fb4:68:19a4:2a63:cc88:6e59/64", + "fdbb:cb5c:fb4:68:494a:3867:57b8:4027/64", + "fdbb:cb5c:fb4:68:1c88:41e:6ce1:4be7/64", + "fdbb:cb5c:fb4:68:114:b84:8faf:b12b/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-195.eng.endgames.local", + "mac": [ + "00:50:56:b1:e4:06" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:ac0ab6c1-2317-478c-93d9-c514d845302d", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:26:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "UnSz8pAKTP/0DENATzn13Yo0jcdbWq70IiBJcDY+DF5M063+El91o+448KVaMHj3rCSrULfJboBf1Ao80UKU5WKz4CYJ3ZVjHm39/f8rXMZSah5lQAkl9Ak2v5wUCFd4KTEwUUEmnUKKSQGC53cBhnvoyPdzfNjt1ml96lZFZbxXt/VyU3u8vhQ=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:30.880Z", + "default_api_key_id": "Va7iwHQBEiA0_DvkcN-4", + "last_checkin": "2020-09-24T16:26:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "Sq7iwHQBEiA0_DvkT98X", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "JCHU-WIN10-1", + "ip": [ + "fdbb:cb5c:fb4:68:6ca6:5ea3:ae36:af51/64", + "fdbb:cb5c:fb4:68:6c9d:def9:bb8a:6695/128", + "fe80::6ca6:5ea3:ae36:af51/64", + "10.0.7.235/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "JCHU-WIN10-1", + "mac": [ + "00:50:56:b1:65:cb" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "ac0ab6c1-2317-478c-93d9-c514d845302d" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:2d187287-658a-4cb6-84d8-d66d1b9a6299", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:26:36.352Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "fpQcy/QWSbafzl6avELe9goTtyojPwQX3id1pe+BBqDarSCB3k5QwWLQP2SXEl2rwJdywUrBz3gMySKi80RYWJFUoWHiipfaE/jXJRqJxZZvhBe8fdSP7YPkdIdLQl/3ktIWqAzjjS1CErqMb5K4HTZIp5FswDQB40SbDkQKPECl9o8pBhLjH/A=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:28.949Z", + "default_api_key_id": "aeriwHQBXUUrssdIdXAX", + "last_checkin": "2020-09-24T16:26:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "Sa7iwHQBEiA0_DvkR99k", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "JCHU-WIN10-2", + "ip": [ + "fdbb:cb5c:fb4:68:dda8:b7a:3e20:9ca0/64", + "fdbb:cb5c:fb4:68:e922:9626:5193:ef68/128", + "fe80::dda8:b7a:3e20:9ca0/64", + "10.0.6.96/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "JCHU-WIN10-2", + "mac": [ + "00:50:56:b1:26:07" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "2d187287-658a-4cb6-84d8-d66d1b9a6299" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:c216aea0-58ba-40a3-b6fe-afa2f5457835", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:26:36.352Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "tSCsI7HPfRVIcw3Yx3xUAl20Hfe9AdEIs/4IBBH9ZO1gxnMMjRkVb/hxhfcdg6dkW+RIc6Pc9Jz7rUvybq8fY0r/pTKGXTFr46dC2+E9jfb7rs/PmYhG2V0/Ei2p+ZQypAIp8mtknSHkX+l74N7niVXKreneLrt99e4ZWIyeuwNwr0HcGjoMEqM=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:59.088Z", + "default_api_key_id": "SK7jwHQBEiA0_DvkNuIq", + "last_checkin": "2020-09-24T16:26:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "OeriwHQBXUUrssdIvXGr", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-6-176.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.6.176/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:3363/64", + "fe80::250:56ff:feb1:3363/64" + ], + "hostname": "mainqa-atlcolo-10-0-6-176.eng.endgames.local", + "mac": [ + "00:50:56:b1:33:63" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-118-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "c216aea0-58ba-40a3-b6fe-afa2f5457835" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:8e652110-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:43.499165-04:00", + "subtype": "RUNNING", + "agent_id": "5460518c-10c7-4c25-b2ec-3f63eafb7d47", + "message": "Application: endpoint-security--7.9.2[5460518c-10c7-4c25-b2ec-3f63eafb7d47]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:43.495361445Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[44,4,0,2,2,4,1,2,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":25.33265565,\"mean\":6.21698140807909}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":58376192,\"mean\":46094231}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0.32258064516129},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0.323624595469256},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0.664451827242525},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":9.55882352941176},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":308,\"system\":3807934}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"b364a499-8e64-4d91-9770-6911c5d6964b\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"ec5403f8-6708-0d58-7aff-b2137b48b816\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-24T16:18:18.145Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:80a6c1f0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:54.930717796-04:00", + "subtype": "RUNNING", + "agent_id": "c216aea0-58ba-40a3-b6fe-afa2f5457835", + "message": "Application: endpoint-security--7.9.2[c216aea0-58ba-40a3-b6fe-afa2f5457835]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:54.929290223Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[57,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":3,\"mean\":3.49666666666667}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":49778688,\"mean\":31986824}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":2.12765957446809},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.72992700729927},\"name\":\"EventsLoopThread\"}],\"uptime\":{\"endpoint\":300,\"system\":3863}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"a15f0431-6835-41c4-a7ee-21a70d41cf5b\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"20ccfdfa-323f-e33e-f2ef-3528edb1afea\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:55.087Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:7bdc8fb0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:45.675453579-04:00", + "subtype": "RUNNING", + "agent_id": "296c368b-35d3-4241-905f-75a24f52ec13", + "message": "Application: endpoint-security--7.9.2[296c368b-35d3-4241-905f-75a24f52ec13]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:45.674010613Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[57,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":2.8,\"mean\":3.17}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":49278976,\"mean\":31884356}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":2.12765957446809},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.72992700729927},\"name\":\"EventsLoopThread\"}],\"uptime\":{\"endpoint\":300,\"system\":5000305}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"6f0cb2fc-3e46-4435-8892-d9f7e71b23fd\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"e9909692-0e35-fd30-e3a3-e2e7253bb5c7\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:47.051Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:81e5aa90-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:40.138333-04:00", + "subtype": "RUNNING", + "agent_id": "b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5", + "message": "Application: endpoint-security--7.9.2[b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:40.134985503Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[55,0,2,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":10.21008368,\"mean\":1.91476589372881}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":71143424,\"mean\":53719456}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":3.08880308880309},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":302,\"system\":1901758}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"49f4e779-287a-4fa8-80e6-247b54c554f1\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"7d59b1a5-afa1-6531-07ea-691602558230\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:57.177Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:82b7eeb0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:13.3157023-04:00", + "subtype": "RUNNING", + "agent_id": "ac0ab6c1-2317-478c-93d9-c514d845302d", + "message": "Application: endpoint-security--7.9.2[ac0ab6c1-2317-478c-93d9-c514d845302d]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:13.13714300Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[53,1,0,1,0,0,2,1,0,3,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":49.0526570938275,\"mean\":4.53577832211642}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":285802496,\"mean\":95647240}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":1.30293159609121},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":3.18021201413428},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":306,\"system\":3625}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"7fdb1b867fd4d2da37870d493e1c67630f59355eab061f91e705f4cc83dd6b9b\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"e21f3ba186d1563b66bb58b7ff9a362c07448e8f4dec00b2f861bf935cb15d77\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"463709447352d429297355247266f641179331171342b3bc3e9c8f6b4b2faed2\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"6474b1bd-96bc-4bde-a770-0e6a7a5bf8c4\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"c85e6c40-d4a1-db21-7458-2565a6b857f3\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-24T16:17:58.555Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:7cbf9cb1-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:15.400204-04:00", + "subtype": "RUNNING", + "agent_id": "2d187287-658a-4cb6-84d8-d66d1b9a6299", + "message": "Application: endpoint-security--7.9.2[2d187287-658a-4cb6-84d8-d66d1b9a6299]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:15.96990100Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[46,2,2,2,4,2,0,0,0,2,0,0,0,0,1,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":48.3070275492921,\"mean\":6.43134047264261}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":228757504,\"mean\":94594836}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":1.30293159609121},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":1.9672131147541},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":2.62295081967213},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0.655737704918033},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":2.11267605633803},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":307,\"system\":3654}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"7fdb1b867fd4d2da37870d493e1c67630f59355eab061f91e705f4cc83dd6b9b\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"e21f3ba186d1563b66bb58b7ff9a362c07448e8f4dec00b2f861bf935cb15d77\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"463709447352d429297355247266f641179331171342b3bc3e9c8f6b4b2faed2\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"21d182a2-5a08-41bb-b601-5d2b4aba4ecd\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"327d0e20-483e-95af-f4e4-7b065606e1aa\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-24T16:17:48.539Z", + "type": "fleet-agent-events" + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/mappings.json new file mode 100644 index 00000000000000..27aea27bebcd7e --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_different_states/mappings.json @@ -0,0 +1,2592 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agents": "034346488514b7058a79140b19ddf631", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "5c4b9a6effceb17ae8a0ab22d0c49767", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_configs": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/data.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/data.json new file mode 100644 index 00000000000000..98488c85878b52 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/data.json @@ -0,0 +1,533 @@ +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:22:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "sdv6996k+S1BiZ/12K3Wi6rb8Lsoh/+shwzKNqujwcmhdbeQ92ygLoO+tudJaJOnL129WT+hhanEf6OgH5PpQBezc03hl9v2AI+BlU+hssfce5OfgFRGLYg8S+ryNHwFhK6EJeN1aivoie+YholNpcpt2l/t+lQpevMI4QYGaMfUzofuivs5JM4=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:54.037Z", + "default_api_key_id": "lq7iwHQBEiA0_Dvk8-Fb", + "last_checkin": "2020-09-24T16:22:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "geriwHQBXUUrssdIqXB2", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-195.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::107d:2365:5a7c:8da/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.195/22", + "fdbb:cb5c:fb4:68:d4ef:63a5:8ffc:f933/64", + "fdbb:cb5c:fb4:68:b082:8681:cf85:27d0/64", + "fdbb:cb5c:fb4:68:7c3d:13f3:5339:be7b/64", + "fdbb:cb5c:fb4:68:19a4:2a63:cc88:6e59/64", + "fdbb:cb5c:fb4:68:494a:3867:57b8:4027/64", + "fdbb:cb5c:fb4:68:1c88:41e:6ce1:4be7/64", + "fdbb:cb5c:fb4:68:114:b84:8faf:b12b/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-195.eng.endgames.local", + "mac": [ + "00:50:56:b1:e4:06" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:c216aea0-58ba-40a3-b6fe-afa2f5457835", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:22:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "tSCsI7HPfRVIcw3Yx3xUAl20Hfe9AdEIs/4IBBH9ZO1gxnMMjRkVb/hxhfcdg6dkW+RIc6Pc9Jz7rUvybq8fY0r/pTKGXTFr46dC2+E9jfb7rs/PmYhG2V0/Ei2p+ZQypAIp8mtknSHkX+l74N7niVXKreneLrt99e4ZWIyeuwNwr0HcGjoMEqM=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:59.088Z", + "default_api_key_id": "SK7jwHQBEiA0_DvkNuIq", + "last_checkin": "2020-09-24T16:22:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "OeriwHQBXUUrssdIvXGr", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-6-176.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.6.176/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:3363/64", + "fe80::250:56ff:feb1:3363/64" + ], + "hostname": "mainqa-atlcolo-10-0-6-176.eng.endgames.local", + "mac": [ + "00:50:56:b1:33:63" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-118-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "c216aea0-58ba-40a3-b6fe-afa2f5457835" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:2d187287-658a-4cb6-84d8-d66d1b9a6299", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:22:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "fpQcy/QWSbafzl6avELe9goTtyojPwQX3id1pe+BBqDarSCB3k5QwWLQP2SXEl2rwJdywUrBz3gMySKi80RYWJFUoWHiipfaE/jXJRqJxZZvhBe8fdSP7YPkdIdLQl/3ktIWqAzjjS1CErqMb5K4HTZIp5FswDQB40SbDkQKPECl9o8pBhLjH/A=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:28.949Z", + "default_api_key_id": "aeriwHQBXUUrssdIdXAX", + "last_checkin": "2020-09-24T16:22:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "Sa7iwHQBEiA0_DvkR99k", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "JCHU-WIN10-2", + "ip": [ + "fdbb:cb5c:fb4:68:dda8:b7a:3e20:9ca0/64", + "fdbb:cb5c:fb4:68:e922:9626:5193:ef68/128", + "fe80::dda8:b7a:3e20:9ca0/64", + "10.0.6.96/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "JCHU-WIN10-2", + "mac": [ + "00:50:56:b1:26:07" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "2d187287-658a-4cb6-84d8-d66d1b9a6299" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:296c368b-35d3-4241-905f-75a24f52ec13", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:22:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "PEF8+bfiv21Yn5yj8I2/vIaQWMrUQK4PeBBwXsrvmVTsbuFejXM0IQtYVKXShBJAoY9CUEKPCRR4rIIdXWZc51i1ZneLoFw+yBw8BsSwhHfbQXvAVQowH7UqKHp0CiA5J9uGSgmw3Q55a4dv4IHih+sBKji7Qf2durs5gCWUJExrRCpMiU3OHSg=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:56.620Z", + "default_api_key_id": "xOrjwHQBXUUrssdIDnHH", + "last_checkin": "2020-09-24T16:22:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "S67iwHQBEiA0_Dvks-Cm", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.7.158/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:371f/64", + "fe80::250:56ff:feb1:371f/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "mac": [ + "00:50:56:b1:37:1f" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-38-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "296c368b-35d3-4241-905f-75a24f52ec13" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:5460518c-10c7-4c25-b2ec-3f63eafb7d47", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:22:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "MngOEjmt4OWFSCvya8AWgDF9p0nPqiCZLpNrqntWdjcGl+vPcbVs+un3ilKC3GQKtKP6KLtMziLR/60teHpAJ0Ls1f+mbCP1PjjAfFL1ZBnGHsvkR099iRJ9q4rCxzmZtifGZQ/s2+t99DRUe8GkJhIj3VR1uN/EKPXmXDWZo0f+bTUDT7vGZVY=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:58.866Z", + "default_api_key_id": "ieriwHQBXUUrssdI83FW", + "last_checkin": "2020-09-24T16:22:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "E-riwHQBXUUrssdIvHEw", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::4de:9ad6:320f:79f5/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.116/22", + "fdbb:cb5c:fb4:68:1cfe:7be7:f700:8810/64", + "fdbb:cb5c:fb4:68:257d:7303:389d:f335/64", + "fdbb:cb5c:fb4:68:7470:3bec:14b5:2caf/64", + "fdbb:cb5c:fb4:68:9c5f:eab7:8345:f711/64", + "fdbb:cb5c:fb4:68:dc96:8bac:67e0:99dd/64", + "fdbb:cb5c:fb4:68:60c6:73b6:1540:602/64", + "fdbb:cb5c:fb4:68:144:6a1b:1aae:a57d/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "mac": [ + "00:50:56:b1:7e:49" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "5460518c-10c7-4c25-b2ec-3f63eafb7d47" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:ac0ab6c1-2317-478c-93d9-c514d845302d", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:22:36.352Z", + "fleet-agents": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "UnSz8pAKTP/0DENATzn13Yo0jcdbWq70IiBJcDY+DF5M063+El91o+448KVaMHj3rCSrULfJboBf1Ao80UKU5WKz4CYJ3ZVjHm39/f8rXMZSah5lQAkl9Ak2v5wUCFd4KTEwUUEmnUKKSQGC53cBhnvoyPdzfNjt1ml96lZFZbxXt/VyU3u8vhQ=", + "current_error_events": "[]", + "config_revision": 2, + "enrolled_at": "2020-09-24T16:11:30.880Z", + "default_api_key_id": "Va7iwHQBEiA0_DvkcN-4", + "last_checkin": "2020-09-24T16:22:36.351Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "Sq7iwHQBEiA0_DvkT98X", + "packages": [ + "endpoint", + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "JCHU-WIN10-1", + "ip": [ + "fdbb:cb5c:fb4:68:6ca6:5ea3:ae36:af51/64", + "fdbb:cb5c:fb4:68:6c9d:def9:bb8a:6695/128", + "fe80::6ca6:5ea3:ae36:af51/64", + "10.0.7.235/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "JCHU-WIN10-1", + "mac": [ + "00:50:56:b1:65:cb" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "ac0ab6c1-2317-478c-93d9-c514d845302d" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:81e5aa90-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:40.138333-04:00", + "subtype": "RUNNING", + "agent_id": "b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5", + "message": "Application: endpoint-security--7.9.2[b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:40.134985503Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[55,0,2,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":10.21008368,\"mean\":1.91476589372881}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":71143424,\"mean\":53719456}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":3.08880308880309},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":302,\"system\":1901758}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"49f4e779-287a-4fa8-80e6-247b54c554f1\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"7d59b1a5-afa1-6531-07ea-691602558230\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:57.177Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:8e652110-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:43.499165-04:00", + "subtype": "RUNNING", + "agent_id": "5460518c-10c7-4c25-b2ec-3f63eafb7d47", + "message": "Application: endpoint-security--7.9.2[5460518c-10c7-4c25-b2ec-3f63eafb7d47]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:43.495361445Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[44,4,0,2,2,4,1,2,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":25.33265565,\"mean\":6.21698140807909}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":58376192,\"mean\":46094231}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0.32258064516129},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0.323624595469256},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0.664451827242525},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":9.55882352941176},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":308,\"system\":3807934}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"b364a499-8e64-4d91-9770-6911c5d6964b\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"ec5403f8-6708-0d58-7aff-b2137b48b816\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-24T16:18:18.145Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:82b7eeb0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:13.3157023-04:00", + "subtype": "RUNNING", + "agent_id": "ac0ab6c1-2317-478c-93d9-c514d845302d", + "message": "Application: endpoint-security--7.9.2[ac0ab6c1-2317-478c-93d9-c514d845302d]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:13.13714300Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[53,1,0,1,0,0,2,1,0,3,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":49.0526570938275,\"mean\":4.53577832211642}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":285802496,\"mean\":95647240}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":1.30293159609121},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":3.18021201413428},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":306,\"system\":3625}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"7fdb1b867fd4d2da37870d493e1c67630f59355eab061f91e705f4cc83dd6b9b\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"e21f3ba186d1563b66bb58b7ff9a362c07448e8f4dec00b2f861bf935cb15d77\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"463709447352d429297355247266f641179331171342b3bc3e9c8f6b4b2faed2\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"6474b1bd-96bc-4bde-a770-0e6a7a5bf8c4\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"c85e6c40-d4a1-db21-7458-2565a6b857f3\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-24T16:17:58.555Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:80a6c1f0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:54.930717796-04:00", + "subtype": "RUNNING", + "agent_id": "c216aea0-58ba-40a3-b6fe-afa2f5457835", + "message": "Application: endpoint-security--7.9.2[c216aea0-58ba-40a3-b6fe-afa2f5457835]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:54.929290223Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[57,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":3,\"mean\":3.49666666666667}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":49778688,\"mean\":31986824}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":2.12765957446809},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.72992700729927},\"name\":\"EventsLoopThread\"}],\"uptime\":{\"endpoint\":300,\"system\":3863}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"a15f0431-6835-41c4-a7ee-21a70d41cf5b\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"20ccfdfa-323f-e33e-f2ef-3528edb1afea\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:55.087Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:7bdc8fb0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:45.675453579-04:00", + "subtype": "RUNNING", + "agent_id": "296c368b-35d3-4241-905f-75a24f52ec13", + "message": "Application: endpoint-security--7.9.2[296c368b-35d3-4241-905f-75a24f52ec13]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:45.674010613Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[57,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":2.8,\"mean\":3.17}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":49278976,\"mean\":31884356}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":2.12765957446809},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.72992700729927},\"name\":\"EventsLoopThread\"}],\"uptime\":{\"endpoint\":300,\"system\":5000305}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"6f0cb2fc-3e46-4435-8892-d9f7e71b23fd\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"e9909692-0e35-fd30-e3a3-e2e7253bb5c7\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:47.051Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:7cbf9cb1-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:15.400204-04:00", + "subtype": "RUNNING", + "agent_id": "2d187287-658a-4cb6-84d8-d66d1b9a6299", + "message": "Application: endpoint-security--7.9.2[2d187287-658a-4cb6-84d8-d66d1b9a6299]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:15.96990100Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[46,2,2,2,4,2,0,0,0,2,0,0,0,0,1,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":48.3070275492921,\"mean\":6.43134047264261}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":228757504,\"mean\":94594836}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":1.30293159609121},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":1.9672131147541},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":2.62295081967213},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0.655737704918033},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":2.11267605633803},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":307,\"system\":3654}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"7fdb1b867fd4d2da37870d493e1c67630f59355eab061f91e705f4cc83dd6b9b\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"e21f3ba186d1563b66bb58b7ff9a362c07448e8f4dec00b2f861bf935cb15d77\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"463709447352d429297355247266f641179331171342b3bc3e9c8f6b4b2faed2\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"21d182a2-5a08-41bb-b601-5d2b4aba4ecd\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"327d0e20-483e-95af-f4e4-7b065606e1aa\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-24T16:17:48.539Z", + "type": "fleet-agent-events" + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/mappings.json new file mode 100644 index 00000000000000..27aea27bebcd7e --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_installed/mappings.json @@ -0,0 +1,2592 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agents": "034346488514b7058a79140b19ddf631", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "5c4b9a6effceb17ae8a0ab22d0c49767", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_configs": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/data.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/data.json new file mode 100644 index 00000000000000..dbcd2604aed15f --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/data.json @@ -0,0 +1,527 @@ +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:5460518c-10c7-4c25-b2ec-3f63eafb7d47", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:30:38.636Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "MngOEjmt4OWFSCvya8AWgDF9p0nPqiCZLpNrqntWdjcGl+vPcbVs+un3ilKC3GQKtKP6KLtMziLR/60teHpAJ0Ls1f+mbCP1PjjAfFL1ZBnGHsvkR099iRJ9q4rCxzmZtifGZQ/s2+t99DRUe8GkJhIj3VR1uN/EKPXmXDWZo0f+bTUDT7vGZVY=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:58.866Z", + "default_api_key_id": "ieriwHQBXUUrssdI83FW", + "last_checkin": "2020-09-24T16:29:07.071Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "E-riwHQBXUUrssdIvHEw", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::4de:9ad6:320f:79f5/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.116/22", + "fdbb:cb5c:fb4:68:1cfe:7be7:f700:8810/64", + "fdbb:cb5c:fb4:68:257d:7303:389d:f335/64", + "fdbb:cb5c:fb4:68:7470:3bec:14b5:2caf/64", + "fdbb:cb5c:fb4:68:9c5f:eab7:8345:f711/64", + "fdbb:cb5c:fb4:68:dc96:8bac:67e0:99dd/64", + "fdbb:cb5c:fb4:68:60c6:73b6:1540:602/64", + "fdbb:cb5c:fb4:68:144:6a1b:1aae:a57d/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-116.eng.endgames.local", + "mac": [ + "00:50:56:b1:7e:49" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "5460518c-10c7-4c25-b2ec-3f63eafb7d47" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:ac0ab6c1-2317-478c-93d9-c514d845302d", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:30:37.974Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "UnSz8pAKTP/0DENATzn13Yo0jcdbWq70IiBJcDY+DF5M063+El91o+448KVaMHj3rCSrULfJboBf1Ao80UKU5WKz4CYJ3ZVjHm39/f8rXMZSah5lQAkl9Ak2v5wUCFd4KTEwUUEmnUKKSQGC53cBhnvoyPdzfNjt1ml96lZFZbxXt/VyU3u8vhQ=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:30.880Z", + "default_api_key_id": "Va7iwHQBEiA0_DvkcN-4", + "last_checkin": "2020-09-24T16:30:37.072Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "Sq7iwHQBEiA0_DvkT98X", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "JCHU-WIN10-1", + "ip": [ + "fdbb:cb5c:fb4:68:6ca6:5ea3:ae36:af51/64", + "fdbb:cb5c:fb4:68:6c9d:def9:bb8a:6695/128", + "fe80::6ca6:5ea3:ae36:af51/64", + "10.0.7.235/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "JCHU-WIN10-1", + "mac": [ + "00:50:56:b1:65:cb" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "ac0ab6c1-2317-478c-93d9-c514d845302d" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:c216aea0-58ba-40a3-b6fe-afa2f5457835", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:30:37.142Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "tSCsI7HPfRVIcw3Yx3xUAl20Hfe9AdEIs/4IBBH9ZO1gxnMMjRkVb/hxhfcdg6dkW+RIc6Pc9Jz7rUvybq8fY0r/pTKGXTFr46dC2+E9jfb7rs/PmYhG2V0/Ei2p+ZQypAIp8mtknSHkX+l74N7niVXKreneLrt99e4ZWIyeuwNwr0HcGjoMEqM=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:59.088Z", + "default_api_key_id": "SK7jwHQBEiA0_DvkNuIq", + "last_checkin": "2020-09-24T16:30:37.072Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "OeriwHQBXUUrssdIvXGr", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-6-176.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.6.176/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:3363/64", + "fe80::250:56ff:feb1:3363/64" + ], + "hostname": "mainqa-atlcolo-10-0-6-176.eng.endgames.local", + "mac": [ + "00:50:56:b1:33:63" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-118-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "c216aea0-58ba-40a3-b6fe-afa2f5457835" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:30:37.072Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "sdv6996k+S1BiZ/12K3Wi6rb8Lsoh/+shwzKNqujwcmhdbeQ92ygLoO+tudJaJOnL129WT+hhanEf6OgH5PpQBezc03hl9v2AI+BlU+hssfce5OfgFRGLYg8S+ryNHwFhK6EJeN1aivoie+YholNpcpt2l/t+lQpevMI4QYGaMfUzofuivs5JM4=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:54.037Z", + "default_api_key_id": "lq7iwHQBEiA0_Dvk8-Fb", + "last_checkin": "2020-09-24T16:30:37.072Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "geriwHQBXUUrssdIqXB2", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-195.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "fe80::1/64", + "fe80::107d:2365:5a7c:8da/64", + "fdbb:cb5c:fb4:68:1ca7:3a67:de43:950c/64", + "10.0.7.195/22", + "fdbb:cb5c:fb4:68:d4ef:63a5:8ffc:f933/64", + "fdbb:cb5c:fb4:68:b082:8681:cf85:27d0/64", + "fdbb:cb5c:fb4:68:7c3d:13f3:5339:be7b/64", + "fdbb:cb5c:fb4:68:19a4:2a63:cc88:6e59/64", + "fdbb:cb5c:fb4:68:494a:3867:57b8:4027/64", + "fdbb:cb5c:fb4:68:1c88:41e:6ce1:4be7/64", + "fdbb:cb5c:fb4:68:114:b84:8faf:b12b/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-195.eng.endgames.local", + "mac": [ + "00:50:56:b1:e4:06" + ], + "architecture": "x86_64", + "id": "4231B1A9-25CB-4157-CF54-6BCD11C742E0" + }, + "os": { + "kernel": "18.2.0", + "full": "Mac OS X(10.14.1)", + "name": "Mac OS X", + "family": "darwin", + "platform": "darwin", + "version": "10.14.1" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:296c368b-35d3-4241-905f-75a24f52ec13", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:30:37.072Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "PEF8+bfiv21Yn5yj8I2/vIaQWMrUQK4PeBBwXsrvmVTsbuFejXM0IQtYVKXShBJAoY9CUEKPCRR4rIIdXWZc51i1ZneLoFw+yBw8BsSwhHfbQXvAVQowH7UqKHp0CiA5J9uGSgmw3Q55a4dv4IHih+sBKji7Qf2durs5gCWUJExrRCpMiU3OHSg=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:56.620Z", + "default_api_key_id": "xOrjwHQBXUUrssdIDnHH", + "last_checkin": "2020-09-24T16:30:37.072Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "S67iwHQBEiA0_Dvks-Cm", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "ip": [ + "127.0.0.1/8", + "::1/128", + "10.0.7.158/22", + "fdbb:cb5c:fb4:68:250:56ff:feb1:371f/64", + "fe80::250:56ff:feb1:371f/64" + ], + "hostname": "mainqa-atlcolo-10-0-7-158.eng.endgames.local", + "mac": [ + "00:50:56:b1:37:1f" + ], + "architecture": "x86_64", + "id": "739e447fc6963034621b714c584eccc1" + }, + "os": { + "kernel": "4.15.0-38-generic", + "full": "Ubuntu bionic(18.04.1 LTS (Bionic Beaver))", + "name": "Ubuntu", + "family": "debian", + "platform": "ubuntu", + "version": "18.04.1 LTS (Bionic Beaver)" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "296c368b-35d3-4241-905f-75a24f52ec13" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agents:2d187287-658a-4cb6-84d8-d66d1b9a6299", + "source": { + "type": "fleet-agents", + "references": [], + "updated_at": "2020-09-24T16:30:37.072Z", + "fleet-agents": { + "config_id": "6d9d3630-fe80-11ea-82b3-5be7a91e28b6", + "default_api_key": "fpQcy/QWSbafzl6avELe9goTtyojPwQX3id1pe+BBqDarSCB3k5QwWLQP2SXEl2rwJdywUrBz3gMySKi80RYWJFUoWHiipfaE/jXJRqJxZZvhBe8fdSP7YPkdIdLQl/3ktIWqAzjjS1CErqMb5K4HTZIp5FswDQB40SbDkQKPECl9o8pBhLjH/A=", + "current_error_events": "[]", + "config_revision": 1, + "enrolled_at": "2020-09-24T16:11:28.949Z", + "default_api_key_id": "aeriwHQBXUUrssdIdXAX", + "last_checkin": "2020-09-24T16:30:37.072Z", + "active": true, + "user_provided_metadata": {}, + "access_api_key_id": "Sa7iwHQBEiA0_DvkR99k", + "packages": [ + "system" + ], + "type": "PERMANENT", + "local_metadata": { + "host": { + "name": "JCHU-WIN10-2", + "ip": [ + "fdbb:cb5c:fb4:68:dda8:b7a:3e20:9ca0/64", + "fdbb:cb5c:fb4:68:e922:9626:5193:ef68/128", + "fe80::dda8:b7a:3e20:9ca0/64", + "10.0.6.96/22", + "::1/128", + "127.0.0.1/8" + ], + "hostname": "JCHU-WIN10-2", + "mac": [ + "00:50:56:b1:26:07" + ], + "architecture": "x86_64", + "id": "4143c277-074e-47a9-b37d-37f94b508705" + }, + "os": { + "kernel": "10.0.18362.1082 (WinBuild.160101.0800)", + "full": "Windows 10 Pro(10.0)", + "name": "Windows 10 Pro", + "family": "windows", + "platform": "windows", + "version": "10.0" + }, + "elastic": { + "agent": { + "version": "7.9.2", + "id": "2d187287-658a-4cb6-84d8-d66d1b9a6299" + } + } + } + } + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:81e5aa90-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:40.138333-04:00", + "subtype": "RUNNING", + "agent_id": "b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5", + "message": "Application: endpoint-security--7.9.2[b1c968f1-a8cf-4bc4-ac81-110c8ffdbde5]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:40.134985503Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[55,0,2,1,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":10.21008368,\"mean\":1.91476589372881}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":71143424,\"mean\":53719456}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":3.08880308880309},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":302,\"system\":1901758}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"49f4e779-287a-4fa8-80e6-247b54c554f1\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"7d59b1a5-afa1-6531-07ea-691602558230\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:57.177Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:8e652110-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:43.499165-04:00", + "subtype": "RUNNING", + "agent_id": "5460518c-10c7-4c25-b2ec-3f63eafb7d47", + "message": "Application: endpoint-security--7.9.2[5460518c-10c7-4c25-b2ec-3f63eafb7d47]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:43.495361445Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[44,4,0,2,2,4,1,2,0,0,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":25.33265565,\"mean\":6.21698140807909}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":58376192,\"mean\":46094231}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0.32258064516129},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0.323624595469256},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0.664451827242525},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":9.55882352941176},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsPidMonitorThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsDelayEventThread\"}],\"uptime\":{\"endpoint\":308,\"system\":3807934}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to kernel extension\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"File write event reporting is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Process event reporting is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Network event reporting is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Full Disk Access is enabled\",\"name\":\"full_disk_access\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel extension\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointmacho-v1-blocklist\",\"sha256\":\"da7ca0eaffd840e612acdc064700b3549dc64768d7d127977cc86d9bdaac22ee\"},{\"name\":\"endpointmacho-v1-exceptionlist\",\"sha256\":\"a6d93374c05e88447a3f2aafe0061efc10ff28d324d701436c103194a7594b51\"},{\"name\":\"endpointmacho-v1-model\",\"sha256\":\"213e0b5dcad10504eac23a7056b2e87d1b694da19832366eae8eb85057945c4f\"},{\"name\":\"global-exceptionlist-macos\",\"sha256\":\"4abf799e6b79f0ee66a2e0b3293a92c2a122a083274cbea9d1b2c83bf57ffce7\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-macos-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"full_disk_access\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"b364a499-8e64-4d91-9770-6911c5d6964b\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"ec5403f8-6708-0d58-7aff-b2137b48b816\",\"os\":{\"Ext\":{\"variant\":\"macOS\"},\"full\":\"macOS 10.14.1\",\"name\":\"macOS\",\"version\":\"10.14.1\"}}}}" + }, + "updated_at": "2020-09-24T16:18:18.145Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:82b7eeb0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:13.3157023-04:00", + "subtype": "RUNNING", + "agent_id": "ac0ab6c1-2317-478c-93d9-c514d845302d", + "message": "Application: endpoint-security--7.9.2[ac0ab6c1-2317-478c-93d9-c514d845302d]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:13.13714300Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[53,1,0,1,0,0,2,1,0,3,0,0,0,0,0,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":49.0526570938275,\"mean\":4.53577832211642}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":285802496,\"mean\":95647240}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":1.30293159609121},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":3.18021201413428},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":306,\"system\":3625}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"7fdb1b867fd4d2da37870d493e1c67630f59355eab061f91e705f4cc83dd6b9b\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"e21f3ba186d1563b66bb58b7ff9a362c07448e8f4dec00b2f861bf935cb15d77\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"463709447352d429297355247266f641179331171342b3bc3e9c8f6b4b2faed2\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"6474b1bd-96bc-4bde-a770-0e6a7a5bf8c4\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"c85e6c40-d4a1-db21-7458-2565a6b857f3\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-24T16:17:58.555Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:80a6c1f0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:54.930717796-04:00", + "subtype": "RUNNING", + "agent_id": "c216aea0-58ba-40a3-b6fe-afa2f5457835", + "message": "Application: endpoint-security--7.9.2[c216aea0-58ba-40a3-b6fe-afa2f5457835]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:54.929290223Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[57,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,1],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":3,\"mean\":3.49666666666667}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":49778688,\"mean\":31986824}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":2.12765957446809},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.72992700729927},\"name\":\"EventsLoopThread\"}],\"uptime\":{\"endpoint\":300,\"system\":3863}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"a15f0431-6835-41c4-a7ee-21a70d41cf5b\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"20ccfdfa-323f-e33e-f2ef-3528edb1afea\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:55.087Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:7bdc8fb0-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:45.675453579-04:00", + "subtype": "RUNNING", + "agent_id": "296c368b-35d3-4241-905f-75a24f52ec13", + "message": "Application: endpoint-security--7.9.2[296c368b-35d3-4241-905f-75a24f52ec13]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:45.674010613Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[57,0,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,1],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":2.8,\"mean\":3.17}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":49278976,\"mean\":31884356}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":2.12765957446809},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"},{\"cpu\":{\"mean\":0.72992700729927},\"name\":\"EventsLoopThread\"}],\"uptime\":{\"endpoint\":300,\"system\":5000305}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"6f0cb2fc-3e46-4435-8892-d9f7e71b23fd\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"e9909692-0e35-fd30-e3a3-e2e7253bb5c7\",\"os\":{\"Ext\":{\"variant\":\"Ubuntu\"},\"full\":\"Ubuntu 18.04.1\",\"name\":\"Linux\",\"version\":\"18.04.1\"}}}}" + }, + "updated_at": "2020-09-24T16:17:47.051Z", + "type": "fleet-agent-events" + } + } +} + +{ + "type": "doc", + "value": { + "index": ".kibana_1", + "type": "_doc", + "id": "fleet-agent-events:7cbf9cb1-fe81-11ea-ac23-9bd6426c270f", + "source": { + "references": [], + "fleet-agent-events": { + "config_id": "81188c00-fe80-11ea-82b3-5be7a91e28b6", + "timestamp": "2020-09-24T12:17:15.400204-04:00", + "subtype": "RUNNING", + "agent_id": "2d187287-658a-4cb6-84d8-d66d1b9a6299", + "message": "Application: endpoint-security--7.9.2[2d187287-658a-4cb6-84d8-d66d1b9a6299]: State changed to RUNNING: ", + "type": "STATE", + "payload": "{\"endpoint-security\":{\"@timestamp\":\"2020-09-24T16:17:15.96990100Z\",\"Endpoint\":{\"configuration\":{\"inputs\":[{\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"policy\":{\"linux\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"}},\"mac\":{\"events\":{\"file\":true,\"network\":true,\"process\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}},\"windows\":{\"events\":{\"dll_and_driver_load\":true,\"dns\":true,\"file\":true,\"network\":true,\"process\":true,\"registry\":true,\"security\":true},\"logging\":{\"file\":\"info\"},\"malware\":{\"mode\":\"prevent\"}}}}]},\"metrics\":{\"cpu\":{\"endpoint\":{\"histogram\":{\"counts\":[46,2,2,2,4,2,0,0,0,2,0,0,0,0,1,0,0,0,0,0],\"values\":[5,10,15,20,25,30,35,40,45,50,55,60,65,70,75,80,85,90,95,100]},\"latest\":48.3070275492921,\"mean\":6.43134047264261}},\"memory\":{\"endpoint\":{\"private\":{\"latest\":228757504,\"mean\":94594836}}},\"threads\":[{\"cpu\":{\"mean\":0},\"name\":\"Cron\"},{\"cpu\":{\"mean\":0},\"name\":\"File Cache\"},{\"cpu\":{\"mean\":0},\"name\":\"FileLogThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingMaintenance\"},{\"cpu\":{\"mean\":0},\"name\":\"BulkConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DocumentLoggingConsumerThread\"},{\"cpu\":{\"mean\":1.30293159609121},\"name\":\"ArtifactManifestDownload\"},{\"cpu\":{\"mean\":0},\"name\":\"PerformanceMonitorWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"MetadataThread\"},{\"cpu\":{\"mean\":0},\"name\":\"EventsQueueThread\"},{\"cpu\":{\"mean\":0},\"name\":\"FileScoreAsyncEventThread\"},{\"cpu\":{\"mean\":0},\"name\":\"QuarantineManagerWorkerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"DelayedAlertEnrichment\"},{\"cpu\":{\"mean\":0},\"name\":\"grpcConnectionManagerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":1.9672131147541},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":2.62295081967213},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncMessageThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelAsyncMessageQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelSyncQueueConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":0.655737704918033},\"name\":\"KernelPortConsumerThread\"},{\"cpu\":{\"mean\":2.11267605633803},\"name\":\"checkinAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"actionsAPIThread\"},{\"cpu\":{\"mean\":0},\"name\":\"stateReportThread\"}],\"uptime\":{\"endpoint\":307,\"system\":3654}},\"policy\":{\"applied\":{\"actions\":[{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"read_elasticsearch_config\",\"status\":\"success\"},{\"message\":\"Successfully read events configuration\",\"name\":\"read_events_config\",\"status\":\"success\"},{\"message\":\"Successfully read malware prevent configuration\",\"name\":\"read_malware_config\",\"status\":\"success\"},{\"message\":\"Succesfully read kernel configuration\",\"name\":\"read_kernel_config\",\"status\":\"success\"},{\"message\":\"Successfully read logging configuration\",\"name\":\"read_logging_config\",\"status\":\"success\"},{\"message\":\"Successfully parsed configuration\",\"name\":\"load_config\",\"status\":\"success\"},{\"message\":\"Successfully downloaded user artifacts\",\"name\":\"download_user_artifacts\",\"status\":\"success\"},{\"message\":\"Global artifacts are available for use\",\"name\":\"download_global_artifacts\",\"status\":\"success\"},{\"message\":\"Successfully configured logging\",\"name\":\"configure_logging\",\"status\":\"success\"},{\"message\":\"Successfully read Elasticsearch configuration\",\"name\":\"configure_elasticsearch_connection\",\"status\":\"success\"},{\"message\":\"Successfully connected to driver\",\"name\":\"connect_kernel\",\"status\":\"success\"},{\"message\":\"Successfully started process event reporting\",\"name\":\"detect_process_events\",\"status\":\"success\"},{\"message\":\"Successfuly started sync image load event reporting\",\"name\":\"detect_sync_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfuly started async image load event reporting\",\"name\":\"detect_async_image_load_events\",\"status\":\"success\"},{\"message\":\"Successfully started file write event reporting\",\"name\":\"detect_file_write_events\",\"status\":\"success\"},{\"message\":\"Successfully stopped file open event reporting\",\"name\":\"detect_file_open_events\",\"status\":\"success\"},{\"message\":\"Successfully started network event reporting\",\"name\":\"detect_network_events\",\"status\":\"success\"},{\"message\":\"Successfully started registry event reporting\",\"name\":\"detect_registry_events\",\"status\":\"success\"},{\"message\":\"Successfully configured kernel\",\"name\":\"configure_kernel\",\"status\":\"success\"},{\"message\":\"Successfully loaded malware model\",\"name\":\"load_malware_model\",\"status\":\"success\"},{\"message\":\"Successfully configured malware prevention/detection\",\"name\":\"configure_malware\",\"status\":\"success\"},{\"message\":\"Success enabling file events; current state is enabled\",\"name\":\"configure_file_events\",\"status\":\"success\"},{\"message\":\"Success enabling network events; current state is enabled\",\"name\":\"configure_network_events\",\"status\":\"success\"},{\"message\":\"Success enabling process events; current state is enabled\",\"name\":\"configure_process_events\",\"status\":\"success\"},{\"message\":\"Success enabling imageload events; current state is enabled\",\"name\":\"configure_imageload_events\",\"status\":\"success\"},{\"message\":\"Success enabling dns events; current state is enabled\",\"name\":\"configure_dns_events\",\"status\":\"success\"},{\"message\":\"Success enabling registry events; current state is enabled\",\"name\":\"configure_registry_events\",\"status\":\"success\"},{\"message\":\"Success enabling security events; current state is enabled\",\"name\":\"configure_security_events\",\"status\":\"success\"},{\"message\":\"Successfully connected to Agent\",\"name\":\"agent_connectivity\",\"status\":\"success\"},{\"message\":\"Successfully executed all workflows\",\"name\":\"workflow\",\"status\":\"success\"}],\"artifacts\":{\"global\":{\"identifiers\":[{\"name\":\"endpointpe-v4-blocklist\",\"sha256\":\"7fdb1b867fd4d2da37870d493e1c67630f59355eab061f91e705f4cc83dd6b9b\"},{\"name\":\"endpointpe-v4-exceptionlist\",\"sha256\":\"e21f3ba186d1563b66bb58b7ff9a362c07448e8f4dec00b2f861bf935cb15d77\"},{\"name\":\"endpointpe-v4-model\",\"sha256\":\"463709447352d429297355247266f641179331171342b3bc3e9c8f6b4b2faed2\"},{\"name\":\"global-exceptionlist-windows\",\"sha256\":\"824859b0c6749cc31951d92a73bbdddfcfe9f38abfe432087934d4dab9766ce8\"}],\"version\":\"1.0.0\"},\"user\":{\"identifiers\":[{\"name\":\"endpoint-exceptionlist-windows-v1\",\"sha256\":\"d801aa1fb7ddcc330a5e3173372ea6af4a3d08ec58074478e85aa5603e926658\"}],\"version\":\"1.0.0\"}},\"id\":\"8f802370-fe80-11ea-82b3-5be7a91e28b6\",\"response\":{\"configurations\":{\"events\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"read_events_config\",\"detect_process_events\",\"detect_file_write_events\",\"detect_network_events\",\"configure_file_events\",\"configure_network_events\",\"configure_process_events\",\"read_kernel_config\",\"configure_kernel\",\"connect_kernel\",\"detect_file_open_events\",\"detect_async_image_load_events\",\"detect_registry_events\",\"configure_imageload_events\",\"configure_dns_events\",\"configure_security_events\",\"configure_registry_events\"],\"status\":\"success\"},\"logging\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_logging_config\",\"configure_logging\",\"workflow\"],\"status\":\"success\"},\"malware\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"workflow\",\"download_global_artifacts\",\"download_user_artifacts\",\"configure_malware\",\"read_malware_config\",\"load_malware_model\",\"read_kernel_config\",\"configure_kernel\",\"detect_process_events\",\"detect_file_write_events\",\"connect_kernel\",\"detect_file_open_events\",\"detect_sync_image_load_events\"],\"status\":\"success\"},\"streaming\":{\"concerned_actions\":[\"agent_connectivity\",\"load_config\",\"read_elasticsearch_config\",\"configure_elasticsearch_connection\",\"workflow\"],\"status\":\"success\"}}},\"status\":\"success\"}}},\"agent\":{\"id\":\"21d182a2-5a08-41bb-b601-5d2b4aba4ecd\",\"version\":\"7.9.2\"},\"ecs\":{\"version\":\"1.5.0\"},\"event\":{\"action\":\"elastic_endpoint_telemetry\"},\"host\":{\"architecture\":\"x86_64\",\"id\":\"327d0e20-483e-95af-f4e4-7b065606e1aa\",\"os\":{\"Ext\":{\"variant\":\"Windows 10 Pro\"},\"full\":\"Windows 10 Pro 1903 (10.0.18362.1082)\",\"name\":\"Windows\",\"version\":\"1903 (10.0.18362.1082)\"}}}}" + }, + "updated_at": "2020-09-24T16:17:48.539Z", + "type": "fleet-agent-events" + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/mappings.json b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/mappings.json new file mode 100644 index 00000000000000..27aea27bebcd7e --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/telemetry/cloned_endpoint_uninstalled/mappings.json @@ -0,0 +1,2592 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "6e96ac5e648f57523879661ea72525b7", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "7b44fba6773e37c806ce290ea9b7024e", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "app_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "canvas-workpad-template": "ae2673f678281e2c055d764b153e9715", + "cases": "32aa96a6d3855ddda53010ae2048ac22", + "cases-comments": "c2061fb929f585df57425102fa928b4b", + "cases-configure": "42711cbb311976c0687853f4c1354572", + "cases-user-actions": "32277330ec6b721abe3b846cfd939a71", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "endpoint:user-artifact": "4a11183eee21e6fbad864f7a30b39ad0", + "endpoint:user-artifact-manifest": "4b9c0e7cfaf86d82a7ee9ed68065e50d", + "epm-packages": "8f6e0b09ea0374c4ffe98c3755373cff", + "exception-list": "497afa2f881a675d72d58e20057f3d8b", + "exception-list-agnostic": "497afa2f881a675d72d58e20057f3d8b", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "fleet-agent-actions": "e520c855577170c24481be05c3ae14ec", + "fleet-agent-events": "3231653fafe4ef3196fe3b32ab774bf2", + "fleet-agents": "034346488514b7058a79140b19ddf631", + "fleet-enrollment-api-keys": "28b91e20b105b6f928e2012600085d8f", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "2b2809653635caf490c93f090502d04c", + "ingest-agent-policies": "9326f99c977fd2ef5ab24b6336a0675c", + "ingest-outputs": "8aa988c376e65443fefc26f1075e93a3", + "ingest-package-policies": "8545e51d7bc8286d6dace3d41240d749", + "ingest_manager_settings": "012cf278ec84579495110bb827d1ed09", + "inventory-view": "88fc7e12fd1b45b6f0787323ce4f18d2", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "d33c68a69ff1e78c9888dedd2164ac22", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "4a05b35c3a3a58fbc72dd0202dc3487f", + "maps-telemetry": "5ef305b18111b77789afefbd36b66171", + "metrics-explorer-view": "a8df1d270ee48c969d22d23812d08187", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "5c4b9a6effceb17ae8a0ab22d0c49767", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "siem-detection-engine-rule-actions": "6569b288c169539db10cb262bf79de18", + "siem-detection-engine-rule-status": "ae783f41c6937db6b7a2ef5c93a9e9b0", + "siem-ui-timeline": "94bc38c7a421d15fbfe8ea565370a421", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "tsvb-validation-telemetry": "3a37ef6c8700ae6fc97d5c7da00e9215", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "215107c281839ea9b3ad5f6419819763", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "uptime-dynamic-settings": "fcdb453a30092f022f2642db29523d80", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc", + "workplace_search_telemetry": "3d1b76c39bfb2cc8296b024d73854724" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "consumer": { + "type": "keyword" + }, + "createdAt": { + "type": "date" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "schedule": { + "properties": { + "interval": { + "type": "keyword" + } + } + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-telemetry": { + "dynamic": "false", + "type": "object" + }, + "app_search_telemetry": { + "dynamic": "false", + "type": "object" + }, + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad-template": { + "dynamic": "false", + "properties": { + "help": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "tags": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "template_key": { + "type": "keyword" + } + } + }, + "cases": { + "properties": { + "closed_at": { + "type": "date" + }, + "closed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "connector_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "description": { + "type": "text" + }, + "external_service": { + "properties": { + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "external_id": { + "type": "keyword" + }, + "external_title": { + "type": "text" + }, + "external_url": { + "type": "text" + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "status": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-comments": { + "properties": { + "comment": { + "type": "text" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "pushed_at": { + "type": "date" + }, + "pushed_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-configure": { + "properties": { + "closure_type": { + "type": "keyword" + }, + "connector_id": { + "type": "keyword" + }, + "connector_name": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + } + } + }, + "cases-user-actions": { + "properties": { + "action": { + "type": "keyword" + }, + "action_at": { + "type": "date" + }, + "action_by": { + "properties": { + "email": { + "type": "keyword" + }, + "full_name": { + "type": "keyword" + }, + "username": { + "type": "keyword" + } + } + }, + "action_field": { + "type": "keyword" + }, + "new_value": { + "type": "text" + }, + "old_value": { + "type": "text" + } + } + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "endpoint:user-artifact": { + "properties": { + "body": { + "type": "binary" + }, + "compressionAlgorithm": { + "index": false, + "type": "keyword" + }, + "created": { + "index": false, + "type": "date" + }, + "decodedSha256": { + "index": false, + "type": "keyword" + }, + "decodedSize": { + "index": false, + "type": "long" + }, + "encodedSha256": { + "type": "keyword" + }, + "encodedSize": { + "index": false, + "type": "long" + }, + "encryptionAlgorithm": { + "index": false, + "type": "keyword" + }, + "identifier": { + "type": "keyword" + } + } + }, + "endpoint:user-artifact-manifest": { + "properties": { + "created": { + "index": false, + "type": "date" + }, + "ids": { + "index": false, + "type": "keyword" + }, + "schemaVersion": { + "type": "keyword" + }, + "semanticVersion": { + "index": false, + "type": "keyword" + } + } + }, + "epm-packages": { + "properties": { + "es_index_patterns": { + "enabled": false, + "type": "object" + }, + "installed_es": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "installed_kibana": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "internal": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "removable": { + "type": "boolean" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "exception-list-agnostic": { + "properties": { + "_tags": { + "type": "keyword" + }, + "comments": { + "properties": { + "comment": { + "type": "keyword" + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "updated_at": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "created_at": { + "type": "keyword" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "keyword" + }, + "entries": { + "properties": { + "entries": { + "properties": { + "field": { + "type": "keyword" + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "field": { + "type": "keyword" + }, + "list": { + "properties": { + "id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "operator": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "value": { + "fields": { + "text": { + "type": "text" + } + }, + "type": "keyword" + } + } + }, + "immutable": { + "type": "boolean" + }, + "item_id": { + "type": "keyword" + }, + "list_id": { + "type": "keyword" + }, + "list_type": { + "type": "keyword" + }, + "meta": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "tie_breaker_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_by": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "fleet-agent-actions": { + "properties": { + "agent_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "data": { + "type": "binary" + }, + "sent_at": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agent-events": { + "properties": { + "action_id": { + "type": "keyword" + }, + "agent_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "data": { + "type": "text" + }, + "message": { + "type": "text" + }, + "payload": { + "type": "text" + }, + "stream_id": { + "type": "keyword" + }, + "subtype": { + "type": "keyword" + }, + "timestamp": { + "type": "date" + }, + "type": { + "type": "keyword" + } + } + }, + "fleet-agents": { + "properties": { + "access_api_key_id": { + "type": "keyword" + }, + "active": { + "type": "boolean" + }, + "config_id": { + "type": "keyword" + }, + "config_revision": { + "type": "integer" + }, + "current_error_events": { + "index": false, + "type": "text" + }, + "default_api_key": { + "type": "binary" + }, + "default_api_key_id": { + "type": "keyword" + }, + "enrolled_at": { + "type": "date" + }, + "last_checkin": { + "type": "date" + }, + "last_checkin_status": { + "type": "keyword" + }, + "last_updated": { + "type": "date" + }, + "local_metadata": { + "type": "flattened" + }, + "packages": { + "type": "keyword" + }, + "shared_id": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "unenrolled_at": { + "type": "date" + }, + "unenrollment_started_at": { + "type": "date" + }, + "updated_at": { + "type": "date" + }, + "user_provided_metadata": { + "type": "flattened" + }, + "version": { + "type": "keyword" + } + } + }, + "fleet-enrollment-api-keys": { + "properties": { + "active": { + "type": "boolean" + }, + "api_key": { + "type": "binary" + }, + "api_key_id": { + "type": "keyword" + }, + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "expire_at": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "inventoryDefaultView": { + "type": "keyword" + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "metricsExplorerDefaultView": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "ingest-agent-policies": { + "properties": { + "description": { + "type": "text" + }, + "is_default": { + "type": "boolean" + }, + "monitoring_enabled": { + "index": false, + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "package_configs": { + "type": "keyword" + }, + "revision": { + "type": "integer" + }, + "status": { + "type": "keyword" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest-outputs": { + "properties": { + "ca_sha256": { + "index": false, + "type": "keyword" + }, + "config": { + "type": "flattened" + }, + "fleet_enroll_password": { + "type": "binary" + }, + "fleet_enroll_username": { + "type": "binary" + }, + "hosts": { + "type": "keyword" + }, + "is_default": { + "type": "boolean" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "ingest-package-policies": { + "properties": { + "config_id": { + "type": "keyword" + }, + "created_at": { + "type": "date" + }, + "created_by": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "enabled": { + "type": "boolean" + }, + "inputs": { + "enabled": false, + "properties": { + "config": { + "type": "flattened" + }, + "enabled": { + "type": "boolean" + }, + "streams": { + "properties": { + "compiled_stream": { + "type": "flattened" + }, + "config": { + "type": "flattened" + }, + "data_stream": { + "properties": { + "dataset": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "enabled": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "type": { + "type": "keyword" + }, + "vars": { + "type": "flattened" + } + }, + "type": "nested" + }, + "name": { + "type": "keyword" + }, + "namespace": { + "type": "keyword" + }, + "output_id": { + "type": "keyword" + }, + "package": { + "properties": { + "name": { + "type": "keyword" + }, + "title": { + "type": "keyword" + }, + "version": { + "type": "keyword" + } + } + }, + "revision": { + "type": "integer" + }, + "updated_at": { + "type": "date" + }, + "updated_by": { + "type": "keyword" + } + } + }, + "ingest_manager_settings": { + "properties": { + "agent_auto_upgrade": { + "type": "keyword" + }, + "has_seen_add_data_notice": { + "index": false, + "type": "boolean" + }, + "kibana_ca_sha256": { + "type": "keyword" + }, + "kibana_url": { + "type": "keyword" + }, + "package_auto_upgrade": { + "type": "keyword" + } + } + }, + "inventory-view": { + "properties": { + "accountId": { + "type": "keyword" + }, + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customMetrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "legend": { + "properties": { + "palette": { + "type": "keyword" + }, + "reverseColors": { + "type": "boolean" + }, + "steps": { + "type": "long" + } + } + }, + "metric": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "label": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "region": { + "type": "keyword" + }, + "sort": { + "properties": { + "by": { + "type": "keyword" + }, + "direction": { + "type": "keyword" + } + } + }, + "time": { + "type": "long" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "description": { + "type": "text" + }, + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "enabled": false, + "type": "object" + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "forceInterval": { + "type": "boolean" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "source": { + "type": "keyword" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "visualization": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "sort": { + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "siem-detection-engine-rule-actions": { + "properties": { + "actions": { + "properties": { + "action_type_id": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "id": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alertThrottle": { + "type": "keyword" + }, + "ruleAlertId": { + "type": "keyword" + }, + "ruleThrottle": { + "type": "keyword" + } + } + }, + "siem-detection-engine-rule-status": { + "properties": { + "alertId": { + "type": "keyword" + }, + "bulkCreateTimeDurations": { + "type": "float" + }, + "gap": { + "type": "text" + }, + "lastFailureAt": { + "type": "date" + }, + "lastFailureMessage": { + "type": "text" + }, + "lastLookBackDate": { + "type": "date" + }, + "lastSuccessAt": { + "type": "date" + }, + "lastSuccessMessage": { + "type": "text" + }, + "searchAfterTimeDurations": { + "type": "float" + }, + "status": { + "type": "keyword" + }, + "statusDate": { + "type": "date" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + }, + "type": { + "type": "text" + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "eventType": { + "type": "keyword" + }, + "excludedRowRendererIds": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "status": { + "type": "keyword" + }, + "templateTimelineId": { + "type": "text" + }, + "templateTimelineVersion": { + "type": "integer" + }, + "timelineType": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "tsvb-validation-telemetry": { + "properties": { + "failedRequests": { + "type": "long" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "properties": { + "errorMessage": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "indexName": { + "type": "keyword" + }, + "lastCompletedStep": { + "type": "long" + }, + "locked": { + "type": "date" + }, + "newIndexName": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexOptions": { + "properties": { + "openAndClose": { + "type": "boolean" + }, + "queueSettings": { + "properties": { + "queuedAt": { + "type": "long" + }, + "startedAt": { + "type": "long" + } + } + } + } + }, + "reindexTaskId": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "reindexTaskPercComplete": { + "type": "float" + }, + "runningReindexCount": { + "type": "integer" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "uptime-dynamic-settings": { + "properties": { + "certAgeThreshold": { + "type": "long" + }, + "certExpirationThreshold": { + "type": "long" + }, + "heartbeatIndices": { + "type": "keyword" + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + }, + "workplace_search_telemetry": { + "dynamic": "false", + "type": "object" + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/maps/kibana/data.json b/x-pack/test/functional/es_archives/maps/kibana/data.json index 71b4a85d63f089..5d6a355939d304 100644 --- a/x-pack/test/functional/es_archives/maps/kibana/data.json +++ b/x-pack/test/functional/es_archives/maps/kibana/data.json @@ -20,7 +20,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields" : "[{\"name\":\"@message\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@message.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"@message\"}}},{\"name\":\"@tags\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"@tags.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"@tags\"}}},{\"name\":\"@timestamp\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"agent\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"agent.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"agent\"}}},{\"name\":\"bytes\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"clientip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"extension\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"extension.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"extension\"}}},{\"name\":\"geo.coordinates\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.dest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.src\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"geo.srcdest\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"headings\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"headings.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"headings\"}}},{\"name\":\"host\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"host.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"host\"}}},{\"name\":\"id\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"index.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"index\"}}},{\"name\":\"ip\",\"type\":\"ip\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"links\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"links.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"links\"}}},{\"name\":\"machine.os\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"machine.os.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"machine.os\"}}},{\"name\":\"machine.ram\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"memory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.char\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"meta.related\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.firstname\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"meta.user.lastname\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"phpmemory\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"referer\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:modified_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:published_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"relatedContent.article:section\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:section.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:section\"}}},{\"name\":\"relatedContent.article:tag\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.article:tag.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.article:tag\"}}},{\"name\":\"relatedContent.og:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:description\"}}},{\"name\":\"relatedContent.og:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image\"}}},{\"name\":\"relatedContent.og:image:height\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:height.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:height\"}}},{\"name\":\"relatedContent.og:image:width\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:image:width.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:image:width\"}}},{\"name\":\"relatedContent.og:site_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:site_name.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:site_name\"}}},{\"name\":\"relatedContent.og:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:title\"}}},{\"name\":\"relatedContent.og:type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:type.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:type\"}}},{\"name\":\"relatedContent.og:url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.og:url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.og:url\"}}},{\"name\":\"relatedContent.twitter:card\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:card.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:card\"}}},{\"name\":\"relatedContent.twitter:description\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:description.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:description\"}}},{\"name\":\"relatedContent.twitter:image\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:image.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:image\"}}},{\"name\":\"relatedContent.twitter:site\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:site.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:site\"}}},{\"name\":\"relatedContent.twitter:title\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.twitter:title.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.twitter:title\"}}},{\"name\":\"relatedContent.url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"relatedContent.url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"relatedContent.url\"}}},{\"name\":\"request\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"request.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"request\"}}},{\"name\":\"response\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"response.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"response\"}}},{\"name\":\"spaces\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"spaces.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"spaces\"}}},{\"name\":\"type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"url\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"url.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"url\"}}},{\"name\":\"utc_time\",\"type\":\"date\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"xss\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"xss.raw\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true,\"subType\":{\"multi\":{\"parent\": \"xss\"}}},{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['@timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", + "fields" : "[{\"name\":\"hour_of_day\",\"type\":\"number\",\"count\":0,\"scripted\":true,\"script\":\"doc['@timestamp'].value.getHour()\",\"lang\":\"painless\",\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false}]", "timeFieldName": "@timestamp", "title": "logstash-*" }, @@ -36,7 +36,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields" : "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"geometry\",\"type\":\"geo_shape\",\"esTypes\":[\"geo_shape\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"name\",\"type\":\"string\",\"esTypes\":[\"keyword\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"prop1\",\"type\":\"number\",\"esTypes\":[\"byte\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "fields" : "[]", "title": "geo_shapes*" }, "type": "index-pattern" @@ -51,7 +51,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"prop1\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"shape_name\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "fields" : "[]", "title": "meta_for_geo_shapes*" }, "type": "index-pattern" @@ -66,7 +66,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"location\",\"type\":\"geo_point\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "fields" : "[]", "title": "antimeridian_points" }, "type": "index-pattern" @@ -81,7 +81,7 @@ "index": ".kibana", "source": { "index-pattern": { - "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"location\",\"type\":\"geo_shape\",\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":false,\"readFromDocValues\":false}]", + "fields" : "[]", "title": "antimeridian_shapes" }, "type": "index-pattern" @@ -96,8 +96,8 @@ "index": ".kibana", "source": { "index-pattern" : { - "title" : "flights", - "fields" : "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"altitude\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"heading\",\"type\":\"number\",\"esTypes\":[\"integer\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"location\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]" + "fields" : "[]", + "title" : "flights" }, "type" : "index-pattern", "references" : [ ], @@ -116,8 +116,8 @@ "index": ".kibana", "source": { "index-pattern" : { - "title" : "connections", - "fields" : "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"destination\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true},{\"name\":\"source\",\"type\":\"geo_point\",\"esTypes\":[\"geo_point\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]" + "fields" : "[]", + "title" : "connections" }, "type" : "index-pattern", "references" : [ ], diff --git a/x-pack/test/security_api_integration/anonymous_es_anonymous.config.ts b/x-pack/test/security_api_integration/anonymous_es_anonymous.config.ts new file mode 100644 index 00000000000000..3fc30c922b742a --- /dev/null +++ b/x-pack/test/security_api_integration/anonymous_es_anonymous.config.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 { FtrConfigProviderContext } from '@kbn/test/types/ftr'; + +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const anonymousAPITestsConfig = await readConfigFile(require.resolve('./anonymous.config.ts')); + return { + ...anonymousAPITestsConfig.getAll(), + + junit: { + reportName: 'X-Pack Security API Integration Tests (Anonymous with ES anonymous access)', + }, + + esTestCluster: { + ...anonymousAPITestsConfig.get('esTestCluster'), + serverArgs: [ + ...anonymousAPITestsConfig.get('esTestCluster.serverArgs'), + 'xpack.security.authc.anonymous.username=anonymous_user', + 'xpack.security.authc.anonymous.roles=anonymous_role', + ], + }, + + kbnTestServer: { + ...anonymousAPITestsConfig.get('kbnTestServer'), + serverArgs: [ + ...anonymousAPITestsConfig + .get('kbnTestServer.serverArgs') + .filter((arg: string) => !arg.startsWith('--xpack.security.authc.providers')), + `--xpack.security.authc.providers=${JSON.stringify({ + anonymous: { anonymous1: { order: 0, credentials: 'elasticsearch_anonymous_user' } }, + basic: { basic1: { order: 1 } }, + })}`, + ], + }, + }; +} diff --git a/x-pack/test/security_api_integration/tests/anonymous/login.ts b/x-pack/test/security_api_integration/tests/anonymous/login.ts index e7c876f54ee5a0..eaf999c5097415 100644 --- a/x-pack/test/security_api_integration/tests/anonymous/login.ts +++ b/x-pack/test/security_api_integration/tests/anonymous/login.ts @@ -31,18 +31,24 @@ export default function ({ getService }: FtrProviderContext) { expect(cookie.maxAge).to.be(0); } + const isElasticsearchAnonymousAccessEnabled = (config.get( + 'esTestCluster.serverArgs' + ) as string[]).some((setting) => setting.startsWith('xpack.security.authc.anonymous')); + describe('Anonymous authentication', () => { - before(async () => { - await security.user.create('anonymous_user', { - password: 'changeme', - roles: [], - full_name: 'Guest', + if (!isElasticsearchAnonymousAccessEnabled) { + before(async () => { + await security.user.create('anonymous_user', { + password: 'changeme', + roles: [], + full_name: 'Guest', + }); }); - }); - after(async () => { - await security.user.delete('anonymous_user'); - }); + after(async () => { + await security.user.delete('anonymous_user'); + }); + } it('should reject API requests if client is not authenticated', async () => { await supertest.get('/internal/security/me').set('kbn-xsrf', 'xxx').expect(401); @@ -97,7 +103,9 @@ export default function ({ getService }: FtrProviderContext) { expect(user.username).to.eql('anonymous_user'); expect(user.authentication_provider).to.eql({ type: 'anonymous', name: 'anonymous1' }); - expect(user.authentication_type).to.eql('realm'); + expect(user.authentication_type).to.eql( + isElasticsearchAnonymousAccessEnabled ? 'anonymous' : 'realm' + ); // Do not assert on the `authentication_realm`, as the value differs for on-prem vs cloud }); @@ -174,7 +182,7 @@ export default function ({ getService }: FtrProviderContext) { expect(cookies).to.have.length(1); checkCookieIsCleared(request.cookie(cookies[0])!); - expect(logoutResponse.headers.location).to.be('/security/logged_out'); + expect(logoutResponse.headers.location).to.be('/security/logged_out?msg=LOGGED_OUT'); // Old cookie should be invalidated and not allow API access. const apiResponse = await supertest diff --git a/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts index e63f8cd2ebe32d..26edc36563e1c1 100644 --- a/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts +++ b/x-pack/test/security_api_integration/tests/kerberos/kerberos_login.ts @@ -259,7 +259,7 @@ export default function ({ getService }: FtrProviderContext) { expect(cookies).to.have.length(1); checkCookieIsCleared(request.cookie(cookies[0])!); - expect(logoutResponse.headers.location).to.be('/security/logged_out'); + expect(logoutResponse.headers.location).to.be('/security/logged_out?msg=LOGGED_OUT'); // Token that was stored in the previous cookie should be invalidated as well and old // session cookie should not allow API access. diff --git a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts index edcc1b5744fe37..a9e83ff7dadcef 100644 --- a/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts +++ b/x-pack/test/security_api_integration/tests/login_selector/basic_functionality.ts @@ -10,7 +10,7 @@ import { resolve } from 'path'; import url from 'url'; import { CA_CERT_PATH } from '@kbn/dev-utils'; import expect from '@kbn/expect'; -import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/model'; import { getStateAndNonce } from '../../fixtures/oidc/oidc_tools'; import { getMutualAuthenticationResponseToken, diff --git a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts index 93eabe33dc6876..0d630dab51cf75 100644 --- a/x-pack/test/security_api_integration/tests/pki/pki_auth.ts +++ b/x-pack/test/security_api_integration/tests/pki/pki_auth.ts @@ -307,7 +307,7 @@ export default function ({ getService }: FtrProviderContext) { expect(cookies).to.have.length(1); checkCookieIsCleared(request.cookie(cookies[0])!); - expect(logoutResponse.headers.location).to.be('/security/logged_out'); + expect(logoutResponse.headers.location).to.be('/security/logged_out?msg=LOGGED_OUT'); }); it('should redirect to home page if session cookie is not provided', async () => { diff --git a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts index c1e8bb9938986a..8251ca3419ac89 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts @@ -7,7 +7,7 @@ import request, { Cookie } from 'request'; import { delay } from 'bluebird'; import expect from '@kbn/expect'; -import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/model'; import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts index 59e8c746a6d077..134c9e9b1ad823 100644 --- a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts @@ -7,7 +7,7 @@ import request, { Cookie } from 'request'; import { delay } from 'bluebird'; import expect from '@kbn/expect'; -import type { AuthenticationProvider } from '../../../../plugins/security/common/types'; +import type { AuthenticationProvider } from '../../../../plugins/security/common/model'; import { getSAMLRequestId, getSAMLResponse } from '../../fixtures/saml/saml_tools'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts index 533ce49b143253..eb0cf4a34b2cc2 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_telemetry.ts @@ -157,5 +157,123 @@ export default function ({ getService }: FtrProviderContext) { }); }); }); + describe('when agents are connected with cloned endpoints', () => { + describe('with endpoint integration installed with malware enabled', () => { + before(async () => { + await telemetryTestResources.getArchiveSetCheckIn( + 'cloned_endpoint_installed', + 'cloned_endpoint_test', + 0 + ); + await esArchiver.load('endpoint/telemetry/cloned_endpoint_test'); + await telemetryTestResources.deleteArchive('cloned_endpoint_test'); + }); + it('reports all endpoints and policies', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry).to.eql({ + total_installed: 6, + active_within_last_24_hours: 6, + os: [ + { + full_name: 'Ubuntu bionic(18.04.1 LTS (Bionic Beaver))', + platform: 'ubuntu', + version: '18.04.1 LTS (Bionic Beaver)', + count: 2, + }, + { + full_name: 'Mac OS X(10.14.1)', + platform: 'darwin', + version: '10.14.1', + count: 2, + }, + { + full_name: 'Windows 10 Pro(10.0)', + platform: 'windows', + version: '10.0', + count: 2, + }, + ], + policies: { + malware: { + active: 4, + inactive: 0, + failure: 0, + }, + }, + }); + }); + }); + describe('with endpoint integration installed on half the endpoints with malware enabled', () => { + before(async () => { + await telemetryTestResources.getArchiveSetCheckIn( + 'cloned_endpoint_different_states', + 'cloned_endpoint_test', + 0 + ); + await esArchiver.load('endpoint/telemetry/cloned_endpoint_test'); + await telemetryTestResources.deleteArchive('cloned_endpoint_test'); + }); + it('reports all endpoints and policies', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry).to.eql({ + total_installed: 3, + active_within_last_24_hours: 3, + os: [ + { + full_name: 'Mac OS X(10.14.1)', + platform: 'darwin', + version: '10.14.1', + count: 1, + }, + { + full_name: 'Ubuntu bionic(18.04.1 LTS (Bionic Beaver))', + platform: 'ubuntu', + version: '18.04.1 LTS (Bionic Beaver)', + count: 1, + }, + { + full_name: 'Windows 10 Pro(10.0)', + platform: 'windows', + version: '10.0', + count: 1, + }, + ], + policies: { + malware: { + active: 2, + inactive: 0, + failure: 0, + }, + }, + }); + }); + }); + describe('with endpoint integration uninstalled', () => { + before(async () => { + await telemetryTestResources.getArchiveSetCheckIn( + 'cloned_endpoint_uninstalled', + 'cloned_endpoint_test', + 0 + ); + await esArchiver.load('endpoint/telemetry/cloned_endpoint_test'); + await telemetryTestResources.deleteArchive('cloned_endpoint_test'); + }); + it('reports all endpoints and policies', async () => { + const endpointTelemetry = await telemetryTestResources.getEndpointTelemetry(); + expect(endpointTelemetry).to.eql({ + total_installed: 0, + active_within_last_24_hours: 0, + os: [], + policies: { + malware: { + active: 0, + inactive: 0, + failure: 0, + }, + }, + }); + }); + }); + }); }); }