From 2ed97688da9ed29069cf87fd24f9e03fbbebfdf8 Mon Sep 17 00:00:00 2001 From: cauemarcondes Date: Mon, 28 Jun 2021 09:16:00 -0400 Subject: [PATCH] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 1159e584e06bc125a09d33d401a09765fea96910 Merge: 8011451b6ac df8787b104e Author: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Sat Jun 26 09:54:54 2021 -0400 Merge branch 'master' into apm-tutorial-token commit df8787b104efd9185a15d29bd5682826e5d0ceaa Author: Michael Marcialis Date: Fri Jun 25 20:58:52 2021 -0400 Home & Kibana Overview Page Template Update (#103003) * apply page template comp to kibana overview * apply page template comp to home page * clean up * strip null actions from array * update snapshot and remove outdated import * fix tests and update snapshots * update tests and snapshots * Switch to KibanaPageTemplate; use template=“empty” * update snapshots * make `EuiCard` transparent * updated snapshots * restored data-test-subj="homeApp" * change scope of styles * update snapshots commit c6d04a91952f04b887bd8454a31a96b16ab245b2 Author: Pete Harverson Date: Fri Jun 25 21:57:57 2021 +0100 [ML] Converts management app jobs list pages to new layout (#103117) * [ML] Converts management app jobs list pages to new layout * i[ML] Fix translations * [ML] Set acccessDenied default state back to false * [ML] Remove headers for error states and text updates following review Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit 672653776d0361ddf2fe6c501520bdf42949be00 Author: Pierre Gayvallet Date: Fri Jun 25 20:41:37 2021 +0200 Allow additive csp configuration (#102059) * add additive csp configuration * add unit tests for new class * fix types * adapt test utils * fix tests * more unit tests on config * generated doc * review comments * update ascii doc * update ascii doc links * automatically add single quotes for keywords * add missing csp directives * add more tests * add additional settings to asciidoc * add null-check * revert test config props * fix usage collection usage * some review comments * last review comments * add kibana-docker variables * try to fix doc reference * try to fix doc reference again * fix tests commit d16a464a4cf5e53d1a83440669f143c4fcf56753 Author: Wylie Conlon Date: Fri Jun 25 13:13:57 2021 -0400 [Lens] Document common formulas in product and add formula tutorial (#103154) * [Lens] Document common formulas in product and add formula tutorial * Make common formulas appear in sidebar and format consistently Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit dfc70bdfd19992edda1a8ab832c2224706172a34 Author: Marco Liberati Date: Fri Jun 25 14:59:36 2021 +0200 [Lens] Enable actions on Lens Embeddable (#102038) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit 922d7cc73d6d699df9615e2b6352ca580ebb6aae Author: Patryk Kopyciński Date: Fri Jun 25 11:03:46 2021 +0300 [Osquery] Return proper indices permissions for osquery_manager package (#103363) commit baf2de5415c6c7ad5a29a95ff54f8376896d1b81 Author: Vadim Dalecky Date: Fri Jun 25 07:58:03 2021 +0200 Dashboard locator (#102854) * Add dashboard locator * feat: 🎸 expose dashboard locator from dashboard plugin * Use dashboard locator in dashboard drilldown * Add tests for dashboard locator * Fix dashboard drilldown tests after refactor * Deprecate dashboard URL generator * Fix TypeScript errors in exmaple plugin * Use correct type for dashboard locator * refactor: 💡 change "route" attribute to "path" * chore: 🤖 remove unused bundle Co-authored-by: Vadim Kibana Co-authored-by: Vadim Kibana <82822460+vadimkibana@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit bc6ee27a29c24582d9553813d4d84864749a7cc8 Author: Vadim Dalecky Date: Fri Jun 25 07:55:06 2021 +0200 Maps locators (#102810) * feat: 🎸 implement maps locator * feat: 🎸 add tile map locator * feat: 🎸 add region map locator * feat: 🎸 register maps locators * refactor: 💡 remove usage of mpas url gen, replace by locator * chore: 🤖 remove url generators * refactor: 💡 use locators in maps deprecation messages * chore: 🤖 remove maps url generators * refactor: 💡 use new property name * feat: 🎸 use constant Co-authored-by: Vadim Kibana Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit bc8ba8314a615e1cbf54a48974f8d36c7b38b83b Author: Andrew Kroh Date: Fri Jun 25 00:22:29 2021 -0400 [Fleet] Add support for constant_keyword "value" in package field definitions (#103000) This enables Fleet to accept package field definitions that declare a constant "value" for `constant_keyword` fields. Fleet will generate an index template for the constant_keyword field that contains the `value` attribute. Relates: https://github.com/elastic/package-spec/pull/194 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit 4b20ff3bad9f73a4c08973d03295c2dfba754776 Author: Aaron Caldwell Date: Thu Jun 24 20:25:26 2021 -0600 [Maps] Add capability to delete features from layer & index (#103145) commit 41b015abc90f89d7b0b3466ba4bdbd3d9092a388 Author: Kevin Logan <56395104+kevinlog@users.noreply.github.com> Date: Thu Jun 24 20:23:13 2021 -0400 [Security Solution] Correct linux OS lookup for Endpoint Exceptions (#103038) commit 3838bfd34a68c6e3ef5757441786b0e19c96773d Author: Scotty Bollinger Date: Thu Jun 24 18:53:03 2021 -0500 [Enterprise Search] Add notices for deactivated users and SMTP callout (#103285) * Port #3904 to Kibana https://github.com/elastic/ent-search/pull/3904 * DRY out logic interfaces Should have done this long ago * Port #3920 to Kibana https://github.com/elastic/ent-search/pull/3920 * Lint fixes * Remove error state from form We already did this for the users flyout. Basically changes the dirty state of the form from an error state to just showing “Required”. i18n had not been translated yet for `ATTRIBUTE_VALUE_ERROR` * Add loading states * Remove manual disabling of button Co-authored-by: Constance * Remove manual disabling of other button * Lint fixes Co-authored-by: Constance commit 205684540857cee84be923404c7e7b82939e002e Author: Clint Andrew Hall Date: Thu Jun 24 18:05:11 2021 -0500 [canvas] Reduce bundle size by combining SCSS imports (#102822) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit 803d0fa57ba0cb955b846b52c2d8489cb101312c Author: Constance Date: Thu Jun 24 16:02:48 2021 -0700 [Enterprise Search] Final KibanaPageTemplate cleanup (#103355) * [AS] Delete AppSearchNav and EngineNav * [WS] Delete WorkplaceSearchNav * [Shared] Delete custom Layout & SideNav components commit bd2215f587de0da0613bf1c5a71c71b8efbd868f Author: Luke Elmers Date: Thu Jun 24 16:46:19 2021 -0600 [docs][migrations v2] Update SO migration docs to include removal of index write block when handling corrupt SOs. (#103014) commit c1ced880bc3b6d3cf819576e0001d46380fb7495 Author: Ross Wolf <31489089+rw-access@users.noreply.github.com> Date: Thu Jun 24 15:31:25 2021 -0600 [Detections] Adds automatic updating for Prebuilt Security Detection Rules package (#101846) * Automatically install and update the security_detection_engine package * Remove security_detection_engine from required Fleet packages * Update fleet package-registry image * Add sha256: to the distribution package * Use distribution from https://beats-ci.elastic.co/job/Ingest-manager/job/release-distribution/152 * Change fleet required packag * Fix bad merge * Update rules to 0.13.1 package * Fix NOTICE.txt commit 45b660172ae868c4e6ddce6e3f88c9ba7e374f87 Author: Jonathan Budzenski Date: Thu Jun 24 16:19:01 2021 -0500 skip suite failing es promotion. #103364 commit b12095b079d7836e554ab0b15702574398fe4149 Author: Chris Cowan Date: Thu Jun 24 14:13:15 2021 -0700 [Metrics UI] Prevent saved views from trampling URL state (#103146) * [Metrics UI] Prevent saved views from trampling URL state * Adding space back in commit d5f68eef4f391a2cf9efcf57e258bbf594cf5f0a Author: Wylie Conlon Date: Thu Jun 24 16:46:50 2021 -0400 [Lens] Fix formula formatting in Metric visualization type (#103167) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit 60086a9aac1fe2fbaa4211210bcb9b3446c1f83a Author: Constance Date: Thu Jun 24 13:30:02 2021 -0700 Fix Engine Overview not properly stretching to full page height (#103337) - Caused by the wrapping
around the child views - removing that div and moving the `data-test-subj` hooks to the individual views fixes the issue commit e1ef2ea5cd2d13ee7ff09bfc015dc43ef4120002 Author: Aaron Caldwell Date: Thu Jun 24 14:18:44 2021 -0600 [Maps] Disable edit features if editing already enabled for layer (#103300) commit c0122f70d6576ab7ec7ec4fce698f8fddb92982a Author: Aaron Caldwell Date: Thu Jun 24 14:17:09 2021 -0600 [Maps] Disable draw mode on layer remove (#103188) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit cebf16fb538895e8bd4fbbc787d964392bfe9430 Author: Candace Park <56409205+parkiino@users.noreply.github.com> Date: Thu Jun 24 16:11:16 2021 -0400 [Security Solution][Endpoint][Host Isolation] Remove agent status for non endpoint alerts (#102976) commit 0857e620c7565281290c3de4334a6ae0e04fc8d8 Author: Scotty Bollinger Date: Thu Jun 24 14:59:10 2021 -0500 [Workplace Search] Remove `isFederatedAuth` checks to expose user features (#103278) * Remove isFederated from main app and routes * Expose all overview cards that were hidden for federated auth * Expose all user features that were hidden for groups * Remove remaining isFederatedAuth references * Lint fixes * Add modified test back for Workplace Search * Remove extraCell Co-authored-by: Constance * Remove brackets Co-authored-by: Constance * Update test name Co-authored-by: Constance Co-authored-by: Constance commit fb3e8f4498680d1d3bc52990572e6d0f0c00c7c2 Author: Constance Date: Thu Jun 24 12:43:26 2021 -0700 [Enterprise Search] Product 404 polish pass (#103198) * Refactor NotFound component - shared NotFound becomes NotFoundPrompt - returns only an EuiEmptyPrompt, and individual products/plugins are in charge of their own layout, rather than NotFound doing a bunch of arduous switch handling (also closer to how errorConnecting is a component set per-plugin) - This is both due to the recent page template refactor and the fact that WS has extra complex logic of needing to switch between its kibana layout and personal dashboard layout - logos are still hosted in shared/ since they need extra custom CSS to work correctly sizing wise and in dark mode. I renamed its folder from `assets`->`logos` for extra clarity * [AS] Update current AS routers using NotFound + update EngineRouter to use NotFound * [WS] Update app router - Handle errorConnecting at the topmost level, instead of in WorkplaceSearchConfigured (to simplify various logic/expectations & match App Search) - Simplify isOrganization check to use `useRouteMatch` instead of a regex - Use new NotFound component - Add NotFound component for the personal dashboard router * [WS] Improve Source 404 UX - Add NotFound to SourceRouter + add breadcrumbs for organization views - When an actual source ID 404s, fix blanket redirect to a dashboard aware redirect - personal dashboard 404s should send the user back to personal sources, not organization sources + add a flash message error (similar to how App Search behaves for engine 404s) + harden error status checks (gracefully allow for non-http errors to fall back flashAPIErrors * [WS] Improve Settings 404 UX - This was the only remaining WS route I found that either did not have a 404 or a fallback to some overview page, so I tweaked the redirect order for a graceful redirect (vs a blank page) * Fix settings router test * Move away from custom product logos to OOTB Enterprise Search logo Keeping it simple, etc. RIP in peace fancy logos * [PR feedback] toContain over stringContaining commit 8011451b6ace4710514a355282c5c3a5dbb7d473 Author: cauemarcondes Date: Thu Jun 24 15:38:18 2021 -0400 fixing unit tests commit 9ba1ead7c8fb640edbf80c9dc2f4aaa361b1baf0 Author: Kerry Gallagher <471693+Kerry350@users.noreply.github.com> Date: Thu Jun 24 20:12:52 2021 +0100 [Logs UI] Log threshold rule performance improvements (#102650) * Add optimisations for executor / chart previews Co-authored-by: Felix Stürmer commit 67d4c3184efc3c909ae0e9ca819f3a7429327195 Author: Kuldeep M Date: Thu Jun 24 20:10:22 2021 +0100 [Workplace Search] source connection panel content vertical alignment (#103225) * fix 1786 source connection panel vertical alignment * Update x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/configured_sources_list.tsx Co-authored-by: Constance Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Constance commit fbcf405f156ab99ab40d0de6993479712a24f55b Author: Josh Dover <1813008+joshdover@users.noreply.github.com> Date: Thu Jun 24 20:47:38 2021 +0200 Add telemetry for Elastic Cloud (#102390) commit fb7b596841be2c115cd2ef78d62eeaf5d994aa78 Author: Kyle Pollich Date: Thu Jun 24 14:29:56 2021 -0400 Fix missing setting modal in integrations app (#103317) commit 23c8d181989b69b8178d0c89c757fc9ce0e9df68 Author: Spencer Date: Thu Jun 24 10:59:49 2021 -0700 [ui-shared-deps] reuse react-beautiful-dnd from eui (#102834) Co-authored-by: spalger commit ebf9e7d72d3a7dad02675fe391801dddde266c0b Author: cauemarcondes Date: Thu Jun 24 13:32:37 2021 -0400 removing commented code commit bf6c53bb45a3488000c836a8cba1432f1f2dc2eb Author: Tim Roes Date: Thu Jun 24 19:31:24 2021 +0200 Improved Visualize button in field popover (#103099) * Improve field popover * Slightly improve type safteyness * Add unit tests for visualize trigger utils * Remove unused div Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit cb93335d96b21b82c1b4aa0ec516dcbcbb954063 Author: cauemarcondes Date: Thu Jun 24 13:30:59 2021 -0400 refactoring commit 5abac25ba3ca4fb20dbba49e354da7f1a6285a46 Author: Marco Liberati Date: Thu Jun 24 19:19:20 2021 +0200 [Lens] Update formula icons (#103287) * :lipstick: Updated formula reference icon * :lipstick: Replace wordwrap icons commit 5af69edfbaf58d3874b043c1fc3674080a1b2430 Author: Dmitry Shevchenko Date: Thu Jun 24 19:17:09 2021 +0200 Fix "Deleted rule" badge is not displayed if 'Rule Name' contains more than 55 words (#103164) commit eb8e9d7cc9a36b2a6cf1da3561aed572da6710eb Author: John Schulz Date: Thu Jun 24 12:56:48 2021 -0400 [Fleet] Remove duplication between two files #103282 ## Summary `public/applications/integrations/constants.tsx` and `public/applications/integrations/sections/epm/constants.tsx` are identical except for this line in `public/applications/integrations/constants.tsx` ```ts export * from '../../constants'; ``` This PR removes all the duplication from the "upper" file (`public/applications/integrations/constants.tsx`) and leaves the other code "down" in `/sections/epm/` closer to where it's used. Initially, I deleted `public/applications/integrations/constants.tsx` entirely but several files do `import` the constants it exports, so I left it. commit bfb98053d6b361a18acd58e7f0ab34eb8a43a0ac Author: Janeen Mikell-Straughn <57149392+jmikell821@users.noreply.github.com> Date: Thu Jun 24 12:53:56 2021 -0400 [DOCS] Security Overview (#103151) * updating overview topic for Kibana * formatting fixes * small formatting tweaks * small formatting tweaks * Update index.asciidoc Updating index file; removing siem-UI and machine learning topics from the TOC. * [DOCS] Change part to chapter * Update index.asciidoc * Adding attribute Co-authored-by: lcawl commit 9ead1fc96b8f2938f3b596149dca4b48ed2d1b51 Author: Pete Harverson Date: Thu Jun 24 17:49:56 2021 +0100 [ML] Add description and owner to kibana.json for ML owned plugins (#103254) commit 119845483f695431a153c9ce92a98b5803e7241b Author: Pete Harverson Date: Thu Jun 24 17:44:13 2021 +0100 [ML] Fixes data frame analytics models list pipelines tab (#103235) commit 5e898734d5c5fcd4d1d9cffaf4a02d78b94e6bd7 Author: Bryan Clement Date: Thu Jun 24 09:31:57 2021 -0700 [Asset management] Osquery app bug squashing (#102406) * only display healthy agents to query * updated toasts to clear on update * null checking aggBuckets * properly display expired actions * clear the error toasts on success * review comments Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit dd20b8adb65c2d974ee8c04fe34fd6b3aac36b97 Author: Aleh Zasypkin Date: Thu Jun 24 18:22:14 2021 +0200 Avoid using deprecated camelCase parameters for SAML APIs. (#103091) commit 7e32f934aa3170a0a1197812bd5f2bce65060b2a Author: ymao1 Date: Thu Jun 24 12:20:16 2021 -0400 [Alerting] Using new es client in alerting functional tests (#102349) * Switching to new es client in alerting tests * Fixing types * Updating functional test * Updating functional test * Updating functional test * Fixing error handling * Fixing types * Fixing error handling * Fixing functional tests * Fixing functional tests Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit aefdb9c2b086878f4daa88ec7cc914fdb4f516ae Author: Nathan Reese Date: Thu Jun 24 09:54:38 2021 -0600 [Maps] timeslider play button (#103147) * [Maps] timeslider play button * cancel subscription on unmount * change playback speed to 1750 Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit f2ebcadc7f526c75e87c32dce365acc6539745ab Author: Yulia Čech <6585477+yuliacech@users.noreply.github.com> Date: Thu Jun 24 17:51:05 2021 +0200 Refactored helpers file into separate domain files (#102383) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit 686ac904d913f485e656e5b1f3604bfeb518240d Author: Dmitry Tomashevich <39378793+Dmitriynj@users.noreply.github.com> Date: Thu Jun 24 18:27:29 2021 +0300 [Discover] Move focus on chart toggle in Discover (#103119) * [Discover] move focus on show chart * [Discover] set actual moveFocus flag commit 44613d67324198427b03dfddd7dd4cb1d1e38d37 Merge: 03750ffe838 d3295d3ae9b Author: cauemarcondes Date: Thu Jun 24 11:15:25 2021 -0400 Merge branch 'apm-tutorial-storybook' into apm-tutorial-token commit dd072c392786b8f7c49c16ee48fc95a0aba2be6e Author: Chris Roberson Date: Thu Jun 24 11:09:47 2021 -0400 [Task Manager] Add config switch around logging at different levels based on the state (#102804) * Gate behind a config with warning message that helps users enable * Update more files * Fix docs formatting * Preserve existing functionality * Add in task type to the message * Show multiple alert types that are over the threshold Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> commit 03750ffe838c44709c49f63779c68e9ce010e43c Merge: 05eb303451a 5a76c84dc93 Author: cauemarcondes Date: Thu Jun 24 11:09:42 2021 -0400 Merge branch 'master' of github.com:elastic/kibana into apm-tutorial-token commit d3295d3ae9b19d3c29ca909ba63d9588b7ede0f2 Author: cauemarcondes Date: Wed Jun 23 17:13:56 2021 -0400 adding storybook commit be0ef2e97dc963dd978f7d05bd4d7836aacac968 Merge: 7a5ee5219da 05eb303451a Author: cauemarcondes Date: Wed Jun 23 16:05:42 2021 -0400 Merge branch 'apm-tutorial-token' of github.com:cauemarcondes/kibana into apm-tutorial-storybook commit 7a5ee5219daf95a8d760bea7dcecd84093de815b Author: cauemarcondes Date: Wed Jun 23 16:05:29 2021 -0400 adding storybook commit 05eb303451a2883a763bf37683a5b46b4f058eea Author: cauemarcondes Date: Wed Jun 23 11:11:59 2021 -0400 updating apm int version commit 7baf6a277aa7d266ad0af6e569807f8e3493d4d3 Author: cauemarcondes Date: Wed Jun 23 11:11:03 2021 -0400 moving files commit d22b841f08142c4461b2fa19225f8d167c7293f8 Merge: 8582eb869b8 157d7a426c6 Author: cauemarcondes Date: Wed Jun 23 09:55:41 2021 -0400 Merge branch 'apm-fleet-tutorial' of github.com:cauemarcondes/kibana into apm-tutorial-token commit 157d7a426c6afb2b077b73f0d38f7dc73b79ad11 Author: cauemarcondes Date: Wed Jun 23 09:55:05 2021 -0400 moving tutorial to a common directory commit 8582eb869b8beae83df25d3c901b8c553bfdac5b Author: cauemarcondes Date: Tue Jun 22 16:28:40 2021 -0400 renaming commit 8f38c6efea7fc91c256d5d3a49f27bd129bd421b Merge: 41081da63cf 98f89a110b2 Author: cauemarcondes Date: Tue Jun 22 16:27:03 2021 -0400 Merge branch 'apm-fleet-tutorial' of github.com:cauemarcondes/kibana into apm-tutorial-token commit 98f89a110b250a3bd986275bce899233ec2d0c54 Merge: 75e6e288d87 dec77cfafb0 Author: cauemarcondes Date: Tue Jun 22 16:26:43 2021 -0400 Merge branch 'master' of github.com:elastic/kibana into apm-fleet-tutorial commit 41081da63cf7db2a447b7a8df70069551a68940d Merge: 4c0d2dbdde0 75e6e288d87 Author: cauemarcondes Date: Tue Jun 22 16:24:59 2021 -0400 Merge branch 'apm-fleet-tutorial' of github.com:cauemarcondes/kibana into apm-tutorial-token commit 4c0d2dbdde0d55fb658f9811925ca3d152fba9b9 Author: cauemarcondes Date: Tue Jun 22 16:23:31 2021 -0400 adding help text commit 75e6e288d8756ed5833a333d6aa1724f31a6becd Author: cauemarcondes Date: Tue Jun 22 16:17:05 2021 -0400 fixing TS issue commit 4ef392eb880963fa79bd610860b4e3ef52b2ceac Author: cauemarcondes Date: Tue Jun 22 15:57:58 2021 -0400 fixing TS issue commit 9dc6281613921760cf5681b294c01801d121ea3e Merge: 915754fe1f5 2087a9d7229 Author: cauemarcondes Date: Tue Jun 22 14:50:08 2021 -0400 Merge branch 'apm-fleet-tutorial' of github.com:cauemarcondes/kibana into apm-tutorial-token commit 915754fe1f59c9a60c7a6d8d6e7108974f54fccc Author: cauemarcondes Date: Tue Jun 22 14:44:00 2021 -0400 adding unit test commit 585dcb9bb3872dad40aa7a10de74787bc8a94324 Author: cauemarcondes Date: Tue Jun 22 13:17:43 2021 -0400 refactoring eui component commit 2087a9d722905c5bd74f7c92138478d466c76d48 Author: cauemarcondes Date: Mon Jun 21 16:38:55 2021 -0400 addressing PR comments commit 00d88ffbf44bdfaed88012a35617351491e1868c Author: cauemarcondes Date: Mon Jun 21 15:58:06 2021 -0400 refactoring commit efb9a7fe36806eb8dbb272a3c9b8e6b412268cf7 Merge: 5033edba6ad 496f71316bc Author: cauemarcondes Date: Mon Jun 21 11:23:09 2021 -0400 Merge branch 'apm-fleet-tutorial' of github.com:cauemarcondes/kibana into apm-tutorial-token commit 496f71316bc331264f81b9423a3e82ea933ac4d2 Author: cauemarcondes Date: Mon Jun 21 11:21:44 2021 -0400 adding unit test commit e9a4463478cf42147800c4f5e80af271c0e4b143 Merge: 5dddd48724c e97cfad3854 Author: cauemarcondes Date: Mon Jun 21 10:26:35 2021 -0400 Merge branch 'master' of github.com:elastic/kibana into apm-fleet-tutorial commit 5dddd48724c417914fbb3b34811dbadf1654d6ee Author: cauemarcondes Date: Mon Jun 21 10:26:14 2021 -0400 adding unit test commit 5033edba6ad3682120b70c1bdd7348b68c1a8062 Author: cauemarcondes Date: Fri Jun 18 16:02:23 2021 -0400 adjusting size commit 798503ab84972791d5667a8b24ea56d0270bba7f Author: cauemarcondes Date: Fri Jun 18 15:35:24 2021 -0400 refactoring commit 0225a8c652822d1efbd1a446b1cd8a8b145a510e Author: cauemarcondes Date: Fri Jun 18 11:36:19 2021 -0400 adding environment credencials commit f5c3464adbdd9438fd9bca2c38a3c57a487e80fd Merge: 7e441d25650 036c157f100 Author: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Wed Jun 16 10:19:17 2021 -0400 Merge branch 'master' into apm-fleet-tutorial commit 7e441d25650eb945b645b73641c22cdd65b3a178 Author: cauemarcondes Date: Tue Jun 15 10:38:55 2021 -0400 fixing issues commit c0519532128bae2d1be8cd6b9f1849633f49ed41 Author: cauemarcondes Date: Tue Jun 15 09:59:00 2021 -0400 adding i18n commit 82e61eb851b1b9d90733acbd1956e06034a9f76d Author: cauemarcondes Date: Mon Jun 14 14:56:16 2021 -0400 fixing tests commit 1746467818d60f83ba1c0b9718f2613b60a43183 Author: cauemarcondes Date: Mon Jun 14 12:35:39 2021 -0400 addressing PR comments commit 610aebbdcfaa051858d9f149fc4727f4ed6c7e90 Merge: ba03e537c78 0993a1c3211 Author: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon Jun 14 09:30:43 2021 -0400 Merge branch 'master' into apm-fleet-tutorial commit ba03e537c78df8e090f3d48cac35558e53142d0b Author: cauemarcondes Date: Thu Jun 10 15:33:39 2021 -0400 fixing TS issue commit 7dadd85e0e528f6a644b01f0fe13e7a83821bab6 Author: cauemarcondes Date: Thu Jun 10 15:17:44 2021 -0400 addin custom component registration function commit 35c56722467d79677678a869ab1a94eab0f63e68 Author: cauemarcondes Date: Thu Jun 10 11:02:28 2021 -0400 registering status check callback commit 2c4112c356d84a6a8e168a078e2814e0ed22fd4e Author: cauemarcondes Date: Wed Jun 9 16:36:03 2021 -0400 refactoring commit 831e2c0c1e521cc3fff02ffa7f53ec2ce85f4411 Merge: 71a43d8b748 6b326e8fa4a Author: cauemarcondes Date: Wed Jun 9 16:31:54 2021 -0400 Merge branch 'apm-fleet-tutorial' of github.com:cauemarcondes/kibana into apm-fleet-tutorial commit 71a43d8b74805b20e05dc4b29d9b57c9acd22d0c Merge: 66b9351387c 4e0c889b309 Author: cauemarcondes Date: Wed Jun 9 14:56:24 2021 -0400 Merge branch 'master' of github.com:elastic/kibana into apm-fleet-tutorial commit 6b326e8fa4a9e0b404dfcc7f366d92c2466112ff Merge: 999069730c0 3930749f0ec Author: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Mon Jun 7 08:25:16 2021 -0400 Merge branch 'master' into apm-fleet-tutorial commit 999069730c0e50c25a8484888d4e69a278d2a504 Merge: 66b9351387c 8f83090d74d Author: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Fri Jun 4 09:45:49 2021 -0400 Merge branch 'master' into apm-fleet-tutorial commit 66b9351387c7adf1eb317c19b411961742078825 Author: cauemarcondes Date: Wed Jun 2 14:05:42 2021 -0400 adding fleet information on APM tutorial commit 2f81e724f5ba61cabff374447bdd119f898687c3 Author: cauemarcondes Date: Tue Jun 1 14:33:43 2021 -0400 checks apm fleet integration when pushing button commit 0f0f458a36fe0c5480385d8e8028cc196abb0667 Author: cauemarcondes Date: Tue Jun 1 10:44:11 2021 -0400 adding fleet information on APM tutorial commit 2a00cc76a8a487da161555aaf0b9176177751c77 Author: cauemarcondes Date: Mon May 31 15:48:00 2021 -0400 fixing i18n commit e854f34b079bfd9f91541ec760dd6429b344b35f Author: cauemarcondes Date: Mon May 31 15:41:03 2021 -0400 adding fleet typing commit 23695bc1575f99281cf09c30675f2fb34a3e4cac Merge: 07f91b519b4 af4b8e66263 Author: cauemarcondes Date: Mon May 31 15:18:44 2021 -0400 Merge branch 'master' of github.com:elastic/kibana into apm-fleet-tutorial commit 07f91b519b4b563aedb46d1013a8465761aeefed Author: cauemarcondes Date: Mon May 31 15:12:00 2021 -0400 adding fleet information on APM tutorial commit 3f484a78179db0f869320fab54702b1896391dfd Author: cauemarcondes Date: Thu May 27 14:51:32 2021 -0400 adding fleet section commit 92d34b144d421109eba80fe7060e61de68f0c370 Merge: e38e390ae07 1ceecd39580 Author: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu May 27 13:06:08 2021 -0400 Merge branch 'master' into apm-moving-tutorial commit e38e390ae075ed23689d4695745d13c3787a9025 Author: cauemarcondes Date: Thu May 27 10:45:58 2021 -0400 fixing i18n commit 7195da0ccd5e723b97894afa1fd7c9ec568bd0e8 Author: cauemarcondes Date: Thu May 27 09:29:54 2021 -0400 removing export commit 3afb5807e0a0cba0867935e6a6114420704806a3 Author: cauemarcondes Date: Wed May 26 14:28:45 2021 -0400 removing tutorial from apm_oss commit 25fec3fec6a4694449ca1ec15a9a12783c15498d Author: cauemarcondes Date: Wed May 26 13:46:46 2021 -0400 using files from apm commit 515c1c96d9ea0eddc2ce212596cd7f0986aa0830 Author: cauemarcondes Date: Wed May 26 13:16:37 2021 -0400 Register tutorial on APM plugin --- NOTICE.txt | 22 +- ...plugin-core-server.cspconfig.__private_.md | 11 + .../kibana-plugin-core-server.cspconfig.md | 1 + ...kibana-plugin-plugins-embeddable-public.md | 1 + ...-embeddable-public.useembeddablefactory.md | 22 + docs/settings/task-manager-settings.asciidoc | 8 +- docs/setup/settings.asciidoc | 51 +- .../setup/upgrade/upgrade-migrations.asciidoc | 49 +- docs/siem/index.asciidoc | 198 +- docs/user/dashboard/lens-advanced.asciidoc | 43 +- package.json | 9 +- packages/kbn-ui-shared-deps/src/entry.js | 1 + packages/kbn-ui-shared-deps/src/index.js | 2 + src/core/server/csp/config.test.ts | 460 ++++- src/core/server/csp/config.ts | 154 +- src/core/server/csp/csp_config.test.ts | 109 +- src/core/server/csp/csp_config.ts | 28 +- src/core/server/csp/csp_directives.test.ts | 266 +++ src/core/server/csp/csp_directives.ts | 159 ++ .../http/cookie_session_storage.test.ts | 6 +- src/core/server/http/http_config.test.ts | 5 +- .../lifecycle_handlers.test.ts | 6 +- src/core/server/http/test_utils.ts | 6 +- src/core/server/server.api.md | 6 +- .../resources/base/bin/kibana-docker | 14 +- src/plugins/dashboard/public/index.ts | 3 + src/plugins/dashboard/public/locator.test.ts | 323 ++++ src/plugins/dashboard/public/locator.ts | 160 ++ src/plugins/dashboard/public/plugin.tsx | 36 +- src/plugins/dashboard/public/url_generator.ts | 6 + .../main/components/chart/discover_chart.tsx | 23 +- ...iscover_field_details_footer.test.tsx.snap | 705 ------- .../components/sidebar/discover_field.tsx | 37 +- .../sidebar/discover_field_details.scss | 10 - .../sidebar/discover_field_details.test.tsx | 41 +- .../sidebar/discover_field_details.tsx | 100 +- .../discover_field_details_footer.test.tsx | 71 - .../sidebar/discover_field_details_footer.tsx | 59 - .../sidebar/discover_field_visualize.tsx | 66 + .../components/sidebar/lib/get_warnings.ts | 33 - .../lib/visualize_trigger_utils.test.ts | 116 ++ .../sidebar/lib/visualize_trigger_utils.ts | 84 +- src/plugins/embeddable/public/index.ts | 1 + .../embeddables/embeddable_renderer.test.tsx | 27 +- .../lib/embeddables/embeddable_renderer.tsx | 154 +- .../public/lib/embeddables/index.ts | 6 +- .../lib/panel/embeddable_panel.test.tsx | 37 + .../public/lib/panel/embeddable_panel.tsx | 83 +- .../lib/panel/panel_header/panel_header.tsx | 6 +- src/plugins/embeddable/public/public.api.md | 5 + .../__snapshots__/home.test.js.snap | 1344 ++++++-------- .../public/application/components/_home.scss | 16 - .../components/_solutions_section.scss | 4 - .../public/application/components/home.js | 99 +- .../application/components/home.test.js | 2 +- .../instruction_set.test.js.snap | 24 + .../__snapshots__/tutorial.test.js.snap | 3 + .../components/tutorial/instruction.js | 23 +- .../components/tutorial/instruction_set.js | 3 + .../tutorial/instruction_set.test.js | 6 + .../components/tutorial/tutorial.js | 1 + .../public/components/_overview.scss | 22 +- .../__snapshots__/add_data.test.tsx.snap | 2 +- .../public/components/add_data/add_data.tsx | 2 +- .../getting_started.test.tsx.snap | 24 +- .../getting_started/getting_started.tsx | 2 +- .../__snapshots__/overview.test.tsx.snap | 1641 +++++++---------- .../components/overview/overview.test.tsx | 2 +- .../public/components/overview/overview.tsx | 261 +-- .../public/overview_page/index.ts | 2 +- .../overview_page_actions.test.tsx.snap | 101 + .../index.ts | 2 +- .../overview_page_actions.test.tsx | 51 + .../overview_page_actions.tsx | 88 + .../overview_page_header.test.tsx.snap | 69 - .../_overview_page_header.scss | 14 - .../overview_page_header/index.scss | 1 - .../overview_page_header.test.tsx | 38 - .../overview_page_header.tsx | 141 -- .../collectors/csp/csp_collector.test.ts | 16 +- .../public/get_deprecation_message.tsx | 32 +- .../public/get_deprecation_message.tsx | 30 +- .../embedded_lens_example/kibana.json | 1 + .../embedded_lens_example/public/app.tsx | 3 + .../ui_actions_enhanced_examples/kibana.json | 3 +- .../app1_to_dashboard_drilldown.ts | 9 +- .../app2_to_dashboard_drilldown.ts | 9 +- x-pack/plugins/apm/public/plugin.ts | 10 + .../tutorial/config_agent/commands/django.ts | 72 + .../tutorial/config_agent/commands/dotnet.ts | 14 + .../tutorial/config_agent/commands/flask.ts | 70 + .../commands/get_commands.test.ts | 536 ++++++ .../config_agent/commands/get_commands.ts | 48 + .../tutorial/config_agent/commands/go.ts | 58 + .../tutorial/config_agent/commands/java.ts | 14 + .../tutorial/config_agent/commands/node.ts | 57 + .../tutorial/config_agent/commands/php.ts} | 19 +- .../tutorial/config_agent/commands/rack.ts | 50 + .../tutorial/config_agent/commands/rails.ts | 21 + .../tutorial/config_agent/commands/rum.ts | 58 + .../config_agent/config_agent.stories.tsx | 119 ++ .../tutorial/config_agent/copy_commands.tsx | 26 + .../config_agent/get_policy_options.test.ts | 336 ++++ .../config_agent/get_policy_options.ts | 67 + .../tutorial/config_agent/index.test.tsx | 205 ++ .../public/tutorial/config_agent/index.tsx | 144 ++ .../tutorial/config_agent/policy_selector.tsx | 93 + .../tutorial/config_agent/rum_script.tsx | 33 + .../tutorial_fleet_instructions.stories.tsx | 48 + .../apm/server/lib/fleet/get_agents.ts | 34 + x-pack/plugins/apm/server/routes/fleet.ts | 69 +- .../instructions/apm_agent_instructions.ts | 361 +--- .../components/var_config/delete_var.tsx | 2 - .../public/components/var_config/edit_var.tsx | 3 - .../components/var_config/var_config.tsx | 2 - x-pack/plugins/canvas/public/style/index.scss | 7 + .../canvas/public/transitions/fade/index.ts | 2 - .../canvas/public/transitions/rotate/index.ts | 2 - .../canvas/public/transitions/slide/index.ts | 2 - .../canvas/public/transitions/zoom/index.ts | 2 - x-pack/plugins/cloud/public/fullstory.ts | 100 + .../plugins/cloud/public/plugin.test.mocks.ts | 13 + x-pack/plugins/cloud/public/plugin.test.ts | 164 +- x-pack/plugins/cloud/public/plugin.ts | 79 +- .../cloud/server/assets/fullstory_library.js | 13 + x-pack/plugins/cloud/server/config.test.ts | 6 +- x-pack/plugins/cloud/server/plugin.ts | 8 + .../cloud/server/routes/fullstory.test.ts | 62 + .../plugins/cloud/server/routes/fullstory.ts | 75 + .../abstract_dashboard_drilldown.tsx | 28 +- ...embeddable_to_dashboard_drilldown.test.tsx | 37 +- .../embeddable_to_dashboard_drilldown.tsx | 23 +- x-pack/plugins/data_visualizer/kibana.json | 7 +- .../common/__mocks__/initial_app_data.ts | 1 - .../enterprise_search/common/types/index.ts | 1 - .../components/analytics/analytics_router.tsx | 8 +- .../components/engine/engine_nav.test.tsx | 184 +- .../components/engine/engine_nav.tsx | 235 +-- .../components/engine/engine_router.tsx | 6 +- .../app_search/components/engine/index.ts | 1 - .../engine_overview/engine_overview.test.tsx | 5 - .../engine_overview/engine_overview.tsx | 6 +- .../engine_overview/engine_overview_empty.tsx | 1 + .../engine_overview_metrics.tsx | 1 + .../app_search/components/not_found/index.ts | 8 + .../components/not_found/not_found.test.tsx | 38 + .../components/not_found/not_found.tsx | 23 + .../components/role_mappings/role_mapping.tsx | 2 + .../role_mappings/role_mappings_logic.test.ts | 3 + .../role_mappings/role_mappings_logic.ts | 86 +- .../components/role_mappings/user.test.tsx | 24 +- .../components/role_mappings/user.tsx | 11 + .../applications/app_search/index.test.tsx | 70 +- .../public/applications/app_search/index.tsx | 50 +- .../applications/shared/layout/index.ts | 4 - .../applications/shared/layout/layout.scss | 96 - .../shared/layout/layout.test.tsx | 77 - .../applications/shared/layout/layout.tsx | 82 - .../applications/shared/layout/side_nav.scss | 87 - .../shared/layout/side_nav.test.tsx | 166 -- .../applications/shared/layout/side_nav.tsx | 130 -- .../shared/layout/side_nav_bg.svg | 25 - .../not_found/assets/app_search_logo.tsx | 33 - .../assets/workplace_search_logo.tsx | 39 - .../applications/shared/not_found/index.ts | 2 +- .../shared/not_found/not_found.test.tsx | 70 - .../shared/not_found/not_found.tsx | 117 -- .../not_found/not_found_prompt.test.tsx | 51 + .../shared/not_found/not_found_prompt.tsx | 65 + .../__mocks__/elasticsearch_users.ts | 1 + .../role_mapping/attribute_selector.test.tsx | 11 +- .../role_mapping/attribute_selector.tsx | 5 +- .../shared/role_mapping/constants.ts | 40 +- .../deactivated_user_callout.test.tsx | 75 + .../role_mapping/deactivated_user_callout.tsx | 28 + .../applications/shared/role_mapping/index.ts | 2 + .../role_mapping/role_mapping_flyout.test.tsx | 1 + .../role_mapping/role_mapping_flyout.tsx | 3 + .../applications/shared/role_mapping/types.ts | 66 + .../shared/role_mapping/user_flyout.test.tsx | 1 + .../shared/role_mapping/user_flyout.tsx | 4 +- .../role_mapping/user_selector.test.tsx | 3 +- .../shared/role_mapping/user_selector.tsx | 18 +- .../shared/role_mapping/users_table.test.tsx | 20 +- .../shared/role_mapping/users_table.tsx | 10 +- .../public/applications/shared/types.ts | 3 +- .../workplace_search/app_logic.test.ts | 3 - .../workplace_search/app_logic.ts | 12 +- .../components/layout/index.ts | 2 +- .../components/layout/nav.test.tsx | 21 +- .../components/layout/nav.tsx | 41 +- .../workplace_search/index.test.tsx | 38 +- .../applications/workplace_search/index.tsx | 38 +- .../applications/workplace_search/routes.ts | 1 - .../add_source/configured_sources_list.tsx | 7 +- .../content_sources/source_logic.test.ts | 80 +- .../views/content_sources/source_logic.ts | 12 +- .../content_sources/source_router.test.tsx | 4 +- .../views/content_sources/source_router.tsx | 6 +- .../groups/components/group_overview.tsx | 7 +- .../groups/components/group_row.test.tsx | 7 - .../views/groups/components/group_row.tsx | 24 +- .../components/group_users_table.test.tsx | 11 +- .../groups/components/group_users_table.tsx | 11 +- .../groups/components/groups_table.test.tsx | 10 +- .../views/groups/components/groups_table.tsx | 4 +- .../groups/components/table_filters.test.tsx | 9 +- .../views/groups/components/table_filters.tsx | 10 +- .../views/groups/groups.test.tsx | 20 +- .../workplace_search/views/groups/groups.tsx | 11 +- .../workplace_search/views/not_found/index.ts | 8 + .../views/not_found/not_found.test.tsx | 52 + .../views/not_found/not_found.tsx | 33 + .../overview/__mocks__/overview_logic.mock.ts | 2 +- .../views/overview/onboarding_steps.test.tsx | 19 +- .../views/overview/onboarding_steps.tsx | 40 +- .../overview/organization_stats.test.tsx | 9 - .../views/overview/organization_stats.tsx | 43 +- .../views/role_mappings/role_mapping.tsx | 2 + .../role_mappings/role_mappings_logic.test.ts | 3 + .../role_mappings/role_mappings_logic.ts | 84 +- .../views/role_mappings/user.test.tsx | 24 +- .../views/role_mappings/user.tsx | 11 + .../views/settings/settings_router.test.tsx | 4 +- .../views/settings/settings_router.tsx | 5 +- .../lib/enterprise_search_config_api.test.ts | 1 - .../lib/enterprise_search_config_api.ts | 1 - x-pack/plugins/file_upload/kibana.json | 7 +- .../public/applications/integrations/app.tsx | 30 +- .../applications/integrations/constants.tsx | 53 - .../detail/assets/assets_accordion.tsx | 2 +- x-pack/plugins/fleet/server/mocks/index.ts | 1 + x-pack/plugins/fleet/server/plugin.ts | 1 + .../__snapshots__/template.test.ts.snap | 4 + .../elasticsearch/template/template.test.ts | 20 + .../epm/elasticsearch/template/template.ts | 6 + .../fields/__snapshots__/field.test.ts.snap | 5 + .../fleet/server/services/epm/fields/field.ts | 1 + .../server/services/epm/fields/tests/base.yml | 3 + x-pack/plugins/fleet/server/services/index.ts | 1 + ...kage_policies_to_agent_permissions.test.ts | 104 ++ .../package_policies_to_agent_permissions.ts | 7 + .../edit_policy/edit_policy.helpers.tsx | 96 - .../edit_policy/features/cold_phase.test.ts | 42 - .../features/delete_phase.helpers.ts | 36 + .../edit_policy/features/delete_phase.test.ts | 59 +- .../edit_policy/features/frozen_phase.test.ts | 18 +- .../general_behavior.helpers.ts | 8 +- .../features/request_flyout.test.ts | 7 +- .../edit_policy/features/rollover.helpers.ts | 51 + .../edit_policy/features/rollover.test.ts | 17 +- .../features/searchable_snapshots.helpers.ts | 59 + .../features/searchable_snapshots.test.ts | 25 +- .../edit_policy/features/timeline.helpers.ts | 29 + .../edit_policy/features/timeline.test.ts | 6 +- .../edit_policy/features/timing.helpers.ts | 36 + .../edit_policy/features/timing.test.ts | 41 + .../edit_policy/features/warm_phase.test.ts | 42 - .../cold_phase_validation.test.ts | 6 +- .../form_validation/error_indicators.test.ts | 6 +- .../hot_phase_validation.test.ts | 10 +- .../policy_name_validation.test.ts | 10 +- .../form_validation/timing.test.ts | 8 +- .../form_validation/validation.helpers.ts | 47 + .../warm_phase_validation.test.ts | 8 +- .../policy_serialization.helpers.ts | 45 + .../policy_serialization.test.ts | 25 +- .../helpers/actions/errors_actions.ts | 10 +- .../helpers/actions/forcemerge_actions.ts | 2 +- ...ts => form_toggle_and_set_value_action.ts} | 15 +- .../helpers/actions/index.ts | 12 +- .../helpers/actions/index_priority_actions.ts | 8 +- .../helpers/actions/phases.ts | 76 + .../helpers/actions/replicas_action.ts | 20 + .../helpers/actions/rollover_actions.ts | 16 +- .../actions/set_wait_for_snapshot_action.ts | 19 - .../helpers/actions/shrink_actions.ts | 10 +- .../actions/snapshot_policy_actions.ts | 27 + .../alerting/logs/log_threshold/types.ts | 108 +- .../http_api/log_alerts/chart_preview_data.ts | 9 + .../criterion_preview_chart.tsx | 13 +- .../components/expression_editor/editor.tsx | 25 + .../containers/saved_view/saved_view.tsx | 13 +- .../log_threshold_chart_preview.ts | 54 +- .../log_threshold_executor.test.ts | 455 ++--- .../log_threshold/log_threshold_executor.ts | 154 +- x-pack/plugins/lens/kibana.json | 1 + .../embeddable/embeddable_component.tsx | 76 +- x-pack/plugins/lens/public/index.ts | 2 +- .../formula/editor/formula_editor.tsx | 7 +- .../formula/editor/formula_help.tsx | 98 +- .../metric_visualization/expression.test.tsx | 98 +- .../metric_visualization/expression.tsx | 2 +- x-pack/plugins/lens/public/plugin.ts | 4 +- x-pack/plugins/maps/common/constants.ts | 1 + .../maps/public/actions/layer_actions.ts | 6 + .../maps/public/actions/map_actions.ts | 19 + .../layers/vector_layer/vector_layer.tsx | 6 + .../es_search_source/es_search_source.tsx | 7 +- .../es_search_source/util/feature_edit.ts | 10 + .../mvt_single_layer_vector_source.tsx | 4 + .../sources/vector_source/vector_source.tsx | 5 + .../classes/util/mb_filter_expressions.ts | 2 +- .../map_container/map_container.tsx | 4 +- .../mb_map/draw_control/draw_control.tsx | 14 + .../draw_feature_control.tsx | 57 +- .../draw_feature_control/index.ts | 9 +- .../mb_map/draw_control/draw_tooltip.tsx | 4 + .../__snapshots__/toc_entry.test.tsx.snap | 5 + .../layer_toc/toc_entry/toc_entry.tsx | 1 + .../toc_entry_actions_popover.test.tsx.snap | 115 ++ .../toc_entry_actions_popover.test.tsx | 14 + .../toc_entry_actions_popover.tsx | 3 +- .../timeslider/timeslider.tsx | 60 + .../feature_edit_tools/feature_edit_tools.tsx | 19 + .../maps/public/embeddable/map_embeddable.tsx | 2 + ...url_generator.test.ts => locators.test.ts} | 85 +- x-pack/plugins/maps/public/locators.ts | 260 +++ x-pack/plugins/maps/public/plugin.ts | 36 +- .../routes/map_page/map_app/map_app.tsx | 1 + .../map_app/wait_until_time_layers_load.ts | 7 +- .../visualize_geo_field_action.ts | 35 +- x-pack/plugins/maps/public/url_generator.ts | 241 --- .../server/data_indexing/indexing_routes.ts | 53 + x-pack/plugins/ml/kibana.json | 7 +- .../public/application/access_denied/page.tsx | 63 +- .../models_management/expanded_row.tsx | 2 +- .../components/access_denied_page.tsx | 80 +- .../components/insufficient_license_page.tsx | 104 +- .../jobs_list_page/jobs_list_page.tsx | 106 +- .../action_results/action_results_summary.tsx | 58 +- .../action_results/use_action_results.ts | 14 +- .../public/actions/use_action_details.ts | 10 +- .../osquery/public/actions/use_all_actions.ts | 10 +- .../agent_policies/use_agent_policies.ts | 10 +- .../public/agent_policies/use_agent_policy.ts | 10 +- .../osquery/public/agents/use_agent_groups.ts | 10 +- .../public/agents/use_agent_policies.ts | 10 +- .../osquery/public/agents/use_agent_status.ts | 10 +- .../osquery/public/agents/use_all_agents.ts | 25 +- .../public/agents/use_osquery_policies.ts | 10 +- .../public/common/hooks/use_error_toast.tsx | 26 + .../common/hooks/use_osquery_integration.tsx | 9 +- .../public/live_queries/form/index.tsx | 28 +- .../osquery/public/queries/edit/tabs.tsx | 11 +- .../osquery/public/results/use_all_results.ts | 10 +- .../routes/live_queries/details/index.tsx | 23 +- .../active_state_switch.tsx | 5 +- .../scheduled_query_groups/form/index.tsx | 5 +- .../osquery/server/lib/parse_agent_groups.ts | 4 +- .../authentication/providers/saml.test.ts | 8 +- .../server/authentication/providers/saml.ts | 4 +- .../public/app/home/index.tsx | 4 +- .../event_details/alert_summary_view.tsx | 11 +- .../exceptionable_endpoint_fields.json | 20 - .../exceptionable_windows_mac_fields.json | 18 +- .../components/exceptions/helpers.test.tsx | 11 - .../common/components/exceptions/helpers.tsx | 99 +- .../__snapshots__/index.test.tsx.snap | 4 +- .../__snapshots__/title.test.tsx.snap | 16 +- .../common/components/header_page/index.tsx | 12 +- .../common/components/header_page/title.tsx | 23 +- .../common/components/header_page/types.ts | 2 + ...de.ts => use_upgrade_security_packages.ts} | 20 +- .../common/utils/endpoint_alert_check.test.ts | 31 + .../common/utils/endpoint_alert_check.ts | 14 + .../detection_engine/rules/details/index.tsx | 22 +- .../network/pages/details/index.test.tsx | 2 +- .../side_panel/event_details/index.tsx | 4 +- .../timeline/body/renderers/constants.tsx | 2 +- .../body/renderers/formatted_field.tsx | 4 +- .../apm_403_response_to_a_post.json | 2 +- .../apm_405_response_method_not_allowed.json | 2 +- .../apm_null_user_agent.json | 2 +- .../apm_sqlmap_user_agent.json | 2 +- ...tion_added_to_google_workspace_domain.json | 2 +- ...tempt_to_deactivate_okta_network_zone.json | 2 +- .../attempt_to_delete_okta_network_zone.json | 2 +- ...collection_cloudtrail_logging_created.json | 2 +- ...ion_gcp_pub_sub_subscription_creation.json | 2 +- ...collection_gcp_pub_sub_topic_creation.json | 2 +- ...llection_microsoft_365_new_inbox_rule.json | 2 +- ...collection_update_event_hub_auth_rule.json | 2 +- ...d_control_certutil_network_connection.json | 7 +- ...mand_and_control_cobalt_strike_beacon.json | 2 +- ...cobalt_strike_default_teamserver_cert.json | 2 +- ..._control_dns_directly_to_the_internet.json | 7 +- ...nd_and_control_dns_tunneling_nslookup.json | 2 +- ...download_rar_powershell_from_internet.json | 7 +- .../command_and_control_fin7_c2_behavior.json | 2 +- .../command_and_control_halfbaked_beacon.json | 2 +- ...d_control_nat_traversal_port_activity.json | 2 +- .../command_and_control_port_26_activity.json | 2 +- ...te_desktop_protocol_from_the_internet.json | 7 +- ...mand_and_control_telnet_port_activity.json | 2 +- ...l_network_computing_from_the_internet.json | 7 +- ...ual_network_computing_to_the_internet.json | 7 +- ...l_access_attempted_bypass_of_okta_mfa.json | 2 +- ...mpts_to_brute_force_okta_user_account.json | 10 +- ...ccess_aws_iam_assume_role_brute_force.json | 2 +- ...ial_access_collection_sensitive_files.json | 2 +- ...dential_access_dumping_hashes_bi_cmds.json | 2 +- ...ial_access_iam_user_addition_to_group.json | 2 +- .../credential_access_kerberosdump_kcc.json | 2 +- .../credential_access_key_vault_modified.json | 2 +- ..._365_brute_force_user_account_attempt.json | 4 +- ...65_potential_password_spraying_attack.json | 2 +- ...ential_access_mitm_localhost_webproxy.json | 2 +- ...okta_brute_force_or_password_spraying.json | 2 +- ...ntial_access_potential_ssh_bruteforce.json | 2 +- ...cess_root_console_failure_brute_force.json | 2 +- ..._access_secretsmanager_getsecretvalue.json | 2 +- ...ccess_storage_account_key_regenerated.json | 2 +- .../credential_access_systemkey_dumping.json | 2 +- .../defense_evasion_amsienable_key_mod.json | 57 + ...vasion_apple_softupdates_modification.json | 2 +- ...evasion_attempt_to_disable_gatekeeper.json | 2 +- ...tempt_to_disable_iptables_or_firewall.json | 2 +- ...ion_attempt_to_disable_syslog_service.json | 2 +- ...e_application_credential_modification.json | 2 +- ...on_azure_diagnostic_settings_deletion.json | 2 +- ...sion_azure_service_principal_addition.json | 2 +- ..._base32_encoding_or_decoding_activity.json | 2 +- ...vasion_clearing_windows_security_logs.json | 2 +- ...se_evasion_cloudtrail_logging_deleted.json | 2 +- ..._evasion_cloudtrail_logging_suspended.json | 2 +- ...nse_evasion_cloudwatch_alarm_deletion.json | 2 +- ..._evasion_config_service_rule_deletion.json | 2 +- ...vasion_configuration_recorder_stopped.json | 2 +- .../defense_evasion_cve_2020_0601.json | 2 +- ...fense_evasion_disable_selinux_attempt.json | 2 +- ...defense_evasion_ec2_flow_log_deletion.json | 2 +- ...ense_evasion_ec2_network_acl_deletion.json | 2 +- .../defense_evasion_event_hub_deletion.json | 2 +- ...fense_evasion_file_deletion_via_shred.json | 2 +- ...defense_evasion_file_mod_writable_dir.json | 2 +- ...ense_evasion_firewall_policy_deletion.json | 2 +- ...nse_evasion_gcp_firewall_rule_created.json | 2 +- ...nse_evasion_gcp_firewall_rule_deleted.json | 2 +- ...se_evasion_gcp_firewall_rule_modified.json | 2 +- ...e_evasion_gcp_logging_bucket_deletion.json | 2 +- ...nse_evasion_gcp_logging_sink_deletion.json | 2 +- ...ion_gcp_pub_sub_subscription_deletion.json | 2 +- ...se_evasion_gcp_pub_sub_topic_deletion.json | 2 +- ...storage_bucket_configuration_modified.json | 2 +- ...p_storage_bucket_permissions_modified.json | 2 +- ...e_evasion_guardduty_detector_deletion.json | 2 +- .../defense_evasion_hidden_file_dir_tmp.json | 2 +- .../defense_evasion_injection_msbuild.json | 2 +- ...ense_evasion_install_root_certificate.json | 2 +- ...defense_evasion_kernel_module_removal.json | 2 +- ...osoft_365_exchange_dlp_policy_removed.json | 2 +- ...change_malware_filter_policy_deletion.json | 2 +- ..._365_exchange_malware_filter_rule_mod.json | 2 +- ...65_exchange_safe_attach_rule_disabled.json | 2 +- ...isc_lolbin_connecting_to_the_internet.json | 7 +- ..._evasion_modify_environment_launchctl.json | 2 +- .../defense_evasion_msxsl_network.json | 7 +- ...ense_evasion_network_watcher_deletion.json | 2 +- ...sion_s3_bucket_configuration_deletion.json | 2 +- .../defense_evasion_safari_config_change.json | 2 +- ...dboxed_office_app_suspicious_zip_file.json | 2 +- ...vasion_stop_process_service_threshold.json | 2 +- ...picious_execution_from_mounted_device.json | 89 + ...ser_password_reset_or_unlock_attempts.json | 12 +- ...vasion_tcc_bypass_mounted_apfs_access.json | 2 +- ..._evasion_unload_endpointsecurity_kext.json | 2 +- ...nusual_network_connection_via_dllhost.json | 51 + ...usual_network_connection_via_rundll32.json | 7 +- .../defense_evasion_waf_acl_deletion.json | 2 +- ...asion_waf_rule_or_rule_group_deletion.json | 2 +- .../discovery_blob_container_access_mod.json | 2 +- .../discovery_kernel_module_enumeration.json | 2 +- ...covery_virtual_machine_fingerprinting.json | 2 +- ...d_to_google_workspace_trusted_domains.json | 2 +- .../elastic_endpoint_security.json | 2 +- .../endgame_adversary_behavior_detected.json | 2 +- .../endgame_cred_dumping_detected.json | 2 +- .../endgame_cred_dumping_prevented.json | 2 +- .../endgame_cred_manipulation_detected.json | 2 +- .../endgame_cred_manipulation_prevented.json | 2 +- .../endgame_exploit_detected.json | 2 +- .../endgame_exploit_prevented.json | 2 +- .../endgame_malware_detected.json | 2 +- .../endgame_malware_prevented.json | 2 +- .../endgame_permission_theft_detected.json | 2 +- .../endgame_permission_theft_prevented.json | 2 +- .../endgame_process_injection_detected.json | 2 +- .../endgame_process_injection_prevented.json | 2 +- .../endgame_ransomware_detected.json | 2 +- .../endgame_ransomware_prevented.json | 2 +- ...and_prompt_connecting_to_the_internet.json | 7 +- .../execution_command_virtual_machine.json | 2 +- ...vasion_electron_app_childproc_node_js.json | 2 +- ...le_program_connecting_to_the_internet.json | 7 +- ...ution_installer_spawned_network_event.json | 7 +- ...on_pentest_eggshell_remote_admin_tool.json | 2 +- .../execution_perl_tty_shell.json | 2 +- .../execution_python_tty_shell.json | 2 +- ...er_program_connecting_to_the_internet.json | 7 +- ...ing_osascript_exec_followed_by_netcon.json | 7 +- ...ltration_ec2_snapshot_change_activity.json | 2 +- .../exfiltration_ec2_vm_export_failure.json | 70 + ...tration_gcp_logging_sink_modification.json | 2 +- ..._365_exchange_transport_rule_creation.json | 2 +- ...osoft_365_exchange_transport_rule_mod.json | 2 +- .../prepackaged_rules/external_alerts.json | 2 +- .../google_workspace_admin_role_deletion.json | 2 +- ...le_workspace_mfa_enforcement_disabled.json | 2 +- .../google_workspace_policy_modified.json | 2 +- ...pact_attempt_to_revoke_okta_api_token.json | 2 +- ...pact_azure_automation_runbook_deleted.json | 2 +- .../impact_cloudtrail_logging_updated.json | 2 +- .../impact_cloudwatch_log_group_deletion.json | 2 +- ...impact_cloudwatch_log_stream_deletion.json | 2 +- .../impact_ec2_disable_ebs_encryption.json | 2 +- .../impact_gcp_iam_role_deletion.json | 2 +- .../impact_gcp_service_account_deleted.json | 2 +- .../impact_gcp_service_account_disabled.json | 2 +- .../impact_gcp_storage_bucket_deleted.json | 2 +- ...virtual_private_cloud_network_deleted.json | 2 +- ...p_virtual_private_cloud_route_created.json | 2 +- ...p_virtual_private_cloud_route_deleted.json | 2 +- .../impact_iam_deactivate_mfa_device.json | 2 +- .../impact_iam_group_deletion.json | 2 +- .../impact_possible_okta_dos_attack.json | 2 +- .../impact_rds_cluster_deletion.json | 2 +- .../impact_rds_instance_cluster_stoppage.json | 2 +- .../impact_resource_group_deletion.json | 2 +- .../rules/prepackaged_rules/index.ts | 10 +- ...ure_active_directory_high_risk_signin.json | 2 +- ...re_active_directory_powershell_signin.json | 2 +- ...tack_via_azure_registered_application.json | 2 +- .../initial_access_console_login_root.json | 2 +- ...ial_access_external_guest_user_invite.json | 2 +- ...l_access_gcp_iam_custom_role_creation.json | 2 +- .../initial_access_login_failures.json | 2 +- .../initial_access_login_location.json | 2 +- .../initial_access_login_sessions.json | 2 +- .../initial_access_login_time.json | 2 +- ...5_exchange_anti_phish_policy_deletion.json | 2 +- ...soft_365_exchange_anti_phish_rule_mod.json | 2 +- ...osoft_365_exchange_safelinks_disabled.json | 2 +- .../initial_access_password_recovery.json | 2 +- ...mote_procedure_call_from_the_internet.json | 7 +- ...remote_procedure_call_to_the_internet.json | 7 +- ...file_sharing_activity_to_the_internet.json | 2 +- ...icious_activity_reported_by_okta_user.json | 2 +- ...al_access_unsecure_elasticsearch_node.json | 2 +- .../initial_access_via_system_manager.json | 2 +- ..._access_zoom_meeting_with_no_passcode.json | 2 +- ...ential_access_kerberos_bifrostconsole.json | 2 +- .../lateral_movement_dns_server_overflow.json | 2 +- ...ral_movement_remote_ssh_login_enabled.json | 2 +- ...ment_telnet_network_activity_external.json | 7 +- ...ment_telnet_network_activity_internal.json | 7 +- .../linux_hping_activity.json | 2 +- .../linux_iodine_activity.json | 2 +- .../linux_nping_activity.json | 2 +- ...nux_process_started_in_temp_directory.json | 2 +- .../linux_strace_activity.json | 2 +- ...led_for_google_workspace_organization.json | 2 +- ...exchange_dkim_signing_config_disabled.json | 2 +- ..._teams_custom_app_interaction_allowed.json | 2 +- .../ml_high_count_network_denies.json | 6 +- .../ml_high_count_network_events.json | 6 +- .../ml_linux_anomalous_metadata_process.json | 7 +- .../ml_linux_anomalous_metadata_user.json | 7 +- ...linux_anomalous_network_port_activity.json | 7 +- .../ml_linux_anomalous_process_all_hosts.json | 7 +- .../ml_linux_anomalous_user_name.json | 7 +- .../ml_rare_destination_country.json | 6 +- .../ml_rare_process_by_host_linux.json | 7 +- .../ml_rare_process_by_host_windows.json | 7 +- .../ml_spike_in_traffic_to_a_country.json | 6 +- ...ml_windows_anomalous_metadata_process.json | 7 +- .../ml_windows_anomalous_metadata_user.json | 7 +- ...ml_windows_anomalous_network_activity.json | 7 +- .../ml_windows_anomalous_path_activity.json | 7 +- ...l_windows_anomalous_process_all_hosts.json | 7 +- ...ml_windows_anomalous_process_creation.json | 7 +- .../ml_windows_anomalous_user_name.json | 7 +- .../rules/prepackaged_rules/notice.ts | 18 +- ...ttempt_to_deactivate_okta_application.json | 2 +- ...kta_attempt_to_deactivate_okta_policy.json | 2 +- ...ttempt_to_deactivate_okta_policy_rule.json | 2 +- ...ta_attempt_to_delete_okta_application.json | 2 +- .../okta_attempt_to_delete_okta_policy.json | 2 +- ...ta_attempt_to_delete_okta_policy_rule.json | 2 +- ...ta_attempt_to_modify_okta_application.json | 2 +- ...a_attempt_to_modify_okta_network_zone.json | 2 +- .../okta_attempt_to_modify_okta_policy.json | 2 +- ...ta_attempt_to_modify_okta_policy_rule.json | 2 +- ..._or_delete_application_sign_on_policy.json | 2 +- ...threat_detected_by_okta_threatinsight.json | 2 +- ...stence_account_creation_hide_at_logon.json | 2 +- ...tor_privileges_assigned_to_okta_group.json | 2 +- ...inistrator_role_assigned_to_okta_user.json | 2 +- ...ence_attempt_to_create_okta_api_token.json | 2 +- ..._deactivate_mfa_for_okta_user_account.json | 2 +- ...set_mfa_factors_for_okta_user_account.json | 2 +- ...ence_azure_automation_account_created.json | 2 +- ...utomation_runbook_created_or_modified.json | 2 +- ...ence_azure_automation_webhook_created.json | 2 +- ...re_conditional_access_policy_modified.json | 2 +- ...nce_azure_pim_user_added_global_admin.json | 2 +- ...ged_identity_management_role_modified.json | 2 +- ..._access_authorization_plugin_creation.json | 2 +- ...l_access_modify_auth_module_or_config.json | 2 +- ...credential_access_modify_ssh_binaries.json | 2 +- ...launch_agent_deamon_logonitem_process.json | 2 +- ...rectory_services_plugins_modification.json | 2 +- ...e_docker_shortcuts_plist_modification.json | 2 +- .../persistence_ec2_network_acl_creation.json | 2 +- .../persistence_enable_root_account.json | 2 +- ..._gcp_iam_service_account_key_deletion.json | 2 +- ...e_gcp_key_created_for_service_account.json | 2 +- ...rsistence_gcp_service_account_created.json | 2 +- ...workspace_admin_role_assigned_to_user.json | 2 +- ...a_domain_wide_delegation_of_authority.json | 2 +- ...e_workspace_custom_admin_role_created.json | 2 +- ...stence_google_workspace_role_modified.json | 2 +- .../persistence_iam_group_creation.json | 2 +- ...stence_loginwindow_plist_modification.json | 2 +- ...rsistence_mfa_disabled_for_azure_user.json | 2 +- ...5_exchange_management_role_assignment.json | 2 +- ...oft_365_teams_external_access_enabled.json | 2 +- ...rosoft_365_teams_guest_access_enabled.json | 2 +- ...ersistence_periodic_tasks_file_mdofiy.json | 2 +- .../persistence_rds_cluster_creation.json | 2 +- ...sistence_shell_activity_by_web_server.json | 2 +- ...ersistence_shell_profile_modification.json | 2 +- ...ence_ssh_authorized_keys_modification.json | 2 +- ...ence_suspicious_calendar_modification.json | 2 +- ...stence_suspicious_com_hijack_registry.json | 4 +- ..._added_as_owner_for_azure_application.json | 2 +- ..._as_owner_for_azure_service_principal.json | 2 +- ...tence_via_atom_init_file_modification.json | 2 +- ...lege_escalation_echo_nopasswd_sudoers.json | 2 +- ...calation_explicit_creds_via_scripting.json | 2 +- ...alation_exploit_adobe_acrobat_updater.json | 2 +- ...lation_ld_preload_shared_object_modif.json | 2 +- ..._escalation_local_user_added_to_admin.json | 2 +- ...ge_escalation_persistence_phantom_dll.json | 4 +- ...ilege_escalation_root_crontab_filemod.json | 2 +- ...ege_escalation_root_login_without_mfa.json | 2 +- ...ation_setuid_setgid_bit_set_via_chmod.json | 2 +- ...ilege_escalation_sudo_buffer_overflow.json | 2 +- ...privilege_escalation_sudoers_file_mod.json | 2 +- ...ege_escalation_updateassumerolepolicy.json | 2 +- .../threat_intel_module_match.json | 2 +- .../task_manager/server/config.test.ts | 15 +- x-pack/plugins/task_manager/server/config.ts | 9 +- .../managed_configuration.test.ts | 5 +- .../server/lib/log_health_metrics.test.ts | 191 +- .../server/lib/log_health_metrics.ts | 73 +- .../configuration_statistics.test.ts | 5 +- .../monitoring_stats_stream.test.ts | 5 +- .../server/monitoring/task_run_statistics.ts | 3 + .../task_manager/server/plugin.test.ts | 10 +- .../server/polling_lifecycle.test.ts | 5 +- .../task_manager/server/routes/health.test.ts | 15 +- x-pack/plugins/transform/kibana.json | 7 +- .../translations/translations/ja-JP.json | 8 +- .../translations/translations/zh-CN.json | 8 +- .../public/custom_time_range_action.tsx | 4 +- .../common/lib/es_test_index_tool.ts | 11 +- .../common/lib/index.ts | 2 +- .../common/lib/task_manager_utils.ts | 20 +- .../actions/builtin_action_types/es_index.ts | 4 +- .../es_index_preconfigured.ts | 4 +- .../tests/actions/execute.ts | 18 +- .../tests/alerting/alerts.ts | 126 +- .../tests/alerting/create.ts | 13 +- .../tests/alerting/delete.ts | 12 +- .../tests/alerting/disable.ts | 12 +- .../tests/alerting/enable.ts | 21 +- .../tests/alerting/health.ts | 2 +- .../tests/alerting/rbac_legacy.ts | 12 +- .../actions/builtin_action_types/es_index.ts | 4 +- .../preconfigured_alert_history_connector.ts | 12 +- .../spaces_only/tests/actions/enqueue.ts | 7 +- .../spaces_only/tests/actions/execute.ts | 10 +- .../spaces_only/tests/alerting/alerts_base.ts | 26 +- .../builtin_alert_types/es_query/alert.ts | 2 +- .../es_query/create_test_data.ts | 2 +- .../index_threshold/alert.ts | 2 +- .../index_threshold/create_test_data.ts | 2 +- .../index_threshold/fields_endpoint.ts | 2 +- .../index_threshold/indices_endpoint.ts | 2 +- .../time_series_query_endpoint.ts | 2 +- .../spaces_only/tests/alerting/create.ts | 13 +- .../spaces_only/tests/alerting/delete.ts | 6 +- .../spaces_only/tests/alerting/disable.ts | 6 +- .../spaces_only/tests/alerting/enable.ts | 13 +- .../index_lifecycle_management/policies.js | 3 +- .../apis/maps/delete_feature.js | 42 + .../test/api_integration/apis/maps/index.js | 1 + x-pack/test/cloud_integration/config.ts | 87 + x-pack/test/cloud_integration/constants.ts | 8 + .../fixtures/saml/saml_provider/kibana.json | 7 + .../fixtures/saml/saml_provider/metadata.xml | 41 + .../saml/saml_provider/server/index.ts | 15 + .../saml/saml_provider/server/init_routes.ts | 58 + .../saml/saml_provider/server/saml_tools.ts | 161 ++ .../ftr_provider_context.d.ts | 13 + .../test/cloud_integration/tests/fullstory.ts | 66 + .../fleet_api_integration/apis/epm/delete.ts | 2 +- x-pack/test/fleet_api_integration/config.ts | 3 +- .../es_archives/maps/data/data.json | 36 + .../es_archives/maps/data/mappings.json | 28 + .../event_log/service_api_integration.ts | 4 +- yarn.lock | 5 + 713 files changed, 12684 insertions(+), 8010 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.cspconfig.__private_.md create mode 100644 docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.useembeddablefactory.md create mode 100644 src/core/server/csp/csp_directives.test.ts create mode 100644 src/core/server/csp/csp_directives.ts create mode 100644 src/plugins/dashboard/public/locator.test.ts create mode 100644 src/plugins/dashboard/public/locator.ts delete mode 100644 src/plugins/discover/public/application/apps/main/components/sidebar/__snapshots__/discover_field_details_footer.test.tsx.snap delete mode 100644 src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details.scss delete mode 100644 src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details_footer.test.tsx delete mode 100644 src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details_footer.tsx create mode 100644 src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_visualize.tsx delete mode 100644 src/plugins/discover/public/application/apps/main/components/sidebar/lib/get_warnings.ts create mode 100644 src/plugins/discover/public/application/apps/main/components/sidebar/lib/visualize_trigger_utils.test.ts create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_actions/__snapshots__/overview_page_actions.test.tsx.snap rename src/plugins/kibana_react/public/overview_page/{overview_page_header => overview_page_actions}/index.ts (89%) create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_actions/overview_page_actions.test.tsx create mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_actions/overview_page_actions.tsx delete mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/__snapshots__/overview_page_header.test.tsx.snap delete mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/_overview_page_header.scss delete mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/index.scss delete mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/overview_page_header.test.tsx delete mode 100644 src/plugins/kibana_react/public/overview_page/overview_page_header/overview_page_header.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/django.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/dotnet.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/flask.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/get_commands.test.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/get_commands.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/go.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/java.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/node.ts rename x-pack/plugins/{enterprise_search/public/applications/shared/not_found/assets/logo.scss => apm/public/tutorial/config_agent/commands/php.ts} (53%) create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/rack.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/rails.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/commands/rum.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/config_agent.stories.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/copy_commands.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.test.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/get_policy_options.ts create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/index.test.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/index.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/policy_selector.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/config_agent/rum_script.tsx create mode 100644 x-pack/plugins/apm/public/tutorial/tutorial_fleet_instructions/tutorial_fleet_instructions.stories.tsx create mode 100644 x-pack/plugins/apm/server/lib/fleet/get_agents.ts create mode 100644 x-pack/plugins/cloud/public/fullstory.ts create mode 100644 x-pack/plugins/cloud/public/plugin.test.mocks.ts create mode 100644 x-pack/plugins/cloud/server/assets/fullstory_library.js create mode 100644 x-pack/plugins/cloud/server/routes/fullstory.test.ts create mode 100644 x-pack/plugins/cloud/server/routes/fullstory.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/app_search/components/not_found/not_found.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.scss delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/layout.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.scss delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/layout/side_nav_bg.svg delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/app_search_logo.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/assets/workplace_search_logo.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.test.tsx delete mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/not_found/not_found_prompt.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/deactivated_user_callout.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/shared/role_mapping/types.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/index.ts create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.test.tsx create mode 100644 x-pack/plugins/enterprise_search/public/applications/workplace_search/views/not_found/not_found.tsx delete mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx delete mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/cold_phase.test.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/delete_phase.helpers.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/rollover.helpers.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/searchable_snapshots.helpers.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timeline.helpers.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timing.helpers.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/timing.test.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/features/warm_phase.test.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/form_validation/validation.helpers.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/serialization/policy_serialization.helpers.ts rename x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/{set_replicas_action.ts => form_toggle_and_set_value_action.ts} (57%) create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/phases.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/replicas_action.ts delete mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/set_wait_for_snapshot_action.ts create mode 100644 x-pack/plugins/index_lifecycle_management/__jest__/client_integration/helpers/actions/snapshot_policy_actions.ts rename x-pack/plugins/maps/public/{url_generator.test.ts => locators.test.ts} (52%) create mode 100644 x-pack/plugins/maps/public/locators.ts delete mode 100644 x-pack/plugins/maps/public/url_generator.ts create mode 100644 x-pack/plugins/osquery/public/common/hooks/use_error_toast.tsx rename x-pack/plugins/security_solution/public/common/hooks/{endpoint/upgrade.ts => use_upgrade_security_packages.ts} (80%) create mode 100644 x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.test.ts create mode 100644 x-pack/plugins/security_solution/public/common/utils/endpoint_alert_check.ts create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_amsienable_key_mod.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_suspicious_execution_from_mounted_device.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/defense_evasion_unusual_network_connection_via_dllhost.json create mode 100644 x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules/exfiltration_ec2_vm_export_failure.json create mode 100644 x-pack/test/api_integration/apis/maps/delete_feature.js create mode 100644 x-pack/test/cloud_integration/config.ts create mode 100644 x-pack/test/cloud_integration/constants.ts create mode 100644 x-pack/test/cloud_integration/fixtures/saml/saml_provider/kibana.json create mode 100644 x-pack/test/cloud_integration/fixtures/saml/saml_provider/metadata.xml create mode 100644 x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/index.ts create mode 100644 x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/init_routes.ts create mode 100644 x-pack/test/cloud_integration/fixtures/saml/saml_provider/server/saml_tools.ts create mode 100644 x-pack/test/cloud_integration/ftr_provider_context.d.ts create mode 100644 x-pack/test/cloud_integration/tests/fullstory.ts diff --git a/NOTICE.txt b/NOTICE.txt index 4eec329b7a6033..4ede43610ca7b3 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -149,17 +149,17 @@ SOFTWARE. --- Detection Rules -Copyright 2020 Elasticsearch B.V. +Copyright 2021 Elasticsearch B.V. --- This product bundles rules based on https://github.com/BlueTeamLabs/sentinel-attack -which is available under a "MIT" license. The files based on this license are: +which is available under a "MIT" license. The rules based on this license are: -- defense_evasion_via_filter_manager -- discovery_process_discovery_via_tasklist_command -- persistence_priv_escalation_via_accessibility_features -- persistence_via_application_shimming -- defense_evasion_execution_via_trusted_developer_utilities +- "Potential Evasion via Filter Manager" (06dceabf-adca-48af-ac79-ffdf4c3b1e9a) +- "Process Discovery via Tasklist" (cc16f774-59f9-462d-8b98-d27ccd4519ec) +- "Potential Modification of Accessibility Binaries" (7405ddf1-6c8e-41ce-818f-48bea6bcaed8) +- "Potential Application Shimming via Sdbinst" (fd4a992d-6130-4802-9ff8-829b89ae801f) +- "Trusted Developer Application Usage" (9d110cb3-5f4b-4c9a-b9f5-53f0a1707ae1) MIT License @@ -185,9 +185,9 @@ SOFTWARE. --- This product bundles rules based on https://github.com/FSecureLABS/leonidas -which is available under a "MIT" license. The files based on this license are: +which is available under a "MIT" license. The rules based on this license are: -- credential_access_secretsmanager_getsecretvalue.toml +- "AWS Access Secret in Secrets Manager" (a00681e3-9ed6-447c-ab2c-be648821c622) MIT License @@ -235,6 +235,10 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +--- +Portions of this code are licensed under the following license: +For license information please see https://edge.fullstory.com/s/fs.js.LICENSE.txt + --- This product bundles bootstrap@3.3.6 which is available under a "MIT" license. diff --git a/docs/development/core/server/kibana-plugin-core-server.cspconfig.__private_.md b/docs/development/core/server/kibana-plugin-core-server.cspconfig.__private_.md new file mode 100644 index 00000000000000..217066481d33c1 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.cspconfig.__private_.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [CspConfig](./kibana-plugin-core-server.cspconfig.md) > ["\#private"](./kibana-plugin-core-server.cspconfig.__private_.md) + +## CspConfig."\#private" property + +Signature: + +```typescript +#private; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.cspconfig.md b/docs/development/core/server/kibana-plugin-core-server.cspconfig.md index 9f4f3211ea2b1c..0337a1f4d33014 100644 --- a/docs/development/core/server/kibana-plugin-core-server.cspconfig.md +++ b/docs/development/core/server/kibana-plugin-core-server.cspconfig.md @@ -20,6 +20,7 @@ The constructor for this class is marked as internal. Third-party code should no | Property | Modifiers | Type | Description | | --- | --- | --- | --- | +| ["\#private"](./kibana-plugin-core-server.cspconfig.__private_.md) | | | | | [DEFAULT](./kibana-plugin-core-server.cspconfig.default.md) | static | CspConfig | | | [disableEmbedding](./kibana-plugin-core-server.cspconfig.disableembedding.md) | | boolean | | | [header](./kibana-plugin-core-server.cspconfig.header.md) | | string | | diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md index b875b1fce42887..444132024596e6 100644 --- a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.md @@ -36,6 +36,7 @@ | [isSavedObjectEmbeddableInput(input)](./kibana-plugin-plugins-embeddable-public.issavedobjectembeddableinput.md) | | | [openAddPanelFlyout(options)](./kibana-plugin-plugins-embeddable-public.openaddpanelflyout.md) | | | [plugin(initializerContext)](./kibana-plugin-plugins-embeddable-public.plugin.md) | | +| [useEmbeddableFactory({ input, factory, onInputUpdated, })](./kibana-plugin-plugins-embeddable-public.useembeddablefactory.md) | | ## Interfaces diff --git a/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.useembeddablefactory.md b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.useembeddablefactory.md new file mode 100644 index 00000000000000..9af20cacc2cee4 --- /dev/null +++ b/docs/development/plugins/embeddable/public/kibana-plugin-plugins-embeddable-public.useembeddablefactory.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-embeddable-public](./kibana-plugin-plugins-embeddable-public.md) > [useEmbeddableFactory](./kibana-plugin-plugins-embeddable-public.useembeddablefactory.md) + +## useEmbeddableFactory() function + +Signature: + +```typescript +export declare function useEmbeddableFactory({ input, factory, onInputUpdated, }: EmbeddableRendererWithFactory): readonly [ErrorEmbeddable | IEmbeddable | undefined, boolean, string | undefined]; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| { input, factory, onInputUpdated, } | EmbeddableRendererWithFactory<I> | | + +Returns: + +`readonly [ErrorEmbeddable | IEmbeddable | undefined, boolean, string | undefined]` + diff --git a/docs/settings/task-manager-settings.asciidoc b/docs/settings/task-manager-settings.asciidoc index 87f5b700870ebf..7f4dbb3a96e6b0 100644 --- a/docs/settings/task-manager-settings.asciidoc +++ b/docs/settings/task-manager-settings.asciidoc @@ -29,7 +29,13 @@ Task Manager runs background tasks by polling for work on an interval. You can | The maximum number of tasks that this Kibana instance will run simultaneously. Defaults to 10. Starting in 8.0, it will not be possible to set the value greater than 100. - | `xpack.task_manager.monitored_stats_warn_delayed_task_start_in_seconds` + | `xpack.task_manager.` + `monitored_stats_health_verbose_log.enabled` + | This flag will enable automatic warn and error logging if task manager self detects a performance issue, such as the time between when a task is scheduled to execute and when it actually executes. Defaults to false. + + | `xpack.task_manager.` + `monitored_stats_health_verbose_log.` + `warn_delayed_task_start_in_seconds` | The amount of seconds we allow a task to delay before printing a warning server log. Defaults to 60. |=== diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index c3c29adcea18f9..bcaa86d73adc4d 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -36,11 +36,57 @@ Set to `false` to disable Console. *Default: `true`* <>. | `csp.rules:` - | A https://w3c.github.io/webappsec-csp/[content-security-policy] template + | deprecated:[7.14.0,"In 8.0 and later, this setting will no longer be supported."] +A https://w3c.github.io/webappsec-csp/[Content Security Policy] template that disables certain unnecessary and potentially insecure capabilities in the browser. It is strongly recommended that you keep the default CSP rules that ship with {kib}. +| `csp.script_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src[Content Security Policy `script-src` directive]. + +| `csp.worker_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/worker-src[Content Security Policy `worker-src` directive]. + +| `csp.style_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src[Content Security Policy `style-src` directive]. + +| `csp.connect_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/connect-src[Content Security Policy `connect-src` directive]. + +| `csp.default_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src[Content Security Policy `default-src` directive]. + +| `csp.font_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/font-src[Content Security Policy `font-src` directive]. + +| `csp.frame_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-src[Content Security Policy `frame-src` directive]. + +| `csp.img_src:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src[Content Security Policy `img-src` directive]. + +| `csp.frame_ancestors:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors[Content Security Policy `frame-ancestors` directive]. + +|=== + +[NOTE] +============ +The `frame-ancestors` directive can also be configured by using +<>. In that case, that takes precedence and any values in `csp.frame_ancestors` +are ignored. +============ + +[cols="2*<"] +|=== + +| `csp.report_uri:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri[Content Security Policy `report-uri` directive]. + +| `csp.report_to:` +| Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-to[Content Security Policy `report-to` directive]. + |[[csp-strict]] `csp.strict:` | Blocks {kib} access to any browser that does not enforce even rudimentary CSP rules. In practice, this disables @@ -538,8 +584,7 @@ a|`server.securityResponseHeaders:` is used in all responses to the client from the {kib} server, and specifies what value is used. Allowed values are any text value or `null`. To disable, set to `null`. *Default:* `null` -[[server-securityResponseHeaders-disableEmbedding]] -a|`server.securityResponseHeaders:` +|[[server-securityResponseHeaders-disableEmbedding]]`server.securityResponseHeaders:` `disableEmbedding:` | Controls whether the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy[`Content-Security-Policy`] and https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options[`X-Frame-Options`] headers are configured to disable embedding diff --git a/docs/setup/upgrade/upgrade-migrations.asciidoc b/docs/setup/upgrade/upgrade-migrations.asciidoc index fdcd71791ad3ab..947043b21ef501 100644 --- a/docs/setup/upgrade/upgrade-migrations.asciidoc +++ b/docs/setup/upgrade/upgrade-migrations.asciidoc @@ -55,22 +55,55 @@ This section highlights common causes of {kib} upgrade failures and how to preve There is a known issue in v7.12.0 for users who tried the fleet beta. Upgrade migrations fail because of a large number of documents in the `.kibana` index. This can cause Kibana to log errors like: -> Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [receive_timeout_transport_exception]: [instance-0000000002][10.32.1.112:19541][cluster:monitor/task/get] request_id [2648] timed out after [59940ms] -> Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [timeout_exception]: Timed out waiting for completion of [org.elasticsearch.index.reindex.BulkByScrollTask@6a74c54] + +[source,sh] +-------------------------------------------- +Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [receive_timeout_transport_exception]: [instance-0000000002][10.32.1.112:19541][cluster:monitor/task/get] request_id [2648] timed out after [59940ms] + +Error: Unable to complete saved object migrations for the [.kibana] index. Please check the health of your Elasticsearch cluster and try again. Error: [timeout_exception]: Timed out waiting for completion of [org.elasticsearch.index.reindex.BulkByScrollTask@6a74c54] +-------------------------------------------- See https://github.com/elastic/kibana/issues/95321 for instructions to work around this issue. [float] ===== Corrupt saved objects -We highly recommend testing your {kib} upgrade in a development cluster to discover and remedy problems caused by corrupt documents, especially when there are custom integrations creating saved objects in your environment. Saved objects that were corrupted through manual editing or integrations will cause migration failures with a log message like `Failed to transform document. Transform: index-pattern:7.0.0\n Doc: {...}` or `Unable to migrate the corrupt Saved Object document ...`. Corrupt documents will have to be fixed or deleted before an upgrade migration can succeed. +We highly recommend testing your {kib} upgrade in a development cluster to discover and remedy problems caused by corrupt documents, especially when there are custom integrations creating saved objects in your environment. + +Saved objects that were corrupted through manual editing or integrations will cause migration failures with a log message like `Failed to transform document. Transform: index-pattern:7.0.0\n Doc: {...}` or `Unable to migrate the corrupt Saved Object document ...`. Corrupt documents will have to be fixed or deleted before an upgrade migration can succeed. For example, given the following error message: -> Unable to migrate the corrupt saved object document with _id: 'marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275'. To allow migrations to proceed, please delete this document from the [.kibana_7.12.0_001] index. -The following steps must be followed to allow the upgrade migration to succeed. -Please be aware the Dashboard having ID `e3c5fc71-ac71-4805-bcab-2bcc9cc93275` belonging to the space `marketing_space` will no more be available: -1. Delete the corrupt document with `DELETE .kibana_7.12.0_001/_doc/marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275` -2. Restart {kib} +[source,sh] +-------------------------------------------- +Unable to migrate the corrupt saved object document with _id: 'marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275'. To allow migrations to proceed, please delete this document from the [.kibana_7.12.0_001] index. +-------------------------------------------- + +The following steps must be followed to delete the document that is causing the migration to fail: + +. Remove the write block which the migration system has placed on the previous index: ++ +[source,sh] +-------------------------------------------- +PUT .kibana_7.12.1_001/_settings +{ + "index": { + "blocks.write": false + } +} +-------------------------------------------- + +. Delete the corrupt document: ++ +[source,sh] +-------------------------------------------- +DELETE .kibana_7.12.0_001/_doc/marketing_space:dashboard:e3c5fc71-ac71-4805-bcab-2bcc9cc93275 +-------------------------------------------- + +. Restart {kib}. + +In this example, the Dashboard with ID `e3c5fc71-ac71-4805-bcab-2bcc9cc93275` that belongs to the space `marketing_space` **will no longer be available**. + +Be sure you have a snapshot before you delete the corrupt document. If restoring from a snapshot is not an option, it is recommended to also delete the `temp` and `target` indices the migration created before restarting {kib} and retrying. [float] ===== User defined index templates that causes new `.kibana*` indices to have incompatible settings or mappings diff --git a/docs/siem/index.asciidoc b/docs/siem/index.asciidoc index 18895f0533fd71..05b1ec0b5b7978 100644 --- a/docs/siem/index.asciidoc +++ b/docs/siem/index.asciidoc @@ -1,60 +1,164 @@ +[chapter] [role="xpack"] [[xpack-siem]] -= Elastic Security += Elastic Security overview +++++ +Security +++++ -[partintro] --- +https://www.elastic.co/security[Elastic Security] combines SIEM threat detection features with endpoint +prevention and response capabilities in one solution. These analytical and +protection capabilities, leveraged by the speed and extensibility of +Elasticsearch, enable analysts to defend their organization from threats before +damage and loss occur. -Elastic Security combines SIEM threat detection features with endpoint -prevention and response capabilities in one solution, including: +Elastic Security provides the following security benefits and capabilities: -* A detection engine to identify attacks and system misconfiguration +* A detection engine to identify attacks and system misconfigurations * A workspace for event triage and investigations * Interactive visualizations to investigate process relationships -* Embedded case management and automated actions -* Detection of signatureless attacks with prebuilt {ml} anomaly jobs and -detection rules +* Inbuilt case management with automated actions +* Detection of signatureless attacks with prebuilt machine learning anomaly jobs +and detection rules -[role="screenshot"] -image::siem/images/overview-ui.png[Elastic Security in Kibana] - -[float] -== Add data - -Kibana provides step-by-step instructions to help you add data. The -{security-guide}[Security Guide] is a good source for more -detailed information and instructions. - -[float] -=== {Beats} - -https://www.elastic.co/products/beats/auditbeat[{auditbeat}], -https://www.elastic.co/products/beats/filebeat[{filebeat}], -https://www.elastic.co/products/beats/winlogbeat[{winlogbeat}], and -https://www.elastic.co/products/beats/packetbeat[{packetbeat}] -send security events and other data to Elasticsearch. +[discrete] +== Elastic Security components and workflow -The default index patterns for Elastic Security events are `auditbeat-*`, `winlogbeat-*`, -`filebeat-*`, `packetbeat-*`, `endgame-*`, `logs-*`, and `apm-*-transaction*`. To change the default pattern patterns, go to *Stack Management > Advanced Settings > securitySolution:defaultIndex*. +The following diagram provides a comprehensive illustration of the Elastic Security workflow. -[float] -=== Elastic Security endpoint agent - -The agent detects and protects against malware, and ships host and network -events directly to Elastic Security. - -[float] -=== Elastic Common Schema (ECS) for normalizing data - -The {ecs-ref}[Elastic Common Schema (ECS)] defines a common set of fields to be -used for storing event data in Elasticsearch. ECS helps users normalize their -event data to better analyze, visualize, and correlate the data represented in -their events. - -Elastic Security can ingest and normalize events from ECS-compatible data sources. +[role="screenshot"] +image::../siem/images/workflow.png[] + +Here's an overview of the flow and its components: + +* Data is shipped from your hosts to {es} via beat modules and the Elastic https://www.elastic.co/endpoint-security/[Endpoint Security agent integration]. This integration provides capabilities such as collecting events, detecting and preventing {security-guide}/detection-engine-overview.html#malware-prevention[malicious activity], and artifact delivery. The {fleet-guide}/fleet-overview.html[{fleet}] app is used to +install and manage agents and integrations on your hosts. ++ +The Endpoint Security integration ships the following data sets: ++ +*** *Windows*: Process, network, file, DNS, registry, DLL and driver loads, +malware security detections +*** *Linux/macOS*: Process, network, file ++ +* https://www.elastic.co/integrations?solution=security[Beat modules]: {beats} +are lightweight data shippers. Beat modules provide a way of collecting and +parsing specific data sets from common sources, such as cloud and OS events, +logs, and metrics. Common security-related modules are listed {security-guide}/ingest-data.html#enable-beat-modules[here]. +* The {security-app} in {kib} is used to manage the *Detection engine*, +*Cases*, and *Timeline*, as well as administer hosts running Endpoint Security: +** Detection engine: Automatically searches for suspicious host and network +activity via the following: +*** {security-guide}/detection-engine-overview.html#detection-engine-overview[Detection rules]: Periodically search the data +({es} indices) sent from your hosts for suspicious events. When a suspicious +event is discovered, a detection alert is generated. External systems, such as +Slack and email, can be used to send notifications when alerts are generated. +You can create your own rules and make use of our {security-guide}/prebuilt-rules.html[prebuilt ones]. +*** {security-guide}/detections-ui-exceptions.html[Exceptions]: Reduce noise and the number of +false positives. Exceptions are associated with rules and prevent alerts when +an exception's conditions are met. *Value lists* contain source event +values that can be used as part of an exception's conditions. When +Elastic {endpoint-sec} is installed on your hosts, you can add malware exceptions +directly to the endpoint from the Security app. +*** {security-guide}/machine-learning.html#included-jobs[{ml-cap} jobs]: Automatic anomaly detection of host and +network events. Anomaly scores are provided per host and can be used with +detection rules. +** {security-guide}/timelines-ui.html[Timeline]: Workspace for investigating alerts and events. +Timelines use queries and filters to drill down into events related to +a specific incident. Timeline templates are attached to rules and use predefined +queries when alerts are investigated. Timelines can be saved and shared with +others, as well as attached to Cases. +** {security-guide}/cases-overview.html[Cases]: An internal system for opening, tracking, and sharing +security issues directly in the Security app. Cases can be integrated with +external ticketing systems. +** {security-guide}/admin-page-ov.html[Administration]: View and manage hosts running {endpoint-sec}. + +{security-guide}/ingest-data.html[Ingest data to Elastic Security] and {security-guide}/install-endpoint.html[Configure and install the Elastic Endpoint integration] describe how to ship security-related +data to {es}. + + +For more background information, see: + +* https://www.elastic.co/products/elasticsearch[{es}]: A real-time, +distributed storage, search, and analytics engine. {es} excels at indexing +streams of semi-structured data, such as logs or metrics. +* https://www.elastic.co/products/kibana[{kib}]: An open-source analytics and +visualization platform designed to work with {es}. You use {kib} to search, +view, and interact with data stored in {es} indices. You can easily compile +advanced data analysis and visualize your data in a variety of charts, tables, +and maps. + +[discrete] +=== Compatibility with cold tier nodes + +Cold tier is a {ref}/data-tiers.html[data tier] that holds time-series data that is accessed only occasionally. In {stack} version >=7.11.0, {elastic-sec} supports cold tier data for the following {es} indices: + +* Index patterns specified in `securitySolution:defaultIndex` +* Index patterns specified in the definitions of detection rules, except for indicator match rules +* Index patterns specified in the data sources selector on various {security-app} pages + +{elastic-sec} does NOT support cold tier data for the following {es} indices: + +* Index patterns controlled by {elastic-sec}, including signals and list indices +* Index patterns specified in indicator match rules + +Using cold tier data for unsupported indices may result in detection rule timeouts and overall performance degradation. + +[discrete] +[[self-protection]] +==== Elastic Endpoint self-protection + +Self-protection means that {elastic-endpoint} has guards against users and attackers that may try to interfere with its functionality. This protection feature is consistently enhanced to prevent attackers who may attempt to use newer, more sophisticated tactics to interfere with the {elastic-endpoint}. Self-protection is enabled by default when {elastic-endpoint} installs on supported platforms, listed below. + +Self-protection is enabled on the following 64-bit Windows versions: + +* Windows 8.1 +* Windows 10 +* Windows Server 2012 R2 +* Windows Server 2016 +* Windows Server 2019 + +And on the following macOS versions: + +* macOS 10.15 (Catalina) +* macOS 11 (Big Sur) + +NOTE: Other Windows and macOS variants (and all Linux distributions) do not have self-protection. + +For {stack} version >= 7.11.0, self-protection defines the following permissions: + +* Users -- even Administrator/root -- *cannot* delete {elastic-endpoint} files (located at `c:\Program Files\Elastic\Endpoint` on Windows, and `/Library/Elastic/Endpoint` on macOS). +* Users *cannot* terminate the {elastic-endpoint} program or service. +* Administrator/root users *can* read the endpoint's files. On Windows, the easiest way to read Endpoint files is to start an Administrator `cmd.exe` prompt. On macOS, an Administrator can use the `sudo` command. +* Administrator/root users *can* stop the {elastic-agent}'s service. On Windows, run the `sc stop "Elastic Agent"` command. On macOS, run the `sudo launchctl stop elastic-agent` command. + + +[discrete] +[[siem-integration]] +=== Integration with other Elastic products + +You can use {elastic-sec} with other Elastic products and features to help you +identify and investigate suspicious activity: + +* https://www.elastic.co/products/stack/machine-learning[{ml-cap}] +* https://www.elastic.co/products/stack/alerting[Alerting] +* https://www.elastic.co/products/stack/canvas[Canvas] + +[discrete] +[[data-sources]] +=== APM transaction data sources + +By default, {elastic-sec} monitors {apm-app-ref}/apm-getting-started.html[APM] +`apm-*-transaction*` indices. To add additional APM indices, update the +index patterns in the `securitySolution:defaultIndex` setting ({kib} -> Stack Management -> Advanced Settings -> `securitySolution:defaultIndex`). --- +[discrete] +[[ecs-compliant-reqs]] +=== ECS compliance data requirements +The {ecs-ref}[Elastic Common Schema (ECS)] defines a common set of fields used for +storing event data in Elasticsearch. ECS helps users normalize their event data +to better analyze, visualize, and correlate the data represented in their +events. {elastic-sec} supports events and indicator index data from any ECS-compliant data source. -include::siem-ui.asciidoc[] -include::machine-learning.asciidoc[] +IMPORTANT: {elastic-sec} requires {ecs-ref}[ECS-compliant data]. If you use third-party data collectors to ship data to {es}, the data must be mapped to ECS. +{security-guide}/siem-field-reference.html[Elastic Security ECS field reference] lists ECS fields used in {elastic-sec}. diff --git a/docs/user/dashboard/lens-advanced.asciidoc b/docs/user/dashboard/lens-advanced.asciidoc index ec8d90aa4920ec..33e0e362058f43 100644 --- a/docs/user/dashboard/lens-advanced.asciidoc +++ b/docs/user/dashboard/lens-advanced.asciidoc @@ -104,7 +104,7 @@ To quickly create many copies of a percentile metric that shows distribution of . From the *Chart Type* dropdown, select *Line*. + [role="screenshot"] -image::images/lens_advanced_2_1.png[Chart type menu with Line selected] +image::images/lens_advanced_2_1.png[Chart type menu with Line selected, width=50%] . From the *Available fields* list, drag and drop *products.price* to the visualization builder. @@ -239,12 +239,11 @@ For each category type that you want to break down, create a filter. Change the legend position to the top of the chart. . From the *Legend* dropdown, select the top position. - + [role="screenshot"] image::images/lens_advanced_4_1.png[Prices share by category] - Click *Save and return*. +. Click *Save and return*. [discrete] [[view-the-cumulative-number-of-products-sold-on-weekends]] @@ -299,7 +298,8 @@ image::images/lens_advanced_5_2.png[Line chart with cumulative sum of orders mad [[compare-time-ranges]] === Compare time ranges -*Lens* allows you to compare the currently selected time range with historical data using the *Time shift* option. +*Lens* allows you to compare the currently selected time range with historical data using the *Time shift* option. To calculate the percent +change, use *Formula*. Time shifts can be used on any metric. The special shift *previous* will show the time window preceding the currently selected one, spanning the same duration. For example, if *Last 7 days* is selected in the time filter, *previous* will show data from 14 days ago to 7 days ago. @@ -326,9 +326,32 @@ To compare current sales numbers with sales from a week ago, follow these steps: .. Click *Time shift* .. Click the *1 week* option. You can also define custom shifts by typing amount followed by time unit (like *1w* for a one week shift), then hit enter. - ++ [role="screenshot"] -image::images/lens_time_shift.png[Line chart with week-over-week sales comparison] +image::images/lens_time_shift.png[Line chart with week-over-week sales comparison, width=50%] + +. Click *Save and return*. + +[float] +[[compare-time-as-percent]] +==== Compare time ranges as a percent change + +To view the percent change in sales between the current time and the previous week, use a *Formula*: + +. Open *Lens*. + +. From the *Available fields* list, drag and drop *Records* to the visualization builder. + +. Click *Count of Records*, then click *Formula*. + +. Type `count() / count(shift='1w') - 1`. To learn more about the formula +syntax, click *Help*. + +. Click *Value format* and select *Percent* with 0 decimals. + +. In the *Display name* field, enter `Percent change`, then click *Close*. + +. Click *Save and return*. [discrete] [[view-customers-over-time-by-continents]] @@ -366,18 +389,14 @@ To split the customers count by continent: . From the *Available fields* list, drag and drop *geoip.continent_name* to the *Columns* field of the editor. + [role="screenshot"] -image::images/lens_advanced_6_1.png[Table with daily customers by continent configuration] +image::images/lens_advanced_6_1.png[Table with daily customers by continent configuration, width=50%] . Click *Save and return*. + [discrete] === Save the dashboard -By default the dashboard attempts to match the palette across panels, but in this case there's no need for that, so it can be disabled. - -[role="screenshot"] -image::images/lens_advanced_7_1.png[Disable palette sync in dashboard] - Now that you have a complete overview of your ecommerce sales data, save the dashboard. . In the toolbar, click *Save*. diff --git a/package.json b/package.json index ecedb64c343ec9..ceb178d0685196 100644 --- a/package.json +++ b/package.json @@ -128,25 +128,26 @@ "@kbn/analytics": "link:bazel-bin/packages/kbn-analytics", "@kbn/apm-config-loader": "link:bazel-bin/packages/kbn-apm-config-loader", "@kbn/apm-utils": "link:bazel-bin/packages/kbn-apm-utils", + "@kbn/common-utils": "link:bazel-bin/packages/kbn-common-utils", "@kbn/config": "link:bazel-bin/packages/kbn-config", "@kbn/config-schema": "link:bazel-bin/packages/kbn-config-schema", "@kbn/crypto": "link:bazel-bin/packages/kbn-crypto", - "@kbn/mapbox-gl": "link:bazel-bin/packages/kbn-mapbox-gl", "@kbn/i18n": "link:bazel-bin/packages/kbn-i18n", "@kbn/interpreter": "link:bazel-bin/packages/kbn-interpreter", "@kbn/io-ts-utils": "link:bazel-bin/packages/kbn-io-ts-utils", "@kbn/legacy-logging": "link:bazel-bin/packages/kbn-legacy-logging", "@kbn/logging": "link:bazel-bin/packages/kbn-logging", + "@kbn/mapbox-gl": "link:bazel-bin/packages/kbn-mapbox-gl", "@kbn/monaco": "link:bazel-bin/packages/kbn-monaco", "@kbn/rule-data-utils": "link:bazel-bin/packages/kbn-rule-data-utils", - "@kbn/securitysolution-list-constants": "link:bazel-bin/packages/kbn-securitysolution-list-constants", "@kbn/securitysolution-es-utils": "link:bazel-bin/packages/kbn-securitysolution-es-utils", "@kbn/securitysolution-hook-utils": "link:bazel-bin/packages/kbn-securitysolution-hook-utils", - "@kbn/securitysolution-io-ts-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-types", "@kbn/securitysolution-io-ts-alerting-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-alerting-types", "@kbn/securitysolution-io-ts-list-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-list-types", + "@kbn/securitysolution-io-ts-types": "link:bazel-bin/packages/kbn-securitysolution-io-ts-types", "@kbn/securitysolution-io-ts-utils": "link:bazel-bin/packages/kbn-securitysolution-io-ts-utils", "@kbn/securitysolution-list-api": "link:bazel-bin/packages/kbn-securitysolution-list-api", + "@kbn/securitysolution-list-constants": "link:bazel-bin/packages/kbn-securitysolution-list-constants", "@kbn/securitysolution-list-hooks": "link:bazel-bin/packages/kbn-securitysolution-list-hooks", "@kbn/securitysolution-list-utils": "link:bazel-bin/packages/kbn-securitysolution-list-utils", "@kbn/securitysolution-t-grid": "link:bazel-bin/packages/kbn-securitysolution-t-grid", @@ -158,7 +159,6 @@ "@kbn/ui-framework": "link:bazel-bin/packages/kbn-ui-framework", "@kbn/ui-shared-deps": "link:bazel-bin/packages/kbn-ui-shared-deps", "@kbn/utility-types": "link:bazel-bin/packages/kbn-utility-types", - "@kbn/common-utils": "link:bazel-bin/packages/kbn-common-utils", "@kbn/utils": "link:bazel-bin/packages/kbn-utils", "@loaders.gl/core": "^2.3.1", "@loaders.gl/json": "^2.3.1", @@ -273,6 +273,7 @@ "jquery": "^3.5.0", "js-levenshtein": "^1.1.6", "js-search": "^1.4.3", + "js-sha256": "^0.9.0", "js-yaml": "^3.14.0", "json-stable-stringify": "^1.0.1", "json-stringify-pretty-compact": "1.2.0", diff --git a/packages/kbn-ui-shared-deps/src/entry.js b/packages/kbn-ui-shared-deps/src/entry.js index b8d21a473c65f5..0e91c45ae6392a 100644 --- a/packages/kbn-ui-shared-deps/src/entry.js +++ b/packages/kbn-ui-shared-deps/src/entry.js @@ -40,6 +40,7 @@ export const ElasticEui = require('@elastic/eui'); export const ElasticEuiLibServices = require('@elastic/eui/lib/services'); export const ElasticEuiLibServicesFormat = require('@elastic/eui/lib/services/format'); export const ElasticEuiChartsTheme = require('@elastic/eui/dist/eui_charts_theme'); +export const ReactBeautifulDnD = require('react-beautiful-dnd'); export const Theme = require('./theme.ts'); export const Lodash = require('lodash'); export const LodashFp = require('lodash/fp'); diff --git a/packages/kbn-ui-shared-deps/src/index.js b/packages/kbn-ui-shared-deps/src/index.js index c5853dc0918756..36c2e6b02879ee 100644 --- a/packages/kbn-ui-shared-deps/src/index.js +++ b/packages/kbn-ui-shared-deps/src/index.js @@ -85,6 +85,8 @@ exports.externals = { '@elastic/eui/dist/eui_charts_theme': '__kbnSharedDeps__.ElasticEuiChartsTheme', '@elastic/eui/dist/eui_theme_light.json': '__kbnSharedDeps__.Theme.euiLightVars', '@elastic/eui/dist/eui_theme_dark.json': '__kbnSharedDeps__.Theme.euiDarkVars', + // transient dep of eui + 'react-beautiful-dnd': '__kbnSharedDeps__.ReactBeautifulDnD', lodash: '__kbnSharedDeps__.Lodash', 'lodash/fp': '__kbnSharedDeps__.LodashFp', fflate: '__kbnSharedDeps__.Fflate', diff --git a/src/core/server/csp/config.test.ts b/src/core/server/csp/config.test.ts index 8036ebeeaad313..6db93addb7da85 100644 --- a/src/core/server/csp/config.test.ts +++ b/src/core/server/csp/config.test.ts @@ -9,11 +9,469 @@ import { config } from './config'; describe('config.validate()', () => { - test(`does not allow "disableEmbedding" to be set to true`, () => { + it(`does not allow "disableEmbedding" to be set to true`, () => { // This is intentionally not editable in the raw CSP config. // Users should set `server.securityResponseHeaders.disableEmbedding` to control this config property. expect(() => config.schema.validate({ disableEmbedding: true })).toThrowError( '[disableEmbedding]: expected value to equal [false]' ); }); + + describe(`"script_src"`, () => { + it(`throws if containing 'unsafe-inline' when 'strict' is true`, () => { + expect(() => + config.schema.validate({ + strict: true, + warnLegacyBrowsers: false, + script_src: [`'self'`, `unsafe-inline`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.strict\` is true"` + ); + + expect(() => + config.schema.validate({ + strict: true, + warnLegacyBrowsers: false, + script_src: [`'self'`, `'unsafe-inline'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.strict\` is true"` + ); + }); + + it(`throws if containing 'unsafe-inline' when 'warnLegacyBrowsers' is true`, () => { + expect(() => + config.schema.validate({ + strict: false, + warnLegacyBrowsers: true, + script_src: [`'self'`, `unsafe-inline`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.warnLegacyBrowsers\` is true"` + ); + + expect(() => + config.schema.validate({ + strict: false, + warnLegacyBrowsers: true, + script_src: [`'self'`, `'unsafe-inline'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"cannot use \`unsafe-inline\` for \`script_src\` when \`csp.warnLegacyBrowsers\` is true"` + ); + }); + + it(`does not throw if containing 'unsafe-inline' when 'strict' and 'warnLegacyBrowsers' are false`, () => { + expect(() => + config.schema.validate({ + strict: false, + warnLegacyBrowsers: false, + script_src: [`'self'`, `unsafe-inline`], + }) + ).not.toThrow(); + + expect(() => + config.schema.validate({ + strict: false, + warnLegacyBrowsers: false, + script_src: [`'self'`, `'unsafe-inline'`], + }) + ).not.toThrow(); + }); + + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + script_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + script_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[script_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + script_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[script_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + script_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[script_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"worker_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + worker_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + worker_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[worker_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + worker_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[worker_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + worker_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[worker_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"style_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + style_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + style_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[style_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + style_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[style_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + style_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[style_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"connect_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + connect_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + connect_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[connect_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + connect_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[connect_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + connect_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[connect_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"default_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + default_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + default_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[default_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + default_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[default_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + default_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[default_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"font_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + font_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + font_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[font_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + font_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[font_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + font_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[font_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"frame_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + frame_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + frame_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[frame_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + frame_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[frame_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + frame_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[frame_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"img_src"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + img_src: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + img_src: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[img_src]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + img_src: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[img_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + img_src: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[img_src]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); + + describe(`"frame_ancestors"`, () => { + it(`throws if 'rules' is also specified`, () => { + expect(() => + config.schema.validate({ + rules: [ + `script-src 'unsafe-eval' 'self'`, + `worker-src 'unsafe-eval' 'self'`, + `style-src 'unsafe-eval' 'self'`, + ], + frame_ancestors: [`'self'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"\\"csp.rules\\" cannot be used when specifying per-directive additions such as \\"script_src\\", \\"worker_src\\" or \\"style_src\\""` + ); + }); + + it('throws if using an `nonce-*` value', () => { + expect(() => + config.schema.validate({ + frame_ancestors: [`hello`, `nonce-foo`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[frame_ancestors]: using \\"nonce-*\\" is considered insecure and is not allowed"` + ); + }); + it("throws if using `none` or `'none'`", () => { + expect(() => + config.schema.validate({ + frame_ancestors: [`hello`, `none`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[frame_ancestors]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + + expect(() => + config.schema.validate({ + frame_ancestors: [`hello`, `'none'`], + }) + ).toThrowErrorMatchingInlineSnapshot( + `"[frame_ancestors]: using \\"none\\" would conflict with Kibana's default csp configuration and is not allowed"` + ); + }); + }); }); diff --git a/src/core/server/csp/config.ts b/src/core/server/csp/config.ts index a61fa1b03a45c8..3a7cb20985cea3 100644 --- a/src/core/server/csp/config.ts +++ b/src/core/server/csp/config.ts @@ -7,28 +7,150 @@ */ import { TypeOf, schema } from '@kbn/config-schema'; +import { ServiceConfigDescriptor } from '../internal_types'; + +interface DirectiveValidationOptions { + allowNone: boolean; + allowNonce: boolean; +} + +const getDirectiveValidator = (options: DirectiveValidationOptions) => { + const validateValue = getDirectiveValueValidator(options); + return (values: string[]) => { + for (const value of values) { + const error = validateValue(value); + if (error) { + return error; + } + } + }; +}; + +const getDirectiveValueValidator = ({ allowNone, allowNonce }: DirectiveValidationOptions) => { + return (value: string) => { + if (!allowNonce && value.startsWith('nonce-')) { + return `using "nonce-*" is considered insecure and is not allowed`; + } + if (!allowNone && (value === `none` || value === `'none'`)) { + return `using "none" would conflict with Kibana's default csp configuration and is not allowed`; + } + }; +}; + +const configSchema = schema.object( + { + rules: schema.maybe(schema.arrayOf(schema.string())), + script_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + worker_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + style_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + connect_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + default_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + font_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + frame_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + img_src: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + frame_ancestors: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: false, allowNonce: false }), + }), + report_uri: schema.arrayOf(schema.string(), { + defaultValue: [], + validate: getDirectiveValidator({ allowNone: true, allowNonce: false }), + }), + report_to: schema.arrayOf(schema.string(), { + defaultValue: [], + }), + strict: schema.boolean({ defaultValue: true }), + warnLegacyBrowsers: schema.boolean({ defaultValue: true }), + disableEmbedding: schema.oneOf([schema.literal(false)], { defaultValue: false }), + }, + { + validate: (cspConfig) => { + if (cspConfig.rules && hasDirectiveSpecified(cspConfig)) { + return `"csp.rules" cannot be used when specifying per-directive additions such as "script_src", "worker_src" or "style_src"`; + } + const hasUnsafeInlineScriptSrc = + cspConfig.script_src.includes(`unsafe-inline`) || + cspConfig.script_src.includes(`'unsafe-inline'`); + + if (cspConfig.strict && hasUnsafeInlineScriptSrc) { + return 'cannot use `unsafe-inline` for `script_src` when `csp.strict` is true'; + } + if (cspConfig.warnLegacyBrowsers && hasUnsafeInlineScriptSrc) { + return 'cannot use `unsafe-inline` for `script_src` when `csp.warnLegacyBrowsers` is true'; + } + }, + } +); + +const hasDirectiveSpecified = (rawConfig: CspConfigType): boolean => { + return Boolean( + rawConfig.script_src.length || + rawConfig.worker_src.length || + rawConfig.style_src.length || + rawConfig.connect_src.length || + rawConfig.default_src.length || + rawConfig.font_src.length || + rawConfig.frame_src.length || + rawConfig.img_src.length || + rawConfig.frame_ancestors.length || + rawConfig.report_uri.length || + rawConfig.report_to.length + ); +}; /** * @internal */ -export type CspConfigType = TypeOf; +export type CspConfigType = TypeOf; -export const config = { +export const config: ServiceConfigDescriptor = { // TODO: Move this to server.csp using config deprecations // ? https://github.com/elastic/kibana/pull/52251 path: 'csp', - schema: schema.object({ - rules: schema.arrayOf(schema.string(), { - defaultValue: [ - `script-src 'unsafe-eval' 'self'`, - `worker-src blob: 'self'`, - `style-src 'unsafe-inline' 'self'`, - ], - }), - strict: schema.boolean({ defaultValue: true }), - warnLegacyBrowsers: schema.boolean({ defaultValue: true }), - disableEmbedding: schema.oneOf([schema.literal(false)], { defaultValue: false }), - }), + schema: configSchema, + deprecations: () => [ + (rawConfig, fromPath, addDeprecation) => { + const cspConfig = rawConfig[fromPath]; + if (cspConfig?.rules) { + addDeprecation({ + message: + '`csp.rules` is deprecated in favor of directive specific configuration. Please use `csp.connect_src`, ' + + '`csp.default_src`, `csp.font_src`, `csp.frame_ancestors`, `csp.frame_src`, `csp.img_src`, ' + + '`csp.report_uri`, `csp.report_to`, `csp.script_src`, `csp.style_src`, and `csp.worker_src` instead.', + correctiveActions: { + manualSteps: [ + `Remove "csp.rules" from the Kibana config file."`, + `Add directive specific configurations to the config file using "csp.connect_src", "csp.default_src", "csp.font_src", ` + + `"csp.frame_ancestors", "csp.frame_src", "csp.img_src", "csp.report_uri", "csp.report_to", "csp.script_src", ` + + `"csp.style_src", and "csp.worker_src".`, + ], + }, + }); + } + }, + ], }; - -export const FRAME_ANCESTORS_RULE = `frame-ancestors 'self'`; // only used by CspConfig when embedding is disabled diff --git a/src/core/server/csp/csp_config.test.ts b/src/core/server/csp/csp_config.test.ts index 1e023c6f08ea80..a1bac7d4ae73ee 100644 --- a/src/core/server/csp/csp_config.test.ts +++ b/src/core/server/csp/csp_config.test.ts @@ -7,7 +7,7 @@ */ import { CspConfig } from './csp_config'; -import { FRAME_ANCESTORS_RULE } from './config'; +import { config as cspConfig, CspConfigType } from './config'; // CSP rules aren't strictly additive, so any change can potentially expand or // restrict the policy in a way we consider a breaking change. For that reason, @@ -23,6 +23,12 @@ import { FRAME_ANCESTORS_RULE } from './config'; // the nature of a change in defaults during a PR review. describe('CspConfig', () => { + let defaultConfig: CspConfigType; + + beforeEach(() => { + defaultConfig = cspConfig.schema.validate({}); + }); + test('DEFAULT', () => { expect(CspConfig.DEFAULT).toMatchInlineSnapshot(` CspConfig { @@ -40,50 +46,129 @@ describe('CspConfig', () => { }); test('defaults from config', () => { - expect(new CspConfig()).toEqual(CspConfig.DEFAULT); + expect(new CspConfig(defaultConfig)).toEqual(CspConfig.DEFAULT); }); describe('partial config', () => { test('allows "rules" to be set and changes header', () => { - const rules = ['foo', 'bar']; - const config = new CspConfig({ rules }); + const rules = [`foo 'self'`, `bar 'self'`]; + const config = new CspConfig({ ...defaultConfig, rules }); expect(config.rules).toEqual(rules); - expect(config.header).toMatchInlineSnapshot(`"foo; bar"`); + expect(config.header).toMatchInlineSnapshot(`"foo 'self'; bar 'self'"`); }); test('allows "strict" to be set', () => { - const config = new CspConfig({ strict: false }); + const config = new CspConfig({ ...defaultConfig, strict: false }); expect(config.strict).toEqual(false); expect(config.strict).not.toEqual(CspConfig.DEFAULT.strict); }); test('allows "warnLegacyBrowsers" to be set', () => { const warnLegacyBrowsers = false; - const config = new CspConfig({ warnLegacyBrowsers }); + const config = new CspConfig({ ...defaultConfig, warnLegacyBrowsers }); expect(config.warnLegacyBrowsers).toEqual(warnLegacyBrowsers); expect(config.warnLegacyBrowsers).not.toEqual(CspConfig.DEFAULT.warnLegacyBrowsers); }); + test('allows "worker_src" to be set and changes header', () => { + const config = new CspConfig({ + ...defaultConfig, + rules: [], + worker_src: ['foo', 'bar'], + }); + expect(config.rules).toEqual([`worker-src foo bar`]); + expect(config.header).toEqual(`worker-src foo bar`); + }); + + test('allows "style_src" to be set and changes header', () => { + const config = new CspConfig({ + ...defaultConfig, + rules: [], + style_src: ['foo', 'bar'], + }); + expect(config.rules).toEqual([`style-src foo bar`]); + expect(config.header).toEqual(`style-src foo bar`); + }); + + test('allows "script_src" to be set and changes header', () => { + const config = new CspConfig({ + ...defaultConfig, + rules: [], + script_src: ['foo', 'bar'], + }); + expect(config.rules).toEqual([`script-src foo bar`]); + expect(config.header).toEqual(`script-src foo bar`); + }); + + test('allows all directives to be set and changes header', () => { + const config = new CspConfig({ + ...defaultConfig, + rules: [], + script_src: ['script', 'foo'], + worker_src: ['worker', 'bar'], + style_src: ['style', 'dolly'], + }); + expect(config.rules).toEqual([ + `script-src script foo`, + `worker-src worker bar`, + `style-src style dolly`, + ]); + expect(config.header).toEqual( + `script-src script foo; worker-src worker bar; style-src style dolly` + ); + }); + + test('applies defaults when `rules` is undefined', () => { + const config = new CspConfig({ + ...defaultConfig, + rules: undefined, + script_src: ['script'], + worker_src: ['worker'], + style_src: ['style'], + }); + expect(config.rules).toEqual([ + `script-src 'unsafe-eval' 'self' script`, + `worker-src blob: 'self' worker`, + `style-src 'unsafe-inline' 'self' style`, + ]); + expect(config.header).toEqual( + `script-src 'unsafe-eval' 'self' script; worker-src blob: 'self' worker; style-src 'unsafe-inline' 'self' style` + ); + }); + describe('allows "disableEmbedding" to be set', () => { const disableEmbedding = true; test('and changes rules/header if custom rules are not defined', () => { - const config = new CspConfig({ disableEmbedding }); + const config = new CspConfig({ ...defaultConfig, disableEmbedding }); expect(config.disableEmbedding).toEqual(disableEmbedding); expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding); - expect(config.rules).toEqual(expect.arrayContaining([FRAME_ANCESTORS_RULE])); + expect(config.rules).toEqual(expect.arrayContaining([`frame-ancestors 'self'`])); expect(config.header).toMatchInlineSnapshot( `"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'; frame-ancestors 'self'"` ); }); test('and does not change rules/header if custom rules are defined', () => { - const rules = ['foo', 'bar']; - const config = new CspConfig({ disableEmbedding, rules }); + const rules = [`foo 'self'`, `bar 'self'`]; + const config = new CspConfig({ ...defaultConfig, disableEmbedding, rules }); expect(config.disableEmbedding).toEqual(disableEmbedding); expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding); expect(config.rules).toEqual(rules); - expect(config.header).toMatchInlineSnapshot(`"foo; bar"`); + expect(config.header).toMatchInlineSnapshot(`"foo 'self'; bar 'self'"`); + }); + + test('and overrides `frame-ancestors` if set', () => { + const config = new CspConfig({ + ...defaultConfig, + disableEmbedding: true, + frame_ancestors: ['foo.com'], + }); + expect(config.disableEmbedding).toEqual(disableEmbedding); + expect(config.disableEmbedding).not.toEqual(CspConfig.DEFAULT.disableEmbedding); + expect(config.header).toMatchInlineSnapshot( + `"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'; frame-ancestors 'self'"` + ); }); }); }); diff --git a/src/core/server/csp/csp_config.ts b/src/core/server/csp/csp_config.ts index 649c81576ef522..13778088d9df2e 100644 --- a/src/core/server/csp/csp_config.ts +++ b/src/core/server/csp/csp_config.ts @@ -6,7 +6,8 @@ * Side Public License, v 1. */ -import { config, FRAME_ANCESTORS_RULE } from './config'; +import { config, CspConfigType } from './config'; +import { CspDirectives } from './csp_directives'; const DEFAULT_CONFIG = Object.freeze(config.schema.validate({})); @@ -50,8 +51,9 @@ export interface ICspConfig { * @public */ export class CspConfig implements ICspConfig { - static readonly DEFAULT = new CspConfig(); + static readonly DEFAULT = new CspConfig(DEFAULT_CONFIG); + readonly #directives: CspDirectives; public readonly rules: string[]; public readonly strict: boolean; public readonly warnLegacyBrowsers: boolean; @@ -62,16 +64,18 @@ export class CspConfig implements ICspConfig { * Returns the default CSP configuration when passed with no config * @internal */ - constructor(rawCspConfig: Partial> = {}) { - const source = { ...DEFAULT_CONFIG, ...rawCspConfig }; - - this.rules = [...source.rules]; - this.strict = source.strict; - this.warnLegacyBrowsers = source.warnLegacyBrowsers; - this.disableEmbedding = source.disableEmbedding; - if (!rawCspConfig.rules?.length && source.disableEmbedding) { - this.rules.push(FRAME_ANCESTORS_RULE); + constructor(rawCspConfig: CspConfigType) { + this.#directives = CspDirectives.fromConfig(rawCspConfig); + if (!rawCspConfig.rules?.length && rawCspConfig.disableEmbedding) { + this.#directives.clearDirectiveValues('frame-ancestors'); + this.#directives.addDirectiveValue('frame-ancestors', `'self'`); } - this.header = this.rules.join('; '); + + this.rules = this.#directives.getRules(); + this.header = this.#directives.getCspHeader(); + + this.strict = rawCspConfig.strict; + this.warnLegacyBrowsers = rawCspConfig.warnLegacyBrowsers; + this.disableEmbedding = rawCspConfig.disableEmbedding; } } diff --git a/src/core/server/csp/csp_directives.test.ts b/src/core/server/csp/csp_directives.test.ts new file mode 100644 index 00000000000000..1077b6ea9f3cd5 --- /dev/null +++ b/src/core/server/csp/csp_directives.test.ts @@ -0,0 +1,266 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CspDirectives } from './csp_directives'; +import { config as cspConfig } from './config'; + +describe('CspDirectives', () => { + describe('#addDirectiveValue', () => { + it('properly updates the rules', () => { + const directives = new CspDirectives(); + directives.addDirectiveValue('style-src', 'foo'); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "style-src foo", + ] + `); + + directives.addDirectiveValue('style-src', 'bar'); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "style-src foo bar", + ] + `); + }); + + it('properly updates the header', () => { + const directives = new CspDirectives(); + directives.addDirectiveValue('style-src', 'foo'); + + expect(directives.getCspHeader()).toMatchInlineSnapshot(`"style-src foo"`); + + directives.addDirectiveValue('style-src', 'bar'); + + expect(directives.getCspHeader()).toMatchInlineSnapshot(`"style-src foo bar"`); + }); + + it('handles distinct directives', () => { + const directives = new CspDirectives(); + directives.addDirectiveValue('style-src', 'foo'); + directives.addDirectiveValue('style-src', 'bar'); + directives.addDirectiveValue('worker-src', 'dolly'); + + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"style-src foo bar; worker-src dolly"` + ); + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "style-src foo bar", + "worker-src dolly", + ] + `); + }); + + it('removes duplicates', () => { + const directives = new CspDirectives(); + directives.addDirectiveValue('style-src', 'foo'); + directives.addDirectiveValue('style-src', 'foo'); + directives.addDirectiveValue('style-src', 'bar'); + + expect(directives.getCspHeader()).toMatchInlineSnapshot(`"style-src foo bar"`); + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "style-src foo bar", + ] + `); + }); + + it('automatically adds single quotes for keywords', () => { + const directives = new CspDirectives(); + directives.addDirectiveValue('style-src', 'none'); + directives.addDirectiveValue('style-src', 'self'); + directives.addDirectiveValue('style-src', 'strict-dynamic'); + directives.addDirectiveValue('style-src', 'report-sample'); + directives.addDirectiveValue('style-src', 'unsafe-inline'); + directives.addDirectiveValue('style-src', 'unsafe-eval'); + directives.addDirectiveValue('style-src', 'unsafe-hashes'); + directives.addDirectiveValue('style-src', 'unsafe-allow-redirects'); + + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"style-src 'none' 'self' 'strict-dynamic' 'report-sample' 'unsafe-inline' 'unsafe-eval' 'unsafe-hashes' 'unsafe-allow-redirects'"` + ); + }); + + it('does not add single quotes for keywords when already present', () => { + const directives = new CspDirectives(); + directives.addDirectiveValue('style-src', `'none'`); + directives.addDirectiveValue('style-src', `'self'`); + directives.addDirectiveValue('style-src', `'strict-dynamic'`); + directives.addDirectiveValue('style-src', `'report-sample'`); + directives.addDirectiveValue('style-src', `'unsafe-inline'`); + directives.addDirectiveValue('style-src', `'unsafe-eval'`); + directives.addDirectiveValue('style-src', `'unsafe-hashes'`); + directives.addDirectiveValue('style-src', `'unsafe-allow-redirects'`); + + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"style-src 'none' 'self' 'strict-dynamic' 'report-sample' 'unsafe-inline' 'unsafe-eval' 'unsafe-hashes' 'unsafe-allow-redirects'"` + ); + }); + }); + + describe('#fromConfig', () => { + it('returns the correct rules for the default config', () => { + const config = cspConfig.schema.validate({}); + const directives = CspDirectives.fromConfig(config); + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'unsafe-eval' 'self'", + "worker-src blob: 'self'", + "style-src 'unsafe-inline' 'self'", + ] + `); + }); + + it('returns the correct header for the default config', () => { + const config = cspConfig.schema.validate({}); + const directives = CspDirectives.fromConfig(config); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'unsafe-eval' 'self'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'"` + ); + }); + + it('handles config with rules', () => { + const config = cspConfig.schema.validate({ + rules: [`script-src 'self' http://foo.com`, `worker-src 'self'`], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'self' http://foo.com", + "worker-src 'self'", + ] + `); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'self' http://foo.com; worker-src 'self'"` + ); + }); + + it('adds single quotes for keyword for rules', () => { + const config = cspConfig.schema.validate({ + rules: [`script-src self http://foo.com`, `worker-src self`], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'self' http://foo.com", + "worker-src 'self'", + ] + `); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'self' http://foo.com; worker-src 'self'"` + ); + }); + + it('handles multiple whitespaces when parsing rules', () => { + const config = cspConfig.schema.validate({ + rules: [` script-src 'self' http://foo.com `, ` worker-src 'self' `], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'self' http://foo.com", + "worker-src 'self'", + ] + `); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'self' http://foo.com; worker-src 'self'"` + ); + }); + + it('supports unregistered directives', () => { + const config = cspConfig.schema.validate({ + rules: [`script-src 'self' http://foo.com`, `img-src 'self'`, 'foo bar'], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'self' http://foo.com", + "img-src 'self'", + "foo bar", + ] + `); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'self' http://foo.com; img-src 'self'; foo bar"` + ); + }); + + it('adds default value for config with directives', () => { + const config = cspConfig.schema.validate({ + script_src: [`baz`], + worker_src: [`foo`], + style_src: [`bar`, `dolly`], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'unsafe-eval' 'self' baz", + "worker-src blob: 'self' foo", + "style-src 'unsafe-inline' 'self' bar dolly", + ] + `); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'unsafe-eval' 'self' baz; worker-src blob: 'self' foo; style-src 'unsafe-inline' 'self' bar dolly"` + ); + }); + + it('adds additional values for some directives without defaults', () => { + const config = cspConfig.schema.validate({ + connect_src: [`connect-src`], + default_src: [`default-src`], + font_src: [`font-src`], + frame_src: [`frame-src`], + img_src: [`img-src`], + frame_ancestors: [`frame-ancestors`], + report_uri: [`report-uri`], + report_to: [`report-to`], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'unsafe-eval' 'self'", + "worker-src blob: 'self'", + "style-src 'unsafe-inline' 'self'", + "connect-src 'self' connect-src", + "default-src 'self' default-src", + "font-src 'self' font-src", + "frame-src 'self' frame-src", + "img-src 'self' img-src", + "frame-ancestors 'self' frame-ancestors", + "report-uri report-uri", + "report-to report-to", + ] + `); + }); + + it('adds single quotes for keywords in added directives', () => { + const config = cspConfig.schema.validate({ + script_src: [`unsafe-hashes`], + }); + const directives = CspDirectives.fromConfig(config); + + expect(directives.getRules()).toMatchInlineSnapshot(` + Array [ + "script-src 'unsafe-eval' 'self' 'unsafe-hashes'", + "worker-src blob: 'self'", + "style-src 'unsafe-inline' 'self'", + ] + `); + expect(directives.getCspHeader()).toMatchInlineSnapshot( + `"script-src 'unsafe-eval' 'self' 'unsafe-hashes'; worker-src blob: 'self'; style-src 'unsafe-inline' 'self'"` + ); + }); + }); +}); diff --git a/src/core/server/csp/csp_directives.ts b/src/core/server/csp/csp_directives.ts new file mode 100644 index 00000000000000..9e3b60f7f1e4f7 --- /dev/null +++ b/src/core/server/csp/csp_directives.ts @@ -0,0 +1,159 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { CspConfigType } from './config'; + +export type CspDirectiveName = + | 'script-src' + | 'worker-src' + | 'style-src' + | 'frame-ancestors' + | 'connect-src' + | 'default-src' + | 'font-src' + | 'frame-src' + | 'img-src' + | 'report-uri' + | 'report-to'; + +/** + * The default rules that are always applied + */ +export const defaultRules: Partial> = { + 'script-src': [`'unsafe-eval'`, `'self'`], + 'worker-src': [`blob:`, `'self'`], + 'style-src': [`'unsafe-inline'`, `'self'`], +}; + +/** + * Per-directive rules that will be added when the configuration contains at least one value + * Main purpose is to add `self` value to some directives when the configuration specifies other values + */ +export const additionalRules: Partial> = { + 'connect-src': [`'self'`], + 'default-src': [`'self'`], + 'font-src': [`'self'`], + 'img-src': [`'self'`], + 'frame-ancestors': [`'self'`], + 'frame-src': [`'self'`], +}; + +export class CspDirectives { + private readonly directives = new Map>(); + + addDirectiveValue(directiveName: CspDirectiveName, directiveValue: string) { + if (!this.directives.has(directiveName)) { + this.directives.set(directiveName, new Set()); + } + this.directives.get(directiveName)!.add(normalizeDirectiveValue(directiveValue)); + } + + clearDirectiveValues(directiveName: CspDirectiveName) { + this.directives.delete(directiveName); + } + + getCspHeader() { + return this.getRules().join('; '); + } + + getRules() { + return [...this.directives.entries()].map(([name, values]) => { + return [name, ...values].join(' '); + }); + } + + static fromConfig(config: CspConfigType): CspDirectives { + const cspDirectives = new CspDirectives(); + + // adding `csp.rules` or `default` rules + const initialRules = config.rules ? parseRules(config.rules) : { ...defaultRules }; + Object.entries(initialRules).forEach(([key, values]) => { + values?.forEach((value) => { + cspDirectives.addDirectiveValue(key as CspDirectiveName, value); + }); + }); + + // adding per-directive configuration + const additiveConfig = parseConfigDirectives(config); + [...additiveConfig.entries()].forEach(([directiveName, directiveValues]) => { + const additionalValues = additionalRules[directiveName] ?? []; + [...additionalValues, ...directiveValues].forEach((value) => { + cspDirectives.addDirectiveValue(directiveName, value); + }); + }); + + return cspDirectives; + } +} + +const parseRules = (rules: string[]): Partial> => { + const directives: Partial> = {}; + rules.forEach((rule) => { + const [name, ...values] = rule.replace(/\s+/g, ' ').trim().split(' '); + directives[name as CspDirectiveName] = values; + }); + return directives; +}; + +const parseConfigDirectives = (cspConfig: CspConfigType): Map => { + const map = new Map(); + + if (cspConfig.script_src?.length) { + map.set('script-src', cspConfig.script_src); + } + if (cspConfig.worker_src?.length) { + map.set('worker-src', cspConfig.worker_src); + } + if (cspConfig.style_src?.length) { + map.set('style-src', cspConfig.style_src); + } + if (cspConfig.connect_src?.length) { + map.set('connect-src', cspConfig.connect_src); + } + if (cspConfig.default_src?.length) { + map.set('default-src', cspConfig.default_src); + } + if (cspConfig.font_src?.length) { + map.set('font-src', cspConfig.font_src); + } + if (cspConfig.frame_src?.length) { + map.set('frame-src', cspConfig.frame_src); + } + if (cspConfig.img_src?.length) { + map.set('img-src', cspConfig.img_src); + } + if (cspConfig.frame_ancestors?.length) { + map.set('frame-ancestors', cspConfig.frame_ancestors); + } + if (cspConfig.report_uri?.length) { + map.set('report-uri', cspConfig.report_uri); + } + if (cspConfig.report_to?.length) { + map.set('report-to', cspConfig.report_to); + } + + return map; +}; + +const keywordTokens = [ + 'none', + 'self', + 'strict-dynamic', + 'report-sample', + 'unsafe-inline', + 'unsafe-eval', + 'unsafe-hashes', + 'unsafe-allow-redirects', +]; + +function normalizeDirectiveValue(value: string) { + if (keywordTokens.includes(value)) { + return `'${value}'`; + } + return value; +} diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts index c8021638664234..55af02a08561b5 100644 --- a/src/core/server/http/cookie_session_storage.test.ts +++ b/src/core/server/http/cookie_session_storage.test.ts @@ -69,7 +69,11 @@ configService.atPath.mockImplementation((path) => { } as any); } if (path === 'csp') { - return new BehaviorSubject({} as any); + return new BehaviorSubject({ + strict: false, + disableEmbedding: false, + warnLegacyBrowsers: true, + }); } throw new Error(`Unexpected config path: ${path}`); }); diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index 56095336d970b9..06a47456322332 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -8,7 +8,7 @@ import uuid from 'uuid'; import { config, HttpConfig } from './http_config'; -import { CspConfig } from '../csp'; +import { config as cspConfig } from '../csp'; import { ExternalUrlConfig } from '../external_url'; const validHostnames = ['www.example.com', '8.8.8.8', '::1', 'localhost', '0.0.0.0']; @@ -459,7 +459,8 @@ describe('HttpConfig', () => { }, }, }); - const httpConfig = new HttpConfig(rawConfig, CspConfig.DEFAULT, ExternalUrlConfig.DEFAULT); + const rawCspConfig = cspConfig.schema.validate({}); + const httpConfig = new HttpConfig(rawConfig, rawCspConfig, ExternalUrlConfig.DEFAULT); expect(httpConfig.customResponseHeaders).toEqual({ string: 'string', diff --git a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts index cbd300fdc9c09b..c2023c5577d617 100644 --- a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts +++ b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts @@ -79,7 +79,11 @@ describe('core lifecycle handlers', () => { } as any); } if (path === 'csp') { - return new BehaviorSubject({} as any); + return new BehaviorSubject({ + strict: false, + disableEmbedding: false, + warnLegacyBrowsers: true, + }); } throw new Error(`Unexpected config path: ${path}`); }); diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts index b3180b43d0026a..4e1a88e967f8f1 100644 --- a/src/core/server/http/test_utils.ts +++ b/src/core/server/http/test_utils.ts @@ -56,7 +56,11 @@ configService.atPath.mockImplementation((path) => { } as any); } if (path === 'csp') { - return new BehaviorSubject({} as any); + return new BehaviorSubject({ + strict: false, + disableEmbedding: false, + warnLegacyBrowsers: true, + }); } throw new Error(`Unexpected config path: ${path}`); }); diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index fcecf39f7e53a9..3bc0b54635eb54 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -777,8 +777,12 @@ export interface CountResponse { // @public export class CspConfig implements ICspConfig { + // (undocumented) + #private; + // Warning: (ae-forgotten-export) The symbol "CspConfigType" needs to be exported by the entry point index.d.ts + // // @internal - constructor(rawCspConfig?: Partial>); + constructor(rawCspConfig: CspConfigType); // (undocumented) static readonly DEFAULT: CspConfig; // (undocumented) diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index d109a824ca81de..a224793bace3f0 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -31,6 +31,17 @@ kibana_vars=( csp.rules csp.strict csp.warnLegacyBrowsers + csp.script_src + csp.worker_src + csp.style_src + csp.connect_src + csp.default_src + csp.font_src + csp.frame_src + csp.img_src + csp.frame_ancestors + csp.report_uri + csp.report_to data.autocomplete.valueSuggestions.terminateAfter data.autocomplete.valueSuggestions.timeout elasticsearch.customHeaders @@ -379,7 +390,8 @@ kibana_vars=( xpack.task_manager.monitored_aggregated_stats_refresh_rate xpack.task_manager.monitored_stats_required_freshness xpack.task_manager.monitored_stats_running_average_window - xpack.task_manager.monitored_stats_warn_delayed_task_start_in_seconds + xpack.task_manager.monitored_stats_health_verbose_log.enabled + xpack.task_manager.monitored_stats_health_verbose_log.warn_delayed_task_start_in_seconds xpack.task_manager.monitored_task_execution_thresholds xpack.task_manager.poll_interval xpack.task_manager.request_capacity diff --git a/src/plugins/dashboard/public/index.ts b/src/plugins/dashboard/public/index.ts index c584b44286e075..ff7708689c2211 100644 --- a/src/plugins/dashboard/public/index.ts +++ b/src/plugins/dashboard/public/index.ts @@ -22,11 +22,14 @@ export { DashboardUrlGenerator, DashboardFeatureFlagConfig, } from './plugin'; + export { DASHBOARD_APP_URL_GENERATOR, createDashboardUrlGenerator, DashboardUrlGeneratorState, } from './url_generator'; +export { DashboardAppLocator, DashboardAppLocatorParams } from './locator'; + export { DashboardSavedObject } from './saved_dashboards'; export { SavedDashboardPanel, DashboardContainerInput } from './types'; diff --git a/src/plugins/dashboard/public/locator.test.ts b/src/plugins/dashboard/public/locator.test.ts new file mode 100644 index 00000000000000..0b647ac00ce310 --- /dev/null +++ b/src/plugins/dashboard/public/locator.test.ts @@ -0,0 +1,323 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { DashboardAppLocatorDefinition } from './locator'; +import { hashedItemStore } from '../../kibana_utils/public'; +import { mockStorage } from '../../kibana_utils/public/storage/hashed_item_store/mock'; +import { esFilters } from '../../data/public'; + +describe('dashboard locator', () => { + beforeEach(() => { + // @ts-ignore + hashedItemStore.storage = mockStorage; + }); + + test('creates a link to a saved dashboard', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({}); + + expect(location).toMatchObject({ + app: 'dashboards', + path: '#/create?_a=()&_g=()', + state: {}, + }); + }); + + test('creates a link with global time range set up', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + }); + + expect(location).toMatchObject({ + app: 'dashboards', + path: '#/create?_a=()&_g=(time:(from:now-15m,mode:relative,to:now))', + state: {}, + }); + }); + + test('creates a link with filters, time range, refresh interval and query to a saved object', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + refreshInterval: { pause: false, value: 300 }, + dashboardId: '123', + filters: [ + { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'hi' }, + }, + { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'hi' }, + $state: { + store: esFilters.FilterStateStore.GLOBAL_STATE, + }, + }, + ], + query: { query: 'bye', language: 'kuery' }, + }); + + expect(location).toMatchObject({ + app: 'dashboards', + path: `#/view/123?_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),query:(language:kuery,query:bye))&_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:hi))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))`, + state: {}, + }); + }); + + test('searchSessionId', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + refreshInterval: { pause: false, value: 300 }, + dashboardId: '123', + filters: [], + query: { query: 'bye', language: 'kuery' }, + searchSessionId: '__sessionSearchId__', + }); + + expect(location).toMatchObject({ + app: 'dashboards', + path: `#/view/123?_a=(filters:!(),query:(language:kuery,query:bye))&_g=(filters:!(),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&searchSessionId=__sessionSearchId__`, + state: {}, + }); + }); + + test('savedQuery', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + savedQuery: '__savedQueryId__', + }); + + expect(location).toMatchObject({ + app: 'dashboards', + path: `#/create?_a=(savedQuery:__savedQueryId__)&_g=()`, + state: {}, + }); + expect(location.path).toContain('__savedQueryId__'); + }); + + test('panels', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + panels: [{ fakePanelContent: 'fakePanelContent' }] as any, + }); + + expect(location).toMatchObject({ + app: 'dashboards', + path: `#/create?_a=(panels:!((fakePanelContent:fakePanelContent)))&_g=()`, + state: {}, + }); + }); + + test('if no useHash setting is given, uses the one was start services', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: true, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + }); + + expect(location.path.indexOf('relative')).toBe(-1); + }); + + test('can override a false useHash ui setting', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + useHash: true, + }); + + expect(location.path.indexOf('relative')).toBe(-1); + }); + + test('can override a true useHash ui setting', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: true, + getDashboardFilterFields: async (dashboardId: string) => [], + }); + const location = await definition.getLocation({ + timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, + useHash: false, + }); + + expect(location.path.indexOf('relative')).toBeGreaterThan(1); + }); + + describe('preserving saved filters', () => { + const savedFilter1 = { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'savedfilter1' }, + }; + + const savedFilter2 = { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'savedfilter2' }, + }; + + const appliedFilter = { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'appliedfilter' }, + }; + + test('attaches filters from destination dashboard', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => { + return dashboardId === 'dashboard1' + ? [savedFilter1] + : dashboardId === 'dashboard2' + ? [savedFilter2] + : []; + }, + }); + + const location1 = await definition.getLocation({ + dashboardId: 'dashboard1', + filters: [appliedFilter], + }); + + expect(location1.path).toEqual(expect.stringContaining('query:savedfilter1')); + expect(location1.path).toEqual(expect.stringContaining('query:appliedfilter')); + + const location2 = await definition.getLocation({ + dashboardId: 'dashboard2', + filters: [appliedFilter], + }); + + expect(location2.path).toEqual(expect.stringContaining('query:savedfilter2')); + expect(location2.path).toEqual(expect.stringContaining('query:appliedfilter')); + }); + + test("doesn't fail if can't retrieve filters from destination dashboard", async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => { + if (dashboardId === 'dashboard1') { + throw new Error('Not found'); + } + return []; + }, + }); + + const location = await definition.getLocation({ + dashboardId: 'dashboard1', + filters: [appliedFilter], + }); + + expect(location.path).not.toEqual(expect.stringContaining('query:savedfilter1')); + expect(location.path).toEqual(expect.stringContaining('query:appliedfilter')); + }); + + test('can enforce empty filters', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => { + if (dashboardId === 'dashboard1') { + return [savedFilter1]; + } + return []; + }, + }); + + const location = await definition.getLocation({ + dashboardId: 'dashboard1', + filters: [], + preserveSavedFilters: false, + }); + + expect(location.path).not.toEqual(expect.stringContaining('query:savedfilter1')); + expect(location.path).not.toEqual(expect.stringContaining('query:appliedfilter')); + expect(location.path).toMatchInlineSnapshot( + `"#/view/dashboard1?_a=(filters:!())&_g=(filters:!())"` + ); + }); + + test('no filters in result url if no filters applied', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => { + if (dashboardId === 'dashboard1') { + return [savedFilter1]; + } + return []; + }, + }); + + const location = await definition.getLocation({ + dashboardId: 'dashboard1', + }); + + expect(location.path).not.toEqual(expect.stringContaining('filters')); + expect(location.path).toMatchInlineSnapshot(`"#/view/dashboard1?_a=()&_g=()"`); + }); + + test('can turn off preserving filters', async () => { + const definition = new DashboardAppLocatorDefinition({ + useHashedUrl: false, + getDashboardFilterFields: async (dashboardId: string) => { + if (dashboardId === 'dashboard1') { + return [savedFilter1]; + } + return []; + }, + }); + + const location = await definition.getLocation({ + dashboardId: 'dashboard1', + filters: [appliedFilter], + preserveSavedFilters: false, + }); + + expect(location.path).not.toEqual(expect.stringContaining('query:savedfilter1')); + expect(location.path).toEqual(expect.stringContaining('query:appliedfilter')); + }); + }); +}); diff --git a/src/plugins/dashboard/public/locator.ts b/src/plugins/dashboard/public/locator.ts new file mode 100644 index 00000000000000..e154351819ee9a --- /dev/null +++ b/src/plugins/dashboard/public/locator.ts @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { SerializableState } from 'src/plugins/kibana_utils/common'; +import type { TimeRange, Filter, Query, QueryState, RefreshInterval } from '../../data/public'; +import type { LocatorDefinition, LocatorPublic } from '../../share/public'; +import type { SavedDashboardPanel } from '../common/types'; +import { esFilters } from '../../data/public'; +import { setStateToKbnUrl } from '../../kibana_utils/public'; +import { ViewMode } from '../../embeddable/public'; +import { DashboardConstants } from './dashboard_constants'; + +const cleanEmptyKeys = (stateObj: Record) => { + Object.keys(stateObj).forEach((key) => { + if (stateObj[key] === undefined) { + delete stateObj[key]; + } + }); + return stateObj; +}; + +export const DASHBOARD_APP_LOCATOR = 'DASHBOARD_APP_LOCATOR'; + +export interface DashboardAppLocatorParams extends SerializableState { + /** + * If given, the dashboard saved object with this id will be loaded. If not given, + * a new, unsaved dashboard will be loaded up. + */ + dashboardId?: string; + /** + * Optionally set the time range in the time picker. + */ + timeRange?: TimeRange; + + /** + * Optionally set the refresh interval. + */ + refreshInterval?: RefreshInterval & SerializableState; + + /** + * Optionally apply filers. NOTE: if given and used in conjunction with `dashboardId`, and the + * saved dashboard has filters saved with it, this will _replace_ those filters. + */ + filters?: Filter[]; + /** + * Optionally set a query. NOTE: if given and used in conjunction with `dashboardId`, and the + * saved dashboard has a query saved with it, this will _replace_ that query. + */ + query?: Query; + /** + * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines + * whether to hash the data in the url to avoid url length issues. + */ + useHash?: boolean; + + /** + * When `true` filters from saved filters from destination dashboard as merged with applied filters + * When `false` applied filters take precedence and override saved filters + * + * true is default + */ + preserveSavedFilters?: boolean; + + /** + * View mode of the dashboard. + */ + viewMode?: ViewMode; + + /** + * Search search session ID to restore. + * (Background search) + */ + searchSessionId?: string; + + /** + * List of dashboard panels + */ + panels?: SavedDashboardPanel[] & SerializableState; + + /** + * Saved query ID + */ + savedQuery?: string; +} + +export type DashboardAppLocator = LocatorPublic; + +export interface DashboardAppLocatorDependencies { + useHashedUrl: boolean; + getDashboardFilterFields: (dashboardId: string) => Promise; +} + +export class DashboardAppLocatorDefinition implements LocatorDefinition { + public readonly id = DASHBOARD_APP_LOCATOR; + + constructor(protected readonly deps: DashboardAppLocatorDependencies) {} + + public readonly getLocation = async (params: DashboardAppLocatorParams) => { + const useHash = params.useHash ?? this.deps.useHashedUrl; + const hash = params.dashboardId ? `view/${params.dashboardId}` : `create`; + + const getSavedFiltersFromDestinationDashboardIfNeeded = async (): Promise => { + if (params.preserveSavedFilters === false) return []; + if (!params.dashboardId) return []; + try { + return await this.deps.getDashboardFilterFields(params.dashboardId); + } catch (e) { + // In case dashboard is missing, build the url without those filters. + // The Dashboard app will handle redirect to landing page with a toast message. + return []; + } + }; + + // leave filters `undefined` if no filters was applied + // in this case dashboard will restore saved filters on its own + const filters = params.filters && [ + ...(await getSavedFiltersFromDestinationDashboardIfNeeded()), + ...params.filters, + ]; + + let path = setStateToKbnUrl( + '_a', + cleanEmptyKeys({ + query: params.query, + filters: filters?.filter((f) => !esFilters.isFilterPinned(f)), + viewMode: params.viewMode, + panels: params.panels, + savedQuery: params.savedQuery, + }), + { useHash }, + `#/${hash}` + ); + + path = setStateToKbnUrl( + '_g', + cleanEmptyKeys({ + time: params.timeRange, + filters: filters?.filter((f) => esFilters.isFilterPinned(f)), + refreshInterval: params.refreshInterval, + }), + { useHash }, + path + ); + + if (params.searchSessionId) { + path = `${path}&${DashboardConstants.SEARCH_SESSION_ID}=${params.searchSessionId}`; + } + + return { + app: DashboardConstants.DASHBOARDS_ID, + path, + state: {}, + }; + }; +} diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index b5d6eda71ca4a2..53a8e90a8c35c9 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -72,6 +72,7 @@ import { DASHBOARD_APP_URL_GENERATOR, DashboardUrlGeneratorState, } from './url_generator'; +import { DashboardAppLocatorDefinition, DashboardAppLocator } from './locator'; import { createSavedDashboardLoader } from './saved_dashboards'; import { DashboardConstants } from './dashboard_constants'; import { PlaceholderEmbeddableFactory } from './application/embeddable/placeholder'; @@ -121,14 +122,25 @@ export interface DashboardStartDependencies { visualizations: VisualizationsStart; } -export type DashboardSetup = void; +export interface DashboardSetup { + locator?: DashboardAppLocator; +} export interface DashboardStart { getSavedDashboardLoader: () => SavedObjectLoader; getDashboardContainerByValueRenderer: () => ReturnType< typeof createDashboardContainerByValueRenderer >; + /** + * @deprecated Use dashboard locator instead. Dashboard locator is available + * under `.locator` key. This dashboard URL generator will be removed soon. + * + * ```ts + * plugins.dashboard.locator.getLocation({ ... }); + * ``` + */ dashboardUrlGenerator?: DashboardUrlGenerator; + locator?: DashboardAppLocator; dashboardFeatureFlagConfig: DashboardFeatureFlagConfig; } @@ -142,7 +154,11 @@ export class DashboardPlugin private currentHistory: ScopedHistory | undefined = undefined; private dashboardFeatureFlagConfig?: DashboardFeatureFlagConfig; + /** + * @deprecated Use locator instead. + */ private dashboardUrlGenerator?: DashboardUrlGenerator; + private locator?: DashboardAppLocator; public setup( core: CoreSetup, @@ -205,6 +221,19 @@ export class DashboardPlugin }; }; + if (share) { + this.locator = share.url.locators.create( + new DashboardAppLocatorDefinition({ + useHashedUrl: core.uiSettings.get('state:storeInSessionStorage'), + getDashboardFilterFields: async (dashboardId: string) => { + const [, , selfStart] = await core.getStartServices(); + const dashboard = await selfStart.getSavedDashboardLoader().get(dashboardId); + return dashboard?.searchSource?.getField('filter') ?? []; + }, + }) + ); + } + const { appMounted, appUnMounted, @@ -333,6 +362,10 @@ export class DashboardPlugin order: 100, }); } + + return { + locator: this.locator, + }; } public start(core: CoreStart, plugins: DashboardStartDependencies): DashboardStart { @@ -417,6 +450,7 @@ export class DashboardPlugin }); }, dashboardUrlGenerator: this.dashboardUrlGenerator, + locator: this.locator, dashboardFeatureFlagConfig: this.dashboardFeatureFlagConfig!, }; } diff --git a/src/plugins/dashboard/public/url_generator.ts b/src/plugins/dashboard/public/url_generator.ts index 58036ef70fa4ad..5c0cd32ee5a166 100644 --- a/src/plugins/dashboard/public/url_generator.ts +++ b/src/plugins/dashboard/public/url_generator.ts @@ -26,6 +26,9 @@ export const GLOBAL_STATE_STORAGE_KEY = '_g'; export const DASHBOARD_APP_URL_GENERATOR = 'DASHBOARD_APP_URL_GENERATOR'; +/** + * @deprecated Use dashboard locator instead. + */ export interface DashboardUrlGeneratorState { /** * If given, the dashboard saved object with this id will be loaded. If not given, @@ -88,6 +91,9 @@ export interface DashboardUrlGeneratorState { savedQuery?: string; } +/** + * @deprecated Use dashboard locator instead. + */ export const createDashboardUrlGenerator = ( getStartServices: () => Promise<{ appBasePath: string; diff --git a/src/plugins/discover/public/application/apps/main/components/chart/discover_chart.tsx b/src/plugins/discover/public/application/apps/main/components/chart/discover_chart.tsx index 210313aac53662..f1967d5b10b3ea 100644 --- a/src/plugins/discover/public/application/apps/main/components/chart/discover_chart.tsx +++ b/src/plugins/discover/public/application/apps/main/components/chart/discover_chart.tsx @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import React, { useCallback } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import moment from 'moment'; import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiSpacer } from '@elastic/eui'; import { IUiSettingsClient } from 'kibana/public'; @@ -47,8 +47,21 @@ export function DiscoverChart({ stateContainer: GetStateReturn; timefield?: string; }) { + const chartRef = useRef<{ element: HTMLElement | null; moveFocus: boolean }>({ + element: null, + moveFocus: false, + }); + + useEffect(() => { + if (chartRef.current.moveFocus && chartRef.current.element) { + chartRef.current.element.focus(); + } + }, [state.hideChart]); + const toggleHideChart = useCallback(() => { - stateContainer.setAppState({ hideChart: !state.hideChart }); + const newHideChart = !state.hideChart; + stateContainer.setAppState({ hideChart: newHideChart }); + chartRef.current.moveFocus = !newHideChart; }, [state, stateContainer]); const onChangeInterval = useCallback( @@ -102,9 +115,7 @@ export function DiscoverChart({ { - toggleHideChart(); - }} + onClick={toggleHideChart} data-test-subj="discoverChartToggle" > {!state.hideChart @@ -122,6 +133,8 @@ export function DiscoverChart({ {!state.hideChart && chartData && (
(chartRef.current.element = element)} + tabIndex={-1} aria-label={i18n.translate('discover.histogramOfFoundDocumentsAriaLabel', { defaultMessage: 'Histogram of found documents', })} diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/__snapshots__/discover_field_details_footer.test.tsx.snap b/src/plugins/discover/public/application/apps/main/components/sidebar/__snapshots__/discover_field_details_footer.test.tsx.snap deleted file mode 100644 index f976b961d8520f..00000000000000 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/__snapshots__/discover_field_details_footer.test.tsx.snap +++ /dev/null @@ -1,705 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`discover sidebar field details footer renders properly 1`] = ` - - -
- -
- -
- - - -
-
-
-
-
-
-
-`; diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field.tsx b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field.tsx index 26a3c482e9d3cb..301866c762fbda 100644 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field.tsx +++ b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field.tsx @@ -8,7 +8,7 @@ import './discover_field.scss'; -import React, { useState, useCallback, memo } from 'react'; +import React, { useState, useCallback, memo, useMemo } from 'react'; import { EuiPopover, EuiPopoverTitle, @@ -18,6 +18,7 @@ import { EuiIcon, EuiFlexGroup, EuiFlexItem, + EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { UiCounterMetricType } from '@kbn/analytics'; @@ -27,7 +28,7 @@ import { FieldIcon, FieldButton } from '../../../../../../../kibana_react/public import { FieldDetails } from './types'; import { IndexPatternField, IndexPattern } from '../../../../../../../data/public'; import { getFieldTypeName } from './lib/get_field_type_name'; -import { DiscoverFieldDetailsFooter } from './discover_field_details_footer'; +import { DiscoverFieldVisualize } from './discover_field_visualize'; function wrapOnDot(str?: string) { // u200B is a non-width white-space character, which allows @@ -172,6 +173,7 @@ const MultiFields: React.FC = memo( })} + {multiFields.map((entry) => ( multiFields?.map((f) => f.field), [multiFields]); + if (field.type === '_source') { return ( {multiFields && ( - - )} - {!details.error && ( - + <> + + + )} + )} diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details.scss b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details.scss deleted file mode 100644 index ca48d67f75dec4..00000000000000 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details.scss +++ /dev/null @@ -1,10 +0,0 @@ -.dscFieldDetails { - color: $euiTextColor; - margin-bottom: $euiSizeS; -} - -.dscFieldDetails__visualizeBtn { - @include euiFontSizeXS; - height: $euiSizeL !important; - min-width: $euiSize * 4; -} diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details.test.tsx b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details.test.tsx index a798abb60b833a..8c9ad5bc9708ae 100644 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details.test.tsx +++ b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details.test.tsx @@ -25,10 +25,11 @@ const indexPattern = getStubIndexPattern( ); describe('discover sidebar field details', function () { + const onAddFilter = jest.fn(); const defaultProps = { indexPattern, details: { buckets: [], error: '', exists: 1, total: 2, columns: [] }, - onAddFilter: jest.fn(), + onAddFilter, }; function mountComponent(field: IndexPatternField) { @@ -36,7 +37,7 @@ describe('discover sidebar field details', function () { return mountWithIntl(); } - it('should enable the visualize link for a number field', function () { + it('click on addFilter calls the function', function () { const visualizableField = new IndexPatternField({ name: 'bytes', type: 'number', @@ -47,37 +48,9 @@ describe('discover sidebar field details', function () { aggregatable: true, readFromDocValues: true, }); - const comp = mountComponent(visualizableField); - expect(findTestSubject(comp, 'fieldVisualize-bytes')).toBeTruthy(); - }); - - it('should disable the visualize link for an _id field', function () { - const conflictField = new IndexPatternField({ - name: '_id', - type: 'string', - esTypes: ['_id'], - count: 0, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }); - const comp = mountComponent(conflictField); - expect(findTestSubject(comp, 'fieldVisualize-_id')).toEqual({}); - }); - - it('should disable the visualize link for an unknown field', function () { - const unknownField = new IndexPatternField({ - name: 'test', - type: 'unknown', - esTypes: ['double'], - count: 0, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }); - const comp = mountComponent(unknownField); - expect(findTestSubject(comp, 'fieldVisualize-test')).toEqual({}); + const component = mountComponent(visualizableField); + const onAddButton = findTestSubject(component, 'onAddFilterButton'); + onAddButton.simulate('click'); + expect(onAddFilter).toHaveBeenCalledWith('_exists_', visualizableField.name, '+'); }); }); diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details.tsx b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details.tsx index ffa7b30de5280d..e29799b720e213 100644 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details.tsx +++ b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details.tsx @@ -6,27 +6,18 @@ * Side Public License, v 1. */ -import React, { useState, useEffect } from 'react'; -import { EuiIconTip, EuiText, EuiButton, EuiSpacer } from '@elastic/eui'; +import React from 'react'; +import { EuiText, EuiSpacer, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics'; import { DiscoverFieldBucket } from './discover_field_bucket'; -import { getWarnings } from './lib/get_warnings'; -import { - triggerVisualizeActions, - isFieldVisualizable, - getVisualizeHref, -} from './lib/visualize_trigger_utils'; import { Bucket, FieldDetails } from './types'; import { IndexPatternField, IndexPattern } from '../../../../../../../data/public'; -import './discover_field_details.scss'; interface DiscoverFieldDetailsProps { field: IndexPatternField; indexPattern: IndexPattern; details: FieldDetails; onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; - trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; } export function DiscoverFieldDetails({ @@ -34,46 +25,12 @@ export function DiscoverFieldDetails({ indexPattern, details, onAddFilter, - trackUiMetric, }: DiscoverFieldDetailsProps) { - const warnings = getWarnings(field); - const [showVisualizeLink, setShowVisualizeLink] = useState(false); - const [visualizeLink, setVisualizeLink] = useState(''); - - useEffect(() => { - isFieldVisualizable(field, indexPattern.id, details.columns).then( - (flag) => { - setShowVisualizeLink(flag); - // get href only if Visualize button is enabled - getVisualizeHref(field, indexPattern.id, details.columns).then( - (uri) => { - if (uri) setVisualizeLink(uri); - }, - () => { - setVisualizeLink(''); - } - ); - }, - () => { - setShowVisualizeLink(false); - } - ); - }, [field, indexPattern.id, details.columns]); - - const handleVisualizeLinkClick = (event: React.MouseEvent) => { - // regular link click. let the uiActions code handle the navigation and show popup if needed - event.preventDefault(); - if (trackUiMetric) { - trackUiMetric(METRIC_TYPE.CLICK, 'visualize_link_click'); - } - triggerVisualizeActions(field, indexPattern.id, details.columns); - }; - return ( <> -
- {details.error && {details.error}} - {!details.error && ( + {details.error && {details.error}} + {!details.error && ( + <>
{details.buckets.map((bucket: Bucket, idx: number) => ( ))}
- )} - - {showVisualizeLink && ( - <> - - {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} - handleVisualizeLinkClick(e)} - href={visualizeLink} - size="s" - className="dscFieldDetails__visualizeBtn" - data-test-subj={`fieldVisualize-${field.name}`} - > + + + {!indexPattern.metaFields.includes(field.name) && !field.scripted ? ( + onAddFilter('_exists_', field.name, '+')} + data-test-subj="onAddFilterButton" + > + + + ) : ( - - {warnings.length > 0 && ( - )} - - )} -
+ + + )} ); } diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details_footer.test.tsx b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details_footer.test.tsx deleted file mode 100644 index aa93b2a6637362..00000000000000 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details_footer.test.tsx +++ /dev/null @@ -1,71 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { findTestSubject } from '@elastic/eui/lib/test'; -import { mountWithIntl } from '@kbn/test/jest'; -// @ts-expect-error -import stubbedLogstashFields from '../../../../../__fixtures__/logstash_fields'; -import { coreMock } from '../../../../../../../../core/public/mocks'; -import { IndexPatternField } from '../../../../../../../data/public'; -import { getStubIndexPattern } from '../../../../../../../data/public/test_utils'; -import { DiscoverFieldDetailsFooter } from './discover_field_details_footer'; - -const indexPattern = getStubIndexPattern( - 'logstash-*', - (cfg: unknown) => cfg, - 'time', - stubbedLogstashFields(), - coreMock.createSetup() -); - -describe('discover sidebar field details footer', function () { - const onAddFilter = jest.fn(); - const defaultProps = { - indexPattern, - details: { buckets: [], error: '', exists: 1, total: 2, columns: [] }, - onAddFilter, - }; - - function mountComponent(field: IndexPatternField) { - const compProps = { ...defaultProps, field }; - return mountWithIntl(); - } - - it('renders properly', function () { - const visualizableField = new IndexPatternField({ - name: 'bytes', - type: 'number', - esTypes: ['long'], - count: 10, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }); - const component = mountComponent(visualizableField); - expect(component).toMatchSnapshot(); - }); - - it('click on addFilter calls the function', function () { - const visualizableField = new IndexPatternField({ - name: 'bytes', - type: 'number', - esTypes: ['long'], - count: 10, - scripted: false, - searchable: true, - aggregatable: true, - readFromDocValues: true, - }); - const component = mountComponent(visualizableField); - const onAddButton = findTestSubject(component, 'onAddFilterButton'); - onAddButton.simulate('click'); - expect(onAddFilter).toHaveBeenCalledWith('_exists_', visualizableField.name, '+'); - }); -}); diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details_footer.tsx b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details_footer.tsx deleted file mode 100644 index 148dfc67c3e416..00000000000000 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_details_footer.tsx +++ /dev/null @@ -1,59 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import React from 'react'; -import { EuiLink, EuiPopoverFooter, EuiText } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { IndexPatternField } from '../../../../../../../data/common/index_patterns/fields'; -import { IndexPattern } from '../../../../../../../data/common/index_patterns/index_patterns'; -import { FieldDetails } from './types'; - -interface DiscoverFieldDetailsFooterProps { - field: IndexPatternField; - indexPattern: IndexPattern; - details: FieldDetails; - onAddFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; -} - -export function DiscoverFieldDetailsFooter({ - field, - indexPattern, - details, - onAddFilter, -}: DiscoverFieldDetailsFooterProps) { - return ( - - - {!indexPattern.metaFields.includes(field.name) && !field.scripted ? ( - onAddFilter('_exists_', field.name, '+')} - data-test-subj="onAddFilterButton" - > - - - ) : ( - - )} - - - ); -} diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_visualize.tsx b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_visualize.tsx new file mode 100644 index 00000000000000..baf740531e6bfe --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/sidebar/discover_field_visualize.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 + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { useEffect, useState } from 'react'; +import { EuiButton, EuiPopoverFooter } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics'; +import type { IndexPattern, IndexPatternField } from 'src/plugins/data/common'; + +import { triggerVisualizeActions, VisualizeInformation } from './lib/visualize_trigger_utils'; +import type { FieldDetails } from './types'; +import { getVisualizeInformation } from './lib/visualize_trigger_utils'; + +interface Props { + field: IndexPatternField; + indexPattern: IndexPattern; + details: FieldDetails; + multiFields?: IndexPatternField[]; + trackUiMetric?: (metricType: UiCounterMetricType, eventName: string | string[]) => void; +} + +export const DiscoverFieldVisualize: React.FC = React.memo( + ({ field, indexPattern, details, trackUiMetric, multiFields }) => { + const [visualizeInfo, setVisualizeInfo] = useState(); + + useEffect(() => { + getVisualizeInformation(field, indexPattern.id, details.columns, multiFields).then( + setVisualizeInfo + ); + }, [details.columns, field, indexPattern, multiFields]); + + if (!visualizeInfo) { + return null; + } + + const handleVisualizeLinkClick = (event: React.MouseEvent) => { + // regular link click. let the uiActions code handle the navigation and show popup if needed + event.preventDefault(); + trackUiMetric?.(METRIC_TYPE.CLICK, 'visualize_link_click'); + triggerVisualizeActions(visualizeInfo.field, indexPattern.id, details.columns); + }; + + return ( + + {/* eslint-disable-next-line @elastic/eui/href-or-on-click */} + + + + + ); + } +); diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/lib/get_warnings.ts b/src/plugins/discover/public/application/apps/main/components/sidebar/lib/get_warnings.ts deleted file mode 100644 index 60ce5351e2cd3a..00000000000000 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/lib/get_warnings.ts +++ /dev/null @@ -1,33 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { IndexPatternField } from '../../../../../../../../data/public'; - -export function getWarnings(field: IndexPatternField) { - let warnings = []; - - if (field.scripted) { - warnings.push( - i18n.translate( - 'discover.fieldChooser.discoverField.scriptedFieldsTakeLongExecuteDescription', - { - defaultMessage: 'Scripted fields can take a long time to execute.', - } - ) - ); - } - - if (warnings.length > 1) { - warnings = warnings.map(function (warning, i) { - return (i > 0 ? '\n' : '') + (i + 1) + ' - ' + warning; - }); - } - - return warnings; -} diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/lib/visualize_trigger_utils.test.ts b/src/plugins/discover/public/application/apps/main/components/sidebar/lib/visualize_trigger_utils.test.ts new file mode 100644 index 00000000000000..0a61bf1ea6029b --- /dev/null +++ b/src/plugins/discover/public/application/apps/main/components/sidebar/lib/visualize_trigger_utils.test.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { IndexPatternField } from 'src/plugins/data/common'; +import type { Action } from 'src/plugins/ui_actions/public'; +import { getVisualizeInformation } from './visualize_trigger_utils'; + +const field = { + name: 'fieldName', + type: 'string', + esTypes: ['text'], + count: 1, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + visualizable: true, +} as IndexPatternField; + +const mockGetActions = jest.fn>>, [string, { fieldName: string }]>( + () => Promise.resolve([]) +); + +jest.mock('../../../../../../kibana_services', () => ({ + getUiActions: () => ({ + getTriggerCompatibleActions: mockGetActions, + }), +})); + +const action: Action = { + id: 'action', + type: 'VISUALIZE_FIELD', + getIconType: () => undefined, + getDisplayName: () => 'Action', + isCompatible: () => Promise.resolve(true), + execute: () => Promise.resolve(), +}; + +describe('visualize_trigger_utils', () => { + afterEach(() => { + mockGetActions.mockReset(); + }); + + describe('getVisualizeInformation', () => { + it('should return for a visualizeable field with an action', async () => { + mockGetActions.mockResolvedValue([action]); + const information = await getVisualizeInformation(field, '1', [], undefined); + expect(information).not.toBeUndefined(); + expect(information?.field).toHaveProperty('name', 'fieldName'); + expect(information?.href).toBeUndefined(); + }); + + it('should return field and href from the action', async () => { + mockGetActions.mockResolvedValue([{ ...action, getHref: () => Promise.resolve('hreflink') }]); + const information = await getVisualizeInformation(field, '1', [], undefined); + expect(information).not.toBeUndefined(); + expect(information?.field).toHaveProperty('name', 'fieldName'); + expect(information).toHaveProperty('href', 'hreflink'); + }); + + it('should return undefined if no field has a compatible action', async () => { + mockGetActions.mockResolvedValue([]); + const information = await getVisualizeInformation( + { ...field, name: 'rootField' } as IndexPatternField, + '1', + [], + [ + { ...field, name: 'multi1' }, + { ...field, name: 'multi2' }, + ] as IndexPatternField[] + ); + expect(information).toBeUndefined(); + }); + + it('should return information for the root field, when multi fields and root are having actions', async () => { + mockGetActions.mockResolvedValue([action]); + const information = await getVisualizeInformation( + { ...field, name: 'rootField' } as IndexPatternField, + '1', + [], + [ + { ...field, name: 'multi1' }, + { ...field, name: 'multi2' }, + ] as IndexPatternField[] + ); + expect(information).not.toBeUndefined(); + expect(information?.field).toHaveProperty('name', 'rootField'); + }); + + it('should return information for first multi field that has a compatible action', async () => { + mockGetActions.mockImplementation(async (_, { fieldName }) => { + if (fieldName === 'multi2' || fieldName === 'multi3') { + return [action]; + } + return []; + }); + const information = await getVisualizeInformation( + { ...field, name: 'rootField' } as IndexPatternField, + '1', + [], + [ + { ...field, name: 'multi1' }, + { ...field, name: 'multi2' }, + { ...field, name: 'multi3' }, + ] as IndexPatternField[] + ); + expect(information).not.toBeUndefined(); + expect(information?.field).toHaveProperty('name', 'multi2'); + }); + }); +}); diff --git a/src/plugins/discover/public/application/apps/main/components/sidebar/lib/visualize_trigger_utils.ts b/src/plugins/discover/public/application/apps/main/components/sidebar/lib/visualize_trigger_utils.ts index 2fabaa0ddd1003..f00b430e5acefa 100644 --- a/src/plugins/discover/public/application/apps/main/components/sidebar/lib/visualize_trigger_utils.ts +++ b/src/plugins/discover/public/application/apps/main/components/sidebar/lib/visualize_trigger_utils.ts @@ -41,30 +41,6 @@ async function getCompatibleActions( return compatibleActions; } -export async function getVisualizeHref( - field: IndexPatternField, - indexPatternId: string | undefined, - contextualFields: string[] -) { - if (!indexPatternId) return undefined; - const triggerOptions = { - indexPatternId, - fieldName: field.name, - contextualFields, - trigger: getTrigger(field.type), - }; - const compatibleActions = await getCompatibleActions( - field.name, - indexPatternId, - contextualFields, - getTriggerConstant(field.type) - ); - // enable the link only if only one action is registered - return compatibleActions.length === 1 - ? compatibleActions[0].getHref?.(triggerOptions) - : undefined; -} - export function triggerVisualizeActions( field: IndexPatternField, indexPatternId: string | undefined, @@ -80,21 +56,55 @@ export function triggerVisualizeActions( getUiActions().getTrigger(trigger).exec(triggerOptions); } -export async function isFieldVisualizable( +export interface VisualizeInformation { + field: IndexPatternField; + href?: string; +} + +/** + * Returns the field name and potentially href of the field or the first multi-field + * that has a compatible visualize uiAction. + */ +export async function getVisualizeInformation( field: IndexPatternField, indexPatternId: string | undefined, - contextualFields: string[] -) { + contextualFields: string[], + multiFields: IndexPatternField[] = [] +): Promise { if (field.name === '_id' || !indexPatternId) { - // for first condition you'd get a 'Fielddata access on the _id field is disallowed' error on ES side. - return false; + // _id fields are not visualizeable in ES + return undefined; } - const trigger = getTriggerConstant(field.type); - const compatibleActions = await getCompatibleActions( - field.name, - indexPatternId, - contextualFields, - trigger - ); - return compatibleActions.length > 0 && field.visualizable; + + for (const f of [field, ...multiFields]) { + if (!f.visualizable) { + continue; + } + // Retrieve compatible actions for the specific field + const actions = await getCompatibleActions( + f.name, + indexPatternId, + contextualFields, + getTriggerConstant(f.type) + ); + + // if the field has compatible actions use this field for visualizing + if (actions.length > 0) { + const triggerOptions = { + indexPatternId, + fieldName: f.name, + contextualFields, + trigger: getTrigger(f.type), + }; + + return { + field: f, + // We use the href of the first action always. Multiple actions will only work + // via the modal shown by triggerVisualizeActions that should be called via onClick. + href: await actions[0].getHref?.(triggerOptions), + }; + } + } + + return undefined; } diff --git a/src/plugins/embeddable/public/index.ts b/src/plugins/embeddable/public/index.ts index 25d080dbfd5462..80171e1ad2faba 100644 --- a/src/plugins/embeddable/public/index.ts +++ b/src/plugins/embeddable/public/index.ts @@ -69,6 +69,7 @@ export { EmbeddablePackageState, EmbeddableRenderer, EmbeddableRendererProps, + useEmbeddableFactory, } from './lib'; export { AttributeService, ATTRIBUTE_SERVICE_KEY } from './lib/attribute_service'; diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx index 457852c48ed771..b919672ad01e3c 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.test.tsx @@ -9,14 +9,39 @@ import React from 'react'; import { waitFor } from '@testing-library/dom'; import { render } from '@testing-library/react'; +import { renderHook } from '@testing-library/react-hooks'; import { HelloWorldEmbeddable, HelloWorldEmbeddableFactoryDefinition, HELLO_WORLD_EMBEDDABLE, } from '../../tests/fixtures'; -import { EmbeddableRenderer } from './embeddable_renderer'; +import { EmbeddableRenderer, useEmbeddableFactory } from './embeddable_renderer'; import { embeddablePluginMock } from '../../mocks'; +describe('useEmbeddableFactory', () => { + it('should update upstream value changes', async () => { + const { setup, doStart } = embeddablePluginMock.createInstance(); + const getFactory = setup.registerEmbeddableFactory( + HELLO_WORLD_EMBEDDABLE, + new HelloWorldEmbeddableFactoryDefinition() + ); + doStart(); + + const { result, waitForNextUpdate } = renderHook(() => + useEmbeddableFactory({ factory: getFactory(), input: { id: 'hello' } }) + ); + + const [, loading] = result.current; + + expect(loading).toBe(true); + + await waitForNextUpdate(); + + const [embeddable] = result.current; + expect(embeddable).toBeDefined(); + }); +}); + describe('', () => { test('Render embeddable', () => { const embeddable = new HelloWorldEmbeddable({ id: 'hello' }); diff --git a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx index 153564187d4b52..433b21e92cce55 100644 --- a/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx +++ b/src/plugins/embeddable/public/lib/embeddables/embeddable_renderer.tsx @@ -28,12 +28,6 @@ interface EmbeddableRendererPropsWithEmbeddable { embeddable: IEmbeddable; } -function isWithEmbeddable( - props: EmbeddableRendererProps -): props is EmbeddableRendererPropsWithEmbeddable { - return 'embeddable' in props; -} - interface EmbeddableRendererWithFactory { input: I; onInputUpdated?: (newInput: I) => void; @@ -46,6 +40,72 @@ function isWithFactory( return 'factory' in props; } +export function useEmbeddableFactory({ + input, + factory, + onInputUpdated, +}: EmbeddableRendererWithFactory) { + const [embeddable, setEmbeddable] = useState | ErrorEmbeddable | undefined>( + undefined + ); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(); + const latestInput = React.useRef(input); + useEffect(() => { + latestInput.current = input; + }, [input]); + + useEffect(() => { + let canceled = false; + + // keeping track of embeddables created by this component to be able to destroy them + let createdEmbeddableRef: IEmbeddable | ErrorEmbeddable | undefined; + setEmbeddable(undefined); + setLoading(true); + factory + .create(latestInput.current!) + .then((createdEmbeddable) => { + if (canceled) { + if (createdEmbeddable) { + createdEmbeddable.destroy(); + } + } else { + createdEmbeddableRef = createdEmbeddable; + setEmbeddable(createdEmbeddable); + } + }) + .catch((err) => { + if (canceled) return; + setError(err?.message); + }) + .finally(() => { + if (canceled) return; + setLoading(false); + }); + + return () => { + canceled = true; + if (createdEmbeddableRef) { + createdEmbeddableRef.destroy(); + } + }; + }, [factory]); + + useEffect(() => { + if (!embeddable) return; + if (isErrorEmbeddable(embeddable)) return; + if (!onInputUpdated) return; + const sub = embeddable.getInput$().subscribe((newInput) => { + onInputUpdated(newInput); + }); + return () => { + sub.unsubscribe(); + }; + }, [embeddable, onInputUpdated]); + + return [embeddable, loading, error] as const; +} + /** * Helper react component to render an embeddable * Can be used if you have an embeddable object or an embeddable factory @@ -82,72 +142,22 @@ function isWithFactory( export const EmbeddableRenderer = ( props: EmbeddableRendererProps ) => { - const { input, onInputUpdated } = props; - const [embeddable, setEmbeddable] = useState | ErrorEmbeddable | undefined>( - isWithEmbeddable(props) ? props.embeddable : undefined - ); - const [loading, setLoading] = useState(!isWithEmbeddable(props)); - const [error, setError] = useState(); - const latestInput = React.useRef(props.input); - useEffect(() => { - latestInput.current = input; - }, [input]); - - const factoryFromProps = isWithFactory(props) ? props.factory : undefined; - const embeddableFromProps = isWithEmbeddable(props) ? props.embeddable : undefined; - useEffect(() => { - let canceled = false; - if (embeddableFromProps) { - setEmbeddable(embeddableFromProps); - return; - } - - // keeping track of embeddables created by this component to be able to destroy them - let createdEmbeddableRef: IEmbeddable | ErrorEmbeddable | undefined; - if (factoryFromProps) { - setEmbeddable(undefined); - setLoading(true); - factoryFromProps - .create(latestInput.current!) - .then((createdEmbeddable) => { - if (canceled) { - if (createdEmbeddable) { - createdEmbeddable.destroy(); - } - } else { - createdEmbeddableRef = createdEmbeddable; - setEmbeddable(createdEmbeddable); - } - }) - .catch((err) => { - if (canceled) return; - setError(err?.message); - }) - .finally(() => { - if (canceled) return; - setLoading(false); - }); - } - - return () => { - canceled = true; - if (createdEmbeddableRef) { - createdEmbeddableRef.destroy(); - } - }; - }, [factoryFromProps, embeddableFromProps]); - - useEffect(() => { - if (!embeddable) return; - if (isErrorEmbeddable(embeddable)) return; - if (!onInputUpdated) return; - const sub = embeddable.getInput$().subscribe((newInput) => { - onInputUpdated(newInput); - }); - return () => { - sub.unsubscribe(); - }; - }, [embeddable, onInputUpdated]); + if (isWithFactory(props)) { + return ; + } + return ; +}; +// +const EmbeddableByFactory = ({ + factory, + input, + onInputUpdated, +}: EmbeddableRendererWithFactory) => { + const [embeddable, loading, error] = useEmbeddableFactory({ + factory, + input, + onInputUpdated, + }); return ; }; diff --git a/src/plugins/embeddable/public/lib/embeddables/index.ts b/src/plugins/embeddable/public/lib/embeddables/index.ts index 71dfd73e534e7c..eede745f317941 100644 --- a/src/plugins/embeddable/public/lib/embeddables/index.ts +++ b/src/plugins/embeddable/public/lib/embeddables/index.ts @@ -16,4 +16,8 @@ export { ErrorEmbeddable, isErrorEmbeddable } from './error_embeddable'; export { withEmbeddableSubscription } from './with_subscription'; export { EmbeddableRoot } from './embeddable_root'; export * from '../../../common/lib/saved_object_embeddable'; -export { EmbeddableRenderer, EmbeddableRendererProps } from './embeddable_renderer'; +export { + EmbeddableRenderer, + EmbeddableRendererProps, + useEmbeddableFactory, +} from './embeddable_renderer'; diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx index 22001608f63ab8..1b3e0388e9bb06 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.test.tsx @@ -542,3 +542,40 @@ test('Check when hide header option is true', async () => { const title = findTestSubject(component, `embeddablePanelHeading-HelloAryaStark`); expect(title.length).toBe(0); }); + +test('Should work in minimal way rendering only the inspector action', async () => { + const inspector = inspectorPluginMock.createStartContract(); + inspector.isAvailable = jest.fn(() => true); + + const container = new HelloWorldContainer({ id: '123', panels: {}, viewMode: ViewMode.VIEW }, { + getEmbeddableFactory, + } as any); + + const embeddable = await container.addNewEmbeddable< + ContactCardEmbeddableInput, + ContactCardEmbeddableOutput, + ContactCardEmbeddable + >(CONTACT_CARD_EMBEDDABLE, { + firstName: 'Arya', + lastName: 'Stark', + }); + + const component = mount( + + Promise.resolve([])} + inspector={inspector} + hideHeader={false} + /> + + ); + + findTestSubject(component, 'embeddablePanelToggleMenuIcon').simulate('click'); + expect(findTestSubject(component, `embeddablePanelContextMenuOpen`).length).toBe(1); + await nextTick(); + component.update(); + expect(findTestSubject(component, `embeddablePanelAction-openInspector`).length).toBe(1); + const action = findTestSubject(component, `embeddablePanelAction-ACTION_CUSTOMIZE_PANEL`); + expect(action.length).toBe(0); +}); diff --git a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx index 8cf2de8c807439..b66950c170d698 100644 --- a/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx +++ b/src/plugins/embeddable/public/lib/panel/embeddable_panel.tsx @@ -54,16 +54,20 @@ const removeById = (disabledActions: string[]) => ({ id }: { id: string }) => interface Props { embeddable: IEmbeddable; getActions: UiActionsService['getTriggerCompatibleActions']; - getEmbeddableFactory: EmbeddableStart['getEmbeddableFactory']; - getAllEmbeddableFactories: EmbeddableStart['getEmbeddableFactories']; - overlays: CoreStart['overlays']; - notifications: CoreStart['notifications']; - application: CoreStart['application']; - inspector: InspectorStartContract; - SavedObjectFinder: React.ComponentType; + getEmbeddableFactory?: EmbeddableStart['getEmbeddableFactory']; + getAllEmbeddableFactories?: EmbeddableStart['getEmbeddableFactories']; + overlays?: CoreStart['overlays']; + notifications?: CoreStart['notifications']; + application?: CoreStart['application']; + inspector?: InspectorStartContract; + SavedObjectFinder?: React.ComponentType; stateTransfer?: EmbeddableStateTransfer; hideHeader?: boolean; + actionPredicate?: (actionId: string) => boolean; reportUiCounter?: UsageCollectionStart['reportUiCounter']; + showShadow?: boolean; + showBadges?: boolean; + showNotifications?: boolean; } interface State { @@ -80,7 +84,11 @@ interface State { errorEmbeddable?: ErrorEmbeddable; } -interface PanelUniversalActions { +interface InspectorPanelAction { + inspectPanel: InspectPanelAction; +} + +interface BasePanelActions { customizePanelTitle: CustomizePanelTitleAction; addPanel: AddPanelAction; inspectPanel: InspectPanelAction; @@ -88,6 +96,15 @@ interface PanelUniversalActions { editPanel: EditPanelAction; } +const emptyObject = {}; +type EmptyObject = typeof emptyObject; + +type PanelUniversalActions = + | BasePanelActions + | InspectorPanelAction + | (BasePanelActions & InspectorPanelAction) + | EmptyObject; + export class EmbeddablePanel extends React.Component { private embeddableRoot: React.RefObject; private parentSubscription?: Subscription; @@ -117,10 +134,15 @@ export class EmbeddablePanel extends React.Component { } private async refreshBadges() { + if (!this.mounted) { + return; + } + if (this.props.showBadges === false) { + return; + } let badges = await this.props.getActions(PANEL_BADGE_TRIGGER, { embeddable: this.props.embeddable, }); - if (!this.mounted) return; const { disabledActions } = this.props.embeddable.getInput(); if (disabledActions) { @@ -135,10 +157,15 @@ export class EmbeddablePanel extends React.Component { } private async refreshNotifications() { + if (!this.mounted) { + return; + } + if (this.props.showNotifications === false) { + return; + } let notifications = await this.props.getActions(PANEL_NOTIFICATION_TRIGGER, { embeddable: this.props.embeddable, }); - if (!this.mounted) return; const { disabledActions } = this.props.embeddable.getInput(); if (disabledActions) { @@ -229,13 +256,18 @@ export class EmbeddablePanel extends React.Component { paddingSize="none" role="figure" aria-labelledby={headerId} + hasShadow={this.props.showShadow} > {!this.props.hideHeader && ( { }; private getUniversalActions = (): PanelUniversalActions => { + let actions = {}; + if (this.props.inspector) { + actions = { + inspectPanel: new InspectPanelAction(this.props.inspector), + }; + } + if ( + !this.props.getEmbeddableFactory || + !this.props.getAllEmbeddableFactories || + !this.props.overlays || + !this.props.notifications || + !this.props.SavedObjectFinder || + !this.props.application + ) { + return actions; + } + const createGetUserData = (overlays: OverlayStart) => async function getUserData(context: { embeddable: IEmbeddable }) { return new Promise<{ title: string | undefined; hideTitle?: boolean }>((resolve) => { @@ -308,6 +357,7 @@ export class EmbeddablePanel extends React.Component { // Universal actions are exposed on the context menu for every embeddable, they bypass the trigger // registry. return { + ...actions, customizePanelTitle: new CustomizePanelTitleAction(createGetUserData(this.props.overlays)), addPanel: new AddPanelAction( this.props.getEmbeddableFactory, @@ -317,7 +367,6 @@ export class EmbeddablePanel extends React.Component { this.props.SavedObjectFinder, this.props.reportUiCounter ), - inspectPanel: new InspectPanelAction(this.props.inspector), removePanel: new RemovePanelAction(), editPanel: new EditPanelAction( this.props.getEmbeddableFactory, @@ -338,9 +387,13 @@ export class EmbeddablePanel extends React.Component { regularActions = regularActions.filter(removeDisabledActions); } - const sortedActions = [...regularActions, ...Object.values(this.state.universalActions)].sort( - sortByOrderField - ); + let sortedActions = regularActions + .concat(Object.values(this.state.universalActions || {}) as Array>) + .sort(sortByOrderField); + + if (this.props.actionPredicate) { + sortedActions = sortedActions.filter(({ id }) => this.props.actionPredicate!(id)); + } return await buildContextMenuForActions({ actions: sortedActions.map((action) => ({ diff --git a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx index 403aa3e3f4c9f7..742a2d19099418 100644 --- a/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx +++ b/src/plugins/embeddable/public/lib/panel/panel_header/panel_header.tsx @@ -36,7 +36,7 @@ export interface PanelHeaderProps { embeddable: IEmbeddable; headerId?: string; showPlaceholderTitle?: boolean; - customizeTitle: CustomizePanelTitleAction; + customizeTitle?: CustomizePanelTitleAction; } function renderBadges(badges: Array>, embeddable: IEmbeddable) { @@ -177,7 +177,7 @@ export function PanelHeader({ > {title || placeholderTitle} - ) : ( + ) : customizeTitle ? ( {title || placeholderTitle} - ); + ) : null; } return description ? ( ({ input, factory, onInputUpdated, }: EmbeddableRendererWithFactory): readonly [ErrorEmbeddable | IEmbeddable | undefined, boolean, string | undefined]; + // Warning: (ae-missing-release-tag) "VALUE_CLICK_TRIGGER" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap b/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap index b949fa7995d303..153438b34eb475 100644 --- a/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap +++ b/src/plugins/home/public/application/components/__snapshots__/home.test.js.snap @@ -1,819 +1,707 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`home change home route should render a link to change the default route in advanced settings if advanced settings is enabled 1`] = ` -
- + />, + "rightSideItems": Array [], } - /> -
+ - - - - - - - - -
-
+ + + + + + + +
); @@ -120,4 +127,6 @@ Instruction.propTypes = { textPre: PropTypes.string, replaceTemplateStrings: PropTypes.func.isRequired, customComponentName: PropTypes.string, + variantId: PropTypes.string, + isCloudEnabled: PropTypes.bool.isRequired, }; diff --git a/src/plugins/home/public/application/components/tutorial/instruction_set.js b/src/plugins/home/public/application/components/tutorial/instruction_set.js index da368120d493c8..08b55a527b3cf5 100644 --- a/src/plugins/home/public/application/components/tutorial/instruction_set.js +++ b/src/plugins/home/public/application/components/tutorial/instruction_set.js @@ -187,6 +187,8 @@ class InstructionSetUi extends React.Component { textPost={instruction.textPost} replaceTemplateStrings={this.props.replaceTemplateStrings} customComponentName={instruction.customComponentName} + variantId={instructionVariant.id} + isCloudEnabled={this.props.isCloudEnabled} /> ); return { @@ -320,6 +322,7 @@ InstructionSetUi.propTypes = { paramValues: PropTypes.object.isRequired, setParameter: PropTypes.func, replaceTemplateStrings: PropTypes.func.isRequired, + isCloudEnabled: PropTypes.bool.isRequired, }; export const InstructionSet = injectI18n(InstructionSetUi); diff --git a/src/plugins/home/public/application/components/tutorial/instruction_set.test.js b/src/plugins/home/public/application/components/tutorial/instruction_set.test.js index 539732a1c51a9c..1bce4f72fde607 100644 --- a/src/plugins/home/public/application/components/tutorial/instruction_set.test.js +++ b/src/plugins/home/public/application/components/tutorial/instruction_set.test.js @@ -49,6 +49,7 @@ test('render', () => { offset={1} paramValues={{}} replaceTemplateStrings={() => {}} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -74,6 +75,7 @@ describe('statusCheckState', () => { statusCheckConfig={statusCheckConfig} replaceTemplateStrings={() => {}} statusCheckState={StatusCheckStates.FETCHING} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -90,6 +92,7 @@ describe('statusCheckState', () => { statusCheckConfig={statusCheckConfig} replaceTemplateStrings={() => {}} statusCheckState={StatusCheckStates.FETCHING} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -106,6 +109,7 @@ describe('statusCheckState', () => { statusCheckConfig={statusCheckConfig} replaceTemplateStrings={() => {}} statusCheckState={StatusCheckStates.ERROR} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -122,6 +126,7 @@ describe('statusCheckState', () => { statusCheckConfig={statusCheckConfig} replaceTemplateStrings={() => {}} statusCheckState={StatusCheckStates.NO_DATA} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line @@ -138,6 +143,7 @@ describe('statusCheckState', () => { statusCheckConfig={statusCheckConfig} replaceTemplateStrings={() => {}} statusCheckState={StatusCheckStates.HAS_DATA} + isCloudEnabled={false} /> ); expect(component).toMatchSnapshot(); // eslint-disable-line diff --git a/src/plugins/home/public/application/components/tutorial/tutorial.js b/src/plugins/home/public/application/components/tutorial/tutorial.js index 92bbb92fa08507..52daa53d4585c8 100644 --- a/src/plugins/home/public/application/components/tutorial/tutorial.js +++ b/src/plugins/home/public/application/components/tutorial/tutorial.js @@ -301,6 +301,7 @@ class TutorialUi extends React.Component { setParameter={this.setParameter} replaceTemplateStrings={this.props.replaceTemplateStrings} key={index} + isCloudEnabled={this.props.isCloudEnabled} /> ); }); diff --git a/src/plugins/kibana_overview/public/components/_overview.scss b/src/plugins/kibana_overview/public/components/_overview.scss index 12e2d9cd921ec7..89f2b6272a4b8f 100644 --- a/src/plugins/kibana_overview/public/components/_overview.scss +++ b/src/plugins/kibana_overview/public/components/_overview.scss @@ -1,18 +1,5 @@ -@import '../../../../core/public/mixins'; - -.kbnOverviewWrapper { - @include kibanaFullBodyMinHeight(); - background-color: $euiColorEmptyShade; - display: flex; - flex-direction: column; -} - -.kbnOverviewContent { - margin: 0 auto; - max-width: 1200px; - padding: $euiSizeXL $euiSize; - width: 100%; - +.kbnOverviewApps__item, +.kbnOverviewMore__item { // Ensure card heights are stretched equally when wrapped with this element .kbnRedirectCrossAppLinks { align-items: flex-start; @@ -109,11 +96,6 @@ } } -// Accounting for no `flush="both"` prop on EuiButtonEmpty -.kbnOverviewDataAdd__actionButton { - margin-right: 0; -} - .kbnOverviewDataManage__item:not(:only-child) { @include euiBreakpoint('m', 'l', 'xl') { flex: 0 0 calc(50% - #{$euiSizeM * 2}); diff --git a/src/plugins/kibana_overview/public/components/add_data/__snapshots__/add_data.test.tsx.snap b/src/plugins/kibana_overview/public/components/add_data/__snapshots__/add_data.test.tsx.snap index 25538d2eda2871..fb32d2814c0fea 100644 --- a/src/plugins/kibana_overview/public/components/add_data/__snapshots__/add_data.test.tsx.snap +++ b/src/plugins/kibana_overview/public/components/add_data/__snapshots__/add_data.test.tsx.snap @@ -32,7 +32,7 @@ exports[`AddData render 1`] = `
= ({ addBasePath, features }) => {
= ({ addBasePath, isDarkTheme, apps }) => } layout="horizontal" paddingSize="none" diff --git a/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap b/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap index 2e7dc9a7ddc604..a32e27050fad17 100644 --- a/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap +++ b/src/plugins/kibana_overview/public/components/overview/__snapshots__/overview.test.tsx.snap @@ -1,10 +1,234 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Overview render 1`] = ` -
, + "rightSideItems": Array [], + } + } + template="empty" > +
+ +

+ +

+
+ + +
+
-`; - -exports[`Overview without features 1`] = ` -
+ + + } + image="/plugins/kibanaOverview/assets/solutions_solution_2_2x.png" + onClick={[Function]} + title="Solution two" + titleElement="h3" + titleSize="xs" + /> + + + + + + } + image="/plugins/kibanaOverview/assets/solutions_solution_3_2x.png" + onClick={[Function]} + title="Solution three" + titleElement="h3" + titleSize="xs" + /> + + + + + + } + image="/plugins/kibanaOverview/assets/solutions_solution_4_2x.png" + onClick={[Function]} + title="Solution four" + titleElement="h3" + titleSize="xs" + /> + + + + + + +
-`; - -exports[`Overview without solutions 1`] = ` -
+ + + + + + + +
+ `; diff --git a/src/plugins/kibana_overview/public/components/overview/overview.test.tsx b/src/plugins/kibana_overview/public/components/overview/overview.test.tsx index 6f97b04a6fdf19..9d260469625ada 100644 --- a/src/plugins/kibana_overview/public/components/overview/overview.test.tsx +++ b/src/plugins/kibana_overview/public/components/overview/overview.test.tsx @@ -21,8 +21,8 @@ jest.mock('../../../../../../src/plugins/kibana_react/public', () => ({ }, }), RedirectAppLinks: jest.fn((element: JSX.Element) => element), + overviewPageActions: jest.fn().mockReturnValue([]), OverviewPageFooter: jest.fn().mockReturnValue(<>), - OverviewPageHeader: jest.fn().mockReturnValue(<>), })); jest.mock('../../lib/ui_metric', () => ({ diff --git a/src/plugins/kibana_overview/public/components/overview/overview.tsx b/src/plugins/kibana_overview/public/components/overview/overview.tsx index 68c52b0395591d..9bba923371f640 100644 --- a/src/plugins/kibana_overview/public/components/overview/overview.tsx +++ b/src/plugins/kibana_overview/public/components/overview/overview.tsx @@ -23,8 +23,9 @@ import { CoreStart } from 'kibana/public'; import { RedirectAppLinks, useKibana, + KibanaPageTemplate, + overviewPageActions, OverviewPageFooter, - OverviewPageHeader, } from '../../../../../../src/plugins/kibana_react/public'; import { FetchResult } from '../../../../../../src/plugins/newsfeed/public'; import { @@ -116,147 +117,149 @@ export const Overview: FC = ({ newsFetchResult, solutions, features }) => const remainingApps = kibanaApps.map(({ id }) => id).filter((id) => !mainApps.includes(id)); return ( -
- } - /> - -
- {isNewKibanaInstance ? ( - - ) : ( - <> -
- -

- -

-
- - {mainApps.length ? ( - <> - - {mainApps.map(renderAppCard)} - - - - - ) : null} + , + rightSideItems: overviewPageActions({ + addBasePath, + application, + hidden: isNewKibanaInstance, + }), + }} + template="empty" + > + {isNewKibanaInstance ? ( + + ) : ( + <> +
+ +

+ +

+
- {remainingApps.length ? ( + {mainApps.length ? ( + <> - {remainingApps.map(renderAppCard)} + {mainApps.map(renderAppCard)} - ) : null} -
-
- - {solutions.length ? ( -
- -

- -

-
+
+ ) : ( + + + + - + )} +
+ + + )} - { - trackUiMetric(METRIC_TYPE.CLICK, 'set_kibana_overview_as_default_route'); - }} - onChangeDefaultRoute={() => { - trackUiMetric(METRIC_TYPE.CLICK, 'change_to_different_default_route'); - }} - /> -
-
+