From 94a4e38053aa38738405fab246ed703459fda7b7 Mon Sep 17 00:00:00 2001
From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>
Date: Wed, 23 Sep 2020 15:49:52 -0400
Subject: [PATCH] [Security Solution] Options to select index patterns (#77192)
* init commit
* lots of cleanup
* starting on tests... problems
* Ready for review
* remove sample data
* remove comment and fix type
* pr changes
* fix type
* scratchy
* sourcerer in timeline
* sourcerer in timeline
* wip
* moving to redux
* working on types
* fixed
* more adjustments, tests fixed
* FF off
* pr ready
* renaming
* url state working, hoc not working
* url state working for timeline and default scope
* script to build fields for beat doc
* refactor sourcerer
* refactor host to useSourcerer
* refactor network to useSourcerer
* refactor overview to useSourcerer
* refactor detections to useSourcerer
* wip for timelines to remove all useSource
* wip indexes timeline
* do component tests
* start container tests
* start container tests
* update selection widget of index patterns + remove last useWithSource
* add indexeNames in network kpi
* fix type errors
* fix type
* missing merge master
* get existing index from config file
* fixing broken tests
* add saving button to avoid to many queries to be aborted
* reducer timeline tests broke
* need to rewind
* much better
* timeline saving index names + clean up url state to only manage default
* more test fixing
* more test changes
* remove all the useWithSource + deprecated the graphql until we delete it in a new PR + delete all the beat doc
* default timeline to all index when creation + filter index patterns to make sure you do not add one who we do not know
* fix types
* test for stateful timeline render
* we should not have change that
* no chnages + snapshot
* fix test + bugs from review
* fix uncommon processes indexNames
* review III
* change design for main page of the sourcerer from design
* bug fixes when opening old timeline + implementation of new design
* fix circular deps
* remove unused attributes for event details
* design cleanup
* fix api integration test with the new search strategy
* add reset + manage accordion state
* fix bugs + types issues
* cleanup
* update docs
* review -> remove tooltip when popover is open
* cypress fixing
* fix for ml_condition_links and url_state cypress tests
* add cy wait for race condition in pagination tests
* missing plumbing kpi host
Co-authored-by: Steph Milovic
Co-authored-by: Patryk Kopycinski
---
...ic.indexpatternsservice.getidswithtitle.md | 16 +
...lugins-data-public.indexpatternsservice.md | 1 +
.../index_patterns/index_patterns.ts | 19 +
src/plugins/data/public/public.api.md | 4 +
.../security_solution/common/constants.ts | 3 +-
.../common/search_strategy/common/index.ts | 2 +-
.../search_strategy/index_fields/index.ts | 81 +
.../timeline/events/details/index.ts | 1 -
.../common/types/timeline/index.ts | 3 +
.../integration/ml_conditional_links.spec.ts | 26 +-
.../cypress/integration/pagination.spec.ts | 1 +
.../cypress/integration/url_state.spec.ts | 12 +-
x-pack/plugins/security_solution/package.json | 1 +
.../security_solution/public/app/app.tsx | 18 +-
.../public/app/home/index.tsx | 30 +-
.../components/case_header_page/index.tsx | 4 +-
.../cases/components/case_view/index.tsx | 1 +
.../components/alerts_viewer/alerts_table.tsx | 2 +
.../common/components/alerts_viewer/index.tsx | 2 +
.../common/components/alerts_viewer/types.ts | 2 +-
.../drag_drop_context_wrapper.test.tsx | 19 +-
.../drag_and_drop/draggable_wrapper.test.tsx | 51 +-
.../draggable_wrapper_hover_content.test.tsx | 21 +-
.../draggable_wrapper_hover_content.tsx | 27 +-
.../drag_and_drop/droppable_wrapper.test.tsx | 45 +-
.../events_viewer/events_viewer.test.tsx | 344 +-
.../events_viewer/events_viewer.tsx | 4 +-
.../components/events_viewer/index.test.tsx | 36 +-
.../common/components/events_viewer/index.tsx | 29 +-
.../add_exception_modal/index.test.tsx | 15 +-
.../exceptions/add_exception_modal/index.tsx | 15 +-
.../edit_exception_modal/index.test.tsx | 11 +-
.../exceptions/edit_exception_modal/index.tsx | 14 +-
.../common/components/header_global/index.tsx | 10 +-
.../__snapshots__/index.test.tsx.snap | 3 +
.../common/components/header_page/index.tsx | 5 +
.../components/last_event_time/index.test.tsx | 8 +-
.../components/last_event_time/index.tsx | 83 +-
.../matrix_histogram/index.test.tsx | 1 +
.../components/matrix_histogram/index.tsx | 5 +-
.../components/matrix_histogram/types.ts | 2 +-
.../navigation/breadcrumbs/index.test.ts | 31 +-
.../common/components/navigation/helpers.ts | 11 +-
.../components/navigation/index.test.tsx | 3 +
.../common/components/navigation/index.tsx | 10 +-
.../navigation/tab_navigation/index.test.tsx | 2 +
.../navigation/tab_navigation/index.tsx | 13 +-
.../navigation/tab_navigation/types.ts | 2 +
.../common/components/navigation/types.ts | 2 +
.../components/sourcerer/index.test.tsx | 192 +-
.../common/components/sourcerer/index.tsx | 301 +-
.../common/components/sourcerer/selectors.tsx | 35 +
.../components/sourcerer/translations.ts | 37 +-
.../public/common/components/top_n/helpers.ts | 8 +-
.../common/components/top_n/index.test.tsx | 93 +-
.../public/common/components/top_n/index.tsx | 7 +-
.../common/components/top_n/top_n.test.tsx | 109 +-
.../public/common/components/top_n/top_n.tsx | 16 +-
.../common/components/url_state/constants.ts | 1 +
.../common/components/url_state/helpers.ts | 20 +
.../components/url_state/index.test.tsx | 2 +-
.../url_state/index_mocked.test.tsx | 45 +-
.../url_state/initialize_redux_by_url.tsx | 20 +-
.../components/url_state/test_dependencies.ts | 1 +
.../common/components/url_state/types.ts | 9 +
.../anomalies_query_tab_body/index.tsx | 2 +
.../anomalies_query_tab_body/types.ts | 1 +
.../events/last_event_time/index.ts | 23 +-
.../containers/matrix_histogram/index.ts | 22 +-
.../common/containers/query_template.tsx | 1 +
.../containers/source/index.gql_query.ts | 31 -
.../common/containers/source/index.test.tsx | 109 -
.../public/common/containers/source/index.tsx | 329 +-
.../public/common/containers/source/mock.ts | 625 +-
.../common/containers/source/translations.ts | 21 +
.../common/containers/sourcerer/constants.ts | 24 +-
.../containers/sourcerer/format.test.tsx | 23 -
.../common/containers/sourcerer/format.ts | 96 -
.../containers/sourcerer/index.test.tsx | 368 +-
.../common/containers/sourcerer/index.tsx | 450 +-
.../common/containers/sourcerer/mocks.ts | 98 +-
.../common/lib/kibana/__mocks__/index.ts | 20 +-
.../public/common/mock/global_state.ts | 27 +
.../public/common/mock/index_pattern.ts | 6 +-
.../public/common/mock/timeline_results.ts | 2 +
.../public/common/store/actions.ts | 1 +
.../public/common/store/model.ts | 1 +
.../public/common/store/reducer.ts | 14 +-
.../public/common/store/selectors.ts | 1 +
.../public/common/store/sourcerer/actions.ts | 36 +
.../public/common/store/sourcerer/index.ts | 12 +
.../public/common/store/sourcerer/model.ts | 86 +
.../public/common/store/sourcerer/reducer.ts | 93 +
.../common/store/sourcerer/selectors.ts | 91 +
.../public/common/store/types.ts | 2 +
.../components/alerts_table/actions.test.tsx | 1 +
.../components/alerts_table/actions.tsx | 2 +
.../components/alerts_table/index.test.tsx | 1 -
.../components/alerts_table/index.tsx | 27 +-
.../timeline_actions/alert_context_menu.tsx | 42 +-
.../investigate_in_timeline_action.tsx | 3 +-
.../detection_engine_header_page/index.tsx | 2 +-
.../rules/step_about_rule/index.test.tsx | 10 +-
.../rules/step_about_rule/index.tsx | 8 +-
.../rules/step_define_rule/index.tsx | 7 +-
.../detections/components/user_info/index.tsx | 24 +-
.../rules/fetch_index_patterns.test.tsx | 475 -
.../rules/fetch_index_patterns.tsx | 132 -
.../detection_engine/rules/index.ts | 1 -
.../detection_engine.test.tsx | 6 +-
.../detection_engine/detection_engine.tsx | 9 +-
.../rules/details/index.test.tsx | 6 +-
.../detection_engine/rules/details/index.tsx | 12 +-
.../public/graphql/introspection.json | 326 +-
.../security_solution/public/graphql/types.ts | 125 +-
.../first_last_seen_host/index.test.tsx | 49 +-
.../components/first_last_seen_host/index.tsx | 75 +-
.../__snapshots__/index.test.tsx.snap | 91 -
.../components/hosts_table/index.test.tsx | 3 -
.../hosts/components/hosts_table/index.tsx | 3 -
.../kpi_hosts/authentications/index.tsx | 2 +
.../components/kpi_hosts/hosts/index.tsx | 2 +
.../hosts/components/kpi_hosts/index.tsx | 9 +-
.../hosts/components/kpi_hosts/types.ts | 1 +
.../components/kpi_hosts/unique_ips/index.tsx | 2 +
.../containers/authentications/index.tsx | 12 +-
.../hosts/containers/hosts/details/_index.tsx | 18 +-
.../hosts/containers/hosts/details/index.tsx | 11 +-
.../hosts/first_last_seen/index.tsx | 18 +-
.../public/hosts/containers/hosts/index.tsx | 12 +-
.../containers/kpi_host_details/index.tsx | 16 +-
.../kpi_hosts/authentications/index.tsx | 12 +-
.../containers/kpi_hosts/hosts/index.tsx | 12 +-
.../containers/kpi_hosts/unique_ips/index.tsx | 12 +-
.../containers/uncommon_processes/index.tsx | 12 +-
.../hosts/pages/details/details_tabs.test.tsx | 1 +
.../hosts/pages/details/details_tabs.tsx | 8 +-
.../public/hosts/pages/details/index.tsx | 16 +-
.../public/hosts/pages/details/types.ts | 1 +
.../public/hosts/pages/hosts.test.tsx | 14 +-
.../public/hosts/pages/hosts.tsx | 15 +-
.../public/hosts/pages/hosts_tabs.tsx | 12 +-
.../authentications_query_tab_body.tsx | 12 +-
.../navigation/events_query_tab_body.tsx | 4 +
.../pages/navigation/hosts_query_tab_body.tsx | 5 +-
.../public/hosts/pages/navigation/types.ts | 4 +-
.../uncommon_process_query_tab_body.tsx | 11 +-
.../public/hosts/pages/types.ts | 3 +-
.../components/administration_list_page.tsx | 7 +-
.../pages/policy/view/policy_details.tsx | 1 +
.../trusted_apps_page.test.tsx.snap | 66 +
.../view/trusted_apps_page.test.tsx | 1 +
.../__snapshots__/index.test.tsx.snap | 1 +
.../components/kpi_network/dns/index.tsx | 2 +
.../components/kpi_network/index.test.tsx | 5 +-
.../network/components/kpi_network/index.tsx | 7 +-
.../kpi_network/network_events/index.tsx | 2 +
.../kpi_network/tls_handshakes/index.tsx | 2 +
.../network/components/kpi_network/types.ts | 1 +
.../kpi_network/unique_flows/index.tsx | 2 +
.../kpi_network/unique_private_ips/index.tsx | 2 +
.../network/containers/details/index.tsx | 12 +-
.../containers/kpi_network/dns/index.tsx | 12 +-
.../kpi_network/network_events/index.tsx | 12 +-
.../kpi_network/tls_handshakes/index.tsx | 12 +-
.../kpi_network/unique_flows/index.tsx | 12 +-
.../kpi_network/unique_private_ips/index.tsx | 12 +-
.../network/containers/network_dns/index.tsx | 12 +-
.../network/containers/network_http/index.tsx | 12 +-
.../network_top_countries/index.tsx | 12 +-
.../containers/network_top_n_flow/index.tsx | 12 +-
.../public/network/containers/tls/index.tsx | 12 +-
.../network/pages/details/index.test.tsx | 8 +-
.../public/network/pages/details/index.tsx | 22 +-
.../details/network_http_query_table.tsx | 1 +
.../network_top_countries_query_table.tsx | 1 +
.../network_top_n_flow_query_table.tsx | 2 +
.../network/pages/details/tls_query_table.tsx | 1 +
.../public/network/pages/details/types.ts | 1 +
.../navigation/countries_query_tab_body.tsx | 2 +
.../pages/navigation/dns_query_tab_body.tsx | 3 +
.../pages/navigation/http_query_tab_body.tsx | 2 +
.../pages/navigation/ips_query_tab_body.tsx | 2 +
.../pages/navigation/network_routes.tsx | 2 +
.../pages/navigation/tls_query_tab_body.tsx | 2 +
.../public/network/pages/navigation/types.ts | 2 +
.../public/network/pages/network.test.tsx | 31 +-
.../public/network/pages/network.tsx | 20 +-
.../alerts_by_category/index.test.tsx | 28 +-
.../components/alerts_by_category/index.tsx | 3 +
.../components/event_counts/index.test.tsx | 18 +-
.../components/event_counts/index.tsx | 4 +
.../components/events_by_dataset/index.tsx | 6 +-
.../__snapshots__/index.test.tsx.snap | 2 +
.../components/host_overview/index.test.tsx | 2 +
.../components/host_overview/index.tsx | 12 +-
.../components/overview_host/index.test.tsx | 13 +-
.../components/overview_host/index.tsx | 9 +-
.../overview_network/index.test.tsx | 16 +-
.../components/overview_network/index.tsx | 3 +
.../recent_cases/no_cases/index.test.tsx | 2 +-
.../containers/overview_host/index.tsx | 23 +-
.../containers/overview_network/index.tsx | 12 +-
.../public/overview/pages/overview.test.tsx | 184 +-
.../public/overview/pages/overview.tsx | 21 +-
.../public/overview/pages/summary.tsx | 6 +-
.../security_solution/public/plugin.tsx | 56 +-
.../components/flyout/button/index.tsx | 5 +-
.../components/manage_timeline/index.test.tsx | 54 +-
.../components/manage_timeline/index.tsx | 39 -
.../components/open_timeline/helpers.test.ts | 6 +
.../components/open_timeline/helpers.ts | 9 +
.../components/open_timeline/index.tsx | 28 +-
.../__snapshots__/timeline.test.tsx.snap | 14 +-
.../components/timeline/body/helpers.tsx | 9 +-
.../components/timeline/body/index.tsx | 8 +-
.../timeline/data_providers/helpers.test.tsx | 6 +-
.../timeline/data_providers/helpers.tsx | 8 +-
.../components/timeline/index.test.tsx | 153 +-
.../timelines/components/timeline/index.tsx | 46 +-
.../properties/use_create_timeline.test.tsx | 9 +-
.../properties/use_create_timeline.tsx | 28 +-
.../timeline/search_or_filter/index.tsx | 38 +-
.../timeline/search_or_filter/pick_events.tsx | 358 +-
.../search_or_filter/search_or_filter.tsx | 14 +-
.../timeline/search_or_filter/selectors.tsx | 43 +
.../timeline/search_or_filter/translations.ts | 60 +-
.../timelines/components/timeline/styles.tsx | 4 +-
.../components/timeline/timeline.test.tsx | 33 +-
.../components/timeline/timeline.tsx | 46 +-
.../timelines/containers/details/index.tsx | 7 +-
.../public/timelines/containers/index.tsx | 98 +-
.../containers/one/index.gql_query.ts | 1 +
.../timelines/containers/persist.gql_query.ts | 1 +
.../timelines/pages/timelines_page.test.tsx | 6 +-
.../public/timelines/pages/timelines_page.tsx | 10 +-
.../timelines/store/timeline/actions.ts | 16 +-
.../timelines/store/timeline/defaults.ts | 1 +
.../timelines/store/timeline/epic.test.ts | 2 +
.../public/timelines/store/timeline/epic.ts | 3 +
.../timeline/epic_local_storage.test.tsx | 6 +-
.../timelines/store/timeline/helpers.ts | 8 +-
.../public/timelines/store/timeline/model.ts | 7 +-
.../timelines/store/timeline/reducer.test.ts | 1537 +-
.../timelines/store/timeline/reducer.ts | 13 +
.../scripts/beat_docs/build.js | 233 +
.../graphql/source_status/schema.gql.ts | 2 +-
.../server/graphql/timeline/schema.gql.ts | 2 +
.../security_solution/server/graphql/types.ts | 265 +-
.../server/lib/compose/kibana.ts | 2 +-
.../lib/events/elasticsearch_adapter.ts | 3 +-
.../lib/index_fields/elasticsearch_adapter.ts | 165 +-
.../server/lib/index_fields/index.ts | 5 +-
.../server/lib/index_fields/types.ts | 3 +-
.../lib/timeline/saved_object_mappings.ts | 3 +
.../security_solution/server/plugin.ts | 7 +
.../index_fields/index.test.ts} | 230 +-
.../search_strategy/index_fields/index.ts | 224 +
.../search_strategy/index_fields/mock.ts | 113 +
.../timeline/factory/events/all/helpers.ts | 1 +
.../factory/events/details/helpers.ts | 3 +-
.../utils/beat_schema/8.0.0/auditbeat.ts | 7902 ----
.../server/utils/beat_schema/8.0.0/ecs.ts | 5675 ---
.../utils/beat_schema/8.0.0/filebeat.ts | 21243 ---------
.../server/utils/beat_schema/8.0.0/index.ts | 37 -
.../utils/beat_schema/8.0.0/packetbeat.ts | 8556 ----
.../utils/beat_schema/8.0.0/winlogbeat.ts | 2844 --
.../server/utils/beat_schema/fields.ts | 36118 ++++++++++++++++
.../server/utils/beat_schema/index.test.ts | 397 -
.../server/utils/beat_schema/index.ts | 129 -
.../server/utils/beat_schema/type.ts | 73 -
.../apis/security_solution/sources.ts | 147 +-
272 files changed, 41559 insertions(+), 52569 deletions(-)
create mode 100644 docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md
create mode 100644 x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts
create mode 100644 x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.tsx
delete mode 100644 x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts
delete mode 100644 x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx
create mode 100644 x-pack/plugins/security_solution/public/common/containers/source/translations.ts
delete mode 100644 x-pack/plugins/security_solution/public/common/containers/sourcerer/format.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/common/containers/sourcerer/format.ts
create mode 100644 x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts
create mode 100644 x-pack/plugins/security_solution/public/common/store/sourcerer/index.ts
create mode 100644 x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts
create mode 100644 x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts
create mode 100644 x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts
delete mode 100644 x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx
delete mode 100644 x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx
create mode 100644 x-pack/plugins/security_solution/public/timelines/components/timeline/search_or_filter/selectors.tsx
create mode 100644 x-pack/plugins/security_solution/scripts/beat_docs/build.js
rename x-pack/plugins/security_solution/server/{lib/index_fields/elasticsearch_adapter.test.ts => search_strategy/index_fields/index.test.ts} (65%)
create mode 100644 x-pack/plugins/security_solution/server/search_strategy/index_fields/index.ts
create mode 100644 x-pack/plugins/security_solution/server/search_strategy/index_fields/mock.ts
delete mode 100644 x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/auditbeat.ts
delete mode 100644 x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/ecs.ts
delete mode 100644 x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/filebeat.ts
delete mode 100644 x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/index.ts
delete mode 100644 x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/packetbeat.ts
delete mode 100644 x-pack/plugins/security_solution/server/utils/beat_schema/8.0.0/winlogbeat.ts
create mode 100644 x-pack/plugins/security_solution/server/utils/beat_schema/fields.ts
delete mode 100644 x-pack/plugins/security_solution/server/utils/beat_schema/index.test.ts
delete mode 100644 x-pack/plugins/security_solution/server/utils/beat_schema/index.ts
delete mode 100644 x-pack/plugins/security_solution/server/utils/beat_schema/type.ts
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md
new file mode 100644
index 0000000000000..7d29ced66afa8
--- /dev/null
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md
@@ -0,0 +1,16 @@
+
+
+[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [IndexPatternsService](./kibana-plugin-plugins-data-public.indexpatternsservice.md) > [getIdsWithTitle](./kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md)
+
+## IndexPatternsService.getIdsWithTitle property
+
+Get list of index pattern ids with titles
+
+Signature:
+
+```typescript
+getIdsWithTitle: (refresh?: boolean) => Promise>;
+```
diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md
index 0022bff34a8e7..af087344268d7 100644
--- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md
+++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.indexpatternsservice.md
@@ -29,6 +29,7 @@ export declare class IndexPatternsService
| [getFieldsForIndexPattern](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforindexpattern.md) | | (indexPattern: IndexPattern | IndexPatternSpec, options?: GetFieldsOptions) => Promise<any>
| Get field list by providing an index patttern (or spec) |
| [getFieldsForWildcard](./kibana-plugin-plugins-data-public.indexpatternsservice.getfieldsforwildcard.md) | | (options?: GetFieldsOptions) => Promise<any>
| Get field list by providing { pattern } |
| [getIds](./kibana-plugin-plugins-data-public.indexpatternsservice.getids.md) | | (refresh?: boolean) => Promise<string[]>
| Get list of index pattern ids |
+| [getIdsWithTitle](./kibana-plugin-plugins-data-public.indexpatternsservice.getidswithtitle.md) | | (refresh?: boolean) => Promise<Array<{
id: string;
title: string;
}>>
| Get list of index pattern ids with titles |
| [getTitles](./kibana-plugin-plugins-data-public.indexpatternsservice.gettitles.md) | | (refresh?: boolean) => Promise<string[]>
| Get list of index pattern titles |
| [refreshFields](./kibana-plugin-plugins-data-public.indexpatternsservice.refreshfields.md) | | (indexPattern: IndexPattern) => Promise<void>
| Refresh field list for a given index pattern |
| [savedObjectToSpec](./kibana-plugin-plugins-data-public.indexpatternsservice.savedobjecttospec.md) | | (savedObject: SavedObject<IndexPatternAttributes>) => IndexPatternSpec
| Converts index pattern saved object to index pattern spec |
diff --git a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts
index c56954ba6a29b..eef8ef10ea754 100644
--- a/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts
+++ b/src/plugins/data/common/index_patterns/index_patterns/index_patterns.ts
@@ -133,6 +133,25 @@ export class IndexPatternsService {
return this.savedObjectsCache.map((obj) => obj?.attributes?.title);
};
+ /**
+ * Get list of index pattern ids with titles
+ * @param refresh Force refresh of index pattern list
+ */
+ getIdsWithTitle = async (
+ refresh: boolean = false
+ ): Promise> => {
+ if (!this.savedObjectsCache || refresh) {
+ await this.refreshSavedObjectsCache();
+ }
+ if (!this.savedObjectsCache) {
+ return [];
+ }
+ return this.savedObjectsCache.map((obj) => ({
+ id: obj?.id,
+ title: obj?.attributes?.title,
+ }));
+ };
+
/**
* Clear index pattern list cache
* @param id optionally clear a single id
diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md
index 5919c1e294b2f..ed58ee840a8f8 100644
--- a/src/plugins/data/public/public.api.md
+++ b/src/plugins/data/public/public.api.md
@@ -1381,6 +1381,10 @@ export class IndexPatternsService {
// Warning: (ae-forgotten-export) The symbol "GetFieldsOptions" needs to be exported by the entry point index.d.ts
getFieldsForWildcard: (options?: GetFieldsOptions) => Promise;
getIds: (refresh?: boolean) => Promise;
+ getIdsWithTitle: (refresh?: boolean) => Promise>;
getTitles: (refresh?: boolean) => Promise;
refreshFields: (indexPattern: IndexPattern) => Promise;
savedObjectToSpec: (savedObject: SavedObject) => IndexPatternSpec;
diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts
index 9321aa769423f..a93d2817fbbb3 100644
--- a/x-pack/plugins/security_solution/common/constants.ts
+++ b/x-pack/plugins/security_solution/common/constants.ts
@@ -11,7 +11,6 @@ export const APP_ICON = 'securityAnalyticsApp';
export const APP_ICON_SOLUTION = 'logoSecurity';
export const APP_PATH = `/app/security`;
export const ADD_DATA_PATH = `/app/home#/tutorial_directory/security`;
-export const ADD_INDEX_PATH = `/app/management/kibana/indexPatterns/create`;
export const DEFAULT_BYTES_FORMAT = 'format:bytes:defaultPattern';
export const DEFAULT_DATE_FORMAT = 'dateFormat';
export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz';
@@ -58,6 +57,8 @@ 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 DETECTIONS_SUB_PLUGIN_ID = `${APP_ID}:${SecurityPageName.detections}`;
+
/** The comma-delimited list of Elasticsearch indices from which the SIEM app collects events */
export const DEFAULT_INDEX_PATTERN = [
'apm-*-transaction*',
diff --git a/x-pack/plugins/security_solution/common/search_strategy/common/index.ts b/x-pack/plugins/security_solution/common/search_strategy/common/index.ts
index 48437e12f75a5..0c1f13dac2e69 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/common/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/common/index.ts
@@ -71,7 +71,7 @@ export interface PaginationInputPaginated {
export interface DocValueFields {
field: string;
- format: string;
+ format?: string | null;
}
export interface Explanation {
diff --git a/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts b/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts
new file mode 100644
index 0000000000000..259a767f8cf70
--- /dev/null
+++ b/x-pack/plugins/security_solution/common/search_strategy/index_fields/index.ts
@@ -0,0 +1,81 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+import { IIndexPattern } from 'src/plugins/data/public';
+import {
+ IEsSearchRequest,
+ IEsSearchResponse,
+ IFieldSubType,
+} from '../../../../../../src/plugins/data/common';
+import { DocValueFields, Maybe } from '../common';
+
+export type BeatFieldsFactoryQueryType = 'beatFields';
+
+interface FieldInfo {
+ category: string;
+ description?: string;
+ example?: string | number;
+ format?: string;
+ name: string;
+ type?: string;
+}
+
+export interface IndexField {
+ /** Where the field belong */
+ category: string;
+ /** Example of field's value */
+ example?: Maybe;
+ /** whether the field's belong to an alias index */
+ indexes: Array>;
+ /** The name of the field */
+ name: string;
+ /** The type of the field's values as recognized by Kibana */
+ type: string;
+ /** Whether the field's values can be efficiently searched for */
+ searchable: boolean;
+ /** Whether the field's values can be aggregated */
+ aggregatable: boolean;
+ /** Description of the field */
+ description?: Maybe;
+ format?: Maybe;
+ /** the elastic type as mapped in the index */
+ esTypes?: string[];
+ subType?: IFieldSubType;
+ readFromDocValues: boolean;
+}
+
+export type BeatFields = Record;
+
+export interface IndexFieldsStrategyRequest extends IEsSearchRequest {
+ indices: string[];
+ onlyCheckIfIndicesExist: boolean;
+}
+
+export interface IndexFieldsStrategyResponse extends IEsSearchResponse {
+ indexFields: IndexField[];
+ indicesExist: string[];
+}
+
+export interface BrowserField {
+ aggregatable: boolean;
+ category: string;
+ description: string | null;
+ example: string | number | null;
+ fields: Readonly>>;
+ format: string;
+ indexes: string[];
+ name: string;
+ searchable: boolean;
+ type: string;
+}
+
+export type BrowserFields = Readonly>>;
+
+export const EMPTY_BROWSER_FIELDS = {};
+export const EMPTY_DOCVALUE_FIELD: DocValueFields[] = [];
+export const EMPTY_INDEX_PATTERN: IIndexPattern = {
+ fields: [],
+ title: '',
+};
diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts
index 6f9192be40150..9fa7f96599deb 100644
--- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts
+++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/details/index.ts
@@ -22,7 +22,6 @@ export interface TimelineEventsDetailsStrategyResponse extends IEsSearchResponse
export interface TimelineEventsDetailsRequestOptions
extends Partial {
- defaultIndex: string[];
indexName: string;
eventId: string;
}
diff --git a/x-pack/plugins/security_solution/common/types/timeline/index.ts b/x-pack/plugins/security_solution/common/types/timeline/index.ts
index 84a007e322f11..3888d37a547f7 100644
--- a/x-pack/plugins/security_solution/common/types/timeline/index.ts
+++ b/x-pack/plugins/security_solution/common/types/timeline/index.ts
@@ -239,6 +239,7 @@ export const SavedTimelineRuntimeType = runtimeTypes.partial({
excludedRowRendererIds: unionWithNullType(runtimeTypes.array(RowRendererIdRuntimeType)),
favorite: unionWithNullType(runtimeTypes.array(SavedFavoriteRuntimeType)),
filters: unionWithNullType(runtimeTypes.array(SavedFilterRuntimeType)),
+ indexNames: unionWithNullType(runtimeTypes.array(runtimeTypes.string)),
kqlMode: unionWithNullType(runtimeTypes.string),
kqlQuery: unionWithNullType(SavedFilterQueryQueryRuntimeType),
title: unionWithNullType(runtimeTypes.string),
@@ -398,3 +399,5 @@ export const importTimelineResultSchema = runtimeTypes.exact(
);
export type ImportTimelineResultSchema = runtimeTypes.TypeOf;
+
+export type TimelineEventsType = 'all' | 'raw' | 'alert' | 'signal' | 'custom';
diff --git a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts
index 0b302efd655a8..06a8d3a79c3cd 100644
--- a/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/ml_conditional_links.spec.ts
@@ -94,7 +94,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpNullKqlQuery);
cy.url().should(
'include',
- '/app/security/network/ip/127.0.0.1/source?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
+ '/app/security/network/ip/127.0.0.1/source?sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
);
});
@@ -102,7 +102,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkSingleIpKqlQuery);
cy.url().should(
'include',
- '/app/security/network/ip/127.0.0.1/source?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
+ '/app/security/network/ip/127.0.0.1/source?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
);
});
@@ -110,7 +110,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpNullKqlQuery);
cy.url().should(
'include',
- 'app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27))'
+ 'app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%27)&sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27))'
);
});
@@ -118,7 +118,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkMultipleIpKqlQuery);
cy.url().should(
'include',
- '/app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
+ '/app/security/network/flows?query=(language:kuery,query:%27((source.ip:%20%22127.0.0.1%22%20or%20destination.ip:%20%22127.0.0.1%22)%20or%20(source.ip:%20%22127.0.0.2%22%20or%20destination.ip:%20%22127.0.0.2%22))%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
);
});
@@ -126,7 +126,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkNullKqlQuery);
cy.url().should(
'include',
- '/app/security/network/flows?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
+ '/app/security/network/flows?sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
);
});
@@ -134,7 +134,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlNetworkKqlQuery);
cy.url().should(
'include',
- '/app/security/network/flows?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
+ '/app/security/network/flows?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-08-28T11:00:00.000Z%27,kind:absolute,to:%272019-08-28T13:59:59.999Z%27)))'
);
});
@@ -142,7 +142,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostSingleHostNullKqlQuery);
cy.url().should(
'include',
- '/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
+ '/app/security/hosts/siem-windows/anomalies?sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
);
});
@@ -150,7 +150,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQueryVariable);
cy.url().should(
'include',
- '/app/security/hosts/siem-windows/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
+ '/app/security/hosts/siem-windows/anomalies?sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
);
});
@@ -158,7 +158,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostSingleHostKqlQuery);
cy.url().should(
'include',
- '/app/security/hosts/siem-windows/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
+ '/app/security/hosts/siem-windows/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
);
});
@@ -166,7 +166,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostMultiHostNullKqlQuery);
cy.url().should(
'include',
- '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
+ '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%27)&sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
);
});
@@ -174,7 +174,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostMultiHostKqlQuery);
cy.url().should(
'include',
- '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
+ '/app/security/hosts/anomalies?query=(language:kuery,query:%27(host.name:%20%22siem-windows%22%20or%20host.name:%20%22siem-suricata%22)%20and%20((process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22))%27)&sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
);
});
@@ -182,7 +182,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostVariableHostNullKqlQuery);
cy.url().should(
'include',
- '/app/security/hosts/anomalies?timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
+ '/app/security/hosts/anomalies?sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
);
});
@@ -190,7 +190,7 @@ describe('ml conditional links', () => {
loginAndWaitForPageWithoutDateRange(mlHostVariableHostKqlQuery);
cy.url().should(
'include',
- '/app/security/hosts/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
+ '/app/security/hosts/anomalies?query=(language:kuery,query:%27(process.name:%20%22conhost.exe%22%20or%20process.name:%20%22sc.exe%22)%27)&sourcerer=(default:!(%27auditbeat-*%27))&timerange=(global:(linkTo:!(timeline),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)),timeline:(linkTo:!(global),timerange:(from:%272019-06-06T06:00:00.000Z%27,kind:absolute,to:%272019-06-07T05:59:59.999Z%27)))'
);
});
});
diff --git a/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts b/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts
index 5dc3182cd9f83..fdccf164c7465 100644
--- a/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/pagination.spec.ts
@@ -35,6 +35,7 @@ describe('Pagination', () => {
.then((processNameFirstPage) => {
goToThirdPage();
waitForUncommonProcessesToBeLoaded();
+ cy.wait(1500);
cy.get(PROCESS_NAME_FIELD)
.first()
.invoke('text')
diff --git a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts
index 04aecfab4561f..2588c580dedd3 100644
--- a/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts
+++ b/x-pack/plugins/security_solution/cypress/integration/url_state.spec.ts
@@ -166,7 +166,7 @@ describe.skip('url state', () => {
cy.get(NETWORK).should(
'have.attr',
'href',
- `/app/security/network?query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')))`
+ `/app/security/network?query=(language:kuery,query:'source.ip:%20%2210.142.0.9%22%20')&sourcerer=(default:!(\'auditbeat-*\'))&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')),timeline:(linkTo:!(global),timerange:(from:'2019-08-01T20:03:29.186Z',kind:absolute,to:'2019-08-01T20:33:29.186Z')))`
);
});
@@ -179,12 +179,12 @@ describe.skip('url state', () => {
cy.get(HOSTS).should(
'have.attr',
'href',
- `/app/security/hosts?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&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?query=(language:kuery,query:'host.name:%20%22siem-kibana%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')))`
);
cy.get(NETWORK).should(
'have.attr',
'href',
- `/app/security/network?query=(language:kuery,query:'host.name:%20%22siem-kibana%22%20')&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/network?query=(language:kuery,query:'host.name:%20%22siem-kibana%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')))`
);
cy.get(HOSTS_NAMES).first().invoke('text').should('eq', 'siem-kibana');
@@ -195,21 +195,21 @@ describe.skip('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')&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?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')))"
);
cy.get(BREADCRUMBS)
.eq(1)
.should(
'have.attr',
'href',
- `/app/security/hosts?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&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?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')))`
);
cy.get(BREADCRUMBS)
.eq(2)
.should(
'have.attr',
'href',
- `/app/security/hosts/siem-kibana?query=(language:kuery,query:'agent.type:%20%22auditbeat%22%20')&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?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')))`
);
});
diff --git a/x-pack/plugins/security_solution/package.json b/x-pack/plugins/security_solution/package.json
index 6982c200a5afd..6d79557fdaa28 100644
--- a/x-pack/plugins/security_solution/package.json
+++ b/x-pack/plugins/security_solution/package.json
@@ -6,6 +6,7 @@
"license": "Elastic-License",
"scripts": {
"extract-mitre-attacks": "node scripts/extract_tactics_techniques_mitre.js && node ../../../scripts/eslint ./public/pages/detection_engine/mitre/mitre_tactics_techniques.ts --fix",
+ "build-beat-doc": "node scripts/beat_docs/build.js && node ../../../scripts/eslint ./server/utils/beat_schema/fields.ts --fix",
"build-graphql-types": "node scripts/generate_types_from_graphql.js",
"cypress:open": "cypress open --config-file ./cypress/cypress.json",
"cypress:open-as-ci": "node ../../../scripts/functional_tests --config ../../test/security_solution_cypress/visual_config.ts",
diff --git a/x-pack/plugins/security_solution/public/app/app.tsx b/x-pack/plugins/security_solution/public/app/app.tsx
index b4e9ba3dd7a71..54b02c374e43f 100644
--- a/x-pack/plugins/security_solution/public/app/app.tsx
+++ b/x-pack/plugins/security_solution/public/app/app.tsx
@@ -28,8 +28,6 @@ import { ApolloClientContext } from '../common/utils/apollo_context';
import { ManageGlobalTimeline } from '../timelines/components/manage_timeline';
import { StartServices } from '../types';
import { PageRouter } from './routes';
-import { ManageSource } from '../common/containers/sourcerer';
-
interface StartAppComponent extends AppFrontendLibs {
children: React.ReactNode;
history: History;
@@ -56,15 +54,13 @@ const StartAppComponent: FC = ({ children, apolloClient, hist
-
-
-
-
- {children}
-
-
-
-
+
+
+
+ {children}
+
+
+
diff --git a/x-pack/plugins/security_solution/public/app/home/index.tsx b/x-pack/plugins/security_solution/public/app/home/index.tsx
index b48ae4e6e2d75..e0dea199e78ff 100644
--- a/x-pack/plugins/security_solution/public/app/home/index.tsx
+++ b/x-pack/plugins/security_solution/public/app/home/index.tsx
@@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useMemo } from 'react';
+import React, { useRef } from 'react';
import styled from 'styled-components';
import { TimelineId } from '../../../common/types/timeline';
@@ -14,11 +14,12 @@ import { HeaderGlobal } from '../../common/components/header_global';
import { HelpMenu } from '../../common/components/help_menu';
import { AutoSaveWarningMsg } from '../../timelines/components/timeline/auto_save_warning';
import { UseUrlState } from '../../common/components/url_state';
-import { useWithSource } from '../../common/containers/source';
import { useShowTimeline } from '../../common/utils/timeline/use_show_timeline';
import { navTabs } from './home_navigations';
-import { useSignalIndex } from '../../detections/containers/detection_engine/alerts/use_signal_index';
-import { useUserInfo } from '../../detections/components/user_info';
+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';
const SecuritySolutionAppWrapper = styled.div`
display: flex;
@@ -42,20 +43,21 @@ interface HomePageProps {
}
const HomePageComponent: React.FC = ({ children }) => {
- const { signalIndexExists, signalIndexName } = useSignalIndex();
+ const { application } = useKibana().services;
+ const subPluginId = useRef('');
- const indexToAdd = useMemo(() => {
- if (signalIndexExists && signalIndexName != null) {
- return [signalIndexName];
- }
- return null;
- }, [signalIndexExists, signalIndexName]);
+ application.currentAppId$.subscribe((appId) => {
+ subPluginId.current = appId ?? '';
+ });
+ useInitSourcerer(
+ subPluginId.current === DETECTIONS_SUB_PLUGIN_ID
+ ? SourcererScopeName.detections
+ : SourcererScopeName.default
+ );
const [showTimeline] = useShowTimeline();
- const { browserFields, indexPattern, indicesExist } = useWithSource('default', indexToAdd);
- // side effect: this will attempt to create the signals index if it doesn't exist
- useUserInfo();
+ const { browserFields, indexPattern, indicesExist } = useSourcererScope();
return (
diff --git a/x-pack/plugins/security_solution/public/cases/components/case_header_page/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_header_page/index.tsx
index 0ac6093f2ee04..4f7b17a730b6a 100644
--- a/x-pack/plugins/security_solution/public/cases/components/case_header_page/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/case_header_page/index.tsx
@@ -9,7 +9,9 @@ import React from 'react';
import { HeaderPage, HeaderPageProps } from '../../../common/components/header_page';
import * as i18n from './translations';
-const CaseHeaderPageComponent: React.FC = (props) => ;
+const CaseHeaderPageComponent: React.FC = (props) => (
+
+);
CaseHeaderPageComponent.defaultProps = {
badgeOptions: {
diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx
index b23169af6ceb3..ad113d3e7e737 100644
--- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx
+++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx
@@ -294,6 +294,7 @@ export const CaseComponent = React.memo(
= ({
defaultModel={alertsDefaultModel}
end={endDate}
id={timelineId}
+ scopeId={SourcererScopeName.default}
start={startDate}
/>
);
diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx
index d522e372d7734..0dcd29a2d965b 100644
--- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/index.tsx
@@ -24,6 +24,7 @@ const AlertsViewComponent: React.FC = ({
deleteQuery,
endDate,
filterQuery,
+ indexNames,
pageFilters,
setQuery,
startDate,
@@ -62,6 +63,7 @@ const AlertsViewComponent: React.FC = ({
endDate={endDate}
filterQuery={filterQuery}
id={ID}
+ indexNames={indexNames}
setQuery={setQuery}
startDate={startDate}
{...alertsHistogramConfigs}
diff --git a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/types.ts b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/types.ts
index b2637eeb2c65e..280b9111042d0 100644
--- a/x-pack/plugins/security_solution/public/common/components/alerts_viewer/types.ts
+++ b/x-pack/plugins/security_solution/public/common/components/alerts_viewer/types.ts
@@ -15,7 +15,7 @@ type CommonQueryProps = HostsComponentsQueryProps | NetworkComponentQueryProps;
export interface AlertsComponentsProps
extends Pick<
CommonQueryProps,
- 'deleteQuery' | 'endDate' | 'filterQuery' | 'skip' | 'setQuery' | 'startDate'
+ 'deleteQuery' | 'endDate' | 'filterQuery' | 'indexNames' | 'skip' | 'setQuery' | 'startDate'
> {
timelineId: TimelineIdLiteral;
pageFilters: Filter[];
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.test.tsx
index 9e8bde8d9ff92..eaaba90b35634 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/drag_drop_context_wrapper.test.tsx
@@ -6,9 +6,8 @@
import { mount, shallow } from 'enzyme';
import React from 'react';
-import { MockedProvider } from 'react-apollo/test-utils';
-import { mockBrowserFields, mocksSource } from '../../containers/source/mock';
+import { mockBrowserFields } from '../../containers/source/mock';
import { TestProviders } from '../../mock';
import { DragDropContextWrapper } from './drag_drop_context_wrapper';
@@ -20,11 +19,9 @@ describe('DragDropContextWrapper', () => {
const wrapper = shallow(
-
-
- {message}
-
-
+
+ {message}
+
);
expect(wrapper.find('DragDropContextWrapper')).toMatchSnapshot();
@@ -35,11 +32,9 @@ describe('DragDropContextWrapper', () => {
const wrapper = mount(
-
-
- {message}
-
-
+
+ {message}
+
);
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx
index ebfa9ac22bdc7..46e7298677f49 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper.test.tsx
@@ -6,11 +6,10 @@
import { shallow } from 'enzyme';
import React from 'react';
-import { MockedProvider } from 'react-apollo/test-utils';
import { DraggableStateSnapshot, DraggingStyle } from 'react-beautiful-dnd';
import '../../mock/match_media';
-import { mockBrowserFields, mocksSource } from '../../containers/source/mock';
+import { mockBrowserFields } from '../../containers/source/mock';
import { TestProviders } from '../../mock';
import { mockDataProviders } from '../../../timelines/components/timeline/data_providers/mock/mock_data_providers';
import { DragDropContextWrapper } from './drag_drop_context_wrapper';
@@ -30,11 +29,9 @@ describe('DraggableWrapper', () => {
test('it renders against the snapshot', () => {
const wrapper = shallow(
-
-
- message} />
-
-
+
+ message} />
+
);
@@ -44,11 +41,9 @@ describe('DraggableWrapper', () => {
test('it renders the children passed to the render prop', () => {
const wrapper = mount(
-
-
- message} />
-
-
+
+ message} />
+
);
@@ -58,11 +53,9 @@ describe('DraggableWrapper', () => {
test('it does NOT render hover actions when the mouse is NOT over the draggable wrapper', () => {
const wrapper = mount(
-
-
- message} />
-
-
+
+ message} />
+
);
@@ -72,11 +65,9 @@ describe('DraggableWrapper', () => {
test('it renders hover actions when the mouse is over the text of draggable wrapper', () => {
const wrapper = mount(
-
-
- message} />
-
-
+
+ message} />
+
);
@@ -92,11 +83,9 @@ describe('DraggableWrapper', () => {
test('it applies text truncation styling when truncate IS specified (implicit: and the user is not dragging)', () => {
const wrapper = mount(
-
-
- message} truncate />
-
-
+
+ message} truncate />
+
);
@@ -108,11 +97,9 @@ describe('DraggableWrapper', () => {
test('it does NOT apply text truncation styling when truncate is NOT specified', () => {
const wrapper = mount(
-
-
- message} />
-
-
+
+ message} />
+
);
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx
index b53da42da55f8..8aa926a36988b 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.test.tsx
@@ -8,14 +8,13 @@ import { mount, ReactWrapper } from 'enzyme';
import React from 'react';
import { coreMock } from '../../../../../../../src/core/public/mocks';
-import { useWithSource } from '../../containers/source';
import { mockBrowserFields } from '../../containers/source/mock';
import '../../mock/match_media';
import { useKibana } from '../../lib/kibana';
import { TestProviders } from '../../mock';
import { FilterManager } from '../../../../../../../src/plugins/data/public';
import { useAddToTimeline } from '../../hooks/use_add_to_timeline';
-
+import { useSourcererScope } from '../../containers/sourcerer';
import { DraggableWrapperHoverContent } from './draggable_wrapper_hover_content';
import {
ManageGlobalTimeline,
@@ -26,12 +25,12 @@ import { TimelineId } from '../../../../common/types/timeline';
jest.mock('../link_to');
jest.mock('../../lib/kibana');
-jest.mock('../../containers/source', () => {
- const original = jest.requireActual('../../containers/source');
+jest.mock('../../containers/sourcerer', () => {
+ const original = jest.requireActual('../../containers/sourcerer');
return {
...original,
- useWithSource: jest.fn(),
+ useSourcererScope: jest.fn(),
};
});
@@ -79,8 +78,10 @@ describe('DraggableWrapperHoverContent', () => {
beforeAll(() => {
// our mock implementation of the useAddToTimeline hook returns a mock startDragToTimeline function:
(useAddToTimeline as jest.Mock).mockReturnValue(jest.fn());
- (useWithSource as jest.Mock).mockReturnValue({
+ (useSourcererScope as jest.Mock).mockReturnValue({
browserFields: mockBrowserFields,
+ selectedPatterns: [],
+ indexPattern: {},
});
});
@@ -203,7 +204,7 @@ describe('DraggableWrapperHoverContent', () => {
wrapper = mount(
);
@@ -311,7 +312,7 @@ describe('DraggableWrapperHoverContent', () => {
{...{
...defaultProps,
onFilterAdded,
- timelineId: 'not-active-timeline',
+ timelineId: TimelineId.test,
value: '',
}}
/>
@@ -606,9 +607,7 @@ describe('DraggableWrapperHoverContent', () => {
test('filter manager, not active timeline', () => {
mount(
-
+
);
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx
index a951bfa98d64b..8c68551ddd981 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/draggable_wrapper_hover_content.tsx
@@ -8,7 +8,7 @@ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
import React, { useCallback, useMemo, useState, useEffect } from 'react';
import { DraggableId } from 'react-beautiful-dnd';
-import { getAllFieldsByName, useWithSource } from '../../containers/source';
+import { getAllFieldsByName } from '../../containers/source';
import { useAddToTimeline } from '../../hooks/use_add_to_timeline';
import { WithCopyToClipboard } from '../../lib/clipboard/with_copy_to_clipboard';
import { useKibana } from '../../lib/kibana';
@@ -20,6 +20,8 @@ import * as i18n from './translations';
import { useManageTimeline } from '../../../timelines/components/manage_timeline';
import { TimelineId } from '../../../../common/types/timeline';
import { SELECTOR_TIMELINE_BODY_CLASS_NAME } from '../../../timelines/components/timeline/styles';
+import { SourcererScopeName } from '../../store/sourcerer/model';
+import { useSourcererScope } from '../../containers/sourcerer';
interface Props {
closePopOver?: () => void;
@@ -49,7 +51,7 @@ const DraggableWrapperHoverContentComponent: React.FC = ({
const filterManagerBackup = useMemo(() => kibana.services.data.query.filterManager, [
kibana.services.data.query.filterManager,
]);
- const { getManageTimelineById, getTimelineFilterManager } = useManageTimeline();
+ const { getTimelineFilterManager } = useManageTimeline();
const filterManager = useMemo(
() =>
@@ -65,13 +67,16 @@ const DraggableWrapperHoverContentComponent: React.FC = ({
// this component is rendered in the context of the active timeline. This
// behavior enables the 'All events' view by appending the alerts index
// to the index pattern.
- const { indexToAdd } = useMemo(
- () =>
- timelineId === TimelineId.active
- ? getManageTimelineById(TimelineId.active)
- : { indexToAdd: null },
- [getManageTimelineById, timelineId]
- );
+ const activeScope: SourcererScopeName =
+ timelineId === TimelineId.active
+ ? SourcererScopeName.timeline
+ : timelineId != null &&
+ [TimelineId.detectionsPage, TimelineId.detectionsRulesDetailsPage].includes(
+ timelineId as TimelineId
+ )
+ ? SourcererScopeName.detections
+ : SourcererScopeName.default;
+ const { browserFields, indexPattern, selectedPatterns } = useSourcererScope(activeScope);
const handleStartDragToTimeline = useCallback(() => {
startDragToTimeline();
@@ -121,8 +126,6 @@ const DraggableWrapperHoverContentComponent: React.FC = ({
}
}, [goGetTimelineId, timelineId]);
- const { browserFields, indexPattern } = useWithSource('default', indexToAdd);
-
return (
<>
{!showTopN && value != null && (
@@ -187,7 +190,7 @@ const DraggableWrapperHoverContentComponent: React.FC = ({
browserFields={browserFields}
field={field}
indexPattern={indexPattern}
- indexToAdd={indexToAdd}
+ indexNames={selectedPatterns}
onFilterAdded={onFilterAdded}
timelineId={timelineId ?? undefined}
toggleTopN={toggleTopN}
diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.test.tsx b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.test.tsx
index bd2f01721290f..14d1c37efb8cf 100644
--- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/droppable_wrapper.test.tsx
@@ -6,9 +6,8 @@
import { shallow } from 'enzyme';
import React from 'react';
-import { MockedProvider } from 'react-apollo/test-utils';
-import { mockBrowserFields, mocksSource } from '../../containers/source/mock';
+import { mockBrowserFields } from '../../containers/source/mock';
import { TestProviders } from '../../mock';
import { DragDropContextWrapper } from './drag_drop_context_wrapper';
@@ -24,11 +23,9 @@ describe('DroppableWrapper', () => {
const wrapper = shallow(
-
-
- {message}
-
-
+
+ {message}
+
);
@@ -40,11 +37,9 @@ describe('DroppableWrapper', () => {
const wrapper = mount(
-
-
- {message}
-
-
+
+ {message}
+
);
@@ -56,13 +51,11 @@ describe('DroppableWrapper', () => {
const wrapper = mount(
-
-
- null} droppableId="testing">
- {message}
-
-
-
+
+ null} droppableId="testing">
+ {message}
+
+
);
@@ -72,14 +65,12 @@ describe('DroppableWrapper', () => {
test('it renders the render prop contents when a render prop is provided', () => {
const wrapper = mount(
-
-
- {`isDraggingOver is: ${isDraggingOver}`}
}
- droppableId="testing"
- />
-
-
+
+ {`isDraggingOver is: ${isDraggingOver}`}
}
+ droppableId="testing"
+ />
+
);
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
index 037655f594241..aac1f4f2687eb 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.test.tsx
@@ -8,15 +8,13 @@ import React from 'react';
import useResizeObserver from 'use-resize-observer/polyfilled';
import '../../mock/match_media';
-import { mockIndexPattern, TestProviders } from '../../mock';
-// we don't have the types for waitFor just yet, so using "as waitFor" until when we do
-import { wait as waitFor } from '@testing-library/react';
+import { mockIndexNames, mockIndexPattern, TestProviders } from '../../mock';
import { mockEventViewerResponse } from './mock';
import { StatefulEventsViewer } from '.';
import { EventsViewer } from './events_viewer';
import { defaultHeaders } from './default_headers';
-import { useFetchIndexPatterns } from '../../../detections/containers/detection_engine/rules/fetch_index_patterns';
+import { useSourcererScope } from '../../containers/sourcerer';
import { mockBrowserFields, mockDocValueFields } from '../../containers/source/mock';
import { eventsDefaultModel } from './default_model';
import { useMountAppended } from '../../utils/use_mount_appended';
@@ -25,6 +23,7 @@ import { TimelineId } from '../../../../common/types/timeline';
import { KqlMode } from '../../../timelines/store/timeline/model';
import { SortDirection } from '../../../timelines/components/timeline/body/sort';
import { AlertsTableFilterGroup } from '../../../detections/components/alerts_table/alerts_filter_group';
+import { SourcererScopeName } from '../../store/sourcerer/model';
import { useTimelineEvents } from '../../../timelines/containers';
jest.mock('../../../timelines/containers', () => ({
@@ -33,8 +32,8 @@ jest.mock('../../../timelines/containers', () => ({
jest.mock('../../components/url_state/normalize_time_range.ts');
-const mockUseFetchIndexPatterns: jest.Mock = useFetchIndexPatterns as jest.Mock;
-jest.mock('../../../detections/containers/detection_engine/rules/fetch_index_patterns');
+const mockUseSourcererScope: jest.Mock = useSourcererScope as jest.Mock;
+jest.mock('../../containers/sourcerer');
const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
jest.mock('use-resize-observer/polyfilled');
@@ -45,9 +44,10 @@ const to = '2019-08-27T22:10:56.794Z';
const defaultMocks = {
browserFields: mockBrowserFields,
- indexPatterns: mockIndexPattern,
docValueFields: mockDocValueFields,
- isLoading: false,
+ indexPattern: mockIndexPattern,
+ loading: false,
+ selectedPatterns: mockIndexNames,
};
const utilityBar = (refetch: inputsModel.Refetch, totalCount: number) => (
@@ -63,6 +63,7 @@ const eventsViewerDefaultProps = {
end: to,
filters: [],
id: TimelineId.detectionsPage,
+ indexNames: mockIndexNames,
indexPattern: mockIndexPattern,
isLive: false,
isLoadingIndexPattern: false,
@@ -79,6 +80,7 @@ const eventsViewerDefaultProps = {
columnId: 'foo',
sortDirection: 'none' as SortDirection,
},
+ scopeId: SourcererScopeName.timeline,
toggleColumn: jest.fn(),
utilityBar,
};
@@ -86,154 +88,57 @@ const eventsViewerDefaultProps = {
describe('EventsViewer', () => {
const mount = useMountAppended();
+ let testProps = {
+ defaultModel: eventsDefaultModel,
+ end: to,
+ id: 'test-stateful-events-viewer',
+ start: from,
+ scopeId: SourcererScopeName.timeline,
+ };
+
beforeEach(() => {
(useTimelineEvents as jest.Mock).mockReturnValue([false, mockEventViewerResponse]);
- mockUseFetchIndexPatterns.mockImplementation(() => [{ ...defaultMocks }]);
});
-
- test('it renders the "Showing..." subtitle with the expected event count', async () => {
- const wrapper = mount(
-
-
-
- );
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().text()).toEqual(
- 'Showing: 12 events'
- );
- });
+ beforeAll(() => {
+ mockUseSourcererScope.mockImplementation(() => defaultMocks);
});
-
- test('it does NOT render fetch index pattern is loading', async () => {
- mockUseFetchIndexPatterns.mockImplementation(() => [{ ...defaultMocks, isLoading: true }]);
-
- const wrapper = mount(
-
-
-
- );
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe(
- false
+ describe('rendering', () => {
+ test('it renders the "Showing..." subtitle with the expected event count', () => {
+ const wrapper = mount(
+
+
+
);
- });
- });
-
- test('it does NOT render when start is empty', async () => {
- mockUseFetchIndexPatterns.mockImplementation(() => [{ ...defaultMocks, isLoading: true }]);
-
- const wrapper = mount(
-
-
-
- );
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe(
- false
+ expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().text()).toEqual(
+ 'Showing: 12 events'
);
});
- });
- test('it does NOT render when end is empty', async () => {
- mockUseFetchIndexPatterns.mockImplementation(() => [{ ...defaultMocks, isLoading: true }]);
-
- const wrapper = mount(
-
-
-
- );
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe(
- false
+ test('it renders the Fields Browser as a settings gear', () => {
+ const wrapper = mount(
+
+
+
);
- });
- });
-
- test('it renders the Fields Browser as a settings gear', async () => {
- const wrapper = mount(
-
-
-
- );
-
- await waitFor(() => {
- wrapper.update();
-
expect(wrapper.find(`[data-test-subj="show-field-browser"]`).first().exists()).toBe(true);
});
- });
-
- test('it renders the footer containing the Load More button', async () => {
- const wrapper = mount(
-
-
-
- );
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="timeline-pagination"]`).first().exists()).toBe(true);
- });
- });
-
- defaultHeaders.forEach((header) => {
- test(`it renders the ${header.id} default EventsViewer column header`, async () => {
+ // TO DO sourcerer @X
+ test('it renders the footer containing the pagination', () => {
const wrapper = mount(
-
+
);
+ expect(wrapper.find(`[data-test-subj="timeline-pagination"]`).first().exists()).toBe(true);
+ });
- await waitFor(() => {
- wrapper.update();
+ defaultHeaders.forEach((header) => {
+ test(`it renders the ${header.id} default EventsViewer column header`, () => {
+ const wrapper = mount(
+
+
+
+ );
defaultHeaders.forEach((h) =>
expect(wrapper.find(`[data-test-subj="header-text-${header.id}"]`).first().exists()).toBe(
@@ -242,10 +147,58 @@ describe('EventsViewer', () => {
);
});
});
+ describe('loading', () => {
+ beforeAll(() => {
+ mockUseSourcererScope.mockImplementation(() => ({ ...defaultMocks, loading: true }));
+ });
+ test('it does NOT render fetch index pattern is loading', () => {
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe(
+ false
+ );
+ });
+
+ test('it does NOT render when start is empty', () => {
+ testProps = {
+ ...testProps,
+ start: '',
+ };
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe(
+ false
+ );
+ });
+
+ test('it does NOT render when end is empty', () => {
+ testProps = {
+ ...testProps,
+ end: '',
+ };
+ const wrapper = mount(
+
+
+
+ );
+
+ expect(wrapper.find(`[data-test-subj="header-section-subtitle"]`).first().exists()).toBe(
+ false
+ );
+ });
+ });
});
describe('headerFilterGroup', () => {
- test('it renders the provided headerFilterGroup', async () => {
+ test('it renders the provided headerFilterGroup', () => {
const wrapper = mount(
{
/>
);
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="alerts-table-filter-group"]`).exists()).toBe(true);
- });
+ expect(wrapper.find(`[data-test-subj="alerts-table-filter-group"]`).exists()).toBe(true);
});
- test('it has a visible HeaderFilterGroupWrapper when Resolver is NOT showing, because graphEventId is undefined', async () => {
+ test('it has a visible HeaderFilterGroupWrapper when Resolver is NOT showing, because graphEventId is undefined', () => {
const wrapper = mount(
{
/>
);
-
- await waitFor(() => {
- wrapper.update();
-
- expect(
- wrapper.find(`[data-test-subj="header-filter-group-wrapper"]`).first()
- ).not.toHaveStyleRule('visibility', 'hidden');
- });
+ expect(
+ wrapper.find(`[data-test-subj="header-filter-group-wrapper"]`).first()
+ ).not.toHaveStyleRule('visibility', 'hidden');
});
- test('it has a visible HeaderFilterGroupWrapper when Resolver is NOT showing, because graphEventId is an empty string', async () => {
+ test('it has a visible HeaderFilterGroupWrapper when Resolver is NOT showing, because graphEventId is an empty string', () => {
const wrapper = mount(
{
/>
);
-
- await waitFor(() => {
- wrapper.update();
-
- expect(
- wrapper.find(`[data-test-subj="header-filter-group-wrapper"]`).first()
- ).not.toHaveStyleRule('visibility', 'hidden');
- });
+ expect(
+ wrapper.find(`[data-test-subj="header-filter-group-wrapper"]`).first()
+ ).not.toHaveStyleRule('visibility', 'hidden');
});
- test('it does NOT have a visible HeaderFilterGroupWrapper when Resolver is showing, because graphEventId is a valid id', async () => {
+ test('it does NOT have a visible HeaderFilterGroupWrapper when Resolver is showing, because graphEventId is a valid id', () => {
const wrapper = mount(
{
/>
);
-
- await waitFor(() => {
- wrapper.update();
-
- expect(
- wrapper.find(`[data-test-subj="header-filter-group-wrapper"]`).first()
- ).toHaveStyleRule('visibility', 'hidden');
- });
+ expect(
+ wrapper.find(`[data-test-subj="header-filter-group-wrapper"]`).first()
+ ).toHaveStyleRule('visibility', 'hidden');
});
- test('it (still) renders an invisible headerFilterGroup (to maintain state while hidden) when Resolver is showing, because graphEventId is a valid id', async () => {
+ test('it (still) renders an invisible headerFilterGroup (to maintain state while hidden) when Resolver is showing, because graphEventId is a valid id', () => {
const wrapper = mount(
{
/>
);
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="alerts-table-filter-group"]`).exists()).toBe(true);
- });
+ expect(wrapper.find(`[data-test-subj="alerts-table-filter-group"]`).exists()).toBe(true);
});
});
describe('utilityBar', () => {
- test('it renders the provided utilityBar when Resolver is NOT showing, because graphEventId is undefined', async () => {
+ test('it renders the provided utilityBar when Resolver is NOT showing, because graphEventId is undefined', () => {
const wrapper = mount(
);
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="mock-utility-bar"]`).exists()).toBe(true);
- });
+ expect(wrapper.find(`[data-test-subj="mock-utility-bar"]`).exists()).toBe(true);
});
- test('it renders the provided utilityBar when Resolver is NOT showing, because graphEventId is an empty string', async () => {
+ test('it renders the provided utilityBar when Resolver is NOT showing, because graphEventId is an empty string', () => {
const wrapper = mount(
);
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="mock-utility-bar"]`).exists()).toBe(true);
- });
+ expect(wrapper.find(`[data-test-subj="mock-utility-bar"]`).exists()).toBe(true);
});
- test('it does NOT render the provided utilityBar when Resolver is showing, because graphEventId is a valid id', async () => {
+ test('it does NOT render the provided utilityBar when Resolver is showing, because graphEventId is a valid id', () => {
const wrapper = mount(
);
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="mock-utility-bar"]`).exists()).toBe(false);
- });
+ expect(wrapper.find(`[data-test-subj="mock-utility-bar"]`).exists()).toBe(false);
});
});
describe('header inspect button', () => {
- test('it renders the inspect button when Resolver is NOT showing, because graphEventId is undefined', async () => {
+ test('it renders the inspect button when Resolver is NOT showing, because graphEventId is undefined', () => {
const wrapper = mount(
);
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBe(true);
- });
+ expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBe(true);
});
- test('it renders the inspect button when Resolver is NOT showing, because graphEventId is an empty string', async () => {
+ test('it renders the inspect button when Resolver is NOT showing, because graphEventId is an empty string', () => {
const wrapper = mount(
);
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBe(true);
- });
+ expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBe(true);
});
- test('it does NOT render the inspect button when Resolver is showing, because graphEventId is a valid id', async () => {
+ test('it does NOT render the inspect button when Resolver is showing, because graphEventId is a valid id', () => {
const wrapper = mount(
);
-
- await waitFor(() => {
- wrapper.update();
-
- expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBe(false);
- });
+ expect(wrapper.find(`[data-test-subj="inspect-icon-button"]`).exists()).toBe(false);
});
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx
index 2998bd031d674..2c8c8136a4733 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/events_viewer.tsx
@@ -95,6 +95,7 @@ interface Props {
headerFilterGroup?: React.ReactNode;
height?: number;
id: string;
+ indexNames: string[];
indexPattern: IIndexPattern;
isLive: boolean;
isLoadingIndexPattern: boolean;
@@ -121,6 +122,7 @@ const EventsViewerComponent: React.FC = ({
filters,
headerFilterGroup,
id,
+ indexNames,
indexPattern,
isLive,
isLoadingIndexPattern,
@@ -213,7 +215,7 @@ const EventsViewerComponent: React.FC = ({
fields,
filterQuery: combinedQueries!.filterQuery,
id,
- indexPattern,
+ indexNames,
limit: itemsPerPage,
sort: sortField,
startDate: start,
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx
index 8c61281422c2a..9a3c0fa1cad2e 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.test.tsx
@@ -10,14 +10,13 @@ import useResizeObserver from 'use-resize-observer/polyfilled';
import '../../mock/match_media';
// we don't have the types for waitFor just yet, so using "as waitFor" until when we do
import { wait as waitFor } from '@testing-library/react';
-import { mockIndexPattern, TestProviders } from '../../mock';
+import { TestProviders } from '../../mock';
import { useMountAppended } from '../../utils/use_mount_appended';
import { mockEventViewerResponse } from './mock';
import { StatefulEventsViewer } from '.';
-import { useFetchIndexPatterns } from '../../../detections/containers/detection_engine/rules/fetch_index_patterns';
-import { mockBrowserFields } from '../../containers/source/mock';
import { eventsDefaultModel } from './default_model';
+import { SourcererScopeName } from '../../store/sourcerer/model';
import { useTimelineEvents } from '../../../timelines/containers';
jest.mock('../../../timelines/containers', () => ({
@@ -26,15 +25,6 @@ jest.mock('../../../timelines/containers', () => ({
jest.mock('../../components/url_state/normalize_time_range.ts');
-const mockUseFetchIndexPatterns: jest.Mock = useFetchIndexPatterns as jest.Mock;
-jest.mock('../../../detections/containers/detection_engine/rules/fetch_index_patterns');
-mockUseFetchIndexPatterns.mockImplementation(() => [
- {
- browserFields: mockBrowserFields,
- indexPatterns: mockIndexPattern,
- },
-]);
-
const mockUseResizeObserver: jest.Mock = useResizeObserver as jest.Mock;
jest.mock('use-resize-observer/polyfilled');
mockUseResizeObserver.mockImplementation(() => ({}));
@@ -42,6 +32,14 @@ mockUseResizeObserver.mockImplementation(() => ({}));
const from = '2019-08-27T22:10:56.794Z';
const to = '2019-08-26T22:10:56.791Z';
+const testProps = {
+ defaultModel: eventsDefaultModel,
+ end: to,
+ indexNames: [],
+ id: 'test-stateful-events-viewer',
+ scopeId: SourcererScopeName.default,
+ start: from,
+};
describe('StatefulEventsViewer', () => {
const mount = useMountAppended();
@@ -50,12 +48,7 @@ describe('StatefulEventsViewer', () => {
test('it renders the events viewer', async () => {
const wrapper = mount(
-
+
);
@@ -70,12 +63,7 @@ describe('StatefulEventsViewer', () => {
test('it renders InspectButtonContainer', async () => {
const wrapper = mount(
-
+
);
diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx
index e4520dab4626a..cd43c7e493065 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/index.tsx
@@ -9,7 +9,6 @@ import { connect, ConnectedProps } from 'react-redux';
import deepEqual from 'fast-deep-equal';
import styled from 'styled-components';
-import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
import { inputsModel, inputsSelectors, State } from '../../store';
import { inputsActions } from '../../store/actions';
import { timelineSelectors, timelineActions } from '../../../timelines/store/timeline';
@@ -20,11 +19,11 @@ import {
} from '../../../timelines/store/timeline/model';
import { OnChangeItemsPerPage } from '../../../timelines/components/timeline/events';
import { Filter } from '../../../../../../../src/plugins/data/public';
-import { useUiSetting } from '../../lib/kibana';
import { EventsViewer } from './events_viewer';
-import { useFetchIndexPatterns } from '../../../detections/containers/detection_engine/rules/fetch_index_patterns';
import { InspectButtonContainer } from '../inspect';
import { useFullScreen } from '../../containers/use_full_screen';
+import { SourcererScopeName } from '../../store/sourcerer/model';
+import { useSourcererScope } from '../../containers/sourcerer';
const DEFAULT_EVENTS_VIEWER_HEIGHT = 652;
@@ -35,10 +34,10 @@ const FullScreenContainer = styled.div<{ $isFullScreen: boolean }>`
`;
export interface OwnProps {
- defaultIndices?: string[];
defaultModel: SubsetTimelineModel;
end: string;
id: string;
+ scopeId: SourcererScopeName;
start: string;
headerFilterGroup?: React.ReactNode;
pageFilters?: Filter[];
@@ -52,7 +51,6 @@ const StatefulEventsViewerComponent: React.FC = ({
columns,
dataProviders,
deletedEventIds,
- defaultIndices,
deleteEventQuery,
end,
excludedRowRendererIds,
@@ -67,6 +65,7 @@ const StatefulEventsViewerComponent: React.FC = ({
query,
removeColumn,
start,
+ scopeId,
showCheckboxes,
sort,
updateItemsPerPage,
@@ -75,13 +74,13 @@ const StatefulEventsViewerComponent: React.FC = ({
// If truthy, the graph viewer (Resolver) is showing
graphEventId,
}) => {
- const [
- { docValueFields, browserFields, indexPatterns, isLoading: isLoadingIndexPattern },
- ] = useFetchIndexPatterns(
- defaultIndices ?? useUiSetting(DEFAULT_INDEX_KEY),
- 'events_viewer'
- );
-
+ const {
+ browserFields,
+ docValueFields,
+ indexPattern,
+ selectedPatterns,
+ loading: isLoadingIndexPattern,
+ } = useSourcererScope(scopeId);
const { globalFullScreen } = useFullScreen();
useEffect(() => {
@@ -90,6 +89,7 @@ const StatefulEventsViewerComponent: React.FC = ({
id,
columns,
excludedRowRendererIds,
+ indexNames: selectedPatterns,
sort,
itemsPerPage,
showCheckboxes,
@@ -144,7 +144,8 @@ const StatefulEventsViewerComponent: React.FC = ({
isLoadingIndexPattern={isLoadingIndexPattern}
filters={globalFilters}
headerFilterGroup={headerFilterGroup}
- indexPattern={indexPatterns}
+ indexNames={selectedPatterns}
+ indexPattern={indexPattern}
isLive={isLive}
itemsPerPage={itemsPerPage!}
itemsPerPageOptions={itemsPerPageOptions!}
@@ -222,8 +223,8 @@ export const StatefulEventsViewer = connector(
StatefulEventsViewerComponent,
(prevProps, nextProps) =>
prevProps.id === nextProps.id &&
+ prevProps.scopeId === nextProps.scopeId &&
deepEqual(prevProps.columns, nextProps.columns) &&
- deepEqual(prevProps.defaultIndices, nextProps.defaultIndices) &&
deepEqual(prevProps.dataProviders, nextProps.dataProviders) &&
deepEqual(prevProps.excludedRowRendererIds, nextProps.excludedRowRendererIds) &&
prevProps.deletedEventIds === nextProps.deletedEventIds &&
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx
index 691a7d99d9345..ed1c1c1cdad1f 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.test.tsx
@@ -13,7 +13,7 @@ import { act } from 'react-dom/test-utils';
import { AddExceptionModal } from './';
import { useCurrentUser } from '../../../../common/lib/kibana';
import { getExceptionListSchemaMock } from '../../../../../../lists/common/schemas/response/exception_list_schema.mock';
-import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules';
+import { useFetchIndex } from '../../../containers/source';
import { stubIndexPattern } from 'src/plugins/data/common/index_patterns/index_pattern.stub';
import { useAddOrUpdateException } from '../use_add_exception';
import { useFetchOrCreateRuleExceptionList } from '../use_fetch_or_create_rule_exception_list';
@@ -28,6 +28,7 @@ import { ExceptionListItemSchema } from '../../../../../../lists/common';
jest.mock('../../../../detections/containers/detection_engine/alerts/use_signal_index');
jest.mock('../../../../common/lib/kibana');
+jest.mock('../../../containers/source');
jest.mock('../../../../detections/containers/detection_engine/rules');
jest.mock('../use_add_exception');
jest.mock('../use_fetch_or_create_rule_exception_list');
@@ -59,9 +60,9 @@ describe('When the add exception modal is opened', () => {
loading: false,
signalIndexName: 'mock-siem-signals-index',
}));
- (useFetchIndexPatterns as jest.Mock).mockImplementation(() => [
+ (useFetchIndex as jest.Mock).mockImplementation(() => [
+ false,
{
- isLoading: false,
indexPatterns: stubIndexPattern,
},
]);
@@ -77,9 +78,9 @@ describe('When the add exception modal is opened', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
// Mocks one of the hooks as loading
- (useFetchIndexPatterns as jest.Mock).mockImplementation(() => [
+ (useFetchIndex as jest.Mock).mockImplementation(() => [
+ true,
{
- isLoading: true,
indexPatterns: stubIndexPattern,
},
]);
@@ -244,9 +245,9 @@ describe('When the add exception modal is opened', () => {
};
beforeEach(() => {
// Mocks the index patterns to contain the pre-populated endpoint fields so that the exception qualifies as bulk closable
- (useFetchIndexPatterns as jest.Mock).mockImplementation(() => [
+ (useFetchIndex as jest.Mock).mockImplementation(() => [
+ false,
{
- isLoading: false,
indexPatterns: {
...stubIndexPattern,
fields: [
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx
index 721e53732c093..e945461f53e81 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/add_exception_modal/index.tsx
@@ -50,8 +50,8 @@ import {
getMappedNonEcsValue,
} from '../helpers';
import { ErrorInfo, ErrorCallout } from '../error_callout';
-import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules';
import { ExceptionsBuilderExceptionItem } from '../types';
+import { useFetchIndex } from '../../../containers/source';
export interface AddExceptionModalBaseProps {
ruleName: string;
@@ -122,14 +122,13 @@ export const AddExceptionModal = memo(function AddExceptionModal({
const [fetchOrCreateListError, setFetchOrCreateListError] = useState(null);
const { addError, addSuccess, addWarning } = useAppToasts();
const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex();
- const [
- { isLoading: isSignalIndexPatternLoading, indexPatterns: signalIndexPatterns },
- ] = useFetchIndexPatterns(signalIndexName !== null ? [signalIndexName] : [], 'signals');
-
- const [{ isLoading: isIndexPatternLoading, indexPatterns }] = useFetchIndexPatterns(
- ruleIndices,
- 'rules'
+ const memoSignalIndexName = useMemo(() => (signalIndexName !== null ? [signalIndexName] : []), [
+ signalIndexName,
+ ]);
+ const [isSignalIndexPatternLoading, { indexPatterns: signalIndexPatterns }] = useFetchIndex(
+ memoSignalIndexName
);
+ const [isIndexPatternLoading, { indexPatterns }] = useFetchIndex(ruleIndices);
const onError = useCallback(
(error: Error): void => {
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.test.tsx
index c724e6a2c711f..d5d2091cc9bc8 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.test.tsx
@@ -12,7 +12,7 @@ import { act } from 'react-dom/test-utils';
import { EditExceptionModal } from './';
import { useCurrentUser } from '../../../../common/lib/kibana';
-import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules';
+import { useFetchIndex } from '../../../containers/source';
import {
stubIndexPattern,
stubIndexPatternWithFields,
@@ -26,6 +26,7 @@ import * as builder from '../builder';
jest.mock('../../../../common/lib/kibana');
jest.mock('../../../../detections/containers/detection_engine/rules');
jest.mock('../use_add_exception');
+jest.mock('../../../containers/source');
jest.mock('../use_fetch_or_create_rule_exception_list');
jest.mock('../../../../detections/containers/detection_engine/alerts/use_signal_index');
jest.mock('../builder');
@@ -50,9 +51,9 @@ describe('When the edit exception modal is opened', () => {
{ isLoading: false },
jest.fn(),
]);
- (useFetchIndexPatterns as jest.Mock).mockImplementation(() => [
+ (useFetchIndex as jest.Mock).mockImplementation(() => [
+ false,
{
- isLoading: false,
indexPatterns: stubIndexPatternWithFields,
},
]);
@@ -67,9 +68,9 @@ describe('When the edit exception modal is opened', () => {
describe('when the modal is loading', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
- (useFetchIndexPatterns as jest.Mock).mockImplementation(() => [
+ (useFetchIndex as jest.Mock).mockImplementation(() => [
+ true,
{
- isLoading: true,
indexPatterns: stubIndexPattern,
},
]);
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx
index 5dbf319c3299d..128686428598c 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/edit_exception_modal/index.tsx
@@ -21,7 +21,8 @@ import {
EuiText,
EuiCallOut,
} from '@elastic/eui';
-import { useFetchIndexPatterns } from '../../../../detections/containers/detection_engine/rules';
+
+import { useFetchIndex } from '../../../containers/source';
import { useSignalIndex } from '../../../../detections/containers/detection_engine/alerts/use_signal_index';
import { useRuleAsync } from '../../../../detections/containers/detection_engine/rules/use_rule_async';
import {
@@ -108,15 +109,12 @@ export const EditExceptionModal = memo(function EditExceptionModal({
>([]);
const { addError, addSuccess } = useAppToasts();
const { loading: isSignalIndexLoading, signalIndexName } = useSignalIndex();
- const [
- { isLoading: isSignalIndexPatternLoading, indexPatterns: signalIndexPatterns },
- ] = useFetchIndexPatterns(signalIndexName !== null ? [signalIndexName] : [], 'signals');
-
- const [{ isLoading: isIndexPatternLoading, indexPatterns }] = useFetchIndexPatterns(
- ruleIndices,
- 'rules'
+ const [isSignalIndexPatternLoading, { indexPatterns: signalIndexPatterns }] = useFetchIndex(
+ signalIndexName !== null ? [signalIndexName] : []
);
+ const [isIndexPatternLoading, { indexPatterns }] = useFetchIndex(ruleIndices);
+
const handleExceptionUpdateError = useCallback(
(error: Error, statusCode: number | null, message: string | null) => {
if (error.message.includes('Conflict')) {
diff --git a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx
index e05e3c2e9aeb1..5b4dd2e9728bb 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_global/index.tsx
@@ -18,7 +18,6 @@ import { getAppOverviewUrl } from '../link_to';
import { MlPopover } from '../ml_popover/ml_popover';
import { SiemNavigation } from '../navigation';
import * as i18n from './translations';
-import { useWithSource } from '../../containers/source';
import { useGetUrlSearch } from '../navigation/use_get_url_search';
import { useKibana } from '../../lib/kibana';
import { APP_ID, ADD_DATA_PATH, APP_DETECTIONS_PATH } from '../../../../common/constants';
@@ -58,11 +57,12 @@ interface HeaderGlobalProps {
hideDetectionEngine?: boolean;
}
export const HeaderGlobal = React.memo(({ hideDetectionEngine = false }) => {
- const { indicesExist } = useWithSource();
const { globalHeaderPortalNode } = useGlobalHeaderPortal();
const { globalFullScreen } = useFullScreen();
const search = useGetUrlSearch(navTabs.overview);
- const { navigateToApp } = useKibana().services.application;
+ const { application, http } = useKibana().services;
+ const { navigateToApp } = application;
+ const basePath = http.basePath.get();
const goToOverview = useCallback(
(ev) => {
ev.preventDefault();
@@ -104,7 +104,7 @@ export const HeaderGlobal = React.memo(({ hideDetectionEngine
- {indicesExist && window.location.pathname.includes(APP_DETECTIONS_PATH) && (
+ {window.location.pathname.includes(APP_DETECTIONS_PATH) && (
@@ -113,7 +113,7 @@ export const HeaderGlobal = React.memo(({ hideDetectionEngine
{i18n.BUTTON_ADD_DATA}
diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/header_page/__snapshots__/index.test.tsx.snap
index a100f5e4f93b4..a2a36b3fe1d3b 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_page/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/common/components/header_page/__snapshots__/index.test.tsx.snap
@@ -36,5 +36,8 @@ exports[`HeaderPage it renders 1`] = `
+
`;
diff --git a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx
index 62880e7510cd2..0cb721bb5382f 100644
--- a/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/header_page/index.tsx
@@ -15,6 +15,8 @@ import { Title } from './title';
import { DraggableArguments, BadgeOptions, TitleProp } from './types';
import { useFormatUrl } from '../link_to';
import { SecurityPageName } from '../../../app/types';
+import { Sourcerer } from '../sourcerer';
+import { SourcererScopeName } from '../../store/sourcerer/model';
interface HeaderProps {
border?: boolean;
@@ -72,6 +74,7 @@ export interface HeaderPageProps extends HeaderProps {
badgeOptions?: BadgeOptions;
children?: React.ReactNode;
draggableArguments?: DraggableArguments;
+ hideSourcerer?: boolean;
subtitle?: SubtitleProps['items'];
subtitle2?: SubtitleProps['items'];
title: TitleProp;
@@ -84,6 +87,7 @@ const HeaderPageComponent: React.FC = ({
border,
children,
draggableArguments,
+ hideSourcerer = false,
isLoading,
subtitle,
subtitle2,
@@ -138,6 +142,7 @@ const HeaderPageComponent: React.FC = ({
)}
+ {!hideSourcerer && }
);
};
diff --git a/x-pack/plugins/security_solution/public/common/components/last_event_time/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/last_event_time/index.test.tsx
index 9473ba67a1c4f..c2800b0705b43 100644
--- a/x-pack/plugins/security_solution/public/common/components/last_event_time/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/last_event_time/index.test.tsx
@@ -37,7 +37,7 @@ describe('Last Event Time Stat', () => {
]);
const wrapper = mount(
-
+
);
expect(wrapper.html()).toBe(
@@ -54,7 +54,7 @@ describe('Last Event Time Stat', () => {
]);
const wrapper = mount(
-
+
);
expect(wrapper.html()).toBe('Last event: 12 minutes ago');
@@ -69,7 +69,7 @@ describe('Last Event Time Stat', () => {
]);
const wrapper = mount(
-
+
);
@@ -85,7 +85,7 @@ describe('Last Event Time Stat', () => {
]);
const wrapper = mount(
-
+
);
expect(wrapper.html()).toContain(getEmptyValue());
diff --git a/x-pack/plugins/security_solution/public/common/components/last_event_time/index.tsx b/x-pack/plugins/security_solution/public/common/components/last_event_time/index.tsx
index e9e8e7a03017c..d508040f84239 100644
--- a/x-pack/plugins/security_solution/public/common/components/last_event_time/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/last_event_time/index.tsx
@@ -8,58 +8,65 @@ import { EuiIcon, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { memo } from 'react';
+import { DocValueFields } from '../../../../common/search_strategy';
import { LastEventIndexKey } from '../../../graphql/types';
import { useTimelineLastEventTime } from '../../containers/events/last_event_time';
import { getEmptyTagValue } from '../empty_value';
import { FormattedRelativePreferenceDate } from '../formatted_date';
export interface LastEventTimeProps {
+ docValueFields: DocValueFields[];
hostName?: string;
indexKey: LastEventIndexKey;
ip?: string;
+ indexNames: string[];
}
-export const LastEventTime = memo(({ hostName, indexKey, ip }) => {
- const [loading, { lastSeen, errorMessage }] = useTimelineLastEventTime({
- indexKey,
- details: {
- hostName,
- ip,
- },
- });
+export const LastEventTime = memo(
+ ({ docValueFields, hostName, indexKey, ip, indexNames }) => {
+ const [loading, { lastSeen, errorMessage }] = useTimelineLastEventTime({
+ docValueFields,
+ indexKey,
+ indexNames,
+ details: {
+ hostName,
+ ip,
+ },
+ });
+
+ if (errorMessage != null) {
+ return (
+
+
+
+ );
+ }
- if (errorMessage != null) {
return (
-
-
-
+ <>
+ {loading && }
+ {!loading && lastSeen != null && new Date(lastSeen).toString() === 'Invalid Date'
+ ? lastSeen
+ : !loading &&
+ lastSeen != null && (
+ ,
+ }}
+ />
+ )}
+ {!loading && lastSeen == null && getEmptyTagValue()}
+ >
);
}
-
- return (
- <>
- {loading && }
- {!loading && lastSeen != null && new Date(lastSeen).toString() === 'Invalid Date'
- ? lastSeen
- : !loading &&
- lastSeen != null && (
- ,
- }}
- />
- )}
- {!loading && lastSeen == null && getEmptyTagValue()}
- >
- );
-});
+);
LastEventTime.displayName = 'LastEventTime';
diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx
index 7286c6b743692..99dc8a802b33d 100644
--- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx
@@ -47,6 +47,7 @@ describe('Matrix Histogram Component', () => {
errorMessage: 'error',
histogramType: MatrixHistogramType.alerts,
id: 'mockId',
+ indexNames: [],
isInspected: false,
isPtrIncluded: false,
setQuery: jest.fn(),
diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx
index 485ca4c93133a..e7d7e60a3c408 100644
--- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx
@@ -37,7 +37,6 @@ export type MatrixHistogramComponentProps = MatrixHistogramProps &
hideHistogramIfEmpty?: boolean;
histogramType: MatrixHistogramType;
id: string;
- indexToAdd?: string[] | null;
legendPosition?: Position;
mapping?: MatrixHistogramMappingTypes;
showSpacer?: boolean;
@@ -72,7 +71,7 @@ export const MatrixHistogramComponent: React.FC =
histogramType,
hideHistogramIfEmpty = false,
id,
- indexToAdd,
+ indexNames,
legendPosition,
mapping,
panelHeight = DEFAULT_PANEL_HEIGHT,
@@ -136,7 +135,7 @@ export const MatrixHistogramComponent: React.FC =
errorMessage,
filterQuery,
histogramType,
- indexToAdd,
+ indexNames,
startDate,
stackByField: selectedStackByOption.value,
});
diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts
index fc1df4d8ca85f..9a892110bde43 100644
--- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts
+++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/types.ts
@@ -59,6 +59,7 @@ interface MatrixHistogramBasicProps {
export interface MatrixHistogramQueryProps {
endDate: string;
errorMessage: string;
+ indexNames: string[];
filterQuery?: ESQuery | string | undefined;
setAbsoluteRangeDatePicker?: ActionCreator<{
id: InputsModelId;
@@ -68,7 +69,6 @@ export interface MatrixHistogramQueryProps {
setAbsoluteRangeDatePickerTarget?: InputsModelId;
stackByField: string;
startDate: string;
- indexToAdd?: string[] | null;
histogramType: MatrixHistogramType;
}
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts
index 89aa77106933e..da5099f61e9b2 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts
@@ -105,6 +105,7 @@ const getMockObject = (
},
},
},
+ sourcerer: {},
});
const getUrlForAppMock = (appId: string, options?: { path?: string; absolute?: boolean }) =>
@@ -130,7 +131,7 @@ describe('Navigation Breadcrumbs', () => {
},
{
href:
- "securitySolution:hosts?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',
},
{
@@ -150,7 +151,7 @@ describe('Navigation Breadcrumbs', () => {
{
text: 'Network',
href:
- "securitySolution:network?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',
@@ -169,7 +170,7 @@ describe('Navigation Breadcrumbs', () => {
{
text: 'Timelines',
href:
- "securitySolution:timelines?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)))",
},
]);
});
@@ -184,12 +185,12 @@ describe('Navigation Breadcrumbs', () => {
{
text: 'Hosts',
href:
- "securitySolution:hosts?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?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: '' },
]);
@@ -205,11 +206,11 @@ describe('Navigation Breadcrumbs', () => {
{
text: 'Network',
href:
- "securitySolution:network?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?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: '' },
]);
@@ -225,11 +226,11 @@ describe('Navigation Breadcrumbs', () => {
{
text: 'Network',
href:
- "securitySolution:network?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?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: '' },
]);
@@ -245,7 +246,7 @@ describe('Navigation Breadcrumbs', () => {
{
text: 'Detections',
href:
- "securitySolution:detections?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: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)))",
},
]);
});
@@ -259,7 +260,7 @@ describe('Navigation Breadcrumbs', () => {
{
text: 'Cases',
href:
- "securitySolution:case?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)))",
},
]);
});
@@ -280,11 +281,11 @@ describe('Navigation Breadcrumbs', () => {
{
text: 'Cases',
href:
- "securitySolution:case?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}?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)))`,
},
]);
});
@@ -311,12 +312,12 @@ describe('Navigation Breadcrumbs', () => {
{
text: 'Hosts',
href:
- "securitySolution:hosts?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?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: '' },
]);
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts b/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts
index 8f5a3ac63fa1a..ed71f55fd0161 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/helpers.ts
@@ -19,12 +19,19 @@ import {
import { Query, Filter } from '../../../../../../../src/plugins/data/public';
import { SearchNavTab } from './types';
+import { SourcererScopePatterns } from '../../store/sourcerer/model';
export const getSearch = (tab: SearchNavTab, urlState: UrlState): string => {
if (tab && tab.urlKey != null && URL_STATE_KEYS[tab.urlKey] != null) {
return URL_STATE_KEYS[tab.urlKey].reduce(
(myLocation: Location, urlKey: KeyUrlState) => {
- let urlStateToReplace: UrlInputsModel | Query | Filter[] | TimelineUrl | string = '';
+ let urlStateToReplace:
+ | Filter[]
+ | Query
+ | SourcererScopePatterns
+ | TimelineUrl
+ | UrlInputsModel
+ | string = '';
if (urlKey === CONSTANTS.appQuery && urlState.query != null) {
if (urlState.query.query === '') {
@@ -40,6 +47,8 @@ export const getSearch = (tab: SearchNavTab, urlState: UrlState): string => {
}
} else if (urlKey === CONSTANTS.timerange) {
urlStateToReplace = urlState[CONSTANTS.timerange];
+ } else if (urlKey === CONSTANTS.sourcerer) {
+ urlStateToReplace = urlState[CONSTANTS.sourcerer];
} else if (urlKey === CONSTANTS.timeline && urlState[CONSTANTS.timeline] != null) {
const timeline = urlState[CONSTANTS.timeline];
if (timeline.id === '') {
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx
index 16cb19f5a0c14..102ed7851e57d 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.test.tsx
@@ -78,6 +78,7 @@ describe('SIEM Navigation', () => {
},
[CONSTANTS.appQuery]: { query: '', language: 'kuery' },
[CONSTANTS.filters]: [],
+ [CONSTANTS.sourcerer]: {},
[CONSTANTS.timeline]: {
id: '',
isOpen: false,
@@ -145,6 +146,7 @@ describe('SIEM Navigation', () => {
pageName: 'hosts',
pathName: '/',
search: '',
+ sourcerer: {},
state: undefined,
tabName: 'authentications',
query: { query: '', language: 'kuery' },
@@ -252,6 +254,7 @@ describe('SIEM Navigation', () => {
query: { language: 'kuery', query: '' },
savedQuery: undefined,
search: '',
+ sourcerer: {},
state: undefined,
tabName: 'authentications',
timeline: { id: '', isOpen: false, graphEventId: '' },
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx
index 5ee35e7da0f3e..b149488ff38a7 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/index.tsx
@@ -40,19 +40,20 @@ export const SiemNavigationComponent: React.FC<
if (pathName || pageName) {
setBreadcrumbs(
{
- query: urlState.query,
detailName,
filters: urlState.filters,
+ flowTarget,
navTabs,
pageName,
pathName,
+ query: urlState.query,
savedQuery: urlState.savedQuery,
search,
+ sourcerer: urlState.sourcerer,
+ state,
tabName,
- flowTarget,
- timerange: urlState.timerange,
timeline: urlState.timeline,
- state,
+ timerange: urlState.timerange,
},
chrome,
getUrlForApp
@@ -69,6 +70,7 @@ export const SiemNavigationComponent: React.FC<
navTabs={navTabs}
pageName={pageName}
pathName={pathName}
+ sourcerer={urlState.sourcerer}
savedQuery={urlState.savedQuery}
tabName={tabName}
timeline={urlState.timeline}
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx
index b25cf3779801b..5c69edbabdc66 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx
@@ -68,6 +68,7 @@ describe('Tab Navigation', () => {
},
[CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' },
[CONSTANTS.filters]: [],
+ [CONSTANTS.sourcerer]: {},
[CONSTANTS.timeline]: {
id: '',
isOpen: false,
@@ -126,6 +127,7 @@ describe('Tab Navigation', () => {
},
[CONSTANTS.appQuery]: { query: 'host.name:"siem-es"', language: 'kuery' },
[CONSTANTS.filters]: [],
+ [CONSTANTS.sourcerer]: {},
[CONSTANTS.timeline]: {
id: '',
isOpen: false,
diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx
index 217ad0e58570f..3eb66b5591b85 100644
--- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.tsx
@@ -94,10 +94,17 @@ export const TabNavigationComponent = (props: TabNavigationProps) => {
() =>
Object.values(navTabs).map((tab) => {
const isSelected = selectedTabId === tab.id;
- const { query, filters, savedQuery, timerange, timeline } = props;
- const search = getSearch(tab, { query, filters, savedQuery, timerange, timeline });
+ const { filters, query, savedQuery, sourcerer, timeline, timerange } = props;
+ const search = getSearch(tab, {
+ filters,
+ query,
+ savedQuery,
+ sourcerer,
+ timeline,
+ timerange,
+ });
const hrefWithSearch =
- tab.href + getSearch(tab, { query, filters, savedQuery, timerange, timeline });
+ tab.href + getSearch(tab, { filters, query, savedQuery, sourcerer, timeline, timerange });
return (
{
- const original = jest.requireActual('../../containers/sourcerer');
+const mockDispatch = jest.fn();
+jest.mock('react-redux', () => {
+ const original = jest.requireActual('react-redux');
return {
...original,
- useManageSource: () => mockManageSource,
+ useDispatch: () => mockDispatch,
};
});
const mockOptions = [
- { label: 'auditbeat-*', key: 'auditbeat-*-0', value: 'auditbeat-*', checked: 'on' },
- { label: 'endgame-*', key: 'endgame-*-1', value: 'endgame-*', checked: 'on' },
- { label: 'filebeat-*', key: 'filebeat-*-2', value: 'filebeat-*', checked: 'on' },
- { label: 'logs-*', key: 'logs-*-3', value: 'logs-*', checked: 'on' },
- { label: 'packetbeat-*', key: 'packetbeat-*-4', value: 'packetbeat-*', checked: undefined },
- { label: 'winlogbeat-*', key: 'winlogbeat-*-5', value: 'winlogbeat-*', checked: 'on' },
- {
- label: 'apm-*-transaction*',
- key: 'apm-*-transaction*-0',
- value: 'apm-*-transaction*',
- disabled: true,
- checked: undefined,
- },
- {
- label: 'blobbeat-*',
- key: 'blobbeat-*-1',
- value: 'blobbeat-*',
- disabled: true,
- checked: undefined,
- },
+ { label: 'apm-*-transaction*', value: 'apm-*-transaction*' },
+ { label: 'auditbeat-*', value: 'auditbeat-*' },
+ { label: 'endgame-*', value: 'endgame-*' },
+ { label: 'filebeat-*', value: 'filebeat-*' },
+ { label: 'logs-*', value: 'logs-*' },
+ { label: 'packetbeat-*', value: 'packetbeat-*' },
+ { label: 'winlogbeat-*', value: 'winlogbeat-*' },
];
+const defaultProps = {
+ scope: sourcererModel.SourcererScopeName.default,
+};
describe('Sourcerer component', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ jest.restoreAllMocks();
+ });
+ const state: State = mockGlobalState;
+ const { storage } = createSecuritySolutionStorageMock();
+ let store = createStore(
+ state,
+ SUB_PLUGINS_REDUCER,
+ apolloClientObservable,
+ kibanaObservable,
+ storage
+ );
+
+ beforeEach(() => {
+ store = createStore(
+ state,
+ SUB_PLUGINS_REDUCER,
+ apolloClientObservable,
+ kibanaObservable,
+ storage
+ );
+ });
+
// Using props callback instead of simulating clicks,
// because EuiSelectable uses a virtualized list, which isn't easily testable via test subjects
- it('Mounts with correct options selected and disabled', () => {
- const wrapper = mount();
+ it('Mounts with all options selected', () => {
+ const wrapper = mount(
+
+
+
+ );
wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click');
-
expect(
- wrapper.find(`[data-test-subj="indexPattern-switcher"]`).first().prop('options')
+ wrapper.find(`[data-test-subj="indexPattern-switcher"]`).first().prop('selectedOptions')
).toEqual(mockOptions);
});
- it('onChange calls updateSourceGroupIndicies', () => {
- const wrapper = mount();
- wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click');
-
- const switcherOnChange = wrapper
- .find(`[data-test-subj="indexPattern-switcher"]`)
- .first()
- .prop('onChange');
- // @ts-ignore
- switcherOnChange([mockOptions[0], mockOptions[1]]);
- expect(updateSourceGroupIndicies).toHaveBeenCalledWith(SecurityPageName.default, [
- mockOptions[0].value,
- mockOptions[1].value,
- ]);
- });
- it('Disabled options have icon tooltip', () => {
- const wrapper = mount();
- wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click');
- // @ts-ignore
- const Rendered = wrapper
- .find(`[data-test-subj="indexPattern-switcher"]`)
- .first()
- .prop('renderOption')(
- {
- label: 'blobbeat-*',
- key: 'blobbeat-*-1',
- value: 'blobbeat-*',
- disabled: true,
- checked: undefined,
+ it('Mounts with some options selected', () => {
+ const state2 = {
+ ...mockGlobalState,
+ sourcerer: {
+ ...mockGlobalState.sourcerer,
+ sourcererScopes: {
+ ...mockGlobalState.sourcerer.sourcererScopes,
+ [SourcererScopeName.default]: {
+ ...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.default],
+ loading: false,
+ selectedPatterns: [DEFAULT_INDEX_PATTERN[0]],
+ },
+ },
},
- ''
+ };
+
+ store = createStore(
+ state2,
+ SUB_PLUGINS_REDUCER,
+ apolloClientObservable,
+ kibanaObservable,
+ storage
+ );
+ const wrapper = mount(
+
+
+
);
- expect(Rendered.props.children[1].props.content).toEqual(i18n.DISABLED_INDEX_PATTERNS);
+ wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click');
+ expect(
+ wrapper.find(`[data-test-subj="indexPattern-switcher"]`).first().prop('selectedOptions')
+ ).toEqual([mockOptions[0]]);
});
-
- it('Button links to index path', () => {
- const wrapper = mount();
+ it('onChange calls updateSourcererScopeIndices', async () => {
+ const wrapper = mount(
+
+
+
+ );
+ expect(true).toBeTruthy();
wrapper.find(`[data-test-subj="sourcerer-trigger"]`).first().simulate('click');
- expect(wrapper.find(`[data-test-subj="add-index"]`).first().prop('href')).toEqual(
- ADD_INDEX_PATH
+ await act(async () => {
+ ((wrapper.find(EuiComboBox).props() as unknown) as {
+ onChange: (a: EuiComboBoxOptionOption[]) => void;
+ }).onChange([mockOptions[0], mockOptions[1]]);
+ await waitFor(() => {
+ wrapper.update();
+ });
+ });
+ wrapper.find(`[data-test-subj="add-index"]`).first().simulate('click');
+
+ expect(mockDispatch).toHaveBeenCalledWith(
+ sourcererActions.setSelectedIndexPatterns({
+ id: SourcererScopeName.default,
+ selectedPatterns: [mockOptions[0].value, mockOptions[1].value],
+ })
);
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx
index 6275ce19c3608..7a74f5bf2247f 100644
--- a/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/index.tsx
@@ -4,50 +4,122 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import React, { useCallback, useMemo, useState } from 'react';
import {
EuiButton,
EuiButtonEmpty,
- EuiHighlight,
- EuiIconTip,
+ EuiComboBox,
+ EuiComboBoxOptionOption,
+ EuiIcon,
+ EuiFlexGroup,
+ EuiFlexItem,
EuiPopover,
- EuiPopoverFooter,
EuiPopoverTitle,
- EuiSelectable,
+ EuiSpacer,
+ EuiText,
+ EuiToolTip,
} from '@elastic/eui';
-import { EuiSelectableOption } from '@elastic/eui/src/components/selectable/selectable_option';
-import { useManageSource } from '../../containers/sourcerer';
+import deepEqual from 'fast-deep-equal';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+import styled from 'styled-components';
+
import * as i18n from './translations';
import { SOURCERER_FEATURE_FLAG_ON } from '../../containers/sourcerer/constants';
-import { ADD_INDEX_PATH } from '../../../../common/constants';
-
-export const MaybeSourcerer = React.memo(() => {
- const {
- activeSourceGroupId,
- availableIndexPatterns,
- getManageSourceGroupById,
- isIndexPatternsLoading,
- updateSourceGroupIndicies,
- } = useManageSource();
- const { defaultPatterns, indexPatterns: selectedOptions, loading: loadingIndices } = useMemo(
- () => getManageSourceGroupById(activeSourceGroupId),
- [getManageSourceGroupById, activeSourceGroupId]
+import { sourcererActions, sourcererModel } from '../../store/sourcerer';
+import { State } from '../../store';
+import { getSourcererScopeSelector, SourcererScopeSelector } from './selectors';
+
+const PopoverContent = styled.div`
+ width: 600px;
+`;
+
+const ResetButton = styled(EuiButtonEmpty)`
+ width: fit-content;
+`;
+interface SourcererComponentProps {
+ scope: sourcererModel.SourcererScopeName;
+}
+
+export const SourcererComponent = React.memo(({ scope: scopeId }) => {
+ const dispatch = useDispatch();
+ const sourcererScopeSelector = useMemo(getSourcererScopeSelector, []);
+ const { configIndexPatterns, kibanaIndexPatterns, sourcererScope } = useSelector<
+ State,
+ SourcererScopeSelector
+ >((state) => sourcererScopeSelector(state, scopeId), deepEqual);
+ const { selectedPatterns, loading } = sourcererScope;
+ const [isPopoverOpen, setPopoverIsOpen] = useState(false);
+ const [selectedOptions, setSelectedOptions] = useState>>(
+ selectedPatterns.map((indexSelected) => ({
+ label: indexSelected,
+ value: indexSelected,
+ }))
);
- const loading = useMemo(() => loadingIndices || isIndexPatternsLoading, [
- isIndexPatternsLoading,
- loadingIndices,
- ]);
+ const setPopoverIsOpenCb = useCallback(() => setPopoverIsOpen((prevState) => !prevState), []);
const onChangeIndexPattern = useCallback(
- (newIndexPatterns: string[]) => {
- updateSourceGroupIndicies(activeSourceGroupId, newIndexPatterns);
+ (newSelectedPatterns: string[]) => {
+ dispatch(
+ sourcererActions.setSelectedIndexPatterns({
+ id: scopeId,
+ selectedPatterns: newSelectedPatterns,
+ })
+ );
},
- [activeSourceGroupId, updateSourceGroupIndicies]
+ [dispatch, scopeId]
+ );
+
+ const renderOption = useCallback(
+ (option) => {
+ const { value } = option;
+ if (kibanaIndexPatterns.some((kip) => kip.title === value)) {
+ return (
+ <>
+ {value}
+ >
+ );
+ }
+ return <>{value}>;
+ },
+ [kibanaIndexPatterns]
+ );
+
+ const onChangeCombo = useCallback((newSelectedOptions) => {
+ setSelectedOptions(newSelectedOptions);
+ }, []);
+
+ const resetDataSources = useCallback(() => {
+ setSelectedOptions(
+ configIndexPatterns.map((indexSelected) => ({
+ label: indexSelected,
+ value: indexSelected,
+ }))
+ );
+ }, [configIndexPatterns]);
+
+ const handleSaveIndices = useCallback(() => {
+ onChangeIndexPattern(selectedOptions.map((so) => so.label));
+ setPopoverIsOpen(false);
+ }, [onChangeIndexPattern, selectedOptions]);
+
+ const handleClosePopOver = useCallback(() => {
+ setPopoverIsOpen(false);
+ }, []);
+
+ const indexesPatternOptions = useMemo(
+ () =>
+ [...configIndexPatterns, ...kibanaIndexPatterns.map((kip) => kip.title)].reduce<
+ Array>
+ >((acc, index) => {
+ if (index != null && !acc.some((o) => o.label.includes(index))) {
+ return [...acc, { label: index, value: index }];
+ }
+ return acc;
+ }, []),
+ [configIndexPatterns, kibanaIndexPatterns]
);
- const [isPopoverOpen, setPopoverIsOpen] = useState(false);
- const setPopoverIsOpenCb = useCallback(() => setPopoverIsOpen((prevState) => !prevState), []);
const trigger = useMemo(
() => (
{
data-test-subj="sourcerer-trigger"
flush="left"
iconSide="right"
- iconType="indexSettings"
+ iconType="arrowDown"
+ isLoading={loading}
onClick={setPopoverIsOpenCb}
size="l"
title={i18n.SOURCERER}
@@ -63,99 +136,91 @@ export const MaybeSourcerer = React.memo(() => {
{i18n.SOURCERER}
),
- [setPopoverIsOpenCb]
- );
- const options: EuiSelectableOption[] = useMemo(
- () =>
- availableIndexPatterns.map((title, id) => ({
- label: title,
- key: `${title}-${id}`,
- value: title,
- checked: selectedOptions.includes(title) ? 'on' : undefined,
- })),
- [availableIndexPatterns, selectedOptions]
+ [setPopoverIsOpenCb, loading]
);
- const unSelectableOptions: EuiSelectableOption[] = useMemo(
- () =>
- defaultPatterns
- .filter((title) => !availableIndexPatterns.includes(title))
- .map((title, id) => ({
- label: title,
- key: `${title}-${id}`,
- value: title,
- disabled: true,
- checked: undefined,
- })),
- [availableIndexPatterns, defaultPatterns]
- );
- const renderOption = useCallback(
- (option, searchValue) => (
- <>
- {option.label}
- {option.disabled ? (
-
- ) : null}
- >
+
+ const comboBox = useMemo(
+ () => (
+
),
- []
+ [indexesPatternOptions, onChangeCombo, renderOption, selectedOptions]
);
- const onChange = useCallback(
- (choices: EuiSelectableOption[]) => {
- const choice = choices.reduce(
- (acc, { checked, label }) => (checked === 'on' ? [...acc, label] : acc),
- []
- );
- onChangeIndexPattern(choice);
- },
- [onChangeIndexPattern]
+
+ useEffect(() => {
+ const newSelecteOptions = selectedPatterns.map((indexSelected) => ({
+ label: indexSelected,
+ value: indexSelected,
+ }));
+ setSelectedOptions((prevSelectedOptions) => {
+ if (!deepEqual(newSelecteOptions, prevSelectedOptions)) {
+ return newSelecteOptions;
+ }
+ return prevSelectedOptions;
+ });
+ }, [selectedPatterns]);
+
+ const tooltipContent = useMemo(
+ () => (isPopoverOpen ? null : sourcererScope.selectedPatterns.sort().join(', ')),
+ [isPopoverOpen, sourcererScope.selectedPatterns]
);
- const allOptions = useMemo(() => [...options, ...unSelectableOptions], [
- options,
- unSelectableOptions,
- ]);
+
return (
- setPopoverIsOpen(false)}
- display="block"
- panelPaddingSize="s"
- ownFocus
- >
-
-
- <>
- {i18n.CHANGE_INDEX_PATTERNS}
-
- >
-
-
- {(list, search) => (
- <>
- {search}
- {list}
- >
- )}
-
-
-
- {i18n.ADD_INDEX_PATTERNS}
-
-
-
-
+
+
+
+
+ <>{i18n.SELECT_INDEX_PATTERNS}>
+
+
+ {i18n.INDEX_PATTERNS_SELECTION_LABEL}
+
+ {comboBox}
+
+
+
+
+ {i18n.INDEX_PATTERNS_RESET}
+
+
+
+
+ {i18n.SAVE_INDEX_PATTERNS}
+
+
+
+
+
+
);
});
-MaybeSourcerer.displayName = 'Sourcerer';
+SourcererComponent.displayName = 'Sourcerer';
-export const Sourcerer = SOURCERER_FEATURE_FLAG_ON ? MaybeSourcerer : () => null;
+export const Sourcerer = SOURCERER_FEATURE_FLAG_ON ? SourcererComponent : () => null;
diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.tsx b/x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.tsx
new file mode 100644
index 0000000000000..6bbe24e921880
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/selectors.tsx
@@ -0,0 +1,35 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { State } from '../../store';
+import { sourcererSelectors } from '../../store/sourcerer';
+import { KibanaIndexPatterns, ManageScope, SourcererScopeName } from '../../store/sourcerer/model';
+
+export interface SourcererScopeSelector {
+ configIndexPatterns: string[];
+ kibanaIndexPatterns: KibanaIndexPatterns;
+ sourcererScope: ManageScope;
+}
+
+export const getSourcererScopeSelector = () => {
+ const getKibanaIndexPatternsSelector = sourcererSelectors.kibanaIndexPatternsSelector();
+ const getScopesSelector = sourcererSelectors.scopesSelector();
+ const getConfigIndexPatternsSelector = sourcererSelectors.configIndexPatternsSelector();
+
+ const mapStateToProps = (state: State, scopeId: SourcererScopeName): SourcererScopeSelector => {
+ const kibanaIndexPatterns = getKibanaIndexPatternsSelector(state);
+ const scope = getScopesSelector(state)[scopeId];
+ const configIndexPatterns = getConfigIndexPatternsSelector(state);
+
+ return {
+ kibanaIndexPatterns,
+ configIndexPatterns,
+ sourcererScope: scope,
+ };
+ };
+
+ return mapStateToProps;
+};
diff --git a/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts b/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts
index 71b1734dad6a6..473eb43d5c4fe 100644
--- a/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts
+++ b/x-pack/plugins/security_solution/public/common/components/sourcerer/translations.ts
@@ -6,23 +6,26 @@
import { i18n } from '@kbn/i18n';
-export const SOURCERER = i18n.translate('xpack.securitySolution.indexPatterns.sourcerer', {
- defaultMessage: 'Sourcerer',
+export const SOURCERER = i18n.translate('xpack.securitySolution.indexPatterns.dataSourcesLabel', {
+ defaultMessage: 'Data sources',
});
-export const CHANGE_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.help', {
- defaultMessage: 'Change index patterns',
+export const ALL_DEFAULT = i18n.translate('xpack.securitySolution.indexPatterns.allDefault', {
+ defaultMessage: 'All default',
});
-export const ADD_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.add', {
- defaultMessage: 'Configure Kibana index patterns',
+export const SELECT_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.help', {
+ defaultMessage: 'Data sources selection',
});
-export const CONFIGURE_INDEX_PATTERNS = i18n.translate(
- 'xpack.securitySolution.indexPatterns.configure',
+export const SAVE_INDEX_PATTERNS = i18n.translate('xpack.securitySolution.indexPatterns.save', {
+ defaultMessage: 'Save',
+});
+
+export const INDEX_PATTERNS_SELECTION_LABEL = i18n.translate(
+ 'xpack.securitySolution.indexPatterns.selectionLabel',
{
- defaultMessage:
- 'Configure additional Kibana index patterns to see them become available in the Security Solution',
+ defaultMessage: 'Choose the source of the data on this page',
}
);
@@ -33,3 +36,17 @@ export const DISABLED_INDEX_PATTERNS = i18n.translate(
'Disabled index patterns are recommended on this page, but first need to be configured in your Kibana index pattern settings',
}
);
+
+export const INDEX_PATTERNS_RESET = i18n.translate(
+ 'xpack.securitySolution.indexPatterns.resetButton',
+ {
+ defaultMessage: 'Reset',
+ }
+);
+
+export const PICK_INDEX_PATTERNS = i18n.translate(
+ 'xpack.securitySolution.indexPatterns.pickIndexPatternsCombo',
+ {
+ defaultMessage: 'Pick index patterns',
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts
index b654eaf17b47b..79cbd87cda201 100644
--- a/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts
+++ b/x-pack/plugins/security_solution/public/common/components/top_n/helpers.ts
@@ -4,13 +4,13 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EventType } from '../../../timelines/store/timeline/model';
+import { TimelineEventsType } from '../../../../common/types/timeline';
import * as i18n from './translations';
export interface TopNOption {
inputDisplay: string;
- value: EventType;
+ value: TimelineEventsType;
'data-test-subj': string;
}
@@ -52,8 +52,8 @@ export const defaultOptions = [...rawEvents, ...alertEvents];
* is always in sync with the `EventType` chosen by the user in
* the active timeline.
*/
-export const getOptions = (activeTimelineEventType?: EventType): TopNOption[] => {
- switch (activeTimelineEventType) {
+export const getOptions = (activeTimelineEventsType?: TimelineEventsType): TopNOption[] => {
+ switch (activeTimelineEventsType) {
case 'all':
return allEvents;
case 'raw':
diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx
index 31318122eb564..594bffbd4ff63 100644
--- a/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.test.tsx
@@ -168,6 +168,17 @@ const store = createStore(
storage
);
+let testProps = {
+ browserFields: mockBrowserFields,
+ field,
+ indexNames: [],
+ indexPattern: mockIndexPattern,
+ timelineId: TimelineId.hostsPageExternalAlerts,
+ toggleTopN: jest.fn(),
+ onFilterAdded: jest.fn(),
+ value,
+};
+
describe('StatefulTopN', () => {
// Suppress warnings about "react-beautiful-dnd"
/* eslint-disable no-console */
@@ -189,16 +200,7 @@ describe('StatefulTopN', () => {
wrapper = mount(
-
+
);
@@ -277,19 +279,14 @@ describe('StatefulTopN', () => {
filterManager,
},
};
+ testProps = {
+ ...testProps,
+ timelineId: TimelineId.active,
+ };
wrapper = mount(
-
+
);
@@ -345,37 +342,33 @@ describe('StatefulTopN', () => {
expect(props.to).toEqual('2020-04-15T03:46:09.047Z');
});
});
+ describe('rendering in a NON-active timeline context', () => {
+ test(`defaults to the 'Alert events' option when rendering in a NON-active timeline context (e.g. the Alerts table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'alerts'`, () => {
+ const filterManager = new FilterManager(mockUiSettingsForFilterManager);
- test(`defaults to the 'Alert events' option when rendering in a NON-active timeline context (e.g. the Alerts table on the Detections page) when 'documentType' from 'useTimelineTypeContext()' is 'alerts'`, () => {
- const filterManager = new FilterManager(mockUiSettingsForFilterManager);
+ const manageTimelineForTesting = {
+ [TimelineId.active]: {
+ ...getTimelineDefaults(TimelineId.active),
+ filterManager,
+ documentType: 'alerts',
+ },
+ };
- const manageTimelineForTesting = {
- [TimelineId.active]: {
- ...getTimelineDefaults(TimelineId.active),
- filterManager,
- documentType: 'alerts',
- },
- };
-
- const wrapper = mount(
-
-
-
-
-
- );
-
- const props = wrapper.find('[data-test-subj="top-n"]').first().props() as Props;
-
- expect(props.defaultView).toEqual('alert');
+ testProps = {
+ ...testProps,
+ timelineId: TimelineId.detectionsPage,
+ };
+ const wrapper = mount(
+
+
+
+
+
+ );
+
+ const props = wrapper.find('[data-test-subj="top-n"]').first().props() as Props;
+
+ expect(props.defaultView).toEqual('alert');
+ });
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx
index d71242329bcda..9c81cb57335a5 100644
--- a/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/top_n/index.tsx
@@ -74,7 +74,7 @@ interface OwnProps {
browserFields: BrowserFields;
field: string;
indexPattern: IIndexPattern;
- indexToAdd: string[] | null;
+ indexNames: string[];
timelineId?: string;
toggleTopN: () => void;
onFilterAdded?: () => void;
@@ -93,7 +93,7 @@ const StatefulTopNComponent: React.FC = ({
dataProviders,
field,
indexPattern,
- indexToAdd,
+ indexNames,
globalFilters = EMPTY_FILTERS,
globalQuery = EMPTY_QUERY,
kqlMode,
@@ -109,7 +109,6 @@ const StatefulTopNComponent: React.FC = ({
const options = getOptions(
timelineId === TimelineId.active ? activeTimelineEventType : undefined
);
-
return (
= ({
filters={timelineId === TimelineId.active ? EMPTY_FILTERS : globalFilters}
from={timelineId === TimelineId.active ? activeTimelineFrom : from}
indexPattern={indexPattern}
- indexToAdd={indexToAdd}
+ indexNames={indexNames}
options={options}
query={timelineId === TimelineId.active ? EMPTY_QUERY : globalQuery}
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx
index 667d1816e8f07..829f918ddfe1b 100644
--- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.test.tsx
@@ -13,6 +13,8 @@ import { setAbsoluteRangeDatePicker } from '../../store/inputs/actions';
import { allEvents, defaultOptions } from './helpers';
import { TopN } from './top_n';
+import { TimelineEventsType } from '../../../../common/types/timeline';
+import { InputsModelId } from '../../store/inputs/constants';
jest.mock('react-router-dom', () => {
const original = jest.requireActual('react-router-dom');
@@ -103,29 +105,34 @@ describe('TopN', () => {
const query = { query: '', language: 'kuery' };
+ const toggleTopN = jest.fn();
+ const eventTypes: { [id: string]: TimelineEventsType } = {
+ raw: 'raw',
+ alert: 'alert',
+ all: 'all',
+ };
+ let testProps = {
+ defaultView: eventTypes.raw,
+ field,
+ filters: [],
+ from: '2020-04-14T00:31:47.695Z',
+ indexNames: [],
+ indexPattern: mockIndexPattern,
+ options: defaultOptions,
+ query,
+ setAbsoluteRangeDatePicker,
+ setAbsoluteRangeDatePickerTarget: 'global' as InputsModelId,
+ setQuery: jest.fn(),
+ to: '2020-04-15T00:31:47.695Z',
+ toggleTopN,
+ value,
+ };
describe('common functionality', () => {
- let toggleTopN: () => void;
let wrapper: ReactWrapper;
-
beforeEach(() => {
- toggleTopN = jest.fn();
wrapper = mount(
-
+
);
});
@@ -143,28 +150,12 @@ describe('TopN', () => {
});
describe('events view', () => {
- let toggleTopN: () => void;
let wrapper: ReactWrapper;
beforeEach(() => {
- toggleTopN = jest.fn();
wrapper = mount(
-
+
);
});
@@ -181,37 +172,25 @@ describe('TopN', () => {
});
describe('alerts view', () => {
- let toggleTopN: () => void;
let wrapper: ReactWrapper;
beforeEach(() => {
- toggleTopN = jest.fn();
+ testProps = {
+ ...testProps,
+ defaultView: eventTypes.alert,
+ };
wrapper = mount(
-
+
);
});
- test(`it renders SignalsByCategory when defaultView is 'signal'`, () => {
+ test(`it renders SignalsByCategory when defaultView is 'alert'`, () => {
expect(wrapper.find('[data-test-subj="alerts-histogram-panel"]').exists()).toBe(true);
});
- test(`it does NOT render EventsByDataset when defaultView is 'signal'`, () => {
+ test(`it does NOT render EventsByDataset when defaultView is 'alert'`, () => {
expect(
wrapper.find('[data-test-subj="eventsByDatasetOverview-uuid.v4()Panel"]').exists()
).toBe(false);
@@ -222,24 +201,14 @@ describe('TopN', () => {
let wrapper: ReactWrapper;
beforeEach(() => {
+ testProps = {
+ ...testProps,
+ defaultView: eventTypes.all,
+ options: allEvents,
+ };
wrapper = mount(
-
+
);
});
diff --git a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx
index 064241a7216f4..4f0a71dcc3ebb 100644
--- a/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/top_n/top_n.tsx
@@ -14,7 +14,7 @@ import { EventsByDataset } from '../../../overview/components/events_by_dataset'
import { SignalsByCategory } from '../../../overview/components/signals_by_category';
import { Filter, IIndexPattern, Query } from '../../../../../../../src/plugins/data/public';
import { InputsModelId } from '../../store/inputs/constants';
-import { EventType } from '../../../timelines/store/timeline/model';
+import { TimelineEventsType } from '../../../../common/types/timeline';
import { TopNOption } from './helpers';
import * as i18n from './translations';
@@ -45,11 +45,11 @@ const TopNContent = styled.div`
export interface Props extends Pick {
combinedQueries?: string;
- defaultView: EventType;
+ defaultView: TimelineEventsType;
field: string;
filters: Filter[];
indexPattern: IIndexPattern;
- indexToAdd?: string[] | null;
+ indexNames: string[];
options: TopNOption[];
query: Query;
setAbsoluteRangeDatePicker: ActionCreator<{
@@ -75,7 +75,7 @@ const TopNComponent: React.FC = ({
field,
from,
indexPattern,
- indexToAdd,
+ indexNames,
options,
query = DEFAULT_QUERY,
setAbsoluteRangeDatePicker,
@@ -85,8 +85,10 @@ const TopNComponent: React.FC = ({
to,
toggleTopN,
}) => {
- const [view, setView] = useState(defaultView);
- const onViewSelected = useCallback((value: string) => setView(value as EventType), [setView]);
+ const [view, setView] = useState(defaultView);
+ const onViewSelected = useCallback((value: string) => setView(value as TimelineEventsType), [
+ setView,
+ ]);
useEffect(() => {
setView(defaultView);
@@ -123,7 +125,7 @@ const TopNComponent: React.FC = ({
from={from}
headerChildren={headerChildren}
indexPattern={indexPattern}
- indexToAdd={indexToAdd}
+ indexNames={indexNames}
onlyField={field}
query={query}
setAbsoluteRangeDatePickerTarget={setAbsoluteRangeDatePickerTarget}
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts b/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts
index 5a4aec93dd9aa..e5c09d229808b 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/constants.ts
@@ -17,6 +17,7 @@ export enum CONSTANTS {
networkPage = 'network.page',
overviewPage = 'overview.page',
savedQuery = 'savedQuery',
+ sourcerer = 'sourcerer',
timeline = 'timeline',
timelinePage = 'timeline.page',
timerange = 'timerange',
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts
index 6052913b4183b..a915b1c9d09a7 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/helpers.ts
@@ -22,6 +22,8 @@ import { formatDate } from '../super_date_picker';
import { NavTab } from '../navigation/types';
import { CONSTANTS, UrlStateType } from './constants';
import { ReplaceStateInLocation, UpdateUrlStateString } from './types';
+import { sourcererSelectors } from '../../store/sourcerer';
+import { SourcererScopeName, SourcererScopePatterns } from '../../store/sourcerer/model';
export const decodeRisonUrlState = (value: string | undefined): T | null => {
try {
@@ -118,6 +120,7 @@ export const makeMapStateToProps = () => {
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector();
const getGlobalSavedQuerySelector = inputsSelectors.globalSavedQuerySelector();
const getTimeline = timelineSelectors.getTimelineByIdSelector();
+ const getSourcererScopes = sourcererSelectors.scopesSelector();
const mapStateToProps = (state: State) => {
const inputState = getInputsSelector(state);
const { linkTo: globalLinkTo, timerange: globalTimerange } = inputState.global;
@@ -147,10 +150,16 @@ export const makeMapStateToProps = () => {
[CONSTANTS.savedQuery]: savedQuery.id,
};
}
+ const sourcerer = getSourcererScopes(state);
+ const activeScopes: SourcererScopeName[] = Object.keys(sourcerer) as SourcererScopeName[];
+ const selectedPatterns: SourcererScopePatterns = activeScopes
+ .filter((scope) => scope === SourcererScopeName.default)
+ .reduce((acc, scope) => ({ ...acc, [scope]: sourcerer[scope]?.selectedPatterns }), {});
return {
urlState: {
...searchAttr,
+ [CONSTANTS.sourcerer]: selectedPatterns,
[CONSTANTS.timerange]: {
global: {
[CONSTANTS.timerange]: globalTimerange,
@@ -217,6 +226,17 @@ export const updateUrlStateString = ({
urlStateKey: urlKey,
});
}
+ } else if (urlKey === CONSTANTS.sourcerer) {
+ const sourcererState = decodeRisonUrlState(newUrlStateString);
+ if (sourcererState != null && Object.keys(sourcererState).length > 0) {
+ return replaceStateInLocation({
+ history,
+ pathName,
+ search,
+ urlStateToReplace: sourcererState,
+ urlStateKey: urlKey,
+ });
+ }
} else if (urlKey === CONSTANTS.filters) {
const queryState = decodeRisonUrlState(newUrlStateString);
if (isEmpty(queryState)) {
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx
index 72df9d613abac..fc970c066e8a5 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/index.test.tsx
@@ -161,7 +161,7 @@ describe('UrlStateContainer', () => {
).toEqual({
hash: '',
pathname: examplePath,
- search: `?query=(language:kuery,query:'host.name:%22siem-es%22')&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)))`,
+ search: `?query=(language:kuery,query:'host.name:%22siem-es%22')&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)))`,
state: '',
});
}
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx
index 723f2d235864f..9e845ec538aa0 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/index_mocked.test.tsx
@@ -83,7 +83,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () =>
hash: '',
pathname: '/network',
search:
- "?query=(language:kuery,query:'host.name:%22siem-es%22')&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)))",
+ "?query=(language:kuery,query:'host.name:%22siem-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)))",
state: '',
});
});
@@ -114,7 +114,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () =>
hash: '',
pathname: '/network',
search:
- "?query=(language:kuery,query:'host.name:%22siem-es%22')&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)))",
+ "?query=(language:kuery,query:'host.name:%22siem-es%22')&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)))",
state: '',
});
});
@@ -147,7 +147,40 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () =>
hash: '',
pathname: '/network',
search:
- "?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=(id:hello_timeline_id,isOpen:!t)",
+ "?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=(id:hello_timeline_id,isOpen:!t)",
+ state: '',
+ });
+ });
+
+ test('sourcerer redux state updates the url', () => {
+ mockProps = getMockPropsObj({
+ page: CONSTANTS.networkPage,
+ examplePath: '/network',
+ namespaceLower: 'network',
+ pageName: SecurityPageName.network,
+ detailName: undefined,
+ }).noSearch.undefinedQuery;
+
+ const wrapper = mount(
+ useUrlStateHooks(args)} />
+ );
+ const newUrlState = {
+ ...mockProps.urlState,
+ sourcerer: ['cool', 'patterns'],
+ };
+
+ wrapper.setProps({
+ hookProps: { ...mockProps, urlState: newUrlState, isInitializing: false },
+ });
+ wrapper.update();
+
+ expect(
+ mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0]
+ ).toStrictEqual({
+ hash: '',
+ pathname: '/network',
+ search:
+ "?sourcerer=!(cool,patterns)&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)))",
state: '',
});
});
@@ -176,7 +209,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () =>
hash: '',
pathname: examplePath,
search:
- "?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)))",
+ "?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)))",
state: '',
});
}
@@ -204,7 +237,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () =>
expect(
mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0].search
).toEqual(
- "?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)))"
+ "?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)))"
);
wrapper.setProps({ hookProps: updatedProps });
@@ -213,7 +246,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () =>
expect(
mockHistory.replace.mock.calls[mockHistory.replace.mock.calls.length - 1][0].search
).toEqual(
- "?query=(language:kuery,query:'host.name:%22siem-es%22')&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)))"
+ "?query=(language:kuery,query:'host.name:%22siem-es%22')&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)))"
);
});
});
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx
index 6eccf52ec72da..1e77ae7766630 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/initialize_redux_by_url.tsx
@@ -8,7 +8,7 @@ import { get, isEmpty } from 'lodash/fp';
import { Dispatch } from 'redux';
import { Query, Filter } from '../../../../../../../src/plugins/data/public';
-import { inputsActions } from '../../store/actions';
+import { inputsActions, sourcererActions } from '../../store/actions';
import { InputsModelId, TimeRangeKinds } from '../../store/inputs/constants';
import {
UrlInputsModel,
@@ -22,6 +22,8 @@ import { decodeRisonUrlState } from './helpers';
import { normalizeTimeRange } from './normalize_time_range';
import { DispatchSetInitialStateFromUrl, SetInitialStateFromUrl } from './types';
import { queryTimelineById } from '../../../timelines/components/open_timeline/helpers';
+import { SourcererScopeName, SourcererScopePatterns } from '../../store/sourcerer/model';
+import { SecurityPageName } from '../../../../common/constants';
export const dispatchSetInitialStateFromUrl = (
dispatch: Dispatch
@@ -40,6 +42,22 @@ export const dispatchSetInitialStateFromUrl = (
if (urlKey === CONSTANTS.timerange) {
updateTimerange(newUrlStateString, dispatch);
}
+ if (urlKey === CONSTANTS.sourcerer) {
+ const sourcererState = decodeRisonUrlState(newUrlStateString);
+ if (sourcererState != null) {
+ const activeScopes: SourcererScopeName[] = Object.keys(sourcererState).filter(
+ (key) => !(key === SourcererScopeName.default && pageName === SecurityPageName.detections)
+ ) as SourcererScopeName[];
+ activeScopes.forEach((scope) =>
+ dispatch(
+ sourcererActions.setSelectedIndexPatterns({
+ id: scope,
+ selectedPatterns: sourcererState[scope] ?? [],
+ })
+ )
+ );
+ }
+ }
if (urlKey === CONSTANTS.appQuery && indexPattern != null) {
const appQuery = decodeRisonUrlState(newUrlStateString);
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts
index 8d471e843320c..6f04226fa3a19 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/test_dependencies.ts
@@ -117,6 +117,7 @@ export const defaultProps: UrlStateContainerPropTypes = {
id: '',
isOpen: false,
},
+ [CONSTANTS.sourcerer]: {},
},
setInitialStateFromUrl: dispatchSetInitialStateFromUrl(mockDispatch),
updateTimeline: (jest.fn() as unknown) as DispatchUpdateTimeline,
diff --git a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts
index f383e18132385..301771a4db6b9 100644
--- a/x-pack/plugins/security_solution/public/common/components/url_state/types.ts
+++ b/x-pack/plugins/security_solution/public/common/components/url_state/types.ts
@@ -22,11 +22,13 @@ import { DispatchUpdateTimeline } from '../../../timelines/components/open_timel
import { NavTab } from '../navigation/types';
import { CONSTANTS, UrlStateType } from './constants';
+import { SourcererScopePatterns } from '../../store/sourcerer/model';
export const ALL_URL_STATE_KEYS: KeyUrlState[] = [
CONSTANTS.appQuery,
CONSTANTS.filters,
CONSTANTS.savedQuery,
+ CONSTANTS.sourcerer,
CONSTANTS.timerange,
CONSTANTS.timeline,
];
@@ -36,6 +38,7 @@ export const URL_STATE_KEYS: Record = {
CONSTANTS.appQuery,
CONSTANTS.filters,
CONSTANTS.savedQuery,
+ CONSTANTS.sourcerer,
CONSTANTS.timerange,
CONSTANTS.timeline,
],
@@ -43,6 +46,7 @@ export const URL_STATE_KEYS: Record = {
CONSTANTS.appQuery,
CONSTANTS.filters,
CONSTANTS.savedQuery,
+ CONSTANTS.sourcerer,
CONSTANTS.timerange,
CONSTANTS.timeline,
],
@@ -51,6 +55,7 @@ export const URL_STATE_KEYS: Record = {
CONSTANTS.appQuery,
CONSTANTS.filters,
CONSTANTS.savedQuery,
+ CONSTANTS.sourcerer,
CONSTANTS.timerange,
CONSTANTS.timeline,
],
@@ -58,6 +63,7 @@ export const URL_STATE_KEYS: Record = {
CONSTANTS.appQuery,
CONSTANTS.filters,
CONSTANTS.savedQuery,
+ CONSTANTS.sourcerer,
CONSTANTS.timerange,
CONSTANTS.timeline,
],
@@ -65,6 +71,7 @@ export const URL_STATE_KEYS: Record = {
CONSTANTS.appQuery,
CONSTANTS.filters,
CONSTANTS.savedQuery,
+ CONSTANTS.sourcerer,
CONSTANTS.timerange,
CONSTANTS.timeline,
],
@@ -72,6 +79,7 @@ export const URL_STATE_KEYS: Record = {
CONSTANTS.appQuery,
CONSTANTS.filters,
CONSTANTS.savedQuery,
+ CONSTANTS.sourcerer,
CONSTANTS.timerange,
CONSTANTS.timeline,
],
@@ -93,6 +101,7 @@ export interface UrlState {
[CONSTANTS.appQuery]?: Query;
[CONSTANTS.filters]?: Filter[];
[CONSTANTS.savedQuery]?: string;
+ [CONSTANTS.sourcerer]: SourcererScopePatterns;
[CONSTANTS.timerange]: UrlInputsModel;
[CONSTANTS.timeline]: TimelineUrl;
}
diff --git a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/index.tsx b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/index.tsx
index f6ebbb990f223..489ccb23c9b2c 100644
--- a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/index.tsx
@@ -29,6 +29,7 @@ const AnomaliesQueryTabBodyComponent: React.FC = ({
AnomaliesTableComponent,
flowTarget,
ip,
+ indexNames,
}) => {
const { jobs } = useInstalledSecurityJobs();
const [anomalyScore] = useUiSetting$(DEFAULT_ANOMALY_SCORE);
@@ -57,6 +58,7 @@ const AnomaliesQueryTabBodyComponent: React.FC = ({
endDate={endDate}
filterQuery={mergedFilterQuery}
id={ID}
+ indexNames={indexNames}
setQuery={setQuery}
startDate={startDate}
{...histogramConfigs}
diff --git a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts
index d716df70246f7..3ce4b8b6d4494 100644
--- a/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts
+++ b/x-pack/plugins/security_solution/public/common/containers/anomalies/anomalies_query_tab_body/types.ts
@@ -24,6 +24,7 @@ export type AnomaliesQueryTabBodyProps = QueryTabBodyProps & {
deleteQuery?: ({ id }: { id: string }) => void;
endDate: GlobalTimeArgs['to'];
flowTarget?: FlowTarget;
+ indexNames: string[];
narrowDateRange: NarrowDateRange;
setQuery: GlobalTimeArgs['setQuery'];
startDate: GlobalTimeArgs['from'];
diff --git a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts
index 3d79c83dc42cb..dc2d6605bc292 100644
--- a/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts
+++ b/x-pack/plugins/security_solution/public/common/containers/events/last_event_time/index.ts
@@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal';
import { noop } from 'lodash/fp';
import { useCallback, useEffect, useRef, useState } from 'react';
-import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
import { inputsModel } from '../../../../common/store';
import { useKibana } from '../../../../common/lib/kibana';
import {
@@ -23,10 +22,10 @@ import {
isCompleteResponse,
isErrorResponse,
} from '../../../../../../../../src/plugins/data/common';
-import { useWithSource } from '../../source';
import * as i18n from './translations';
+import { DocValueFields } from '../../../../../common/search_strategy';
-// const ID = 'timelineEventsLastEventTimeQuery';
+const ID = 'timelineEventsLastEventTimeQuery';
export interface UseTimelineLastEventTimeArgs {
lastSeen: string | null;
@@ -35,26 +34,29 @@ export interface UseTimelineLastEventTimeArgs {
}
interface UseTimelineLastEventTimeProps {
+ docValueFields: DocValueFields[];
indexKey: LastEventIndexKey;
+ indexNames: string[];
details: LastTimeDetails;
}
export const useTimelineLastEventTime = ({
+ docValueFields,
indexKey,
+ indexNames,
details,
}: UseTimelineLastEventTimeProps): [boolean, UseTimelineLastEventTimeArgs] => {
- const { data, notifications, uiSettings } = useKibana().services;
- const { docValueFields } = useWithSource('default');
+ const { data, notifications } = useKibana().services;
const refetch = useRef(noop);
const abortCtrl = useRef(new AbortController());
- const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
const [loading, setLoading] = useState(false);
const [TimelineLastEventTimeRequest, setTimelineLastEventTimeRequest] = useState<
TimelineEventsLastEventTimeRequestOptions
>({
- defaultIndex,
- factoryQueryType: TimelineEventsQueries.lastEventTime,
+ defaultIndex: indexNames,
docValueFields,
+ factoryQueryType: TimelineEventsQueries.lastEventTime,
+ id: ID,
indexKey,
details,
});
@@ -133,7 +135,8 @@ export const useTimelineLastEventTime = ({
setTimelineLastEventTimeRequest((prevRequest) => {
const myRequest = {
...prevRequest,
- defaultIndex,
+ defaultIndex: indexNames,
+ docValueFields,
indexKey,
details,
};
@@ -142,7 +145,7 @@ export const useTimelineLastEventTime = ({
}
return prevRequest;
});
- }, [defaultIndex, details, indexKey]);
+ }, [indexNames, details, docValueFields, indexKey]);
useEffect(() => {
timelineLastEventTimeSearch(TimelineLastEventTimeRequest);
diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts
index 8e0c133f95b4d..ca8bcc637717b 100644
--- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts
+++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts
@@ -5,14 +5,13 @@
*/
import deepEqual from 'fast-deep-equal';
-import { isEmpty, noop } from 'lodash/fp';
-import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { noop } from 'lodash/fp';
+import { useCallback, useEffect, useRef, useState } from 'react';
import { MatrixHistogramQueryProps } from '../../components/matrix_histogram/types';
-import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
import { inputsModel } from '../../../common/store';
import { createFilter } from '../../../common/containers/helpers';
-import { useKibana, useUiSetting$ } from '../../../common/lib/kibana';
+import { useKibana } from '../../../common/lib/kibana';
import {
MatrixHistogramQuery,
MatrixHistogramRequestOptions,
@@ -40,25 +39,18 @@ export const useMatrixHistogram = ({
errorMessage,
filterQuery,
histogramType,
- indexToAdd,
+ indexNames,
stackByField,
startDate,
}: MatrixHistogramQueryProps): [boolean, UseMatrixHistogramArgs] => {
const { data, notifications } = useKibana().services;
const refetch = useRef(noop);
const abortCtrl = useRef(new AbortController());
- const [configIndex] = useUiSetting$(DEFAULT_INDEX_KEY);
- const defaultIndex = useMemo(() => {
- if (indexToAdd != null && !isEmpty(indexToAdd)) {
- return [...configIndex, ...indexToAdd];
- }
- return configIndex;
- }, [configIndex, indexToAdd]);
const [loading, setLoading] = useState(false);
const [matrixHistogramRequest, setMatrixHistogramRequest] = useState<
MatrixHistogramRequestOptions
>({
- defaultIndex,
+ defaultIndex: indexNames,
factoryQueryType: MatrixHistogramQuery,
filterQuery: createFilter(filterQuery),
histogramType,
@@ -140,7 +132,7 @@ export const useMatrixHistogram = ({
setMatrixHistogramRequest((prevRequest) => {
const myRequest = {
...prevRequest,
- defaultIndex,
+ defaultIndex: indexNames,
filterQuery: createFilter(filterQuery),
timerange: {
interval: '12h',
@@ -153,7 +145,7 @@ export const useMatrixHistogram = ({
}
return prevRequest;
});
- }, [defaultIndex, endDate, filterQuery, startDate]);
+ }, [indexNames, endDate, filterQuery, startDate]);
useEffect(() => {
hostsSearch(matrixHistogramRequest);
diff --git a/x-pack/plugins/security_solution/public/common/containers/query_template.tsx b/x-pack/plugins/security_solution/public/common/containers/query_template.tsx
index eaa43c255a944..80791d91481a8 100644
--- a/x-pack/plugins/security_solution/public/common/containers/query_template.tsx
+++ b/x-pack/plugins/security_solution/public/common/containers/query_template.tsx
@@ -14,6 +14,7 @@ import { DocValueFields } from './source';
export { DocValueFields };
export interface QueryTemplateProps {
+ indexNames: string[];
docValueFields?: DocValueFields[];
id?: string;
endDate?: string;
diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts b/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts
deleted file mode 100644
index 630515c5cbed4..0000000000000
--- a/x-pack/plugins/security_solution/public/common/containers/source/index.gql_query.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import gql from 'graphql-tag';
-
-export const sourceQuery = gql`
- query SourceQuery($sourceId: ID = "default", $defaultIndex: [String!]!) {
- source(id: $sourceId) {
- id
- status {
- indicesExist(defaultIndex: $defaultIndex)
- indexFields(defaultIndex: $defaultIndex) {
- category
- description
- example
- indexes
- name
- searchable
- type
- aggregatable
- format
- esTypes
- subType
- }
- }
- }
- }
-`;
diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx
deleted file mode 100644
index 8ba7f7da7b8e3..0000000000000
--- a/x-pack/plugins/security_solution/public/common/containers/source/index.test.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { act, renderHook } from '@testing-library/react-hooks';
-
-import { useWithSource, indicesExistOrDataTemporarilyUnavailable } from '.';
-import { NO_ALERT_INDEX } from '../../../../common/constants';
-import { mockBrowserFields, mockIndexFields, mocksSource } from './mock';
-
-jest.mock('../../lib/kibana');
-jest.mock('../../utils/apollo_context', () => ({
- useApolloClient: jest.fn().mockReturnValue({
- query: jest.fn().mockImplementation(() => Promise.resolve(mocksSource[0].result)),
- }),
-}));
-
-describe('Index Fields & Browser Fields', () => {
- test('At initialization the value of indicesExists should be true', async () => {
- const { result, waitForNextUpdate } = renderHook(() => useWithSource());
- const initialResult = result.current;
-
- await waitForNextUpdate();
-
- return expect(initialResult).toEqual({
- browserFields: {},
- docValueFields: [],
- errorMessage: null,
- indexPattern: {
- fields: [],
- title:
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
- },
- indicesExist: true,
- loading: true,
- });
- });
-
- test('returns memoized value', async () => {
- const { result, waitForNextUpdate, rerender } = renderHook(() => useWithSource());
- await waitForNextUpdate();
-
- const result1 = result.current;
- act(() => rerender());
- const result2 = result.current;
-
- return expect(result1).toBe(result2);
- });
-
- test('Index Fields', async () => {
- const { result, waitForNextUpdate } = renderHook(() => useWithSource());
-
- await waitForNextUpdate();
-
- return expect(result).toEqual({
- current: {
- indicesExist: true,
- browserFields: mockBrowserFields,
- docValueFields: [
- {
- field: '@timestamp',
- format: 'date_time',
- },
- {
- field: 'event.end',
- format: 'date_time',
- },
- ],
- indexPattern: {
- fields: mockIndexFields,
- title:
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
- },
- loading: false,
- errorMessage: null,
- },
- error: undefined,
- });
- });
-
- test('Make sure we are not querying for NO_ALERT_INDEX and it is not includes in the index pattern', async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useWithSource('default', [NO_ALERT_INDEX])
- );
-
- await waitForNextUpdate();
- return expect(result.current.indexPattern.title).toEqual(
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*'
- );
- });
-
- describe('indicesExistOrDataTemporarilyUnavailable', () => {
- test('it returns true when undefined', () => {
- let undefVar;
- const result = indicesExistOrDataTemporarilyUnavailable(undefVar);
- expect(result).toBeTruthy();
- });
- test('it returns true when true', () => {
- const result = indicesExistOrDataTemporarilyUnavailable(true);
- expect(result).toBeTruthy();
- });
- test('it returns false when false', () => {
- const result = indicesExistOrDataTemporarilyUnavailable(false);
- expect(result).toBeFalsy();
- });
- });
-});
diff --git a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx
index ffbecf9e3d433..4b1db8a2871bd 100644
--- a/x-pack/plugins/security_solution/public/common/containers/source/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/containers/source/index.tsx
@@ -4,42 +4,30 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { isUndefined } from 'lodash';
import { set } from '@elastic/safer-lodash-set/fp';
-import { get, keyBy, pick, isEmpty } from 'lodash/fp';
-import { useEffect, useMemo, useState } from 'react';
+import { keyBy, pick, isEmpty, isEqual, isUndefined } from 'lodash/fp';
import memoizeOne from 'memoize-one';
+import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
+import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import { IIndexPattern } from 'src/plugins/data/public';
-import { DEFAULT_INDEX_KEY, NO_ALERT_INDEX } from '../../../../common/constants';
-import { useUiSetting$ } from '../../lib/kibana';
+import { useKibana } from '../../lib/kibana';
+import {
+ IndexField,
+ IndexFieldsStrategyResponse,
+ IndexFieldsStrategyRequest,
+ BrowserField,
+ BrowserFields,
+} from '../../../../common/search_strategy/index_fields';
+import { AbortError } from '../../../../../../../src/plugins/data/common';
+import * as i18n from './translations';
+import { SourcererScopeName } from '../../store/sourcerer/model';
+import { sourcererActions, sourcererSelectors } from '../../store/sourcerer';
-import { IndexField, SourceQuery } from '../../../graphql/types';
+import { State } from '../../store';
+import { DocValueFields } from '../../../../common/search_strategy/common';
-import { sourceQuery } from './index.gql_query';
-import { useApolloClient } from '../../utils/apollo_context';
-
-export { sourceQuery };
-
-export interface BrowserField {
- aggregatable: boolean;
- category: string;
- description: string | null;
- example: string | number | null;
- fields: Readonly>>;
- format: string;
- indexes: string[];
- name: string;
- searchable: boolean;
- type: string;
-}
-
-export interface DocValueFields {
- field: string;
- format: string;
-}
-
-export type BrowserFields = Readonly>>;
+export { BrowserField, BrowserFields, DocValueFields };
export const getAllBrowserFields = (browserFields: BrowserFields): Array> =>
Object.values(browserFields).reduce>>(
@@ -85,14 +73,12 @@ export const getDocValueFields = memoizeOne(
(_title: string, fields: IndexField[]): DocValueFields[] =>
fields && fields.length > 0
? fields.reduce((accumulator: DocValueFields[], field: IndexField) => {
- if (field.type === 'date' && accumulator.length < 100) {
- const format: string =
- field.format != null && !isEmpty(field.format) ? field.format : 'date_time';
+ if (field.readFromDocValues && accumulator.length < 100) {
return [
...accumulator,
{
field: field.name,
- format,
+ format: field.format,
},
];
}
@@ -107,115 +93,196 @@ export const indicesExistOrDataTemporarilyUnavailable = (
indicesExist: boolean | null | undefined
) => indicesExist || isUndefined(indicesExist);
-const EMPTY_BROWSER_FIELDS = {};
-const EMPTY_DOCVALUE_FIELD: DocValueFields[] = [];
+const DEFAULT_BROWSER_FIELDS = {};
+const DEFAULT_INDEX_PATTERNS = { fields: [], title: '' };
+const DEFAULT_DOC_VALUE_FIELDS: DocValueFields[] = [];
-interface UseWithSourceState {
+interface FetchIndexReturn {
browserFields: BrowserFields;
docValueFields: DocValueFields[];
- errorMessage: string | null;
- indexPattern: IIndexPattern;
- indicesExist: boolean | undefined | null;
- loading: boolean;
+ indexes: string[];
+ indexExists: boolean;
+ indexPatterns: IIndexPattern;
}
-export const useWithSource = (
- sourceId = 'default',
- indexToAdd?: string[] | null,
- onlyCheckIndexToAdd?: boolean,
- // Fun fact: When using this hook multiple times within a component (e.g. add_exception_modal & edit_exception_modal),
- // the apolloClient will perform queryDeduplication and prevent the first query from executing. A deep compare is not
- // performed on `indices`, so another field must be passed to circumvent this.
- // For details, see https://github.com/apollographql/react-apollo/issues/2202
- queryDeduplication = 'default'
-) => {
- const [configIndex] = useUiSetting$(DEFAULT_INDEX_KEY);
- const defaultIndex = useMemo(() => {
- const filterIndexAdd = (indexToAdd ?? []).filter((item) => item !== NO_ALERT_INDEX);
- if (!isEmpty(filterIndexAdd)) {
- return onlyCheckIndexToAdd ? filterIndexAdd : [...configIndex, ...filterIndexAdd];
- }
- return configIndex;
- }, [configIndex, indexToAdd, onlyCheckIndexToAdd]);
-
- const [state, setState] = useState({
- browserFields: EMPTY_BROWSER_FIELDS,
- docValueFields: EMPTY_DOCVALUE_FIELD,
- errorMessage: null,
- indexPattern: getIndexFields(defaultIndex.join(), []),
- indicesExist: indicesExistOrDataTemporarilyUnavailable(undefined),
- loading: true,
+export const useFetchIndex = (
+ indexNames: string[],
+ onlyCheckIfIndicesExist: boolean = false
+): [boolean, FetchIndexReturn] => {
+ const { data, notifications } = useKibana().services;
+ const abortCtrl = useRef(new AbortController());
+ const previousIndexesName = useRef([]);
+ const [isLoading, setLoading] = useState(true);
+
+ const [state, setState] = useState({
+ browserFields: DEFAULT_BROWSER_FIELDS,
+ docValueFields: DEFAULT_DOC_VALUE_FIELDS,
+ indexes: indexNames,
+ indexExists: true,
+ indexPatterns: DEFAULT_INDEX_PATTERNS,
});
- const apolloClient = useApolloClient();
+ const indexFieldsSearch = useCallback(
+ (iNames) => {
+ let didCancel = false;
+ const asyncSearch = async () => {
+ abortCtrl.current = new AbortController();
+ setLoading(true);
+ const searchSubscription$ = data.search
+ .search(
+ { indices: iNames, onlyCheckIfIndicesExist },
+ {
+ abortSignal: abortCtrl.current.signal,
+ strategy: 'securitySolutionIndexFields',
+ }
+ )
+ .subscribe({
+ next: (response) => {
+ if (!response.isPartial && !response.isRunning) {
+ if (!didCancel) {
+ const stringifyIndices = response.indicesExist.sort().join();
+ previousIndexesName.current = response.indicesExist;
+ setLoading(false);
+ setState({
+ browserFields: getBrowserFields(stringifyIndices, response.indexFields),
+ docValueFields: getDocValueFields(stringifyIndices, response.indexFields),
+ indexes: response.indicesExist,
+ indexExists: response.indicesExist.length > 0,
+ indexPatterns: getIndexFields(stringifyIndices, response.indexFields),
+ });
+ }
+ searchSubscription$.unsubscribe();
+ } else if (!didCancel && response.isPartial && !response.isRunning) {
+ setLoading(false);
+ notifications.toasts.addWarning(i18n.ERROR_BEAT_FIELDS);
+ searchSubscription$.unsubscribe();
+ }
+ },
+ error: (msg) => {
+ if (!didCancel) {
+ setLoading(false);
+ }
- useEffect(() => {
- let isSubscribed = true;
- const abortCtrl = new AbortController();
-
- async function fetchSource() {
- if (!apolloClient) return;
-
- setState((prevState) => ({ ...prevState, loading: true }));
-
- try {
- const result = await apolloClient.query<
- SourceQuery.Query,
- SourceQuery.Variables & { queryDeduplication: string }
- >({
- query: sourceQuery,
- fetchPolicy: 'cache-first',
- variables: {
- sourceId,
- defaultIndex,
- queryDeduplication,
- },
- context: {
- fetchOptions: {
- signal: abortCtrl.signal,
+ if (!(msg instanceof AbortError)) {
+ notifications.toasts.addDanger({
+ text: msg.message,
+ title: i18n.FAIL_BEAT_FIELDS,
+ });
+ }
},
- },
- });
-
- if (isSubscribed) {
- setState({
- loading: false,
- indicesExist: indicesExistOrDataTemporarilyUnavailable(
- get('data.source.status.indicesExist', result)
- ),
- browserFields: getBrowserFields(
- defaultIndex.join(),
- get('data.source.status.indexFields', result)
- ),
- docValueFields: getDocValueFields(
- defaultIndex.join(),
- get('data.source.status.indexFields', result)
- ),
- indexPattern: getIndexFields(
- defaultIndex.join(),
- get('data.source.status.indexFields', result)
- ),
- errorMessage: null,
});
- }
- } catch (error) {
- if (isSubscribed) {
- setState((prevState) => ({
- ...prevState,
- loading: false,
- errorMessage: error.message,
- }));
- }
- }
+ };
+ abortCtrl.current.abort();
+ asyncSearch();
+ return () => {
+ didCancel = true;
+ abortCtrl.current.abort();
+ };
+ },
+ [data.search, notifications.toasts, onlyCheckIfIndicesExist]
+ );
+
+ useEffect(() => {
+ if (!isEmpty(indexNames) && !isEqual(previousIndexesName.current, indexNames)) {
+ indexFieldsSearch(indexNames);
}
+ }, [indexNames, indexFieldsSearch, previousIndexesName]);
+
+ return [isLoading, state];
+};
+
+export const useIndexFields = (sourcererScopeName: SourcererScopeName) => {
+ const { data, notifications } = useKibana().services;
+ const abortCtrl = useRef(new AbortController());
+ const dispatch = useDispatch();
+ const previousIndexesName = useRef([]);
+
+ const indexNamesSelectedSelector = useMemo(
+ () => sourcererSelectors.getIndexNamesSelectedSelector(),
+ []
+ );
+ const indexNames = useSelector(
+ (state) => indexNamesSelectedSelector(state, sourcererScopeName),
+ shallowEqual
+ );
- fetchSource();
+ const setLoading = useCallback(
+ (loading: boolean) => {
+ dispatch(sourcererActions.setSourcererScopeLoading({ id: sourcererScopeName, loading }));
+ },
+ [dispatch, sourcererScopeName]
+ );
+
+ const indexFieldsSearch = useCallback(
+ (indicesName) => {
+ let didCancel = false;
+ const asyncSearch = async () => {
+ abortCtrl.current = new AbortController();
+ setLoading(true);
+ const searchSubscription$ = data.search
+ .search(
+ { indices: indicesName, onlyCheckIfIndicesExist: false },
+ {
+ abortSignal: abortCtrl.current.signal,
+ strategy: 'securitySolutionIndexFields',
+ }
+ )
+ .subscribe({
+ next: (response) => {
+ if (!response.isPartial && !response.isRunning) {
+ if (!didCancel) {
+ const stringifyIndices = response.indicesExist.sort().join();
+ previousIndexesName.current = response.indicesExist;
+ dispatch(
+ sourcererActions.setSource({
+ id: sourcererScopeName,
+ payload: {
+ browserFields: getBrowserFields(stringifyIndices, response.indexFields),
+ docValueFields: getDocValueFields(stringifyIndices, response.indexFields),
+ errorMessage: null,
+ id: sourcererScopeName,
+ indexPattern: getIndexFields(stringifyIndices, response.indexFields),
+ indicesExist: response.indicesExist.length > 0,
+ loading: false,
+ },
+ })
+ );
+ }
+ searchSubscription$.unsubscribe();
+ } else if (!didCancel && response.isPartial && !response.isRunning) {
+ // TODO: Make response error status clearer
+ setLoading(false);
+ notifications.toasts.addWarning(i18n.ERROR_BEAT_FIELDS);
+ searchSubscription$.unsubscribe();
+ }
+ },
+ error: (msg) => {
+ if (!didCancel) {
+ setLoading(false);
+ }
- return () => {
- isSubscribed = false;
- return abortCtrl.abort();
- };
- }, [apolloClient, sourceId, defaultIndex, queryDeduplication]);
+ if (!(msg instanceof AbortError)) {
+ notifications.toasts.addDanger({
+ text: msg.message,
+ title: i18n.FAIL_BEAT_FIELDS,
+ });
+ }
+ },
+ });
+ };
+ abortCtrl.current.abort();
+ asyncSearch();
+ return () => {
+ didCancel = true;
+ abortCtrl.current.abort();
+ };
+ },
+ [data.search, dispatch, notifications.toasts, setLoading, sourcererScopeName]
+ );
- return state;
+ useEffect(() => {
+ if (!isEmpty(indexNames) && !isEqual(previousIndexesName.current, indexNames)) {
+ indexFieldsSearch(indexNames);
+ }
+ }, [indexNames, indexFieldsSearch, previousIndexesName]);
};
diff --git a/x-pack/plugins/security_solution/public/common/containers/source/mock.ts b/x-pack/plugins/security_solution/public/common/containers/source/mock.ts
index bba6a15d73970..7fcd11f71f081 100644
--- a/x-pack/plugins/security_solution/public/common/containers/source/mock.ts
+++ b/x-pack/plugins/security_solution/public/common/containers/source/mock.ts
@@ -5,347 +5,296 @@
*/
import { DEFAULT_INDEX_PATTERN } from '../../../../common/constants';
+import { DocValueFields } from '../../../../common/search_strategy';
+import { BrowserFields } from '../../../../common/search_strategy/index_fields';
-import { BrowserFields, DocValueFields } from '.';
-import { sourceQuery } from './index.gql_query';
-
-export const mocksSource = [
- {
- request: {
- query: sourceQuery,
- variables: {
- sourceId: 'default',
- defaultIndex: DEFAULT_INDEX_PATTERN,
- },
+export const mocksSource = {
+ indexFields: [
+ {
+ category: 'base',
+ description:
+ 'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.',
+ example: '2016-05-23T08:05:34.853Z',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: '@timestamp',
+ searchable: true,
+ type: 'date',
+ aggregatable: true,
},
- result: {
- data: {
- source: {
- id: 'default',
- configuration: {},
- status: {
- indicesExist: true,
- winlogbeatIndices: [
- 'winlogbeat-7.0.0-2019.02.17',
- 'winlogbeat-7.0.0-2019.02.18',
- 'winlogbeat-7.0.0-2019.02.19',
- 'winlogbeat-7.0.0-2019.02.20',
- 'winlogbeat-7.0.0-2019.02.21',
- 'winlogbeat-7.0.0-2019.02.21-000001',
- 'winlogbeat-7.0.0-2019.02.22',
- 'winlogbeat-8.0.0-2019.02.19-000001',
- ],
- auditbeatIndices: [
- 'auditbeat-7.0.0-2019.02.17',
- 'auditbeat-7.0.0-2019.02.18',
- 'auditbeat-7.0.0-2019.02.19',
- 'auditbeat-7.0.0-2019.02.20',
- 'auditbeat-7.0.0-2019.02.21',
- 'auditbeat-7.0.0-2019.02.21-000001',
- 'auditbeat-7.0.0-2019.02.22',
- 'auditbeat-8.0.0-2019.02.19-000001',
- ],
- filebeatIndices: [
- 'filebeat-7.0.0-iot-2019.06',
- 'filebeat-7.0.0-iot-2019.07',
- 'filebeat-7.0.0-iot-2019.08',
- 'filebeat-7.0.0-iot-2019.09',
- 'filebeat-7.0.0-iot-2019.10',
- 'filebeat-8.0.0-2019.02.19-000001',
- ],
- indexFields: [
- {
- category: 'base',
- description:
- 'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.',
- example: '2016-05-23T08:05:34.853Z',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: '@timestamp',
- searchable: true,
- type: 'date',
- aggregatable: true,
- },
- {
- category: 'agent',
- description:
- 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.',
- example: '8a4f500f',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'agent.ephemeral_id',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'agent',
- description: null,
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'agent.hostname',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'agent',
- description:
- 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.',
- example: '8a4f500d',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'agent.id',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'agent',
- description:
- 'Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.',
- example: 'foo',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'agent.name',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'auditd',
- description: null,
- example: null,
- format: '',
- indexes: ['auditbeat'],
- name: 'auditd.data.a0',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'auditd',
- description: null,
- example: null,
- format: '',
- indexes: ['auditbeat'],
- name: 'auditd.data.a1',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'auditd',
- description: null,
- example: null,
- format: '',
- indexes: ['auditbeat'],
- name: 'auditd.data.a2',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'client',
- description:
- 'Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'client.address',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'client',
- description: 'Bytes sent from the client to the server.',
- example: '184',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'client.bytes',
- searchable: true,
- type: 'number',
- aggregatable: true,
- },
- {
- category: 'client',
- description: 'Client domain.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'client.domain',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'client',
- description: 'Country ISO code.',
- example: 'CA',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'client.geo.country_iso_code',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'cloud',
- description:
- 'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.',
- example: '666777888999',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'cloud.account.id',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'cloud',
- description: 'Availability zone in which this host is running.',
- example: 'us-east-1c',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'cloud.availability_zone',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'container',
- description: 'Unique container id.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'container.id',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'container',
- description: 'Name of the image the container was built on.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'container.image.name',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'container',
- description: 'Container image tag.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'container.image.tag',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'destination',
- description:
- 'Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'destination.address',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- category: 'destination',
- description: 'Bytes sent from the destination to the source.',
- example: '184',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'destination.bytes',
- searchable: true,
- type: 'number',
- aggregatable: true,
- },
- {
- category: 'destination',
- description: 'Destination domain.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'destination.domain',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- {
- aggregatable: true,
- category: 'destination',
- description:
- 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.',
- example: '',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'destination.ip',
- searchable: true,
- type: 'ip',
- },
- {
- aggregatable: true,
- category: 'destination',
- description: 'Port of the destination.',
- example: '',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'destination.port',
- searchable: true,
- type: 'long',
- },
- {
- aggregatable: true,
- category: 'source',
- description:
- 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.',
- example: '',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'source.ip',
- searchable: true,
- type: 'ip',
- },
- {
- aggregatable: true,
- category: 'source',
- description: 'Port of the source.',
- example: '',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'source.port',
- searchable: true,
- type: 'long',
- },
- {
- aggregatable: true,
- category: 'event',
- description:
- 'event.end contains the date when the event ended or when the activity was last observed.',
- example: null,
- format: '',
- indexes: DEFAULT_INDEX_PATTERN,
- name: 'event.end',
- searchable: true,
- type: 'date',
- },
- ],
- },
- },
- },
+ {
+ category: 'agent',
+ description:
+ 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.',
+ example: '8a4f500f',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'agent.ephemeral_id',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
},
- },
-];
+ {
+ category: 'agent',
+ description: null,
+ example: null,
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'agent.hostname',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'agent',
+ description:
+ 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.',
+ example: '8a4f500d',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'agent.id',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'agent',
+ description:
+ 'Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.',
+ example: 'foo',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'agent.name',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'auditd',
+ description: null,
+ example: null,
+ format: '',
+ indexes: ['auditbeat'],
+ name: 'auditd.data.a0',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'auditd',
+ description: null,
+ example: null,
+ format: '',
+ indexes: ['auditbeat'],
+ name: 'auditd.data.a1',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'auditd',
+ description: null,
+ example: null,
+ format: '',
+ indexes: ['auditbeat'],
+ name: 'auditd.data.a2',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'client',
+ description:
+ 'Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.',
+ example: null,
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'client.address',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'client',
+ description: 'Bytes sent from the client to the server.',
+ example: '184',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'client.bytes',
+ searchable: true,
+ type: 'number',
+ aggregatable: true,
+ },
+ {
+ category: 'client',
+ description: 'Client domain.',
+ example: null,
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'client.domain',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'client',
+ description: 'Country ISO code.',
+ example: 'CA',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'client.geo.country_iso_code',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'cloud',
+ description:
+ 'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.',
+ example: '666777888999',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'cloud.account.id',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'cloud',
+ description: 'Availability zone in which this host is running.',
+ example: 'us-east-1c',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'cloud.availability_zone',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'container',
+ description: 'Unique container id.',
+ example: null,
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'container.id',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'container',
+ description: 'Name of the image the container was built on.',
+ example: null,
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'container.image.name',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'container',
+ description: 'Container image tag.',
+ example: null,
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'container.image.tag',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'destination',
+ description:
+ 'Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.',
+ example: null,
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'destination.address',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ category: 'destination',
+ description: 'Bytes sent from the destination to the source.',
+ example: '184',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'destination.bytes',
+ searchable: true,
+ type: 'number',
+ aggregatable: true,
+ },
+ {
+ category: 'destination',
+ description: 'Destination domain.',
+ example: null,
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'destination.domain',
+ searchable: true,
+ type: 'string',
+ aggregatable: true,
+ },
+ {
+ aggregatable: true,
+ category: 'destination',
+ description: 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.',
+ example: '',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'destination.ip',
+ searchable: true,
+ type: 'ip',
+ },
+ {
+ aggregatable: true,
+ category: 'destination',
+ description: 'Port of the destination.',
+ example: '',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'destination.port',
+ searchable: true,
+ type: 'long',
+ },
+ {
+ aggregatable: true,
+ category: 'source',
+ description: 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.',
+ example: '',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'source.ip',
+ searchable: true,
+ type: 'ip',
+ },
+ {
+ aggregatable: true,
+ category: 'source',
+ description: 'Port of the source.',
+ example: '',
+ format: '',
+ indexes: ['auditbeat', 'filebeat', 'packetbeat'],
+ name: 'source.port',
+ searchable: true,
+ type: 'long',
+ },
+ {
+ aggregatable: true,
+ category: 'event',
+ description:
+ 'event.end contains the date when the event ended or when the activity was last observed.',
+ example: null,
+ format: '',
+ indexes: DEFAULT_INDEX_PATTERN,
+ name: 'event.end',
+ searchable: true,
+ type: 'date',
+ },
+ ],
+};
export const mockIndexFields = [
{ aggregatable: true, name: '@timestamp', searchable: true, type: 'date' },
diff --git a/x-pack/plugins/security_solution/public/common/containers/source/translations.ts b/x-pack/plugins/security_solution/public/common/containers/source/translations.ts
new file mode 100644
index 0000000000000..f12a9a0b41a7b
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/containers/source/translations.ts
@@ -0,0 +1,21 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { i18n } from '@kbn/i18n';
+
+export const ERROR_BEAT_FIELDS = i18n.translate(
+ 'xpack.securitySolution.beatFields.errorSearchDescription',
+ {
+ defaultMessage: `An error has occurred on getting beat fields`,
+ }
+);
+
+export const FAIL_BEAT_FIELDS = i18n.translate(
+ 'xpack.securitySolution.beatFields.failSearchDescription',
+ {
+ defaultMessage: `Failed to run search on beat fields`,
+ }
+);
diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/constants.ts b/x-pack/plugins/security_solution/public/common/containers/sourcerer/constants.ts
index 106294ba54f5a..be3d074811032 100644
--- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/constants.ts
+++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/constants.ts
@@ -4,26 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const SOURCERER_FEATURE_FLAG_ON = false;
-
-export enum SecurityPageName {
- default = 'default',
- host = 'host',
- detections = 'detections',
- timeline = 'timeline',
- network = 'network',
-}
-
-export type SourceGroupsType = keyof typeof SecurityPageName;
-
-export const sourceGroups = {
- [SecurityPageName.default]: [
- 'apm-*-transaction*',
- 'auditbeat-*',
- 'endgame-*',
- 'filebeat-*',
- 'logs-*',
- 'winlogbeat-*',
- 'blobbeat-*',
- ],
-};
+export const SOURCERER_FEATURE_FLAG_ON = true;
diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.test.tsx
deleted file mode 100644
index b8017df09b738..0000000000000
--- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.test.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { indicesExistOrDataTemporarilyUnavailable } from './format';
-
-describe('indicesExistOrDataTemporarilyUnavailable', () => {
- it('it returns true when undefined', () => {
- let undefVar;
- const result = indicesExistOrDataTemporarilyUnavailable(undefVar);
- expect(result).toBeTruthy();
- });
- it('it returns true when true', () => {
- const result = indicesExistOrDataTemporarilyUnavailable(true);
- expect(result).toBeTruthy();
- });
- it('it returns false when false', () => {
- const result = indicesExistOrDataTemporarilyUnavailable(false);
- expect(result).toBeFalsy();
- });
-});
diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.ts b/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.ts
deleted file mode 100644
index 8c9a16ed705ef..0000000000000
--- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/format.ts
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { isEmpty, pick } from 'lodash/fp';
-import memoizeOne from 'memoize-one';
-import { set } from '@elastic/safer-lodash-set/fp';
-import { isUndefined } from 'lodash';
-import { IndexField } from '../../../graphql/types';
-import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns';
-
-export interface BrowserField {
- aggregatable: boolean;
- category: string;
- description: string | null;
- example: string | number | null;
- fields: Readonly>>;
- format: string;
- indexes: string[];
- name: string;
- searchable: boolean;
- type: string;
-}
-
-export interface DocValueFields {
- field: string;
- format: string;
-}
-
-export type BrowserFields = Readonly>>;
-
-export const getAllBrowserFields = (browserFields: BrowserFields): Array> =>
- Object.values(browserFields).reduce>>(
- (acc, namespace) => [
- ...acc,
- ...Object.values(namespace.fields != null ? namespace.fields : {}),
- ],
- []
- );
-
-export const getIndexFields = memoizeOne(
- (title: string, fields: IndexField[]): IIndexPattern =>
- fields && fields.length > 0
- ? {
- fields: fields.map((field) =>
- pick(['name', 'searchable', 'type', 'aggregatable', 'esTypes', 'subType'], field)
- ),
- title,
- }
- : { fields: [], title },
- (newArgs, lastArgs) => newArgs[0] === lastArgs[0] && newArgs[1].length === lastArgs[1].length
-);
-
-export const getBrowserFields = memoizeOne(
- (_title: string, fields: IndexField[]): BrowserFields =>
- fields && fields.length > 0
- ? fields.reduce(
- (accumulator: BrowserFields, field: IndexField) =>
- set([field.category, 'fields', field.name], field, accumulator),
- {}
- )
- : {},
- // Update the value only if _title has changed
- (newArgs, lastArgs) => newArgs[0] === lastArgs[0]
-);
-
-export const getDocValueFields = memoizeOne(
- (_title: string, fields: IndexField[]): DocValueFields[] =>
- fields && fields.length > 0
- ? fields.reduce((accumulator: DocValueFields[], field: IndexField) => {
- if (field.type === 'date' && accumulator.length < 100) {
- const format: string =
- field.format != null && !isEmpty(field.format) ? field.format : 'date_time';
- return [
- ...accumulator,
- {
- field: field.name,
- format,
- },
- ];
- }
- return accumulator;
- }, [])
- : [],
- // Update the value only if _title has changed
- (newArgs, lastArgs) => newArgs[0] === lastArgs[0]
-);
-
-export const indicesExistOrDataTemporarilyUnavailable = (
- indicesExist: boolean | null | undefined
-) => indicesExist || isUndefined(indicesExist);
-
-export const EMPTY_BROWSER_FIELDS = {};
-export const EMPTY_DOCVALUE_FIELD: DocValueFields[] = [];
diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx
index 38af84e0968f8..673db7af2b5e6 100644
--- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.test.tsx
@@ -4,28 +4,73 @@
* you may not use this file except in compliance with the Elastic License.
*/
+/* eslint-disable react/display-name */
+
+import React from 'react';
import { act, renderHook } from '@testing-library/react-hooks';
+import { Provider } from 'react-redux';
-import { getSourceDefaults, useSourceManager, UseSourceManager } from '.';
+import { useInitSourcerer } from '.';
+import { mockPatterns, mockSource } from './mocks';
+// import { SourcererScopeName } from '../../store/sourcerer/model';
+import { RouteSpyState } from '../../utils/route/types';
+import { SecurityPageName } from '../../../../common/constants';
+import { createStore, State } from '../../store';
import {
- mockSourceSelections,
- mockSourceGroup,
- mockSourceGroups,
- mockPatterns,
- mockSource,
-} from './mocks';
-import { SecurityPageName } from './constants';
-const mockSourceDefaults = mockSource(SecurityPageName.default);
+ apolloClientObservable,
+ createSecuritySolutionStorageMock,
+ kibanaObservable,
+ mockGlobalState,
+ SUB_PLUGINS_REDUCER,
+} from '../../mock';
+const mockSourceDefaults = mockSource;
+
+const mockRouteSpy: RouteSpyState = {
+ pageName: SecurityPageName.overview,
+ detailName: undefined,
+ tabName: undefined,
+ search: '',
+ pathName: '/',
+};
+const mockDispatch = jest.fn();
+jest.mock('react-redux', () => {
+ const original = jest.requireActual('react-redux');
+
+ return {
+ ...original,
+ useDispatch: () => mockDispatch,
+ };
+});
+jest.mock('../../utils/route/use_route_spy', () => ({
+ useRouteSpy: () => [mockRouteSpy],
+}));
jest.mock('../../lib/kibana', () => ({
useKibana: jest.fn().mockReturnValue({
services: {
+ application: {
+ capabilities: {
+ siem: {
+ crud: true,
+ },
+ },
+ },
data: {
indexPatterns: {
getTitles: jest.fn().mockImplementation(() => Promise.resolve(mockPatterns)),
},
+ search: {
+ search: jest.fn().mockImplementation(() => ({
+ subscribe: jest.fn().mockImplementation(() => ({
+ error: jest.fn(),
+ next: jest.fn(),
+ })),
+ })),
+ },
},
+ notifications: {},
},
}),
+ useUiSetting$: jest.fn().mockImplementation(() => [mockPatterns]),
}));
jest.mock('../../utils/apollo_context', () => ({
useApolloClient: jest.fn().mockReturnValue({
@@ -34,148 +79,193 @@ jest.mock('../../utils/apollo_context', () => ({
}));
describe('Sourcerer Hooks', () => {
- const testId = SecurityPageName.default;
- const uninitializedId = SecurityPageName.host;
+ // const testId = SourcererScopeName.default;
+ // const uninitializedId = SourcererScopeName.detections;
beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});
+ const state: State = mockGlobalState;
+ const { storage } = createSecuritySolutionStorageMock();
+ let store = createStore(
+ state,
+ SUB_PLUGINS_REDUCER,
+ apolloClientObservable,
+ kibanaObservable,
+ storage
+ );
+
+ beforeEach(() => {
+ store = createStore(
+ state,
+ SUB_PLUGINS_REDUCER,
+ apolloClientObservable,
+ kibanaObservable,
+ storage
+ );
+ });
describe('Initialization', () => {
- it('initializes loading default index patterns', async () => {
+ it('initializes loading default and timeline index patterns', async () => {
await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useSourceManager()
- );
- await waitForNextUpdate();
- expect(result.current).toEqual({
- activeSourceGroupId: 'default',
- availableIndexPatterns: [],
- availableSourceGroupIds: [],
- isIndexPatternsLoading: true,
- sourceGroups: {},
- getManageSourceGroupById: result.current.getManageSourceGroupById,
- initializeSourceGroup: result.current.initializeSourceGroup,
- setActiveSourceGroupId: result.current.setActiveSourceGroupId,
- updateSourceGroupIndicies: result.current.updateSourceGroupIndicies,
+ const { waitForNextUpdate } = renderHook(() => useInitSourcerer(), {
+ wrapper: ({ children }) => {children},
});
- });
- });
- it('initializes loading default source group', async () => {
- await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useSourceManager()
- );
await waitForNextUpdate();
await waitForNextUpdate();
- expect(result.current).toEqual({
- activeSourceGroupId: 'default',
- availableIndexPatterns: mockPatterns,
- availableSourceGroupIds: [],
- isIndexPatternsLoading: false,
- sourceGroups: {},
- getManageSourceGroupById: result.current.getManageSourceGroupById,
- initializeSourceGroup: result.current.initializeSourceGroup,
- setActiveSourceGroupId: result.current.setActiveSourceGroupId,
- updateSourceGroupIndicies: result.current.updateSourceGroupIndicies,
+ expect(mockDispatch).toBeCalledTimes(2);
+ expect(mockDispatch.mock.calls[0][0]).toEqual({
+ type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING',
+ payload: { id: 'default', loading: true },
});
- });
- });
- it('initialize completes with formatted source group data', async () => {
- await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useSourceManager()
- );
- await waitForNextUpdate();
- await waitForNextUpdate();
- await waitForNextUpdate();
- expect(result.current).toEqual({
- activeSourceGroupId: testId,
- availableIndexPatterns: mockPatterns,
- availableSourceGroupIds: [testId],
- isIndexPatternsLoading: false,
- sourceGroups: {
- default: mockSourceGroup(testId),
- },
- getManageSourceGroupById: result.current.getManageSourceGroupById,
- initializeSourceGroup: result.current.initializeSourceGroup,
- setActiveSourceGroupId: result.current.setActiveSourceGroupId,
- updateSourceGroupIndicies: result.current.updateSourceGroupIndicies,
+ expect(mockDispatch.mock.calls[1][0]).toEqual({
+ type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING',
+ payload: { id: 'timeline', loading: true },
});
+ // expect(mockDispatch.mock.calls[1][0]).toEqual({
+ // type: 'x-pack/security_solution/local/sourcerer/SET_INDEX_PATTERNS_LIST',
+ // payload: { allIndexPatterns: mockPatterns, kibanaIndexPatterns: [] },
+ // });
});
});
+ // TO DO sourcerer @S
+ // it('initializes loading default source group', async () => {
+ // await act(async () => {
+ // const { result, waitForNextUpdate } = renderHook(
+ // () => useInitSourcerer(),
+ // {
+ // wrapper: ({ children }) => {children},
+ // }
+ // );
+ // await waitForNextUpdate();
+ // await waitForNextUpdate();
+ // expect(result.current).toEqual({
+ // activeSourcererScopeId: 'default',
+ // kibanaIndexPatterns: mockPatterns,
+ // isIndexPatternsLoading: false,
+ // getSourcererScopeById: result.current.getSourcererScopeById,
+ // setActiveSourcererScopeId: result.current.setActiveSourcererScopeId,
+ // updateSourcererScopeIndices: result.current.updateSourcererScopeIndices,
+ // });
+ // });
+ // });
+ // it('initialize completes with formatted source group data', async () => {
+ // await act(async () => {
+ // const { result, waitForNextUpdate } = renderHook(
+ // () => useInitSourcerer(),
+ // {
+ // wrapper: ({ children }) => {children},
+ // }
+ // );
+ // await waitForNextUpdate();
+ // await waitForNextUpdate();
+ // await waitForNextUpdate();
+ // expect(result.current).toEqual({
+ // activeSourcererScopeId: testId,
+ // kibanaIndexPatterns: mockPatterns,
+ // isIndexPatternsLoading: false,
+ // getSourcererScopeById: result.current.getSourcererScopeById,
+ // setActiveSourcererScopeId: result.current.setActiveSourcererScopeId,
+ // updateSourcererScopeIndices: result.current.updateSourcererScopeIndices,
+ // });
+ // });
+ // });
});
- describe('Methods', () => {
- it('getManageSourceGroupById: initialized source group returns defaults', async () => {
- await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useSourceManager()
- );
- await waitForNextUpdate();
- await waitForNextUpdate();
- await waitForNextUpdate();
- const initializedSourceGroup = result.current.getManageSourceGroupById(testId);
- expect(initializedSourceGroup).toEqual(mockSourceGroup(testId));
- });
- });
- it('getManageSourceGroupById: uninitialized source group returns defaults', async () => {
- await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useSourceManager()
- );
- await waitForNextUpdate();
- await waitForNextUpdate();
- await waitForNextUpdate();
- const uninitializedSourceGroup = result.current.getManageSourceGroupById(uninitializedId);
- expect(uninitializedSourceGroup).toEqual(getSourceDefaults(uninitializedId, mockPatterns));
- });
- });
- it('initializeSourceGroup: initializes source group', async () => {
- await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useSourceManager()
- );
- await waitForNextUpdate();
- await waitForNextUpdate();
- await waitForNextUpdate();
- result.current.initializeSourceGroup(
- uninitializedId,
- mockSourceGroups[uninitializedId],
- true
- );
- await waitForNextUpdate();
- const initializedSourceGroup = result.current.getManageSourceGroupById(uninitializedId);
- expect(initializedSourceGroup.indexPatterns).toEqual(mockSourceSelections[uninitializedId]);
- });
- });
- it('setActiveSourceGroupId: active source group id gets set only if it gets initialized first', async () => {
- await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useSourceManager()
- );
- await waitForNextUpdate();
- expect(result.current.activeSourceGroupId).toEqual(testId);
- result.current.setActiveSourceGroupId(uninitializedId);
- expect(result.current.activeSourceGroupId).toEqual(testId);
- result.current.initializeSourceGroup(uninitializedId);
- result.current.setActiveSourceGroupId(uninitializedId);
- expect(result.current.activeSourceGroupId).toEqual(uninitializedId);
- });
- });
- it('updateSourceGroupIndicies: updates source group indicies', async () => {
- await act(async () => {
- const { result, waitForNextUpdate } = renderHook(() =>
- useSourceManager()
- );
- await waitForNextUpdate();
- await waitForNextUpdate();
- await waitForNextUpdate();
- let sourceGroup = result.current.getManageSourceGroupById(testId);
- expect(sourceGroup.indexPatterns).toEqual(mockSourceSelections[testId]);
- result.current.updateSourceGroupIndicies(testId, ['endgame-*', 'filebeat-*']);
- await waitForNextUpdate();
- sourceGroup = result.current.getManageSourceGroupById(testId);
- expect(sourceGroup.indexPatterns).toEqual(['endgame-*', 'filebeat-*']);
- });
- });
- });
+ // describe('Methods', () => {
+ // it('getSourcererScopeById: initialized source group returns defaults', async () => {
+ // await act(async () => {
+ // const { result, waitForNextUpdate } = renderHook(
+ // () => useInitSourcerer(),
+ // {
+ // wrapper: ({ children }) => {children},
+ // }
+ // );
+ // await waitForNextUpdate();
+ // await waitForNextUpdate();
+ // await waitForNextUpdate();
+ // const initializedSourcererScope = result.current.getSourcererScopeById(testId);
+ // expect(initializedSourcererScope).toEqual(mockSourcererScope(testId));
+ // });
+ // });
+ // it('getSourcererScopeById: uninitialized source group returns defaults', async () => {
+ // await act(async () => {
+ // const { result, waitForNextUpdate } = renderHook(
+ // () => useInitSourcerer(),
+ // {
+ // wrapper: ({ children }) => {children},
+ // }
+ // );
+ // await waitForNextUpdate();
+ // await waitForNextUpdate();
+ // await waitForNextUpdate();
+ // const uninitializedSourcererScope = result.current.getSourcererScopeById(uninitializedId);
+ // expect(uninitializedSourcererScope).toEqual(
+ // getSourceDefaults(uninitializedId, mockPatterns)
+ // );
+ // });
+ // });
+ // // it('initializeSourcererScope: initializes source group', async () => {
+ // // await act(async () => {
+ // // const { result, waitForNextUpdate } = renderHook(
+ // // () => useSourcerer(),
+ // // {
+ // // wrapper: ({ children }) => {children},
+ // // }
+ // // );
+ // // await waitForNextUpdate();
+ // // await waitForNextUpdate();
+ // // await waitForNextUpdate();
+ // // result.current.initializeSourcererScope(
+ // // uninitializedId,
+ // // mockSourcererScopes[uninitializedId],
+ // // true
+ // // );
+ // // await waitForNextUpdate();
+ // // const initializedSourcererScope = result.current.getSourcererScopeById(uninitializedId);
+ // // expect(initializedSourcererScope.selectedPatterns).toEqual(
+ // // mockSourcererScopes[uninitializedId]
+ // // );
+ // // });
+ // // });
+ // it('setActiveSourcererScopeId: active source group id gets set only if it gets initialized first', async () => {
+ // await act(async () => {
+ // const { result, waitForNextUpdate } = renderHook(
+ // () => useInitSourcerer(),
+ // {
+ // wrapper: ({ children }) => {children},
+ // }
+ // );
+ // await waitForNextUpdate();
+ // expect(result.current.activeSourcererScopeId).toEqual(testId);
+ // result.current.setActiveSourcererScopeId(uninitializedId);
+ // expect(result.current.activeSourcererScopeId).toEqual(testId);
+ // // result.current.initializeSourcererScope(uninitializedId);
+ // result.current.setActiveSourcererScopeId(uninitializedId);
+ // expect(result.current.activeSourcererScopeId).toEqual(uninitializedId);
+ // });
+ // });
+ // it('updateSourcererScopeIndices: updates source group indices', async () => {
+ // await act(async () => {
+ // const { result, waitForNextUpdate } = renderHook(
+ // () => useInitSourcerer(),
+ // {
+ // wrapper: ({ children }) => {children},
+ // }
+ // );
+ // await waitForNextUpdate();
+ // await waitForNextUpdate();
+ // await waitForNextUpdate();
+ // let sourceGroup = result.current.getSourcererScopeById(testId);
+ // expect(sourceGroup.selectedPatterns).toEqual(mockSourcererScopes[testId]);
+ // expect(sourceGroup.scopePatterns).toEqual(mockSourcererScopes[testId]);
+ // result.current.updateSourcererScopeIndices({
+ // id: testId,
+ // selectedPatterns: ['endgame-*', 'filebeat-*'],
+ // });
+ // await waitForNextUpdate();
+ // sourceGroup = result.current.getSourcererScopeById(testId);
+ // expect(sourceGroup.scopePatterns).toEqual(mockSourcererScopes[testId]);
+ // expect(sourceGroup.selectedPatterns).toEqual(['endgame-*', 'filebeat-*']);
+ // });
+ // });
+ // });
});
diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx
index 91907b45aa449..afacd68d71592 100644
--- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/index.tsx
@@ -4,412 +4,72 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { get, noop, isEmpty } from 'lodash/fp';
-import React, { createContext, useCallback, useContext, useEffect, useReducer } from 'react';
-import { IIndexPattern } from 'src/plugins/data/public';
-
-import { NO_ALERT_INDEX } from '../../../../common/constants';
-import { useKibana } from '../../lib/kibana';
-
-import { SourceQuery } from '../../../graphql/types';
-
-import { sourceQuery } from '../source/index.gql_query';
-import { useApolloClient } from '../../utils/apollo_context';
-import {
- sourceGroups,
- SecurityPageName,
- SourceGroupsType,
- SOURCERER_FEATURE_FLAG_ON,
-} from './constants';
-import {
- BrowserFields,
- DocValueFields,
- EMPTY_BROWSER_FIELDS,
- EMPTY_DOCVALUE_FIELD,
- getBrowserFields,
- getDocValueFields,
- getIndexFields,
- indicesExistOrDataTemporarilyUnavailable,
-} from './format';
-
-// TYPES
-interface ManageSource {
- browserFields: BrowserFields;
- defaultPatterns: string[];
- docValueFields: DocValueFields[];
- errorMessage: string | null;
- id: SourceGroupsType;
- indexPattern: IIndexPattern;
- indexPatterns: string[];
- indicesExist: boolean | undefined | null;
- loading: boolean;
-}
-
-interface ManageSourceInit extends Partial {
- id: SourceGroupsType;
-}
-
-type ManageSourceGroupById = {
- [id in SourceGroupsType]?: ManageSource;
-};
-
-type ActionManageSource =
- | {
- type: 'SET_SOURCE';
- id: SourceGroupsType;
- defaultIndex: string[];
- payload: ManageSourceInit;
- }
- | {
- type: 'SET_IS_SOURCE_LOADING';
- id: SourceGroupsType;
- payload: boolean;
- }
- | {
- type: 'SET_ACTIVE_SOURCE_GROUP_ID';
- payload: SourceGroupsType;
- }
- | {
- type: 'SET_AVAILABLE_INDEX_PATTERNS';
- payload: string[];
- }
- | {
- type: 'SET_IS_INDEX_PATTERNS_LOADING';
- payload: boolean;
- };
-
-interface ManageSourcerer {
- activeSourceGroupId: SourceGroupsType;
- availableIndexPatterns: string[];
- availableSourceGroupIds: SourceGroupsType[];
- isIndexPatternsLoading: boolean;
- sourceGroups: ManageSourceGroupById;
-}
-
-export interface UseSourceManager extends ManageSourcerer {
- getManageSourceGroupById: (id: SourceGroupsType) => ManageSource;
- initializeSourceGroup: (
- id: SourceGroupsType,
- indexToAdd?: string[] | null,
- onlyCheckIndexToAdd?: boolean
- ) => void;
- setActiveSourceGroupId: (id: SourceGroupsType) => void;
- updateSourceGroupIndicies: (id: SourceGroupsType, updatedIndicies: string[]) => void;
-}
-
-// DEFAULTS/INIT
-export const getSourceDefaults = (id: SourceGroupsType, defaultIndex: string[]) => ({
- browserFields: EMPTY_BROWSER_FIELDS,
- defaultPatterns: defaultIndex,
- docValueFields: EMPTY_DOCVALUE_FIELD,
- errorMessage: null,
- id,
- indexPattern: getIndexFields(defaultIndex.join(), []),
- indexPatterns: defaultIndex,
- indicesExist: indicesExistOrDataTemporarilyUnavailable(undefined),
- loading: true,
-});
-
-const initManageSource: ManageSourcerer = {
- activeSourceGroupId: SecurityPageName.default,
- availableIndexPatterns: [],
- availableSourceGroupIds: [],
- isIndexPatternsLoading: true,
- sourceGroups: {},
-};
-const init: UseSourceManager = {
- ...initManageSource,
- getManageSourceGroupById: (id: SourceGroupsType) => getSourceDefaults(id, []),
- initializeSourceGroup: () => noop,
- setActiveSourceGroupId: () => noop,
- updateSourceGroupIndicies: () => noop,
-};
-
-const reducerManageSource = (state: ManageSourcerer, action: ActionManageSource) => {
- switch (action.type) {
- case 'SET_SOURCE':
- return {
- ...state,
- sourceGroups: {
- ...state.sourceGroups,
- [action.id]: {
- ...getSourceDefaults(action.id, action.defaultIndex),
- ...state.sourceGroups[action.id],
- ...action.payload,
- },
- },
- availableSourceGroupIds: state.availableSourceGroupIds.includes(action.id)
- ? state.availableSourceGroupIds
- : [...state.availableSourceGroupIds, action.id],
- };
- case 'SET_IS_SOURCE_LOADING':
- return {
- ...state,
- sourceGroups: {
- ...state.sourceGroups,
- [action.id]: {
- ...state.sourceGroups[action.id],
- id: action.id,
- loading: action.payload,
- },
- },
- };
- case 'SET_ACTIVE_SOURCE_GROUP_ID':
- return {
- ...state,
- activeSourceGroupId: action.payload,
- };
- case 'SET_AVAILABLE_INDEX_PATTERNS':
- return {
- ...state,
- availableIndexPatterns: action.payload,
- };
- case 'SET_IS_INDEX_PATTERNS_LOADING':
- return {
- ...state,
- isIndexPatternsLoading: action.payload,
- };
- default:
- return state;
- }
-};
-
-// HOOKS
-export const useSourceManager = (): UseSourceManager => {
- const {
- services: {
- data: { indexPatterns },
- },
- } = useKibana();
- const apolloClient = useApolloClient();
- const [state, dispatch] = useReducer(reducerManageSource, initManageSource);
-
- // Kibana Index Patterns
- const setIsIndexPatternsLoading = useCallback((loading: boolean) => {
- dispatch({
- type: 'SET_IS_INDEX_PATTERNS_LOADING',
- payload: loading,
- });
- }, []);
- const getDefaultIndex = useCallback(
- (indexToAdd?: string[] | null, onlyCheckIndexToAdd?: boolean) => {
- const filterIndexAdd = (indexToAdd ?? []).filter((item) => item !== NO_ALERT_INDEX);
- if (!isEmpty(filterIndexAdd)) {
- return onlyCheckIndexToAdd
- ? filterIndexAdd.sort()
- : [
- ...state.availableIndexPatterns,
- ...filterIndexAdd.filter((index) => !state.availableIndexPatterns.includes(index)),
- ].sort();
- }
- return state.availableIndexPatterns.sort();
- },
- [state.availableIndexPatterns]
- );
- const setAvailableIndexPatterns = useCallback((availableIndexPatterns: string[]) => {
- dispatch({
- type: 'SET_AVAILABLE_INDEX_PATTERNS',
- payload: availableIndexPatterns,
- });
- }, []);
- const fetchKibanaIndexPatterns = useCallback(() => {
- setIsIndexPatternsLoading(true);
- const abortCtrl = new AbortController();
-
- async function fetchTitles() {
- try {
- const result = await indexPatterns.getTitles();
- setAvailableIndexPatterns(result);
- setIsIndexPatternsLoading(false);
- } catch (error) {
- setIsIndexPatternsLoading(false);
- }
- }
-
- fetchTitles();
-
- return () => {
- return abortCtrl.abort();
- };
- }, [indexPatterns, setAvailableIndexPatterns, setIsIndexPatternsLoading]);
-
- // Security Solution Source Groups
- const setActiveSourceGroupId = useCallback(
- (sourceGroupId: SourceGroupsType) => {
- if (state.availableSourceGroupIds.includes(sourceGroupId)) {
- dispatch({
- type: 'SET_ACTIVE_SOURCE_GROUP_ID',
- payload: sourceGroupId,
- });
- }
- },
- [state.availableSourceGroupIds]
- );
- const setIsSourceLoading = useCallback(
- ({ id, loading }: { id: SourceGroupsType; loading: boolean }) => {
- dispatch({
- type: 'SET_IS_SOURCE_LOADING',
- id,
- payload: loading,
- });
- },
+import deepEqual from 'fast-deep-equal';
+import isEqual from 'lodash/isEqual';
+import { useEffect, useMemo } from 'react';
+import { useDispatch, useSelector } from 'react-redux';
+
+import { sourcererActions, sourcererSelectors } from '../../store/sourcerer';
+import { ManageScope, SourcererScopeName } from '../../store/sourcerer/model';
+import { useIndexFields } from '../source';
+import { State } from '../../store';
+import { useUserInfo } from '../../../detections/components/user_info';
+
+export const useInitSourcerer = (
+ scopeId: SourcererScopeName.default | SourcererScopeName.detections = SourcererScopeName.default
+) => {
+ const dispatch = useDispatch();
+
+ const { loading: loadingSignalIndex, isSignalIndexExists, signalIndexName } = useUserInfo();
+ const getConfigIndexPatternsSelector = useMemo(
+ () => sourcererSelectors.configIndexPatternsSelector(),
[]
);
- const enrichSource = useCallback(
- (id: SourceGroupsType, indexToAdd?: string[] | null, onlyCheckIndexToAdd?: boolean) => {
- let isSubscribed = true;
- const abortCtrl = new AbortController();
- const defaultIndex = getDefaultIndex(indexToAdd, onlyCheckIndexToAdd);
- const selectedPatterns = defaultIndex.filter((pattern) =>
- state.availableIndexPatterns.includes(pattern)
- );
- if (state.sourceGroups[id] == null) {
- dispatch({
- type: 'SET_SOURCE',
- id,
- defaultIndex: selectedPatterns,
- payload: { defaultPatterns: defaultIndex, id },
- });
- }
-
- async function fetchSource() {
- if (!apolloClient) return;
- setIsSourceLoading({ id, loading: true });
- try {
- const result = await apolloClient.query({
- query: sourceQuery,
- fetchPolicy: 'network-only',
- variables: {
- sourceId: 'default', // always
- defaultIndex: selectedPatterns,
- },
- context: {
- fetchOptions: {
- signal: abortCtrl.signal,
- },
- },
- });
- if (isSubscribed) {
- dispatch({
- type: 'SET_SOURCE',
- id,
- defaultIndex: selectedPatterns,
- payload: {
- browserFields: getBrowserFields(
- selectedPatterns.join(),
- get('data.source.status.indexFields', result)
- ),
- docValueFields: getDocValueFields(
- selectedPatterns.join(),
- get('data.source.status.indexFields', result)
- ),
- errorMessage: null,
- id,
- indexPattern: getIndexFields(
- selectedPatterns.join(),
- get('data.source.status.indexFields', result)
- ),
- indexPatterns: selectedPatterns,
- indicesExist: indicesExistOrDataTemporarilyUnavailable(
- get('data.source.status.indicesExist', result)
- ),
- loading: false,
- },
- });
- }
- } catch (error) {
- if (isSubscribed) {
- dispatch({
- type: 'SET_SOURCE',
- id,
- defaultIndex: selectedPatterns,
- payload: {
- errorMessage: error.message,
- id,
- loading: false,
- },
- });
- }
- }
- }
-
- fetchSource();
-
- return () => {
- isSubscribed = false;
- return abortCtrl.abort();
- };
- },
- [
- apolloClient,
- getDefaultIndex,
- setIsSourceLoading,
- state.availableIndexPatterns,
- state.sourceGroups,
- ]
- );
+ const ConfigIndexPatterns = useSelector(getConfigIndexPatternsSelector, isEqual);
- const initializeSourceGroup = useCallback(
- (id: SourceGroupsType, indexToAdd?: string[] | null, onlyCheckIndexToAdd?: boolean) =>
- enrichSource(id, indexToAdd, onlyCheckIndexToAdd),
- [enrichSource]
- );
-
- const updateSourceGroupIndicies = useCallback(
- (id: SourceGroupsType, updatedIndicies: string[]) => enrichSource(id, updatedIndicies, true),
- [enrichSource]
- );
- const getManageSourceGroupById = useCallback(
- (id: SourceGroupsType) => {
- const sourceById = state.sourceGroups[id];
- if (sourceById != null) {
- return sourceById;
- }
- return getSourceDefaults(id, getDefaultIndex());
- },
- [getDefaultIndex, state.sourceGroups]
- );
+ useIndexFields(scopeId);
+ useIndexFields(SourcererScopeName.timeline);
- // load initial default index
useEffect(() => {
- fetchKibanaIndexPatterns();
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
+ if (!loadingSignalIndex && signalIndexName != null) {
+ dispatch(sourcererActions.setSignalIndexName({ signalIndexName }));
+ }
+ }, [dispatch, loadingSignalIndex, signalIndexName]);
+ // Related to timeline
useEffect(() => {
- if (!state.isIndexPatternsLoading) {
- Object.entries(sourceGroups).forEach(([key, value]) =>
- initializeSourceGroup(key as SourceGroupsType, value, true)
+ if (!loadingSignalIndex && signalIndexName != null) {
+ dispatch(
+ sourcererActions.setSelectedIndexPatterns({
+ id: SourcererScopeName.timeline,
+ selectedPatterns: [...ConfigIndexPatterns, signalIndexName],
+ })
);
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [state.isIndexPatternsLoading]);
+ }, [ConfigIndexPatterns, dispatch, loadingSignalIndex, signalIndexName]);
- return {
- ...state,
- getManageSourceGroupById,
- initializeSourceGroup,
- setActiveSourceGroupId,
- updateSourceGroupIndicies,
- };
+ // Related to the detection page
+ useEffect(() => {
+ if (
+ scopeId === SourcererScopeName.detections &&
+ isSignalIndexExists &&
+ signalIndexName != null
+ ) {
+ dispatch(
+ sourcererActions.setSelectedIndexPatterns({
+ id: scopeId,
+ selectedPatterns: [signalIndexName],
+ })
+ );
+ }
+ }, [dispatch, isSignalIndexExists, scopeId, signalIndexName]);
};
-const ManageSourceContext = createContext(init);
-
-export const useManageSource = () => useContext(ManageSourceContext);
-
-interface ManageSourceProps {
- children: React.ReactNode;
-}
-
-export const MaybeManageSource = ({ children }: ManageSourceProps) => {
- const indexPatternManager = useSourceManager();
- return (
-
- {children}
-
+export const useSourcererScope = (scope: SourcererScopeName = SourcererScopeName.default) => {
+ const sourcererScopeSelector = useMemo(() => sourcererSelectors.getSourcererScopeSelector(), []);
+ const SourcererScope = useSelector(
+ (state) => sourcererScopeSelector(state, scope),
+ deepEqual
);
+ return SourcererScope;
};
-export const ManageSource = SOURCERER_FEATURE_FLAG_ON
- ? MaybeManageSource
- : ({ children }: ManageSourceProps) => <>{children}>;
diff --git a/x-pack/plugins/security_solution/public/common/containers/sourcerer/mocks.ts b/x-pack/plugins/security_solution/public/common/containers/sourcerer/mocks.ts
index cde14e54694f0..c34a6917f300e 100644
--- a/x-pack/plugins/security_solution/public/common/containers/sourcerer/mocks.ts
+++ b/x-pack/plugins/security_solution/public/common/containers/sourcerer/mocks.ts
@@ -4,8 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { SecurityPageName } from './constants';
-import { getSourceDefaults } from './index';
+import { initSourcererScope } from '../../store/sourcerer/model';
export const mockPatterns = [
'auditbeat-*',
@@ -14,32 +13,10 @@ export const mockPatterns = [
'logs-*',
'packetbeat-*',
'winlogbeat-*',
+ 'journalbeat-*',
];
-export const mockSourceGroups = {
- [SecurityPageName.default]: [
- 'apm-*-transaction*',
- 'auditbeat-*',
- 'blobbeat-*',
- 'endgame-*',
- 'filebeat-*',
- 'logs-*',
- 'winlogbeat-*',
- ],
- [SecurityPageName.host]: [
- 'apm-*-transaction*',
- 'endgame-*',
- 'logs-*',
- 'packetbeat-*',
- 'winlogbeat-*',
- ],
-};
-
-export const mockSourceSelections = {
- [SecurityPageName.default]: ['auditbeat-*', 'endgame-*', 'filebeat-*', 'logs-*', 'winlogbeat-*'],
- [SecurityPageName.host]: ['endgame-*', 'logs-*', 'packetbeat-*', 'winlogbeat-*'],
-};
-export const mockSource = (testId: SecurityPageName.default | SecurityPageName.host) => ({
+export const mockSource = {
data: {
source: {
id: 'default',
@@ -50,7 +27,7 @@ export const mockSource = (testId: SecurityPageName.default | SecurityPageName.h
category: '_id',
description: 'Each document has an _id that uniquely identifies it',
example: 'Y-6TfmcB0WOhS6qyMv3s',
- indexes: mockSourceSelections[testId],
+ indexes: mockPatterns,
name: '_id',
searchable: true,
type: 'string',
@@ -67,48 +44,45 @@ export const mockSource = (testId: SecurityPageName.default | SecurityPageName.h
loading: false,
networkStatus: 7,
stale: false,
-});
+};
-export const mockSourceGroup = (testId: SecurityPageName.default | SecurityPageName.host) => {
- const indexes = mockSourceSelections[testId];
- return {
- ...getSourceDefaults(testId, mockPatterns),
- defaultPatterns: mockSourceGroups[testId],
- browserFields: {
- _id: {
- fields: {
- _id: {
- __typename: 'IndexField',
- aggregatable: false,
- category: '_id',
- description: 'Each document has an _id that uniquely identifies it',
- esTypes: null,
- example: 'Y-6TfmcB0WOhS6qyMv3s',
- format: null,
- indexes,
- name: '_id',
- searchable: true,
- subType: null,
- type: 'string',
- },
- },
- },
- },
- indexPattern: {
- fields: [
- {
+export const mockSourcererScope = {
+ ...initSourcererScope,
+ scopePatterns: mockPatterns,
+ browserFields: {
+ _id: {
+ fields: {
+ _id: {
+ __typename: 'IndexField',
aggregatable: false,
+ category: '_id',
+ description: 'Each document has an _id that uniquely identifies it',
esTypes: null,
+ example: 'Y-6TfmcB0WOhS6qyMv3s',
+ format: null,
+ indexes: mockPatterns,
name: '_id',
searchable: true,
subType: null,
type: 'string',
},
- ],
- title: indexes.join(),
+ },
},
- indexPatterns: indexes,
- indicesExist: true,
- loading: false,
- };
+ },
+ indexPattern: {
+ fields: [
+ {
+ aggregatable: false,
+ esTypes: null,
+ name: '_id',
+ searchable: true,
+ subType: null,
+ type: 'string',
+ },
+ ],
+ title: mockPatterns.join(),
+ },
+ selectedPatterns: mockPatterns,
+ indicesExist: true,
+ loading: false,
};
diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts
index 573ef92f7e069..3051459d5de0c 100644
--- a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts
+++ b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts
@@ -12,9 +12,25 @@ import {
createStartServicesMock,
createWithKibanaMock,
} from '../kibana_react.mock';
-
+const mockStartServicesMock = createStartServicesMock();
export const KibanaServices = { get: jest.fn(), getKibanaVersion: jest.fn(() => '8.0.0') };
-export const useKibana = jest.fn().mockReturnValue({ services: createStartServicesMock() });
+export const useKibana = jest.fn().mockReturnValue({
+ services: {
+ ...mockStartServicesMock,
+ data: {
+ ...mockStartServicesMock.data,
+ search: {
+ ...mockStartServicesMock.data.search,
+ search: jest.fn().mockImplementation(() => ({
+ subscribe: jest.fn().mockImplementation(() => ({
+ error: jest.fn(),
+ next: jest.fn(),
+ })),
+ })),
+ },
+ },
+ },
+});
export const useUiSetting = jest.fn(createUseUiSettingMock());
export const useUiSetting$ = jest.fn(createUseUiSetting$Mock());
export const useHttp = jest.fn().mockReturnValue(createStartServicesMock().http);
diff --git a/x-pack/plugins/security_solution/public/common/mock/global_state.ts b/x-pack/plugins/security_solution/public/common/mock/global_state.ts
index a74c9a6d2009d..0944b6aa27f67 100644
--- a/x-pack/plugins/security_solution/public/common/mock/global_state.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/global_state.ts
@@ -22,11 +22,15 @@ import {
DEFAULT_TO,
DEFAULT_INTERVAL_TYPE,
DEFAULT_INTERVAL_VALUE,
+ DEFAULT_INDEX_PATTERN,
} from '../../../common/constants';
import { networkModel } from '../../network/store';
import { TimelineType, TimelineStatus } from '../../../common/types/timeline';
import { mockManagementState } from '../../management/store/reducer';
import { ManagementState } from '../../management/types';
+import { initialSourcererState, SourcererScopeName } from '../store/sourcerer/model';
+import { mockBrowserFields, mockDocValueFields } from '../containers/source/mock';
+import { mockIndexPattern } from './index_pattern';
export const mockGlobalState: State = {
app: {
@@ -203,6 +207,7 @@ export const mockGlobalState: State = {
id: 'test',
savedObjectId: null,
columns: defaultHeaders,
+ indexNames: DEFAULT_INDEX_PATTERN,
itemsPerPage: 5,
dataProviders: [],
description: '',
@@ -241,6 +246,28 @@ export const mockGlobalState: State = {
},
insertTimeline: null,
},
+ sourcerer: {
+ ...initialSourcererState,
+ sourcererScopes: {
+ ...initialSourcererState.sourcererScopes,
+ [SourcererScopeName.default]: {
+ ...initialSourcererState.sourcererScopes[SourcererScopeName.default],
+ selectedPatterns: DEFAULT_INDEX_PATTERN,
+ browserFields: mockBrowserFields,
+ indexPattern: mockIndexPattern,
+ docValueFields: mockDocValueFields,
+ loading: false,
+ },
+ [SourcererScopeName.timeline]: {
+ ...initialSourcererState.sourcererScopes[SourcererScopeName.timeline],
+ selectedPatterns: DEFAULT_INDEX_PATTERN,
+ browserFields: mockBrowserFields,
+ indexPattern: mockIndexPattern,
+ docValueFields: mockDocValueFields,
+ loading: false,
+ },
+ },
+ },
/**
* These state's are wrapped in `Immutable`, but for compatibility with the overall app architecture,
* they are cast to mutable versions here.
diff --git a/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts b/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts
index 826057560f942..e4abc17e9034c 100644
--- a/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/index_pattern.ts
@@ -4,7 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/
-export const mockIndexPattern = {
+import { IIndexPattern } from '../../../../../../src/plugins/data/common/index_patterns';
+
+export const mockIndexPattern: IIndexPattern = {
fields: [
{
name: '@timestamp',
@@ -93,3 +95,5 @@ export const mockIndexPattern = {
],
title: 'filebeat-*,auditbeat-*,packetbeat-*',
};
+
+export const mockIndexNames = ['filebeat-*', 'auditbeat-*', 'packetbeat-*'];
diff --git a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts
index 26013915315af..6403a50ad4a1d 100644
--- a/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts
+++ b/x-pack/plugins/security_solution/public/common/mock/timeline_results.ts
@@ -2124,6 +2124,7 @@ export const mockTimelineModel: TimelineModel = {
highlightedDropAndProviderId: '',
historyIds: [],
id: 'ef579e40-jibber-jabber',
+ indexNames: [],
isFavorite: false,
isLive: false,
isLoading: false,
@@ -2228,6 +2229,7 @@ export const defaultTimelineProps: CreateTimelineProps = {
highlightedDropAndProviderId: '',
historyIds: [],
id: TimelineId.active,
+ indexNames: [],
isFavorite: false,
isLive: false,
isLoading: false,
diff --git a/x-pack/plugins/security_solution/public/common/store/actions.ts b/x-pack/plugins/security_solution/public/common/store/actions.ts
index 6b446ab6692d9..f4134b5c47c2c 100644
--- a/x-pack/plugins/security_solution/public/common/store/actions.ts
+++ b/x-pack/plugins/security_solution/public/common/store/actions.ts
@@ -12,6 +12,7 @@ import { TrustedAppsPageAction } from '../../management/pages/trusted_apps/store
export { appActions } from './app';
export { dragAndDropActions } from './drag_and_drop';
export { inputsActions } from './inputs';
+export { sourcererActions } from './sourcerer';
import { RoutingAction } from './routing';
export type AppAction =
diff --git a/x-pack/plugins/security_solution/public/common/store/model.ts b/x-pack/plugins/security_solution/public/common/store/model.ts
index 0032a95cce321..04603d0607583 100644
--- a/x-pack/plugins/security_solution/public/common/store/model.ts
+++ b/x-pack/plugins/security_solution/public/common/store/model.ts
@@ -7,4 +7,5 @@
export { appModel } from './app';
export { dragAndDropModel } from './drag_and_drop';
export { inputsModel } from './inputs';
+export { sourcererModel } from './sourcerer';
export * from './types';
diff --git a/x-pack/plugins/security_solution/public/common/store/reducer.ts b/x-pack/plugins/security_solution/public/common/store/reducer.ts
index a0977cea71da7..60cb6a4e960bd 100644
--- a/x-pack/plugins/security_solution/public/common/store/reducer.ts
+++ b/x-pack/plugins/security_solution/public/common/store/reducer.ts
@@ -9,6 +9,7 @@ import { combineReducers, PreloadedState, AnyAction, Reducer } from 'redux';
import { appReducer, initialAppState } from './app';
import { dragAndDropReducer, initialDragAndDropState } from './drag_and_drop';
import { createInitialInputsState, inputsReducer } from './inputs';
+import { sourcererReducer, sourcererModel } from './sourcerer';
import { HostsPluginReducer } from '../../hosts/store';
import { NetworkPluginReducer } from '../../network/store';
@@ -18,6 +19,7 @@ import { SecuritySubPlugins } from '../../app/types';
import { ManagementPluginReducer } from '../../management';
import { State } from './types';
import { AppAction } from './actions';
+import { KibanaIndexPatterns } from './sourcerer/model';
export type SubPluginsInitReducer = HostsPluginReducer &
NetworkPluginReducer &
@@ -28,13 +30,22 @@ export type SubPluginsInitReducer = HostsPluginReducer &
* Factory for the 'initialState' that is used to preload state into the Security App's redux store.
*/
export const createInitialState = (
- pluginsInitState: SecuritySubPlugins['store']['initialState']
+ pluginsInitState: SecuritySubPlugins['store']['initialState'],
+ {
+ kibanaIndexPatterns,
+ configIndexPatterns,
+ }: { kibanaIndexPatterns: KibanaIndexPatterns; configIndexPatterns: string[] }
): PreloadedState => {
const preloadedState: PreloadedState = {
app: initialAppState,
dragAndDrop: initialDragAndDropState,
...pluginsInitState,
inputs: createInitialInputsState(),
+ sourcerer: {
+ ...sourcererModel.initialSourcererState,
+ kibanaIndexPatterns,
+ configIndexPatterns,
+ },
};
return preloadedState;
};
@@ -49,5 +60,6 @@ export const createReducer: (
app: appReducer,
dragAndDrop: dragAndDropReducer,
inputs: inputsReducer,
+ sourcerer: sourcererReducer,
...pluginsReducer,
});
diff --git a/x-pack/plugins/security_solution/public/common/store/selectors.ts b/x-pack/plugins/security_solution/public/common/store/selectors.ts
index b938bae39b634..3cefd92bf9e60 100644
--- a/x-pack/plugins/security_solution/public/common/store/selectors.ts
+++ b/x-pack/plugins/security_solution/public/common/store/selectors.ts
@@ -7,3 +7,4 @@
export { appSelectors } from './app';
export { dragAndDropSelectors } from './drag_and_drop';
export { inputsSelectors } from './inputs';
+export { sourcererSelectors } from './sourcerer';
diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts
new file mode 100644
index 0000000000000..0b40586798f09
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/actions.ts
@@ -0,0 +1,36 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import actionCreatorFactory from 'typescript-fsa';
+import { TimelineEventsType } from '../../../../common/types/timeline';
+
+import { KibanaIndexPatterns, ManageScopeInit, SourcererScopeName } from './model';
+
+const actionCreator = actionCreatorFactory('x-pack/security_solution/local/sourcerer');
+
+export const setSource = actionCreator<{
+ id: SourcererScopeName;
+ payload: ManageScopeInit;
+}>('SET_SOURCE');
+
+export const setIndexPatternsList = actionCreator<{
+ kibanaIndexPatterns: KibanaIndexPatterns;
+ configIndexPatterns: string[];
+}>('SET_INDEX_PATTERNS_LIST');
+
+export const setSignalIndexName = actionCreator<{ signalIndexName: string }>(
+ 'SET_SIGNAL_INDEX_NAME'
+);
+
+export const setSourcererScopeLoading = actionCreator<{ id: SourcererScopeName; loading: boolean }>(
+ 'SET_SOURCERER_SCOPE_LOADING'
+);
+
+export const setSelectedIndexPatterns = actionCreator<{
+ id: SourcererScopeName;
+ selectedPatterns: string[];
+ eventType?: TimelineEventsType;
+}>('SET_SELECTED_INDEX_PATTERNS');
diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/index.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/index.ts
new file mode 100644
index 0000000000000..551c7d8e3efbc
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/index.ts
@@ -0,0 +1,12 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import * as sourcererActions from './actions';
+import * as sourcererModel from './model';
+import * as sourcererSelectors from './selectors';
+
+export { sourcererActions, sourcererModel, sourcererSelectors };
+export * from './reducer';
diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts
new file mode 100644
index 0000000000000..93f7ff95dfb00
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts
@@ -0,0 +1,86 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { IIndexPattern } from '../../../../../../../src/plugins/data/common/index_patterns';
+import { DocValueFields } from '../../../../common/search_strategy/common';
+import {
+ BrowserFields,
+ EMPTY_BROWSER_FIELDS,
+ EMPTY_DOCVALUE_FIELD,
+ EMPTY_INDEX_PATTERN,
+} from '../../../../common/search_strategy/index_fields';
+
+export type ErrorModel = Error[];
+
+export enum SourcererScopeName {
+ default = 'default',
+ detections = 'detections',
+ timeline = 'timeline',
+}
+
+export interface ManageScope {
+ browserFields: BrowserFields;
+ docValueFields: DocValueFields[];
+ errorMessage: string | null;
+ id: SourcererScopeName;
+ indexPattern: IIndexPattern;
+ indicesExist: boolean | undefined | null;
+ loading: boolean;
+ selectedPatterns: string[];
+}
+
+export interface ManageScopeInit extends Partial {
+ id: SourcererScopeName;
+}
+
+export type SourcererScopeById = {
+ [id in SourcererScopeName]: ManageScope;
+};
+
+export type KibanaIndexPatterns = Array<{ id: string; title: string }>;
+
+// ManageSourcerer
+export interface SourcererModel {
+ kibanaIndexPatterns: KibanaIndexPatterns;
+ configIndexPatterns: string[];
+ signalIndexName: string | null;
+ sourcererScopes: SourcererScopeById;
+}
+
+export const initSourcererScope = {
+ browserFields: EMPTY_BROWSER_FIELDS,
+ docValueFields: EMPTY_DOCVALUE_FIELD,
+ errorMessage: null,
+ indexPattern: EMPTY_INDEX_PATTERN,
+ indicesExist: true,
+ loading: true,
+ selectedPatterns: [],
+};
+
+export const initialSourcererState: SourcererModel = {
+ kibanaIndexPatterns: [],
+ configIndexPatterns: [],
+ signalIndexName: null,
+ sourcererScopes: {
+ [SourcererScopeName.default]: {
+ ...initSourcererScope,
+ id: SourcererScopeName.default,
+ },
+ [SourcererScopeName.detections]: {
+ ...initSourcererScope,
+ id: SourcererScopeName.detections,
+ },
+ [SourcererScopeName.timeline]: {
+ ...initSourcererScope,
+ id: SourcererScopeName.timeline,
+ },
+ },
+};
+
+export type FSourcererScopePatterns = {
+ [id in SourcererScopeName]: string[];
+};
+export type SourcererScopePatterns = Partial;
diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts
new file mode 100644
index 0000000000000..b65d4d6338e50
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/reducer.ts
@@ -0,0 +1,93 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import isEmpty from 'lodash/isEmpty';
+import { reducerWithInitialState } from 'typescript-fsa-reducers';
+
+import {
+ setIndexPatternsList,
+ setSourcererScopeLoading,
+ setSelectedIndexPatterns,
+ setSignalIndexName,
+ setSource,
+} from './actions';
+import { initialSourcererState, SourcererModel, SourcererScopeName } from './model';
+
+export type SourcererState = SourcererModel;
+
+export const sourcererReducer = reducerWithInitialState(initialSourcererState)
+ .case(setIndexPatternsList, (state, { kibanaIndexPatterns, configIndexPatterns }) => ({
+ ...state,
+ kibanaIndexPatterns,
+ configIndexPatterns,
+ }))
+ .case(setSignalIndexName, (state, { signalIndexName }) => ({
+ ...state,
+ signalIndexName,
+ }))
+ .case(setSourcererScopeLoading, (state, { id, loading }) => ({
+ ...state,
+ sourcererScopes: {
+ ...state.sourcererScopes,
+ [id]: {
+ ...state.sourcererScopes[id],
+ loading,
+ },
+ },
+ }))
+ .case(setSelectedIndexPatterns, (state, { id, selectedPatterns, eventType }) => {
+ const kibanaIndexPatterns = state.kibanaIndexPatterns.map((kip) => kip.title);
+ const newSelectedPatterns = selectedPatterns.filter(
+ (sp) =>
+ state.configIndexPatterns.includes(sp) ||
+ kibanaIndexPatterns.includes(sp) ||
+ (!isEmpty(state.signalIndexName) && state.signalIndexName === sp)
+ );
+ let defaultIndexPatterns = state.configIndexPatterns;
+ if (id === SourcererScopeName.timeline && isEmpty(newSelectedPatterns)) {
+ if (eventType === 'all' && !isEmpty(state.signalIndexName)) {
+ defaultIndexPatterns = [...state.configIndexPatterns, state.signalIndexName ?? ''];
+ } else if (eventType === 'raw') {
+ defaultIndexPatterns = state.configIndexPatterns;
+ } else if (
+ !isEmpty(state.signalIndexName) &&
+ (eventType === 'signal' || eventType === 'alert')
+ ) {
+ defaultIndexPatterns = [state.signalIndexName ?? ''];
+ }
+ } else if (id === SourcererScopeName.detections && isEmpty(newSelectedPatterns)) {
+ defaultIndexPatterns = [state.signalIndexName ?? ''];
+ }
+ return {
+ ...state,
+ sourcererScopes: {
+ ...state.sourcererScopes,
+ [id]: {
+ ...state.sourcererScopes[id],
+ selectedPatterns: isEmpty(newSelectedPatterns)
+ ? defaultIndexPatterns
+ : newSelectedPatterns,
+ },
+ },
+ };
+ })
+ .case(setSource, (state, { id, payload }) => {
+ const { ...sourcererScopes } = payload;
+ return {
+ ...state,
+ sourcererScopes: {
+ ...state.sourcererScopes,
+ [id]: {
+ ...state.sourcererScopes[id],
+ ...sourcererScopes,
+ ...(state.sourcererScopes[id].selectedPatterns.length === 0
+ ? { selectedPatterns: state.configIndexPatterns }
+ : {}),
+ },
+ },
+ };
+ })
+ .build();
diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts
new file mode 100644
index 0000000000000..ca9ea26ba5bac
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/selectors.ts
@@ -0,0 +1,91 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License;
+ * you may not use this file except in compliance with the Elastic License.
+ */
+
+import { createSelector } from 'reselect';
+import { State } from '../types';
+import { SourcererScopeById, KibanaIndexPatterns, SourcererScopeName, ManageScope } from './model';
+
+export const sourcererKibanaIndexPatternsSelector = ({ sourcerer }: State): KibanaIndexPatterns =>
+ sourcerer.kibanaIndexPatterns;
+
+export const sourcererSignalIndexNameSelector = ({ sourcerer }: State): string | null =>
+ sourcerer.signalIndexName;
+
+export const sourcererConfigIndexPatternsSelector = ({ sourcerer }: State): string[] =>
+ sourcerer.configIndexPatterns;
+
+export const sourcererScopesSelector = ({ sourcerer }: State): SourcererScopeById =>
+ sourcerer.sourcererScopes;
+
+export const scopesSelector = () => createSelector(sourcererScopesSelector, (scopes) => scopes);
+
+export const kibanaIndexPatternsSelector = () =>
+ createSelector(
+ sourcererKibanaIndexPatternsSelector,
+ (kibanaIndexPatterns) => kibanaIndexPatterns
+ );
+
+export const signalIndexNameSelector = () =>
+ createSelector(sourcererSignalIndexNameSelector, (signalIndexName) => signalIndexName);
+
+export const configIndexPatternsSelector = () =>
+ createSelector(
+ sourcererConfigIndexPatternsSelector,
+ (configIndexPatterns) => configIndexPatterns
+ );
+
+export const getIndexNamesSelectedSelector = () => {
+ const getScopesSelector = scopesSelector();
+ const getConfigIndexPatternsSelector = configIndexPatternsSelector();
+
+ const mapStateToProps = (state: State, scopeId: SourcererScopeName): string[] => {
+ const scope = getScopesSelector(state)[scopeId];
+ const configIndexPatterns = getConfigIndexPatternsSelector(state);
+
+ return scope.selectedPatterns.length === 0 ? configIndexPatterns : scope.selectedPatterns;
+ };
+
+ return mapStateToProps;
+};
+
+export const getAllExistingIndexNamesSelector = () => {
+ const getSignalIndexNameSelector = signalIndexNameSelector();
+ const getConfigIndexPatternsSelector = configIndexPatternsSelector();
+
+ const mapStateToProps = (state: State): string[] => {
+ const signalIndexName = getSignalIndexNameSelector(state);
+ const configIndexPatterns = getConfigIndexPatternsSelector(state);
+
+ return signalIndexName != null
+ ? [...configIndexPatterns, signalIndexName]
+ : configIndexPatterns;
+ };
+
+ return mapStateToProps;
+};
+
+export const defaultIndexNamesSelector = () => {
+ const getScopesSelector = scopesSelector();
+ const getConfigIndexPatternsSelector = configIndexPatternsSelector();
+
+ const mapStateToProps = (state: State, scopeId: SourcererScopeName): string[] => {
+ const scope = getScopesSelector(state)[scopeId];
+ const configIndexPatterns = getConfigIndexPatternsSelector(state);
+
+ return scope.selectedPatterns.length === 0 ? configIndexPatterns : scope.selectedPatterns;
+ };
+
+ return mapStateToProps;
+};
+
+export const getSourcererScopeSelector = () => {
+ const getScopesSelector = scopesSelector();
+
+ const mapStateToProps = (state: State, scopeId: SourcererScopeName): ManageScope =>
+ getScopesSelector(state)[scopeId];
+
+ return mapStateToProps;
+};
diff --git a/x-pack/plugins/security_solution/public/common/store/types.ts b/x-pack/plugins/security_solution/public/common/store/types.ts
index 91d92e4758c4a..6903567c752bc 100644
--- a/x-pack/plugins/security_solution/public/common/store/types.ts
+++ b/x-pack/plugins/security_solution/public/common/store/types.ts
@@ -12,6 +12,7 @@ import { AppAction } from './actions';
import { Immutable } from '../../../common/endpoint/types';
import { AppState } from './app/reducer';
import { InputsState } from './inputs/reducer';
+import { SourcererState } from './sourcerer/reducer';
import { HostsPluginState } from '../../hosts/store';
import { DragAndDropState } from './drag_and_drop/reducer';
import { TimelinePluginState } from '../../timelines/store/timeline';
@@ -25,6 +26,7 @@ export type StoreState = HostsPluginState &
app: AppState;
dragAndDrop: DragAndDropState;
inputs: InputsState;
+ sourcerer: SourcererState;
};
/**
* The redux `State` type for the Security App.
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx
index 678aaf06e50e4..e3440f4158513 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx
@@ -197,6 +197,7 @@ describe('alert actions', () => {
highlightedDropAndProviderId: '',
historyIds: [],
id: '',
+ indexNames: [],
isFavorite: false,
isLive: false,
isLoading: false,
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx
index 640726bb2e7c8..7f98d3b2f71de 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.tsx
@@ -279,6 +279,7 @@ export const sendAlertToTimelineAction = async ({
...getThresholdAggregationDataProvider(ecsData, nonEcsData),
],
id: TimelineId.active,
+ indexNames: [],
dateRange: {
start: from,
end: to,
@@ -329,6 +330,7 @@ export const sendAlertToTimelineAction = async ({
},
],
id: TimelineId.active,
+ indexNames: [],
dateRange: {
start: from,
end: to,
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx
index be24957602037..6724d3a83d617 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.test.tsx
@@ -22,7 +22,6 @@ describe('AlertsTableComponent', () => {
hasIndexWrite
from={'2020-07-07T08:20:18.966Z'}
loading
- signalsIndex="index"
to={'2020-07-08T08:20:18.966Z'}
globalQuery={{
query: 'query',
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
index 0416b3d2a459f..d66d37a020040 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/index.tsx
@@ -13,7 +13,6 @@ import { Status } from '../../../../common/detection_engine/schemas/common/schem
import { Filter, esQuery } from '../../../../../../../src/plugins/data/public';
import { TimelineIdLiteral } from '../../../../common/types/timeline';
import { useAppToasts } from '../../../common/hooks/use_app_toasts';
-import { useFetchIndexPatterns } from '../../containers/detection_engine/rules/fetch_index_patterns';
import { StatefulEventsViewer } from '../../../common/components/events_viewer';
import { HeaderSection } from '../../../common/components/header_section';
import { combineQueries } from '../../../timelines/components/timeline/helpers';
@@ -45,6 +44,8 @@ import {
displaySuccessToast,
displayErrorToast,
} from '../../../common/components/toasters';
+import { SourcererScopeName } from '../../../common/store/sourcerer/model';
+import { useSourcererScope } from '../../../common/containers/sourcerer';
interface OwnProps {
timelineId: TimelineIdLiteral;
@@ -55,7 +56,6 @@ interface OwnProps {
loading: boolean;
showBuildingBlockAlerts: boolean;
onShowBuildingBlockAlertsChanged: (showBuildingBlockAlerts: boolean) => void;
- signalsIndex: string;
to: string;
}
@@ -80,19 +80,20 @@ export const AlertsTableComponent: React.FC = ({
setEventsLoading,
showBuildingBlockAlerts,
onShowBuildingBlockAlertsChanged,
- signalsIndex,
to,
}) => {
const [showClearSelectionAction, setShowClearSelectionAction] = useState(false);
const [filterGroup, setFilterGroup] = useState(FILTER_OPEN);
- const [{ browserFields, indexPatterns, isLoading: indexPatternsLoading }] = useFetchIndexPatterns(
- signalsIndex !== '' ? [signalsIndex] : [],
- 'alerts_table'
- );
+ const {
+ browserFields,
+ indexPattern: indexPatterns,
+ loading: indexPatternsLoading,
+ selectedPatterns,
+ } = useSourcererScope(SourcererScopeName.detections);
const kibana = useKibana();
const [, dispatchToaster] = useStateToaster();
const { addWarning } = useAppToasts();
- const { initializeTimeline, setSelectAll, setIndexToAdd } = useManageTimeline();
+ const { initializeTimeline, setSelectAll } = useManageTimeline();
const getGlobalQuery = useCallback(
(customFilters: Filter[]) => {
@@ -284,7 +285,6 @@ export const AlertsTableComponent: React.FC = ({
]
);
- const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]);
const defaultFiltersMemo = useMemo(() => {
if (isEmpty(defaultFilters)) {
return buildAlertStatusFilter(filterGroup);
@@ -301,7 +301,6 @@ export const AlertsTableComponent: React.FC = ({
filterManager,
footerText: i18n.TOTAL_COUNT_OF_ALERTS,
id: timelineId,
- indexToAdd: defaultIndices,
loadingText: i18n.LOADING_ALERTS,
selectAll: false,
queryFields: requiredFieldsForActions,
@@ -310,16 +309,12 @@ export const AlertsTableComponent: React.FC = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
- useEffect(() => {
- setIndexToAdd({ id: timelineId, indexToAdd: defaultIndices });
- }, [timelineId, defaultIndices, setIndexToAdd]);
-
const headerFilterGroup = useMemo(
() => ,
[onFilterGroupChangedCallback]
);
- if (loading || indexPatternsLoading || isEmpty(signalsIndex)) {
+ if (loading || indexPatternsLoading || isEmpty(selectedPatterns)) {
return (
@@ -330,12 +325,12 @@ export const AlertsTableComponent: React.FC = ({
return (
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx
index 4559e44b8c3c5..82fed152ea66d 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/alert_context_menu.tsx
@@ -109,7 +109,7 @@ const AlertContextMenuComponent: React.FC = ({
const closeAddExceptionModal = useCallback(() => {
setShouldShowAddExceptionModal(false);
setAddExceptionModalState(addExceptionModalInitialState);
- }, [setShouldShowAddExceptionModal, setAddExceptionModalState]);
+ }, []);
const onAddExceptionCancel = useCallback(() => {
closeAddExceptionModal();
@@ -305,33 +305,6 @@ const AlertContextMenuComponent: React.FC = ({
[setShouldShowAddExceptionModal, setAddExceptionModalState]
);
- const AddExceptionModal = useCallback(
- () =>
- shouldShowAddExceptionModal === true && addExceptionModalState.alertData !== null ? (
-
- ) : null,
- [
- shouldShowAddExceptionModal,
- addExceptionModalState.alertData,
- addExceptionModalState.ruleName,
- addExceptionModalState.ruleId,
- addExceptionModalState.ruleIndices,
- addExceptionModalState.exceptionListType,
- onAddExceptionCancel,
- onAddExceptionConfirm,
- alertStatus,
- ]
- );
-
const button = (
= ({
-
+ {shouldShowAddExceptionModal === true && addExceptionModalState.alertData !== null && (
+
+ )}
>
);
};
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx
index 4ab5fa5e6012f..f4649b016f67c 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/investigate_in_timeline_action.tsx
@@ -15,7 +15,6 @@ import { useApolloClient } from '../../../../common/utils/apollo_context';
import { sendAlertToTimelineAction } from '../actions';
import { dispatchUpdateTimeline } from '../../../../timelines/components/open_timeline/helpers';
import { ActionIconItem } from '../../../../timelines/components/timeline/body/actions/action_icon_item';
-
import { CreateTimelineProps } from '../types';
import {
ACTION_INVESTIGATE_IN_TIMELINE,
@@ -49,6 +48,8 @@ const InvestigateInTimelineActionComponent: React.FC = (props) => (
-
+
);
DetectionEngineHeaderPageComponent.defaultProps = {
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx
index cb25785eaa5b2..4312be0b46990 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.test.tsx
@@ -8,8 +8,9 @@ import { mount, shallow } from 'enzyme';
import { ThemeProvider } from 'styled-components';
import euiDarkVars from '@elastic/eui/dist/eui_theme_light.json';
+import { stubIndexPattern } from 'src/plugins/data/common/index_patterns/index_pattern.stub';
import { StepAboutRule } from '.';
-
+import { useFetchIndex } from '../../../../common/containers/source';
import { mockAboutStepRule } from '../../../pages/detection_engine/rules/all/__mocks__/mock';
import { StepRuleDescription } from '../description_step';
import { stepAboutDefaultValue } from './default_value';
@@ -20,6 +21,7 @@ import {
} from '../../../pages/detection_engine/rules/types';
import { fillEmptySeverityMappings } from '../../../pages/detection_engine/rules/helpers';
+jest.mock('../../../../common/containers/source');
const theme = () => ({ eui: euiDarkVars, darkMode: true });
/* eslint-disable no-console */
@@ -44,6 +46,12 @@ describe('StepAboutRuleComponent', () => {
beforeEach(() => {
formHook = null;
+ (useFetchIndex as jest.Mock).mockImplementation(() => [
+ false,
+ {
+ indexPatterns: stubIndexPattern,
+ },
+ ]);
});
test('it renders StepRuleDescription if isReadOnlyView is true and "name" property exists', () => {
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx
index 66f95f5ce15d2..90b70e53a459e 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_about_rule/index.tsx
@@ -39,8 +39,8 @@ import { NextStep } from '../next_step';
import { MarkdownEditorForm } from '../../../../common/components/markdown_editor/eui_form';
import { SeverityField } from '../severity_mapping';
import { RiskScoreField } from '../risk_score_mapping';
-import { useFetchIndexPatterns } from '../../../containers/detection_engine/rules';
import { AutocompleteField } from '../autocomplete_field';
+import { useFetchIndex } from '../../../../common/containers/source';
const CommonUseField = getUseField({ component: Field });
@@ -74,10 +74,8 @@ const StepAboutRuleComponent: FC = ({
}) => {
const initialState = defaultValues ?? stepAboutDefaultValue;
const [severityValue, setSeverityValue] = useState(initialState.severity.value);
- const [{ isLoading: indexPatternLoading, indexPatterns }] = useFetchIndexPatterns(
- defineRuleData?.index ?? [],
- RuleStep.aboutRule
- );
+ const [indexPatternLoading, { indexPatterns }] = useFetchIndex(defineRuleData?.index ?? []);
+
const canUseExceptions =
defineRuleData?.ruleType &&
!isMlRule(defineRuleData.ruleType) &&
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx
index 7846f0c406668..99999ddbf1976 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_define_rule/index.tsx
@@ -14,7 +14,6 @@ import { DEFAULT_TIMELINE_TITLE } from '../../../../timelines/components/timelin
import { isMlRule } from '../../../../../common/machine_learning/helpers';
import { hasMlAdminPermissions } from '../../../../../common/machine_learning/has_ml_admin_permissions';
import { hasMlLicense } from '../../../../../common/machine_learning/has_ml_license';
-import { useFetchIndexPatterns } from '../../../containers/detection_engine/rules';
import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities';
import { useUiSetting$ } from '../../../../common/lib/kibana';
import {
@@ -48,6 +47,7 @@ import { schema } from './schema';
import * as i18n from './translations';
import { isEqlRule, isThresholdRule } from '../../../../../common/detection_engine/utils';
import { EqlQueryBar } from '../eql_query_bar';
+import { useFetchIndex } from '../../../../common/containers/source';
const CommonUseField = getUseField({ component: Field });
@@ -125,10 +125,7 @@ const StepDefineRuleComponent: FC = ({
}) as unknown) as [Partial];
const index = formIndex || initialState.index;
const ruleType = formRuleType || initialState.ruleType;
- const [{ browserFields, indexPatterns, isLoading: indexPatternsLoading }] = useFetchIndexPatterns(
- index,
- RuleStep.defineRule
- );
+ const [indexPatternsLoading, { browserFields, indexPatterns }] = useFetchIndex(index);
// reset form when rule type changes
useEffect(() => {
diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx
index e1a29c3575d95..00e108ffb89b6 100644
--- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.tsx
@@ -169,22 +169,19 @@ export const useUserInfo = (): State => {
if (loading !== privilegeLoading || indexNameLoading) {
dispatch({ type: 'updateLoading', loading: privilegeLoading || indexNameLoading });
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [loading, privilegeLoading, indexNameLoading]);
+ }, [dispatch, loading, privilegeLoading, indexNameLoading]);
useEffect(() => {
if (!loading && hasIndexManage !== hasApiIndexManage && hasApiIndexManage != null) {
dispatch({ type: 'updateHasIndexManage', hasIndexManage: hasApiIndexManage });
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [loading, hasIndexManage, hasApiIndexManage]);
+ }, [dispatch, loading, hasIndexManage, hasApiIndexManage]);
useEffect(() => {
if (!loading && hasIndexWrite !== hasApiIndexWrite && hasApiIndexWrite != null) {
dispatch({ type: 'updateHasIndexWrite', hasIndexWrite: hasApiIndexWrite });
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [loading, hasIndexWrite, hasApiIndexWrite]);
+ }, [dispatch, loading, hasIndexWrite, hasApiIndexWrite]);
useEffect(() => {
if (
@@ -194,36 +191,31 @@ export const useUserInfo = (): State => {
) {
dispatch({ type: 'updateIsSignalIndexExists', isSignalIndexExists: isApiSignalIndexExists });
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [loading, isSignalIndexExists, isApiSignalIndexExists]);
+ }, [dispatch, loading, isSignalIndexExists, isApiSignalIndexExists]);
useEffect(() => {
if (!loading && isAuthenticated !== isApiAuthenticated && isApiAuthenticated != null) {
dispatch({ type: 'updateIsAuthenticated', isAuthenticated: isApiAuthenticated });
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [loading, isAuthenticated, isApiAuthenticated]);
+ }, [dispatch, loading, isAuthenticated, isApiAuthenticated]);
useEffect(() => {
if (!loading && hasEncryptionKey !== isApiEncryptionKey && isApiEncryptionKey != null) {
dispatch({ type: 'updateHasEncryptionKey', hasEncryptionKey: isApiEncryptionKey });
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [loading, hasEncryptionKey, isApiEncryptionKey]);
+ }, [dispatch, loading, hasEncryptionKey, isApiEncryptionKey]);
useEffect(() => {
if (!loading && canUserCRUD !== capabilitiesCanUserCRUD && capabilitiesCanUserCRUD != null) {
dispatch({ type: 'updateCanUserCRUD', canUserCRUD: capabilitiesCanUserCRUD });
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [loading, canUserCRUD, capabilitiesCanUserCRUD]);
+ }, [dispatch, loading, canUserCRUD, capabilitiesCanUserCRUD]);
useEffect(() => {
if (!loading && signalIndexName !== apiSignalIndexName && apiSignalIndexName != null) {
dispatch({ type: 'updateSignalIndexName', signalIndexName: apiSignalIndexName });
}
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [loading, signalIndexName, apiSignalIndexName]);
+ }, [dispatch, loading, signalIndexName, apiSignalIndexName]);
useEffect(() => {
if (
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx
deleted file mode 100644
index d36c19a6a35c6..0000000000000
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.test.tsx
+++ /dev/null
@@ -1,475 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { renderHook, act } from '@testing-library/react-hooks';
-
-import { DEFAULT_INDEX_PATTERN } from '../../../../../common/constants';
-import { useApolloClient } from '../../../../common/utils/apollo_context';
-import { mocksSource } from '../../../../common/containers/source/mock';
-
-import { useFetchIndexPatterns, Return } from './fetch_index_patterns';
-
-const mockUseApolloClient = useApolloClient as jest.Mock;
-jest.mock('../../../../common/utils/apollo_context');
-
-describe('useFetchIndexPatterns', () => {
- beforeEach(() => {
- mockUseApolloClient.mockClear();
- });
- test('happy path', async () => {
- await act(async () => {
- mockUseApolloClient.mockImplementation(() => ({
- query: () => Promise.resolve(mocksSource[0].result),
- }));
- const { result, waitForNextUpdate } = renderHook(() =>
- useFetchIndexPatterns(DEFAULT_INDEX_PATTERN)
- );
- await waitForNextUpdate();
- await waitForNextUpdate();
-
- expect(result.current).toEqual([
- {
- browserFields: {
- base: {
- fields: {
- '@timestamp': {
- category: 'base',
- description:
- 'Date/time when the event originated. For log events this is the date/time when the event was generated, and not when it was read. Required field for all events.',
- example: '2016-05-23T08:05:34.853Z',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: '@timestamp',
- searchable: true,
- type: 'date',
- aggregatable: true,
- },
- },
- },
- agent: {
- fields: {
- 'agent.ephemeral_id': {
- category: 'agent',
- description:
- 'Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but `agent.id` does not.',
- example: '8a4f500f',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'agent.ephemeral_id',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- 'agent.hostname': {
- category: 'agent',
- description: null,
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'agent.hostname',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- 'agent.id': {
- category: 'agent',
- description:
- 'Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.',
- example: '8a4f500d',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'agent.id',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- 'agent.name': {
- category: 'agent',
- description:
- 'Name of the agent. This is a name that can be given to an agent. This can be helpful if for example two Filebeat instances are running on the same host but a human readable separation is needed on which Filebeat instance data is coming from. If no name is given, the name is often left empty.',
- example: 'foo',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'agent.name',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- },
- },
- auditd: {
- fields: {
- 'auditd.data.a0': {
- category: 'auditd',
- description: null,
- example: null,
- format: '',
- indexes: ['auditbeat'],
- name: 'auditd.data.a0',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- 'auditd.data.a1': {
- category: 'auditd',
- description: null,
- example: null,
- format: '',
- indexes: ['auditbeat'],
- name: 'auditd.data.a1',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- 'auditd.data.a2': {
- category: 'auditd',
- description: null,
- example: null,
- format: '',
- indexes: ['auditbeat'],
- name: 'auditd.data.a2',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- },
- },
- client: {
- fields: {
- 'client.address': {
- category: 'client',
- description:
- 'Some event client addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'client.address',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- 'client.bytes': {
- category: 'client',
- description: 'Bytes sent from the client to the server.',
- example: '184',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'client.bytes',
- searchable: true,
- type: 'number',
- aggregatable: true,
- },
- 'client.domain': {
- category: 'client',
- description: 'Client domain.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'client.domain',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- 'client.geo.country_iso_code': {
- category: 'client',
- description: 'Country ISO code.',
- example: 'CA',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'client.geo.country_iso_code',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- },
- },
- cloud: {
- fields: {
- 'cloud.account.id': {
- category: 'cloud',
- description:
- 'The cloud account or organization id used to identify different entities in a multi-tenant environment. Examples: AWS account id, Google Cloud ORG Id, or other unique identifier.',
- example: '666777888999',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'cloud.account.id',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- 'cloud.availability_zone': {
- category: 'cloud',
- description: 'Availability zone in which this host is running.',
- example: 'us-east-1c',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'cloud.availability_zone',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- },
- },
- container: {
- fields: {
- 'container.id': {
- category: 'container',
- description: 'Unique container id.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'container.id',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- 'container.image.name': {
- category: 'container',
- description: 'Name of the image the container was built on.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'container.image.name',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- 'container.image.tag': {
- category: 'container',
- description: 'Container image tag.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'container.image.tag',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- },
- },
- destination: {
- fields: {
- 'destination.address': {
- category: 'destination',
- description:
- 'Some event destination addresses are defined ambiguously. The event will sometimes list an IP, a domain or a unix socket. You should always store the raw address in the `.address` field. Then it should be duplicated to `.ip` or `.domain`, depending on which one it is.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'destination.address',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- 'destination.bytes': {
- category: 'destination',
- description: 'Bytes sent from the destination to the source.',
- example: '184',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'destination.bytes',
- searchable: true,
- type: 'number',
- aggregatable: true,
- },
- 'destination.domain': {
- category: 'destination',
- description: 'Destination domain.',
- example: null,
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'destination.domain',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- 'destination.ip': {
- aggregatable: true,
- category: 'destination',
- description:
- 'IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.',
- example: '',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'destination.ip',
- searchable: true,
- type: 'ip',
- },
- 'destination.port': {
- aggregatable: true,
- category: 'destination',
- description: 'Port of the destination.',
- example: '',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'destination.port',
- searchable: true,
- type: 'long',
- },
- },
- },
- source: {
- fields: {
- 'source.ip': {
- aggregatable: true,
- category: 'source',
- description:
- 'IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.',
- example: '',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'source.ip',
- searchable: true,
- type: 'ip',
- },
- 'source.port': {
- aggregatable: true,
- category: 'source',
- description: 'Port of the source.',
- example: '',
- format: '',
- indexes: ['auditbeat', 'filebeat', 'packetbeat'],
- name: 'source.port',
- searchable: true,
- type: 'long',
- },
- },
- },
- event: {
- fields: {
- 'event.end': {
- aggregatable: true,
- category: 'event',
- description:
- 'event.end contains the date when the event ended or when the activity was last observed.',
- example: null,
- format: '',
- indexes: [
- 'apm-*-transaction*',
- 'auditbeat-*',
- 'endgame-*',
- 'filebeat-*',
- 'logs-*',
- 'packetbeat-*',
- 'winlogbeat-*',
- ],
- name: 'event.end',
- searchable: true,
- type: 'date',
- },
- },
- },
- },
- isLoading: false,
- indices: [
- 'apm-*-transaction*',
- 'auditbeat-*',
- 'endgame-*',
- 'filebeat-*',
- 'logs-*',
- 'packetbeat-*',
- 'winlogbeat-*',
- ],
- indicesExists: true,
- docValueFields: [
- {
- field: '@timestamp',
- format: 'date_time',
- },
- {
- field: 'event.end',
- format: 'date_time',
- },
- ],
- indexPatterns: {
- fields: [
- { name: '@timestamp', searchable: true, type: 'date', aggregatable: true },
- { name: 'agent.ephemeral_id', searchable: true, type: 'string', aggregatable: true },
- { name: 'agent.hostname', searchable: true, type: 'string', aggregatable: true },
- { name: 'agent.id', searchable: true, type: 'string', aggregatable: true },
- { name: 'agent.name', searchable: true, type: 'string', aggregatable: true },
- { name: 'auditd.data.a0', searchable: true, type: 'string', aggregatable: true },
- { name: 'auditd.data.a1', searchable: true, type: 'string', aggregatable: true },
- { name: 'auditd.data.a2', searchable: true, type: 'string', aggregatable: true },
- { name: 'client.address', searchable: true, type: 'string', aggregatable: true },
- { name: 'client.bytes', searchable: true, type: 'number', aggregatable: true },
- { name: 'client.domain', searchable: true, type: 'string', aggregatable: true },
- {
- name: 'client.geo.country_iso_code',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- { name: 'cloud.account.id', searchable: true, type: 'string', aggregatable: true },
- {
- name: 'cloud.availability_zone',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- { name: 'container.id', searchable: true, type: 'string', aggregatable: true },
- {
- name: 'container.image.name',
- searchable: true,
- type: 'string',
- aggregatable: true,
- },
- { name: 'container.image.tag', searchable: true, type: 'string', aggregatable: true },
- { name: 'destination.address', searchable: true, type: 'string', aggregatable: true },
- { name: 'destination.bytes', searchable: true, type: 'number', aggregatable: true },
- { name: 'destination.domain', searchable: true, type: 'string', aggregatable: true },
- { name: 'destination.ip', searchable: true, type: 'ip', aggregatable: true },
- { name: 'destination.port', searchable: true, type: 'long', aggregatable: true },
- { name: 'source.ip', searchable: true, type: 'ip', aggregatable: true },
- { name: 'source.port', searchable: true, type: 'long', aggregatable: true },
- { name: 'event.end', searchable: true, type: 'date', aggregatable: true },
- ],
- title:
- 'apm-*-transaction*,auditbeat-*,endgame-*,filebeat-*,logs-*,packetbeat-*,winlogbeat-*',
- },
- },
- result.current[1],
- ]);
- });
- });
-
- test('unhappy path', async () => {
- await act(async () => {
- mockUseApolloClient.mockImplementation(() => ({
- query: () => Promise.reject(new Error('Something went wrong')),
- }));
- const { result, waitForNextUpdate } = renderHook(() =>
- useFetchIndexPatterns(DEFAULT_INDEX_PATTERN)
- );
-
- await waitForNextUpdate();
- await waitForNextUpdate();
-
- expect(result.current).toEqual([
- {
- browserFields: {},
- docValueFields: [],
- indexPatterns: {
- fields: [],
- title: '',
- },
- indices: [
- 'apm-*-transaction*',
- 'auditbeat-*',
- 'endgame-*',
- 'filebeat-*',
- 'logs-*',
- 'packetbeat-*',
- 'winlogbeat-*',
- ],
- indicesExists: false,
- isLoading: false,
- },
- result.current[1],
- ]);
- });
- });
-});
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx
deleted file mode 100644
index 82c9292af7451..0000000000000
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/fetch_index_patterns.tsx
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License;
- * you may not use this file except in compliance with the Elastic License.
- */
-
-import { isEmpty, get } from 'lodash/fp';
-import { useEffect, useState, Dispatch, SetStateAction } from 'react';
-import deepEqual from 'fast-deep-equal';
-
-import { IIndexPattern } from '../../../../../../../../src/plugins/data/public';
-import {
- BrowserFields,
- getBrowserFields,
- getDocValueFields,
- getIndexFields,
- sourceQuery,
- DocValueFields,
-} from '../../../../common/containers/source';
-import { errorToToaster, useStateToaster } from '../../../../common/components/toasters';
-import { SourceQuery } from '../../../../graphql/types';
-import { useApolloClient } from '../../../../common/utils/apollo_context';
-
-import * as i18n from './translations';
-
-interface FetchIndexPatternReturn {
- browserFields: BrowserFields;
- docValueFields: DocValueFields[];
- isLoading: boolean;
- indices: string[];
- indicesExists: boolean;
- indexPatterns: IIndexPattern;
-}
-
-export type Return = [FetchIndexPatternReturn, Dispatch>];
-
-const DEFAULT_BROWSER_FIELDS = {};
-const DEFAULT_INDEX_PATTERNS = { fields: [], title: '' };
-const DEFAULT_DOC_VALUE_FIELDS: DocValueFields[] = [];
-
-// Fun fact: When using this hook multiple times within a component (e.g. add_exception_modal & edit_exception_modal),
-// the apolloClient will perform queryDeduplication and prevent the first query from executing. A deep compare is not
-// performed on `indices`, so another field must be passed to circumvent this.
-// For details, see https://github.com/apollographql/react-apollo/issues/2202
-export const useFetchIndexPatterns = (
- defaultIndices: string[] = [],
- queryDeduplication?: string
-): Return => {
- const apolloClient = useApolloClient();
- const [indices, setIndices] = useState(defaultIndices);
-
- const [state, setState] = useState({
- browserFields: DEFAULT_BROWSER_FIELDS,
- docValueFields: DEFAULT_DOC_VALUE_FIELDS,
- indices: defaultIndices,
- indicesExists: false,
- indexPatterns: DEFAULT_INDEX_PATTERNS,
- isLoading: false,
- });
-
- const [, dispatchToaster] = useStateToaster();
-
- useEffect(() => {
- if (!deepEqual(defaultIndices, indices)) {
- setIndices(defaultIndices);
- setState((prevState) => ({ ...prevState, indices: defaultIndices }));
- }
- }, [defaultIndices, indices]);
-
- useEffect(() => {
- let isSubscribed = true;
- const abortCtrl = new AbortController();
-
- async function fetchIndexPatterns() {
- if (apolloClient && !isEmpty(indices)) {
- setState((prevState) => ({ ...prevState, isLoading: true }));
- apolloClient
- .query({
- query: sourceQuery,
- fetchPolicy: 'cache-first',
- variables: {
- sourceId: 'default',
- defaultIndex: indices,
- ...(queryDeduplication != null ? { queryDeduplication } : {}),
- },
- context: {
- fetchOptions: {
- signal: abortCtrl.signal,
- },
- },
- })
- .then(
- (result) => {
- if (isSubscribed) {
- setState({
- browserFields: getBrowserFields(
- indices.join(),
- get('data.source.status.indexFields', result)
- ),
- docValueFields: getDocValueFields(
- indices.join(),
- get('data.source.status.indexFields', result)
- ),
- indices,
- isLoading: false,
- indicesExists: get('data.source.status.indicesExist', result),
- indexPatterns: getIndexFields(
- indices.join(),
- get('data.source.status.indexFields', result)
- ),
- });
- }
- },
- (error) => {
- if (isSubscribed) {
- setState((prevState) => ({ ...prevState, isLoading: false }));
- errorToToaster({ title: i18n.RULE_ADD_FAILURE, error, dispatchToaster });
- }
- }
- );
- }
- }
- fetchIndexPatterns();
- return () => {
- isSubscribed = false;
- abortCtrl.abort();
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [indices]);
-
- return [state, setIndices];
-};
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts
index a40ab2e487851..930391261ac87 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/index.ts
@@ -5,7 +5,6 @@
*/
export * from './api';
-export * from './fetch_index_patterns';
export * from './use_update_rule';
export * from './use_create_rule';
export * from './types';
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx
index 8c21f6a1e8cb7..a5d21d2847586 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.test.tsx
@@ -20,7 +20,7 @@ import {
import { setAbsoluteRangeDatePicker } from '../../../common/store/inputs/actions';
import { DetectionEnginePageComponent } from './detection_engine';
import { useUserData } from '../../components/user_info';
-import { useWithSource } from '../../../common/containers/source';
+import { useSourcererScope } from '../../../common/containers/sourcerer';
import { createStore, State } from '../../../common/store';
import { mockHistory, Router } from '../../../cases/components/__mock__/router';
@@ -34,7 +34,7 @@ jest.mock('../../../common/components/query_bar', () => ({
}));
jest.mock('../../containers/detection_engine/lists/use_lists_config');
jest.mock('../../components/user_info');
-jest.mock('../../../common/containers/source');
+jest.mock('../../../common/containers/sourcerer');
jest.mock('../../../common/components/link_to');
jest.mock('../../../common/containers/use_global_time', () => ({
useGlobalTime: jest.fn().mockReturnValue({
@@ -74,7 +74,7 @@ describe('DetectionEnginePageComponent', () => {
beforeAll(() => {
(useParams as jest.Mock).mockReturnValue({});
(useUserData as jest.Mock).mockReturnValue([{}]);
- (useWithSource as jest.Mock).mockReturnValue({
+ (useSourcererScope as jest.Mock).mockReturnValue({
indicesExist: true,
indexPattern: {},
});
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
index 3a3854f145db3..b39cd37521602 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/detection_engine.tsx
@@ -13,7 +13,6 @@ import { useHistory } from 'react-router-dom';
import { SecurityPageName } from '../../../app/types';
import { TimelineId } from '../../../../common/types/timeline';
import { useGlobalTime } from '../../../common/containers/use_global_time';
-import { useWithSource } from '../../../common/containers/source';
import { UpdateDateRange } from '../../../common/components/charts/common';
import { FiltersGlobal } from '../../../common/components/filters_global';
import { getRulesUrl } from '../../../common/components/link_to/redirect_to_detection_engine';
@@ -46,6 +45,8 @@ import { timelineSelectors } from '../../../timelines/store/timeline';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
import { TimelineModel } from '../../../timelines/store/timeline/model';
import { buildShowBuildingBlockFilter } from '../../components/alerts_table/default_config';
+import { useSourcererScope } from '../../../common/containers/sourcerer';
+import { SourcererScopeName } from '../../../common/store/sourcerer/model';
export const DetectionEnginePageComponent: React.FC = ({
filters,
@@ -117,10 +118,7 @@ export const DetectionEnginePageComponent: React.FC = ({
[setShowBuildingBlockAlerts]
);
- const indexToAdd = useMemo(() => (signalIndexName == null ? [] : [signalIndexName]), [
- signalIndexName,
- ]);
- const { indicesExist, indexPattern } = useWithSource('default', indexToAdd);
+ const { indicesExist, indexPattern } = useSourcererScope(SourcererScopeName.detections);
if (isUserAuthenticated != null && !isUserAuthenticated && !loading) {
return (
@@ -202,7 +200,6 @@ export const DetectionEnginePageComponent: React.FC = ({
defaultFilters={alertsTableDefaultFilters}
showBuildingBlockAlerts={showBuildingBlockAlerts}
onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChangedCallback}
- signalsIndex={signalIndexName ?? ''}
to={to}
/>
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx
index f8f9da78b2a06..22c3c43fb2356 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.test.tsx
@@ -20,7 +20,7 @@ import { RuleDetailsPageComponent } from './index';
import { createStore, State } from '../../../../../common/store';
import { setAbsoluteRangeDatePicker } from '../../../../../common/store/inputs/actions';
import { useUserData } from '../../../../components/user_info';
-import { useWithSource } from '../../../../../common/containers/source';
+import { useSourcererScope } from '../../../../../common/containers/sourcerer';
import { useParams } from 'react-router-dom';
import { mockHistory, Router } from '../../../../../cases/components/__mock__/router';
@@ -35,7 +35,7 @@ jest.mock('../../../../../common/components/query_bar', () => ({
jest.mock('../../../../containers/detection_engine/lists/use_lists_config');
jest.mock('../../../../../common/components/link_to');
jest.mock('../../../../components/user_info');
-jest.mock('../../../../../common/containers/source');
+jest.mock('../../../../../common/containers/sourcerer');
jest.mock('../../../../../common/containers/use_global_time', () => ({
useGlobalTime: jest.fn().mockReturnValue({
from: '2020-07-07T08:20:18.966Z',
@@ -71,7 +71,7 @@ describe('RuleDetailsPageComponent', () => {
beforeAll(() => {
(useUserData as jest.Mock).mockReturnValue([{}]);
(useParams as jest.Mock).mockReturnValue({});
- (useWithSource as jest.Mock).mockReturnValue({
+ (useSourcererScope as jest.Mock).mockReturnValue({
indicesExist: true,
indexPattern: {},
});
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
index 68799f46eee57..4816358e06226 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx
@@ -36,10 +36,7 @@ import { SiemSearchBar } from '../../../../../common/components/search_bar';
import { WrapperPage } from '../../../../../common/components/wrapper_page';
import { Rule } from '../../../../containers/detection_engine/rules';
import { useListsConfig } from '../../../../containers/detection_engine/lists/use_lists_config';
-
-import { useWithSource } from '../../../../../common/containers/source';
import { SpyRoute } from '../../../../../common/utils/route/spy_routes';
-
import { StepAboutRuleToggleDetails } from '../../../../components/rules/step_about_rule_details';
import { DetectionEngineHeaderPage } from '../../../../components/detection_engine_header_page';
import { AlertsHistogramPanel } from '../../../../components/alerts_histogram_panel';
@@ -89,6 +86,8 @@ import { showGlobalFilters } from '../../../../../timelines/components/timeline/
import { timelineSelectors } from '../../../../../timelines/store/timeline';
import { timelineDefaults } from '../../../../../timelines/store/timeline/defaults';
import { TimelineModel } from '../../../../../timelines/store/timeline/model';
+import { useSourcererScope } from '../../../../../common/containers/sourcerer';
+import { SourcererScopeName } from '../../../../../common/store/sourcerer/model';
enum RuleDetailTabs {
alerts = 'alerts',
@@ -265,10 +264,6 @@ export const RuleDetailsPageComponent: FC = ({
[rule, ruleDetailTab]
);
- const indexToAdd = useMemo(() => (signalIndexName == null ? [] : [signalIndexName]), [
- signalIndexName,
- ]);
-
const updateDateRangeCallback = useCallback(
({ x }) => {
if (!x) {
@@ -308,7 +303,7 @@ export const RuleDetailsPageComponent: FC = ({
[setShowBuildingBlockAlerts]
);
- const { indicesExist, indexPattern } = useWithSource('default', indexToAdd);
+ const { indicesExist, indexPattern } = useSourcererScope(SourcererScopeName.detections);
const exceptionLists = useMemo((): {
lists: ExceptionIdentifiers[];
@@ -500,7 +495,6 @@ export const RuleDetailsPageComponent: FC = ({
loading={loading}
showBuildingBlockAlerts={showBuildingBlockAlerts}
onShowBuildingBlockAlertsChanged={onShowBuildingBlockAlertsChangedCallback}
- signalsIndex={signalIndexName ?? ''}
to={to}
/>
)}
diff --git a/x-pack/plugins/security_solution/public/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json
index 9e6a4f21ec64f..568a960f0804e 100644
--- a/x-pack/plugins/security_solution/public/graphql/introspection.json
+++ b/x-pack/plugins/security_solution/public/graphql/introspection.json
@@ -2382,7 +2382,7 @@
"ofType": {
"kind": "NON_NULL",
"name": null,
- "ofType": { "kind": "OBJECT", "name": "IndexField", "ofType": null }
+ "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
}
}
},
@@ -2405,153 +2405,6 @@
"enumValues": null,
"possibleTypes": null
},
- {
- "kind": "OBJECT",
- "name": "IndexField",
- "description": "A descriptor of a field in an index",
- "fields": [
- {
- "name": "category",
- "description": "Where the field belong",
- "args": [],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "example",
- "description": "Example of field's value",
- "args": [],
- "type": { "kind": "SCALAR", "name": "String", "ofType": null },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "indexes",
- "description": "whether the field's belong to an alias index",
- "args": [],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "LIST",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "name",
- "description": "The name of the field",
- "args": [],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "type",
- "description": "The type of the field's values as recognized by Kibana",
- "args": [],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "searchable",
- "description": "Whether the field's values can be efficiently searched for",
- "args": [],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "aggregatable",
- "description": "Whether the field's values can be aggregated",
- "args": [],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "description",
- "description": "Description of the field",
- "args": [],
- "type": { "kind": "SCALAR", "name": "String", "ofType": null },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "format",
- "description": "",
- "args": [],
- "type": { "kind": "SCALAR", "name": "String", "ofType": null },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "esTypes",
- "description": "the elastic type as mapped in the index",
- "args": [],
- "type": { "kind": "SCALAR", "name": "ToStringArrayNoNullable", "ofType": null },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "subType",
- "description": "",
- "args": [],
- "type": { "kind": "SCALAR", "name": "ToIFieldSubTypeNonNullable", "ofType": null },
- "isDeprecated": false,
- "deprecationReason": null
- }
- ],
- "inputFields": null,
- "interfaces": [],
- "enumValues": null,
- "possibleTypes": null
- },
- {
- "kind": "SCALAR",
- "name": "ToStringArrayNoNullable",
- "description": "",
- "fields": null,
- "inputFields": null,
- "interfaces": null,
- "enumValues": null,
- "possibleTypes": null
- },
- {
- "kind": "SCALAR",
- "name": "ToIFieldSubTypeNonNullable",
- "description": "",
- "fields": null,
- "inputFields": null,
- "interfaces": null,
- "enumValues": null,
- "possibleTypes": null
- },
{
"kind": "INPUT_OBJECT",
"name": "TimerangeInput",
@@ -9466,6 +9319,22 @@
"isDeprecated": false,
"deprecationReason": null
},
+ {
+ "name": "indexNames",
+ "description": "",
+ "args": [],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
{
"name": "notes",
"description": "",
@@ -10933,6 +10802,20 @@
},
"defaultValue": null
},
+ {
+ "name": "indexNames",
+ "description": "",
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
+ }
+ },
+ "defaultValue": null
+ },
{
"name": "title",
"description": "",
@@ -12214,6 +12097,16 @@
],
"possibleTypes": null
},
+ {
+ "kind": "SCALAR",
+ "name": "ToStringArrayNoNullable",
+ "description": "",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
{
"kind": "OBJECT",
"name": "EcsEdges",
@@ -12548,6 +12441,143 @@
],
"possibleTypes": null
},
+ {
+ "kind": "SCALAR",
+ "name": "ToIFieldSubTypeNonNullable",
+ "description": "",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "IndexField",
+ "description": "A descriptor of a field in an index",
+ "fields": [
+ {
+ "name": "category",
+ "description": "Where the field belong",
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "example",
+ "description": "Example of field's value",
+ "args": [],
+ "type": { "kind": "SCALAR", "name": "String", "ofType": null },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "indexes",
+ "description": "whether the field's belong to an alias index",
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "name",
+ "description": "The name of the field",
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "type",
+ "description": "The type of the field's values as recognized by Kibana",
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": { "kind": "SCALAR", "name": "String", "ofType": null }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "searchable",
+ "description": "Whether the field's values can be efficiently searched for",
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "aggregatable",
+ "description": "Whether the field's values can be aggregated",
+ "args": [],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "description",
+ "description": "Description of the field",
+ "args": [],
+ "type": { "kind": "SCALAR", "name": "String", "ofType": null },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "format",
+ "description": "",
+ "args": [],
+ "type": { "kind": "SCALAR", "name": "String", "ofType": null },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "esTypes",
+ "description": "the elastic type as mapped in the index",
+ "args": [],
+ "type": { "kind": "SCALAR", "name": "ToStringArrayNoNullable", "ofType": null },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "subType",
+ "description": "",
+ "args": [],
+ "type": { "kind": "SCALAR", "name": "ToIFieldSubTypeNonNullable", "ofType": null },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [],
+ "enumValues": null,
+ "possibleTypes": null
+ },
{
"kind": "ENUM",
"name": "FlowDirection",
diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts
index 1699ac4dd33eb..0bce952912c5c 100644
--- a/x-pack/plugins/security_solution/public/graphql/types.ts
+++ b/x-pack/plugins/security_solution/public/graphql/types.ts
@@ -132,6 +132,8 @@ export interface TimelineInput {
kqlQuery?: Maybe;
+ indexNames?: Maybe;
+
title?: Maybe;
templateTimelineId?: Maybe;
@@ -413,10 +415,6 @@ export enum FlowDirection {
biDirectional = 'biDirectional',
}
-export type ToStringArrayNoNullable = any;
-
-export type ToIFieldSubTypeNonNullable = any;
-
export type ToStringArray = string[];
export type Date = string;
@@ -431,6 +429,10 @@ export type ToAny = any;
export type EsValue = any;
+export type ToStringArrayNoNullable = any;
+
+export type ToIFieldSubTypeNonNullable = any;
+
// ====================================================
// Scalars
// ====================================================
@@ -589,33 +591,7 @@ export interface SourceStatus {
/** Whether the configured alias or wildcard pattern resolve to any auditbeat indices */
indicesExist: boolean;
/** The list of fields defined in the index mappings */
- indexFields: IndexField[];
-}
-
-/** A descriptor of a field in an index */
-export interface IndexField {
- /** Where the field belong */
- category: string;
- /** Example of field's value */
- example?: Maybe;
- /** whether the field's belong to an alias index */
- indexes: (Maybe)[];
- /** The name of the field */
- name: string;
- /** The type of the field's values as recognized by Kibana */
- type: string;
- /** Whether the field's values can be efficiently searched for */
- searchable: boolean;
- /** Whether the field's values can be aggregated */
- aggregatable: boolean;
- /** Description of the field */
- description?: Maybe;
-
- format?: Maybe;
- /** the elastic type as mapped in the index */
- esTypes?: Maybe;
-
- subType?: Maybe;
+ indexFields: string[];
}
export interface AuthenticationsData {
@@ -1946,6 +1922,8 @@ export interface TimelineResult {
kqlQuery?: Maybe;
+ indexNames?: Maybe;
+
notes?: Maybe;
noteIds?: Maybe;
@@ -2218,6 +2196,32 @@ export interface HostFields {
type?: Maybe;
}
+/** A descriptor of a field in an index */
+export interface IndexField {
+ /** Where the field belong */
+ category: string;
+ /** Example of field's value */
+ example?: Maybe;
+ /** whether the field's belong to an alias index */
+ indexes: (Maybe)[];
+ /** The name of the field */
+ name: string;
+ /** The type of the field's values as recognized by Kibana */
+ type: string;
+ /** Whether the field's values can be efficiently searched for */
+ searchable: boolean;
+ /** Whether the field's values can be aggregated */
+ aggregatable: boolean;
+ /** Description of the field */
+ description?: Maybe;
+
+ format?: Maybe;
+ /** the elastic type as mapped in the index */
+ esTypes?: Maybe;
+
+ subType?: Maybe;
+}
+
// ====================================================
// Arguments
// ====================================================
@@ -2637,61 +2641,6 @@ export namespace GetMatrixHistogramQuery {
};
}
-export namespace SourceQuery {
- export type Variables = {
- sourceId?: Maybe;
- defaultIndex: string[];
- };
-
- export type Query = {
- __typename?: 'Query';
-
- source: Source;
- };
-
- export type Source = {
- __typename?: 'Source';
-
- id: string;
-
- status: Status;
- };
-
- export type Status = {
- __typename?: 'SourceStatus';
-
- indicesExist: boolean;
-
- indexFields: IndexFields[];
- };
-
- export type IndexFields = {
- __typename?: 'IndexField';
-
- category: string;
-
- description: Maybe;
-
- example: Maybe;
-
- indexes: (Maybe)[];
-
- name: string;
-
- searchable: boolean;
-
- type: string;
-
- aggregatable: boolean;
-
- format: Maybe;
-
- esTypes: Maybe;
-
- subType: Maybe;
- };
-}
-
export namespace GetAuthenticationsQuery {
export type Variables = {
sourceId: string;
@@ -5269,6 +5218,8 @@ export namespace GetOneTimeline {
kqlQuery: Maybe;
+ indexNames: Maybe;
+
notes: Maybe;
noteIds: Maybe;
@@ -5601,6 +5552,8 @@ export namespace PersistTimelineMutation {
kqlQuery: Maybe;
+ indexNames: Maybe;
+
title: Maybe;
dateRange: Maybe;
diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx
index 606b43c6508fb..4f64cca45d162 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.test.tsx
@@ -40,7 +40,12 @@ describe('FirstLastSeen Component', () => {
useFirstLastSeenHostMock.mockReturnValue([true, MOCKED_RESPONSE]);
const { container } = render(
-
+
);
expect(container.innerHTML).toBe(
@@ -52,7 +57,12 @@ describe('FirstLastSeen Component', () => {
useFirstLastSeenHostMock.mockReturnValue([false, MOCKED_RESPONSE]);
const { container } = render(
-
+
);
@@ -69,7 +79,12 @@ describe('FirstLastSeen Component', () => {
useFirstLastSeenHostMock.mockReturnValue([false, MOCKED_RESPONSE]);
const { container } = render(
-
+
);
await act(() =>
@@ -91,7 +106,12 @@ describe('FirstLastSeen Component', () => {
]);
const { container } = render(
-
+
);
@@ -114,7 +134,12 @@ describe('FirstLastSeen Component', () => {
]);
const { container } = render(
-
+
);
@@ -137,7 +162,12 @@ describe('FirstLastSeen Component', () => {
]);
const { container } = render(
-
+
);
await act(() =>
@@ -157,7 +187,12 @@ describe('FirstLastSeen Component', () => {
]);
const { container } = render(
-
+
);
await act(() =>
diff --git a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx
index a1b72fb39069c..ee415560cf9de 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/components/first_last_seen_host/index.tsx
@@ -10,6 +10,7 @@ import React, { useMemo } from 'react';
import { useFirstLastSeenHost } from '../../containers/hosts/first_last_seen';
import { getEmptyTagValue } from '../../../common/components/empty_value';
import { FormattedRelativePreferenceDate } from '../../../common/components/formatted_date';
+import { DocValueFields } from '../../../../common/search_strategy';
export enum FirstLastSeenHostType {
FIRST_SEEN = 'first-seen',
@@ -17,47 +18,53 @@ export enum FirstLastSeenHostType {
}
interface FirstLastSeenHostProps {
+ docValueFields: DocValueFields[];
hostName: string;
+ indexNames: string[];
type: FirstLastSeenHostType;
}
-export const FirstLastSeenHost = React.memo(({ hostName, type }) => {
- const [loading, { firstSeen, lastSeen, errorMessage }] = useFirstLastSeenHost({
- hostName,
- });
- const valueSeen = useMemo(
- () => (type === FirstLastSeenHostType.FIRST_SEEN ? firstSeen : lastSeen),
- [firstSeen, lastSeen, type]
- );
+export const FirstLastSeenHost = React.memo(
+ ({ docValueFields, hostName, type, indexNames }) => {
+ const [loading, { firstSeen, lastSeen, errorMessage }] = useFirstLastSeenHost({
+ docValueFields,
+ hostName,
+ indexNames,
+ });
+ const valueSeen = useMemo(
+ () => (type === FirstLastSeenHostType.FIRST_SEEN ? firstSeen : lastSeen),
+ [firstSeen, lastSeen, type]
+ );
+
+ if (errorMessage != null) {
+ return (
+
+
+
+ );
+ }
- if (errorMessage != null) {
return (
-
-
-
+ <>
+ {loading && }
+ {!loading && valueSeen != null && new Date(valueSeen).toString() === 'Invalid Date'
+ ? valueSeen
+ : !loading &&
+ valueSeen != null && (
+
+
+
+ )}
+ {!loading && valueSeen == null && getEmptyTagValue()}
+ >
);
}
-
- return (
- <>
- {loading && }
- {!loading && valueSeen != null && new Date(valueSeen).toString() === 'Invalid Date'
- ? valueSeen
- : !loading &&
- valueSeen != null && (
-
-
-
- )}
- {!loading && valueSeen == null && getEmptyTagValue()}
- >
- );
-});
+);
FirstLastSeenHost.displayName = 'FirstLastSeenHost';
diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/__snapshots__/index.test.tsx.snap
index 3143e680913b2..1d70f4f72ac8b 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/__snapshots__/index.test.tsx.snap
@@ -68,97 +68,6 @@ exports[`Hosts Table rendering it renders the default Hosts table 1`] = `
}
fakeTotalCount={50}
id="hostsQuery"
- indexPattern={
- Object {
- "fields": Array [
- Object {
- "aggregatable": true,
- "name": "@timestamp",
- "searchable": true,
- "type": "date",
- },
- Object {
- "aggregatable": true,
- "name": "@version",
- "searchable": true,
- "type": "string",
- },
- Object {
- "aggregatable": true,
- "name": "agent.ephemeral_id",
- "searchable": true,
- "type": "string",
- },
- Object {
- "aggregatable": true,
- "name": "agent.hostname",
- "searchable": true,
- "type": "string",
- },
- Object {
- "aggregatable": true,
- "name": "agent.id",
- "searchable": true,
- "type": "string",
- },
- Object {
- "aggregatable": true,
- "name": "agent.test1",
- "searchable": true,
- "type": "string",
- },
- Object {
- "aggregatable": true,
- "name": "agent.test2",
- "searchable": true,
- "type": "string",
- },
- Object {
- "aggregatable": true,
- "name": "agent.test3",
- "searchable": true,
- "type": "string",
- },
- Object {
- "aggregatable": true,
- "name": "agent.test4",
- "searchable": true,
- "type": "string",
- },
- Object {
- "aggregatable": true,
- "name": "agent.test5",
- "searchable": true,
- "type": "string",
- },
- Object {
- "aggregatable": true,
- "name": "agent.test6",
- "searchable": true,
- "type": "string",
- },
- Object {
- "aggregatable": true,
- "name": "agent.test7",
- "searchable": true,
- "type": "string",
- },
- Object {
- "aggregatable": true,
- "name": "agent.test8",
- "searchable": true,
- "type": "string",
- },
- Object {
- "aggregatable": true,
- "name": "host.name",
- "searchable": true,
- "type": "string",
- },
- ],
- "title": "filebeat-*,auditbeat-*,packetbeat-*",
- }
- }
isInspect={false}
loadPage={[MockFunction]}
loading={false}
diff --git a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx
index c4a391687843c..29e4dc48ae3c7 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/components/hosts_table/index.test.tsx
@@ -12,7 +12,6 @@ import { MockedProvider } from 'react-apollo/test-utils';
import '../../../common/mock/match_media';
import {
apolloClientObservable,
- mockIndexPattern,
mockGlobalState,
TestProviders,
SUB_PLUGINS_REDUCER,
@@ -69,7 +68,6 @@ describe('Hosts Table', () => {
data={mockData.Hosts.edges}
id="hostsQuery"
isInspect={false}
- indexPattern={mockIndexPattern}
fakeTotalCount={getOr(50, 'fakeTotalCount', mockData.Hosts.pageInfo)}
loading={false}
loadPage={loadPage}
@@ -92,7 +90,6 @@ describe('Hosts Table', () => {
void;
@@ -77,7 +75,6 @@ const HostsTableComponent = React.memo(
direction,
fakeTotalCount,
id,
- indexPattern,
isInspect,
limit,
loading,
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx
index 0949616827470..84003e5dea5e9 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/authentications/index.tsx
@@ -42,6 +42,7 @@ export const fieldsMapping: Readonly = [
const HostsKpiAuthenticationsComponent: React.FC = ({
filterQuery,
from,
+ indexNames,
to,
narrowDateRange,
setQuery,
@@ -50,6 +51,7 @@ const HostsKpiAuthenticationsComponent: React.FC = ({
const [loading, { refetch, id, inspect, ...data }] = useHostsKpiAuthentications({
filterQuery,
endDate: to,
+ indexNames,
startDate: from,
skip,
});
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx
index b1c4d6331e450..908ff717e2711 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/hosts/index.tsx
@@ -31,6 +31,7 @@ export const fieldsMapping: Readonly = [
const HostsKpiHostsComponent: React.FC = ({
filterQuery,
from,
+ indexNames,
to,
narrowDateRange,
setQuery,
@@ -39,6 +40,7 @@ const HostsKpiHostsComponent: React.FC = ({
const [loading, { refetch, id, inspect, ...data }] = useHostsKpiHosts({
filterQuery,
endDate: to,
+ indexNames,
startDate: from,
skip,
});
diff --git a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx
index fff4c64900a8b..6174e174db5a6 100644
--- a/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/components/kpi_hosts/index.tsx
@@ -13,12 +13,13 @@ import { HostsKpiUniqueIps } from './unique_ips';
import { HostsKpiProps } from './types';
export const HostsKpiComponent = React.memo(
- ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => (
+ ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => (
(
(
(
HostsKpiComponent.displayName = 'HostsKpiComponent';
export const HostsDetailsKpiComponent = React.memo(
- ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => (
+ ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => (
(
= [
const HostsKpiUniqueIpsComponent: React.FC = ({
filterQuery,
from,
+ indexNames,
to,
narrowDateRange,
setQuery,
@@ -50,6 +51,7 @@ const HostsKpiUniqueIpsComponent: React.FC = ({
const [loading, { refetch, id, inspect, ...data }] = useHostsKpiUniqueIps({
filterQuery,
endDate: to,
+ indexNames,
startDate: from,
skip,
});
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx
index 7bf4f7a833fb8..b1563e85c93dd 100644
--- a/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/containers/authentications/index.tsx
@@ -15,7 +15,6 @@ import {
isErrorResponse,
} from '../../../../../../../src/plugins/data/common';
-import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
import { HostsQueries } from '../../../../common/search_strategy/security_solution';
import {
HostAuthenticationsRequestOptions,
@@ -56,6 +55,7 @@ interface UseAuthentications {
docValueFields?: DocValueFields[];
filterQuery?: ESTermQuery | string;
endDate: string;
+ indexNames: string[];
startDate: string;
type: hostsModel.HostsType;
skip: boolean;
@@ -65,6 +65,7 @@ export const useAuthentications = ({
docValueFields,
filterQuery,
endDate,
+ indexNames,
startDate,
type,
skip,
@@ -74,15 +75,14 @@ export const useAuthentications = ({
(state: State) => getAuthenticationsSelector(state, type),
shallowEqual
);
- const { data, notifications, uiSettings } = useKibana().services;
+ const { data, notifications } = useKibana().services;
const refetch = useRef(noop);
const abortCtrl = useRef(new AbortController());
- const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
const [loading, setLoading] = useState(false);
const [authenticationsRequest, setAuthenticationsRequest] = useState<
HostAuthenticationsRequestOptions
>({
- defaultIndex,
+ defaultIndex: indexNames,
docValueFields: docValueFields ?? [],
factoryQueryType: HostsQueries.authentications,
filterQuery: createFilter(filterQuery),
@@ -186,7 +186,7 @@ export const useAuthentications = ({
setAuthenticationsRequest((prevRequest) => {
const myRequest = {
...prevRequest,
- defaultIndex,
+ defaultIndex: indexNames,
docValueFields: docValueFields ?? [],
filterQuery: createFilter(filterQuery),
pagination: generateTablePaginationOptions(activePage, limit),
@@ -201,7 +201,7 @@ export const useAuthentications = ({
}
return prevRequest;
});
- }, [activePage, defaultIndex, docValueFields, endDate, filterQuery, limit, skip, startDate]);
+ }, [activePage, docValueFields, endDate, filterQuery, indexNames, limit, skip, startDate]);
useEffect(() => {
authenticationsSearch(authenticationsRequest);
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx
index f68c340a47723..5b69e20398a35 100644
--- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/_index.tsx
@@ -10,7 +10,6 @@ import deepEqual from 'fast-deep-equal';
import { noop } from 'lodash/fp';
import { useCallback, useEffect, useRef, useState } from 'react';
-import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
import { inputsModel } from '../../../../common/store';
import { useKibana } from '../../../../common/lib/kibana';
import {
@@ -41,9 +40,10 @@ export interface HostDetailsArgs {
}
interface UseHostDetails {
- id?: string;
- hostName: string;
endDate: string;
+ hostName: string;
+ id?: string;
+ indexNames: string[];
skip?: boolean;
startDate: string;
}
@@ -51,17 +51,17 @@ interface UseHostDetails {
export const useHostDetails = ({
endDate,
hostName,
+ indexNames,
+ id = ID,
skip = false,
startDate,
- id = ID,
}: UseHostDetails): [boolean, HostDetailsArgs] => {
- const { data, notifications, uiSettings } = useKibana().services;
+ const { data, notifications } = useKibana().services;
const refetch = useRef(noop);
const abortCtrl = useRef(new AbortController());
- const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
const [loading, setLoading] = useState(false);
const [hostDetailsRequest, setHostDetailsRequest] = useState({
- defaultIndex,
+ defaultIndex: indexNames,
hostName,
factoryQueryType: HostsQueries.details,
timerange: {
@@ -142,7 +142,7 @@ export const useHostDetails = ({
setHostDetailsRequest((prevRequest) => {
const myRequest = {
...prevRequest,
- defaultIndex,
+ defaultIndex: indexNames,
hostName,
timerange: {
interval: '12h',
@@ -155,7 +155,7 @@ export const useHostDetails = ({
}
return prevRequest;
});
- }, [defaultIndex, endDate, hostName, startDate, skip]);
+ }, [endDate, hostName, indexNames, startDate, skip]);
useEffect(() => {
hostDetailsSearch(hostDetailsRequest);
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx
index 12a82c7980b61..0236270d18618 100644
--- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/details/index.tsx
@@ -10,11 +10,9 @@ import { Query } from 'react-apollo';
import { connect } from 'react-redux';
import { compose } from 'redux';
-import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
import { inputsModel, inputsSelectors, State } from '../../../../common/store';
import { getDefaultFetchPolicy } from '../../../../common/containers/helpers';
import { QueryTemplate, QueryTemplateProps } from '../../../../common/containers/query_template';
-import { withKibana, WithKibanaProps } from '../../../../common/lib/kibana';
import { HostOverviewQuery } from './host_overview.gql_query';
import { GetHostOverviewQuery, HostItem } from '../../../../graphql/types';
@@ -42,7 +40,7 @@ export interface OwnProps extends QueryTemplateProps {
endDate: string;
}
-type HostsOverViewProps = OwnProps & HostOverviewReduxProps & WithKibanaProps;
+type HostsOverViewProps = OwnProps & HostOverviewReduxProps;
class HostOverviewByNameComponentQuery extends QueryTemplate<
HostsOverViewProps,
@@ -52,10 +50,10 @@ class HostOverviewByNameComponentQuery extends QueryTemplate<
public render() {
const {
id = ID,
+ indexNames,
isInspected,
children,
hostName,
- kibana,
skip,
sourceId,
startDate,
@@ -75,7 +73,7 @@ class HostOverviewByNameComponentQuery extends QueryTemplate<
from: startDate,
to: endDate,
},
- defaultIndex: kibana.services.uiSettings.get(DEFAULT_INDEX_KEY),
+ defaultIndex: indexNames,
inspect: isInspected,
}}
>
@@ -108,6 +106,5 @@ const makeMapStateToProps = () => {
};
export const HostOverviewByNameQuery = compose>(
- connect(makeMapStateToProps),
- withKibana
+ connect(makeMapStateToProps)
)(HostOverviewByNameComponentQuery);
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx
index a6376642dfa29..cc944a59571f1 100644
--- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/first_last_seen/index.tsx
@@ -7,17 +7,15 @@
import deepEqual from 'fast-deep-equal';
import { useCallback, useEffect, useRef, useState } from 'react';
-import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
-
import { useKibana } from '../../../../common/lib/kibana';
import {
HostsQueries,
HostFirstLastSeenRequestOptions,
HostFirstLastSeenStrategyResponse,
} from '../../../../../common/search_strategy/security_solution';
-import { useWithSource } from '../../../../common/containers/source';
import * as i18n from './translations';
+import { DocValueFields } from '../../../../../common/search_strategy';
import {
AbortError,
isCompleteResponse,
@@ -33,21 +31,23 @@ export interface FirstLastSeenHostArgs {
lastSeen?: string | null;
}
interface UseHostFirstLastSeen {
+ docValueFields: DocValueFields[];
hostName: string;
+ indexNames: string[];
}
export const useFirstLastSeenHost = ({
+ docValueFields,
hostName,
+ indexNames,
}: UseHostFirstLastSeen): [boolean, FirstLastSeenHostArgs] => {
- const { docValueFields } = useWithSource('default');
- const { data, notifications, uiSettings } = useKibana().services;
+ const { data, notifications } = useKibana().services;
const abortCtrl = useRef(new AbortController());
- const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
const [loading, setLoading] = useState(false);
const [firstLastSeenHostRequest, setFirstLastSeenHostRequest] = useState<
HostFirstLastSeenRequestOptions
>({
- defaultIndex,
+ defaultIndex: indexNames,
docValueFields: docValueFields ?? [],
factoryQueryType: HostsQueries.firstLastSeen,
hostName,
@@ -124,7 +124,7 @@ export const useFirstLastSeenHost = ({
setFirstLastSeenHostRequest((prevRequest) => {
const myRequest = {
...prevRequest,
- defaultIndex,
+ defaultIndex: indexNames,
docValueFields: docValueFields ?? [],
hostName,
};
@@ -133,7 +133,7 @@ export const useFirstLastSeenHost = ({
}
return prevRequest;
});
- }, [defaultIndex, docValueFields, hostName]);
+ }, [indexNames, docValueFields, hostName]);
useEffect(() => {
firstLastSeenHostSearch(firstLastSeenHostRequest);
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx
index 2eb926a9733c3..6ca0272e58d7d 100644
--- a/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/containers/hosts/index.tsx
@@ -9,7 +9,6 @@ import { noop } from 'lodash/fp';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useSelector } from 'react-redux';
-import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
import { inputsModel, State } from '../../../common/store';
import { createFilter } from '../../../common/containers/helpers';
import { useKibana } from '../../../common/lib/kibana';
@@ -54,6 +53,7 @@ interface UseAllHost {
docValueFields?: DocValueFields[];
filterQuery?: ESTermQuery | string;
endDate: string;
+ indexNames: string[];
skip?: boolean;
startDate: string;
type: hostsModel.HostsType;
@@ -63,6 +63,7 @@ export const useAllHost = ({
docValueFields,
filterQuery,
endDate,
+ indexNames,
skip = false,
startDate,
type,
@@ -71,13 +72,12 @@ export const useAllHost = ({
const { activePage, direction, limit, sortField } = useSelector((state: State) =>
getHostsSelector(state, type)
);
- const { data, notifications, uiSettings } = useKibana().services;
+ const { data, notifications } = useKibana().services;
const refetch = useRef(noop);
const abortCtrl = useRef(new AbortController());
- const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
const [loading, setLoading] = useState(false);
const [hostsRequest, setHostRequest] = useState({
- defaultIndex,
+ defaultIndex: indexNames,
docValueFields: docValueFields ?? [],
factoryQueryType: HostsQueries.hosts,
filterQuery: createFilter(filterQuery),
@@ -181,7 +181,7 @@ export const useAllHost = ({
setHostRequest((prevRequest) => {
const myRequest = {
...prevRequest,
- defaultIndex,
+ defaultIndex: indexNames,
docValueFields: docValueFields ?? [],
filterQuery: createFilter(filterQuery),
pagination: generateTablePaginationOptions(activePage, limit),
@@ -202,11 +202,11 @@ export const useAllHost = ({
});
}, [
activePage,
- defaultIndex,
direction,
docValueFields,
endDate,
filterQuery,
+ indexNames,
limit,
skip,
startDate,
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.tsx
index 1551e7d706714..26e4eaf9ea82e 100644
--- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_host_details/index.tsx
@@ -9,10 +9,8 @@ import React from 'react';
import { Query } from 'react-apollo';
import { connect, ConnectedProps } from 'react-redux';
-import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
import { KpiHostDetailsData, GetKpiHostDetailsQuery } from '../../../graphql/types';
import { inputsModel, inputsSelectors, State } from '../../../common/store';
-import { useUiSetting } from '../../../common/lib/kibana';
import { createFilter, getDefaultFetchPolicy } from '../../../common/containers/helpers';
import { QueryTemplateProps } from '../../../common/containers/query_template';
@@ -33,7 +31,17 @@ export interface QueryKpiHostDetailsProps extends QueryTemplateProps {
}
const KpiHostDetailsComponentQuery = React.memo(
- ({ id = ID, children, endDate, filterQuery, isInspected, skip, sourceId, startDate }) => (
+ ({
+ id = ID,
+ children,
+ endDate,
+ filterQuery,
+ indexNames,
+ isInspected,
+ skip,
+ sourceId,
+ startDate,
+ }) => (
query={kpiHostDetailsQuery}
fetchPolicy={getDefaultFetchPolicy()}
@@ -47,7 +55,7 @@ const KpiHostDetailsComponentQuery = React.memo(DEFAULT_INDEX_KEY),
+ defaultIndex: indexNames ?? [],
inspect: isInspected,
}}
>
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx
index 0d90b73e0a584..404231be1e6cd 100644
--- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/authentications/index.tsx
@@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal';
import { noop } from 'lodash/fp';
import { useCallback, useEffect, useRef, useState } from 'react';
-import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
import { inputsModel } from '../../../../common/store';
import { createFilter } from '../../../../common/containers/helpers';
import { useKibana } from '../../../../common/lib/kibana';
@@ -37,6 +36,7 @@ export interface HostsKpiAuthenticationsArgs
interface UseHostsKpiAuthentications {
filterQuery?: ESTermQuery | string;
endDate: string;
+ indexNames: string[];
skip?: boolean;
startDate: string;
}
@@ -44,18 +44,18 @@ interface UseHostsKpiAuthentications {
export const useHostsKpiAuthentications = ({
filterQuery,
endDate,
+ indexNames,
skip = false,
startDate,
}: UseHostsKpiAuthentications): [boolean, HostsKpiAuthenticationsArgs] => {
- const { data, notifications, uiSettings } = useKibana().services;
+ const { data, notifications } = useKibana().services;
const refetch = useRef(noop);
const abortCtrl = useRef(new AbortController());
- const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
const [loading, setLoading] = useState(false);
const [hostsKpiAuthenticationsRequest, setHostsKpiAuthenticationsRequest] = useState<
HostsKpiAuthenticationsRequestOptions
>({
- defaultIndex,
+ defaultIndex: indexNames,
factoryQueryType: HostsKpiQueries.kpiAuthentications,
filterQuery: createFilter(filterQuery),
id: ID,
@@ -147,7 +147,7 @@ export const useHostsKpiAuthentications = ({
setHostsKpiAuthenticationsRequest((prevRequest) => {
const myRequest = {
...prevRequest,
- defaultIndex,
+ defaultIndex: indexNames,
filterQuery: createFilter(filterQuery),
timerange: {
interval: '12h',
@@ -160,7 +160,7 @@ export const useHostsKpiAuthentications = ({
}
return prevRequest;
});
- }, [defaultIndex, endDate, filterQuery, skip, startDate]);
+ }, [indexNames, endDate, filterQuery, skip, startDate]);
useEffect(() => {
hostsKpiAuthenticationsSearch(hostsKpiAuthenticationsRequest);
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx
index 190ce1aa7eae1..bb918a9214f40 100644
--- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/hosts/index.tsx
@@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal';
import { noop } from 'lodash/fp';
import { useCallback, useEffect, useRef, useState } from 'react';
-import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
import { inputsModel } from '../../../../common/store';
import { createFilter } from '../../../../common/containers/helpers';
import { useKibana } from '../../../../common/lib/kibana';
@@ -36,6 +35,7 @@ export interface HostsKpiHostsArgs extends Omit {
- const { data, notifications, uiSettings } = useKibana().services;
+ const { data, notifications } = useKibana().services;
const refetch = useRef(noop);
const abortCtrl = useRef(new AbortController());
- const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
const [loading, setLoading] = useState(false);
const [hostsKpiHostsRequest, setHostsKpiHostsRequest] = useState({
- defaultIndex,
+ defaultIndex: indexNames,
factoryQueryType: HostsKpiQueries.kpiHosts,
filterQuery: createFilter(filterQuery),
id: ID,
@@ -135,7 +135,7 @@ export const useHostsKpiHosts = ({
setHostsKpiHostsRequest((prevRequest) => {
const myRequest = {
...prevRequest,
- defaultIndex,
+ defaultIndex: indexNames,
filterQuery: createFilter(filterQuery),
timerange: {
interval: '12h',
@@ -148,7 +148,7 @@ export const useHostsKpiHosts = ({
}
return prevRequest;
});
- }, [defaultIndex, endDate, filterQuery, skip, startDate]);
+ }, [indexNames, endDate, filterQuery, skip, startDate]);
useEffect(() => {
hostsKpiHostsSearch(hostsKpiHostsRequest);
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx
index ac5cc12807f00..b8e93eef8dc91 100644
--- a/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/containers/kpi_hosts/unique_ips/index.tsx
@@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal';
import { noop } from 'lodash/fp';
import { useCallback, useEffect, useRef, useState } from 'react';
-import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
import { inputsModel } from '../../../../common/store';
import { createFilter } from '../../../../common/containers/helpers';
import { useKibana } from '../../../../common/lib/kibana';
@@ -37,6 +36,7 @@ export interface HostsKpiUniqueIpsArgs
interface UseHostsKpiUniqueIps {
filterQuery?: ESTermQuery | string;
endDate: string;
+ indexNames: string[];
skip?: boolean;
startDate: string;
}
@@ -44,18 +44,18 @@ interface UseHostsKpiUniqueIps {
export const useHostsKpiUniqueIps = ({
filterQuery,
endDate,
+ indexNames,
skip = false,
startDate,
}: UseHostsKpiUniqueIps): [boolean, HostsKpiUniqueIpsArgs] => {
- const { data, notifications, uiSettings } = useKibana().services;
+ const { data, notifications } = useKibana().services;
const refetch = useRef(noop);
const abortCtrl = useRef(new AbortController());
- const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
const [loading, setLoading] = useState(false);
const [hostsKpiUniqueIpsRequest, setHostsKpiUniqueIpsRequest] = useState<
HostsKpiUniqueIpsRequestOptions
>({
- defaultIndex,
+ defaultIndex: indexNames,
factoryQueryType: HostsKpiQueries.kpiUniqueIps,
filterQuery: createFilter(filterQuery),
id: ID,
@@ -144,7 +144,7 @@ export const useHostsKpiUniqueIps = ({
setHostsKpiUniqueIpsRequest((prevRequest) => {
const myRequest = {
...prevRequest,
- defaultIndex,
+ defaultIndex: indexNames,
filterQuery: createFilter(filterQuery),
timerange: {
interval: '12h',
@@ -157,7 +157,7 @@ export const useHostsKpiUniqueIps = ({
}
return prevRequest;
});
- }, [defaultIndex, endDate, filterQuery, skip, startDate]);
+ }, [indexNames, endDate, filterQuery, skip, startDate]);
useEffect(() => {
hostsKpiUniqueIpsSearch(hostsKpiUniqueIpsRequest);
diff --git a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx
index e28a808378dd7..4036837024025 100644
--- a/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/containers/uncommon_processes/index.tsx
@@ -15,7 +15,6 @@ import {
isErrorResponse,
} from '../../../../../../../src/plugins/data/common';
-import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
import { inputsModel, State } from '../../../common/store';
import { useKibana } from '../../../common/lib/kibana';
import { generateTablePaginationOptions } from '../../../common/components/paginated_table/helpers';
@@ -53,6 +52,7 @@ interface UseUncommonProcesses {
docValueFields?: DocValueFields[];
filterQuery?: ESTermQuery | string;
endDate: string;
+ indexNames: string[];
skip?: boolean;
startDate: string;
type: hostsModel.HostsType;
@@ -62,6 +62,7 @@ export const useUncommonProcesses = ({
docValueFields,
filterQuery,
endDate,
+ indexNames,
skip = false,
startDate,
type,
@@ -70,15 +71,14 @@ export const useUncommonProcesses = ({
const { activePage, limit } = useSelector((state: State) =>
getUncommonProcessesSelector(state, type)
);
- const { data, notifications, uiSettings } = useKibana().services;
+ const { data, notifications } = useKibana().services;
const refetch = useRef(noop);
const abortCtrl = useRef(new AbortController());
- const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
const [loading, setLoading] = useState(false);
const [uncommonProcessesRequest, setUncommonProcessesRequest] = useState<
HostsUncommonProcessesRequestOptions
>({
- defaultIndex,
+ defaultIndex: indexNames,
docValueFields: docValueFields ?? [],
factoryQueryType: HostsQueries.uncommonProcesses,
filterQuery: createFilter(filterQuery),
@@ -186,7 +186,7 @@ export const useUncommonProcesses = ({
setUncommonProcessesRequest((prevRequest) => {
const myRequest = {
...prevRequest,
- defaultIndex,
+ defaultIndex: indexNames,
docValueFields: docValueFields ?? [],
filterQuery: createFilter(filterQuery),
pagination: generateTablePaginationOptions(activePage, limit),
@@ -202,7 +202,7 @@ export const useUncommonProcesses = ({
}
return prevRequest;
});
- }, [activePage, defaultIndex, docValueFields, endDate, filterQuery, limit, skip, startDate]);
+ }, [activePage, indexNames, docValueFields, endDate, filterQuery, limit, skip, startDate]);
useEffect(() => {
uncommonProcessesSearch(uncommonProcessesRequest);
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx
index 11a268c7b64ad..708c8b2b40b35 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.test.tsx
@@ -84,6 +84,7 @@ describe('body', () => {
setQuery={jest.fn()}
setAbsoluteRangeDatePicker={(jest.fn() as unknown) as SetAbsoluteRangeDatePicker}
hostDetailsPagePath={hostDetailsPagePath}
+ indexNames={[]}
indexPattern={mockIndexPattern}
type={type}
pageFilters={mockHostDetailsPageFilters}
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx
index 4d4eead0e778a..284e6e27cf615 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/details/details_tabs.tsx
@@ -28,12 +28,13 @@ import {
export const HostDetailsTabs = React.memo(
({
+ detailName,
docValueFields,
- pageFilters,
filterQuery,
- detailName,
- setAbsoluteRangeDatePicker,
+ indexNames,
indexPattern,
+ pageFilters,
+ setAbsoluteRangeDatePicker,
hostDetailsPagePath,
}) => {
const { from, to, isInitializing, deleteQuery, setQuery } = useGlobalTime();
@@ -73,6 +74,7 @@ export const HostDetailsTabs = React.memo(
startDate: from,
type,
indexPattern,
+ indexNames,
hostName: detailName,
narrowDateRange,
updateDateRange,
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx
index 57e1b128ce64d..55b2b529000be 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/details/index.tsx
@@ -28,7 +28,6 @@ import { SiemSearchBar } from '../../../common/components/search_bar';
import { WrapperPage } from '../../../common/components/wrapper_page';
import { HostOverviewByNameQuery } from '../../containers/hosts/details';
import { useGlobalTime } from '../../../common/containers/use_global_time';
-import { useWithSource } from '../../../common/containers/source';
import { LastEventIndexKey } from '../../../graphql/types';
import { useKibana } from '../../../common/lib/kibana';
import { convertToBuildEsQuery } from '../../../common/lib/keury';
@@ -51,6 +50,7 @@ import { timelineSelectors } from '../../../timelines/store/timeline';
import { TimelineModel } from '../../../timelines/store/timeline/model';
import { TimelineId } from '../../../../common/types/timeline';
import { timelineDefaults } from '../../../timelines/store/timeline/defaults';
+import { useSourcererScope } from '../../../common/containers/sourcerer';
const HostOverviewManage = manageQuery(HostOverview);
@@ -89,7 +89,7 @@ const HostDetailsComponent = React.memo(
},
[setAbsoluteRangeDatePicker]
);
- const { docValueFields, indicesExist, indexPattern } = useWithSource();
+ const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope();
const filterQuery = convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
indexPattern,
@@ -111,12 +111,18 @@ const HostDetailsComponent = React.memo(
+
}
title={detailName}
/>
(
>
{({ isLoadingAnomaliesData, anomaliesData }) => (
(
data={hostOverview as HostItem}
anomaliesData={anomaliesData}
isLoadingAnomaliesData={isLoadingAnomaliesData}
+ indexNames={selectedPatterns}
loading={loading}
startDate={from}
endDate={to}
@@ -161,6 +169,7 @@ const HostDetailsComponent = React.memo(
(
;
export type HostDetailsTabsProps = HostBodyComponentDispatchProps &
HostsQueryProps & {
docValueFields?: DocValueFields[];
+ indexNames: string[];
pageFilters?: Filter[];
filterQuery: string;
indexPattern: IIndexPattern;
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx
index 566f8f23efd39..b341647afdfbc 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.test.tsx
@@ -10,7 +10,6 @@ import { Router } from 'react-router-dom';
import { Filter } from '../../../../../../src/plugins/data/common/es_query';
import '../../common/mock/match_media';
-import { useWithSource } from '../../common/containers/source';
import {
apolloClientObservable,
TestProviders,
@@ -25,8 +24,9 @@ import { State, createStore } from '../../common/store';
import { HostsComponentProps } from './types';
import { Hosts } from './hosts';
import { HostsTabs } from './hosts_tabs';
+import { useSourcererScope } from '../../common/containers/sourcerer';
-jest.mock('../../common/containers/source');
+jest.mock('../../common/containers/sourcerer');
// Test will fail because we will to need to mock some core services to make the test work
// For now let's forget about SiemSearchBar and QueryBar
@@ -58,14 +58,14 @@ const mockHistory = {
createHref: jest.fn(),
listen: jest.fn(),
};
-
+const mockUseSourcererScope = useSourcererScope as jest.Mock;
describe('Hosts - rendering', () => {
const hostProps: HostsComponentProps = {
hostsPagePath: '',
};
test('it renders the Setup Instructions text when no index is available', async () => {
- (useWithSource as jest.Mock).mockReturnValue({
+ mockUseSourcererScope.mockReturnValue({
indicesExist: false,
});
@@ -80,7 +80,7 @@ describe('Hosts - rendering', () => {
});
test('it DOES NOT render the Setup Instructions text when an index is available', async () => {
- (useWithSource as jest.Mock).mockReturnValue({
+ mockUseSourcererScope.mockReturnValue({
indicesExist: true,
indexPattern: {},
});
@@ -95,7 +95,7 @@ describe('Hosts - rendering', () => {
});
test('it should render tab navigation', async () => {
- (useWithSource as jest.Mock).mockReturnValue({
+ mockUseSourcererScope.mockReturnValue({
indicesExist: true,
indexPattern: {},
});
@@ -142,7 +142,7 @@ describe('Hosts - rendering', () => {
},
},
];
- (useWithSource as jest.Mock).mockReturnValue({
+ mockUseSourcererScope.mockReturnValue({
indicesExist: true,
indexPattern: { fields: [], title: 'title' },
});
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
index 4b8e3cc6987ac..ea8cf11e7595a 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts.tsx
@@ -22,7 +22,6 @@ import { SiemSearchBar } from '../../common/components/search_bar';
import { WrapperPage } from '../../common/components/wrapper_page';
import { useFullScreen } from '../../common/containers/use_full_screen';
import { useGlobalTime } from '../../common/containers/use_global_time';
-import { useWithSource } from '../../common/containers/source';
import { TimelineId } from '../../../common/types/timeline';
import { LastEventIndexKey } from '../../graphql/types';
import { useKibana } from '../../common/lib/kibana';
@@ -46,6 +45,7 @@ import { showGlobalFilters } from '../../timelines/components/timeline/helpers';
import { timelineSelectors } from '../../timelines/store/timeline';
import { timelineDefaults } from '../../timelines/store/timeline/defaults';
import { TimelineModel } from '../../timelines/store/timeline/model';
+import { useSourcererScope } from '../../common/containers/sourcerer';
export const HostsComponent = React.memo(
({ filters, graphEventId, query, setAbsoluteRangeDatePicker, hostsPagePath }) => {
@@ -74,7 +74,7 @@ export const HostsComponent = React.memo(
},
[setAbsoluteRangeDatePicker]
);
- const { docValueFields, indicesExist, indexPattern } = useWithSource();
+ const { docValueFields, indicesExist, indexPattern, selectedPatterns } = useSourcererScope();
const filterQuery = convertToBuildEsQuery({
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
indexPattern,
@@ -101,12 +101,19 @@ export const HostsComponent = React.memo(
}
+ subtitle={
+
+ }
title={i18n.PAGE_TITLE}
/>
(
to={to}
filterQuery={tabsFilterQuery}
isInitializing={isInitializing}
+ indexNames={selectedPatterns}
setAbsoluteRangeDatePicker={setAbsoluteRangeDatePicker}
setQuery={setQuery}
from={from}
type={hostsModel.HostsType.page}
- indexPattern={indexPattern}
hostsPagePath={hostsPagePath}
/>
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx
index 8e2ea06fd20cb..17dd20bac2d0d 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/hosts_tabs.tsx
@@ -28,24 +28,24 @@ export const HostsTabs = memo(
deleteQuery,
docValueFields,
filterQuery,
- setAbsoluteRangeDatePicker,
- to,
from,
- setQuery,
+ indexNames,
isInitializing,
- type,
- indexPattern,
hostsPagePath,
+ setAbsoluteRangeDatePicker,
+ setQuery,
+ to,
+ type,
}) => {
const tabProps = {
deleteQuery,
endDate: to,
filterQuery,
+ indexNames,
skip: isInitializing,
setQuery,
startDate: from,
type,
- indexPattern,
narrowDateRange: useCallback(
(score: Anomaly, interval: string) => {
const fromTo = scoreIntervalToDateTime(score, interval);
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx
index d3fc68874ce91..efce312fd85f2 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/authentications_query_tab_body.tsx
@@ -66,6 +66,7 @@ const AuthenticationsQueryTabBodyComponent: React.FC
docValueFields,
endDate,
filterQuery,
+ indexNames,
skip,
setQuery,
startDate,
@@ -74,7 +75,15 @@ const AuthenticationsQueryTabBodyComponent: React.FC
const [
loading,
{ authentications, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch },
- ] = useAuthentications({ docValueFields, endDate, filterQuery, skip, startDate, type });
+ ] = useAuthentications({
+ docValueFields,
+ endDate,
+ filterQuery,
+ indexNames,
+ skip,
+ startDate,
+ type,
+ });
useEffect(() => {
return () => {
@@ -90,6 +99,7 @@ const AuthenticationsQueryTabBodyComponent: React.FC
endDate={endDate}
filterQuery={filterQuery}
id={ID}
+ indexNames={indexNames}
setQuery={setQuery}
startDate={startDate}
{...histogramConfigs}
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx
index be8412caf7732..e30071ec04f0c 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/events_query_tab_body.tsx
@@ -20,6 +20,7 @@ import { useFullScreen } from '../../../common/containers/use_full_screen';
import * as i18n from '../translations';
import { MatrixHistogramType } from '../../../../common/search_strategy/security_solution';
import { useManageTimeline } from '../../../timelines/components/manage_timeline';
+import { SourcererScopeName } from '../../../common/store/sourcerer/model';
const EVENTS_HISTOGRAM_ID = 'eventsHistogramQuery';
@@ -54,6 +55,7 @@ const EventsQueryTabBodyComponent: React.FC = ({
deleteQuery,
endDate,
filterQuery,
+ indexNames,
pageFilters,
setQuery,
startDate,
@@ -85,6 +87,7 @@ const EventsQueryTabBodyComponent: React.FC = ({
setQuery={setQuery}
startDate={startDate}
id={EVENTS_HISTOGRAM_ID}
+ indexNames={indexNames}
{...histogramConfigs}
/>
)}
@@ -92,6 +95,7 @@ const EventsQueryTabBodyComponent: React.FC = ({
defaultModel={eventsDefaultModel}
end={endDate}
id={TimelineId.hostsPageEvents}
+ scopeId={SourcererScopeName.default}
start={startDate}
pageFilters={pageFilters}
/>
diff --git a/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx b/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx
index f8dcf9635c053..deda4b618fa64 100644
--- a/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx
+++ b/x-pack/plugins/security_solution/public/hosts/pages/navigation/hosts_query_tab_body.tsx
@@ -18,7 +18,7 @@ export const HostsQueryTabBody = ({
docValueFields,
endDate,
filterQuery,
- indexPattern,
+ indexNames,
skip,
setQuery,
startDate,
@@ -27,7 +27,7 @@ export const HostsQueryTabBody = ({
const [
loading,
{ hosts, totalCount, pageInfo, loadPage, id, inspect, isInspected, refetch },
- ] = useAllHost({ docValueFields, endDate, filterQuery, skip, startDate, type });
+ ] = useAllHost({ docValueFields, endDate, filterQuery, indexNames, skip, startDate, type });
return (
-
+
{actions}
diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
index 235f150637116..40c982cfc071b 100644
--- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx
@@ -203,6 +203,7 @@ export const PolicyDetails = React.memo(() => {
)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
reactTestingLibrary.act(() => {
history.push('/trusted_apps');
});
+ window.scrollTo = jest.fn();
});
test.skip('rendering', () => {
diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap
index a03d7c2317517..c512bd99a7916 100644
--- a/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap
+++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/__snapshots__/index.test.tsx.snap
@@ -4,6 +4,7 @@ exports[`NetworkKpiComponent rendering it renders the default widget 1`] = `
= [
const NetworkKpiDnsComponent: React.FC = ({
filterQuery,
from,
+ indexNames,
to,
narrowDateRange,
setQuery,
@@ -36,6 +37,7 @@ const NetworkKpiDnsComponent: React.FC = ({
const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiDns({
filterQuery,
endDate: to,
+ indexNames,
startDate: from,
skip,
});
diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.test.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.test.tsx
index 9c6b2fe3c8ca4..25a9fe03f9205 100644
--- a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.test.tsx
@@ -22,12 +22,13 @@ import { NetworkKpiComponent } from '.';
describe('NetworkKpiComponent', () => {
const state: State = mockGlobalState;
const props = {
+ filterQuery: '',
from: '2019-06-15T06:00:00.000Z',
- to: '2019-06-18T06:00:00.000Z',
+ indexNames: [],
narrowDateRange: jest.fn(),
- filterQuery: '',
setQuery: jest.fn(),
skip: true,
+ to: '2019-06-18T06:00:00.000Z',
};
const { storage } = createSecuritySolutionStorageMock();
diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx
index 95534e1a61988..1a04d1cc2c0eb 100644
--- a/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/index.tsx
@@ -15,7 +15,7 @@ import { NetworkKpiUniquePrivateIps } from './unique_private_ips';
import { NetworkKpiProps } from './types';
export const NetworkKpiComponent = React.memo(
- ({ filterQuery, from, to, setQuery, skip, narrowDateRange }) => (
+ ({ filterQuery, from, indexNames, to, setQuery, skip, narrowDateRange }) => (
@@ -23,6 +23,7 @@ export const NetworkKpiComponent = React.memo(
(
(
(
(
= [
const NetworkKpiNetworkEventsComponent: React.FC = ({
filterQuery,
from,
+ indexNames,
to,
narrowDateRange,
setQuery,
@@ -41,6 +42,7 @@ const NetworkKpiNetworkEventsComponent: React.FC = ({
const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiNetworkEvents({
filterQuery,
endDate: to,
+ indexNames,
startDate: from,
skip,
});
diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/tls_handshakes/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/tls_handshakes/index.tsx
index 575d4256e8395..500314446bc12 100644
--- a/x-pack/plugins/security_solution/public/network/components/kpi_network/tls_handshakes/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/tls_handshakes/index.tsx
@@ -28,6 +28,7 @@ export const fieldsMapping: Readonly = [
const NetworkKpiTlsHandshakesComponent: React.FC = ({
filterQuery,
from,
+ indexNames,
to,
narrowDateRange,
setQuery,
@@ -36,6 +37,7 @@ const NetworkKpiTlsHandshakesComponent: React.FC = ({
const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiTlsHandshakes({
filterQuery,
endDate: to,
+ indexNames,
startDate: from,
skip,
});
diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/types.ts b/x-pack/plugins/security_solution/public/network/components/kpi_network/types.ts
index d3a0ac5a6c5dd..860e6e228bbaa 100644
--- a/x-pack/plugins/security_solution/public/network/components/kpi_network/types.ts
+++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/types.ts
@@ -10,6 +10,7 @@ import { GlobalTimeArgs } from '../../../common/containers/use_global_time';
export interface NetworkKpiProps {
filterQuery: string;
from: string;
+ indexNames: string[];
to: string;
narrowDateRange: UpdateDateRange;
setQuery: GlobalTimeArgs['setQuery'];
diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_flows/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_flows/index.tsx
index d22d4454952f9..65624ba481378 100644
--- a/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_flows/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_flows/index.tsx
@@ -28,6 +28,7 @@ export const fieldsMapping: Readonly = [
const NetworkKpiUniqueFlowsComponent: React.FC = ({
filterQuery,
from,
+ indexNames,
to,
narrowDateRange,
setQuery,
@@ -36,6 +37,7 @@ const NetworkKpiUniqueFlowsComponent: React.FC = ({
const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiUniqueFlows({
filterQuery,
endDate: to,
+ indexNames,
startDate: from,
skip,
});
diff --git a/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_private_ips/index.tsx b/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_private_ips/index.tsx
index a7dfb38219c07..a8a179b97f51a 100644
--- a/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_private_ips/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/components/kpi_network/unique_private_ips/index.tsx
@@ -47,6 +47,7 @@ export const fieldsMapping: Readonly = [
const NetworkKpiUniquePrivateIpsComponent: React.FC = ({
filterQuery,
from,
+ indexNames,
to,
narrowDateRange,
setQuery,
@@ -55,6 +56,7 @@ const NetworkKpiUniquePrivateIpsComponent: React.FC = ({
const [loading, { refetch, id, inspect, ...data }] = useNetworkKpiUniquePrivateIps({
filterQuery,
endDate: to,
+ indexNames,
startDate: from,
skip,
});
diff --git a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx
index f6ea86bd552f4..217241bdadcbb 100644
--- a/x-pack/plugins/security_solution/public/network/containers/details/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/containers/details/index.tsx
@@ -9,7 +9,6 @@ import { useState, useEffect, useCallback, useRef } from 'react';
import deepEqual from 'fast-deep-equal';
import { ESTermQuery } from '../../../../common/typed_json';
-import { DEFAULT_INDEX_KEY } from '../../../../common/constants';
import { inputsModel } from '../../../common/store';
import { useKibana } from '../../../common/lib/kibana';
import { createFilter } from '../../../common/containers/helpers';
@@ -42,6 +41,7 @@ interface UseNetworkDetails {
id?: string;
docValueFields: DocValueFields[];
ip: string;
+ indexNames: string[];
filterQuery?: ESTermQuery | string;
skip: boolean;
}
@@ -49,18 +49,18 @@ interface UseNetworkDetails {
export const useNetworkDetails = ({
docValueFields,
filterQuery,
+ indexNames,
id = ID,
skip,
ip,
}: UseNetworkDetails): [boolean, NetworkDetailsArgs] => {
- const { data, notifications, uiSettings } = useKibana().services;
+ const { data, notifications } = useKibana().services;
const refetch = useRef(noop);
const abortCtrl = useRef(new AbortController());
- const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
const [loading, setLoading] = useState(false);
const [networkDetailsRequest, setNetworkDetailsRequest] = useState({
- defaultIndex,
+ defaultIndex: indexNames,
docValueFields: docValueFields ?? [],
factoryQueryType: NetworkQueries.details,
filterQuery: createFilter(filterQuery),
@@ -137,7 +137,7 @@ export const useNetworkDetails = ({
setNetworkDetailsRequest((prevRequest) => {
const myRequest = {
...prevRequest,
- defaultIndex,
+ defaultIndex: indexNames,
ip,
docValueFields: docValueFields ?? [],
filterQuery: createFilter(filterQuery),
@@ -147,7 +147,7 @@ export const useNetworkDetails = ({
}
return prevRequest;
});
- }, [defaultIndex, filterQuery, skip, ip, docValueFields]);
+ }, [indexNames, filterQuery, skip, ip, docValueFields]);
useEffect(() => {
networkDetailsSearch(networkDetailsRequest);
diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx
index 2afbff3138c6b..dc60bb0a82ba8 100644
--- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/dns/index.tsx
@@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal';
import { noop } from 'lodash/fp';
import { useCallback, useEffect, useRef, useState } from 'react';
-import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
import { inputsModel } from '../../../../common/store';
import { createFilter } from '../../../../common/containers/helpers';
import { useKibana } from '../../../../common/lib/kibana';
@@ -41,6 +40,7 @@ export interface NetworkKpiDnsArgs {
interface UseNetworkKpiDns {
filterQuery?: ESTermQuery | string;
endDate: string;
+ indexNames: string[];
skip?: boolean;
startDate: string;
}
@@ -48,16 +48,16 @@ interface UseNetworkKpiDns {
export const useNetworkKpiDns = ({
filterQuery,
endDate,
+ indexNames,
skip = false,
startDate,
}: UseNetworkKpiDns): [boolean, NetworkKpiDnsArgs] => {
- const { data, notifications, uiSettings } = useKibana().services;
+ const { data, notifications } = useKibana().services;
const refetch = useRef(noop);
const abortCtrl = useRef(new AbortController());
- const defaultIndex = uiSettings.get(DEFAULT_INDEX_KEY);
const [loading, setLoading] = useState(false);
const [networkKpiDnsRequest, setNetworkKpiDnsRequest] = useState({
- defaultIndex,
+ defaultIndex: indexNames,
factoryQueryType: NetworkKpiQueries.dns,
filterQuery: createFilter(filterQuery),
id: ID,
@@ -138,7 +138,7 @@ export const useNetworkKpiDns = ({
setNetworkKpiDnsRequest((prevRequest) => {
const myRequest = {
...prevRequest,
- defaultIndex,
+ defaultIndex: indexNames,
filterQuery: createFilter(filterQuery),
timerange: {
interval: '12h',
@@ -151,7 +151,7 @@ export const useNetworkKpiDns = ({
}
return prevRequest;
});
- }, [defaultIndex, endDate, filterQuery, skip, startDate]);
+ }, [indexNames, endDate, filterQuery, skip, startDate]);
useEffect(() => {
networkKpiDnsSearch(networkKpiDnsRequest);
diff --git a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx
index 26b57ef36b09d..a1727d5bb4331 100644
--- a/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx
+++ b/x-pack/plugins/security_solution/public/network/containers/kpi_network/network_events/index.tsx
@@ -8,7 +8,6 @@ import deepEqual from 'fast-deep-equal';
import { noop } from 'lodash/fp';
import { useCallback, useEffect, useRef, useState } from 'react';
-import { DEFAULT_INDEX_KEY } from '../../../../../common/constants';
import { inputsModel } from '../../../../common/store';
import { createFilter } from '../../../../common/containers/helpers';
import { useKibana } from '../../../../common/lib/kibana';
@@ -41,6 +40,7 @@ export interface NetworkKpiNetworkEventsArgs {
interface UseNetworkKpiNetworkEvents {
filterQuery?: ESTermQuery | string;
endDate: string;
+ indexNames: string[];
skip?: boolean;
startDate: string;
}
@@ -48,18 +48,18 @@ interface UseNetworkKpiNetworkEvents {
export const useNetworkKpiNetworkEvents = ({
filterQuery,
endDate,
+ indexNames,
skip = false,
startDate,
}: UseNetworkKpiNetworkEvents): [boolean, NetworkKpiNetworkEventsArgs] => {
- const { data, notifications, uiSettings } = useKibana().services;
+ const { data, notifications } = useKibana().services;
const refetch = useRef