[Security Solutions] Side Navigation phase 2 (#103275)
* [SecuritySolutions] [Navigation] Prepare new routing and migrate overview (#101733) * prepare new routing and migrate overview * test fix and todo comments identified * telemetry using app views * navigation groups implemented * cleaning * export subplugin routes as route props array * [Security Solution][Navigation] Migrate Security Solutions 'explore' tab group to deep link navigation (#102306) * Update navigateToApp and getUrlForApp to provide the deepLinkId * Update Hosts and Network routes to start from /hosts and /network * Add Hosts and Network to side nav menu under "Explore" menu group * Delete Hosts and Network old menu code * Fix broken tests * [SecuritySolution] Add detections subplugin to deeplink (#101791) * prepare new routing and migrate overview * init nav deeplink * split detections into rules and alerts * init exception link * init detections * link to rules creation page * link to rules creation page * rename detections to alerts * fix unit tests * fix rules creation page * remove console * fix lint error * fix unit tests * fix unit tests * isolating rules and exceptions page * replace history push with navigateToApp * fix unit test * temporary fix for createCoreStartMock * update cypress * skip failing cypress * skip failing cypress Co-authored-by: semd <sergi.massaneda@elastic.co> * Migrate "Investigate" tab group to new side navigation (#102705) * Migrate "Investigate" tab group to new side navigation It includes: * Timelines * Cases * Quick fix useFormatUrl and HeaderPage navigation * [Security Solutions] Management navigation (#102685) * prepare new routing and migrate overview * test fix and todo comments identified * telemetry using app views * navigation groups implemented * cleaning * export subplugin routes as route props array * breadcrumbs changes and sidenav generation improvements * jest tests for breadcrumbs and navigation changes * retrocompatibility for sections that are not yet migrated to deepLinks * management deepLinks and plugin refactoring * home navigation changes * management navigation migrated to deeplinks * jest tests fixed * header page back link improved and tests fixed * type errors fixes * improve home navigation encapsulation Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> * Fix type checking * export header page * fix padding * add redirect routes * unskip detection cypress * fix i18n * fix create your own rules btn * fix cancel button on rules creation page * test fixes * fix breadcrumbs for rules pages * unit test fixes * additional fixes * [Security Solutions] Navigation usage tracker and general changes (#103271) * [Security Solutions] use of currentAppId$ migrated. and some small fixes * unused constants removed * remove unused constant * test fix and types * fix cypress * fix cypress tests * Fix case navTab permission and tests * Revert 'timeline.isOpen' breadcrumb code that was deleted during merge * Fix useInsertTimeline test by removing '/' * change global navigation visible deeplinks * fix /admininstration top level redirect to * fix global search icon, nav order and overview hosts link * update start a new case link * fix rules link in exception list table * unskip cypress tests * update rules link * fix full screen timeline * fixing broken links and administration telemetry split * remove unused comments * remove timeline z-index and cleanup global header component * some minor fixes * add unit tests for detections breadcrumbs * remove case to global/search nav when cases is none * rename test scenario * fix side_panel flyout * fix cases use cases between search/gobal nav * timeline snapshot regenerated and cypres test fixed * rollback management tracking split as it causes unexpected errors on the telemetry component Co-authored-by: Pablo Machado <pablo.nevesmachado@elastic.co> Co-authored-by: Angela Chuang <6295984+angorayc@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Michael Olorunnisola <michael.olorunnisola@elastic.co> Co-authored-by: Angela Chuang <yi-chun.chuang@elastic.co> Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>
This commit is contained in:
parent
3b5bd02347
commit
85709925cc
|
@ -157,14 +157,7 @@ export const applicationUsageSchema = {
|
|||
security_login: commonSchema,
|
||||
security_logout: commonSchema,
|
||||
security_overwritten_session: commonSchema,
|
||||
securitySolution: commonSchema, // It's a forward app so we'll likely never report it
|
||||
'securitySolution:overview': commonSchema,
|
||||
'securitySolution:detections': commonSchema,
|
||||
'securitySolution:hosts': commonSchema,
|
||||
'securitySolution:network': commonSchema,
|
||||
'securitySolution:timelines': commonSchema,
|
||||
'securitySolution:case': commonSchema,
|
||||
'securitySolution:administration': commonSchema,
|
||||
securitySolution: commonSchema,
|
||||
siem: commonSchema,
|
||||
space_selector: commonSchema,
|
||||
uptime: commonSchema,
|
||||
|
|
|
@ -5280,923 +5280,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"securitySolution:overview": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "Always `main`"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 90 days"
|
||||
}
|
||||
},
|
||||
"views": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application view being tracked"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application sub view since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application sub view is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securitySolution:detections": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "Always `main`"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 90 days"
|
||||
}
|
||||
},
|
||||
"views": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application view being tracked"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application sub view since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application sub view is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securitySolution:hosts": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "Always `main`"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 90 days"
|
||||
}
|
||||
},
|
||||
"views": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application view being tracked"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application sub view since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application sub view is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securitySolution:network": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "Always `main`"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 90 days"
|
||||
}
|
||||
},
|
||||
"views": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application view being tracked"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application sub view since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application sub view is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securitySolution:timelines": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "Always `main`"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 90 days"
|
||||
}
|
||||
},
|
||||
"views": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application view being tracked"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application sub view since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application sub view is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securitySolution:case": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "Always `main`"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 90 days"
|
||||
}
|
||||
},
|
||||
"views": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application view being tracked"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application sub view since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application sub view is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securitySolution:administration": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "Always `main`"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen over the last 90 days"
|
||||
}
|
||||
},
|
||||
"views": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application being tracked"
|
||||
}
|
||||
},
|
||||
"viewId": {
|
||||
"type": "keyword",
|
||||
"_meta": {
|
||||
"description": "The application view being tracked"
|
||||
}
|
||||
},
|
||||
"clicks_total": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the application sub view since we started counting them"
|
||||
}
|
||||
},
|
||||
"clicks_7_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"clicks_30_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"clicks_90_days": {
|
||||
"type": "long",
|
||||
"_meta": {
|
||||
"description": "General number of clicks in the active application sub view over the last 90 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_total": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application sub view is active and on-screen since we started counting them."
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_7_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 7 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_30_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 30 days"
|
||||
}
|
||||
},
|
||||
"minutes_on_screen_90_days": {
|
||||
"type": "float",
|
||||
"_meta": {
|
||||
"description": "Minutes the application is active and on-screen active application sub view over the last 90 days"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"siem": {
|
||||
"properties": {
|
||||
"appId": {
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
|
||||
import { EuiLink } from '@elastic/eui';
|
||||
import * as i18n from '../translations';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { LinkAnchor } from '../../links';
|
||||
|
||||
const NoCasesComponent = ({
|
||||
createCaseHref,
|
||||
|
@ -17,13 +18,22 @@ const NoCasesComponent = ({
|
|||
createCaseHref: string;
|
||||
hasWritePermissions: boolean;
|
||||
}) => {
|
||||
const { navigateToUrl } = useKibana().services.application;
|
||||
const goToCaseCreation = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
navigateToUrl(createCaseHref);
|
||||
},
|
||||
[createCaseHref, navigateToUrl]
|
||||
);
|
||||
return hasWritePermissions ? (
|
||||
<>
|
||||
<span>{i18n.NO_CASES}</span>
|
||||
<EuiLink
|
||||
<LinkAnchor
|
||||
data-test-subj="no-cases-create-case"
|
||||
onClick={goToCaseCreation}
|
||||
href={createCaseHref}
|
||||
>{` ${i18n.START_A_NEW_CASE}`}</EuiLink>
|
||||
>{` ${i18n.START_A_NEW_CASE}`}</LinkAnchor>
|
||||
{'!'}
|
||||
</>
|
||||
) : (
|
||||
|
|
|
@ -62,29 +62,57 @@ export const DEFAULT_INDICATOR_SOURCE_PATH = 'threatintel.indicator';
|
|||
export const INDICATOR_DESTINATION_PATH = 'threat.indicator';
|
||||
|
||||
export enum SecurityPageName {
|
||||
detections = 'detections',
|
||||
overview = 'overview',
|
||||
detections = 'detections',
|
||||
alerts = 'alerts',
|
||||
rules = 'rules',
|
||||
exceptions = 'exceptions',
|
||||
hosts = 'hosts',
|
||||
network = 'network',
|
||||
timelines = 'timelines',
|
||||
case = 'case',
|
||||
administration = 'administration',
|
||||
endpoints = 'endpoints',
|
||||
policies = 'policies',
|
||||
trustedApps = 'trusted_apps',
|
||||
eventFilters = 'event_filters',
|
||||
}
|
||||
|
||||
/**
|
||||
* The ID of the cases plugin
|
||||
*/
|
||||
export const CASES_APP_ID = `${APP_ID}:${SecurityPageName.case}`;
|
||||
export enum SecurityPageGroupName {
|
||||
detect = 'detect',
|
||||
explore = 'explore',
|
||||
investigate = 'investigate',
|
||||
manage = 'manage',
|
||||
}
|
||||
|
||||
export const APP_OVERVIEW_PATH = `${APP_PATH}/overview`;
|
||||
export const APP_DETECTIONS_PATH = `${APP_PATH}/detections`;
|
||||
export const APP_HOSTS_PATH = `${APP_PATH}/hosts`;
|
||||
export const APP_NETWORK_PATH = `${APP_PATH}/network`;
|
||||
export const APP_TIMELINES_PATH = `${APP_PATH}/timelines`;
|
||||
export const APP_CASES_PATH = `${APP_PATH}/cases`;
|
||||
export const APP_MANAGEMENT_PATH = `${APP_PATH}/administration`;
|
||||
export const TIMELINES_PATH = '/timelines';
|
||||
export const CASES_PATH = '/cases';
|
||||
export const OVERVIEW_PATH = '/overview';
|
||||
export const DETECTIONS_PATH = '/detections';
|
||||
export const ALERTS_PATH = '/alerts';
|
||||
export const RULES_PATH = '/rules';
|
||||
export const EXCEPTIONS_PATH = '/exceptions';
|
||||
export const HOSTS_PATH = '/hosts';
|
||||
export const NETWORK_PATH = '/network';
|
||||
export const MANAGEMENT_PATH = '/administration';
|
||||
export const ENDPOINTS_PATH = `${MANAGEMENT_PATH}/endpoints`;
|
||||
export const TRUSTED_APPS_PATH = `${MANAGEMENT_PATH}/trusted_apps`;
|
||||
export const EVENT_FILTERS_PATH = `${MANAGEMENT_PATH}/event_filters`;
|
||||
|
||||
export const DETECTIONS_SUB_PLUGIN_ID = `${APP_ID}:${SecurityPageName.detections}`;
|
||||
export const APP_OVERVIEW_PATH = `${APP_PATH}${OVERVIEW_PATH}`;
|
||||
export const APP_MANAGEMENT_PATH = `${APP_PATH}${MANAGEMENT_PATH}`;
|
||||
|
||||
export const APP_ALERTS_PATH = `${APP_PATH}${ALERTS_PATH}`;
|
||||
export const APP_RULES_PATH = `${APP_PATH}${RULES_PATH}`;
|
||||
export const APP_EXCEPTIONS_PATH = `${APP_PATH}${EXCEPTIONS_PATH}`;
|
||||
|
||||
export const APP_HOSTS_PATH = `${APP_PATH}${HOSTS_PATH}`;
|
||||
export const APP_NETWORK_PATH = `${APP_PATH}${NETWORK_PATH}`;
|
||||
export const APP_TIMELINES_PATH = `${APP_PATH}${TIMELINES_PATH}`;
|
||||
export const APP_CASES_PATH = `${APP_PATH}${CASES_PATH}`;
|
||||
export const APP_ENDPOINTS_PATH = `${APP_PATH}${ENDPOINTS_PATH}`;
|
||||
export const APP_TRUSTED_APPS_PATH = `${APP_PATH}${TRUSTED_APPS_PATH}`;
|
||||
export const APP_EVENT_FILTERS_PATH = `${APP_PATH}${EVENT_FILTERS_PATH}`;
|
||||
|
||||
/** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */
|
||||
export const DEFAULT_INDEX_PATTERN = [
|
||||
|
|
|
@ -21,11 +21,13 @@ import { createCase } from '../../tasks/api_calls/cases';
|
|||
|
||||
describe('attach timeline to case', () => {
|
||||
context('without cases created', () => {
|
||||
beforeEach(() => {
|
||||
beforeEach((done) => {
|
||||
cleanKibana();
|
||||
createTimeline(timeline).then((response) =>
|
||||
cy.wrap(response.body.data.persistTimeline.timeline).as('myTimeline')
|
||||
);
|
||||
|
||||
createTimeline(timeline).then((response) => {
|
||||
cy.wrap(response.body.data.persistTimeline.timeline).as('myTimeline');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('attach timeline to a new case', function () {
|
||||
|
|
|
@ -20,17 +20,17 @@ import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
|||
|
||||
import { unmappedRule } from '../../objects/rule';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Alert details with unmapped fields', () => {
|
||||
beforeEach(() => {
|
||||
cleanKibana();
|
||||
esArchiverLoad('unmapped_fields');
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRuleActivated(unmappedRule);
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
expandFirstAlert();
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { ROLES } from '../../../common/test';
|
||||
import { DETECTIONS_RULE_MANAGEMENT_URL, DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { DETECTIONS_RULE_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation';
|
||||
import { newRule } from '../../objects/rule';
|
||||
import { PAGE_TITLE } from '../../screens/common/page';
|
||||
|
||||
|
@ -37,7 +37,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr
|
|||
// First, we have to open the app on behalf of a privileged user in order to initialize it.
|
||||
// Otherwise the app will be disabled and show a "welcome"-like page.
|
||||
cleanKibana();
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.platform_engineer);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL, ROLES.platform_engineer);
|
||||
waitForAlertsIndexToBeCreated();
|
||||
|
||||
// After that we can login as a soc manager.
|
||||
|
@ -57,7 +57,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr
|
|||
});
|
||||
context('On Detections home page', () => {
|
||||
beforeEach(() => {
|
||||
loadPageAsPlatformEngineerUser(DETECTIONS_URL);
|
||||
loadPageAsPlatformEngineerUser(ALERTS_URL);
|
||||
});
|
||||
|
||||
it('We show the need admin primary callout', () => {
|
||||
|
@ -107,7 +107,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr
|
|||
});
|
||||
context('On Detections home page', () => {
|
||||
beforeEach(() => {
|
||||
loadPageAsPlatformEngineerUser(DETECTIONS_URL);
|
||||
loadPageAsPlatformEngineerUser(ALERTS_URL);
|
||||
});
|
||||
|
||||
it('We show the need admin primary callout', () => {
|
||||
|
@ -157,7 +157,7 @@ describe('Detections > Need Admin Callouts indicating an admin is needed to migr
|
|||
});
|
||||
context('On Detections home page', () => {
|
||||
beforeEach(() => {
|
||||
loadPageAsPlatformEngineerUser(DETECTIONS_URL);
|
||||
loadPageAsPlatformEngineerUser(ALERTS_URL);
|
||||
});
|
||||
|
||||
it('We show the need admin primary callout', () => {
|
||||
|
|
|
@ -15,11 +15,11 @@ import { waitForAlertsToPopulate } from '../../tasks/create_new_rule';
|
|||
import { login, loginAndWaitForPage, waitForPageWithoutDateRange } from '../../tasks/login';
|
||||
import { refreshPage } from '../../tasks/security_header';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
import { ATTACH_ALERT_TO_CASE_BUTTON } from '../../screens/alerts_detection_rules';
|
||||
|
||||
const loadDetectionsPage = (role: ROLES) => {
|
||||
waitForPageWithoutDateRange(DETECTIONS_URL, role);
|
||||
waitForPageWithoutDateRange(ALERTS_URL, role);
|
||||
waitForAlertsToPopulate();
|
||||
};
|
||||
|
||||
|
@ -27,7 +27,7 @@ describe('Alerts timeline', () => {
|
|||
before(() => {
|
||||
// First we login as a privileged user to create alerts.
|
||||
cleanKibana();
|
||||
loginAndWaitForPage(DETECTIONS_URL, ROLES.platform_engineer);
|
||||
loginAndWaitForPage(ALERTS_URL, ROLES.platform_engineer);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRuleActivated(newRule);
|
||||
|
|
|
@ -31,12 +31,12 @@ import { waitForAlertsToPopulate } from '../../tasks/create_new_rule';
|
|||
import { loginAndWaitForPage } from '../../tasks/login';
|
||||
import { refreshPage } from '../../tasks/security_header';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Closing alerts', () => {
|
||||
beforeEach(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPage(DETECTIONS_URL);
|
||||
loginAndWaitForPage(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRuleActivated(newRule, '1', '100m', 100);
|
||||
|
|
|
@ -28,12 +28,12 @@ import { waitForAlertsToPopulate } from '../../tasks/create_new_rule';
|
|||
import { loginAndWaitForPage } from '../../tasks/login';
|
||||
import { refreshPage } from '../../tasks/security_header';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Marking alerts as in-progress', () => {
|
||||
beforeEach(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPage(DETECTIONS_URL);
|
||||
loginAndWaitForPage(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRuleActivated(newRule);
|
||||
|
|
|
@ -19,12 +19,12 @@ import { waitForAlertsToPopulate } from '../../tasks/create_new_rule';
|
|||
import { loginAndWaitForPage } from '../../tasks/login';
|
||||
import { refreshPage } from '../../tasks/security_header';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Alerts timeline', () => {
|
||||
beforeEach(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPage(DETECTIONS_URL);
|
||||
loginAndWaitForPage(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRuleActivated(newRule);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { ROLES } from '../../../common/test';
|
||||
import { DETECTIONS_RULE_MANAGEMENT_URL, DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { DETECTIONS_RULE_MANAGEMENT_URL, ALERTS_URL } from '../../urls/navigation';
|
||||
import { newRule } from '../../objects/rule';
|
||||
import { PAGE_TITLE } from '../../screens/common/page';
|
||||
|
||||
|
@ -47,7 +47,7 @@ describe('Detections > Callouts', () => {
|
|||
// First, we have to open the app on behalf of a privileged user in order to initialize it.
|
||||
// Otherwise the app will be disabled and show a "welcome"-like page.
|
||||
cleanKibana();
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.platform_engineer);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL, ROLES.platform_engineer);
|
||||
waitForAlertsIndexToBeCreated();
|
||||
|
||||
// After that we can login as a read-only user.
|
||||
|
@ -57,7 +57,7 @@ describe('Detections > Callouts', () => {
|
|||
context('indicating read-only access to resources', () => {
|
||||
context('On Detections home page', () => {
|
||||
beforeEach(() => {
|
||||
loadPageAsReadOnlyUser(DETECTIONS_URL);
|
||||
loadPageAsReadOnlyUser(ALERTS_URL);
|
||||
});
|
||||
|
||||
it('We show one primary callout', () => {
|
||||
|
@ -125,7 +125,7 @@ describe('Detections > Callouts', () => {
|
|||
context('indicating read-write access to resources', () => {
|
||||
context('On Detections home page', () => {
|
||||
beforeEach(() => {
|
||||
loadPageAsPlatformEngineer(DETECTIONS_URL);
|
||||
loadPageAsPlatformEngineer(ALERTS_URL);
|
||||
});
|
||||
|
||||
it('We show no callout', () => {
|
||||
|
|
|
@ -29,12 +29,12 @@ import { waitForAlertsToPopulate } from '../../tasks/create_new_rule';
|
|||
import { loginAndWaitForPage } from '../../tasks/login';
|
||||
import { refreshPage } from '../../tasks/security_header';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Opening alerts', () => {
|
||||
beforeEach(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPage(DETECTIONS_URL);
|
||||
loginAndWaitForPage(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRuleActivated(newRule);
|
||||
|
|
|
@ -110,7 +110,7 @@ import { saveEditedRule, waitForKibana } from '../../tasks/edit_rule';
|
|||
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
import { activatesRule } from '../../tasks/rule_details';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Custom detection rules creation', () => {
|
||||
const expectedUrls = newRule.referenceUrls.join('');
|
||||
|
@ -133,7 +133,7 @@ describe('Custom detection rules creation', () => {
|
|||
});
|
||||
|
||||
it('Creates and activates a new rule', function () {
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
goToManageAlertsDetectionRules();
|
||||
|
@ -226,7 +226,7 @@ describe('Custom detection rules deletion and edition', () => {
|
|||
context('Deletion', () => {
|
||||
beforeEach(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
goToManageAlertsDetectionRules();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRuleActivated(newRule, 'rule1');
|
||||
|
@ -302,7 +302,7 @@ describe('Custom detection rules deletion and edition', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
goToManageAlertsDetectionRules();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRuleActivated(existingRule, 'rule1');
|
||||
|
|
|
@ -75,7 +75,7 @@ import {
|
|||
} from '../../tasks/create_new_rule';
|
||||
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Detection rules, EQL', () => {
|
||||
const expectedUrls = eqlRule.referenceUrls.join('');
|
||||
|
@ -99,7 +99,7 @@ describe('Detection rules, EQL', () => {
|
|||
});
|
||||
|
||||
it('Creates and activates a new EQL rule', function () {
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
goToManageAlertsDetectionRules();
|
||||
|
@ -194,7 +194,7 @@ describe('Detection rules, sequence EQL', () => {
|
|||
});
|
||||
|
||||
it('Creates and activates a new EQL rule with a sequence', function () {
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
goToManageAlertsDetectionRules();
|
||||
|
|
|
@ -16,7 +16,7 @@ import { createCustomRule } from '../../tasks/api_calls/rules';
|
|||
import { cleanKibana } from '../../tasks/common';
|
||||
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Export rules', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -25,7 +25,7 @@ describe('Export rules', () => {
|
|||
'POST',
|
||||
'/api/detection_engine/rules/_export?exclude_export_details=false&file_name=rules_export.ndjson'
|
||||
).as('export');
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRule(newRule).as('ruleResponse');
|
||||
|
|
|
@ -123,7 +123,7 @@ import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
|
|||
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
import { addsFieldsToTimeline, goBackToAllRulesTable } from '../../tasks/rule_details';
|
||||
|
||||
import { DETECTIONS_URL, RULE_CREATION } from '../../urls/navigation';
|
||||
import { ALERTS_URL, RULE_CREATION } from '../../urls/navigation';
|
||||
|
||||
describe('indicator match', () => {
|
||||
describe('Detection rules, Indicator Match', () => {
|
||||
|
@ -407,7 +407,7 @@ describe('indicator match', () => {
|
|||
describe('Generating signals', () => {
|
||||
beforeEach(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
});
|
||||
|
||||
it('Creates and activates a new Indicator Match rule', () => {
|
||||
|
@ -559,7 +559,7 @@ describe('indicator match', () => {
|
|||
cleanKibana();
|
||||
esArchiverLoad('threat_indicator');
|
||||
esArchiverLoad('suspicious_source_event');
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
goToManageAlertsDetectionRules();
|
||||
createCustomIndicatorRule(newThreatIndicatorRule);
|
||||
reload();
|
||||
|
@ -571,7 +571,7 @@ describe('indicator match', () => {
|
|||
});
|
||||
|
||||
beforeEach(() => {
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
goToManageAlertsDetectionRules();
|
||||
goToRuleDetails();
|
||||
});
|
||||
|
@ -687,7 +687,7 @@ describe('indicator match', () => {
|
|||
describe('Duplicates the indicator rule', () => {
|
||||
beforeEach(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
goToManageAlertsDetectionRules();
|
||||
createCustomIndicatorRule(newThreatIndicatorRule);
|
||||
reload();
|
||||
|
|
|
@ -62,7 +62,7 @@ import {
|
|||
} from '../../tasks/create_new_rule';
|
||||
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Detection rules, machine learning', () => {
|
||||
const expectedUrls = machineLearningRule.referenceUrls.join('');
|
||||
|
@ -76,7 +76,7 @@ describe('Detection rules, machine learning', () => {
|
|||
});
|
||||
|
||||
it('Creates and activates a new ml rule', () => {
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
goToManageAlertsDetectionRules();
|
||||
|
|
|
@ -86,7 +86,7 @@ import {
|
|||
} from '../../tasks/create_new_rule';
|
||||
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Detection rules, override', () => {
|
||||
const expectedUrls = newOverrideRule.referenceUrls.join('');
|
||||
|
@ -108,7 +108,7 @@ describe('Detection rules, override', () => {
|
|||
});
|
||||
|
||||
it('Creates and activates a new custom rule with override option', function () {
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
goToManageAlertsDetectionRules();
|
||||
|
|
|
@ -34,7 +34,7 @@ import {
|
|||
} from '../../tasks/alerts_detection_rules';
|
||||
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
import { totalNumberOfPrebuiltRules } from '../../objects/rule';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
|
@ -50,7 +50,7 @@ describe('Alerts rules, prebuilt rules', () => {
|
|||
const expectedNumberOfPages = Math.ceil(totalNumberOfPrebuiltRules / rowsPerPage);
|
||||
const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`;
|
||||
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsIndexToBeCreated();
|
||||
goToManageAlertsDetectionRules();
|
||||
waitForRulesTableToBeLoaded();
|
||||
|
@ -72,7 +72,7 @@ describe('Actions with prebuilt rules', () => {
|
|||
const expectedElasticRulesBtnText = `Elastic rules (${expectedNumberOfRules})`;
|
||||
|
||||
cleanKibana();
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsIndexToBeCreated();
|
||||
goToManageAlertsDetectionRules();
|
||||
waitForRulesTableToBeLoaded();
|
||||
|
|
|
@ -36,7 +36,7 @@ import {
|
|||
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../../common/constants';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
import { createCustomRule } from '../../tasks/api_calls/rules';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
import { existingRule, newOverrideRule, newRule, newThresholdRule } from '../../objects/rule';
|
||||
|
@ -44,7 +44,7 @@ import { existingRule, newOverrideRule, newRule, newThresholdRule } from '../../
|
|||
describe('Alerts detection rules', () => {
|
||||
beforeEach(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRule(newRule, '1');
|
||||
|
|
|
@ -81,7 +81,7 @@ import {
|
|||
} from '../../tasks/create_new_rule';
|
||||
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
|
||||
describe('Detection rules, threshold', () => {
|
||||
const expectedUrls = newThresholdRule.referenceUrls.join('');
|
||||
|
@ -96,7 +96,7 @@ describe('Detection rules, threshold', () => {
|
|||
createTimeline(newThresholdRule.timeline).then((response) => {
|
||||
rule.timeline.id = response.body.data.persistTimeline.timeline.savedObjectId;
|
||||
});
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
});
|
||||
|
|
|
@ -31,7 +31,7 @@ import {
|
|||
ADD_EXCEPTIONS_BTN,
|
||||
} from '../../screens/exceptions';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
|
||||
// NOTE: You might look at these tests and feel they're overkill,
|
||||
|
@ -42,7 +42,7 @@ import { cleanKibana } from '../../tasks/common';
|
|||
describe('Exceptions modal', () => {
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRule(newRule);
|
||||
goToManageAlertsDetectionRules();
|
||||
|
|
|
@ -14,7 +14,10 @@ import { goToManageAlertsDetectionRules, waitForAlertsIndexToBeCreated } from '.
|
|||
import { createCustomRule } from '../../tasks/api_calls/rules';
|
||||
import { goToRuleDetails, waitForRulesTableToBeLoaded } from '../../tasks/alerts_detection_rules';
|
||||
import { esArchiverLoad, esArchiverUnload } from '../../tasks/es_archiver';
|
||||
import { loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
import {
|
||||
loginAndWaitForPageWithoutDateRange,
|
||||
waitForPageWithoutDateRange,
|
||||
} from '../../tasks/login';
|
||||
import {
|
||||
addsExceptionFromRuleSettings,
|
||||
goBackToAllRulesTable,
|
||||
|
@ -22,13 +25,12 @@ import {
|
|||
waitForTheRuleToBeExecuted,
|
||||
} from '../../tasks/rule_details';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL, EXCEPTIONS_URL } from '../../urls/navigation';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
import {
|
||||
deleteExceptionListWithRuleReference,
|
||||
deleteExceptionListWithoutRuleReference,
|
||||
exportExceptionList,
|
||||
goToExceptionsTable,
|
||||
searchForExceptionList,
|
||||
waitForExceptionsTableToBeLoaded,
|
||||
clearSearchSelection,
|
||||
|
@ -42,7 +44,7 @@ import { createExceptionList } from '../../tasks/api_calls/exceptions';
|
|||
describe('Exceptions Table', () => {
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRule(newRule);
|
||||
goToManageAlertsDetectionRules();
|
||||
|
@ -69,7 +71,7 @@ describe('Exceptions Table', () => {
|
|||
});
|
||||
|
||||
it('Filters exception lists on search', () => {
|
||||
goToExceptionsTable();
|
||||
waitForPageWithoutDateRange(EXCEPTIONS_URL);
|
||||
waitForExceptionsTableToBeLoaded();
|
||||
|
||||
cy.get(EXCEPTIONS_TABLE_SHOWING_LISTS).should('have.text', `Showing 3 lists`);
|
||||
|
@ -110,7 +112,7 @@ describe('Exceptions Table', () => {
|
|||
it('Exports exception list', async function () {
|
||||
cy.intercept(/(\/api\/exception_lists\/_export)/).as('export');
|
||||
|
||||
goToExceptionsTable();
|
||||
waitForPageWithoutDateRange(EXCEPTIONS_URL);
|
||||
waitForExceptionsTableToBeLoaded();
|
||||
|
||||
exportExceptionList();
|
||||
|
@ -124,7 +126,7 @@ describe('Exceptions Table', () => {
|
|||
});
|
||||
|
||||
it('Deletes exception list without rule reference', () => {
|
||||
goToExceptionsTable();
|
||||
waitForPageWithoutDateRange(EXCEPTIONS_URL);
|
||||
waitForExceptionsTableToBeLoaded();
|
||||
|
||||
cy.get(EXCEPTIONS_TABLE_SHOWING_LISTS).should('have.text', `Showing 3 lists`);
|
||||
|
@ -135,7 +137,7 @@ describe('Exceptions Table', () => {
|
|||
});
|
||||
|
||||
it('Deletes exception list with rule reference', () => {
|
||||
goToExceptionsTable();
|
||||
waitForPageWithoutDateRange(EXCEPTIONS_URL);
|
||||
waitForExceptionsTableToBeLoaded();
|
||||
|
||||
cy.get(EXCEPTIONS_TABLE_SHOWING_LISTS).should('have.text', `Showing 2 lists`);
|
||||
|
|
|
@ -33,7 +33,7 @@ import {
|
|||
} from '../../tasks/rule_details';
|
||||
import { refreshPage } from '../../tasks/security_header';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
|
||||
describe('From alert', () => {
|
||||
|
@ -41,7 +41,7 @@ describe('From alert', () => {
|
|||
|
||||
beforeEach(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRule(newRule, 'rule_testing', '10s');
|
||||
goToManageAlertsDetectionRules();
|
||||
|
|
|
@ -32,14 +32,14 @@ import {
|
|||
} from '../../tasks/rule_details';
|
||||
import { refreshPage } from '../../tasks/security_header';
|
||||
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
|
||||
describe('From rule', () => {
|
||||
const NUMBER_OF_AUDITBEAT_EXCEPTIONS_ALERTS = '1';
|
||||
beforeEach(() => {
|
||||
cleanKibana();
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsIndexToBeCreated();
|
||||
createCustomRule(newRule, 'rule_testing', '10s');
|
||||
goToManageAlertsDetectionRules();
|
||||
|
|
|
@ -9,7 +9,9 @@ import {
|
|||
CASES,
|
||||
DETECTIONS,
|
||||
HOSTS,
|
||||
ADMINISTRATION,
|
||||
ENDPOINTS,
|
||||
TRUSTED_APPS,
|
||||
EVENT_FILTERS,
|
||||
NETWORK,
|
||||
OVERVIEW,
|
||||
TIMELINES,
|
||||
|
@ -19,11 +21,13 @@ import { loginAndWaitForPage } from '../../tasks/login';
|
|||
import { navigateFromHeaderTo } from '../../tasks/security_header';
|
||||
|
||||
import {
|
||||
DETECTIONS_URL,
|
||||
ALERTS_URL,
|
||||
CASES_URL,
|
||||
HOSTS_URL,
|
||||
KIBANA_HOME,
|
||||
ADMINISTRATION_URL,
|
||||
ENDPOINTS_URL,
|
||||
TRUSTED_APPS_URL,
|
||||
EVENT_FILTERS_URL,
|
||||
NETWORK_URL,
|
||||
OVERVIEW_URL,
|
||||
TIMELINES_URL,
|
||||
|
@ -34,9 +38,9 @@ import {
|
|||
} from '../../tasks/kibana_navigation';
|
||||
import {
|
||||
CASES_PAGE,
|
||||
DETECTIONS_PAGE,
|
||||
ALERTS_PAGE,
|
||||
HOSTS_PAGE,
|
||||
ADMINISTRATION_PAGE,
|
||||
ENDPOINTS_PAGE,
|
||||
NETWORK_PAGE,
|
||||
OVERVIEW_PAGE,
|
||||
TIMELINES_PAGE,
|
||||
|
@ -54,9 +58,9 @@ describe('top-level navigation common to all pages in the Security app', () => {
|
|||
cy.url().should('include', OVERVIEW_URL);
|
||||
});
|
||||
|
||||
it('navigates to the Detections page', () => {
|
||||
it('navigates to the Alerts page', () => {
|
||||
navigateFromHeaderTo(DETECTIONS);
|
||||
cy.url().should('include', DETECTIONS_URL);
|
||||
cy.url().should('include', ALERTS_URL);
|
||||
});
|
||||
|
||||
it('navigates to the Hosts page', () => {
|
||||
|
@ -79,9 +83,17 @@ describe('top-level navigation common to all pages in the Security app', () => {
|
|||
cy.url().should('include', CASES_URL);
|
||||
});
|
||||
|
||||
it('navigates to the Administration page', () => {
|
||||
navigateFromHeaderTo(ADMINISTRATION);
|
||||
cy.url().should('include', ADMINISTRATION_URL);
|
||||
it('navigates to the Endpoints page', () => {
|
||||
navigateFromHeaderTo(ENDPOINTS);
|
||||
cy.url().should('include', ENDPOINTS_URL);
|
||||
});
|
||||
it('navigates to the Trusted Apps page', () => {
|
||||
navigateFromHeaderTo(TRUSTED_APPS);
|
||||
cy.url().should('include', TRUSTED_APPS_URL);
|
||||
});
|
||||
it('navigates to the Event Filters page', () => {
|
||||
navigateFromHeaderTo(EVENT_FILTERS);
|
||||
cy.url().should('include', EVENT_FILTERS_URL);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -97,9 +109,9 @@ describe('Kibana navigation to all pages in the Security app ', () => {
|
|||
cy.url().should('include', OVERVIEW_URL);
|
||||
});
|
||||
|
||||
it('navigates to the Detections page', () => {
|
||||
navigateFromKibanaCollapsibleTo(DETECTIONS_PAGE);
|
||||
cy.url().should('include', DETECTIONS_URL);
|
||||
it('navigates to the Alerts page', () => {
|
||||
navigateFromKibanaCollapsibleTo(ALERTS_PAGE);
|
||||
cy.url().should('include', ALERTS_URL);
|
||||
});
|
||||
|
||||
it('navigates to the Hosts page', () => {
|
||||
|
@ -122,8 +134,8 @@ describe('Kibana navigation to all pages in the Security app ', () => {
|
|||
cy.url().should('include', CASES_URL);
|
||||
});
|
||||
|
||||
it('navigates to the Administration page', () => {
|
||||
navigateFromKibanaCollapsibleTo(ADMINISTRATION_PAGE);
|
||||
cy.url().should('include', ADMINISTRATION_URL);
|
||||
it('navigates to the Endpoints page', () => {
|
||||
navigateFromKibanaCollapsibleTo(ENDPOINTS_PAGE);
|
||||
cy.url().should('include', ENDPOINTS_URL);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,7 +7,15 @@
|
|||
|
||||
import { loginAndWaitForPage, loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
|
||||
import { DETECTIONS } from '../../urls/navigation';
|
||||
import {
|
||||
ALERTS_URL,
|
||||
DETECTIONS,
|
||||
DETECTIONS_RULE_MANAGEMENT_URL,
|
||||
RULE_CREATION,
|
||||
SECURITY_DETECTIONS_RULES_CREATION_URL,
|
||||
SECURITY_DETECTIONS_RULES_URL,
|
||||
SECURITY_DETECTIONS_URL,
|
||||
} from '../../urls/navigation';
|
||||
import { ABSOLUTE_DATE_RANGE } from '../../urls/state';
|
||||
import {
|
||||
DATE_PICKER_START_DATE_POPOVER_BUTTON,
|
||||
|
@ -25,10 +33,24 @@ describe('URL compatibility', () => {
|
|||
cleanKibana();
|
||||
});
|
||||
|
||||
it('Redirects to Detection alerts from old Detections URL', () => {
|
||||
it('Redirects to alerts from old siem Detections URL', () => {
|
||||
loginAndWaitForPage(DETECTIONS);
|
||||
cy.url().should('include', ALERTS_URL);
|
||||
});
|
||||
|
||||
cy.url().should('include', '/security/detections');
|
||||
it('Redirects to alerts from old Detections URL', () => {
|
||||
loginAndWaitForPage(SECURITY_DETECTIONS_URL);
|
||||
cy.url().should('include', ALERTS_URL);
|
||||
});
|
||||
|
||||
it('Redirects to rules from old Detections rules URL', () => {
|
||||
loginAndWaitForPage(SECURITY_DETECTIONS_RULES_URL);
|
||||
cy.url().should('include', DETECTIONS_RULE_MANAGEMENT_URL);
|
||||
});
|
||||
|
||||
it('Redirects to rules creation from old Detections rules creation URL', () => {
|
||||
loginAndWaitForPage(SECURITY_DETECTIONS_RULES_CREATION_URL);
|
||||
cy.url().should('include', RULE_CREATION);
|
||||
});
|
||||
|
||||
it('sets the global start and end dates from the url with timestamps', () => {
|
||||
|
|
|
@ -214,7 +214,7 @@ describe('url state', () => {
|
|||
cy.get(ANOMALIES_TAB).should(
|
||||
'have.attr',
|
||||
'href',
|
||||
"/app/security/hosts/siem-kibana/anomalies?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&sourcerer=(default:!('auditbeat-*'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))"
|
||||
"/app/security/hosts/siem-kibana/anomalies?sourcerer=(default:!('auditbeat-*'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2020-01-01T21:33:29.186Z')))&query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')"
|
||||
);
|
||||
cy.get(BREADCRUMBS)
|
||||
.eq(1)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { ROLES } from '../../../common/test';
|
||||
import { deleteRoleAndUser, loginAndWaitForPageWithoutDateRange } from '../../tasks/login';
|
||||
import { DETECTIONS_URL } from '../../urls/navigation';
|
||||
import { ALERTS_URL } from '../../urls/navigation';
|
||||
import {
|
||||
waitForAlertsPanelToBeLoaded,
|
||||
waitForAlertsIndexToBeCreated,
|
||||
|
@ -35,7 +35,7 @@ import {
|
|||
describe('value lists', () => {
|
||||
describe('management modal', () => {
|
||||
beforeEach(() => {
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL);
|
||||
waitForAlertsPanelToBeLoaded();
|
||||
waitForAlertsIndexToBeCreated();
|
||||
waitForListsIndexToBeCreated();
|
||||
|
@ -228,7 +228,7 @@ describe('value lists', () => {
|
|||
|
||||
describe('user with restricted access role', () => {
|
||||
beforeEach(() => {
|
||||
loginAndWaitForPageWithoutDateRange(DETECTIONS_URL, ROLES.t1_analyst);
|
||||
loginAndWaitForPageWithoutDateRange(ALERTS_URL, ROLES.t1_analyst);
|
||||
goToManageAlertsDetectionRules();
|
||||
});
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ export const EXCEPTIONS_TABLE_TAB = '[data-test-subj="allRulesTableTab-exception
|
|||
|
||||
export const EXCEPTIONS_TABLE = '[data-test-subj="exceptions-table"]';
|
||||
|
||||
export const EXCEPTIONS_TABLE_SEARCH = '[data-test-subj="header-section-supplements"] input';
|
||||
export const EXCEPTIONS_TABLE_SEARCH = '[data-test-subj="exceptionsHeaderSearchInput"]';
|
||||
|
||||
export const EXCEPTIONS_TABLE_SHOWING_LISTS = '[data-test-subj="showingExceptionLists"]';
|
||||
|
||||
|
@ -50,7 +50,8 @@ export const EXCEPTIONS_TABLE_DELETE_BTN = '[data-test-subj="exceptionsTableDele
|
|||
|
||||
export const EXCEPTIONS_TABLE_EXPORT_BTN = '[data-test-subj="exceptionsTableExportButton"]';
|
||||
|
||||
export const EXCEPTIONS_TABLE_SEARCH_CLEAR = '[data-test-subj="header-section-supplements"] button';
|
||||
export const EXCEPTIONS_TABLE_SEARCH_CLEAR =
|
||||
'[data-test-subj="allExceptionListsPanel"] button.euiFormControlLayoutClearButton';
|
||||
|
||||
export const EXCEPTIONS_TABLE_LIST_NAME = '[data-test-subj="exceptionsTableName"]';
|
||||
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export const DETECTIONS_PAGE =
|
||||
'[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Detections"]';
|
||||
export const ALERTS_PAGE =
|
||||
'[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Alerts"]';
|
||||
|
||||
export const CASES_PAGE = '[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Cases"]';
|
||||
|
||||
|
@ -14,8 +14,8 @@ export const HOSTS_PAGE = '[data-test-subj="collapsibleNavGroup-securitySolution
|
|||
|
||||
export const KIBANA_NAVIGATION_TOGGLE = '[data-test-subj="toggleNavButton"]';
|
||||
|
||||
export const ADMINISTRATION_PAGE =
|
||||
'[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Administration"]';
|
||||
export const ENDPOINTS_PAGE =
|
||||
'[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Endpoints"]';
|
||||
|
||||
export const NETWORK_PAGE =
|
||||
'[data-test-subj="collapsibleNavGroup-securitySolution"] [title="Network"]';
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export const DETECTIONS = '[data-test-subj="navigation-detections"]';
|
||||
export const DETECTIONS = '[data-test-subj="navigation-alerts"]';
|
||||
|
||||
export const BREADCRUMBS = '[data-test-subj="breadcrumbs"] a';
|
||||
|
||||
|
@ -15,7 +15,11 @@ export const HOSTS = '[data-test-subj="navigation-hosts"]';
|
|||
|
||||
export const KQL_INPUT = '[data-test-subj="queryInput"]';
|
||||
|
||||
export const ADMINISTRATION = '[data-test-subj="navigation-administration"]';
|
||||
export const ENDPOINTS = '[data-test-subj="navigation-endpoints"]';
|
||||
|
||||
export const TRUSTED_APPS = '[data-test-subj="navigation-trusted_apps"]';
|
||||
|
||||
export const EVENT_FILTERS = '[data-test-subj="navigation-event_filters"]';
|
||||
|
||||
export const NETWORK = '[data-test-subj="navigation-network"]';
|
||||
|
||||
|
|
|
@ -5,13 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export const DETECTIONS_URL = 'app/security/detections';
|
||||
export const DETECTIONS_RULE_MANAGEMENT_URL = 'app/security/detections/rules';
|
||||
export const detectionsRuleDetailsUrl = (ruleId: string) =>
|
||||
`app/security/detections/rules/id/${ruleId}`;
|
||||
export const ALERTS_URL = 'app/security/alerts';
|
||||
export const DETECTIONS_RULE_MANAGEMENT_URL = 'app/security/rules';
|
||||
export const detectionsRuleDetailsUrl = (ruleId: string) => `app/security/rules/id/${ruleId}`;
|
||||
|
||||
export const CASES_URL = '/app/security/cases';
|
||||
export const DETECTIONS = '/app/siem#/detections';
|
||||
export const SECURITY_DETECTIONS_URL = '/app/security/detections';
|
||||
export const SECURITY_DETECTIONS_RULES_URL = '/app/security/detections/rules';
|
||||
export const SECURITY_DETECTIONS_RULES_CREATION_URL = '/app/security/detections/rules/create';
|
||||
|
||||
export const EXCEPTIONS_URL = 'app/security/exceptions';
|
||||
|
||||
export const HOSTS_URL = '/app/security/hosts/allHosts';
|
||||
export const HOSTS_PAGE_TAB_URLS = {
|
||||
allHosts: '/app/security/hosts/allHosts',
|
||||
|
@ -21,9 +26,11 @@ export const HOSTS_PAGE_TAB_URLS = {
|
|||
uncommonProcesses: '/app/security/hosts/uncommonProcesses',
|
||||
};
|
||||
export const KIBANA_HOME = '/app/home#/';
|
||||
export const ADMINISTRATION_URL = '/app/security/administration';
|
||||
export const ENDPOINTS_URL = '/app/security/administration/endpoints';
|
||||
export const TRUSTED_APPS_URL = '/app/security/administration/trusted_apps';
|
||||
export const EVENT_FILTERS_URL = '/app/security/administration/event_filters';
|
||||
export const NETWORK_URL = '/app/security/network';
|
||||
export const OVERVIEW_URL = '/app/security/overview';
|
||||
export const RULE_CREATION = 'app/security/detections/rules/create';
|
||||
export const RULE_CREATION = 'app/security/rules/create';
|
||||
export const TIMELINES_URL = '/app/security/timelines';
|
||||
export const TIMELINE_TEMPLATES_URL = '/app/security/timelines/template';
|
||||
|
|
|
@ -37,5 +37,13 @@
|
|||
],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"requiredBundles": ["esUiShared", "fleet", "kibanaUtils", "kibanaReact", "lists", "ml"]
|
||||
"requiredBundles": [
|
||||
"esUiShared",
|
||||
"fleet",
|
||||
"kibanaUtils",
|
||||
"kibanaReact",
|
||||
"usageCollection",
|
||||
"lists",
|
||||
"ml"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { getDeepLinks } from '.';
|
||||
import { Capabilities } from '../../../../../../src/core/public';
|
||||
import { SecurityPageName } from '../types';
|
||||
|
||||
describe('public search functions', () => {
|
||||
it('returns a subset of links for basic license, full set for platinum', () => {
|
||||
const basicLicense = 'basic';
|
||||
const platinumLicense = 'platinum';
|
||||
const basicLinks = getDeepLinks(basicLicense);
|
||||
const platinumLinks = getDeepLinks(platinumLicense);
|
||||
|
||||
basicLinks.forEach((basicLink, index) => {
|
||||
const platinumLink = platinumLinks[index];
|
||||
expect(basicLink.id).toEqual(platinumLink.id);
|
||||
const platinumDeepLinksCount = platinumLink.deepLinks?.length || 0;
|
||||
const basicDeepLinksCount = basicLink.deepLinks?.length || 0;
|
||||
expect(platinumDeepLinksCount).toBeGreaterThanOrEqual(basicDeepLinksCount);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns case links for basic license with only read_cases capabilities', () => {
|
||||
const basicLicense = 'basic';
|
||||
const basicLinks = getDeepLinks(basicLicense, ({
|
||||
siem: { read_cases: true, crud_cases: false },
|
||||
} as unknown) as Capabilities);
|
||||
|
||||
expect(basicLinks.some((l) => l.id === SecurityPageName.case)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns case links with NO deepLinks for basic license with only read_cases capabilities', () => {
|
||||
const basicLicense = 'basic';
|
||||
const basicLinks = getDeepLinks(basicLicense, ({
|
||||
siem: { read_cases: true, crud_cases: false },
|
||||
} as unknown) as Capabilities);
|
||||
|
||||
expect(
|
||||
basicLinks.find((l) => l.id === SecurityPageName.case)?.deepLinks?.length === 0
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns case links with deepLinks for basic license with crud_cases capabilities', () => {
|
||||
const basicLicense = 'basic';
|
||||
const basicLinks = getDeepLinks(basicLicense, ({
|
||||
siem: { read_cases: true, crud_cases: true },
|
||||
} as unknown) as Capabilities);
|
||||
|
||||
expect(
|
||||
(basicLinks.find((l) => l.id === SecurityPageName.case)?.deepLinks?.length ?? 0) > 0
|
||||
).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns NO case links for basic license with NO read_cases capabilities', () => {
|
||||
const basicLicense = 'basic';
|
||||
const basicLinks = getDeepLinks(basicLicense, ({
|
||||
siem: { read_cases: false, crud_cases: false },
|
||||
} as unknown) as Capabilities);
|
||||
|
||||
expect(basicLinks.some((l) => l.id === SecurityPageName.case)).toBeFalsy();
|
||||
});
|
||||
|
||||
it('returns case links for basic license with undefined capabilities', () => {
|
||||
const basicLicense = 'basic';
|
||||
const basicLinks = getDeepLinks(basicLicense, undefined);
|
||||
|
||||
expect(basicLinks.some((l) => l.id === SecurityPageName.case)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('returns case deepLinks for basic license with undefined capabilities', () => {
|
||||
const basicLicense = 'basic';
|
||||
const basicLinks = getDeepLinks(basicLicense, undefined);
|
||||
|
||||
expect(
|
||||
(basicLinks.find((l) => l.id === SecurityPageName.case)?.deepLinks?.length ?? 0) > 0
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
395
x-pack/plugins/security_solution/public/app/deep_links/index.ts
Normal file
395
x-pack/plugins/security_solution/public/app/deep_links/index.ts
Normal file
|
@ -0,0 +1,395 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import { LicenseType } from '../../../../licensing/common/types';
|
||||
import { SecurityDeepLinkName, SecurityDeepLinks, SecurityPageName } from '../types';
|
||||
import {
|
||||
AppDeepLink,
|
||||
ApplicationStart,
|
||||
AppNavLinkStatus,
|
||||
AppUpdater,
|
||||
} from '../../../../../../src/core/public';
|
||||
import {
|
||||
OVERVIEW,
|
||||
DETECT,
|
||||
ALERTS,
|
||||
RULES,
|
||||
EXCEPTIONS,
|
||||
HOSTS,
|
||||
NETWORK,
|
||||
TIMELINES,
|
||||
CASE,
|
||||
ADMINISTRATION,
|
||||
} from '../translations';
|
||||
import {
|
||||
OVERVIEW_PATH,
|
||||
ALERTS_PATH,
|
||||
RULES_PATH,
|
||||
EXCEPTIONS_PATH,
|
||||
HOSTS_PATH,
|
||||
NETWORK_PATH,
|
||||
TIMELINES_PATH,
|
||||
CASES_PATH,
|
||||
ENDPOINTS_PATH,
|
||||
TRUSTED_APPS_PATH,
|
||||
EVENT_FILTERS_PATH,
|
||||
} from '../../../common/constants';
|
||||
|
||||
export const topDeepLinks: AppDeepLink[] = [
|
||||
{
|
||||
id: SecurityPageName.overview,
|
||||
title: OVERVIEW,
|
||||
path: OVERVIEW_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.overview', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
],
|
||||
order: 9000,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.detections,
|
||||
title: DETECT,
|
||||
path: ALERTS_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.detect', {
|
||||
defaultMessage: 'Detect',
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.hosts,
|
||||
title: HOSTS,
|
||||
path: HOSTS_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.hosts', {
|
||||
defaultMessage: 'Hosts',
|
||||
}),
|
||||
],
|
||||
order: 9002,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.network,
|
||||
title: NETWORK,
|
||||
path: NETWORK_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.network', {
|
||||
defaultMessage: 'Network',
|
||||
}),
|
||||
],
|
||||
order: 9003,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.timelines,
|
||||
title: TIMELINES,
|
||||
path: TIMELINES_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.timelines', {
|
||||
defaultMessage: 'Timelines',
|
||||
}),
|
||||
],
|
||||
order: 9004,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.case,
|
||||
title: CASE,
|
||||
path: CASES_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.cases', {
|
||||
defaultMessage: 'Cases',
|
||||
}),
|
||||
],
|
||||
order: 9005,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.administration,
|
||||
title: ADMINISTRATION,
|
||||
path: ENDPOINTS_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.administration', {
|
||||
defaultMessage: 'Administration',
|
||||
}),
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const nestedDeepLinks: SecurityDeepLinks = {
|
||||
[SecurityPageName.overview]: {
|
||||
base: [],
|
||||
},
|
||||
[SecurityPageName.detections]: {
|
||||
base: [
|
||||
{
|
||||
id: SecurityPageName.alerts,
|
||||
title: ALERTS,
|
||||
path: ALERTS_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.alerts', {
|
||||
defaultMessage: 'Alerts',
|
||||
}),
|
||||
],
|
||||
searchable: true,
|
||||
order: 9001,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.rules,
|
||||
title: RULES,
|
||||
path: RULES_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.rules', {
|
||||
defaultMessage: 'Rules',
|
||||
}),
|
||||
],
|
||||
searchable: true,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.exceptions,
|
||||
title: EXCEPTIONS,
|
||||
path: EXCEPTIONS_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
keywords: [
|
||||
i18n.translate('xpack.securitySolution.search.exceptions', {
|
||||
defaultMessage: 'Exceptions',
|
||||
}),
|
||||
],
|
||||
searchable: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
[SecurityPageName.hosts]: {
|
||||
base: [
|
||||
{
|
||||
id: 'authentications',
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.authentications', {
|
||||
defaultMessage: 'Authentications',
|
||||
}),
|
||||
path: '/authentications',
|
||||
},
|
||||
{
|
||||
id: 'uncommonProcesses',
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.uncommonProcesses', {
|
||||
defaultMessage: 'Uncommon Processes',
|
||||
}),
|
||||
path: '/uncommonProcesses',
|
||||
},
|
||||
{
|
||||
id: 'events',
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.events', {
|
||||
defaultMessage: 'Events',
|
||||
}),
|
||||
path: '/events',
|
||||
},
|
||||
{
|
||||
id: 'externalAlerts',
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.externalAlerts', {
|
||||
defaultMessage: 'External Alerts',
|
||||
}),
|
||||
path: '/alerts',
|
||||
},
|
||||
],
|
||||
premium: [
|
||||
{
|
||||
id: 'anomalies',
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', {
|
||||
defaultMessage: 'Anomalies',
|
||||
}),
|
||||
path: '/anomalies',
|
||||
},
|
||||
],
|
||||
},
|
||||
[SecurityPageName.network]: {
|
||||
base: [
|
||||
{
|
||||
id: 'dns',
|
||||
title: i18n.translate('xpack.securitySolution.search.network.dns', {
|
||||
defaultMessage: 'DNS',
|
||||
}),
|
||||
path: '/dns',
|
||||
},
|
||||
{
|
||||
id: 'http',
|
||||
title: i18n.translate('xpack.securitySolution.search.network.http', {
|
||||
defaultMessage: 'HTTP',
|
||||
}),
|
||||
path: '/http',
|
||||
},
|
||||
{
|
||||
id: 'tls',
|
||||
title: i18n.translate('xpack.securitySolution.search.network.tls', {
|
||||
defaultMessage: 'TLS',
|
||||
}),
|
||||
path: '/tls',
|
||||
},
|
||||
{
|
||||
id: 'externalAlertsNetwork',
|
||||
title: i18n.translate('xpack.securitySolution.search.network.externalAlerts', {
|
||||
defaultMessage: 'External Alerts',
|
||||
}),
|
||||
path: '/external-alerts',
|
||||
},
|
||||
],
|
||||
premium: [
|
||||
{
|
||||
id: 'anomalies',
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', {
|
||||
defaultMessage: 'Anomalies',
|
||||
}),
|
||||
path: '/anomalies',
|
||||
},
|
||||
],
|
||||
},
|
||||
[SecurityPageName.timelines]: {
|
||||
base: [
|
||||
{
|
||||
id: 'timelineTemplates',
|
||||
title: i18n.translate('xpack.securitySolution.search.timeline.templates', {
|
||||
defaultMessage: 'Templates',
|
||||
}),
|
||||
path: '/template',
|
||||
},
|
||||
],
|
||||
},
|
||||
[SecurityPageName.case]: {
|
||||
base: [
|
||||
{
|
||||
id: 'create',
|
||||
title: i18n.translate('xpack.securitySolution.search.cases.create', {
|
||||
defaultMessage: 'Create New Case',
|
||||
}),
|
||||
path: '/create',
|
||||
},
|
||||
],
|
||||
premium: [
|
||||
{
|
||||
id: 'configure',
|
||||
title: i18n.translate('xpack.securitySolution.search.cases.configure', {
|
||||
defaultMessage: 'Configure Cases',
|
||||
}),
|
||||
path: '/configure',
|
||||
},
|
||||
],
|
||||
},
|
||||
[SecurityPageName.administration]: {
|
||||
base: [
|
||||
{
|
||||
id: SecurityPageName.endpoints,
|
||||
navLinkStatus: AppNavLinkStatus.visible,
|
||||
title: i18n.translate('xpack.securitySolution.search.administration.endpoints', {
|
||||
defaultMessage: 'Endpoints',
|
||||
}),
|
||||
order: 9006,
|
||||
path: ENDPOINTS_PATH,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.trustedApps,
|
||||
title: i18n.translate('xpack.securitySolution.search.administration.trustedApps', {
|
||||
defaultMessage: 'Trusted Applications',
|
||||
}),
|
||||
path: TRUSTED_APPS_PATH,
|
||||
},
|
||||
{
|
||||
id: SecurityPageName.eventFilters,
|
||||
title: i18n.translate('xpack.securitySolution.search.administration.eventFilters', {
|
||||
defaultMessage: 'Event Filters',
|
||||
}),
|
||||
path: EVENT_FILTERS_PATH,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A function that generates the plugin deepLinks
|
||||
* @param licenseType optional string for license level, if not provided basic is assumed.
|
||||
*/
|
||||
export function getDeepLinks(
|
||||
licenseType?: LicenseType,
|
||||
capabilities?: ApplicationStart['capabilities']
|
||||
): AppDeepLink[] {
|
||||
return topDeepLinks
|
||||
.filter(
|
||||
(deepLink) =>
|
||||
deepLink.id !== SecurityPageName.case ||
|
||||
capabilities == null ||
|
||||
(deepLink.id === SecurityPageName.case && capabilities.siem.read_cases === true)
|
||||
)
|
||||
.map((deepLink) => {
|
||||
const deepLinkId = deepLink.id as SecurityDeepLinkName;
|
||||
const subPluginDeepLinks = nestedDeepLinks[deepLinkId];
|
||||
const baseDeepLinks = Array.isArray(subPluginDeepLinks.base)
|
||||
? [...subPluginDeepLinks.base]
|
||||
: [];
|
||||
if (
|
||||
deepLinkId === SecurityPageName.case &&
|
||||
capabilities != null &&
|
||||
capabilities.siem.crud_cases === false
|
||||
) {
|
||||
return {
|
||||
...deepLink,
|
||||
deepLinks: [],
|
||||
};
|
||||
}
|
||||
if (isPremiumLicense(licenseType) && subPluginDeepLinks?.premium) {
|
||||
return {
|
||||
...deepLink,
|
||||
deepLinks: [...baseDeepLinks, ...subPluginDeepLinks.premium],
|
||||
};
|
||||
}
|
||||
return {
|
||||
...deepLink,
|
||||
deepLinks: baseDeepLinks,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function isPremiumLicense(licenseType?: LicenseType): boolean {
|
||||
return (
|
||||
licenseType === 'gold' ||
|
||||
licenseType === 'platinum' ||
|
||||
licenseType === 'enterprise' ||
|
||||
licenseType === 'trial'
|
||||
);
|
||||
}
|
||||
|
||||
export function updateGlobalNavigation({
|
||||
capabilities,
|
||||
updater$,
|
||||
}: {
|
||||
capabilities: ApplicationStart['capabilities'];
|
||||
updater$: Subject<AppUpdater>;
|
||||
}) {
|
||||
const deepLinks = getDeepLinks(undefined, capabilities);
|
||||
const updatedDeepLinks = deepLinks.map((link) => {
|
||||
switch (link.id) {
|
||||
case 'case':
|
||||
return {
|
||||
...link,
|
||||
navLinkStatus: capabilities.siem.read_cases
|
||||
? AppNavLinkStatus.visible
|
||||
: AppNavLinkStatus.hidden,
|
||||
};
|
||||
default:
|
||||
return link;
|
||||
}
|
||||
});
|
||||
|
||||
updater$.next(() => ({
|
||||
deepLinks: updatedDeepLinks,
|
||||
}));
|
||||
}
|
|
@ -11,6 +11,7 @@ import {
|
|||
EuiHeaderSectionItem,
|
||||
} from '@elastic/eui';
|
||||
import React, { useEffect, useMemo } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { createPortalNode, OutPortal, InPortal } from 'react-reverse-portal';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -18,7 +19,8 @@ import { AppMountParameters } from '../../../../../../../src/core/public';
|
|||
import { toMountPoint } from '../../../../../../../src/plugins/kibana_react/public';
|
||||
import { MlPopover } from '../../../common/components/ml_popover/ml_popover';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { ADD_DATA_PATH, APP_DETECTIONS_PATH } from '../../../../common/constants';
|
||||
import { ADD_DATA_PATH } from '../../../../common/constants';
|
||||
import { isDetectionsPath } from '../../../../public/helpers';
|
||||
|
||||
const BUTTON_ADD_DATA = i18n.translate('xpack.securitySolution.globalHeader.buttonAddData', {
|
||||
defaultMessage: 'Add data',
|
||||
|
@ -31,27 +33,29 @@ const BUTTON_ADD_DATA = i18n.translate('xpack.securitySolution.globalHeader.butt
|
|||
export const GlobalHeader = React.memo(
|
||||
({ setHeaderActionMenu }: { setHeaderActionMenu: AppMountParameters['setHeaderActionMenu'] }) => {
|
||||
const portalNode = useMemo(() => createPortalNode(), []);
|
||||
const { http } = useKibana().services;
|
||||
const {
|
||||
http: {
|
||||
basePath: { prepend },
|
||||
},
|
||||
} = useKibana().services;
|
||||
const { pathname } = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
let unmount = () => {};
|
||||
|
||||
setHeaderActionMenu((element) => {
|
||||
const mount = toMountPoint(<OutPortal node={portalNode} />);
|
||||
unmount = mount(element);
|
||||
return unmount;
|
||||
return mount(element);
|
||||
});
|
||||
|
||||
return () => {
|
||||
portalNode.unmount();
|
||||
unmount();
|
||||
setHeaderActionMenu(undefined);
|
||||
};
|
||||
}, [portalNode, setHeaderActionMenu]);
|
||||
|
||||
return (
|
||||
<InPortal node={portalNode}>
|
||||
<EuiHeaderSection side="right">
|
||||
{window.location.pathname.includes(APP_DETECTIONS_PATH) && (
|
||||
{isDetectionsPath(pathname) && (
|
||||
<EuiHeaderSectionItem>
|
||||
<MlPopover />
|
||||
</EuiHeaderSectionItem>
|
||||
|
@ -61,7 +65,7 @@ export const GlobalHeader = React.memo(
|
|||
<EuiHeaderLink
|
||||
color="primary"
|
||||
data-test-subj="add-data"
|
||||
href={http.basePath.prepend(ADD_DATA_PATH)}
|
||||
href={prepend(ADD_DATA_PATH)}
|
||||
iconType="indexOpen"
|
||||
>
|
||||
{BUTTON_ADD_DATA}
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* 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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as i18n from '../translations';
|
||||
import { SecurityPageName, SecurityPageGroupName } from '../types';
|
||||
import { SiemNavTab, NavTabGroups } from '../../common/components/navigation/types';
|
||||
import {
|
||||
APP_OVERVIEW_PATH,
|
||||
APP_RULES_PATH,
|
||||
APP_ALERTS_PATH,
|
||||
APP_EXCEPTIONS_PATH,
|
||||
APP_HOSTS_PATH,
|
||||
APP_NETWORK_PATH,
|
||||
APP_TIMELINES_PATH,
|
||||
APP_CASES_PATH,
|
||||
APP_MANAGEMENT_PATH,
|
||||
APP_ENDPOINTS_PATH,
|
||||
APP_TRUSTED_APPS_PATH,
|
||||
APP_EVENT_FILTERS_PATH,
|
||||
} from '../../../common/constants';
|
||||
|
||||
export const navTabs: SiemNavTab = {
|
||||
[SecurityPageName.overview]: {
|
||||
id: SecurityPageName.overview,
|
||||
name: i18n.OVERVIEW,
|
||||
href: APP_OVERVIEW_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'overview',
|
||||
},
|
||||
[SecurityPageName.alerts]: {
|
||||
id: SecurityPageName.alerts,
|
||||
name: i18n.ALERTS,
|
||||
href: APP_ALERTS_PATH,
|
||||
disabled: false,
|
||||
urlKey: SecurityPageName.alerts,
|
||||
},
|
||||
[SecurityPageName.rules]: {
|
||||
id: SecurityPageName.rules,
|
||||
name: i18n.RULES,
|
||||
href: APP_RULES_PATH,
|
||||
disabled: false,
|
||||
urlKey: SecurityPageName.rules,
|
||||
},
|
||||
[SecurityPageName.exceptions]: {
|
||||
id: SecurityPageName.exceptions,
|
||||
name: i18n.EXCEPTIONS,
|
||||
href: APP_EXCEPTIONS_PATH,
|
||||
disabled: false,
|
||||
urlKey: SecurityPageName.exceptions,
|
||||
},
|
||||
[SecurityPageName.hosts]: {
|
||||
id: SecurityPageName.hosts,
|
||||
name: i18n.HOSTS,
|
||||
href: APP_HOSTS_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'host',
|
||||
},
|
||||
[SecurityPageName.network]: {
|
||||
id: SecurityPageName.network,
|
||||
name: i18n.NETWORK,
|
||||
href: APP_NETWORK_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'network',
|
||||
},
|
||||
[SecurityPageName.timelines]: {
|
||||
id: SecurityPageName.timelines,
|
||||
name: i18n.TIMELINES,
|
||||
href: APP_TIMELINES_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'timeline',
|
||||
},
|
||||
[SecurityPageName.case]: {
|
||||
id: SecurityPageName.case,
|
||||
name: i18n.CASE,
|
||||
href: APP_CASES_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'case',
|
||||
},
|
||||
[SecurityPageName.administration]: {
|
||||
id: SecurityPageName.administration,
|
||||
name: i18n.ADMINISTRATION,
|
||||
href: APP_MANAGEMENT_PATH,
|
||||
disabled: false,
|
||||
urlKey: SecurityPageName.administration,
|
||||
},
|
||||
[SecurityPageName.endpoints]: {
|
||||
id: SecurityPageName.endpoints,
|
||||
name: i18n.ENDPOINTS,
|
||||
href: APP_ENDPOINTS_PATH,
|
||||
disabled: false,
|
||||
urlKey: SecurityPageName.administration,
|
||||
},
|
||||
[SecurityPageName.trustedApps]: {
|
||||
id: SecurityPageName.trustedApps,
|
||||
name: i18n.TRUSTED_APPLICATIONS,
|
||||
href: APP_TRUSTED_APPS_PATH,
|
||||
disabled: false,
|
||||
urlKey: SecurityPageName.administration,
|
||||
},
|
||||
[SecurityPageName.eventFilters]: {
|
||||
id: SecurityPageName.eventFilters,
|
||||
name: i18n.EVENT_FILTERS,
|
||||
href: APP_EVENT_FILTERS_PATH,
|
||||
disabled: false,
|
||||
urlKey: SecurityPageName.administration,
|
||||
},
|
||||
};
|
||||
|
||||
export const navTabGroups: NavTabGroups = {
|
||||
[SecurityPageGroupName.detect]: {
|
||||
id: SecurityPageGroupName.detect,
|
||||
name: i18n.DETECT,
|
||||
},
|
||||
[SecurityPageGroupName.explore]: {
|
||||
id: SecurityPageGroupName.explore,
|
||||
name: i18n.EXPLORE,
|
||||
},
|
||||
[SecurityPageGroupName.investigate]: {
|
||||
id: SecurityPageGroupName.investigate,
|
||||
name: i18n.INVESTIGATE,
|
||||
},
|
||||
[SecurityPageGroupName.manage]: {
|
||||
id: SecurityPageGroupName.manage,
|
||||
name: i18n.MANAGE,
|
||||
},
|
||||
};
|
|
@ -1,72 +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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as i18n from '../translations';
|
||||
import { SecurityPageName } from '../types';
|
||||
import { SiemNavTab } from '../../common/components/navigation/types';
|
||||
import {
|
||||
APP_OVERVIEW_PATH,
|
||||
APP_DETECTIONS_PATH,
|
||||
APP_HOSTS_PATH,
|
||||
APP_NETWORK_PATH,
|
||||
APP_TIMELINES_PATH,
|
||||
APP_CASES_PATH,
|
||||
APP_MANAGEMENT_PATH,
|
||||
} from '../../../common/constants';
|
||||
|
||||
export const navTabs: SiemNavTab = {
|
||||
[SecurityPageName.overview]: {
|
||||
id: SecurityPageName.overview,
|
||||
name: i18n.OVERVIEW,
|
||||
href: APP_OVERVIEW_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'overview',
|
||||
},
|
||||
[SecurityPageName.detections]: {
|
||||
id: SecurityPageName.detections,
|
||||
name: i18n.DETECTION_ENGINE,
|
||||
href: APP_DETECTIONS_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'detections',
|
||||
},
|
||||
[SecurityPageName.hosts]: {
|
||||
id: SecurityPageName.hosts,
|
||||
name: i18n.HOSTS,
|
||||
href: APP_HOSTS_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'host',
|
||||
},
|
||||
[SecurityPageName.network]: {
|
||||
id: SecurityPageName.network,
|
||||
name: i18n.NETWORK,
|
||||
href: APP_NETWORK_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'network',
|
||||
},
|
||||
|
||||
[SecurityPageName.timelines]: {
|
||||
id: SecurityPageName.timelines,
|
||||
name: i18n.TIMELINES,
|
||||
href: APP_TIMELINES_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'timeline',
|
||||
},
|
||||
[SecurityPageName.case]: {
|
||||
id: SecurityPageName.case,
|
||||
name: i18n.CASE,
|
||||
href: APP_CASES_PATH,
|
||||
disabled: false,
|
||||
urlKey: 'case',
|
||||
},
|
||||
[SecurityPageName.administration]: {
|
||||
id: SecurityPageName.administration,
|
||||
name: i18n.ADMINISTRATION,
|
||||
href: APP_MANAGEMENT_PATH,
|
||||
disabled: false,
|
||||
urlKey: SecurityPageName.administration,
|
||||
},
|
||||
};
|
|
@ -5,7 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import React from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { DragDropContextWrapper } from '../../common/components/drag_and_drop/drag_drop_context_wrapper';
|
||||
import { AppLeaveHandler, AppMountParameters } from '../../../../../../src/core/public';
|
||||
|
@ -14,12 +15,11 @@ import { HelpMenu } from '../../common/components/help_menu';
|
|||
import { UseUrlState } from '../../common/components/url_state';
|
||||
import { navTabs } from './home_navigations';
|
||||
import { useInitSourcerer, useSourcererScope } from '../../common/containers/sourcerer';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import { DETECTIONS_SUB_PLUGIN_ID } from '../../../common/constants';
|
||||
import { SourcererScopeName } from '../../common/store/sourcerer/model';
|
||||
import { useUpgradeSecurityPackages } from '../../common/hooks/use_upgrade_security_packages';
|
||||
import { GlobalHeader } from './global_header';
|
||||
import { SecuritySolutionTemplateWrapper } from './template_wrapper';
|
||||
import { isDetectionsPath } from '../../helpers';
|
||||
|
||||
interface HomePageProps {
|
||||
children: React.ReactNode;
|
||||
|
@ -32,23 +32,14 @@ const HomePageComponent: React.FC<HomePageProps> = ({
|
|||
onAppLeave,
|
||||
setHeaderActionMenu,
|
||||
}) => {
|
||||
const { application } = useKibana().services;
|
||||
const subPluginId = useRef<string>('');
|
||||
|
||||
application.currentAppId$.subscribe((appId) => {
|
||||
subPluginId.current = appId ?? '';
|
||||
});
|
||||
const { pathname } = useLocation();
|
||||
|
||||
useInitSourcerer(
|
||||
subPluginId.current === DETECTIONS_SUB_PLUGIN_ID
|
||||
? SourcererScopeName.detections
|
||||
: SourcererScopeName.default
|
||||
isDetectionsPath(pathname) ? SourcererScopeName.detections : SourcererScopeName.default
|
||||
);
|
||||
|
||||
const { browserFields, indexPattern } = useSourcererScope(
|
||||
subPluginId.current === DETECTIONS_SUB_PLUGIN_ID
|
||||
? SourcererScopeName.detections
|
||||
: SourcererScopeName.default
|
||||
isDetectionsPath(pathname) ? SourcererScopeName.detections : SourcererScopeName.default
|
||||
);
|
||||
|
||||
// side effect: this will attempt to upgrade the endpoint package if it is not up to date
|
||||
|
|
|
@ -7,34 +7,28 @@
|
|||
|
||||
/* eslint-disable react/display-name */
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import React from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { KibanaPageTemplateProps } from '../../../../../../../../src/plugins/kibana_react/public';
|
||||
import { AppLeaveHandler } from '../../../../../../../../src/core/public';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
import { useShowTimeline } from '../../../../common/utils/timeline/use_show_timeline';
|
||||
import { useSourcererScope } from '../../../../common/containers/sourcerer';
|
||||
import { DETECTIONS_SUB_PLUGIN_ID } from '../../../../../common/constants';
|
||||
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
|
||||
import { TimelineId } from '../../../../../common/types/timeline';
|
||||
import { AutoSaveWarningMsg } from '../../../../timelines/components/timeline/auto_save_warning';
|
||||
import { Flyout } from '../../../../timelines/components/flyout';
|
||||
import { isDetectionsPath } from '../../../../../public/helpers';
|
||||
|
||||
export const BOTTOM_BAR_CLASSNAME = 'timeline-bottom-bar';
|
||||
|
||||
export const SecuritySolutionBottomBar = React.memo(
|
||||
({ onAppLeave }: { onAppLeave: (handler: AppLeaveHandler) => void }) => {
|
||||
const subPluginId = useRef<string>('');
|
||||
const { application } = useKibana().services;
|
||||
application.currentAppId$.subscribe((appId) => {
|
||||
subPluginId.current = appId ?? '';
|
||||
});
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const [showTimeline] = useShowTimeline();
|
||||
|
||||
const { indicesExist } = useSourcererScope(
|
||||
subPluginId.current === DETECTIONS_SUB_PLUGIN_ID
|
||||
? SourcererScopeName.detections
|
||||
: SourcererScopeName.default
|
||||
isDetectionsPath(pathname) ? SourcererScopeName.detections : SourcererScopeName.default
|
||||
);
|
||||
|
||||
return indicesExist && showTimeline ? (
|
||||
|
|
|
@ -7,7 +7,10 @@
|
|||
|
||||
import React from 'react';
|
||||
import { render, unmountComponentAtNode } from 'react-dom';
|
||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { OVERVIEW_PATH } from '../../common/constants';
|
||||
|
||||
import { NotFoundPage } from '../app/404';
|
||||
import { SecurityApp } from './app';
|
||||
import { RenderAppProps } from './types';
|
||||
|
||||
|
@ -18,8 +21,11 @@ export const renderApp = ({
|
|||
setHeaderActionMenu,
|
||||
services,
|
||||
store,
|
||||
SubPluginRoutes,
|
||||
usageCollection,
|
||||
subPlugins,
|
||||
}: RenderAppProps): (() => void) => {
|
||||
const ApplicationUsageTrackingProvider =
|
||||
usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment;
|
||||
render(
|
||||
<SecurityApp
|
||||
history={history}
|
||||
|
@ -28,7 +34,30 @@ export const renderApp = ({
|
|||
setHeaderActionMenu={setHeaderActionMenu}
|
||||
store={store}
|
||||
>
|
||||
<SubPluginRoutes />
|
||||
<ApplicationUsageTrackingProvider>
|
||||
<Switch>
|
||||
{[
|
||||
...subPlugins.overview.routes,
|
||||
...subPlugins.alerts.routes,
|
||||
...subPlugins.rules.routes,
|
||||
...subPlugins.exceptions.routes,
|
||||
...subPlugins.hosts.routes,
|
||||
...subPlugins.network.routes,
|
||||
...subPlugins.timelines.routes,
|
||||
...subPlugins.cases.routes,
|
||||
...subPlugins.management.routes,
|
||||
].map((route, index) => (
|
||||
<Route key={`route-${index}`} {...route} />
|
||||
))}
|
||||
|
||||
<Route path="" exact>
|
||||
<Redirect to={OVERVIEW_PATH} />
|
||||
</Route>
|
||||
<Route>
|
||||
<NotFoundPage />
|
||||
</Route>
|
||||
</Switch>
|
||||
</ApplicationUsageTrackingProvider>
|
||||
</SecurityApp>,
|
||||
element
|
||||
);
|
||||
|
|
|
@ -1,22 +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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { getDeepLinksAndKeywords } from '.';
|
||||
import { SecurityPageName } from '../../../common/constants';
|
||||
|
||||
describe('public search functions', () => {
|
||||
it('returns a subset of links for basic license, full set for platinum', () => {
|
||||
const basicLicense = 'basic';
|
||||
const platinumLicense = 'platinum';
|
||||
for (const pageName of Object.values(SecurityPageName)) {
|
||||
expect.assertions(Object.values(SecurityPageName).length * 2);
|
||||
const basicLinkCount = getDeepLinksAndKeywords(pageName, basicLicense).deepLinks?.length || 0;
|
||||
const platinumLinks = getDeepLinksAndKeywords(pageName, platinumLicense);
|
||||
expect(platinumLinks.deepLinks?.length).toBeGreaterThanOrEqual(basicLinkCount);
|
||||
expect(platinumLinks.keywords?.length).not.toBe(null);
|
||||
}
|
||||
});
|
||||
});
|
|
@ -1,240 +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; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
import { AppUpdater } from 'src/core/public';
|
||||
import { LicenseType } from '../../../../licensing/common/types';
|
||||
import { SecuritySubPluginNames, SecurityDeepLinks } from '../types';
|
||||
import { App } from '../../../../../../src/core/public';
|
||||
|
||||
const securityDeepLinks: SecurityDeepLinks = {
|
||||
detections: {
|
||||
base: [
|
||||
{
|
||||
id: 'siemDetectionRules',
|
||||
title: i18n.translate('xpack.securitySolution.search.detections.manage', {
|
||||
defaultMessage: 'Manage Rules',
|
||||
}),
|
||||
keywords: ['rules'],
|
||||
path: '/rules',
|
||||
},
|
||||
],
|
||||
},
|
||||
hosts: {
|
||||
base: [
|
||||
{
|
||||
id: 'authentications',
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.authentications', {
|
||||
defaultMessage: 'Authentications',
|
||||
}),
|
||||
path: '/authentications',
|
||||
},
|
||||
{
|
||||
id: 'uncommonProcesses',
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.uncommonProcesses', {
|
||||
defaultMessage: 'Uncommon Processes',
|
||||
}),
|
||||
path: '/uncommonProcesses',
|
||||
},
|
||||
{
|
||||
id: 'events',
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.events', {
|
||||
defaultMessage: 'Events',
|
||||
}),
|
||||
path: '/events',
|
||||
},
|
||||
{
|
||||
id: 'externalAlerts',
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.externalAlerts', {
|
||||
defaultMessage: 'External Alerts',
|
||||
}),
|
||||
path: '/alerts',
|
||||
},
|
||||
],
|
||||
premium: [
|
||||
{
|
||||
id: 'anomalies',
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', {
|
||||
defaultMessage: 'Anomalies',
|
||||
}),
|
||||
path: '/anomalies',
|
||||
},
|
||||
],
|
||||
},
|
||||
network: {
|
||||
base: [
|
||||
{
|
||||
id: 'dns',
|
||||
title: i18n.translate('xpack.securitySolution.search.network.dns', {
|
||||
defaultMessage: 'DNS',
|
||||
}),
|
||||
path: '/dns',
|
||||
},
|
||||
{
|
||||
id: 'http',
|
||||
title: i18n.translate('xpack.securitySolution.search.network.http', {
|
||||
defaultMessage: 'HTTP',
|
||||
}),
|
||||
path: '/http',
|
||||
},
|
||||
{
|
||||
id: 'tls',
|
||||
title: i18n.translate('xpack.securitySolution.search.network.tls', {
|
||||
defaultMessage: 'TLS',
|
||||
}),
|
||||
path: '/tls',
|
||||
},
|
||||
{
|
||||
id: 'externalAlertsNetwork',
|
||||
title: i18n.translate('xpack.securitySolution.search.network.externalAlerts', {
|
||||
defaultMessage: 'External Alerts',
|
||||
}),
|
||||
path: '/external-alerts',
|
||||
},
|
||||
],
|
||||
premium: [
|
||||
{
|
||||
id: 'anomalies',
|
||||
title: i18n.translate('xpack.securitySolution.search.hosts.anomalies', {
|
||||
defaultMessage: 'Anomalies',
|
||||
}),
|
||||
path: '/anomalies',
|
||||
},
|
||||
],
|
||||
},
|
||||
timelines: {
|
||||
base: [
|
||||
{
|
||||
id: 'timelineTemplates',
|
||||
title: i18n.translate('xpack.securitySolution.search.timeline.templates', {
|
||||
defaultMessage: 'Templates',
|
||||
}),
|
||||
path: '/template',
|
||||
},
|
||||
],
|
||||
},
|
||||
overview: {
|
||||
base: [],
|
||||
},
|
||||
case: {
|
||||
base: [
|
||||
{
|
||||
id: 'create',
|
||||
title: i18n.translate('xpack.securitySolution.search.cases.create', {
|
||||
defaultMessage: 'Create New Case',
|
||||
}),
|
||||
path: '/create',
|
||||
},
|
||||
],
|
||||
premium: [
|
||||
{
|
||||
id: 'configure',
|
||||
title: i18n.translate('xpack.securitySolution.search.cases.configure', {
|
||||
defaultMessage: 'Configure Cases',
|
||||
}),
|
||||
path: '/configure',
|
||||
},
|
||||
],
|
||||
},
|
||||
administration: {
|
||||
base: [
|
||||
{
|
||||
id: 'trustApplications',
|
||||
title: i18n.translate('xpack.securitySolution.search.administration.trustedApps', {
|
||||
defaultMessage: 'Trusted Applications',
|
||||
}),
|
||||
path: '/trusted_apps',
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const subpluginKeywords: { [key in SecuritySubPluginNames]: string[] } = {
|
||||
detections: [
|
||||
i18n.translate('xpack.securitySolution.search.detections', {
|
||||
defaultMessage: 'Detections',
|
||||
}),
|
||||
],
|
||||
hosts: [
|
||||
i18n.translate('xpack.securitySolution.search.hosts', {
|
||||
defaultMessage: 'Hosts',
|
||||
}),
|
||||
],
|
||||
network: [
|
||||
i18n.translate('xpack.securitySolution.search.network', {
|
||||
defaultMessage: 'Network',
|
||||
}),
|
||||
],
|
||||
timelines: [
|
||||
i18n.translate('xpack.securitySolution.search.timelines', {
|
||||
defaultMessage: 'Timelines',
|
||||
}),
|
||||
],
|
||||
overview: [
|
||||
i18n.translate('xpack.securitySolution.search.overview', {
|
||||
defaultMessage: 'Overview',
|
||||
}),
|
||||
],
|
||||
case: [
|
||||
i18n.translate('xpack.securitySolution.search.cases', {
|
||||
defaultMessage: 'Cases',
|
||||
}),
|
||||
],
|
||||
administration: [
|
||||
i18n.translate('xpack.securitySolution.search.administration', {
|
||||
defaultMessage: 'Endpoint Administration',
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* A function that generates a subPlugin's meta tag
|
||||
* @param subPluginName SubPluginName of the app to retrieve meta information for.
|
||||
* @param licenseType optional string for license level, if not provided basic is assumed.
|
||||
*/
|
||||
export function getDeepLinksAndKeywords(
|
||||
subPluginName: SecuritySubPluginNames,
|
||||
licenseType?: LicenseType
|
||||
): Pick<App, 'deepLinks' | 'keywords'> {
|
||||
const baseRoutes = [...securityDeepLinks[subPluginName].base];
|
||||
if (
|
||||
licenseType === 'gold' ||
|
||||
licenseType === 'platinum' ||
|
||||
licenseType === 'enterprise' ||
|
||||
licenseType === 'trial'
|
||||
) {
|
||||
const premiumRoutes =
|
||||
securityDeepLinks[subPluginName] && securityDeepLinks[subPluginName].premium;
|
||||
if (premiumRoutes !== undefined) {
|
||||
return {
|
||||
keywords: subpluginKeywords[subPluginName],
|
||||
deepLinks: [...baseRoutes, ...premiumRoutes],
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
keywords: subpluginKeywords[subPluginName],
|
||||
deepLinks: baseRoutes,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* A function that updates a subplugin's meta property as appropriate when license level changes.
|
||||
* @param subPluginName SubPluginName of the app to register deepLinks for
|
||||
* @param appUpdater an instance of appUpdater$ observable to update search links when needed.
|
||||
* @param licenseType A string representing the current license level.
|
||||
*/
|
||||
export function registerDeepLinks(
|
||||
subPluginName: SecuritySubPluginNames,
|
||||
appUpdater?: Subject<AppUpdater>,
|
||||
licenseType?: LicenseType
|
||||
) {
|
||||
if (appUpdater !== undefined) {
|
||||
appUpdater.next(() => ({ ...getDeepLinksAndKeywords(subPluginName, licenseType) }));
|
||||
}
|
||||
}
|
|
@ -19,12 +19,13 @@ export const NETWORK = i18n.translate('xpack.securitySolution.navigation.network
|
|||
defaultMessage: 'Network',
|
||||
});
|
||||
|
||||
export const DETECTION_ENGINE = i18n.translate(
|
||||
'xpack.securitySolution.navigation.detectionEngine',
|
||||
{
|
||||
defaultMessage: 'Detections',
|
||||
}
|
||||
);
|
||||
export const RULES = i18n.translate('xpack.securitySolution.navigation.rules', {
|
||||
defaultMessage: 'Rules',
|
||||
});
|
||||
|
||||
export const EXCEPTIONS = i18n.translate('xpack.securitySolution.navigation.exceptions', {
|
||||
defaultMessage: 'Exceptions',
|
||||
});
|
||||
|
||||
export const ALERTS = i18n.translate('xpack.securitySolution.navigation.alerts', {
|
||||
defaultMessage: 'Alerts',
|
||||
|
@ -41,3 +42,31 @@ export const CASE = i18n.translate('xpack.securitySolution.navigation.case', {
|
|||
export const ADMINISTRATION = i18n.translate('xpack.securitySolution.navigation.administration', {
|
||||
defaultMessage: 'Administration',
|
||||
});
|
||||
export const ENDPOINTS = i18n.translate('xpack.securitySolution.search.administration.endpoints', {
|
||||
defaultMessage: 'Endpoints',
|
||||
});
|
||||
export const TRUSTED_APPLICATIONS = i18n.translate(
|
||||
'xpack.securitySolution.search.administration.trustedApps',
|
||||
{
|
||||
defaultMessage: 'Trusted Applications',
|
||||
}
|
||||
);
|
||||
export const EVENT_FILTERS = i18n.translate(
|
||||
'xpack.securitySolution.search.administration.eventFilters',
|
||||
{
|
||||
defaultMessage: 'Event Filters',
|
||||
}
|
||||
);
|
||||
|
||||
export const DETECT = i18n.translate('xpack.securitySolution.navigation.detect', {
|
||||
defaultMessage: 'Detect',
|
||||
});
|
||||
export const EXPLORE = i18n.translate('xpack.securitySolution.navigation.explore', {
|
||||
defaultMessage: 'Explore',
|
||||
});
|
||||
export const INVESTIGATE = i18n.translate('xpack.securitySolution.navigation.investigate', {
|
||||
defaultMessage: 'Investigate',
|
||||
});
|
||||
export const MANAGE = i18n.translate('xpack.securitySolution.navigation.manage', {
|
||||
defaultMessage: 'Manage',
|
||||
});
|
||||
|
|
|
@ -16,9 +16,10 @@ import {
|
|||
StateFromReducersMapObject,
|
||||
CombinedState,
|
||||
} from 'redux';
|
||||
|
||||
import { RouteProps } from 'react-router-dom';
|
||||
import { AppMountParameters, AppDeepLink } from '../../../../../src/core/public';
|
||||
import { StartServices } from '../types';
|
||||
import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/public';
|
||||
import { StartedSubPlugins, StartServices } from '../types';
|
||||
|
||||
/**
|
||||
* The React properties used to render `SecurityApp` as well as the `element` to render it into.
|
||||
|
@ -26,7 +27,8 @@ import { StartServices } from '../types';
|
|||
export interface RenderAppProps extends AppMountParameters {
|
||||
services: StartServices;
|
||||
store: Store<State, Action>;
|
||||
SubPluginRoutes: React.FC;
|
||||
subPlugins: StartedSubPlugins;
|
||||
usageCollection?: UsageCollectionSetup;
|
||||
}
|
||||
|
||||
import { State, SubPluginsInitReducer } from '../common/store';
|
||||
|
@ -34,7 +36,7 @@ import { Immutable } from '../../common/endpoint/types';
|
|||
import { AppAction } from '../common/store/actions';
|
||||
import { TimelineState } from '../timelines/store/timeline/types';
|
||||
import { SecurityPageName } from '../../common/constants';
|
||||
export { SecurityPageName } from '../../common/constants';
|
||||
export { SecurityPageName, SecurityPageGroupName } from '../../common/constants';
|
||||
|
||||
export interface SecuritySubPluginStore<K extends SecuritySubPluginKeyStore, T> {
|
||||
initialState: Record<K, T | undefined>;
|
||||
|
@ -42,8 +44,10 @@ export interface SecuritySubPluginStore<K extends SecuritySubPluginKeyStore, T>
|
|||
middleware?: Array<Middleware<{}, State, Dispatch<AppAction | Immutable<AppAction>>>>;
|
||||
}
|
||||
|
||||
export type SecuritySubPluginRoutes = RouteProps[];
|
||||
|
||||
export interface SecuritySubPlugin {
|
||||
SubPluginRoutes: React.FC;
|
||||
routes: SecuritySubPluginRoutes;
|
||||
storageTimelines?: Pick<TimelineState, 'timelineById'>;
|
||||
}
|
||||
|
||||
|
@ -55,14 +59,21 @@ export type SecuritySubPluginKeyStore =
|
|||
| 'alertList'
|
||||
| 'management';
|
||||
|
||||
export type SecuritySubPluginNames = keyof typeof SecurityPageName;
|
||||
export type SecurityDeepLinkName =
|
||||
| SecurityPageName.overview
|
||||
| SecurityPageName.detections
|
||||
| SecurityPageName.hosts
|
||||
| SecurityPageName.network
|
||||
| SecurityPageName.timelines
|
||||
| SecurityPageName.case
|
||||
| SecurityPageName.administration;
|
||||
|
||||
interface SecurityDeepLink {
|
||||
base: AppDeepLink[];
|
||||
premium?: AppDeepLink[];
|
||||
}
|
||||
|
||||
export type SecurityDeepLinks = { [key in SecuritySubPluginNames]: SecurityDeepLink };
|
||||
export type SecurityDeepLinks = { [key in SecurityDeepLinkName]: SecurityDeepLink };
|
||||
|
||||
/**
|
||||
* Returned by the various 'SecuritySubPlugin' classes from the `start` method.
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from '../../../common/components/link_to';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { APP_ID, CASES_APP_ID } from '../../../../common/constants';
|
||||
import { APP_ID } from '../../../../common/constants';
|
||||
|
||||
export interface AllCasesNavProps {
|
||||
detailName: string;
|
||||
|
@ -36,7 +36,8 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
|
|||
const goToCreateCase = useCallback(
|
||||
async (ev) => {
|
||||
ev.preventDefault();
|
||||
return navigateToApp(CASES_APP_ID, {
|
||||
return navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCreateCaseUrl(urlSearch),
|
||||
});
|
||||
},
|
||||
|
@ -46,7 +47,8 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
|
|||
const goToCaseConfigure = useCallback(
|
||||
async (ev) => {
|
||||
ev.preventDefault();
|
||||
return navigateToApp(CASES_APP_ID, {
|
||||
return navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getConfigureCasesUrl(urlSearch),
|
||||
});
|
||||
},
|
||||
|
@ -59,7 +61,8 @@ export const AllCases = React.memo<AllCasesProps>(({ userCanCrud }) => {
|
|||
return formatUrl(getCaseDetailsUrl({ id: detailName, subCaseId }));
|
||||
},
|
||||
onClick: async ({ detailName, subCaseId, search }: AllCasesNavProps) => {
|
||||
return navigateToApp(CASES_APP_ID, {
|
||||
return navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCaseDetailsUrl({ id: detailName, search, subCaseId }),
|
||||
});
|
||||
},
|
||||
|
|
|
@ -23,11 +23,7 @@ import { Case, CaseViewRefreshPropInterface } from '../../../../../cases/common'
|
|||
import { TimelineId } from '../../../../common/types/timeline';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import { KibanaServices, useKibana } from '../../../common/lib/kibana';
|
||||
import {
|
||||
APP_ID,
|
||||
CASES_APP_ID,
|
||||
DETECTION_ENGINE_QUERY_SIGNALS_URL,
|
||||
} from '../../../../common/constants';
|
||||
import { APP_ID, DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../common/constants';
|
||||
import { timelineActions } from '../../../timelines/store/timeline';
|
||||
import { useSourcererScope } from '../../../common/containers/sourcerer';
|
||||
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
|
||||
|
@ -132,7 +128,7 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) =
|
|||
const dispatch = useDispatch();
|
||||
const { formatUrl, search } = useFormatUrl(SecurityPageName.case);
|
||||
const { formatUrl: detectionsFormatUrl, search: detectionsUrlSearch } = useFormatUrl(
|
||||
SecurityPageName.detections
|
||||
SecurityPageName.rules
|
||||
);
|
||||
|
||||
const allCasesLink = getCaseUrl(search);
|
||||
|
@ -190,7 +186,8 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) =
|
|||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
return navigateToApp(CASES_APP_ID, {
|
||||
return navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: allCasesLink,
|
||||
});
|
||||
},
|
||||
|
@ -201,7 +198,8 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) =
|
|||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
return navigateToApp(CASES_APP_ID, {
|
||||
return navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCaseDetailsUrl({ id: caseId }),
|
||||
});
|
||||
},
|
||||
|
@ -213,7 +211,8 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) =
|
|||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
return navigateToApp(CASES_APP_ID, {
|
||||
return navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getConfigureCasesUrl(search),
|
||||
});
|
||||
},
|
||||
|
@ -227,7 +226,8 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) =
|
|||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
return navigateToApp(`${APP_ID}:${SecurityPageName.detections}`, {
|
||||
return navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.rules,
|
||||
path: getRuleDetailsUrl(ruleId ?? ''),
|
||||
});
|
||||
},
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Create } from '.';
|
|||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { Case } from '../../../../../cases/public/containers/types';
|
||||
import { basicCase } from '../../../../../cases/public/containers/mock';
|
||||
import { APP_ID, CASES_APP_ID } from '../../../../common/constants';
|
||||
import { APP_ID, SecurityPageName } from '../../../../common/constants';
|
||||
import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
|
||||
|
||||
jest.mock('../use_insert_timeline');
|
||||
|
@ -71,8 +71,9 @@ describe('Create case', () => {
|
|||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockNavigateToApp).toHaveBeenCalledWith(CASES_APP_ID, {
|
||||
expect(mockNavigateToApp).toHaveBeenCalledWith(APP_ID, {
|
||||
path: `?${mockRes}`,
|
||||
deepLinkId: SecurityPageName.case,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
@ -95,8 +96,9 @@ describe('Create case', () => {
|
|||
);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(mockNavigateToApp).toHaveBeenNthCalledWith(1, CASES_APP_ID, {
|
||||
expect(mockNavigateToApp).toHaveBeenNthCalledWith(1, APP_ID, {
|
||||
path: `/basic-case-id?${mockRes}`,
|
||||
deepLinkId: SecurityPageName.case,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
|
@ -12,9 +12,10 @@ import { getCaseDetailsUrl, getCaseUrl } from '../../../common/components/link_t
|
|||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline';
|
||||
import { useInsertTimeline } from '../use_insert_timeline';
|
||||
import { APP_ID, CASES_APP_ID } from '../../../../common/constants';
|
||||
import { APP_ID } from '../../../../common/constants';
|
||||
import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search';
|
||||
import { navTabs } from '../../../app/home/home_navigations';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
|
||||
export const Create = React.memo(() => {
|
||||
const {
|
||||
|
@ -24,14 +25,16 @@ export const Create = React.memo(() => {
|
|||
const search = useGetUrlSearch(navTabs.case);
|
||||
const onSuccess = useCallback(
|
||||
async ({ id }) =>
|
||||
navigateToApp(CASES_APP_ID, {
|
||||
navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCaseDetailsUrl({ id, search }),
|
||||
}),
|
||||
[navigateToApp, search]
|
||||
);
|
||||
const handleSetIsCancel = useCallback(
|
||||
async () =>
|
||||
navigateToApp(CASES_APP_ID, {
|
||||
navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCaseUrl(search),
|
||||
}),
|
||||
[navigateToApp, search]
|
||||
|
|
|
@ -15,6 +15,7 @@ import { TestProviders } from '../../../common/mock';
|
|||
import { AddToCaseAction } from './add_to_case_action';
|
||||
import { basicCase } from '../../../../../cases/public/containers/mock';
|
||||
import { Case, SECURITY_SOLUTION_OWNER } from '../../../../../cases/common';
|
||||
import { APP_ID, SecurityPageName } from '../../../../common/constants';
|
||||
|
||||
jest.mock('../../../common/lib/kibana');
|
||||
jest.mock('../../../common/components/link_to', () => {
|
||||
|
@ -177,8 +178,9 @@ describe('AddToCaseAction', () => {
|
|||
.first()
|
||||
.simulate('click');
|
||||
|
||||
expect(mockNavigateToApp).toHaveBeenCalledWith('securitySolution:case', {
|
||||
expect(mockNavigateToApp).toHaveBeenCalledWith(APP_ID, {
|
||||
path: '/basic-case-id',
|
||||
deepLinkId: SecurityPageName.case,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
|
||||
import { Case, CaseStatuses, StatusAll } from '../../../../../cases/common';
|
||||
import { APP_ID, CASES_APP_ID } from '../../../../common/constants';
|
||||
import { APP_ID } from '../../../../common/constants';
|
||||
import { Ecs } from '../../../../common/ecs';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import {
|
||||
|
@ -80,7 +80,8 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
|||
|
||||
const onViewCaseClick = useCallback(
|
||||
(id) => {
|
||||
navigateToApp(CASES_APP_ID, {
|
||||
navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCaseDetailsUrl({ id }),
|
||||
});
|
||||
},
|
||||
|
@ -134,7 +135,8 @@ const AddToCaseActionComponent: React.FC<AddToCaseActionProps> = ({
|
|||
const goToCreateCase = useCallback(
|
||||
async (ev) => {
|
||||
ev.preventDefault();
|
||||
return navigateToApp(CASES_APP_ID, {
|
||||
return navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCreateCaseUrl(urlSearch),
|
||||
});
|
||||
},
|
||||
|
|
|
@ -6,14 +6,14 @@
|
|||
*/
|
||||
|
||||
import { SecuritySubPlugin } from '../app/types';
|
||||
import { CasesRoutes } from './routes';
|
||||
import { routes } from './routes';
|
||||
|
||||
export class Cases {
|
||||
public setup() {}
|
||||
|
||||
public start(): SecuritySubPlugin {
|
||||
return {
|
||||
SubPluginRoutes: CasesRoutes,
|
||||
routes,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import { useGetUserCasesPermissions, useKibana } from '../../common/lib/kibana';
|
|||
import { getCaseUrl } from '../../common/components/link_to';
|
||||
import { navTabs } from '../../app/home/home_navigations';
|
||||
import { CaseView } from '../components/case_view';
|
||||
import { CASES_APP_ID } from '../../../common/constants';
|
||||
import { APP_ID } from '../../../common/constants';
|
||||
|
||||
export const CaseDetailsPage = React.memo(() => {
|
||||
const {
|
||||
|
@ -31,9 +31,12 @@ export const CaseDetailsPage = React.memo(() => {
|
|||
|
||||
useEffect(() => {
|
||||
if (userPermissions != null && !userPermissions.read) {
|
||||
navigateToApp(CASES_APP_ID, { path: getCaseUrl(search) });
|
||||
navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCaseUrl(search),
|
||||
});
|
||||
}
|
||||
}, [navigateToApp, userPermissions, search]);
|
||||
}, [userPermissions, navigateToApp, search]);
|
||||
|
||||
return caseId != null ? (
|
||||
<>
|
||||
|
|
|
@ -18,7 +18,8 @@ import { navTabs } from '../../app/home/home_navigations';
|
|||
import { CaseHeaderPage } from '../components/case_header_page';
|
||||
import { WhitePageWrapper, SectionWrapper } from '../components/wrappers';
|
||||
import * as i18n from './translations';
|
||||
import { APP_ID, CASES_APP_ID } from '../../../common/constants';
|
||||
import { APP_ID } from '../../../common/constants';
|
||||
import { SiemNavTabKey } from '../../common/components/navigation/types';
|
||||
|
||||
const ConfigureCasesPageComponent: React.FC = () => {
|
||||
const {
|
||||
|
@ -32,14 +33,15 @@ const ConfigureCasesPageComponent: React.FC = () => {
|
|||
() => ({
|
||||
href: getCaseUrl(search),
|
||||
text: i18n.BACK_TO_ALL,
|
||||
pageId: SecurityPageName.case,
|
||||
pageId: SecurityPageName.case as SiemNavTabKey,
|
||||
}),
|
||||
[search]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (userPermissions != null && !userPermissions.read) {
|
||||
navigateToApp(CASES_APP_ID, {
|
||||
navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCaseUrl(search),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -17,7 +17,8 @@ import { navTabs } from '../../app/home/home_navigations';
|
|||
import { CaseHeaderPage } from '../components/case_header_page';
|
||||
import { Create } from '../components/create';
|
||||
import * as i18n from './translations';
|
||||
import { CASES_APP_ID } from '../../../common/constants';
|
||||
import { APP_ID } from '../../../common/constants';
|
||||
import { SiemNavTabKey } from '../../common/components/navigation/types';
|
||||
|
||||
export const CreateCasePage = React.memo(() => {
|
||||
const userPermissions = useGetUserCasesPermissions();
|
||||
|
@ -30,14 +31,15 @@ export const CreateCasePage = React.memo(() => {
|
|||
() => ({
|
||||
href: getCaseUrl(search),
|
||||
text: i18n.BACK_TO_ALL,
|
||||
pageId: SecurityPageName.case,
|
||||
pageId: SecurityPageName.case as SiemNavTabKey,
|
||||
}),
|
||||
[search]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (userPermissions != null && !userPermissions.crud) {
|
||||
navigateToApp(CASES_APP_ID, {
|
||||
navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCaseUrl(search),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,14 +14,14 @@ import { CasesPage } from './case';
|
|||
import { CreateCasePage } from './create_case';
|
||||
import { ConfigureCasesPage } from './configure_cases';
|
||||
import { useGetUserCasesPermissions, useKibana } from '../../common/lib/kibana';
|
||||
import { CASES_PATH } from '../../../common/constants';
|
||||
|
||||
const casesPagePath = '';
|
||||
const caseDetailsPagePath = `${casesPagePath}/:detailName`;
|
||||
const caseDetailsPagePath = `${CASES_PATH}/:detailName`;
|
||||
const subCaseDetailsPagePath = `${caseDetailsPagePath}/sub-cases/:subCaseId`;
|
||||
const caseDetailsPagePathWithCommentId = `${caseDetailsPagePath}/:commentId`;
|
||||
const subCaseDetailsPagePathWithCommentId = `${subCaseDetailsPagePath}/:commentId`;
|
||||
const createCasePagePath = `${casesPagePath}/create`;
|
||||
const configureCasesPagePath = `${casesPagePath}/configure`;
|
||||
const createCasePagePath = `${CASES_PATH}/create`;
|
||||
const configureCasesPagePath = `${CASES_PATH}/configure`;
|
||||
|
||||
const CaseContainerComponent: React.FC = () => {
|
||||
const userPermissions = useGetUserCasesPermissions();
|
||||
|
@ -63,7 +63,7 @@ const CaseContainerComponent: React.FC = () => {
|
|||
<Route path={caseDetailsPagePath}>
|
||||
<CaseDetailsPage />
|
||||
</Route>
|
||||
<Route strict exact path={casesPagePath}>
|
||||
<Route strict exact path={CASES_PATH}>
|
||||
<CasesPage />
|
||||
</Route>
|
||||
</Switch>
|
||||
|
|
|
@ -13,7 +13,8 @@ import { getCaseDetailsUrl, getCreateCaseUrl } from '../../common/components/lin
|
|||
import { RouteSpyState } from '../../common/utils/route/types';
|
||||
import * as i18n from './translations';
|
||||
import { GetUrlForApp } from '../../common/components/navigation/types';
|
||||
import { CASES_APP_ID } from '../../../common/constants';
|
||||
import { APP_ID } from '../../../common/constants';
|
||||
import { SecurityPageName } from '../../app/types';
|
||||
|
||||
export const getBreadcrumbs = (
|
||||
params: RouteSpyState,
|
||||
|
@ -25,7 +26,8 @@ export const getBreadcrumbs = (
|
|||
let breadcrumb = [
|
||||
{
|
||||
text: i18n.PAGE_TITLE,
|
||||
href: getUrlForApp(CASES_APP_ID, {
|
||||
href: getUrlForApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: queryParameters,
|
||||
}),
|
||||
},
|
||||
|
@ -35,7 +37,8 @@ export const getBreadcrumbs = (
|
|||
...breadcrumb,
|
||||
{
|
||||
text: i18n.CREATE_BC_TITLE,
|
||||
href: getUrlForApp(CASES_APP_ID, {
|
||||
href: getUrlForApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCreateCaseUrl(queryParameters),
|
||||
}),
|
||||
},
|
||||
|
@ -45,7 +48,8 @@ export const getBreadcrumbs = (
|
|||
...breadcrumb,
|
||||
{
|
||||
text: params.state?.caseTitle ?? '',
|
||||
href: getUrlForApp(CASES_APP_ID, {
|
||||
href: getUrlForApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCaseDetailsUrl({ id: params.detailName, search: queryParameters }),
|
||||
}),
|
||||
},
|
||||
|
|
|
@ -6,16 +6,21 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router-dom';
|
||||
|
||||
import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public';
|
||||
import { SecurityPageName, SecuritySubPluginRoutes } from '../app/types';
|
||||
import { CASES_PATH } from '../../common/constants';
|
||||
import { Case } from './pages';
|
||||
import { NotFoundPage } from '../app/404';
|
||||
|
||||
export const CasesRoutes: React.FC = () => (
|
||||
<Switch>
|
||||
<Route path="/">
|
||||
<Case />
|
||||
</Route>
|
||||
<Route render={() => <NotFoundPage />} />
|
||||
</Switch>
|
||||
export const CasesRoutes = () => (
|
||||
<TrackApplicationView viewId={SecurityPageName.case}>
|
||||
<Case />
|
||||
</TrackApplicationView>
|
||||
);
|
||||
|
||||
export const routes: SecuritySubPluginRoutes = [
|
||||
{
|
||||
path: CASES_PATH,
|
||||
render: CasesRoutes,
|
||||
},
|
||||
];
|
||||
|
|
|
@ -8,10 +8,13 @@
|
|||
import React, { memo, MouseEventHandler } from 'react';
|
||||
import { EuiLink, EuiLinkProps, EuiButton, EuiButtonProps } from '@elastic/eui';
|
||||
import { useNavigateToAppEventHandler } from '../../hooks/endpoint/use_navigate_to_app_event_handler';
|
||||
import { APP_ID } from '../../../../common/constants';
|
||||
|
||||
export type LinkToAppProps = (EuiLinkProps | EuiButtonProps) & {
|
||||
/** the app id - normally the value of the `id` in that plugin's `kibana.json` */
|
||||
appId: string;
|
||||
appId?: string;
|
||||
/** optional app deep link id */
|
||||
deepLinkId?: string;
|
||||
/** Any app specific path (route) */
|
||||
appPath?: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
|
@ -26,8 +29,17 @@ export type LinkToAppProps = (EuiLinkProps | EuiButtonProps) & {
|
|||
* a given app without causing a full browser refresh
|
||||
*/
|
||||
export const LinkToApp = memo<LinkToAppProps>(
|
||||
({ appId, appPath: path, appState: state, onClick, asButton, children, ...otherProps }) => {
|
||||
const handleOnClick = useNavigateToAppEventHandler(appId, { path, state, onClick });
|
||||
({
|
||||
appId = APP_ID,
|
||||
deepLinkId,
|
||||
appPath: path,
|
||||
appState: state,
|
||||
onClick,
|
||||
asButton,
|
||||
children,
|
||||
...otherProps
|
||||
}) => {
|
||||
const handleOnClick = useNavigateToAppEventHandler(appId, { deepLinkId, path, state, onClick });
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -9,7 +9,8 @@ import React, { memo, useEffect } from 'react';
|
|||
import { useLocation } from 'react-router-dom';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { AppLocation } from '../../../../common/endpoint/types';
|
||||
import { AppAction } from '../../store/actions';
|
||||
import { timelineActions } from '../../../timelines/store/timeline';
|
||||
import { TimelineId } from '../../../../../timelines/common';
|
||||
|
||||
/**
|
||||
* This component should be used above all routes, but below the Provider.
|
||||
|
@ -17,7 +18,11 @@ import { AppAction } from '../../store/actions';
|
|||
*/
|
||||
export const RouteCapture = memo(({ children }) => {
|
||||
const location: AppLocation = useLocation();
|
||||
const dispatch: (action: AppAction) => unknown = useDispatch();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(timelineActions.showTimeline({ id: TimelineId.active, show: false }));
|
||||
}, [dispatch, location.pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch({ type: 'userChangedUrl', payload: location });
|
||||
|
|
|
@ -15,17 +15,7 @@ import { HeaderPage } from './index';
|
|||
import { useMountAppended } from '../../utils/use_mount_appended';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const original = jest.requireActual('react-router-dom');
|
||||
|
||||
return {
|
||||
...original,
|
||||
useHistory: () => ({
|
||||
useHistory: jest.fn(),
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('../../lib/kibana');
|
||||
jest.mock('../link_to');
|
||||
|
||||
describe('HeaderPage', () => {
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { LinkIcon, LinkIconProps } from '../link_icon';
|
||||
|
@ -24,6 +23,8 @@ import { useFormatUrl } from '../link_to';
|
|||
import { SecurityPageName } from '../../../app/types';
|
||||
import { Sourcerer } from '../sourcerer';
|
||||
import { SourcererScopeName } from '../../store/sourcerer/model';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { SiemNavTabKey } from '../navigation/types';
|
||||
|
||||
interface HeaderProps {
|
||||
border?: boolean;
|
||||
|
@ -64,10 +65,10 @@ const HeaderSection = styled(EuiPageHeaderSection)`
|
|||
HeaderSection.displayName = 'HeaderSection';
|
||||
|
||||
interface BackOptions {
|
||||
href: LinkIconProps['href'];
|
||||
text: LinkIconProps['children'];
|
||||
href?: LinkIconProps['href'];
|
||||
dataTestSubj?: string;
|
||||
pageId: SecurityPageName;
|
||||
pageId: SiemNavTabKey;
|
||||
}
|
||||
|
||||
export interface HeaderPageProps extends HeaderProps {
|
||||
|
@ -99,16 +100,18 @@ const HeaderPageComponent: React.FC<HeaderPageProps> = ({
|
|||
titleNode,
|
||||
...rest
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
const { navigateToUrl } = useKibana().services.application;
|
||||
|
||||
const { formatUrl } = useFormatUrl(backOptions?.pageId ?? SecurityPageName.overview);
|
||||
const backUrl = formatUrl(backOptions?.href ?? '');
|
||||
const goTo = useCallback(
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
if (backOptions) {
|
||||
history.push(backOptions.href ?? '');
|
||||
navigateToUrl(backUrl);
|
||||
}
|
||||
},
|
||||
[backOptions, history]
|
||||
[backOptions, navigateToUrl, backUrl]
|
||||
);
|
||||
return (
|
||||
<>
|
||||
|
@ -119,7 +122,7 @@ const HeaderPageComponent: React.FC<HeaderPageProps> = ({
|
|||
<LinkIcon
|
||||
dataTestSubj={backOptions.dataTestSubj ?? 'link-back'}
|
||||
onClick={goTo}
|
||||
href={formatUrl(backOptions.href ?? '')}
|
||||
href={backUrl}
|
||||
iconType="arrowLeft"
|
||||
>
|
||||
{backOptions.text}
|
||||
|
@ -128,7 +131,6 @@ const HeaderPageComponent: React.FC<HeaderPageProps> = ({
|
|||
)}
|
||||
|
||||
{!backOptions && backComponent && <>{backComponent}</>}
|
||||
|
||||
{titleNode || (
|
||||
<Title
|
||||
draggableArguments={draggableArguments}
|
||||
|
|
|
@ -11,7 +11,7 @@ export { getDetectionEngineUrl } from '../redirect_to_detection_engine';
|
|||
export { getAppOverviewUrl } from '../redirect_to_overview';
|
||||
export { getHostDetailsUrl, getHostsUrl } from '../redirect_to_hosts';
|
||||
export { getNetworkUrl, getNetworkDetailsUrl } from '../redirect_to_network';
|
||||
export { getTimelinesUrl, getTimelineTabsUrl, getTimelineUrl } from '../redirect_to_timelines';
|
||||
export { getTimelineTabsUrl, getTimelineUrl } from '../redirect_to_timelines';
|
||||
export {
|
||||
getCaseDetailsUrl,
|
||||
getCaseUrl,
|
||||
|
|
|
@ -7,17 +7,17 @@
|
|||
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { useCallback } from 'react';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import { useGetUrlSearch } from '../navigation/use_get_url_search';
|
||||
import { navTabs } from '../../../app/home/home_navigations';
|
||||
import { APP_ID } from '../../../../common/constants';
|
||||
import { useKibana } from '../../lib/kibana';
|
||||
import { SiemNavTabKey } from '../navigation/types';
|
||||
|
||||
export { getDetectionEngineUrl, getRuleDetailsUrl } from './redirect_to_detection_engine';
|
||||
export { getAppOverviewUrl } from './redirect_to_overview';
|
||||
export { getHostDetailsUrl, getHostsUrl } from './redirect_to_hosts';
|
||||
export { getNetworkUrl, getNetworkDetailsUrl } from './redirect_to_network';
|
||||
export { getTimelinesUrl, getTimelineTabsUrl, getTimelineUrl } from './redirect_to_timelines';
|
||||
export { getTimelineTabsUrl, getTimelineUrl } from './redirect_to_timelines';
|
||||
export {
|
||||
getCaseDetailsUrl,
|
||||
getCaseUrl,
|
||||
|
@ -33,7 +33,7 @@ interface FormatUrlOptions {
|
|||
|
||||
export type FormatUrl = (path: string, options?: Partial<FormatUrlOptions>) => string;
|
||||
|
||||
export const useFormatUrl = (page: SecurityPageName) => {
|
||||
export const useFormatUrl = (page: SiemNavTabKey) => {
|
||||
const { getUrlForApp } = useKibana().services.application;
|
||||
const search = useGetUrlSearch(navTabs[page]);
|
||||
const formatUrl = useCallback<FormatUrl>(
|
||||
|
@ -48,12 +48,10 @@ export const useFormatUrl = (page: SecurityPageName) => {
|
|||
? ''
|
||||
: `?${pathArr[1]}`
|
||||
}`;
|
||||
return getUrlForApp(`${APP_ID}:${page}`, {
|
||||
path: formattedPath,
|
||||
absolute,
|
||||
});
|
||||
return getUrlForApp(APP_ID, { deepLinkId: page, path: formattedPath, absolute });
|
||||
},
|
||||
[getUrlForApp, page, search]
|
||||
);
|
||||
|
||||
return { formatUrl, search };
|
||||
};
|
||||
|
|
|
@ -9,15 +9,12 @@ import { appendSearch } from './helpers';
|
|||
|
||||
export const getDetectionEngineUrl = (search?: string) => `${appendSearch(search)}`;
|
||||
|
||||
export const getDetectionEngineTabUrl = (tabPath: string, search?: string) =>
|
||||
`/${tabPath}${appendSearch(search)}`;
|
||||
export const getRulesUrl = (search?: string) => `${appendSearch(search)}`;
|
||||
|
||||
export const getRulesUrl = (search?: string) => `/rules${appendSearch(search)}`;
|
||||
|
||||
export const getCreateRuleUrl = (search?: string) => `/rules/create${appendSearch(search)}`;
|
||||
export const getCreateRuleUrl = (search?: string) => `/create${appendSearch(search)}`;
|
||||
|
||||
export const getRuleDetailsUrl = (detailName: string, search?: string) =>
|
||||
`/rules/id/${detailName}${appendSearch(search)}`;
|
||||
`/id/${detailName}${appendSearch(search)}`;
|
||||
|
||||
export const getEditRuleUrl = (detailName: string, search?: string) =>
|
||||
`/rules/id/${detailName}/edit${appendSearch(search)}`;
|
||||
`/id/${detailName}/edit${appendSearch(search)}`;
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
*/
|
||||
|
||||
import { HostsTableType } from '../../../hosts/store/model';
|
||||
|
||||
import { HOSTS_PATH } from '../../../../common/constants';
|
||||
import { appendSearch } from './helpers';
|
||||
|
||||
export const getHostsUrl = (search?: string) => `${appendSearch(search)}`;
|
||||
export const getHostsUrl = (search?: string) => `${HOSTS_PATH}${appendSearch(search)}`;
|
||||
|
||||
export const getTabsOnHostsUrl = (tabName: HostsTableType, search?: string) =>
|
||||
`/${tabName}${appendSearch(search)}`;
|
||||
|
|
|
@ -9,8 +9,6 @@ import { isEmpty } from 'lodash/fp';
|
|||
import { TimelineTypeLiteral } from '../../../../common/types/timeline';
|
||||
import { appendSearch } from './helpers';
|
||||
|
||||
export const getTimelinesUrl = (search?: string) => `${appendSearch(search)}`;
|
||||
|
||||
export const getTimelineTabsUrl = (tabName: TimelineTypeLiteral, search?: string) =>
|
||||
`/${tabName}${appendSearch(search)}`;
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import React, { useMemo, useCallback } from 'react';
|
|||
import { isNil } from 'lodash/fp';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { IP_REPUTATION_LINKS_SETTING, APP_ID, CASES_APP_ID } from '../../../../common/constants';
|
||||
import { IP_REPUTATION_LINKS_SETTING, APP_ID } from '../../../../common/constants';
|
||||
import {
|
||||
DefaultFieldRendererOverflow,
|
||||
DEFAULT_MORE_MAX_HEIGHT,
|
||||
|
@ -71,7 +71,8 @@ const HostDetailsLinkComponent: React.FC<{
|
|||
const goToHostDetails = useCallback(
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
navigateToApp(`${APP_ID}:${SecurityPageName.hosts}`, {
|
||||
navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.hosts,
|
||||
path: getHostDetailsUrl(encodeURIComponent(hostName), search),
|
||||
});
|
||||
},
|
||||
|
@ -142,7 +143,8 @@ const NetworkDetailsLinkComponent: React.FC<{
|
|||
const goToNetworkDetails = useCallback(
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
navigateToApp(`${APP_ID}:${SecurityPageName.network}`, {
|
||||
navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.network,
|
||||
path: getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(ip)), flowTarget, search),
|
||||
});
|
||||
},
|
||||
|
@ -179,7 +181,8 @@ const CaseDetailsLinkComponent: React.FC<{
|
|||
const goToCaseDetails = useCallback(
|
||||
async (ev) => {
|
||||
ev.preventDefault();
|
||||
return navigateToApp(CASES_APP_ID, {
|
||||
return navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCaseDetailsUrl({ id: detailName, search, subCaseId }),
|
||||
});
|
||||
},
|
||||
|
@ -206,7 +209,8 @@ export const CreateCaseLink = React.memo<{ children: React.ReactNode }>(({ child
|
|||
const goToCreateCase = useCallback(
|
||||
async (ev) => {
|
||||
ev.preventDefault();
|
||||
return navigateToApp(CASES_APP_ID, {
|
||||
return navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.case,
|
||||
path: getCreateCaseUrl(search),
|
||||
});
|
||||
},
|
||||
|
|
|
@ -8,95 +8,98 @@
|
|||
import { parse, stringify } from 'query-string';
|
||||
import React from 'react';
|
||||
|
||||
import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
|
||||
import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom';
|
||||
|
||||
import { addEntitiesToKql } from './add_entities_to_kql';
|
||||
import { replaceKQLParts } from './replace_kql_parts';
|
||||
import { emptyEntity, multipleEntities, getMultipleEntities } from './entity_helpers';
|
||||
import { HostsTableType } from '../../../../hosts/store/model';
|
||||
|
||||
import { url as urlUtils } from '../../../../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
import { HOSTS_PATH } from '../../../../../common/constants';
|
||||
interface QueryStringType {
|
||||
'?_g': string;
|
||||
query: string | null;
|
||||
timerange: string | null;
|
||||
}
|
||||
|
||||
type MlHostConditionalProps = Partial<RouteComponentProps<{}>> & { url: string };
|
||||
export const MlHostConditionalContainer = React.memo(() => {
|
||||
const { path } = useRouteMatch();
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
strict
|
||||
exact
|
||||
path={path}
|
||||
render={({ location }) => {
|
||||
const queryStringDecoded = parse(location.search.substring(1), {
|
||||
sort: false,
|
||||
}) as Required<QueryStringType>;
|
||||
|
||||
export const MlHostConditionalContainer = React.memo<MlHostConditionalProps>(({ url }) => (
|
||||
<Switch>
|
||||
<Route
|
||||
strict
|
||||
exact
|
||||
path={url}
|
||||
render={({ location }) => {
|
||||
const queryStringDecoded = parse(location.search.substring(1), {
|
||||
sort: false,
|
||||
}) as Required<QueryStringType>;
|
||||
|
||||
if (queryStringDecoded.query != null) {
|
||||
queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query);
|
||||
}
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
return <Redirect to={`?${reEncoded}`} />;
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/:hostName`}
|
||||
render={({
|
||||
location,
|
||||
match: {
|
||||
params: { hostName },
|
||||
},
|
||||
}) => {
|
||||
const queryStringDecoded = parse(location.search.substring(1), {
|
||||
sort: false,
|
||||
}) as Required<QueryStringType>;
|
||||
|
||||
if (queryStringDecoded.query != null) {
|
||||
queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query);
|
||||
}
|
||||
if (emptyEntity(hostName)) {
|
||||
if (queryStringDecoded.query != null) {
|
||||
queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query);
|
||||
}
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
|
||||
return <Redirect to={`/${HostsTableType.anomalies}?${reEncoded}`} />;
|
||||
} else if (multipleEntities(hostName)) {
|
||||
const hosts: string[] = getMultipleEntities(hostName);
|
||||
queryStringDecoded.query = addEntitiesToKql(
|
||||
['host.name'],
|
||||
hosts,
|
||||
queryStringDecoded.query || ''
|
||||
);
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
return <Redirect to={`${HOSTS_PATH}?${reEncoded}`} />;
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={`${path}/:hostName`}
|
||||
render={({
|
||||
location,
|
||||
match: {
|
||||
params: { hostName },
|
||||
},
|
||||
}) => {
|
||||
const queryStringDecoded = parse(location.search.substring(1), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
}) as Required<QueryStringType>;
|
||||
|
||||
return <Redirect to={`/${HostsTableType.anomalies}?${reEncoded}`} />;
|
||||
} else {
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
if (queryStringDecoded.query != null) {
|
||||
queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query);
|
||||
}
|
||||
if (emptyEntity(hostName)) {
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
|
||||
return <Redirect to={`/${hostName}/${HostsTableType.anomalies}?${reEncoded}`} />;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path="/ml-hosts/"
|
||||
render={({ location: { search = '' } }) => (
|
||||
<Redirect from="/ml-hosts/" to={`/ml-hosts${search}`} />
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
));
|
||||
return <Redirect to={`${HOSTS_PATH}/${HostsTableType.anomalies}?${reEncoded}`} />;
|
||||
} else if (multipleEntities(hostName)) {
|
||||
const hosts: string[] = getMultipleEntities(hostName);
|
||||
queryStringDecoded.query = addEntitiesToKql(
|
||||
['host.name'],
|
||||
hosts,
|
||||
queryStringDecoded.query || ''
|
||||
);
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
|
||||
return <Redirect to={`${HOSTS_PATH}/${HostsTableType.anomalies}?${reEncoded}`} />;
|
||||
} else {
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
|
||||
return (
|
||||
<Redirect to={`${HOSTS_PATH}/${hostName}/${HostsTableType.anomalies}?${reEncoded}`} />
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={`${HOSTS_PATH}/ml-hosts/`}
|
||||
render={({ location: { search = '' } }) => (
|
||||
<Redirect from={`${HOSTS_PATH}/ml-hosts/`} to={`${HOSTS_PATH}/ml-hosts${search}`} />
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
});
|
||||
|
||||
MlHostConditionalContainer.displayName = 'MlHostConditionalContainer';
|
||||
|
|
|
@ -8,95 +8,102 @@
|
|||
import { parse, stringify } from 'query-string';
|
||||
import React from 'react';
|
||||
|
||||
import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom';
|
||||
import { Redirect, Route, Switch, useRouteMatch } from 'react-router-dom';
|
||||
import { addEntitiesToKql } from './add_entities_to_kql';
|
||||
import { replaceKQLParts } from './replace_kql_parts';
|
||||
import { emptyEntity, getMultipleEntities, multipleEntities } from './entity_helpers';
|
||||
|
||||
import { url as urlUtils } from '../../../../../../../../src/plugins/kibana_utils/public';
|
||||
|
||||
import { NETWORK_PATH } from '../../../../../common/constants';
|
||||
interface QueryStringType {
|
||||
'?_g': string;
|
||||
query: string | null;
|
||||
timerange: string | null;
|
||||
}
|
||||
|
||||
type MlNetworkConditionalProps = Partial<RouteComponentProps<{}>> & { url: string };
|
||||
export const MlNetworkConditionalContainer = React.memo(() => {
|
||||
const { path } = useRouteMatch();
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
strict
|
||||
exact
|
||||
path={path}
|
||||
render={({ location }) => {
|
||||
const queryStringDecoded = parse(location.search.substring(1), {
|
||||
sort: false,
|
||||
}) as Required<QueryStringType>;
|
||||
|
||||
export const MlNetworkConditionalContainer = React.memo<MlNetworkConditionalProps>(({ url }) => (
|
||||
<Switch>
|
||||
<Route
|
||||
strict
|
||||
exact
|
||||
path={url}
|
||||
render={({ location }) => {
|
||||
const queryStringDecoded = parse(location.search.substring(1), {
|
||||
sort: false,
|
||||
}) as Required<QueryStringType>;
|
||||
if (queryStringDecoded.query != null) {
|
||||
queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query);
|
||||
}
|
||||
|
||||
if (queryStringDecoded.query != null) {
|
||||
queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query);
|
||||
}
|
||||
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
|
||||
return <Redirect to={`?${reEncoded}`} />;
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={`${url}/ip/:ip`}
|
||||
render={({
|
||||
location,
|
||||
match: {
|
||||
params: { ip },
|
||||
},
|
||||
}) => {
|
||||
const queryStringDecoded = parse(location.search.substring(1), {
|
||||
sort: false,
|
||||
}) as Required<QueryStringType>;
|
||||
|
||||
if (queryStringDecoded.query != null) {
|
||||
queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query);
|
||||
}
|
||||
|
||||
if (emptyEntity(ip)) {
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
|
||||
return <Redirect to={`?${reEncoded}`} />;
|
||||
} else if (multipleEntities(ip)) {
|
||||
const ips: string[] = getMultipleEntities(ip);
|
||||
queryStringDecoded.query = addEntitiesToKql(
|
||||
['source.ip', 'destination.ip'],
|
||||
ips,
|
||||
queryStringDecoded.query || ''
|
||||
);
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
return <Redirect to={`${NETWORK_PATH}?${reEncoded}`} />;
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={`${path}/ip/:ip`}
|
||||
render={({
|
||||
location,
|
||||
match: {
|
||||
params: { ip },
|
||||
},
|
||||
}) => {
|
||||
const queryStringDecoded = parse(location.search.substring(1), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
return <Redirect to={`?${reEncoded}`} />;
|
||||
} else {
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
return <Redirect to={`/ip/${ip}?${reEncoded}`} />;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path="/ml-network/"
|
||||
render={({ location: { search = '' } }) => (
|
||||
<Redirect from="/ml-network/" to={`/ml-network${search}`} />
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
));
|
||||
}) as Required<QueryStringType>;
|
||||
|
||||
if (queryStringDecoded.query != null) {
|
||||
queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query);
|
||||
}
|
||||
|
||||
if (emptyEntity(ip)) {
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
|
||||
return <Redirect to={`${NETWORK_PATH}?${reEncoded}`} />;
|
||||
} else if (multipleEntities(ip)) {
|
||||
const ips: string[] = getMultipleEntities(ip);
|
||||
queryStringDecoded.query = addEntitiesToKql(
|
||||
['source.ip', 'destination.ip'],
|
||||
ips,
|
||||
queryStringDecoded.query || ''
|
||||
);
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
return <Redirect to={`${NETWORK_PATH}?${reEncoded}`} />;
|
||||
} else {
|
||||
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
|
||||
sort: false,
|
||||
encode: false,
|
||||
});
|
||||
return <Redirect to={`${NETWORK_PATH}/ip/${ip}?${reEncoded}`} />;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Route
|
||||
path={`${NETWORK_PATH}/ml-network/`}
|
||||
render={({ location: { search = '' } }) => (
|
||||
<Redirect
|
||||
from={`${NETWORK_PATH}/ml-network/`}
|
||||
to={{
|
||||
pathname: `${NETWORK_PATH}/ml-network`,
|
||||
search,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
});
|
||||
|
||||
MlNetworkConditionalContainer.displayName = 'MlNetworkConditionalContainer';
|
||||
|
|
|
@ -73,6 +73,27 @@ const getMockObject = (
|
|||
name: 'Timelines',
|
||||
urlKey: 'timeline',
|
||||
},
|
||||
alerts: {
|
||||
disabled: false,
|
||||
href: '/app/security/alerts',
|
||||
id: 'alerts',
|
||||
name: 'Alerts',
|
||||
urlKey: 'alerts',
|
||||
},
|
||||
exceptions: {
|
||||
disabled: false,
|
||||
href: '/app/security/exceptions',
|
||||
id: 'exceptions',
|
||||
name: 'Exceptions',
|
||||
urlKey: 'exceptions',
|
||||
},
|
||||
rules: {
|
||||
disabled: false,
|
||||
href: '/app/security/rules',
|
||||
id: 'rules',
|
||||
name: 'Rules',
|
||||
urlKey: 'rules',
|
||||
},
|
||||
},
|
||||
pageName,
|
||||
pathName,
|
||||
|
@ -112,8 +133,10 @@ const getMockObject = (
|
|||
});
|
||||
|
||||
// The string returned is different from what getUrlForApp returns, but does not matter for the purposes of this test.
|
||||
const getUrlForAppMock = (appId: string, options?: { path?: string; absolute?: boolean }) =>
|
||||
`${appId}${options?.path ?? ''}`;
|
||||
const getUrlForAppMock = (
|
||||
appId: string,
|
||||
options?: { deepLinkId?: string; path?: string; absolute?: boolean }
|
||||
) => `${appId}${options?.deepLinkId ? `/${options.deepLinkId}` : ''}${options?.path ?? ''}`;
|
||||
|
||||
describe('Navigation Breadcrumbs', () => {
|
||||
const hostName = 'siem-kibana';
|
||||
|
@ -130,12 +153,12 @@ describe('Navigation Breadcrumbs', () => {
|
|||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{
|
||||
href: 'securitySolutionoverview',
|
||||
href: 'securitySolution/overview',
|
||||
text: 'Security',
|
||||
},
|
||||
{
|
||||
href:
|
||||
"securitySolution:hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
"securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
text: 'Hosts',
|
||||
},
|
||||
{
|
||||
|
@ -151,11 +174,11 @@ describe('Navigation Breadcrumbs', () => {
|
|||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolutionoverview' },
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Network',
|
||||
href:
|
||||
"securitySolution:network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
"securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
{
|
||||
text: 'Flows',
|
||||
|
@ -170,11 +193,11 @@ describe('Navigation Breadcrumbs', () => {
|
|||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolutionoverview' },
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Timelines',
|
||||
href:
|
||||
"securitySolution:timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
"securitySolution/timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -185,16 +208,16 @@ describe('Navigation Breadcrumbs', () => {
|
|||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolutionoverview' },
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Hosts',
|
||||
href:
|
||||
"securitySolution:hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
"securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
{
|
||||
text: 'siem-kibana',
|
||||
href:
|
||||
"securitySolution:hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
"securitySolution/hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
{ text: 'Authentications', href: '' },
|
||||
]);
|
||||
|
@ -206,15 +229,15 @@ describe('Navigation Breadcrumbs', () => {
|
|||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolutionoverview' },
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Network',
|
||||
href:
|
||||
"securitySolution:network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
"securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
{
|
||||
text: ipv4,
|
||||
href: `securitySolution:network/ip/${ipv4}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`,
|
||||
href: `securitySolution/network/ip/${ipv4}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`,
|
||||
},
|
||||
{ text: 'Flows', href: '' },
|
||||
]);
|
||||
|
@ -226,45 +249,152 @@ describe('Navigation Breadcrumbs', () => {
|
|||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolutionoverview' },
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Network',
|
||||
href:
|
||||
"securitySolution:network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
"securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
{
|
||||
text: ipv6,
|
||||
href: `securitySolution:network/ip/${ipv6Encoded}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`,
|
||||
href: `securitySolution/network/ip/${ipv6Encoded}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`,
|
||||
},
|
||||
{ text: 'Flows', href: '' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return Alerts breadcrumbs when supplied detection pathname', () => {
|
||||
test('should return Alerts breadcrumbs when supplied alerts pathname', () => {
|
||||
const breadcrumbs = getBreadcrumbsForRoute(
|
||||
getMockObject('detections', '/', undefined),
|
||||
getMockObject('alerts', '/alerts', undefined),
|
||||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolutionoverview' },
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Detections',
|
||||
href:
|
||||
"securitySolution:detections?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
text: 'Alerts',
|
||||
href: '',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return Exceptions breadcrumbs when supplied exceptions pathname', () => {
|
||||
const breadcrumbs = getBreadcrumbsForRoute(
|
||||
getMockObject('exceptions', '/exceptions', undefined),
|
||||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Exceptions',
|
||||
href: '',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return Rules breadcrumbs when supplied rules pathname', () => {
|
||||
const breadcrumbs = getBreadcrumbsForRoute(
|
||||
getMockObject('rules', '/rules', undefined),
|
||||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Rules',
|
||||
href:
|
||||
"securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return Rules breadcrumbs when supplied rules Creation pathname', () => {
|
||||
const breadcrumbs = getBreadcrumbsForRoute(
|
||||
getMockObject('rules', '/rules/create', undefined),
|
||||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Rules',
|
||||
href:
|
||||
"securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
{
|
||||
text: 'Create',
|
||||
href:
|
||||
"securitySolution/rules/create?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return Rules breadcrumbs when supplied rules Details pathname', () => {
|
||||
const mockDetailName = '5a4a0460-d822-11eb-8962-bfd4aff0a9b3';
|
||||
const mockRuleName = 'RULE_NAME';
|
||||
const breadcrumbs = getBreadcrumbsForRoute(
|
||||
{
|
||||
...getMockObject('rules', `/rules/id/${mockDetailName}`, undefined),
|
||||
detailName: mockDetailName,
|
||||
state: {
|
||||
ruleName: mockRuleName,
|
||||
},
|
||||
},
|
||||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Rules',
|
||||
href:
|
||||
"securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
{
|
||||
text: mockRuleName,
|
||||
href: `securitySolution/rules/id/${mockDetailName}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return Rules breadcrumbs when supplied rules Edit pathname', () => {
|
||||
const mockDetailName = '5a4a0460-d822-11eb-8962-bfd4aff0a9b3';
|
||||
const mockRuleName = 'RULE_NAME';
|
||||
const breadcrumbs = getBreadcrumbsForRoute(
|
||||
{
|
||||
...getMockObject('rules', `/rules/id/${mockDetailName}/edit`, undefined),
|
||||
detailName: mockDetailName,
|
||||
state: {
|
||||
ruleName: mockRuleName,
|
||||
},
|
||||
},
|
||||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Rules',
|
||||
href:
|
||||
"securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
{
|
||||
text: 'RULE_NAME',
|
||||
href: `securitySolution/rules/id/${mockDetailName}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`,
|
||||
},
|
||||
{
|
||||
text: 'Edit',
|
||||
href: `securitySolution/rules/id/${mockDetailName}/edit?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return Cases breadcrumbs when supplied case pathname', () => {
|
||||
const breadcrumbs = getBreadcrumbsForRoute(
|
||||
getMockObject('case', '/', undefined),
|
||||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolutionoverview' },
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Cases',
|
||||
href:
|
||||
"securitySolution:case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
"securitySolution/case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -281,15 +411,15 @@ describe('Navigation Breadcrumbs', () => {
|
|||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolutionoverview' },
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Cases',
|
||||
href:
|
||||
"securitySolution:case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
"securitySolution/case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
{
|
||||
text: sampleCase.name,
|
||||
href: `securitySolution:case/${sampleCase.id}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`,
|
||||
href: `securitySolution/case/${sampleCase.id}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -299,10 +429,10 @@ describe('Navigation Breadcrumbs', () => {
|
|||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolutionoverview' },
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Administration',
|
||||
href: 'securitySolution:administration',
|
||||
href: 'securitySolution/endpoints',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -321,11 +451,11 @@ describe('Navigation Breadcrumbs', () => {
|
|||
getUrlForAppMock
|
||||
);
|
||||
expect(breadcrumbs).toEqual([
|
||||
{ text: 'Security', href: 'securitySolutionoverview' },
|
||||
{ text: 'Security', href: 'securitySolution/overview' },
|
||||
{
|
||||
text: 'Timelines',
|
||||
href:
|
||||
"securitySolution:timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))&timeline=(activeTab:query,graphEventId:GRAPH_EVENT_ID,id:TIMELINE_ID,isOpen:!f)",
|
||||
"securitySolution/timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))&timeline=(activeTab:query,graphEventId:GRAPH_EVENT_ID,id:TIMELINE_ID,isOpen:!f)",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -333,20 +463,35 @@ describe('Navigation Breadcrumbs', () => {
|
|||
|
||||
describe('setBreadcrumbs()', () => {
|
||||
test('should call chrome breadcrumb service with correct breadcrumbs', () => {
|
||||
setBreadcrumbs(getMockObject('hosts', '/', hostName), chromeMock, getUrlForAppMock);
|
||||
const navigateToUrlMock = jest.fn();
|
||||
setBreadcrumbs(
|
||||
getMockObject('hosts', '/', hostName),
|
||||
chromeMock,
|
||||
getUrlForAppMock,
|
||||
navigateToUrlMock
|
||||
);
|
||||
expect(setBreadcrumbsMock).toBeCalledWith([
|
||||
{ text: 'Security', href: 'securitySolutionoverview' },
|
||||
{
|
||||
expect.objectContaining({
|
||||
text: 'Security',
|
||||
href: 'securitySolution/overview',
|
||||
onClick: expect.any(Function),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
text: 'Hosts',
|
||||
href:
|
||||
"securitySolution:hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
},
|
||||
{
|
||||
"securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
onClick: expect.any(Function),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
text: 'siem-kibana',
|
||||
href:
|
||||
"securitySolution:hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
"securitySolution/hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))",
|
||||
onClick: expect.any(Function),
|
||||
}),
|
||||
{
|
||||
text: 'Authentications',
|
||||
href: '',
|
||||
},
|
||||
{ text: 'Authentications', href: '' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,16 +28,29 @@ import { getAppOverviewUrl } from '../../link_to';
|
|||
|
||||
import { TabNavigationProps } from '../tab_navigation/types';
|
||||
import { getSearch } from '../helpers';
|
||||
import { GetUrlForApp, SearchNavTab } from '../types';
|
||||
import { GetUrlForApp, NavigateToUrl, SearchNavTab } from '../types';
|
||||
|
||||
export const setBreadcrumbs = (
|
||||
spyState: RouteSpyState & TabNavigationProps,
|
||||
chrome: StartServices['chrome'],
|
||||
getUrlForApp: GetUrlForApp
|
||||
getUrlForApp: GetUrlForApp,
|
||||
navigateToUrl: NavigateToUrl
|
||||
) => {
|
||||
const breadcrumbs = getBreadcrumbsForRoute(spyState, getUrlForApp);
|
||||
if (breadcrumbs) {
|
||||
chrome.setBreadcrumbs(breadcrumbs);
|
||||
chrome.setBreadcrumbs(
|
||||
breadcrumbs.map((breadcrumb) => ({
|
||||
...breadcrumb,
|
||||
...(breadcrumb.href && !breadcrumb.onClick
|
||||
? {
|
||||
onClick: (ev) => {
|
||||
ev.preventDefault();
|
||||
navigateToUrl(breadcrumb.href!);
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
}))
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -53,12 +66,12 @@ const isTimelinesRoutes = (spyState: RouteSpyState): spyState is TimelineRouteSp
|
|||
const isCaseRoutes = (spyState: RouteSpyState): spyState is RouteSpyState =>
|
||||
spyState != null && spyState.pageName === SecurityPageName.case;
|
||||
|
||||
const isAlertsRoutes = (spyState: RouteSpyState) =>
|
||||
spyState != null && spyState.pageName === SecurityPageName.detections;
|
||||
|
||||
const isAdminRoutes = (spyState: RouteSpyState): spyState is AdministrationRouteSpyState =>
|
||||
spyState != null && spyState.pageName === SecurityPageName.administration;
|
||||
|
||||
const isRulesRoutes = (spyState: RouteSpyState): spyState is AdministrationRouteSpyState =>
|
||||
spyState != null && spyState.pageName === SecurityPageName.rules;
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
export const getBreadcrumbsForRoute = (
|
||||
objectParam: RouteSpyState & TabNavigationProps,
|
||||
|
@ -69,7 +82,7 @@ export const getBreadcrumbsForRoute = (
|
|||
// Sets `timeline.isOpen` to false in the state to avoid reopening the timeline on breadcrumb click. https://github.com/elastic/kibana/issues/100322
|
||||
const object = { ...objectParam, timeline: { ...objectParam.timeline, isOpen: false } };
|
||||
|
||||
const overviewPath = getUrlForApp(APP_ID, { path: SecurityPageName.overview });
|
||||
const overviewPath = getUrlForApp(APP_ID, { deepLinkId: SecurityPageName.overview });
|
||||
const siemRootBreadcrumb: ChromeBreadcrumb = {
|
||||
text: APP_NAME,
|
||||
href: getAppOverviewUrl(overviewPath),
|
||||
|
@ -80,6 +93,7 @@ export const getBreadcrumbsForRoute = (
|
|||
if (spyState.tabName != null) {
|
||||
urlStateKeys = [...urlStateKeys, getOr(tempNav, spyState.tabName, object.navTabs)];
|
||||
}
|
||||
|
||||
return [
|
||||
siemRootBreadcrumb,
|
||||
...getHostDetailsBreadcrumbs(
|
||||
|
@ -110,8 +124,8 @@ export const getBreadcrumbsForRoute = (
|
|||
),
|
||||
];
|
||||
}
|
||||
if (isAlertsRoutes(spyState) && object.navTabs) {
|
||||
const tempNav: SearchNavTab = { urlKey: 'detections', isDetailPage: false };
|
||||
if (isRulesRoutes(spyState) && object.navTabs) {
|
||||
const tempNav: SearchNavTab = { urlKey: SecurityPageName.rules, isDetailPage: false };
|
||||
let urlStateKeys = [getOr(tempNav, spyState.pageName, object.navTabs)];
|
||||
if (spyState.tabName != null) {
|
||||
urlStateKeys = [...urlStateKeys, getOr(tempNav, spyState.tabName, object.navTabs)];
|
||||
|
@ -129,6 +143,7 @@ export const getBreadcrumbsForRoute = (
|
|||
),
|
||||
];
|
||||
}
|
||||
|
||||
if (isCaseRoutes(spyState) && object.navTabs) {
|
||||
const tempNav: SearchNavTab = { urlKey: 'case', isDetailPage: false };
|
||||
let urlStateKeys = [getOr(tempNav, spyState.pageName, object.navTabs)];
|
||||
|
|
|
@ -32,7 +32,8 @@ jest.mock('./breadcrumbs', () => ({
|
|||
setBreadcrumbs: jest.fn(),
|
||||
}));
|
||||
const mockGetUrlForApp = jest.fn();
|
||||
jest.mock('../../lib/kibana', () => {
|
||||
const mockNavigateToUrl = jest.fn();
|
||||
jest.mock('../../lib/kibana/kibana_react', () => {
|
||||
return {
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
|
@ -40,6 +41,7 @@ jest.mock('../../lib/kibana', () => {
|
|||
application: {
|
||||
navigateToApp: jest.fn(),
|
||||
getUrlForApp: mockGetUrlForApp,
|
||||
navigateToUrl: mockNavigateToUrl,
|
||||
},
|
||||
},
|
||||
}),
|
||||
|
@ -47,6 +49,13 @@ jest.mock('../../lib/kibana', () => {
|
|||
});
|
||||
jest.mock('../link_to');
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
useLocation: jest.fn(() => ({
|
||||
search: '',
|
||||
})),
|
||||
useHistory: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('SIEM Navigation', () => {
|
||||
const mockProps: TabNavigationComponentProps &
|
||||
SecuritySolutionTabNavigationProps &
|
||||
|
@ -97,57 +106,7 @@ describe('SIEM Navigation', () => {
|
|||
1,
|
||||
{
|
||||
detailName: undefined,
|
||||
navTabs: {
|
||||
detections: {
|
||||
disabled: false,
|
||||
href: '/app/security/detections',
|
||||
id: 'detections',
|
||||
name: 'Detections',
|
||||
urlKey: 'detections',
|
||||
},
|
||||
case: {
|
||||
disabled: false,
|
||||
href: '/app/security/cases',
|
||||
id: 'case',
|
||||
name: 'Cases',
|
||||
urlKey: 'case',
|
||||
},
|
||||
administration: {
|
||||
disabled: false,
|
||||
href: '/app/security/administration',
|
||||
id: 'administration',
|
||||
name: 'Administration',
|
||||
urlKey: 'administration',
|
||||
},
|
||||
hosts: {
|
||||
disabled: false,
|
||||
href: '/app/security/hosts',
|
||||
id: 'hosts',
|
||||
name: 'Hosts',
|
||||
urlKey: 'host',
|
||||
},
|
||||
network: {
|
||||
disabled: false,
|
||||
href: '/app/security/network',
|
||||
id: 'network',
|
||||
name: 'Network',
|
||||
urlKey: 'network',
|
||||
},
|
||||
overview: {
|
||||
disabled: false,
|
||||
href: '/app/security/overview',
|
||||
id: 'overview',
|
||||
name: 'Overview',
|
||||
urlKey: 'overview',
|
||||
},
|
||||
timelines: {
|
||||
disabled: false,
|
||||
href: '/app/security/timelines',
|
||||
id: 'timelines',
|
||||
name: 'Timelines',
|
||||
urlKey: 'timeline',
|
||||
},
|
||||
},
|
||||
navTabs,
|
||||
pageName: 'hosts',
|
||||
pathName: '/',
|
||||
search: '',
|
||||
|
@ -188,7 +147,8 @@ describe('SIEM Navigation', () => {
|
|||
},
|
||||
},
|
||||
undefined,
|
||||
mockGetUrlForApp
|
||||
mockGetUrlForApp,
|
||||
mockNavigateToUrl
|
||||
);
|
||||
});
|
||||
test('it calls setBreadcrumbs with correct path on update', () => {
|
||||
|
@ -204,57 +164,7 @@ describe('SIEM Navigation', () => {
|
|||
detailName: undefined,
|
||||
filters: [],
|
||||
flowTarget: undefined,
|
||||
navTabs: {
|
||||
detections: {
|
||||
disabled: false,
|
||||
href: '/app/security/detections',
|
||||
id: 'detections',
|
||||
name: 'Detections',
|
||||
urlKey: 'detections',
|
||||
},
|
||||
case: {
|
||||
disabled: false,
|
||||
href: '/app/security/cases',
|
||||
id: 'case',
|
||||
name: 'Cases',
|
||||
urlKey: 'case',
|
||||
},
|
||||
hosts: {
|
||||
disabled: false,
|
||||
href: '/app/security/hosts',
|
||||
id: 'hosts',
|
||||
name: 'Hosts',
|
||||
urlKey: 'host',
|
||||
},
|
||||
administration: {
|
||||
disabled: false,
|
||||
href: '/app/security/administration',
|
||||
id: 'administration',
|
||||
name: 'Administration',
|
||||
urlKey: 'administration',
|
||||
},
|
||||
network: {
|
||||
disabled: false,
|
||||
href: '/app/security/network',
|
||||
id: 'network',
|
||||
name: 'Network',
|
||||
urlKey: 'network',
|
||||
},
|
||||
overview: {
|
||||
disabled: false,
|
||||
href: '/app/security/overview',
|
||||
id: 'overview',
|
||||
name: 'Overview',
|
||||
urlKey: 'overview',
|
||||
},
|
||||
timelines: {
|
||||
disabled: false,
|
||||
href: '/app/security/timelines',
|
||||
id: 'timelines',
|
||||
name: 'Timelines',
|
||||
urlKey: 'timeline',
|
||||
},
|
||||
},
|
||||
navTabs,
|
||||
pageName: 'network',
|
||||
pathName: '/',
|
||||
query: { language: 'kuery', query: '' },
|
||||
|
@ -288,7 +198,8 @@ describe('SIEM Navigation', () => {
|
|||
},
|
||||
},
|
||||
undefined,
|
||||
mockGetUrlForApp
|
||||
mockGetUrlForApp,
|
||||
mockNavigateToUrl
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -39,7 +39,7 @@ export const TabNavigationComponent: React.FC<
|
|||
}) => {
|
||||
const {
|
||||
chrome,
|
||||
application: { getUrlForApp },
|
||||
application: { getUrlForApp, navigateToUrl },
|
||||
} = useKibana().services;
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -62,7 +62,8 @@ export const TabNavigationComponent: React.FC<
|
|||
timerange: urlState.timerange,
|
||||
},
|
||||
chrome,
|
||||
getUrlForApp
|
||||
getUrlForApp,
|
||||
navigateToUrl
|
||||
);
|
||||
}
|
||||
}, [
|
||||
|
@ -77,6 +78,7 @@ export const TabNavigationComponent: React.FC<
|
|||
flowTarget,
|
||||
tabName,
|
||||
getUrlForApp,
|
||||
navigateToUrl,
|
||||
]);
|
||||
|
||||
return (
|
||||
|
|
|
@ -9,8 +9,6 @@ import { mount } from 'enzyme';
|
|||
import React from 'react';
|
||||
import { TimelineTabs } from '../../../../../common/types/timeline';
|
||||
|
||||
import { navTabs } from '../../../../app/home/home_navigations';
|
||||
import { SecurityPageName } from '../../../../app/types';
|
||||
import { navTabsHostDetails } from '../../../../hosts/pages/details/nav_tabs';
|
||||
import { HostsTableType } from '../../../../hosts/store/model';
|
||||
import { RouteSpyState } from '../../../utils/route/types';
|
||||
|
@ -18,153 +16,107 @@ import { CONSTANTS } from '../../url_state/constants';
|
|||
import { TabNavigationComponent } from './';
|
||||
import { TabNavigationProps } from './types';
|
||||
|
||||
jest.mock('../../../lib/kibana');
|
||||
jest.mock('../../link_to');
|
||||
jest.mock('../../../lib/kibana/kibana_react', () => {
|
||||
const originalModule = jest.requireActual('../../../../common/lib/kibana/kibana_react');
|
||||
return {
|
||||
...originalModule,
|
||||
useKibana: jest.fn().mockReturnValue({
|
||||
services: {
|
||||
application: {
|
||||
getUrlForApp: (appId: string, options?: { path?: string }) =>
|
||||
`/app/${appId}${options?.path}`,
|
||||
navigateToApp: jest.fn(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
useUiSetting$: jest.fn().mockReturnValue([]),
|
||||
};
|
||||
});
|
||||
|
||||
const SEARCH_QUERY = '?search=test';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const original = jest.requireActual('react-router-dom');
|
||||
|
||||
return {
|
||||
...original,
|
||||
useHistory: () => ({
|
||||
push: jest.fn(),
|
||||
}),
|
||||
useLocation: jest.fn(() => ({
|
||||
search: SEARCH_QUERY,
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
describe('Tab Navigation', () => {
|
||||
const pageName = SecurityPageName.hosts;
|
||||
const hostName = 'siem-window';
|
||||
const tabName = HostsTableType.authentications;
|
||||
const pathName = `/${pageName}/${hostName}/${tabName}`;
|
||||
const hostName = 'siem-window';
|
||||
|
||||
describe('Page Navigation', () => {
|
||||
const mockProps: TabNavigationProps & RouteSpyState = {
|
||||
pageName,
|
||||
pathName,
|
||||
detailName: undefined,
|
||||
search: '',
|
||||
tabName,
|
||||
navTabs,
|
||||
[CONSTANTS.timerange]: {
|
||||
global: {
|
||||
[CONSTANTS.timerange]: {
|
||||
from: '2019-05-16T23:10:43.696Z',
|
||||
fromStr: 'now-24h',
|
||||
kind: 'relative',
|
||||
to: '2019-05-17T23:10:43.697Z',
|
||||
toStr: 'now',
|
||||
},
|
||||
linkTo: ['timeline'],
|
||||
describe('Table Navigation', () => {
|
||||
const mockHasMlUserPermissions = true;
|
||||
const mockProps: TabNavigationProps & RouteSpyState = {
|
||||
pageName: 'hosts',
|
||||
pathName: '/hosts',
|
||||
detailName: undefined,
|
||||
search: '',
|
||||
tabName: HostsTableType.authentications,
|
||||
navTabs: navTabsHostDetails(hostName, mockHasMlUserPermissions),
|
||||
[CONSTANTS.timerange]: {
|
||||
global: {
|
||||
[CONSTANTS.timerange]: {
|
||||
from: '2019-05-16T23:10:43.696Z',
|
||||
fromStr: 'now-24h',
|
||||
kind: 'relative',
|
||||
to: '2019-05-17T23:10:43.697Z',
|
||||
toStr: 'now',
|
||||
},
|
||||
timeline: {
|
||||
[CONSTANTS.timerange]: {
|
||||
from: '2019-05-16T23:10:43.696Z',
|
||||
fromStr: 'now-24h',
|
||||
kind: 'relative',
|
||||
to: '2019-05-17T23:10:43.697Z',
|
||||
toStr: 'now',
|
||||
},
|
||||
linkTo: ['global'],
|
||||
linkTo: ['timeline'],
|
||||
},
|
||||
timeline: {
|
||||
[CONSTANTS.timerange]: {
|
||||
from: '2019-05-16T23:10:43.696Z',
|
||||
fromStr: 'now-24h',
|
||||
kind: 'relative',
|
||||
to: '2019-05-17T23:10:43.697Z',
|
||||
toStr: 'now',
|
||||
},
|
||||
linkTo: ['global'],
|
||||
},
|
||||
[CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' },
|
||||
[CONSTANTS.filters]: [],
|
||||
[CONSTANTS.sourcerer]: {},
|
||||
[CONSTANTS.timeline]: {
|
||||
activeTab: TimelineTabs.query,
|
||||
id: '',
|
||||
isOpen: false,
|
||||
graphEventId: '',
|
||||
},
|
||||
};
|
||||
test('it mounts with correct tab highlighted', () => {
|
||||
const wrapper = mount(<TabNavigationComponent {...mockProps} />);
|
||||
const hostsTab = wrapper.find('EuiTab[data-test-subj="navigation-hosts"]');
|
||||
expect(hostsTab.prop('isSelected')).toBeTruthy();
|
||||
});
|
||||
test('it changes active tab when nav changes by props', () => {
|
||||
const wrapper = mount(<TabNavigationComponent {...mockProps} />);
|
||||
const networkTab = () => wrapper.find('EuiTab[data-test-subj="navigation-network"]').first();
|
||||
expect(networkTab().prop('isSelected')).toBeFalsy();
|
||||
wrapper.setProps({
|
||||
pageName: 'network',
|
||||
pathName: '/network',
|
||||
tabName: undefined,
|
||||
});
|
||||
wrapper.update();
|
||||
expect(networkTab().prop('isSelected')).toBeTruthy();
|
||||
});
|
||||
},
|
||||
[CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' },
|
||||
[CONSTANTS.filters]: [],
|
||||
[CONSTANTS.sourcerer]: {},
|
||||
[CONSTANTS.timeline]: {
|
||||
activeTab: TimelineTabs.query,
|
||||
id: '',
|
||||
isOpen: false,
|
||||
graphEventId: '',
|
||||
},
|
||||
};
|
||||
test('it mounts with correct tab highlighted', () => {
|
||||
const wrapper = mount(<TabNavigationComponent {...mockProps} />);
|
||||
const tableNavigationTab = wrapper.find(
|
||||
`EuiTab[data-test-subj="navigation-${HostsTableType.authentications}"]`
|
||||
);
|
||||
|
||||
expect(tableNavigationTab.prop('isSelected')).toBeTruthy();
|
||||
});
|
||||
test('it changes active tab when nav changes by props', () => {
|
||||
const wrapper = mount(<TabNavigationComponent {...mockProps} />);
|
||||
const tableNavigationTab = () =>
|
||||
wrapper.find(`[data-test-subj="navigation-${HostsTableType.events}"]`).first();
|
||||
expect(tableNavigationTab().prop('isSelected')).toBeFalsy();
|
||||
wrapper.setProps({
|
||||
tabName: HostsTableType.events,
|
||||
});
|
||||
wrapper.update();
|
||||
expect(tableNavigationTab().prop('isSelected')).toBeTruthy();
|
||||
});
|
||||
test('it carries the url state in the link', () => {
|
||||
const wrapper = mount(<TabNavigationComponent {...mockProps} />);
|
||||
|
||||
describe('Table Navigation', () => {
|
||||
const mockHasMlUserPermissions = true;
|
||||
const mockProps: TabNavigationProps & RouteSpyState = {
|
||||
pageName: 'hosts',
|
||||
pathName: '/hosts',
|
||||
detailName: undefined,
|
||||
search: '',
|
||||
tabName: HostsTableType.authentications,
|
||||
navTabs: navTabsHostDetails(hostName, mockHasMlUserPermissions),
|
||||
[CONSTANTS.timerange]: {
|
||||
global: {
|
||||
[CONSTANTS.timerange]: {
|
||||
from: '2019-05-16T23:10:43.696Z',
|
||||
fromStr: 'now-24h',
|
||||
kind: 'relative',
|
||||
to: '2019-05-17T23:10:43.697Z',
|
||||
toStr: 'now',
|
||||
},
|
||||
linkTo: ['timeline'],
|
||||
},
|
||||
timeline: {
|
||||
[CONSTANTS.timerange]: {
|
||||
from: '2019-05-16T23:10:43.696Z',
|
||||
fromStr: 'now-24h',
|
||||
kind: 'relative',
|
||||
to: '2019-05-17T23:10:43.697Z',
|
||||
toStr: 'now',
|
||||
},
|
||||
linkTo: ['global'],
|
||||
},
|
||||
},
|
||||
[CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' },
|
||||
[CONSTANTS.filters]: [],
|
||||
[CONSTANTS.sourcerer]: {},
|
||||
[CONSTANTS.timeline]: {
|
||||
activeTab: TimelineTabs.query,
|
||||
id: '',
|
||||
isOpen: false,
|
||||
graphEventId: '',
|
||||
},
|
||||
};
|
||||
test('it mounts with correct tab highlighted', () => {
|
||||
const wrapper = mount(<TabNavigationComponent {...mockProps} />);
|
||||
const tableNavigationTab = wrapper.find(
|
||||
`EuiTab[data-test-subj="navigation-${HostsTableType.authentications}"]`
|
||||
);
|
||||
|
||||
expect(tableNavigationTab.prop('isSelected')).toBeTruthy();
|
||||
});
|
||||
test('it changes active tab when nav changes by props', () => {
|
||||
const wrapper = mount(<TabNavigationComponent {...mockProps} />);
|
||||
const tableNavigationTab = () =>
|
||||
wrapper.find(`[data-test-subj="navigation-${HostsTableType.events}"]`).first();
|
||||
expect(tableNavigationTab().prop('isSelected')).toBeFalsy();
|
||||
wrapper.setProps({
|
||||
pageName: SecurityPageName.hosts,
|
||||
pathName: `/${SecurityPageName.hosts}`,
|
||||
tabName: HostsTableType.events,
|
||||
});
|
||||
wrapper.update();
|
||||
expect(tableNavigationTab().prop('isSelected')).toBeTruthy();
|
||||
});
|
||||
test('it carries the url state in the link', () => {
|
||||
const wrapper = mount(<TabNavigationComponent {...mockProps} />);
|
||||
const firstTab = wrapper.find(
|
||||
`EuiTab[data-test-subj="navigation-${HostsTableType.authentications}"]`
|
||||
);
|
||||
expect(firstTab.props().href).toBe('/siem-window/authentications');
|
||||
});
|
||||
const firstTab = wrapper.find(
|
||||
`EuiTab[data-test-subj="navigation-${HostsTableType.authentications}"]`
|
||||
);
|
||||
expect(firstTab.props().href).toBe(
|
||||
`/app/securitySolution/hosts/siem-window/authentications${SEARCH_QUERY}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,48 +8,35 @@
|
|||
import { EuiTab, EuiTabs } from '@elastic/eui';
|
||||
import { getOr } from 'lodash/fp';
|
||||
import React, { useEffect, useState, useCallback, useMemo } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import deepEqual from 'fast-deep-equal';
|
||||
|
||||
import { APP_ID } from '../../../../../common/constants';
|
||||
import { useNavigation } from '../../../lib/kibana/hooks';
|
||||
import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../lib/telemetry';
|
||||
import { getSearch } from '../helpers';
|
||||
import { TabNavigationProps, TabNavigationItemProps } from './types';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
import { SecurityPageName } from '../../../../app/types';
|
||||
import { useFormatUrl } from '../../link_to';
|
||||
|
||||
const TabNavigationItemComponent = ({
|
||||
disabled,
|
||||
href,
|
||||
hrefWithSearch,
|
||||
id,
|
||||
name,
|
||||
isSelected,
|
||||
pageId,
|
||||
urlSearch,
|
||||
}: TabNavigationItemProps) => {
|
||||
const history = useHistory();
|
||||
const { navigateToApp, getUrlForApp } = useKibana().services.application;
|
||||
const { formatUrl } = useFormatUrl(((pageId ?? id) as unknown) as SecurityPageName);
|
||||
const { getAppUrl, navigateTo } = useNavigation();
|
||||
|
||||
const handleClick = useCallback(
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
if (id in SecurityPageName && pageId == null) {
|
||||
navigateToApp(`${APP_ID}:${id}`, { path: urlSearch });
|
||||
} else {
|
||||
history.push(hrefWithSearch);
|
||||
}
|
||||
navigateTo({ path: hrefWithSearch });
|
||||
track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${id}`);
|
||||
},
|
||||
[history, hrefWithSearch, id, navigateToApp, pageId, urlSearch]
|
||||
[navigateTo, hrefWithSearch, id]
|
||||
);
|
||||
const appHref =
|
||||
pageId != null
|
||||
? formatUrl(href)
|
||||
: getUrlForApp(`${APP_ID}:${id}`, {
|
||||
path: urlSearch,
|
||||
});
|
||||
|
||||
const appHref = getAppUrl({
|
||||
path: hrefWithSearch,
|
||||
});
|
||||
|
||||
return (
|
||||
<EuiTab
|
||||
data-href={appHref}
|
||||
|
@ -68,28 +55,17 @@ const TabNavigationItem = React.memo(TabNavigationItemComponent);
|
|||
|
||||
export const TabNavigationComponent: React.FC<TabNavigationProps> = ({
|
||||
display,
|
||||
filters,
|
||||
query,
|
||||
navTabs,
|
||||
pageName,
|
||||
savedQuery,
|
||||
sourcerer,
|
||||
tabName,
|
||||
timeline,
|
||||
timerange,
|
||||
}) => {
|
||||
const mapLocationToTab = useCallback(
|
||||
(): string =>
|
||||
getOr(
|
||||
'',
|
||||
'id',
|
||||
Object.values(navTabs).find(
|
||||
(item) =>
|
||||
(tabName === item.id && item.pageId != null) ||
|
||||
(pageName === item.id && item.pageId == null)
|
||||
)
|
||||
Object.values(navTabs).find((item) => tabName === item.id)
|
||||
),
|
||||
[pageName, tabName, navTabs]
|
||||
[tabName, navTabs]
|
||||
);
|
||||
const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab());
|
||||
useEffect(() => {
|
||||
|
@ -100,38 +76,27 @@ export const TabNavigationComponent: React.FC<TabNavigationProps> = ({
|
|||
}
|
||||
|
||||
// we do need navTabs in case the selectedTabId appears after initial load (ex. checking permissions for anomalies)
|
||||
}, [pageName, tabName, navTabs, mapLocationToTab, selectedTabId]);
|
||||
}, [tabName, navTabs, mapLocationToTab, selectedTabId]);
|
||||
|
||||
const { search } = useLocation();
|
||||
|
||||
const renderTabs = useMemo(
|
||||
() =>
|
||||
Object.values(navTabs).map((tab) => {
|
||||
const isSelected = selectedTabId === tab.id;
|
||||
const search = getSearch(tab, {
|
||||
filters,
|
||||
query,
|
||||
savedQuery,
|
||||
sourcerer,
|
||||
timeline,
|
||||
timerange,
|
||||
});
|
||||
const hrefWithSearch =
|
||||
tab.href + getSearch(tab, { filters, query, savedQuery, sourcerer, timeline, timerange });
|
||||
|
||||
return (
|
||||
<TabNavigationItem
|
||||
key={`navigation-${tab.id}`}
|
||||
id={tab.id}
|
||||
href={tab.href}
|
||||
hrefWithSearch={hrefWithSearch}
|
||||
hrefWithSearch={tab.href + search}
|
||||
name={tab.name}
|
||||
disabled={tab.disabled}
|
||||
pageId={tab.pageId}
|
||||
isSelected={isSelected}
|
||||
urlSearch={search}
|
||||
/>
|
||||
);
|
||||
}),
|
||||
[navTabs, selectedTabId, filters, query, savedQuery, sourcerer, timeline, timerange]
|
||||
[navTabs, selectedTabId, search]
|
||||
);
|
||||
|
||||
return <EuiTabs display={display}>{renderTabs}</EuiTabs>;
|
||||
|
@ -143,15 +108,8 @@ export const TabNavigation = React.memo(
|
|||
TabNavigationComponent,
|
||||
(prevProps, nextProps) =>
|
||||
prevProps.display === nextProps.display &&
|
||||
prevProps.pageName === nextProps.pageName &&
|
||||
prevProps.savedQuery === nextProps.savedQuery &&
|
||||
prevProps.tabName === nextProps.tabName &&
|
||||
deepEqual(prevProps.filters, nextProps.filters) &&
|
||||
deepEqual(prevProps.query, nextProps.query) &&
|
||||
deepEqual(prevProps.navTabs, nextProps.navTabs) &&
|
||||
deepEqual(prevProps.sourcerer, nextProps.sourcerer) &&
|
||||
deepEqual(prevProps.timeline, nextProps.timeline) &&
|
||||
deepEqual(prevProps.timerange, nextProps.timerange)
|
||||
deepEqual(prevProps.navTabs, nextProps.navTabs)
|
||||
);
|
||||
|
||||
TabNavigation.displayName = 'TabNavigation';
|
||||
|
|
|
@ -27,12 +27,9 @@ export interface TabNavigationProps extends SecuritySolutionTabNavigationProps {
|
|||
}
|
||||
|
||||
export interface TabNavigationItemProps {
|
||||
href: string;
|
||||
hrefWithSearch: string;
|
||||
id: string;
|
||||
disabled: boolean;
|
||||
name: string;
|
||||
isSelected: boolean;
|
||||
urlSearch: string;
|
||||
pageId?: string;
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { UrlStateType } from '../url_state/constants';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
import { SecurityPageName, SecurityPageGroupName } from '../../../app/types';
|
||||
import { UrlState } from '../url_state/types';
|
||||
import { SiemRouteType } from '../../utils/route/types';
|
||||
|
||||
|
@ -23,13 +23,25 @@ export interface TabNavigationComponentProps {
|
|||
|
||||
export type SearchNavTab = NavTab | { urlKey: UrlStateType; isDetailPage: boolean };
|
||||
|
||||
export interface NavGroupTab {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type SecurityNavTabGroupKey =
|
||||
| SecurityPageGroupName.detect
|
||||
| SecurityPageGroupName.explore
|
||||
| SecurityPageGroupName.investigate
|
||||
| SecurityPageGroupName.manage;
|
||||
|
||||
export type NavTabGroups = Record<SecurityNavTabGroupKey, NavGroupTab>;
|
||||
|
||||
export interface NavTab {
|
||||
id: string;
|
||||
name: string;
|
||||
href: string;
|
||||
disabled: boolean;
|
||||
urlKey: UrlStateType;
|
||||
isDetailPage?: boolean;
|
||||
urlKey?: UrlStateType;
|
||||
pageId?: SecurityPageName;
|
||||
}
|
||||
|
||||
|
@ -37,14 +49,21 @@ export type SiemNavTabKey =
|
|||
| SecurityPageName.overview
|
||||
| SecurityPageName.hosts
|
||||
| SecurityPageName.network
|
||||
| SecurityPageName.detections
|
||||
| SecurityPageName.alerts
|
||||
| SecurityPageName.rules
|
||||
| SecurityPageName.exceptions
|
||||
| SecurityPageName.timelines
|
||||
| SecurityPageName.case
|
||||
| SecurityPageName.administration;
|
||||
| SecurityPageName.administration
|
||||
| SecurityPageName.endpoints
|
||||
| SecurityPageName.trustedApps
|
||||
| SecurityPageName.eventFilters;
|
||||
|
||||
export type SiemNavTab = Record<SiemNavTabKey, NavTab>;
|
||||
|
||||
export type GetUrlForApp = (
|
||||
appId: string,
|
||||
options?: { path?: string; absolute?: boolean }
|
||||
options?: { deepLinkId?: string; path?: string; absolute?: boolean }
|
||||
) => string;
|
||||
|
||||
export type NavigateToUrl = (url: string) => void;
|
||||
|
|
|
@ -74,8 +74,8 @@ describe('useSecuritySolutionNavigation', () => {
|
|||
services: {
|
||||
application: {
|
||||
navigateToApp: jest.fn(),
|
||||
getUrlForApp: (appId: string, options?: { path?: string; absolute?: boolean }) =>
|
||||
`${appId}${options?.path ?? ''}`,
|
||||
getUrlForApp: (appId: string, options?: { path?: string; deepLinkId?: boolean }) =>
|
||||
`${appId}/${options?.deepLinkId ?? ''}${options?.path ?? ''}`,
|
||||
},
|
||||
chrome: {
|
||||
setBreadcrumbs: jest.fn(),
|
||||
|
@ -97,67 +97,131 @@ describe('useSecuritySolutionNavigation', () => {
|
|||
"id": "securitySolution",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolution:overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-href": "securitySolution/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-test-subj": "navigation-overview",
|
||||
"disabled": false,
|
||||
"href": "securitySolution:overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"href": "securitySolution/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"id": "overview",
|
||||
"isSelected": false,
|
||||
"name": "Overview",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
},
|
||||
Object {
|
||||
"id": "detect",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolution:detections?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-test-subj": "navigation-detections",
|
||||
"data-href": "securitySolution/alerts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-test-subj": "navigation-alerts",
|
||||
"disabled": false,
|
||||
"href": "securitySolution:detections?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"id": "detections",
|
||||
"href": "securitySolution/alerts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"id": "alerts",
|
||||
"isSelected": false,
|
||||
"name": "Detections",
|
||||
"name": "Alerts",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolution:hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-href": "securitySolution/rules?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-test-subj": "navigation-rules",
|
||||
"disabled": false,
|
||||
"href": "securitySolution/rules?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"id": "rules",
|
||||
"isSelected": false,
|
||||
"name": "Rules",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolution/exceptions?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-test-subj": "navigation-exceptions",
|
||||
"disabled": false,
|
||||
"href": "securitySolution/exceptions?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"id": "exceptions",
|
||||
"isSelected": false,
|
||||
"name": "Exceptions",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Detect",
|
||||
},
|
||||
Object {
|
||||
"id": "explore",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolution/hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-test-subj": "navigation-hosts",
|
||||
"disabled": false,
|
||||
"href": "securitySolution:hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"href": "securitySolution/hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"id": "hosts",
|
||||
"isSelected": true,
|
||||
"name": "Hosts",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolution:network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-href": "securitySolution/network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-test-subj": "navigation-network",
|
||||
"disabled": false,
|
||||
"href": "securitySolution:network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"href": "securitySolution/network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"id": "network",
|
||||
"isSelected": false,
|
||||
"name": "Network",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Explore",
|
||||
},
|
||||
Object {
|
||||
"id": "investigate",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolution:timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-href": "securitySolution/timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-test-subj": "navigation-timelines",
|
||||
"disabled": false,
|
||||
"href": "securitySolution:timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"href": "securitySolution/timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"id": "timelines",
|
||||
"isSelected": false,
|
||||
"name": "Timelines",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "Investigate",
|
||||
},
|
||||
Object {
|
||||
"id": "manage",
|
||||
"items": Array [
|
||||
Object {
|
||||
"data-href": "securitySolution:administration",
|
||||
"data-test-subj": "navigation-administration",
|
||||
"data-href": "securitySolution/endpoints",
|
||||
"data-test-subj": "navigation-endpoints",
|
||||
"disabled": false,
|
||||
"href": "securitySolution:administration",
|
||||
"id": "administration",
|
||||
"href": "securitySolution/endpoints",
|
||||
"id": "endpoints",
|
||||
"isSelected": false,
|
||||
"name": "Administration",
|
||||
"name": "Endpoints",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolution/trusted_apps",
|
||||
"data-test-subj": "navigation-trusted_apps",
|
||||
"disabled": false,
|
||||
"href": "securitySolution/trusted_apps",
|
||||
"id": "trusted_apps",
|
||||
"isSelected": false,
|
||||
"name": "Trusted Applications",
|
||||
"onClick": [Function],
|
||||
},
|
||||
Object {
|
||||
"data-href": "securitySolution/event_filters",
|
||||
"data-test-subj": "navigation-event_filters",
|
||||
"disabled": false,
|
||||
"href": "securitySolution/event_filters",
|
||||
"id": "event_filters",
|
||||
"isSelected": false,
|
||||
"name": "Event Filters",
|
||||
"onClick": [Function],
|
||||
},
|
||||
],
|
||||
"name": "",
|
||||
"name": "Manage",
|
||||
},
|
||||
],
|
||||
"name": "Security",
|
||||
|
@ -177,15 +241,15 @@ describe('useSecuritySolutionNavigation', () => {
|
|||
useSecuritySolutionNavigation()
|
||||
);
|
||||
|
||||
const caseNavItem = (result.current?.items || [])[0].items?.find(
|
||||
const caseNavItem = (result.current?.items || [])[3].items?.find(
|
||||
(item) => item['data-test-subj'] === 'navigation-case'
|
||||
);
|
||||
expect(caseNavItem).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"data-href": "securitySolution:case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-href": "securitySolution/case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"data-test-subj": "navigation-case",
|
||||
"disabled": false,
|
||||
"href": "securitySolution:case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"href": "securitySolution/case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))",
|
||||
"id": "case",
|
||||
"isSelected": false,
|
||||
"name": "Cases",
|
||||
|
@ -204,7 +268,7 @@ describe('useSecuritySolutionNavigation', () => {
|
|||
useSecuritySolutionNavigation()
|
||||
);
|
||||
|
||||
const caseNavItem = (result.current?.items || [])[0].items?.find(
|
||||
const caseNavItem = (result.current?.items || [])[3].items?.find(
|
||||
(item) => item['data-test-subj'] === 'navigation-case'
|
||||
);
|
||||
expect(caseNavItem).toBeFalsy();
|
||||
|
|
|
@ -6,15 +6,13 @@
|
|||
*/
|
||||
|
||||
import { useEffect } from 'react';
|
||||
import { pickBy } from 'lodash/fp';
|
||||
import { usePrimaryNavigation } from './use_primary_navigation';
|
||||
import { useGetUserCasesPermissions, useKibana } from '../../../lib/kibana';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
import { setBreadcrumbs } from '../breadcrumbs';
|
||||
import { makeMapStateToProps } from '../../url_state/helpers';
|
||||
import { useRouteSpy } from '../../../utils/route/use_route_spy';
|
||||
import { navTabs } from '../../../../app/home/home_navigations';
|
||||
import { useDeepEqualSelector } from '../../../hooks/use_selector';
|
||||
import { SecurityPageName } from '../../../../../common/constants';
|
||||
|
||||
/**
|
||||
* @description - This hook provides the structure necessary by the KibanaPageTemplate for rendering the primary security_solution side navigation.
|
||||
|
@ -26,7 +24,7 @@ export const useSecuritySolutionNavigation = () => {
|
|||
const { urlState } = useDeepEqualSelector(urlMapState);
|
||||
const {
|
||||
chrome,
|
||||
application: { getUrlForApp },
|
||||
application: { getUrlForApp, navigateToUrl },
|
||||
} = useKibana().services;
|
||||
|
||||
const { detailName, flowTarget, pageName, pathName, search, state, tabName } = routeProps;
|
||||
|
@ -51,7 +49,8 @@ export const useSecuritySolutionNavigation = () => {
|
|||
timerange: urlState.timerange,
|
||||
},
|
||||
chrome,
|
||||
getUrlForApp
|
||||
getUrlForApp,
|
||||
navigateToUrl
|
||||
);
|
||||
}
|
||||
}, [
|
||||
|
@ -65,25 +64,17 @@ export const useSecuritySolutionNavigation = () => {
|
|||
flowTarget,
|
||||
tabName,
|
||||
getUrlForApp,
|
||||
navigateToUrl,
|
||||
]);
|
||||
|
||||
const hasCasesReadPermissions = useGetUserCasesPermissions()?.read;
|
||||
|
||||
// build a list of tabs to exclude
|
||||
const tabsToExclude = new Set<string>([
|
||||
...(!hasCasesReadPermissions ? [SecurityPageName.case] : []),
|
||||
]);
|
||||
|
||||
// include the tab if it is not in the set of excluded ones
|
||||
const tabsToDisplay = pickBy((_, key) => !tabsToExclude.has(key), navTabs);
|
||||
|
||||
return usePrimaryNavigation({
|
||||
query: urlState.query,
|
||||
filters: urlState.filters,
|
||||
navTabs: tabsToDisplay,
|
||||
navTabs,
|
||||
pageName,
|
||||
sourcerer: urlState.sourcerer,
|
||||
savedQuery: urlState.savedQuery,
|
||||
tabName,
|
||||
timeline: urlState.timeline,
|
||||
timerange: urlState.timerange,
|
||||
});
|
||||
|
|
|
@ -12,4 +12,4 @@ export type PrimaryNavigationItemsProps = Omit<
|
|||
'pathName' | 'pageName' | 'tabName'
|
||||
> & { selectedTabId: string };
|
||||
|
||||
export type PrimaryNavigationProps = Omit<TabNavigationProps, 'pathName' | 'tabName'>;
|
||||
export type PrimaryNavigationProps = Omit<TabNavigationProps, 'pathName'>;
|
||||
|
|
|
@ -5,62 +5,85 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { EuiSideNavItemType } from '@elastic/eui/src/components/side_nav/side_nav_types';
|
||||
import { navTabGroups } from '../../../../app/home/home_navigations';
|
||||
import { APP_ID } from '../../../../../common/constants';
|
||||
import { track, METRIC_TYPE, TELEMETRY_EVENT } from '../../../lib/telemetry';
|
||||
import { getSearch } from '../helpers';
|
||||
import { PrimaryNavigationItemsProps } from './types';
|
||||
import { useKibana } from '../../../lib/kibana';
|
||||
import { useGetUserCasesPermissions, useKibana } from '../../../lib/kibana';
|
||||
import { NavTab } from '../types';
|
||||
|
||||
export const usePrimaryNavigationItems = ({
|
||||
filters,
|
||||
navTabs,
|
||||
query,
|
||||
savedQuery,
|
||||
selectedTabId,
|
||||
sourcerer,
|
||||
timeline,
|
||||
timerange,
|
||||
}: PrimaryNavigationItemsProps) => {
|
||||
...urlStateProps
|
||||
}: PrimaryNavigationItemsProps): Array<EuiSideNavItemType<{}>> => {
|
||||
const { navigateToApp, getUrlForApp } = useKibana().services.application;
|
||||
|
||||
const navItems = Object.values(navTabs).map((tab) => {
|
||||
const { id, name, disabled } = tab;
|
||||
const isSelected = selectedTabId === id;
|
||||
const urlSearch = getSearch(tab, {
|
||||
filters,
|
||||
query,
|
||||
savedQuery,
|
||||
sourcerer,
|
||||
timeline,
|
||||
timerange,
|
||||
});
|
||||
const getSideNav = useCallback(
|
||||
(tab: NavTab) => {
|
||||
const { id, name, disabled } = tab;
|
||||
const isSelected = selectedTabId === id;
|
||||
const urlSearch = getSearch(tab, urlStateProps);
|
||||
|
||||
const handleClick = (ev: React.MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
navigateToApp(`${APP_ID}:${id}`, { path: urlSearch });
|
||||
track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${id}`);
|
||||
};
|
||||
const handleClick = (ev: React.MouseEvent) => {
|
||||
ev.preventDefault();
|
||||
navigateToApp(APP_ID, { deepLinkId: id, path: urlSearch });
|
||||
};
|
||||
|
||||
const appHref = getUrlForApp(`${APP_ID}:${id}`, { path: urlSearch });
|
||||
const appHref = getUrlForApp(APP_ID, { deepLinkId: id, path: urlSearch });
|
||||
|
||||
return {
|
||||
'data-href': appHref,
|
||||
'data-test-subj': `navigation-${id}`,
|
||||
disabled,
|
||||
href: appHref,
|
||||
id,
|
||||
isSelected,
|
||||
name,
|
||||
onClick: handleClick,
|
||||
};
|
||||
});
|
||||
return {
|
||||
'data-href': appHref,
|
||||
'data-test-subj': `navigation-${id}`,
|
||||
disabled,
|
||||
href: appHref,
|
||||
id,
|
||||
isSelected,
|
||||
name,
|
||||
onClick: handleClick,
|
||||
};
|
||||
},
|
||||
[getUrlForApp, navigateToApp, selectedTabId, urlStateProps]
|
||||
);
|
||||
|
||||
const navItemsToDisplay = usePrimaryNavigationItemsToDisplay(navTabs);
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
navItemsToDisplay.map((item) => ({
|
||||
...item,
|
||||
items: item.items.map((t: NavTab) => getSideNav(t)),
|
||||
})),
|
||||
[getSideNav, navItemsToDisplay]
|
||||
);
|
||||
};
|
||||
|
||||
function usePrimaryNavigationItemsToDisplay(navTabs: Record<string, NavTab>) {
|
||||
const hasCasesReadPermissions = useGetUserCasesPermissions()?.read;
|
||||
|
||||
return [
|
||||
{
|
||||
id: APP_ID, // TODO: When separating into sub-sections (detect, explore, investigate). Those names can also serve as the section id
|
||||
items: navItems,
|
||||
id: APP_ID,
|
||||
name: '',
|
||||
items: [navTabs.overview],
|
||||
},
|
||||
{
|
||||
...navTabGroups.detect,
|
||||
items: [navTabs.alerts, navTabs.rules, navTabs.exceptions],
|
||||
},
|
||||
{
|
||||
...navTabGroups.explore,
|
||||
items: [navTabs.hosts, navTabs.network],
|
||||
},
|
||||
{
|
||||
...navTabGroups.investigate,
|
||||
items: hasCasesReadPermissions ? [navTabs.timelines, navTabs.case] : [navTabs.timelines],
|
||||
},
|
||||
{
|
||||
...navTabGroups.manage,
|
||||
items: [navTabs.endpoints, navTabs.trusted_apps, navTabs.event_filters],
|
||||
},
|
||||
];
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { getOr } from 'lodash/fp';
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
|
@ -24,17 +23,13 @@ export const usePrimaryNavigation = ({
|
|||
pageName,
|
||||
savedQuery,
|
||||
sourcerer,
|
||||
tabName,
|
||||
timeline,
|
||||
timerange,
|
||||
}: PrimaryNavigationProps): KibanaPageTemplateProps['solutionNav'] => {
|
||||
const mapLocationToTab = useCallback(
|
||||
(): string =>
|
||||
getOr(
|
||||
'',
|
||||
'id',
|
||||
Object.values(navTabs).find((item) => pageName === item.id && item.pageId == null)
|
||||
),
|
||||
[pageName, navTabs]
|
||||
(): string => ((tabName && navTabs[tabName]) || navTabs[pageName])?.id ?? '',
|
||||
[pageName, tabName, navTabs]
|
||||
);
|
||||
|
||||
const [selectedTabId, setSelectedTabId] = useState(mapLocationToTab());
|
||||
|
@ -50,11 +45,11 @@ export const usePrimaryNavigation = ({
|
|||
}, [pageName, navTabs, mapLocationToTab, selectedTabId]);
|
||||
|
||||
const navItems = usePrimaryNavigationItems({
|
||||
filters,
|
||||
navTabs,
|
||||
selectedTabId,
|
||||
filters,
|
||||
query,
|
||||
savedQuery,
|
||||
selectedTabId,
|
||||
sourcerer,
|
||||
timeline,
|
||||
timerange,
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
|
||||
export enum CONSTANTS {
|
||||
appQuery = 'query',
|
||||
alertsPage = 'alerts.page',
|
||||
caseDetails = 'case.details',
|
||||
casePage = 'case.page',
|
||||
detectionsPage = 'detections.page',
|
||||
filters = 'filters',
|
||||
hostsDetails = 'hosts.details',
|
||||
hostsPage = 'hosts.page',
|
||||
|
@ -27,7 +27,9 @@ export enum CONSTANTS {
|
|||
|
||||
export type UrlStateType =
|
||||
| 'case'
|
||||
| 'detections'
|
||||
| 'alerts'
|
||||
| 'rules'
|
||||
| 'exceptions'
|
||||
| 'host'
|
||||
| 'network'
|
||||
| 'overview'
|
||||
|
|
|
@ -92,8 +92,12 @@ export const getUrlType = (pageName: string): UrlStateType => {
|
|||
return 'host';
|
||||
} else if (pageName === SecurityPageName.network) {
|
||||
return 'network';
|
||||
} else if (pageName === SecurityPageName.detections) {
|
||||
return 'detections';
|
||||
} else if (pageName === SecurityPageName.alerts) {
|
||||
return 'alerts';
|
||||
} else if (pageName === SecurityPageName.rules) {
|
||||
return 'rules';
|
||||
} else if (pageName === SecurityPageName.exceptions) {
|
||||
return 'exceptions';
|
||||
} else if (pageName === SecurityPageName.timelines) {
|
||||
return 'timeline';
|
||||
} else if (pageName === SecurityPageName.case) {
|
||||
|
|
|
@ -170,7 +170,7 @@ describe('UrlStateContainer', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('After Initialization, keep Relative Date up to date for global only on detections page', () => {
|
||||
describe('After Initialization, keep Relative Date up to date for global only on alerts page', () => {
|
||||
test.each(testCases)(
|
||||
'%o',
|
||||
async (page, namespaceLower, namespaceUpper, examplePath, type, pageName, detailName) => {
|
||||
|
@ -196,7 +196,7 @@ describe('UrlStateContainer', () => {
|
|||
});
|
||||
wrapper.update();
|
||||
|
||||
if (CONSTANTS.detectionsPage === page) {
|
||||
if (CONSTANTS.alertsPage === page) {
|
||||
await waitFor(() => {
|
||||
expect(mockSetRelativeRangeDatePicker.mock.calls[3][0]).toEqual({
|
||||
from: '2020-01-01T00:00:00.000Z',
|
||||
|
|
|
@ -45,7 +45,7 @@ export const dispatchSetInitialStateFromUrl = (
|
|||
const sourcererState = decodeRisonUrlState<SourcererScopePatterns>(newUrlStateString);
|
||||
if (sourcererState != null) {
|
||||
const activeScopes: SourcererScopeName[] = Object.keys(sourcererState).filter(
|
||||
(key) => !(key === SourcererScopeName.default && pageName === SecurityPageName.detections)
|
||||
(key) => !(key === SourcererScopeName.default && pageName === SecurityPageName.alerts)
|
||||
) as SourcererScopeName[];
|
||||
activeScopes.forEach((scope) =>
|
||||
dispatch(
|
||||
|
|
|
@ -34,7 +34,23 @@ export const ALL_URL_STATE_KEYS: KeyUrlState[] = [
|
|||
];
|
||||
|
||||
export const URL_STATE_KEYS: Record<UrlStateType, KeyUrlState[]> = {
|
||||
detections: [
|
||||
alerts: [
|
||||
CONSTANTS.appQuery,
|
||||
CONSTANTS.filters,
|
||||
CONSTANTS.savedQuery,
|
||||
CONSTANTS.sourcerer,
|
||||
CONSTANTS.timerange,
|
||||
CONSTANTS.timeline,
|
||||
],
|
||||
rules: [
|
||||
CONSTANTS.appQuery,
|
||||
CONSTANTS.filters,
|
||||
CONSTANTS.savedQuery,
|
||||
CONSTANTS.sourcerer,
|
||||
CONSTANTS.timerange,
|
||||
CONSTANTS.timeline,
|
||||
],
|
||||
exceptions: [
|
||||
CONSTANTS.appQuery,
|
||||
CONSTANTS.filters,
|
||||
CONSTANTS.savedQuery,
|
||||
|
@ -88,7 +104,7 @@ export const URL_STATE_KEYS: Record<UrlStateType, KeyUrlState[]> = {
|
|||
export type LocationTypes =
|
||||
| CONSTANTS.caseDetails
|
||||
| CONSTANTS.casePage
|
||||
| CONSTANTS.detectionsPage
|
||||
| CONSTANTS.alertsPage
|
||||
| CONSTANTS.hostsDetails
|
||||
| CONSTANTS.hostsPage
|
||||
| CONSTANTS.networkDetails
|
||||
|
|
|
@ -221,7 +221,7 @@ export const useUrlStateHooks = ({
|
|||
}
|
||||
});
|
||||
} else if (pathName !== prevProps.pathName) {
|
||||
handleInitialize(type, pageName === SecurityPageName.detections);
|
||||
handleInitialize(type, pageName === SecurityPageName.alerts);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isInitializing, history, pathName, pageName, prevProps, urlState]);
|
||||
|
|
|
@ -158,8 +158,10 @@ export const useFetchIndex = (
|
|||
next: (response) => {
|
||||
if (isCompleteResponse(response)) {
|
||||
const stringifyIndices = response.indicesExist.sort().join();
|
||||
|
||||
previousIndexesName.current = response.indicesExist;
|
||||
setLoading(false);
|
||||
|
||||
setState({
|
||||
browserFields: getBrowserFields(stringifyIndices, response.indexFields),
|
||||
docValueFields: getDocValueFields(stringifyIndices, response.indexFields),
|
||||
|
@ -167,6 +169,7 @@ export const useFetchIndex = (
|
|||
indexExists: response.indicesExist.length > 0,
|
||||
indexPatterns: getIndexFields(stringifyIndices, response.indexFields),
|
||||
});
|
||||
|
||||
searchSubscription$.current.unsubscribe();
|
||||
} else if (isErrorResponse(response)) {
|
||||
setLoading(false);
|
||||
|
@ -187,7 +190,7 @@ export const useFetchIndex = (
|
|||
abortCtrl.current.abort();
|
||||
asyncSearch();
|
||||
},
|
||||
[data.search, addError, addWarning, onlyCheckIfIndicesExist]
|
||||
[data.search, addError, addWarning, onlyCheckIfIndicesExist, setLoading, setState]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -37,7 +37,7 @@ export const useNavigateToAppEventHandler = <S = unknown>(
|
|||
options?: NavigateToAppHandlerOptions<S>
|
||||
): EventHandlerCallback => {
|
||||
const { services } = useKibana();
|
||||
const { path, state, onClick } = options || {};
|
||||
const { path, state, onClick, deepLinkId } = options || {};
|
||||
return useCallback(
|
||||
(ev) => {
|
||||
try {
|
||||
|
@ -70,8 +70,8 @@ export const useNavigateToAppEventHandler = <S = unknown>(
|
|||
}
|
||||
|
||||
ev.preventDefault();
|
||||
services.application.navigateToApp(appId, { path, state });
|
||||
services.application.navigateToApp(appId, { deepLinkId, path, state });
|
||||
},
|
||||
[appId, onClick, path, services.application, state]
|
||||
[appId, deepLinkId, onClick, path, services.application, state]
|
||||
);
|
||||
};
|
||||
|
|
|
@ -17,6 +17,8 @@ import {
|
|||
createStartServicesMock,
|
||||
createWithKibanaMock,
|
||||
} from '../kibana_react.mock';
|
||||
import { APP_ID } from '../../../../../common/constants';
|
||||
|
||||
const mockStartServicesMock = createStartServicesMock();
|
||||
export const KibanaServices = { get: jest.fn(), getKibanaVersion: jest.fn(() => '8.0.0') };
|
||||
export const useKibana = jest.fn().mockReturnValue({
|
||||
|
@ -60,3 +62,10 @@ export const useCurrentUser = jest.fn();
|
|||
export const withKibana = jest.fn(createWithKibanaMock());
|
||||
export const KibanaContextProvider = jest.fn(createKibanaContextProviderMock());
|
||||
export const useGetUserCasesPermissions = jest.fn();
|
||||
export const useAppUrl = jest.fn().mockReturnValue({
|
||||
getAppUrl: jest
|
||||
.fn()
|
||||
.mockImplementation(({ appId = APP_ID, ...options }) =>
|
||||
mockStartServicesMock.application.getUrlForApp(appId, options)
|
||||
),
|
||||
});
|
||||
|
|
|
@ -12,7 +12,7 @@ import { i18n } from '@kbn/i18n';
|
|||
|
||||
import { camelCase, isArray, isObject } from 'lodash';
|
||||
import { set } from '@elastic/safer-lodash-set';
|
||||
import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../../common/constants';
|
||||
import { APP_ID, DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../../../common/constants';
|
||||
import { errorToToaster, useStateToaster } from '../../components/toasters';
|
||||
import { AuthenticatedUser } from '../../../../../security/common/model';
|
||||
import { StartServices } from '../../../types';
|
||||
|
@ -160,3 +160,57 @@ export const useGetUserCasesPermissions = () => {
|
|||
|
||||
return casesPermissions;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a full URL to the provided page path by using
|
||||
* kibana's `getUrlForApp()`
|
||||
*/
|
||||
export const useAppUrl = () => {
|
||||
const { getUrlForApp } = useKibana().services.application;
|
||||
|
||||
const getAppUrl = useCallback(
|
||||
({ appId = APP_ID, ...options }: { appId?: string; deepLinkId?: string; path?: string }) =>
|
||||
getUrlForApp(appId, options),
|
||||
[getUrlForApp]
|
||||
);
|
||||
return { getAppUrl };
|
||||
};
|
||||
|
||||
/**
|
||||
* Navigate to any app using kibana's `navigateToApp()`
|
||||
* or by url using `navigateToUrl()`
|
||||
*/
|
||||
export const useNavigateTo = () => {
|
||||
const { navigateToApp, navigateToUrl } = useKibana().services.application;
|
||||
|
||||
const navigateTo = useCallback(
|
||||
({
|
||||
url,
|
||||
appId = APP_ID,
|
||||
...options
|
||||
}: {
|
||||
url?: string;
|
||||
appId?: string;
|
||||
deepLinkId?: string;
|
||||
path?: string;
|
||||
}) => {
|
||||
if (url) {
|
||||
navigateToUrl(url);
|
||||
} else {
|
||||
navigateToApp(appId, options);
|
||||
}
|
||||
},
|
||||
[navigateToApp, navigateToUrl]
|
||||
);
|
||||
return { navigateTo };
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns navigateTo and getAppUrl navigation hooks
|
||||
*
|
||||
*/
|
||||
export const useNavigation = () => {
|
||||
const { navigateTo } = useNavigateTo();
|
||||
const { getAppUrl } = useAppUrl();
|
||||
return { navigateTo, getAppUrl };
|
||||
};
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { createMemoryHistory } from 'history';
|
||||
import { createMemoryHistory, MemoryHistory } from 'history';
|
||||
import { render as reactRender, RenderOptions, RenderResult } from '@testing-library/react';
|
||||
import { Action, Reducer, Store } from 'redux';
|
||||
import { AppDeepLink } from 'kibana/public';
|
||||
import { coreMock } from '../../../../../../../src/core/public/mocks';
|
||||
import { StartPlugins, StartServices } from '../../../types';
|
||||
import { depsStartMock } from './dependencies_start_mock';
|
||||
|
@ -21,10 +22,10 @@ import { createStartServicesMock } from '../../lib/kibana/kibana_react.mock';
|
|||
import { SUB_PLUGINS_REDUCER, mockGlobalState, createSecuritySolutionStorageMock } from '..';
|
||||
import { ExperimentalFeatures } from '../../../../common/experimental_features';
|
||||
import { PLUGIN_ID } from '../../../../../fleet/common';
|
||||
import { APP_ID } from '../../../../common/constants';
|
||||
import { APP_ID, APP_PATH } from '../../../../common/constants';
|
||||
import { KibanaContextProvider, KibanaServices } from '../../lib/kibana';
|
||||
import { MANAGEMENT_APP_ID } from '../../../management/common/constants';
|
||||
import { fleetGetPackageListHttpMock } from '../../../management/pages/endpoint_hosts/mocks';
|
||||
import { getDeepLinks } from '../../../app/deep_links';
|
||||
|
||||
type UiRender = (ui: React.ReactElement, options?: RenderOptions) => RenderResult;
|
||||
|
||||
|
@ -93,7 +94,7 @@ const experimentalFeaturesReducer: Reducer<State['app'], UpdateExperimentalFeatu
|
|||
*/
|
||||
export const createAppRootMockRenderer = (): AppContextTestRender => {
|
||||
const history = createMemoryHistory<never>();
|
||||
const coreStart = createCoreStartMock();
|
||||
const coreStart = createCoreStartMock(history);
|
||||
const depsStart = depsStartMock();
|
||||
const middlewareSpy = createSpyMiddleware();
|
||||
const { storage } = createSecuritySolutionStorageMock();
|
||||
|
@ -166,26 +167,56 @@ export const createAppRootMockRenderer = (): AppContextTestRender => {
|
|||
};
|
||||
};
|
||||
|
||||
const createCoreStartMock = (): ReturnType<typeof coreMock.createStart> => {
|
||||
const createCoreStartMock = (
|
||||
history: MemoryHistory<never>
|
||||
): ReturnType<typeof coreMock.createStart> => {
|
||||
const coreStart = coreMock.createStart({ basePath: '/mock' });
|
||||
|
||||
const deepLinkPaths = getDeepLinkPaths(getDeepLinks());
|
||||
|
||||
// Mock the certain APP Ids returned by `application.getUrlForApp()`
|
||||
coreStart.application.getUrlForApp.mockImplementation((appId) => {
|
||||
coreStart.application.getUrlForApp.mockImplementation((appId, { deepLinkId, path } = {}) => {
|
||||
switch (appId) {
|
||||
case PLUGIN_ID:
|
||||
return '/app/fleet';
|
||||
case APP_ID:
|
||||
return '/app/security';
|
||||
case MANAGEMENT_APP_ID:
|
||||
return '/app/security/administration';
|
||||
return `${APP_PATH}${
|
||||
deepLinkId && deepLinkPaths[deepLinkId] ? deepLinkPaths[deepLinkId] : ''
|
||||
}${path ?? ''}`;
|
||||
default:
|
||||
return `${appId} not mocked!`;
|
||||
}
|
||||
});
|
||||
|
||||
coreStart.application.navigateToApp.mockImplementation((appId, { deepLinkId, path } = {}) => {
|
||||
if (appId === APP_ID) {
|
||||
history.push(
|
||||
`${deepLinkId && deepLinkPaths[deepLinkId] ? deepLinkPaths[deepLinkId] : ''}${path ?? ''}`
|
||||
);
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
coreStart.application.navigateToUrl.mockImplementation((url) => {
|
||||
history.push(url.replace(APP_PATH, ''));
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
||||
return coreStart;
|
||||
};
|
||||
|
||||
const getDeepLinkPaths = (deepLinks: AppDeepLink[]): Record<string, string> => {
|
||||
return deepLinks.reduce((result: Record<string, string>, deepLink) => {
|
||||
if (deepLink.path) {
|
||||
result[deepLink.id] = deepLink.path;
|
||||
}
|
||||
if (deepLink.deepLinks) {
|
||||
return { ...result, ...getDeepLinkPaths(deepLink.deepLinks) };
|
||||
}
|
||||
return result;
|
||||
}, {});
|
||||
};
|
||||
|
||||
const applyDefaultCoreHttpMocks = (http: AppContextTestRender['coreStart']['http']) => {
|
||||
// Need to mock getting the endpoint package from the fleet API because it is used as soon
|
||||
// as the store middleware for Endpoint list is initialized, thus mocking it here would avoid
|
||||
|
|
|
@ -12,6 +12,8 @@ import { shallow, mount } from 'enzyme';
|
|||
import '../../../common/mock/match_media';
|
||||
import { esQuery } from '../../../../../../../src/plugins/data/public';
|
||||
import { TestProviders } from '../../../common/mock';
|
||||
import { SecurityPageName } from '../../../app/types';
|
||||
|
||||
import { AlertsHistogramPanel, buildCombinedQueries, parseCombinedQueries } from './index';
|
||||
import * as helpers from './helpers';
|
||||
|
||||
|
@ -83,7 +85,10 @@ describe('AlertsHistogramPanel', () => {
|
|||
preventDefault: jest.fn(),
|
||||
});
|
||||
|
||||
expect(mockNavigateToApp).toBeCalledWith('securitySolution:detections', { path: '' });
|
||||
expect(mockNavigateToApp).toBeCalledWith('securitySolution', {
|
||||
deepLinkId: SecurityPageName.alerts,
|
||||
path: '',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -152,7 +152,7 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>(
|
|||
});
|
||||
const kibana = useKibana();
|
||||
const { navigateToApp } = kibana.services.application;
|
||||
const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.detections);
|
||||
const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.alerts);
|
||||
|
||||
const totalAlerts = useMemo(
|
||||
() =>
|
||||
|
@ -175,7 +175,8 @@ export const AlertsHistogramPanel = memo<AlertsHistogramPanelProps>(
|
|||
const goToDetectionEngine = useCallback(
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
navigateToApp(`${APP_ID}:${SecurityPageName.detections}`, {
|
||||
navigateToApp(APP_ID, {
|
||||
deepLinkId: SecurityPageName.alerts,
|
||||
path: getDetectionEngineUrl(urlSearch),
|
||||
});
|
||||
},
|
||||
|
|
|
@ -27,6 +27,7 @@ jest.mock('react-router-dom', () => {
|
|||
});
|
||||
|
||||
jest.mock('../../../../common/components/link_to');
|
||||
jest.mock('../../../../common/lib/kibana');
|
||||
|
||||
jest.mock('../../../containers/detection_engine/rules/api', () => ({
|
||||
getPrePackagedRulesStatus: jest.fn().mockResolvedValue({
|
||||
|
@ -52,11 +53,14 @@ describe('PrePackagedRulesPrompt', () => {
|
|||
let appToastsMock: jest.Mocked<ReturnType<typeof useAppToastsMock.create>>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
appToastsMock = useAppToastsMock.create();
|
||||
(useAppToasts as jest.Mock).mockReturnValue(appToastsMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
const wrapper = shallow(<PrePackagedRulesPrompt {...props} />);
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
|||
import React, { memo, useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { getCreateRuleUrl } from '../../../../common/components/link_to/redirect_to_detection_engine';
|
||||
import * as i18n from './translations';
|
||||
import { LinkButton } from '../../../../common/components/links';
|
||||
|
@ -17,6 +16,8 @@ import { SecurityPageName } from '../../../../app/types';
|
|||
import { useFormatUrl } from '../../../../common/components/link_to';
|
||||
import { usePrePackagedRules } from '../../../containers/detection_engine/rules';
|
||||
import { useUserData } from '../../user_info';
|
||||
import { APP_ID } from '../../../../../common/constants';
|
||||
import { useKibana } from '../../../../common/lib/kibana';
|
||||
|
||||
const EmptyPrompt = styled(EuiEmptyPrompt)`
|
||||
align-self: center; /* Corrects horizontal centering in IE11 */
|
||||
|
@ -35,18 +36,18 @@ const PrePackagedRulesPromptComponent: React.FC<PrePackagedRulesPromptProps> = (
|
|||
loading = false,
|
||||
userHasPermissions = false,
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
const handlePreBuiltCreation = useCallback(() => {
|
||||
createPrePackagedRules();
|
||||
}, [createPrePackagedRules]);
|
||||
const { formatUrl } = useFormatUrl(SecurityPageName.detections);
|
||||
const { formatUrl } = useFormatUrl(SecurityPageName.rules);
|
||||
const { navigateToApp } = useKibana().services.application;
|
||||
|
||||
const goToCreateRule = useCallback(
|
||||
(ev) => {
|
||||
ev.preventDefault();
|
||||
history.push(getCreateRuleUrl());
|
||||
navigateToApp(APP_ID, { deepLinkId: SecurityPageName.rules, path: getCreateRuleUrl() });
|
||||
},
|
||||
[history]
|
||||
[navigateToApp]
|
||||
);
|
||||
|
||||
const [
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue