From 10dbee17166c48823dcae6e21ff553577fa349f8 Mon Sep 17 00:00:00 2001 From: Andrew Goldstein Date: Tue, 17 May 2022 17:29:26 -0600 Subject: [PATCH] [8.2] [Security Solution] Fixes sorting issues related to unmapped fields (#132190) (#132357) ## [Security Solution] Fixes sorting issues related to unmapped fields This PR fixes the following issues related to sorting unmapped fields in timelines and the events / alerts tables: - - - The `unmapped_type` property [addition](https://github.com/elastic/kibana/pull/87241/files#diff-52fd5870dcd5f783f9fc8ac3a18a8674d83ac6136e09fe0e0bcae30427d61c3fR55) to the `sort` parameter of requests was using the `type` field metadata from `BrowserFields`, but the `type` metadata (for some fields) contains the value `string`, which is not a [valid field data type](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html). The fix for the issues above: - Populates the `sort` property of requests with values from the `esTypes` `BrowserFields` metadata (instead of `type`) - The `esTypes` metadata may specify more than one field value type. When `esTypes` contains more than one type, and `keyword` is one of the types, the `sort` property of the request will prefer `keyword` over other the other types - When the field metadata has an empty `esTypes` collection, the `sort` property of the request will default to using `"unmapped_type": "keyword"` - The field type displayed in tooltips when hovering over columns in a timeline now displays values from `esTypes` instead of `type` ### Desk testing To reproduce issue and to verify the fix: 1) Open Kibana `Dev tools` 2) Execute the following query to delete any exiting `logs-ti_test` index: ``` DELETE logs-ti_test ``` 3) Execute the following query to create an index named `logs-ti_test`, which has the following properities: - Dynamic mappings are disabled via `"dynamic": false` - It does NOT contain a mapping for `event.action` (we will sort by this field in later steps) - It contains a mapping for the non-ECS `testing` field ``` PUT logs-ti_test { "mappings": { "dynamic": false, "properties": { "@timestamp": { "type": "date" }, "event": { "properties": { "category": { "type": "keyword" }, "dataset": { "type": "keyword" }, "kind": { "type": "keyword" }, "type": { "type": "keyword" } } }, "host": { "properties": { "name": { "type": "keyword" } } }, "testing": { "type": "keyword", "ignore_above": 1024 }, "threat": { "properties": { "indicator": { "properties": { "file": { "properties": { "hash": { "properties": { "md5": { "type": "keyword" } } } } } } } } } } } } ``` 4) Execute the following query to add a new document to the `logs-ti_test` index, and note that: - It does NOT contain a `event.action` field - It contains a value for the non-ECS `testing` field ``` POST logs-ti_test/_doc/ { "@timestamp": "2022-05-12T00:00:14.725Z", "host": { "name": "foozle" }, "threat": { "indicator": { "file": { "hash": { "md5": "a4f87cbcd2a4241da77b6bf0c5d9e8553fec991f" } } } }, "event": { "kind": "enrichment", "type": "indicator", "dataset": "ti_*", "category": "threat" }, "testing": "simulated threat intel data" } ``` 5) Navigate to the Security > Hosts page 6) Select `Last 1 year` from the date picker 7) Click the `Events` tab 8) Enter the following KQL query in the search bar at the top of the page: ``` host.name: foozle ``` 9) Hover over the `foozle` entry in the `host.name` column in the Events table, and click the `Add to timeline investigation` cell action 10) Open the timeline 11) Hover over the `event.action` field **Expected result** - The tooltip displays type `keyword` for the `event.action` field **Actual result** - The tooltip displays type `string` for the `event.action` field 12) Click the `event.action` column to add a secondary sort **Expected result** - The table is sorted by `@timestamp` and `event.action` - The table contents are (still) visible **Actual result** - The table is sorted by `@timestamp` and `event.action` - The contents of the table are now empty 13) Click the timeline's `Inspect` button 14) In the `Inspect Timeline` dialog, click the `Request` tab 15) Scroll down to the `sort` property of the request **Expected result** - The `event.action` field contains a `"unmapped_type": "keyword"` property, per the example below: ```json "sort": [ { "@timestamp": { "order": "desc", "unmapped_type": "date" } }, { "event.action": { "order": "desc", "unmapped_type": "keyword" } } ], ``` **Actual result** - The request's `event.action` field contains a `"unmapped_type": "string"` property, per the example below: ```json "sort": [ { "@timestamp": { "order": "desc", "unmapped_type": "number" } }, { "event.action": { "order": "desc", "unmapped_type": "string" } } ], ``` 16) In the `Inspect Timeline` dialog, click the `Response` tab **Expected result** - The response contains `0` `failed` shards / no failures **Actual result** - The response contains failures for the `logs-ti_test` index, with the following reason: ``` "reason": "No mapper found for type [string]" ``` per the example below: ```json { "took": 1, "timed_out": false, "_shards": { "total": 4, "successful": 3, "skipped": 0, "failed": 1, "failures": [ { "shard": 0, "index": "logs-ti_test", "node": "NCRcGeDqSlKQiuPWVFvMEg", "reason": { "type": "illegal_argument_exception", "reason": "No mapper found for type [string]" } } ] }, ``` --- .../common/search_strategy/timeline/index.ts | 1 + .../common/types/timeline/store.ts | 1 + .../drag_drop_context_wrapper.test.tsx.snap | 98 +++++++++++++++- .../events_viewer/default_headers.tsx | 2 + .../source/__snapshots__/index.test.tsx.snap | 98 +++++++++++++++- .../public/common/containers/source/mock.ts | 72 +++++++++++- .../public/common/mock/global_state.ts | 9 +- .../public/common/mock/header.ts | 27 +++-- .../public/common/mock/timeline_results.ts | 20 +++- .../components/alerts_table/actions.test.tsx | 3 +- .../alerts_table/default_config.tsx | 3 +- .../components/open_timeline/helpers.test.ts | 3 +- .../timeline/body/actions/header_actions.tsx | 17 ++- .../__snapshots__/index.test.tsx.snap | 108 +++++++++++++++++- .../body/column_headers/column_header.tsx | 8 +- .../body/column_headers/default_headers.ts | 3 +- .../header/__snapshots__/index.test.tsx.snap | 25 +++- .../body/column_headers/header/index.test.tsx | 10 +- .../body/column_headers/header/index.tsx | 5 +- .../__snapshots__/index.test.tsx.snap | 7 +- .../header_tooltip_content/index.test.tsx | 19 ++- .../header_tooltip_content/index.tsx | 9 +- .../body/column_headers/helpers.test.ts | 3 + .../body/column_headers/index.test.tsx | 50 ++++++-- .../components/timeline/body/index.test.tsx | 3 +- .../plain_column_renderer.test.tsx.snap | 2 +- .../__snapshots__/index.test.tsx.snap | 47 ++++++-- .../__snapshots__/index.test.tsx.snap | 52 +++++++-- .../pinned_tab_content/index.test.tsx | 3 +- .../timeline/pinned_tab_content/index.tsx | 3 +- .../__snapshots__/index.test.tsx.snap | 52 +++++++-- .../timeline/query_tab_content/index.test.tsx | 3 +- .../timeline/query_tab_content/index.tsx | 3 +- .../public/timelines/containers/index.tsx | 5 +- .../timelines/store/timeline/defaults.ts | 3 +- .../timelines/store/timeline/epic.test.ts | 12 +- .../public/timelines/store/timeline/epic.ts | 2 +- .../timeline/epic_local_storage.test.tsx | 4 +- .../timelines/store/timeline/reducer.test.ts | 11 +- .../search_strategy/index_fields/index.ts | 1 + .../common/search_strategy/timeline/index.ts | 1 + .../common/types/timeline/columns/index.tsx | 1 + .../timelines/common/types/timeline/store.ts | 1 + .../__snapshots__/index.test.tsx.snap | 5 +- .../body/column_headers/column_header.tsx | 8 +- .../body/column_headers/default_headers.ts | 3 +- .../header/__snapshots__/index.test.tsx.snap | 14 ++- .../body/column_headers/helpers.test.tsx | 1 + .../t_grid/body/column_headers/index.test.tsx | 2 +- .../components/t_grid/body/helpers.test.tsx | 34 +++++- .../public/components/t_grid/body/helpers.tsx | 17 ++- .../components/t_grid/body/index.test.tsx | 3 +- .../components/t_grid/integrated/index.tsx | 3 +- .../components/t_grid/standalone/index.tsx | 3 +- .../timelines/public/container/index.tsx | 5 +- .../timelines/public/mock/global_state.ts | 9 +- .../plugins/timelines/public/mock/header.ts | 27 +++-- .../public/mock/mock_timeline_data.ts | 3 +- .../plugins/timelines/public/mock/t_grid.tsx | 1 + .../timelines/public/store/t_grid/defaults.ts | 4 +- .../public/store/t_grid/helpers.test.tsx | 2 +- .../timeline/eql/helpers.test.ts | 20 ++-- .../factory/events/all/helpers.test.ts | 42 +++++++ .../timeline/factory/events/all/helpers.ts | 21 ++++ .../events/all/query.events_all.dsl.ts | 3 +- .../apis/security_solution/events.ts | 2 +- 66 files changed, 894 insertions(+), 148 deletions(-) create mode 100644 x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.test.ts create mode 100644 x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.ts diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts index a66a10110f5b2..258bb7ba5245f 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts @@ -47,6 +47,7 @@ export interface TimelineRequestBasicOptions extends IEsSearchRequest { export interface TimelineRequestSortField extends SortField { type: string; + esTypes: string[]; } export interface TimelineRequestOptionsPaginated diff --git a/x-pack/plugins/security_solution/common/types/timeline/store.ts b/x-pack/plugins/security_solution/common/types/timeline/store.ts index 250ff061e81dd..f9399e4eeb6f4 100644 --- a/x-pack/plugins/security_solution/common/types/timeline/store.ts +++ b/x-pack/plugins/security_solution/common/types/timeline/store.ts @@ -33,6 +33,7 @@ export type SortDirection = 'none' | 'asc' | 'desc' | Direction; export interface SortColumnTimeline { columnId: string; columnType: string; + esTypes?: string[]; sortDirection: SortDirection; } diff --git a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap index 46add75debdd6..a0ef37259aaa2 100644 --- a/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/drag_and_drop/__snapshots__/drag_drop_context_wrapper.test.tsx.snap @@ -10,6 +10,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "agent", "description": "Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but \`agent.id\` does not.", + "esTypes": Array [ + "keyword", + ], "example": "8a4f500f", "format": "", "indexes": Array [ @@ -25,6 +28,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "agent", "description": null, + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -40,6 +46,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "agent", "description": "Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.", + "esTypes": Array [ + "keyword", + ], "example": "8a4f500d", "format": "", "indexes": Array [ @@ -55,6 +64,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "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.", + "esTypes": Array [ + "keyword", + ], "example": "foo", "format": "", "indexes": Array [ @@ -74,6 +86,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "auditd", "description": null, + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -87,6 +102,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "auditd", "description": null, + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -100,6 +118,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "auditd", "description": null, + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -117,6 +138,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "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.", + "esTypes": Array [ + "date", + ], "example": "2016-05-23T08:05:34.853Z", "format": "", "indexes": Array [ @@ -133,6 +157,7 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": false, "category": "base", "description": "Each document has an _id that uniquely identifies it", + "esTypes": Array [], "example": "Y-6TfmcB0WOhS6qyMv3s", "indexes": Array [ "auditbeat", @@ -147,6 +172,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": false, "category": "base", "description": "For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message.", + "esTypes": Array [ + "text", + ], "example": "Hello World", "format": "string", "indexes": Array [ @@ -166,6 +194,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "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.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -181,6 +212,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "client", "description": "Bytes sent from the client to the server.", + "esTypes": Array [ + "long", + ], "example": "184", "format": "", "indexes": Array [ @@ -196,6 +230,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "client", "description": "Client domain.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -211,6 +248,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "client", "description": "Country ISO code.", + "esTypes": Array [ + "keyword", + ], "example": "CA", "format": "", "indexes": Array [ @@ -230,6 +270,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "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.", + "esTypes": Array [ + "keyword", + ], "example": "666777888999", "format": "", "indexes": Array [ @@ -245,6 +288,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "cloud", "description": "Availability zone in which this host is running.", + "esTypes": Array [ + "keyword", + ], "example": "us-east-1c", "format": "", "indexes": Array [ @@ -264,6 +310,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "container", "description": "Unique container id.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -279,6 +328,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "container", "description": "Name of the image the container was built on.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -294,6 +346,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "container", "description": "Container image tag.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -313,6 +368,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "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.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -328,6 +386,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "destination", "description": "Bytes sent from the destination to the source.", + "esTypes": Array [ + "long", + ], "example": "184", "format": "", "indexes": Array [ @@ -343,6 +404,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "destination", "description": "Destination domain.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -358,6 +422,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "destination", "description": "IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.", + "esTypes": Array [ + "ip", + ], "example": "", "format": "", "indexes": Array [ @@ -373,6 +440,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "destination", "description": "Port of the destination.", + "esTypes": Array [ + "long", + ], "example": "", "format": "", "indexes": Array [ @@ -382,7 +452,7 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = ], "name": "destination.port", "searchable": true, - "type": "long", + "type": "number", }, }, }, @@ -392,6 +462,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "event", "description": "The action captured by the event. This describes the information in the event. It is more specific than \`event.category\`. Examples are \`group-add\`, \`process-started\`, \`file-created\`. The value is normally defined by the implementer.", + "esTypes": Array [ + "keyword", + ], "example": "user-password-change", "format": "string", "indexes": Array [ @@ -413,6 +486,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "event", "description": "This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy. \`event.category\` represents the \\"big buckets\\" of ECS categories. For example, filtering on \`event.category:process\` yields all events relating to process activity. This field is closely related to \`event.type\`, which is used as a subcategory. This field is an array. This will allow proper categorization of some events that fall in multiple categories.", + "esTypes": Array [ + "keyword", + ], "example": "authentication", "format": "string", "indexes": Array [ @@ -434,6 +510,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "event", "description": "event.end contains the date when the event ended or when the activity was last observed.", + "esTypes": Array [ + "date", + ], "example": null, "format": "", "indexes": Array [ @@ -455,6 +534,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "event", "description": "The numeric severity of the event according to your event source. What the different severity values mean can be different between sources and use cases. It's up to the implementer to make sure severities are consistent across events from the same source. The Syslog severity belongs in \`log.syslog.severity.code\`. \`event.severity\` is meant to represent the severity according to the event source (e.g. firewall, IDS). If the event source does not publish its own severity, you may optionally copy the \`log.syslog.severity.code\` to \`event.severity\`.", + "esTypes": Array [ + "long", + ], "example": 7, "format": "number", "indexes": Array [ @@ -480,6 +562,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "host", "description": "Name of the host. It can contain what \`hostname\` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.", + "esTypes": Array [ + "keyword", + ], "format": "string", "indexes": Array [ "apm-*-transaction*", @@ -548,6 +633,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "source", "description": "IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.", + "esTypes": Array [ + "ip", + ], "example": "", "format": "", "indexes": Array [ @@ -563,6 +651,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "source", "description": "Port of the source.", + "esTypes": Array [ + "long", + ], "example": "", "format": "", "indexes": Array [ @@ -572,7 +663,7 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = ], "name": "source.port", "searchable": true, - "type": "long", + "type": "number", }, }, }, @@ -582,6 +673,9 @@ exports[`DragDropContextWrapper rendering it renders against the snapshot 1`] = "aggregatable": true, "category": "user", "description": "Short name or login of the user.", + "esTypes": Array [ + "keyword", + ], "example": "albert", "format": "string", "indexes": Array [ diff --git a/x-pack/plugins/security_solution/public/common/components/events_viewer/default_headers.tsx b/x-pack/plugins/security_solution/public/common/components/events_viewer/default_headers.tsx index 1578c77f283fb..b7de934cfaa0a 100644 --- a/x-pack/plugins/security_solution/public/common/components/events_viewer/default_headers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/events_viewer/default_headers.tsx @@ -14,6 +14,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ columnHeaderType: defaultColumnHeaderType, id: '@timestamp', initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, + esTypes: ['date'], + type: 'date', }, { columnHeaderType: defaultColumnHeaderType, diff --git a/x-pack/plugins/security_solution/public/common/containers/source/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/common/containers/source/__snapshots__/index.test.tsx.snap index 43329875275ca..37e6a9b6ba0a6 100644 --- a/x-pack/plugins/security_solution/public/common/containers/source/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/containers/source/__snapshots__/index.test.tsx.snap @@ -6,6 +6,9 @@ Array [ "aggregatable": true, "category": "agent", "description": "Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but \`agent.id\` does not.", + "esTypes": Array [ + "keyword", + ], "example": "8a4f500f", "format": "", "indexes": Array [ @@ -21,6 +24,9 @@ Array [ "aggregatable": true, "category": "agent", "description": null, + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -36,6 +42,9 @@ Array [ "aggregatable": true, "category": "agent", "description": "Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.", + "esTypes": Array [ + "keyword", + ], "example": "8a4f500d", "format": "", "indexes": Array [ @@ -51,6 +60,9 @@ Array [ "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.", + "esTypes": Array [ + "keyword", + ], "example": "foo", "format": "", "indexes": Array [ @@ -66,6 +78,9 @@ Array [ "aggregatable": true, "category": "auditd", "description": null, + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -79,6 +94,9 @@ Array [ "aggregatable": true, "category": "auditd", "description": null, + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -92,6 +110,9 @@ Array [ "aggregatable": true, "category": "auditd", "description": null, + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -105,6 +126,9 @@ Array [ "aggregatable": true, "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.", + "esTypes": Array [ + "date", + ], "example": "2016-05-23T08:05:34.853Z", "format": "", "indexes": Array [ @@ -121,6 +145,7 @@ Array [ "aggregatable": false, "category": "base", "description": "Each document has an _id that uniquely identifies it", + "esTypes": Array [], "example": "Y-6TfmcB0WOhS6qyMv3s", "indexes": Array [ "auditbeat", @@ -135,6 +160,9 @@ Array [ "aggregatable": false, "category": "base", "description": "For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message.", + "esTypes": Array [ + "text", + ], "example": "Hello World", "format": "string", "indexes": Array [ @@ -150,6 +178,9 @@ Array [ "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.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -165,6 +196,9 @@ Array [ "aggregatable": true, "category": "client", "description": "Bytes sent from the client to the server.", + "esTypes": Array [ + "long", + ], "example": "184", "format": "", "indexes": Array [ @@ -180,6 +214,9 @@ Array [ "aggregatable": true, "category": "client", "description": "Client domain.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -195,6 +232,9 @@ Array [ "aggregatable": true, "category": "client", "description": "Country ISO code.", + "esTypes": Array [ + "keyword", + ], "example": "CA", "format": "", "indexes": Array [ @@ -210,6 +250,9 @@ Array [ "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.", + "esTypes": Array [ + "keyword", + ], "example": "666777888999", "format": "", "indexes": Array [ @@ -225,6 +268,9 @@ Array [ "aggregatable": true, "category": "cloud", "description": "Availability zone in which this host is running.", + "esTypes": Array [ + "keyword", + ], "example": "us-east-1c", "format": "", "indexes": Array [ @@ -240,6 +286,9 @@ Array [ "aggregatable": true, "category": "container", "description": "Unique container id.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -255,6 +304,9 @@ Array [ "aggregatable": true, "category": "container", "description": "Name of the image the container was built on.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -270,6 +322,9 @@ Array [ "aggregatable": true, "category": "container", "description": "Container image tag.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -285,6 +340,9 @@ Array [ "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.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -300,6 +358,9 @@ Array [ "aggregatable": true, "category": "destination", "description": "Bytes sent from the destination to the source.", + "esTypes": Array [ + "long", + ], "example": "184", "format": "", "indexes": Array [ @@ -315,6 +376,9 @@ Array [ "aggregatable": true, "category": "destination", "description": "Destination domain.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -330,6 +394,9 @@ Array [ "aggregatable": true, "category": "destination", "description": "IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.", + "esTypes": Array [ + "ip", + ], "example": "", "format": "", "indexes": Array [ @@ -345,6 +412,9 @@ Array [ "aggregatable": true, "category": "destination", "description": "Port of the destination.", + "esTypes": Array [ + "long", + ], "example": "", "format": "", "indexes": Array [ @@ -354,12 +424,15 @@ Array [ ], "name": "destination.port", "searchable": true, - "type": "long", + "type": "number", }, Object { "aggregatable": true, "category": "event", "description": "event.end contains the date when the event ended or when the activity was last observed.", + "esTypes": Array [ + "date", + ], "example": null, "format": "", "indexes": Array [ @@ -381,6 +454,9 @@ Array [ "aggregatable": true, "category": "event", "description": "The action captured by the event. This describes the information in the event. It is more specific than \`event.category\`. Examples are \`group-add\`, \`process-started\`, \`file-created\`. The value is normally defined by the implementer.", + "esTypes": Array [ + "keyword", + ], "example": "user-password-change", "format": "string", "indexes": Array [ @@ -402,6 +478,9 @@ Array [ "aggregatable": true, "category": "event", "description": "This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy. \`event.category\` represents the \\"big buckets\\" of ECS categories. For example, filtering on \`event.category:process\` yields all events relating to process activity. This field is closely related to \`event.type\`, which is used as a subcategory. This field is an array. This will allow proper categorization of some events that fall in multiple categories.", + "esTypes": Array [ + "keyword", + ], "example": "authentication", "format": "string", "indexes": Array [ @@ -423,6 +502,9 @@ Array [ "aggregatable": true, "category": "event", "description": "The numeric severity of the event according to your event source. What the different severity values mean can be different between sources and use cases. It's up to the implementer to make sure severities are consistent across events from the same source. The Syslog severity belongs in \`log.syslog.severity.code\`. \`event.severity\` is meant to represent the severity according to the event source (e.g. firewall, IDS). If the event source does not publish its own severity, you may optionally copy the \`log.syslog.severity.code\` to \`event.severity\`.", + "esTypes": Array [ + "long", + ], "example": 7, "format": "number", "indexes": Array [ @@ -444,6 +526,9 @@ Array [ "aggregatable": true, "category": "host", "description": "Name of the host. It can contain what \`hostname\` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.", + "esTypes": Array [ + "keyword", + ], "format": "string", "indexes": Array [ "apm-*-transaction*", @@ -464,6 +549,9 @@ Array [ "aggregatable": true, "category": "source", "description": "IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.", + "esTypes": Array [ + "ip", + ], "example": "", "format": "", "indexes": Array [ @@ -479,6 +567,9 @@ Array [ "aggregatable": true, "category": "source", "description": "Port of the source.", + "esTypes": Array [ + "long", + ], "example": "", "format": "", "indexes": Array [ @@ -488,12 +579,15 @@ Array [ ], "name": "source.port", "searchable": true, - "type": "long", + "type": "number", }, Object { "aggregatable": true, "category": "user", "description": "Short name or login of the user.", + "esTypes": Array [ + "keyword", + ], "example": "albert", "format": "string", "indexes": Array [ 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 a1b17484001d0..17f216baed3b7 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 @@ -22,6 +22,7 @@ export const mocksSource = { name: '@timestamp', searchable: true, type: 'date', + esTypes: ['date'], aggregatable: true, readFromDocValues: true, }, @@ -31,6 +32,7 @@ export const mocksSource = { example: 'Y-6TfmcB0WOhS6qyMv3s', name: '_id', type: 'string', + esTypes: [], searchable: true, aggregatable: false, indexes: ['auditbeat', 'filebeat', 'packetbeat'], @@ -42,6 +44,7 @@ export const mocksSource = { example: 'Hello World', name: 'message', type: 'string', + esTypes: ['text'], searchable: true, aggregatable: false, format: 'string', @@ -57,6 +60,7 @@ export const mocksSource = { name: 'agent.ephemeral_id', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -68,6 +72,7 @@ export const mocksSource = { name: 'agent.hostname', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -80,6 +85,7 @@ export const mocksSource = { name: 'agent.id', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -92,6 +98,7 @@ export const mocksSource = { name: 'agent.name', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -103,6 +110,7 @@ export const mocksSource = { name: 'auditd.data.a0', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -114,6 +122,7 @@ export const mocksSource = { name: 'auditd.data.a1', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -125,6 +134,7 @@ export const mocksSource = { name: 'auditd.data.a2', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -137,6 +147,7 @@ export const mocksSource = { name: 'client.address', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -148,6 +159,7 @@ export const mocksSource = { name: 'client.bytes', searchable: true, type: 'number', + esTypes: ['long'], aggregatable: true, }, { @@ -159,6 +171,7 @@ export const mocksSource = { name: 'client.domain', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -170,6 +183,7 @@ export const mocksSource = { name: 'client.geo.country_iso_code', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -182,6 +196,7 @@ export const mocksSource = { name: 'cloud.account.id', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -193,6 +208,7 @@ export const mocksSource = { name: 'cloud.availability_zone', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -204,6 +220,7 @@ export const mocksSource = { name: 'container.id', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -215,6 +232,7 @@ export const mocksSource = { name: 'container.image.name', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -226,6 +244,7 @@ export const mocksSource = { name: 'container.image.tag', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -238,6 +257,7 @@ export const mocksSource = { name: 'destination.address', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -249,6 +269,7 @@ export const mocksSource = { name: 'destination.bytes', searchable: true, type: 'number', + esTypes: ['long'], aggregatable: true, }, { @@ -260,6 +281,7 @@ export const mocksSource = { name: 'destination.domain', searchable: true, type: 'string', + esTypes: ['keyword'], aggregatable: true, }, { @@ -272,6 +294,7 @@ export const mocksSource = { name: 'destination.ip', searchable: true, type: 'ip', + esTypes: ['ip'], }, { aggregatable: true, @@ -282,7 +305,8 @@ export const mocksSource = { indexes: ['auditbeat', 'filebeat', 'packetbeat'], name: 'destination.port', searchable: true, - type: 'long', + type: 'number', + esTypes: ['long'], }, { aggregatable: true, @@ -294,6 +318,7 @@ export const mocksSource = { name: 'source.ip', searchable: true, type: 'ip', + esTypes: ['ip'], }, { aggregatable: true, @@ -304,7 +329,8 @@ export const mocksSource = { indexes: ['auditbeat', 'filebeat', 'packetbeat'], name: 'source.port', searchable: true, - type: 'long', + type: 'number', + esTypes: ['long'], }, { aggregatable: true, @@ -317,6 +343,7 @@ export const mocksSource = { name: 'event.end', searchable: true, type: 'date', + esTypes: ['date'], }, { category: 'event', @@ -325,6 +352,7 @@ export const mocksSource = { example: 'user-password-change', name: 'event.action', type: 'string', + esTypes: ['keyword'], searchable: true, aggregatable: true, format: 'string', @@ -337,6 +365,7 @@ export const mocksSource = { example: 'authentication', name: 'event.category', type: 'string', + esTypes: ['keyword'], searchable: true, aggregatable: true, format: 'string', @@ -349,6 +378,7 @@ export const mocksSource = { example: 7, name: 'event.severity', type: 'number', + esTypes: ['long'], format: 'number', searchable: true, aggregatable: true, @@ -360,6 +390,7 @@ export const mocksSource = { 'Name of the host. It can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', name: 'host.name', type: 'string', + esTypes: ['keyword'], searchable: true, aggregatable: true, format: 'string', @@ -371,6 +402,7 @@ export const mocksSource = { example: 'albert', name: 'user.name', type: 'string', + esTypes: ['keyword'], searchable: true, aggregatable: true, format: 'string', @@ -459,6 +491,7 @@ export const mockBrowserFields: BrowserFields = { name: 'agent.ephemeral_id', searchable: true, type: 'string', + esTypes: ['keyword'], }, 'agent.hostname': { aggregatable: true, @@ -470,6 +503,7 @@ export const mockBrowserFields: BrowserFields = { name: 'agent.hostname', searchable: true, type: 'string', + esTypes: ['keyword'], }, 'agent.id': { aggregatable: true, @@ -482,6 +516,7 @@ export const mockBrowserFields: BrowserFields = { name: 'agent.id', searchable: true, type: 'string', + esTypes: ['keyword'], }, 'agent.name': { aggregatable: true, @@ -494,6 +529,7 @@ export const mockBrowserFields: BrowserFields = { name: 'agent.name', searchable: true, type: 'string', + esTypes: ['keyword'], }, }, }, @@ -509,6 +545,7 @@ export const mockBrowserFields: BrowserFields = { name: 'auditd.data.a0', searchable: true, type: 'string', + esTypes: ['keyword'], }, 'auditd.data.a1': { aggregatable: true, @@ -520,6 +557,7 @@ export const mockBrowserFields: BrowserFields = { name: 'auditd.data.a1', searchable: true, type: 'string', + esTypes: ['keyword'], }, 'auditd.data.a2': { aggregatable: true, @@ -531,6 +569,7 @@ export const mockBrowserFields: BrowserFields = { name: 'auditd.data.a2', searchable: true, type: 'string', + esTypes: ['keyword'], }, }, }, @@ -547,6 +586,7 @@ export const mockBrowserFields: BrowserFields = { name: '@timestamp', searchable: true, type: 'date', + esTypes: ['date'], readFromDocValues: true, }, _id: { @@ -555,6 +595,7 @@ export const mockBrowserFields: BrowserFields = { example: 'Y-6TfmcB0WOhS6qyMv3s', name: '_id', type: 'string', + esTypes: [], searchable: true, aggregatable: false, indexes: ['auditbeat', 'filebeat', 'packetbeat'], @@ -566,6 +607,7 @@ export const mockBrowserFields: BrowserFields = { example: 'Hello World', name: 'message', type: 'string', + esTypes: ['text'], searchable: true, aggregatable: false, format: 'string', @@ -586,6 +628,7 @@ export const mockBrowserFields: BrowserFields = { name: 'client.address', searchable: true, type: 'string', + esTypes: ['keyword'], }, 'client.bytes': { aggregatable: true, @@ -597,6 +640,7 @@ export const mockBrowserFields: BrowserFields = { name: 'client.bytes', searchable: true, type: 'number', + esTypes: ['long'], }, 'client.domain': { aggregatable: true, @@ -608,6 +652,7 @@ export const mockBrowserFields: BrowserFields = { name: 'client.domain', searchable: true, type: 'string', + esTypes: ['keyword'], }, 'client.geo.country_iso_code': { aggregatable: true, @@ -619,6 +664,7 @@ export const mockBrowserFields: BrowserFields = { name: 'client.geo.country_iso_code', searchable: true, type: 'string', + esTypes: ['keyword'], }, }, }, @@ -635,6 +681,7 @@ export const mockBrowserFields: BrowserFields = { name: 'cloud.account.id', searchable: true, type: 'string', + esTypes: ['keyword'], }, 'cloud.availability_zone': { aggregatable: true, @@ -646,6 +693,7 @@ export const mockBrowserFields: BrowserFields = { name: 'cloud.availability_zone', searchable: true, type: 'string', + esTypes: ['keyword'], }, }, }, @@ -661,6 +709,7 @@ export const mockBrowserFields: BrowserFields = { name: 'container.id', searchable: true, type: 'string', + esTypes: ['keyword'], }, 'container.image.name': { aggregatable: true, @@ -672,6 +721,7 @@ export const mockBrowserFields: BrowserFields = { name: 'container.image.name', searchable: true, type: 'string', + esTypes: ['keyword'], }, 'container.image.tag': { aggregatable: true, @@ -683,6 +733,7 @@ export const mockBrowserFields: BrowserFields = { name: 'container.image.tag', searchable: true, type: 'string', + esTypes: ['keyword'], }, }, }, @@ -699,6 +750,7 @@ export const mockBrowserFields: BrowserFields = { name: 'destination.address', searchable: true, type: 'string', + esTypes: ['keyword'], }, 'destination.bytes': { aggregatable: true, @@ -710,6 +762,7 @@ export const mockBrowserFields: BrowserFields = { name: 'destination.bytes', searchable: true, type: 'number', + esTypes: ['long'], }, 'destination.domain': { aggregatable: true, @@ -721,6 +774,7 @@ export const mockBrowserFields: BrowserFields = { name: 'destination.domain', searchable: true, type: 'string', + esTypes: ['keyword'], }, 'destination.ip': { aggregatable: true, @@ -733,6 +787,7 @@ export const mockBrowserFields: BrowserFields = { name: 'destination.ip', searchable: true, type: 'ip', + esTypes: ['ip'], }, 'destination.port': { aggregatable: true, @@ -743,7 +798,8 @@ export const mockBrowserFields: BrowserFields = { indexes: ['auditbeat', 'filebeat', 'packetbeat'], name: 'destination.port', searchable: true, - type: 'long', + type: 'number', + esTypes: ['long'], }, }, }, @@ -759,6 +815,7 @@ export const mockBrowserFields: BrowserFields = { name: 'event.end', searchable: true, type: 'date', + esTypes: ['date'], aggregatable: true, }, 'event.action': { @@ -768,6 +825,7 @@ export const mockBrowserFields: BrowserFields = { example: 'user-password-change', name: 'event.action', type: 'string', + esTypes: ['keyword'], searchable: true, aggregatable: true, format: 'string', @@ -780,6 +838,7 @@ export const mockBrowserFields: BrowserFields = { example: 'authentication', name: 'event.category', type: 'string', + esTypes: ['keyword'], searchable: true, aggregatable: true, format: 'string', @@ -792,6 +851,7 @@ export const mockBrowserFields: BrowserFields = { example: 7, name: 'event.severity', type: 'number', + esTypes: ['long'], format: 'number', searchable: true, aggregatable: true, @@ -807,6 +867,7 @@ export const mockBrowserFields: BrowserFields = { 'Name of the host. It can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', name: 'host.name', type: 'string', + esTypes: ['keyword'], searchable: true, aggregatable: true, format: 'string', @@ -826,6 +887,7 @@ export const mockBrowserFields: BrowserFields = { name: 'source.ip', searchable: true, type: 'ip', + esTypes: ['ip'], }, 'source.port': { aggregatable: true, @@ -836,7 +898,8 @@ export const mockBrowserFields: BrowserFields = { indexes: ['auditbeat', 'filebeat', 'packetbeat'], name: 'source.port', searchable: true, - type: 'long', + type: 'number', + esTypes: ['long'], }, }, }, @@ -848,6 +911,7 @@ export const mockBrowserFields: BrowserFields = { example: 'albert', name: 'user.name', type: 'string', + esTypes: ['keyword'], searchable: true, aggregatable: true, format: 'string', 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 3c3cd748fecee..d252553586a8b 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 @@ -327,7 +327,14 @@ export const mockGlobalState: State = { pinnedEventIds: {}, pinnedEventsSaveObject: {}, itemsPerPageOptions: [5, 10, 20], - sort: [{ columnId: '@timestamp', columnType: 'number', sortDirection: Direction.desc }], + sort: [ + { + columnId: '@timestamp', + columnType: 'date', + esTypes: ['date'], + sortDirection: Direction.desc, + }, + ], isSaving: false, version: null, status: TimelineStatus.active, diff --git a/x-pack/plugins/security_solution/public/common/mock/header.ts b/x-pack/plugins/security_solution/public/common/mock/header.ts index 66bfda1a02619..c9e56d1c6b032 100644 --- a/x-pack/plugins/security_solution/public/common/mock/header.ts +++ b/x-pack/plugins/security_solution/public/common/mock/header.ts @@ -21,6 +21,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ example: '2016-05-23T08:05:34.853Z', id: '@timestamp', type: 'date', + esTypes: ['date'], aggregatable: true, initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, }, @@ -31,7 +32,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ "Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events.", example: '7', id: 'event.severity', - type: 'long', + type: 'number', + esTypes: ['long'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -42,7 +44,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ 'Event category.\nThis contains high-level information about the contents of the event. It is more generic than `event.action`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', example: 'user-management', id: 'event.category', - type: 'keyword', + type: 'string', + esTypes: ['keyword'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -53,7 +56,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ 'The action captured by the event.\nThis describes the information in the event. It is more specific than `event.category`. Examples are `group-add`, `process-started`, `file-created`. The value is normally defined by the implementer.', example: 'user-password-change', id: 'event.action', - type: 'keyword', + type: 'string', + esTypes: ['keyword'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -64,7 +68,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ 'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', example: '', id: 'host.name', - type: 'keyword', + type: 'string', + esTypes: ['keyword'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -75,6 +80,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ example: '', id: 'source.ip', type: 'ip', + esTypes: ['ip'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -85,6 +91,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ example: '', id: 'destination.ip', type: 'ip', + esTypes: ['ip'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -97,6 +104,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ format: 'bytes', id: 'destination.bytes', type: 'number', + esTypes: ['long'], initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { @@ -105,7 +113,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ description: 'Short name or login of the user.', example: 'albert', id: 'user.name', - type: 'keyword', + type: 'string', + esTypes: ['keyword'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -115,8 +124,9 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ description: 'Each document has an _id that uniquely identifies it', example: 'Y-6TfmcB0WOhS6qyMv3s', id: '_id', - type: 'keyword', - aggregatable: true, + type: 'string', + esTypes: [], // empty for _id + aggregatable: false, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { @@ -126,7 +136,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ 'For log events the message field contains the log message.\nIn other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.', example: 'Hello World', id: 'message', - type: 'text', + type: 'string', + esTypes: ['text'], aggregatable: false, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, 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 e450446e9e116..ec539d1f1fd0b 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 @@ -2017,7 +2017,8 @@ export const mockTimelineModel: TimelineModel = { sort: [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, ], @@ -2066,7 +2067,13 @@ export const mockTimelineResult = { }; const defaultTimelineColumns: CreateTimelineProps['timeline']['columns'] = [ - { columnHeaderType: 'not-filtered', id: '@timestamp', type: 'number', initialWidth: 190 }, + { + columnHeaderType: 'not-filtered', + id: '@timestamp', + type: 'date', + esTypes: ['date'], + initialWidth: 190, + }, { columnHeaderType: 'not-filtered', id: 'message', initialWidth: 180 }, { columnHeaderType: 'not-filtered', id: 'event.category', initialWidth: 180 }, { columnHeaderType: 'not-filtered', id: 'event.action', initialWidth: 180 }, @@ -2136,7 +2143,14 @@ export const defaultTimelineProps: CreateTimelineProps = { sessionViewConfig: null, show: false, showCheckboxes: false, - sort: [{ columnId: '@timestamp', columnType: 'number', sortDirection: Direction.desc }], + sort: [ + { + columnId: '@timestamp', + columnType: 'date', + esTypes: ['date'], + sortDirection: Direction.desc, + }, + ], status: TimelineStatus.draft, title: '', timelineType: TimelineType.default, 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 dd79a244d2b1b..a0e5798158174 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 @@ -208,7 +208,8 @@ describe('alert actions', () => { { columnHeaderType: 'not-filtered', id: '@timestamp', - type: 'number', + type: 'date', + esTypes: ['date'], initialWidth: 190, }, { diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx index 8ae7e358f280d..34e189884c191 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/default_config.tsx @@ -166,7 +166,8 @@ export const alertsPreviewDefaultModel: SubsetTimelineModel = { sort: [ { columnId: 'kibana.alert.original_time', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: 'desc', }, ], diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts index 225158061ad21..875e85502b9a3 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/helpers.test.ts @@ -77,7 +77,8 @@ const columns = [ { columnHeaderType: 'not-filtered', id: '@timestamp', - type: 'number', + type: 'date', + esTypes: ['date'], initialWidth: 190, }, { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.tsx index 0e26edc6ae1c1..7b95f5d534041 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/actions/header_actions.tsx @@ -122,11 +122,18 @@ const HeaderActionsComponent: React.FC = ({ dispatch( timelineActions.updateSort({ id: timelineId, - sort: cols.map(({ id, direction }) => ({ - columnId: id, - columnType: columnHeaders.find((ch) => ch.id === id)?.type ?? 'text', - sortDirection: direction as SortDirection, - })), + sort: cols.map(({ id, direction }) => { + const columnHeader = columnHeaders.find((ch) => ch.id === id); + const columnType = columnHeader?.type ?? ''; + const esTypes = columnHeader?.esTypes ?? []; + + return { + columnId: id, + columnType, + esTypes, + sortDirection: direction as SortDirection, + }; + }), }) ), [columnHeaders, dispatch, timelineId] diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index affd5c894075a..e16d45bf1f3a9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -11,6 +11,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "agent", "description": "Ephemeral identifier of this agent (if one exists). This id normally changes across restarts, but \`agent.id\` does not.", + "esTypes": Array [ + "keyword", + ], "example": "8a4f500f", "format": "", "indexes": Array [ @@ -26,6 +29,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "agent", "description": null, + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -41,6 +47,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "agent", "description": "Unique identifier of this agent (if one exists). Example: For Beats this would be beat.id.", + "esTypes": Array [ + "keyword", + ], "example": "8a4f500d", "format": "", "indexes": Array [ @@ -56,6 +65,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "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.", + "esTypes": Array [ + "keyword", + ], "example": "foo", "format": "", "indexes": Array [ @@ -75,6 +87,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "auditd", "description": null, + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -88,6 +103,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "auditd", "description": null, + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -101,6 +119,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "auditd", "description": null, + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -118,6 +139,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "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.", + "esTypes": Array [ + "date", + ], "example": "2016-05-23T08:05:34.853Z", "format": "", "indexes": Array [ @@ -134,6 +158,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": false, "category": "base", "description": "Each document has an _id that uniquely identifies it", + "esTypes": Array [], "example": "Y-6TfmcB0WOhS6qyMv3s", "indexes": Array [ "auditbeat", @@ -148,6 +173,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": false, "category": "base", "description": "For log events the message field contains the log message, optimized for viewing in a log viewer. For structured logs without an original message field, other fields can be concatenated to form a human-readable summary of the event. If multiple messages exist, they can be combined into one message.", + "esTypes": Array [ + "text", + ], "example": "Hello World", "format": "string", "indexes": Array [ @@ -167,6 +195,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "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.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -182,6 +213,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "client", "description": "Bytes sent from the client to the server.", + "esTypes": Array [ + "long", + ], "example": "184", "format": "", "indexes": Array [ @@ -197,6 +231,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "client", "description": "Client domain.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -212,6 +249,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "client", "description": "Country ISO code.", + "esTypes": Array [ + "keyword", + ], "example": "CA", "format": "", "indexes": Array [ @@ -231,6 +271,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "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.", + "esTypes": Array [ + "keyword", + ], "example": "666777888999", "format": "", "indexes": Array [ @@ -246,6 +289,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "cloud", "description": "Availability zone in which this host is running.", + "esTypes": Array [ + "keyword", + ], "example": "us-east-1c", "format": "", "indexes": Array [ @@ -265,6 +311,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "container", "description": "Unique container id.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -280,6 +329,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "container", "description": "Name of the image the container was built on.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -295,6 +347,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "container", "description": "Container image tag.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -314,6 +369,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "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.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -329,6 +387,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "destination", "description": "Bytes sent from the destination to the source.", + "esTypes": Array [ + "long", + ], "example": "184", "format": "", "indexes": Array [ @@ -344,6 +405,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "destination", "description": "Destination domain.", + "esTypes": Array [ + "keyword", + ], "example": null, "format": "", "indexes": Array [ @@ -359,6 +423,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "destination", "description": "IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.", + "esTypes": Array [ + "ip", + ], "example": "", "format": "", "indexes": Array [ @@ -374,6 +441,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "destination", "description": "Port of the destination.", + "esTypes": Array [ + "long", + ], "example": "", "format": "", "indexes": Array [ @@ -383,7 +453,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` ], "name": "destination.port", "searchable": true, - "type": "long", + "type": "number", }, }, }, @@ -393,6 +463,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "event", "description": "The action captured by the event. This describes the information in the event. It is more specific than \`event.category\`. Examples are \`group-add\`, \`process-started\`, \`file-created\`. The value is normally defined by the implementer.", + "esTypes": Array [ + "keyword", + ], "example": "user-password-change", "format": "string", "indexes": Array [ @@ -414,6 +487,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "event", "description": "This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy. \`event.category\` represents the \\"big buckets\\" of ECS categories. For example, filtering on \`event.category:process\` yields all events relating to process activity. This field is closely related to \`event.type\`, which is used as a subcategory. This field is an array. This will allow proper categorization of some events that fall in multiple categories.", + "esTypes": Array [ + "keyword", + ], "example": "authentication", "format": "string", "indexes": Array [ @@ -435,6 +511,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "event", "description": "event.end contains the date when the event ended or when the activity was last observed.", + "esTypes": Array [ + "date", + ], "example": null, "format": "", "indexes": Array [ @@ -456,6 +535,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "event", "description": "The numeric severity of the event according to your event source. What the different severity values mean can be different between sources and use cases. It's up to the implementer to make sure severities are consistent across events from the same source. The Syslog severity belongs in \`log.syslog.severity.code\`. \`event.severity\` is meant to represent the severity according to the event source (e.g. firewall, IDS). If the event source does not publish its own severity, you may optionally copy the \`log.syslog.severity.code\` to \`event.severity\`.", + "esTypes": Array [ + "long", + ], "example": 7, "format": "number", "indexes": Array [ @@ -481,6 +563,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "host", "description": "Name of the host. It can contain what \`hostname\` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.", + "esTypes": Array [ + "keyword", + ], "format": "string", "indexes": Array [ "apm-*-transaction*", @@ -549,6 +634,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "source", "description": "IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.", + "esTypes": Array [ + "ip", + ], "example": "", "format": "", "indexes": Array [ @@ -564,6 +652,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "source", "description": "Port of the source.", + "esTypes": Array [ + "long", + ], "example": "", "format": "", "indexes": Array [ @@ -573,7 +664,7 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` ], "name": "source.port", "searchable": true, - "type": "long", + "type": "number", }, }, }, @@ -583,6 +674,9 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` "aggregatable": true, "category": "user", "description": "Short name or login of the user.", + "esTypes": Array [ + "keyword", + ], "example": "albert", "format": "string", "indexes": Array [ @@ -602,9 +696,12 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` Array [ Object { "columnHeaderType": "not-filtered", + "esTypes": Array [ + "date", + ], "id": "@timestamp", "initialWidth": 190, - "type": "number", + "type": "date", }, Object { "columnHeaderType": "not-filtered", @@ -670,7 +767,10 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` Array [ Object { "columnId": "@timestamp", - "columnType": "number", + "columnType": "date", + "esTypes": Array [ + "date", + ], "sortDirection": "desc", }, ] diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx index 74593e40ddf4c..f12b3786183a8 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/column_header.tsx @@ -118,6 +118,8 @@ const ColumnHeaderComponent: React.FC = ({ const onColumnSort = useCallback( (sortDirection: Direction) => { const columnId = header.id; + const columnType = header.type ?? ''; + const esTypes = header.esTypes ?? []; const headerIndex = sort.findIndex((col) => col.columnId === columnId); const newSort = headerIndex === -1 @@ -125,7 +127,8 @@ const ColumnHeaderComponent: React.FC = ({ ...sort, { columnId, - columnType: `${header.type}`, + columnType, + esTypes, sortDirection, }, ] @@ -133,7 +136,8 @@ const ColumnHeaderComponent: React.FC = ({ ...sort.slice(0, headerIndex), { columnId, - columnType: `${header.type}`, + columnType, + esTypes, sortDirection, }, ...sort.slice(headerIndex + 1), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/default_headers.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/default_headers.ts index 58e8d1869233f..7bad6fc73afb9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/default_headers.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/default_headers.ts @@ -15,8 +15,9 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ { columnHeaderType: defaultColumnHeaderType, id: '@timestamp', - type: 'number', initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, + esTypes: ['date'], + type: 'date', }, { columnHeaderType: defaultColumnHeaderType, diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap index 50da19c3d48f3..0a621f0218337 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap @@ -6,9 +6,12 @@ exports[`Header renders correctly against snapshot 1`] = ` header={ Object { "columnHeaderType": "not-filtered", + "esTypes": Array [ + "date", + ], "id": "@timestamp", "initialWidth": 190, - "type": "number", + "type": "date", } } isLoading={false} @@ -19,7 +22,10 @@ exports[`Header renders correctly against snapshot 1`] = ` Array [ Object { "columnId": "@timestamp", - "columnType": "number", + "columnType": "date", + "esTypes": Array [ + "date", + ], "sortDirection": "desc", }, ] @@ -29,9 +35,12 @@ exports[`Header renders correctly against snapshot 1`] = ` header={ Object { "columnHeaderType": "not-filtered", + "esTypes": Array [ + "date", + ], "id": "@timestamp", "initialWidth": 190, - "type": "number", + "type": "date", } } isLoading={false} @@ -40,7 +49,10 @@ exports[`Header renders correctly against snapshot 1`] = ` Array [ Object { "columnId": "@timestamp", - "columnType": "number", + "columnType": "date", + "esTypes": Array [ + "date", + ], "sortDirection": "desc", }, ] @@ -51,9 +63,12 @@ exports[`Header renders correctly against snapshot 1`] = ` header={ Object { "columnHeaderType": "not-filtered", + "esTypes": Array [ + "date", + ], "id": "@timestamp", "initialWidth": 190, - "type": "number", + "type": "date", } } onFilterChange={[Function]} diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx index 4fa72fa5da424..b0c21ac3698df 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.test.tsx @@ -43,7 +43,8 @@ describe('Header', () => { const sort: Sort[] = [ { columnId: columnHeader.id, - columnType: columnHeader.type ?? 'number', + columnType: columnHeader.type ?? '', + esTypes: columnHeader.esTypes ?? [], sortDirection: Direction.desc, }, ]; @@ -183,6 +184,7 @@ describe('Header', () => { { columnId: columnHeader.id, columnType: columnHeader.type ?? 'number', + esTypes: columnHeader.esTypes ?? [], sortDirection: Direction.asc, // (because the previous state was Direction.desc) }, ], @@ -251,6 +253,7 @@ describe('Header', () => { { columnId: 'differentSocks', columnType: columnHeader.type ?? 'number', + esTypes: columnHeader.esTypes ?? [], sortDirection: Direction.desc, }, ]; @@ -264,6 +267,7 @@ describe('Header', () => { const sortDescending: Sort = { columnId: columnHeader.id, columnType: columnHeader.type ?? 'number', + esTypes: columnHeader.esTypes ?? [], sortDirection: Direction.desc, }; @@ -274,6 +278,7 @@ describe('Header', () => { const sortAscending: Sort = { columnId: columnHeader.id, columnType: columnHeader.type ?? 'number', + esTypes: columnHeader.esTypes ?? [], sortDirection: Direction.asc, }; @@ -284,6 +289,7 @@ describe('Header', () => { const sortNone: Sort = { columnId: columnHeader.id, columnType: columnHeader.type ?? 'number', + esTypes: columnHeader.esTypes ?? [], sortDirection: 'none', }; @@ -297,6 +303,7 @@ describe('Header', () => { { columnId: columnHeader.id, columnType: columnHeader.type ?? 'number', + esTypes: columnHeader.esTypes ?? [], sortDirection: Direction.desc, }, ]; @@ -314,6 +321,7 @@ describe('Header', () => { { columnId: 'someOtherColumn', columnType: columnHeader.type ?? 'number', + esTypes: columnHeader.esTypes ?? [], sortDirection: 'none', }, ]; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.tsx index 166a4c2da871c..44e461224a180 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header/index.tsx @@ -43,7 +43,8 @@ export const HeaderComponent: React.FC = ({ const onColumnSort = useCallback(() => { const columnId = header.id; - const columnType = header.type ?? 'text'; + const columnType = header.type ?? ''; + const esTypes = header.esTypes ?? []; const sortDirection = getNewSortDirectionOnClick({ clickedHeader: header, currentSort: sort, @@ -56,6 +57,7 @@ export const HeaderComponent: React.FC = ({ { columnId, columnType, + esTypes, sortDirection, }, ]; @@ -65,6 +67,7 @@ export const HeaderComponent: React.FC = ({ { columnId, columnType, + esTypes, sortDirection, }, ...sort.slice(headerIndex + 1), diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/__snapshots__/index.test.tsx.snap index 945a9a7aee698..750f3956786a5 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/__snapshots__/index.test.tsx.snap @@ -40,11 +40,12 @@ exports[`HeaderToolTipContent it renders the expected table content 1`] = ` data-test-subj="type-icon" type="clock" /> - date - +

diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx index 8f64b4e7e6db3..532937c3e8b99 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx @@ -42,7 +42,24 @@ describe('HeaderToolTipContent', () => { test('it renders the type of the field', () => { const wrapper = mount(); - expect(wrapper.find('[data-test-subj="type-value"]').first().text()).toEqual(header.type); + expect( + wrapper + .find(`[data-test-subj="type-value-${header.esTypes?.at(0)}"]`) + .first() + .text() + ).toEqual(header.esTypes?.at(0)); + }); + + test('it renders multiple `esTypes`', () => { + const hasMultipleTypes = { ...header, esTypes: ['long', 'date'] }; + + const wrapper = mount(); + + hasMultipleTypes.esTypes.forEach((esType) => { + expect(wrapper.find(`[data-test-subj="type-value-${esType}"]`).first().text()).toEqual( + esType + ); + }); }); test('it renders the description of the field', () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.tsx index 4be37de54b365..8cadcad9ef79d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/header_tooltip_content/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiIcon } from '@elastic/eui'; +import { EuiIcon, EuiBadge } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React from 'react'; import styled from 'styled-components'; @@ -29,6 +29,7 @@ P.displayName = 'P'; const ToolTipTableMetadata = styled.span` margin-right: 5px; display: block; + font-weight: bold; `; ToolTipTableMetadata.displayName = 'ToolTipTableMetadata'; @@ -62,7 +63,11 @@ export const HeaderToolTipContent = React.memo<{ header: ColumnHeaderOptions }>( - {header.type} + {header.esTypes?.map((esType) => ( + + {esType} + + ))}

{!isEmpty(header.description) && ( diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts index 74cd56bd4e389..84cc6e60d928c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/helpers.test.ts @@ -40,6 +40,7 @@ describe('helpers', () => { readFromDocValues: true, searchable: true, type: 'date', + esTypes: ['date'], initialWidth: 190, }, { @@ -54,6 +55,7 @@ describe('helpers', () => { name: 'source.ip', searchable: true, type: 'ip', + esTypes: ['ip'], initialWidth: 180, }, { @@ -69,6 +71,7 @@ describe('helpers', () => { name: 'destination.ip', searchable: true, type: 'ip', + esTypes: ['ip'], initialWidth: 180, }, ]; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx index 9df435c277777..e5a62c64299cf 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/column_headers/index.test.tsx @@ -55,7 +55,8 @@ describe('ColumnHeaders', () => { const sort: Sort[] = [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, ]; @@ -112,12 +113,14 @@ describe('ColumnHeaders', () => { let mockSort: Sort[] = [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, { columnId: 'host.name', - columnType: 'text', + columnType: 'string', + esTypes: [], sortDirection: Direction.asc, }, ]; @@ -132,12 +135,14 @@ describe('ColumnHeaders', () => { mockSort = [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, { columnId: 'host.name', - columnType: 'text', + columnType: 'string', + esTypes: [], sortDirection: Direction.asc, }, ]; @@ -156,21 +161,29 @@ describe('ColumnHeaders', () => { .find('[data-test-subj="header-event.category"] [data-test-subj="header-sort-button"]') .first() .simulate('click'); + expect(mockDispatch).toHaveBeenCalledWith( timelineActions.updateSort({ id: timelineId, sort: [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, { columnId: 'host.name', - columnType: 'text', + columnType: 'string', + esTypes: [], sortDirection: Direction.asc, }, - { columnId: 'event.category', columnType: 'text', sortDirection: Direction.desc }, + { + columnId: 'event.category', + columnType: '', + esTypes: [], + sortDirection: Direction.desc, + }, ], }) ); @@ -195,10 +208,16 @@ describe('ColumnHeaders', () => { sort: [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], + sortDirection: Direction.asc, + }, + { + columnId: 'host.name', + columnType: 'string', + esTypes: [], sortDirection: Direction.asc, }, - { columnId: 'host.name', columnType: 'text', sortDirection: Direction.asc }, ], }) ); @@ -221,16 +240,23 @@ describe('ColumnHeaders', () => { .find('[data-test-subj="header-host.name"] [data-test-subj="header-sort-button"]') .first() .simulate('click'); + expect(mockDispatch).toHaveBeenCalledWith( timelineActions.updateSort({ id: timelineId, sort: [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], + sortDirection: Direction.desc, + }, + { + columnId: 'host.name', + columnType: '', + esTypes: [], sortDirection: Direction.desc, }, - { columnId: 'host.name', columnType: 'text', sortDirection: Direction.desc }, ], }) ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx index bcb4c01fa409f..bfb0495e8c9b9 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/index.test.tsx @@ -71,7 +71,8 @@ jest.mock('../../../../common/lib/kibana', () => { const mockSort: Sort[] = [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, ]; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/__snapshots__/plain_column_renderer.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/__snapshots__/plain_column_renderer.test.tsx.snap index 6a09567fbf41a..ef61e08a8a883 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/__snapshots__/plain_column_renderer.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/body/renderers/__snapshots__/plain_column_renderer.test.tsx.snap @@ -7,7 +7,7 @@ exports[`plain_column_renderer rendering renders correctly against snapshot 1`] eventId="1" fieldFormat="" fieldName="event.category" - fieldType="keyword" + fieldType="string" isAggregatable={true} isDraggable={true} key="plain-column-renderer-formatted-field-value-test-event.category-1-event.category-Access-0" diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/__snapshots__/index.test.tsx.snap index 7c0f0a1ac7f8e..d5135b55e6ebd 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/eql_tab_content/__snapshots__/index.test.tsx.snap @@ -12,6 +12,9 @@ exports[`Timeline rendering renders correctly against snapshot 1`] = ` "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.", + "esTypes": Array [ + "date", + ], "example": "2016-05-23T08:05:34.853Z", "id": "@timestamp", "initialWidth": 190, @@ -22,10 +25,13 @@ Required field for all events.", "category": "event", "columnHeaderType": "not-filtered", "description": "Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events.", + "esTypes": Array [ + "long", + ], "example": "7", "id": "event.severity", "initialWidth": 180, - "type": "long", + "type": "number", }, Object { "aggregatable": true, @@ -33,10 +39,13 @@ Required field for all events.", "columnHeaderType": "not-filtered", "description": "Event category. This contains high-level information about the contents of the event. It is more generic than \`event.action\`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.", + "esTypes": Array [ + "keyword", + ], "example": "user-management", "id": "event.category", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { "aggregatable": true, @@ -44,10 +53,13 @@ This contains high-level information about the contents of the event. It is more "columnHeaderType": "not-filtered", "description": "The action captured by the event. This describes the information in the event. It is more specific than \`event.category\`. Examples are \`group-add\`, \`process-started\`, \`file-created\`. The value is normally defined by the implementer.", + "esTypes": Array [ + "keyword", + ], "example": "user-password-change", "id": "event.action", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { "aggregatable": true, @@ -55,10 +67,13 @@ This describes the information in the event. It is more specific than \`event.ca "columnHeaderType": "not-filtered", "description": "Name of the host. It can contain what \`hostname\` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.", + "esTypes": Array [ + "keyword", + ], "example": "", "id": "host.name", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { "aggregatable": true, @@ -66,6 +81,9 @@ It can contain what \`hostname\` returns on Unix systems, the fully qualified do "columnHeaderType": "not-filtered", "description": "IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.", + "esTypes": Array [ + "ip", + ], "example": "", "id": "source.ip", "initialWidth": 180, @@ -77,6 +95,9 @@ Can be one or multiple IPv4 or IPv6 addresses.", "columnHeaderType": "not-filtered", "description": "IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.", + "esTypes": Array [ + "ip", + ], "example": "", "id": "destination.ip", "initialWidth": 180, @@ -87,6 +108,9 @@ Can be one or multiple IPv4 or IPv6 addresses.", "category": "destination", "columnHeaderType": "not-filtered", "description": "Bytes sent from the source to the destination", + "esTypes": Array [ + "long", + ], "example": "123", "format": "bytes", "id": "destination.bytes", @@ -98,20 +122,24 @@ Can be one or multiple IPv4 or IPv6 addresses.", "category": "user", "columnHeaderType": "not-filtered", "description": "Short name or login of the user.", + "esTypes": Array [ + "keyword", + ], "example": "albert", "id": "user.name", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { - "aggregatable": true, + "aggregatable": false, "category": "base", "columnHeaderType": "not-filtered", "description": "Each document has an _id that uniquely identifies it", + "esTypes": Array [], "example": "Y-6TfmcB0WOhS6qyMv3s", "id": "_id", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { "aggregatable": false, @@ -119,10 +147,13 @@ Can be one or multiple IPv4 or IPv6 addresses.", "columnHeaderType": "not-filtered", "description": "For log events the message field contains the log message. In other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.", + "esTypes": Array [ + "text", + ], "example": "Hello World", "id": "message", "initialWidth": 180, - "type": "text", + "type": "string", }, ] } diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/__snapshots__/index.test.tsx.snap index 2ccf562c9ca6f..85d1956920f50 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/__snapshots__/index.test.tsx.snap @@ -11,6 +11,9 @@ exports[`PinnedTabContent rendering renders correctly against snapshot 1`] = ` "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.", + "esTypes": Array [ + "date", + ], "example": "2016-05-23T08:05:34.853Z", "id": "@timestamp", "initialWidth": 190, @@ -21,10 +24,13 @@ Required field for all events.", "category": "event", "columnHeaderType": "not-filtered", "description": "Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events.", + "esTypes": Array [ + "long", + ], "example": "7", "id": "event.severity", "initialWidth": 180, - "type": "long", + "type": "number", }, Object { "aggregatable": true, @@ -32,10 +38,13 @@ Required field for all events.", "columnHeaderType": "not-filtered", "description": "Event category. This contains high-level information about the contents of the event. It is more generic than \`event.action\`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.", + "esTypes": Array [ + "keyword", + ], "example": "user-management", "id": "event.category", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { "aggregatable": true, @@ -43,10 +52,13 @@ This contains high-level information about the contents of the event. It is more "columnHeaderType": "not-filtered", "description": "The action captured by the event. This describes the information in the event. It is more specific than \`event.category\`. Examples are \`group-add\`, \`process-started\`, \`file-created\`. The value is normally defined by the implementer.", + "esTypes": Array [ + "keyword", + ], "example": "user-password-change", "id": "event.action", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { "aggregatable": true, @@ -54,10 +66,13 @@ This describes the information in the event. It is more specific than \`event.ca "columnHeaderType": "not-filtered", "description": "Name of the host. It can contain what \`hostname\` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.", + "esTypes": Array [ + "keyword", + ], "example": "", "id": "host.name", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { "aggregatable": true, @@ -65,6 +80,9 @@ It can contain what \`hostname\` returns on Unix systems, the fully qualified do "columnHeaderType": "not-filtered", "description": "IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.", + "esTypes": Array [ + "ip", + ], "example": "", "id": "source.ip", "initialWidth": 180, @@ -76,6 +94,9 @@ Can be one or multiple IPv4 or IPv6 addresses.", "columnHeaderType": "not-filtered", "description": "IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.", + "esTypes": Array [ + "ip", + ], "example": "", "id": "destination.ip", "initialWidth": 180, @@ -86,6 +107,9 @@ Can be one or multiple IPv4 or IPv6 addresses.", "category": "destination", "columnHeaderType": "not-filtered", "description": "Bytes sent from the source to the destination", + "esTypes": Array [ + "long", + ], "example": "123", "format": "bytes", "id": "destination.bytes", @@ -97,20 +121,24 @@ Can be one or multiple IPv4 or IPv6 addresses.", "category": "user", "columnHeaderType": "not-filtered", "description": "Short name or login of the user.", + "esTypes": Array [ + "keyword", + ], "example": "albert", "id": "user.name", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { - "aggregatable": true, + "aggregatable": false, "category": "base", "columnHeaderType": "not-filtered", "description": "Each document has an _id that uniquely identifies it", + "esTypes": Array [], "example": "Y-6TfmcB0WOhS6qyMv3s", "id": "_id", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { "aggregatable": false, @@ -118,10 +146,13 @@ Can be one or multiple IPv4 or IPv6 addresses.", "columnHeaderType": "not-filtered", "description": "For log events the message field contains the log message. In other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.", + "esTypes": Array [ + "text", + ], "example": "Hello World", "id": "message", "initialWidth": 180, - "type": "text", + "type": "string", }, ] } @@ -1125,7 +1156,10 @@ In other use cases the message field can be used to concatenate different values Array [ Object { "columnId": "@timestamp", - "columnType": "number", + "columnType": "date", + "esTypes": Array [ + "date", + ], "sortDirection": "desc", }, ] diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx index b908a9023a684..1a5adca7e5b2c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.test.tsx @@ -82,7 +82,8 @@ describe('PinnedTabContent', () => { const sort: Sort[] = [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, ]; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx index 8abae059e607d..e25102a473c46 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/pinned_tab_content/index.tsx @@ -174,10 +174,11 @@ export const PinnedTabContentComponent: React.FC = ({ const timelineQuerySortField = useMemo( () => - sort.map(({ columnId, columnType, sortDirection }) => ({ + sort.map(({ columnId, columnType, esTypes, sortDirection }) => ({ field: columnId, type: columnType, direction: sortDirection as Direction, + esTypes: esTypes ?? [], })), [sort] ); diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap index cc6feb69561df..5b0757ee775f7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/__snapshots__/index.test.tsx.snap @@ -12,6 +12,9 @@ exports[`Timeline rendering renders correctly against snapshot 1`] = ` "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.", + "esTypes": Array [ + "date", + ], "example": "2016-05-23T08:05:34.853Z", "id": "@timestamp", "initialWidth": 190, @@ -22,10 +25,13 @@ Required field for all events.", "category": "event", "columnHeaderType": "not-filtered", "description": "Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events.", + "esTypes": Array [ + "long", + ], "example": "7", "id": "event.severity", "initialWidth": 180, - "type": "long", + "type": "number", }, Object { "aggregatable": true, @@ -33,10 +39,13 @@ Required field for all events.", "columnHeaderType": "not-filtered", "description": "Event category. This contains high-level information about the contents of the event. It is more generic than \`event.action\`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.", + "esTypes": Array [ + "keyword", + ], "example": "user-management", "id": "event.category", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { "aggregatable": true, @@ -44,10 +53,13 @@ This contains high-level information about the contents of the event. It is more "columnHeaderType": "not-filtered", "description": "The action captured by the event. This describes the information in the event. It is more specific than \`event.category\`. Examples are \`group-add\`, \`process-started\`, \`file-created\`. The value is normally defined by the implementer.", + "esTypes": Array [ + "keyword", + ], "example": "user-password-change", "id": "event.action", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { "aggregatable": true, @@ -55,10 +67,13 @@ This describes the information in the event. It is more specific than \`event.ca "columnHeaderType": "not-filtered", "description": "Name of the host. It can contain what \`hostname\` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.", + "esTypes": Array [ + "keyword", + ], "example": "", "id": "host.name", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { "aggregatable": true, @@ -66,6 +81,9 @@ It can contain what \`hostname\` returns on Unix systems, the fully qualified do "columnHeaderType": "not-filtered", "description": "IP address of the source. Can be one or multiple IPv4 or IPv6 addresses.", + "esTypes": Array [ + "ip", + ], "example": "", "id": "source.ip", "initialWidth": 180, @@ -77,6 +95,9 @@ Can be one or multiple IPv4 or IPv6 addresses.", "columnHeaderType": "not-filtered", "description": "IP address of the destination. Can be one or multiple IPv4 or IPv6 addresses.", + "esTypes": Array [ + "ip", + ], "example": "", "id": "destination.ip", "initialWidth": 180, @@ -87,6 +108,9 @@ Can be one or multiple IPv4 or IPv6 addresses.", "category": "destination", "columnHeaderType": "not-filtered", "description": "Bytes sent from the source to the destination", + "esTypes": Array [ + "long", + ], "example": "123", "format": "bytes", "id": "destination.bytes", @@ -98,20 +122,24 @@ Can be one or multiple IPv4 or IPv6 addresses.", "category": "user", "columnHeaderType": "not-filtered", "description": "Short name or login of the user.", + "esTypes": Array [ + "keyword", + ], "example": "albert", "id": "user.name", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { - "aggregatable": true, + "aggregatable": false, "category": "base", "columnHeaderType": "not-filtered", "description": "Each document has an _id that uniquely identifies it", + "esTypes": Array [], "example": "Y-6TfmcB0WOhS6qyMv3s", "id": "_id", "initialWidth": 180, - "type": "keyword", + "type": "string", }, Object { "aggregatable": false, @@ -119,10 +147,13 @@ Can be one or multiple IPv4 or IPv6 addresses.", "columnHeaderType": "not-filtered", "description": "For log events the message field contains the log message. In other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.", + "esTypes": Array [ + "text", + ], "example": "Hello World", "id": "message", "initialWidth": 180, - "type": "text", + "type": "string", }, ] } @@ -1267,7 +1298,10 @@ In other use cases the message field can be used to concatenate different values Array [ Object { "columnId": "@timestamp", - "columnType": "number", + "columnType": "date", + "esTypes": Array [ + "date", + ], "sortDirection": "desc", }, ] diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx index 7ae25f5b5b6ab..992a5dfd0b75e 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.test.tsx @@ -95,7 +95,8 @@ describe('Timeline', () => { const sort: Sort[] = [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, ]; diff --git a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx index 1a6fcbf7c25ba..01e83c1492770 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/timeline/query_tab_content/index.tsx @@ -266,9 +266,10 @@ export const QueryTabContentComponent: React.FC = ({ return [...columnFields, ...requiredFieldsForActions]; }; - const timelineQuerySortField = sort.map(({ columnId, columnType, sortDirection }) => ({ + const timelineQuerySortField = sort.map(({ columnId, columnType, esTypes, sortDirection }) => ({ field: columnId, direction: sortDirection as Direction, + esTypes: esTypes ?? [], type: columnType, })); diff --git a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx index 3586aa186b020..2d764f3c081fa 100644 --- a/x-pack/plugins/security_solution/public/timelines/containers/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/containers/index.tsx @@ -100,11 +100,12 @@ const getTimelineEvents = (timelineEdges: TimelineEdges[]): TimelineItem[] => timelineEdges.map((e: TimelineEdges) => e.node); const ID = 'timelineEventsQuery'; -export const initSortDefault = [ +export const initSortDefault: TimelineRequestSortField[] = [ { field: '@timestamp', direction: Direction.asc, - type: 'number', + type: 'date', + esTypes: ['date'], }, ]; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts index c1d37d21c2b1d..1213953c431fb 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/defaults.ts @@ -71,7 +71,8 @@ export const timelineDefaults: SubsetTimelineModel & sort: [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: 'desc', }, ], diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts index f79ecdb33f185..d3f204d58d884 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.test.ts @@ -162,7 +162,14 @@ describe('Epic Timeline', () => { sessionViewConfig: null, show: true, showCheckboxes: false, - sort: [{ columnId: '@timestamp', columnType: 'number', sortDirection: Direction.desc }], + sort: [ + { + columnId: '@timestamp', + columnType: 'date', + esTypes: ['date'], + sortDirection: Direction.desc, + }, + ], status: TimelineStatus.active, version: 'WzM4LDFd', id: '11169110-fc22-11e9-8ca9-072f15ce2685', @@ -304,7 +311,8 @@ describe('Epic Timeline', () => { sort: [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: 'desc', }, ], diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts index 57580c96d06b8..167f39c152837 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic.ts @@ -368,7 +368,7 @@ export const convertTimelineAsInput = ( return set( key, get(key, timeline).map((col: ColumnHeaderOptions) => - omit(['initialWidth', 'width', '__typename'], col) + omit(['initialWidth', 'width', '__typename', 'esTypes'], col) ), acc ); diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx index af27d2a11ad4b..e35a5b6480426 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_local_storage.test.tsx @@ -58,7 +58,8 @@ describe('epicLocalStorage', () => { const sort: Sort[] = [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, ]; @@ -161,6 +162,7 @@ describe('epicLocalStorage', () => { { columnId: 'event.severity', columnType: 'number', + esTypes: ['long'], sortDirection: Direction.desc, }, ], diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts index 203b988be378f..a6eeffca6e03e 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/reducer.test.ts @@ -129,7 +129,8 @@ const basicTimeline: TimelineModel = { sort: [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, ], @@ -958,6 +959,7 @@ describe('Timeline', () => { { columnId: 'some column', columnType: 'text', + esTypes: ['keyword'], sortDirection: Direction.desc, }, ], @@ -970,7 +972,12 @@ describe('Timeline', () => { test('should update the sort attribute', () => { expect(update.foo.sort).toEqual([ - { columnId: 'some column', columnType: 'text', sortDirection: Direction.desc }, + { + columnId: 'some column', + columnType: 'text', + esTypes: ['keyword'], + sortDirection: Direction.desc, + }, ]); }); }); diff --git a/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts b/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts index 544ca033b060c..27d21d09d64eb 100644 --- a/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/index_fields/index.ts @@ -70,6 +70,7 @@ export interface BrowserField { name: string; searchable: boolean; type: string; + esTypes?: string[]; subType?: IFieldSubType; readFromDocValues: boolean; runtimeField?: RuntimeField; diff --git a/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts b/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts index c328ba49493f5..1b48a414fbf6d 100644 --- a/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/timelines/common/search_strategy/timeline/index.ts @@ -48,6 +48,7 @@ export interface TimelineRequestBasicOptions extends IEsSearchRequest { } export interface TimelineRequestSortField extends SortField { + esTypes: string[]; type: string; } diff --git a/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx b/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx index fd758e74df0e9..d137fd5960a98 100644 --- a/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx +++ b/x-pack/plugins/timelines/common/types/timeline/columns/index.tsx @@ -77,6 +77,7 @@ export type ColumnHeaderOptions = Pick< category?: string; columnHeaderType: ColumnHeaderType; description?: string | null; + esTypes?: string[]; example?: string | number | null; format?: string; linkField?: string; diff --git a/x-pack/plugins/timelines/common/types/timeline/store.ts b/x-pack/plugins/timelines/common/types/timeline/store.ts index db2f09e4f74c7..74f58ce3622ad 100644 --- a/x-pack/plugins/timelines/common/types/timeline/store.ts +++ b/x-pack/plugins/timelines/common/types/timeline/store.ts @@ -34,6 +34,7 @@ export type SortDirection = 'none' | 'asc' | 'desc' | Direction; export interface SortColumnTimeline { columnId: string; columnType: string; + esTypes?: string[]; sortDirection: SortDirection; } diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/__snapshots__/index.test.tsx.snap index 233e1c921cd50..82e48d4a4214a 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/__snapshots__/index.test.tsx.snap @@ -596,9 +596,12 @@ exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` Array [ Object { "columnHeaderType": "not-filtered", + "esTypes": Array [ + "date", + ], "id": "@timestamp", "initialWidth": 190, - "type": "number", + "type": "date", }, Object { "columnHeaderType": "not-filtered", diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/column_header.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/column_header.tsx index 4e6db10cc8bce..4ec33e3a6883d 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/column_header.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/column_header.tsx @@ -119,6 +119,8 @@ const ColumnHeaderComponent: React.FC = ({ const onColumnSort = useCallback( (sortDirection: Direction) => { const columnId = header.id; + const columnType = header.type ?? ''; + const esTypes = header.esTypes ?? []; const headerIndex = sort.findIndex((col) => col.columnId === columnId); const newSort = headerIndex === -1 @@ -126,7 +128,8 @@ const ColumnHeaderComponent: React.FC = ({ ...sort, { columnId, - columnType: `${header.type}`, + columnType, + esTypes, sortDirection, }, ] @@ -134,7 +137,8 @@ const ColumnHeaderComponent: React.FC = ({ ...sort.slice(0, headerIndex), { columnId, - columnType: `${header.type}`, + columnType, + esTypes, sortDirection, }, ...sort.slice(headerIndex + 1), diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/default_headers.ts b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/default_headers.ts index a5fb5f4bacd43..9312ac17bf691 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/default_headers.ts +++ b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/default_headers.ts @@ -14,8 +14,9 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ { columnHeaderType: defaultColumnHeaderType, id: '@timestamp', - type: 'number', initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, + esTypes: ['date'], + type: 'date', }, { columnHeaderType: defaultColumnHeaderType, diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/header/__snapshots__/index.test.tsx.snap b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/header/__snapshots__/index.test.tsx.snap index ff2bdf2f643a0..86ecb3de71c28 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/header/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/header/__snapshots__/index.test.tsx.snap @@ -6,9 +6,12 @@ exports[`Header renders correctly against snapshot 1`] = ` header={ Object { "columnHeaderType": "not-filtered", + "esTypes": Array [ + "date", + ], "id": "@timestamp", "initialWidth": 190, - "type": "number", + "type": "date", } } isLoading={false} @@ -19,7 +22,7 @@ exports[`Header renders correctly against snapshot 1`] = ` Array [ Object { "columnId": "@timestamp", - "columnType": "number", + "columnType": "date", "sortDirection": "desc", }, ] @@ -29,9 +32,12 @@ exports[`Header renders correctly against snapshot 1`] = ` header={ Object { "columnHeaderType": "not-filtered", + "esTypes": Array [ + "date", + ], "id": "@timestamp", "initialWidth": 190, - "type": "number", + "type": "date", } } isLoading={false} @@ -40,7 +46,7 @@ exports[`Header renders correctly against snapshot 1`] = ` Array [ Object { "columnId": "@timestamp", - "columnType": "number", + "columnType": "date", "sortDirection": "desc", }, ] diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.test.tsx index 4aba02607ec2e..c91189b509a37 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/helpers.test.tsx @@ -209,6 +209,7 @@ describe('helpers', () => { defaultSortDirection, 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.', + esTypes: ['date'], example: '2016-05-23T08:05:34.853Z', format: '', id: '@timestamp', diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/index.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/index.test.tsx index 084cd31d76e61..3a2084579f419 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/index.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/column_headers/index.test.tsx @@ -245,7 +245,7 @@ describe('ColumnHeaders', () => { sort: [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', sortDirection: Direction.asc, }, { columnId: 'host.name', columnType: 'text', sortDirection: Direction.asc }, diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx index e655037732650..b6b884c0f01ee 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/helpers.test.tsx @@ -260,6 +260,7 @@ describe('helpers', () => { initialWidth: 176, category: 'kibana', type: 'date', + esTypes: ['date'], aggregatable: true, actions: { showSortAsc: { @@ -291,12 +292,22 @@ describe('helpers', () => { test('it returns the expected results when each column has a corresponding entry in `columnHeaders`', () => { expect(mapSortingColumns({ columns, columnHeaders })).toEqual([ - { columnId: 'kibana.rac.alert.status', columnType: 'string', sortDirection: 'asc' }, - { columnId: 'kibana.rac.alert.start', columnType: 'date', sortDirection: 'desc' }, + { + columnId: 'kibana.rac.alert.status', + columnType: 'string', + esTypes: [], + sortDirection: 'asc', + }, + { + columnId: 'kibana.rac.alert.start', + columnType: 'date', + esTypes: ['date'], + sortDirection: 'desc', + }, ]); }); - test('it defaults to a `columnType` of `text` when a column does NOT has a corresponding entry in `columnHeaders`', () => { + test('it defaults to a `columnType` of empty string when a column does NOT has a corresponding entry in `columnHeaders`', () => { const withUnknownColumn: Array<{ id: string; direction: 'asc' | 'desc'; @@ -316,11 +327,22 @@ describe('helpers', () => { ]; expect(mapSortingColumns({ columns: withUnknownColumn, columnHeaders })).toEqual([ - { columnId: 'kibana.rac.alert.status', columnType: 'string', sortDirection: 'asc' }, - { columnId: 'kibana.rac.alert.start', columnType: 'date', sortDirection: 'desc' }, + { + columnId: 'kibana.rac.alert.status', + columnType: 'string', + esTypes: [], + sortDirection: 'asc', + }, + { + columnId: 'kibana.rac.alert.start', + columnType: 'date', + esTypes: ['date'], + sortDirection: 'desc', + }, { columnId: 'unknown', - columnType: 'text', // <-- mapped to the default + columnType: '', // <-- mapped to the default + esTypes: [], // <-- mapped to the default sortDirection: 'asc', }, ]); diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/helpers.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/helpers.tsx index afe7c5954cdad..7dbadadfb0e5e 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/helpers.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/helpers.tsx @@ -123,11 +123,18 @@ export const mapSortingColumns = ({ direction: 'asc' | 'desc'; }>; }): SortColumnTimeline[] => - columns.map(({ id, direction }) => ({ - columnId: id, - columnType: columnHeaders.find((ch) => ch.id === id)?.type ?? 'text', - sortDirection: direction, - })); + columns.map(({ id, direction }) => { + const columnHeader = columnHeaders.find((ch) => ch.id === id); + const columnType = columnHeader?.type ?? ''; + const esTypes = columnHeader?.esTypes ?? []; + + return { + columnId: id, + columnType, + esTypes, + sortDirection: direction, + }; + }); export const allowSorting = ({ browserField, diff --git a/x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx b/x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx index 7ee56478392fe..4ac9aec83a5cb 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/body/index.test.tsx @@ -23,7 +23,8 @@ import { defaultColumnHeaderType } from '../../../store/t_grid/defaults'; const mockSort: Sort[] = [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, ]; diff --git a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx index 2991d80c7af0a..c5f04a1c7e036 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/integrated/index.tsx @@ -226,10 +226,11 @@ const TGridIntegratedComponent: React.FC = ({ const sortField = useMemo( () => - sort.map(({ columnId, columnType, sortDirection }) => ({ + sort.map(({ columnId, columnType, esTypes, sortDirection }) => ({ field: columnId, type: columnType, direction: sortDirection as Direction, + esTypes: esTypes ?? [], })), [sort] ); diff --git a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx index be71d159eafca..ce7f6af484459 100644 --- a/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx +++ b/x-pack/plugins/timelines/public/components/t_grid/standalone/index.tsx @@ -201,10 +201,11 @@ const TGridStandaloneComponent: React.FC = ({ const sortField = useMemo( () => - sortStore.map(({ columnId, columnType, sortDirection }) => ({ + sortStore.map(({ columnId, columnType, esTypes, sortDirection }) => ({ field: columnId, type: columnType, direction: sortDirection as Direction, + esTypes: esTypes ?? [], })), [sortStore] ); diff --git a/x-pack/plugins/timelines/public/container/index.tsx b/x-pack/plugins/timelines/public/container/index.tsx index 06316739fa307..55c316a93003d 100644 --- a/x-pack/plugins/timelines/public/container/index.tsx +++ b/x-pack/plugins/timelines/public/container/index.tsx @@ -110,9 +110,10 @@ const getInspectResponse = ( const ID = 'timelineEventsQuery'; export const initSortDefault = [ { + direction: Direction.desc, + esTypes: ['date'], field: '@timestamp', - direction: Direction.asc, - type: 'number', + type: 'date', }, ]; diff --git a/x-pack/plugins/timelines/public/mock/global_state.ts b/x-pack/plugins/timelines/public/mock/global_state.ts index 955a612f89c1d..e4493964f7f54 100644 --- a/x-pack/plugins/timelines/public/mock/global_state.ts +++ b/x-pack/plugins/timelines/public/mock/global_state.ts @@ -40,7 +40,14 @@ export const mockGlobalState: TimelineState = { itemsPerPageOptions: [5, 10, 20], loadingEventIds: [], showCheckboxes: false, - sort: [{ columnId: '@timestamp', columnType: 'number', sortDirection: Direction.desc }], + sort: [ + { + columnId: '@timestamp', + columnType: 'date', + esTypes: ['date'], + sortDirection: Direction.desc, + }, + ], selectedEventIds: {}, savedObjectId: null, version: null, diff --git a/x-pack/plugins/timelines/public/mock/header.ts b/x-pack/plugins/timelines/public/mock/header.ts index a0a9f0fe15293..b52807d8aa958 100644 --- a/x-pack/plugins/timelines/public/mock/header.ts +++ b/x-pack/plugins/timelines/public/mock/header.ts @@ -21,6 +21,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ example: '2016-05-23T08:05:34.853Z', id: '@timestamp', type: 'date', + esTypes: ['date'], aggregatable: true, initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, }, @@ -31,7 +32,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ "Severity describes the severity of the event. What the different severity values mean can very different between use cases. It's up to the implementer to make sure severities are consistent across events.", example: '7', id: 'event.severity', - type: 'long', + type: 'number', + esTypes: ['long'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -42,7 +44,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ 'Event category.\nThis contains high-level information about the contents of the event. It is more generic than `event.action`, in the sense that typically a category contains multiple actions. Warning: In future versions of ECS, we plan to provide a list of acceptable values for this field, please use with caution.', example: 'user-management', id: 'event.category', - type: 'keyword', + type: 'string', + esTypes: ['keyword'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -53,7 +56,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ 'The action captured by the event.\nThis describes the information in the event. It is more specific than `event.category`. Examples are `group-add`, `process-started`, `file-created`. The value is normally defined by the implementer.', example: 'user-password-change', id: 'event.action', - type: 'keyword', + type: 'string', + esTypes: ['keyword'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -64,7 +68,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ 'Name of the host.\nIt can contain what `hostname` returns on Unix systems, the fully qualified domain name, or a name specified by the user. The sender decides which value to use.', example: '', id: 'host.name', - type: 'keyword', + type: 'string', + esTypes: ['keyword'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -75,6 +80,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ example: '', id: 'source.ip', type: 'ip', + esTypes: ['ip'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -85,6 +91,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ example: '', id: 'destination.ip', type: 'ip', + esTypes: ['ip'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -97,6 +104,7 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ format: 'bytes', id: 'destination.bytes', type: 'number', + esTypes: ['long'], initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { @@ -105,7 +113,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ description: 'Short name or login of the user.', example: 'albert', id: 'user.name', - type: 'keyword', + type: 'string', + esTypes: ['keyword'], aggregatable: true, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, @@ -115,8 +124,9 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ description: 'Each document has an _id that uniquely identifies it', example: 'Y-6TfmcB0WOhS6qyMv3s', id: '_id', - type: 'keyword', - aggregatable: true, + type: 'string', + esTypes: [], // empty for _id + aggregatable: false, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, { @@ -126,7 +136,8 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ 'For log events the message field contains the log message.\nIn other use cases the message field can be used to concatenate different values which are then freely searchable. If multiple messages exist, they can be combined into one message.', example: 'Hello World', id: 'message', - type: 'text', + type: 'string', + esTypes: ['text'], aggregatable: false, initialWidth: DEFAULT_COLUMN_MIN_WIDTH, }, diff --git a/x-pack/plugins/timelines/public/mock/mock_timeline_data.ts b/x-pack/plugins/timelines/public/mock/mock_timeline_data.ts index 55ec6862309aa..e4df9efb62d69 100644 --- a/x-pack/plugins/timelines/public/mock/mock_timeline_data.ts +++ b/x-pack/plugins/timelines/public/mock/mock_timeline_data.ts @@ -1580,7 +1580,8 @@ export const mockTgridModel: TGridModel = { sort: [ { columnId: '@timestamp', - columnType: 'number', + columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, ], diff --git a/x-pack/plugins/timelines/public/mock/t_grid.tsx b/x-pack/plugins/timelines/public/mock/t_grid.tsx index d856cd43cadaf..b4f0110136886 100644 --- a/x-pack/plugins/timelines/public/mock/t_grid.tsx +++ b/x-pack/plugins/timelines/public/mock/t_grid.tsx @@ -123,6 +123,7 @@ export const tGridIntegratedProps: TGridIntegratedProps = { { columnId: '@timestamp', columnType: 'date', + esTypes: ['date'], sortDirection: 'desc', }, ], diff --git a/x-pack/plugins/timelines/public/store/t_grid/defaults.ts b/x-pack/plugins/timelines/public/store/t_grid/defaults.ts index 8e09f43a8ad4a..dfed73c16bf17 100644 --- a/x-pack/plugins/timelines/public/store/t_grid/defaults.ts +++ b/x-pack/plugins/timelines/public/store/t_grid/defaults.ts @@ -20,8 +20,9 @@ export const defaultHeaders: ColumnHeaderOptions[] = [ { columnHeaderType: defaultColumnHeaderType, id: '@timestamp', - type: 'number', initialWidth: DEFAULT_DATE_COLUMN_MIN_WIDTH, + esTypes: ['date'], + type: 'date', }, { columnHeaderType: defaultColumnHeaderType, @@ -84,6 +85,7 @@ export const tGridDefaults: SubsetTGridModel = { { columnId: '@timestamp', columnType: 'date', + esTypes: ['date'], sortDirection: Direction.desc, }, ], diff --git a/x-pack/plugins/timelines/public/store/t_grid/helpers.test.tsx b/x-pack/plugins/timelines/public/store/t_grid/helpers.test.tsx index a29a117946392..af0efd0834f9d 100644 --- a/x-pack/plugins/timelines/public/store/t_grid/helpers.test.tsx +++ b/x-pack/plugins/timelines/public/store/t_grid/helpers.test.tsx @@ -24,7 +24,7 @@ const defaultTimelineById = { describe('setInitializeTgridSettings', () => { test('it returns the expected sort when tGridSettingsProps has an override', () => { const sort: SortColumnTimeline[] = [ - { columnId: 'foozle', columnType: 'date', sortDirection: 'asc' }, + { columnId: 'foozle', columnType: 'date', esTypes: ['date'], sortDirection: 'asc' }, ]; const tGridSettingsProps: Partial = { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.test.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.test.ts index 1b58e63ec8752..5c6a0ac0bd416 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.test.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/eql/helpers.test.ts @@ -35,9 +35,10 @@ describe('Search Strategy EQL helper', () => { pagination: { activePage: 0, querySize: 25 }, sort: [ { - field: '@timestamp', direction: Direction.desc, - type: 'number', + esTypes: ['date'], + field: '@timestamp', + type: 'date', }, ], timerange: { @@ -85,9 +86,10 @@ describe('Search Strategy EQL helper', () => { pagination: { activePage: 1, querySize: 2 }, sort: [ { - field: '@timestamp', direction: Direction.desc, - type: 'number', + esTypes: ['date'], + field: '@timestamp', + type: 'date', }, ], timerange: { @@ -141,9 +143,10 @@ describe('Search Strategy EQL helper', () => { pagination: { activePage: 0, querySize: 2 }, sort: [ { - field: '@timestamp', direction: Direction.desc, - type: 'number', + field: '@timestamp', + esTypes: ['date'], + type: 'date', }, ], timerange: { @@ -418,9 +421,10 @@ describe('Search Strategy EQL helper', () => { pagination: { activePage: 3, querySize: 2 }, sort: [ { - field: '@timestamp', direction: Direction.desc, - type: 'number', + esTypes: ['date'], + field: '@timestamp', + type: 'date', }, ], timerange: { diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.test.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.test.ts new file mode 100644 index 0000000000000..aa6e9c8262c36 --- /dev/null +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.test.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { getPreferredEsType } from './helpers'; + +describe('helpers', () => { + describe('getPreferredEsType', () => { + it('prefers `keyword` over other types when `esTypes` contains a `keyword` entry', () => { + const esTypes = ['long', 'keyword']; + + expect(getPreferredEsType(esTypes)).toEqual('keyword'); + }); + + it('returns the first entry when esTypes has multiple entries, but no `keyword` entry', () => { + const esTypes = ['long', 'date']; + + expect(getPreferredEsType(esTypes)).toEqual('long'); + }); + + it('returns the first entry when esTypes has only one (non-`keyword`) entry', () => { + const esTypes = ['date']; + + expect(getPreferredEsType(esTypes)).toEqual('date'); + }); + + it('returns `keyword` when esTypes only contains a `keyword` entry', () => { + const esTypes: string[] = ['keyword']; + + expect(getPreferredEsType(esTypes)).toEqual('keyword'); + }); + + it('returns `keyword` when esTypes is empty', () => { + const esTypes: string[] = []; + + expect(getPreferredEsType(esTypes)).toEqual('keyword'); + }); + }); +}); diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.ts new file mode 100644 index 0000000000000..f33e019cba2ce --- /dev/null +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/helpers.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 + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +/** + * When `esTypes` types array contains more than one value, and one of those + * (multiple) values is `keyword`, the `keyword` entry is returned. The + * `keyword` entry is preferred over other values when it exists in the array. + * + * The `keyword` value is also returned when the `esTypes` array is empty. + */ +export const getPreferredEsType = (esTypes: string[]): string => { + if (esTypes.length === 1 || (esTypes.length > 1 && !esTypes.includes('keyword'))) { + return esTypes[0]; // no preference + } else { + return 'keyword'; // esTypes includes `keyword`, or it's empty + } +}; diff --git a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts index 04c61a645cf16..88aa2ad29c86a 100644 --- a/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts +++ b/x-pack/plugins/timelines/server/search_strategy/timeline/factory/events/all/query.events_all.dsl.ts @@ -15,6 +15,7 @@ import { TimelineRequestSortField, } from '../../../../../../common/search_strategy'; import { createQueryFilterClauses } from '../../../../../../server/utils/build_query'; +import { getPreferredEsType } from './helpers'; export const buildTimelineEventsAllQuery = ({ authFilter, @@ -58,7 +59,7 @@ export const buildTimelineEventsAllQuery = ({ return { [field]: { order: item.direction, - unmapped_type: item.type, + unmapped_type: getPreferredEsType(item.esTypes), }, }; }); diff --git a/x-pack/test/api_integration/apis/security_solution/events.ts b/x-pack/test/api_integration/apis/security_solution/events.ts index fef37e9939fcb..878de41a34289 100644 --- a/x-pack/test/api_integration/apis/security_solution/events.ts +++ b/x-pack/test/api_integration/apis/security_solution/events.ts @@ -49,7 +49,7 @@ export default function ({ getService }: FtrProviderContext) { { field: '@timestamp', direction: Direction.desc, - type: 'number', + esTypes: ['date'], }, ], timerange: {