From 5d8917d6cabf756a26a5c90769c36a059256449f Mon Sep 17 00:00:00 2001 From: FrankHassanabad Date: Thu, 24 Jan 2019 11:14:38 -0700 Subject: [PATCH 1/4] Refactored plumbing of agg terms of Authentications * Made it sort by desc of number of failures * Reduced type usages where possible to Ecs based types * Added newer fields to the Authentications based on feedback --- .../__snapshots__/index.test.tsx.snap | 50 +++-- .../hosts/authentications_table/index.tsx | 59 ++++-- .../page/hosts/authentications_table/mock.ts | 50 +++-- .../authentications_table/translations.ts | 47 ++++- .../authentications/index.gql_query.ts | 24 ++- .../containers/authentications/index.tsx | 2 +- .../containers/uncommon_processes/index.tsx | 60 +++--- .../secops/public/graphql/introspection.json | 137 +++++++------ x-pack/plugins/secops/public/graphql/types.ts | 82 +++++--- .../secops/public/mock/global_state.ts | 1 - .../public/store/local/hosts/actions.ts | 4 - .../secops/public/store/local/hosts/model.ts | 6 +- .../public/store/local/hosts/reducer.ts | 12 -- .../authentications/authentications.mock.ts | 50 +++-- .../graphql/authentications/schema.gql.ts | 11 +- .../graphql/authentications/schema.test.ts | 27 ++- x-pack/plugins/secops/server/graphql/types.ts | 183 ++++++++++-------- .../authentications/elastic_adapter.test.ts | 57 +----- .../authentications/elasticsearch_adapter.ts | 29 ++- .../server/lib/authentications/query.dsl.ts | 52 ++--- .../server/lib/authentications/types.ts | 12 +- .../server/lib/ecs_fields/extend_map.test.ts | 56 ++++++ .../server/lib/ecs_fields/extend_map.ts | 16 ++ .../secops/server/lib/ecs_fields/index.ts | 18 +- .../plugins/secops/server/lib/hosts/types.ts | 11 +- .../elasticsearch_adapter.ts | 15 +- .../lib/uncommon_processes/query.dsl.ts | 2 +- .../server/lib/uncommon_processes/types.ts | 7 +- .../apis/secops/authentications.ts | 2 +- .../test/api_integration/apis/secops/index.js | 2 +- 30 files changed, 668 insertions(+), 416 deletions(-) create mode 100644 x-pack/plugins/secops/server/lib/ecs_fields/extend_map.test.ts create mode 100644 x-pack/plugins/secops/server/lib/ecs_fields/extend_map.ts diff --git a/x-pack/plugins/secops/public/components/page/hosts/authentications_table/__snapshots__/index.test.tsx.snap b/x-pack/plugins/secops/public/components/page/hosts/authentications_table/__snapshots__/index.test.tsx.snap index 3096e5bacf7d4..8ced4521e9ad1 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/authentications_table/__snapshots__/index.test.tsx.snap +++ b/x-pack/plugins/secops/public/components/page/hosts/authentications_table/__snapshots__/index.test.tsx.snap @@ -11,14 +11,25 @@ exports[`Authentication Table Component rendering it renders the default Authent "node": Object { "_id": "cPsuhGcB0WOhS6qyTKC0", "failures": 10, - "host": Object { - "id": "123", - "ip": "192.168.0.1", - "name": "host-computer-1", + "lastFailure": Object { + "host": Object { + "id": "host-id-1", + "name": "host-2", + }, + "source": Object { + "ip": "8.8.8.8", + }, + "timestamp": "2019-01-23T22:35:32.222Z", }, - "latest": "2019-01-11T06:18:30.745Z", - "source": Object { - "ip": "127.0.0.1", + "lastSuccess": Object { + "host": Object { + "id": "host-id-1", + "name": "host-1", + }, + "source": Object { + "ip": "127.0.0.1", + }, + "timestamp": "2019-01-23T22:35:32.222Z", }, "successes": 0, "user": Object { @@ -33,14 +44,25 @@ exports[`Authentication Table Component rendering it renders the default Authent "node": Object { "_id": "KwQDiWcB0WOhS6qyXmrW", "failures": 10, - "host": Object { - "id": "234", - "ip": "192.168.0.1", - "name": "host-computer-2", + "lastFailure": Object { + "host": Object { + "id": "host-id-1", + "name": "host-2", + }, + "source": Object { + "ip": "8.8.8.8", + }, + "timestamp": "2019-01-23T22:35:32.222Z", }, - "latest": "2019-01-11T06:18:30.745Z", - "source": Object { - "ip": "127.0.0.1", + "lastSuccess": Object { + "host": Object { + "id": "host-id-1", + "name": "host-1", + }, + "source": Object { + "ip": "127.0.0.1", + }, + "timestamp": "2019-01-23T22:35:32.222Z", }, "successes": 0, "user": Object { diff --git a/x-pack/plugins/secops/public/components/page/hosts/authentications_table/index.tsx b/x-pack/plugins/secops/public/components/page/hosts/authentications_table/index.tsx index 6ab2ff19f5e7c..71cd46f1fa349 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/authentications_table/index.tsx +++ b/x-pack/plugins/secops/public/components/page/hosts/authentications_table/index.tsx @@ -10,11 +10,12 @@ import React from 'react'; import { connect } from 'react-redux'; import { pure } from 'recompose'; +import { has } from 'lodash/fp'; import moment from 'moment'; import { AuthenticationsEdges } from '../../../../graphql/types'; import { authenticationsSelector, hostsActions, State } from '../../../../store'; import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; -import { defaultToEmpty, getEmptyValue } from '../../../empty_value'; +import { defaultToEmpty, getEmptyValue, getOrEmpty } from '../../../empty_value'; import { ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; import { Provider } from '../../../timeline/data_providers/provider'; import * as i18n from './translations'; @@ -74,7 +75,7 @@ const AuthenticationTableComponent = pure( }) => ( loadMore(nextCursor)} @@ -84,7 +85,7 @@ const AuthenticationTableComponent = pure( updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })} title={

- {i18n.AUTHENTICATION_FAILURES} {totalCount} + {i18n.AUTHENTICATIONS} {totalCount}

} /> @@ -146,6 +147,34 @@ const getAuthenticationColumns = (startDate: number) => [ hideForMobile: false, render: ({ node }: AuthenticationsEdges) => <>{defaultToEmpty(node.failures)}, }, + { + name: i18n.LAST_FAILED_TIME, + truncateText: false, + hideForMobile: false, + render: ({ node }: AuthenticationsEdges) => { + return ( + <> + {has('lastFailure.timestamp', node) ? ( + + ) : ( + getEmptyValue() + )} + + ); + }, + }, + { + name: i18n.LAST_FAILED_SOURCE, + truncateText: false, + hideForMobile: false, + render: ({ node }: AuthenticationsEdges) => <>{getOrEmpty('lastFailure.source.ip', node)}, + }, + { + name: i18n.LAST_FAILED_DESTINATION, + truncateText: false, + hideForMobile: false, + render: ({ node }: AuthenticationsEdges) => <>{getOrEmpty('lastFailure.host.name', node)}, + }, { name: i18n.SUCCESSES, truncateText: false, @@ -153,23 +182,31 @@ const getAuthenticationColumns = (startDate: number) => [ render: ({ node }: AuthenticationsEdges) => <>{defaultToEmpty(node.successes)}, }, { - name: i18n.FROM, + name: i18n.LAST_SUCCESSFUL_TIME, truncateText: false, hideForMobile: false, - render: ({ node }: AuthenticationsEdges) => <>{defaultToEmpty(node.source.ip)}, + render: ({ node }: AuthenticationsEdges) => { + return ( + <> + {has('lastSuccess.timestamp', node) ? ( + + ) : ( + getEmptyValue() + )} + + ); + }, }, { - name: i18n.TO, + name: i18n.LAST_SUCCESSFUL_SOURCE, truncateText: false, hideForMobile: false, - render: ({ node }: AuthenticationsEdges) => <>{defaultToEmpty(node.host.name)}, + render: ({ node }: AuthenticationsEdges) => <>{getOrEmpty('lastSuccess.source.ip', node)}, }, { - name: i18n.LATEST, + name: i18n.LAST_SUCCESSFUL_DESTINATION, truncateText: false, hideForMobile: false, - render: ({ node }: AuthenticationsEdges) => ( - <>{node.latest ? : getEmptyValue()} - ), + render: ({ node }: AuthenticationsEdges) => <>{getOrEmpty('lastSuccess.host.name', node)}, }, ]; diff --git a/x-pack/plugins/secops/public/components/page/hosts/authentications_table/mock.ts b/x-pack/plugins/secops/public/components/page/hosts/authentications_table/mock.ts index 0de011c1d6030..7f7529c25b286 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/authentications_table/mock.ts +++ b/x-pack/plugins/secops/public/components/page/hosts/authentications_table/mock.ts @@ -16,12 +16,25 @@ export const mockData: { Authentications: AuthenticationsData } = { failures: 10, successes: 0, user: { name: 'Evan Hassanabad' }, - source: { ip: '127.0.0.1' }, - latest: '2019-01-11T06:18:30.745Z', - host: { - id: '123', - name: 'host-computer-1', - ip: '192.168.0.1', + lastSuccess: { + timestamp: '2019-01-23T22:35:32.222Z', + source: { + ip: '127.0.0.1', + }, + host: { + id: 'host-id-1', + name: 'host-1', + }, + }, + lastFailure: { + timestamp: '2019-01-23T22:35:32.222Z', + source: { + ip: '8.8.8.8', + }, + host: { + id: 'host-id-1', + name: 'host-2', + }, }, }, cursor: { @@ -34,12 +47,25 @@ export const mockData: { Authentications: AuthenticationsData } = { failures: 10, successes: 0, user: { name: 'Braden Hassanabad' }, - source: { ip: '127.0.0.1' }, - latest: '2019-01-11T06:18:30.745Z', - host: { - id: '234', - name: 'host-computer-2', - ip: '192.168.0.1', + lastSuccess: { + timestamp: '2019-01-23T22:35:32.222Z', + source: { + ip: '127.0.0.1', + }, + host: { + id: 'host-id-1', + name: 'host-1', + }, + }, + lastFailure: { + timestamp: '2019-01-23T22:35:32.222Z', + source: { + ip: '8.8.8.8', + }, + host: { + id: 'host-id-1', + name: 'host-2', + }, }, }, cursor: { diff --git a/x-pack/plugins/secops/public/components/page/hosts/authentications_table/translations.ts b/x-pack/plugins/secops/public/components/page/hosts/authentications_table/translations.ts index abed5e6e9c906..81fb179ecca02 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/authentications_table/translations.ts +++ b/x-pack/plugins/secops/public/components/page/hosts/authentications_table/translations.ts @@ -6,16 +6,43 @@ import { i18n } from '@kbn/i18n'; -export const LATEST = i18n.translate('xpack.secops.authenticationsTable.latest', { - defaultMessage: 'Latest', -}); +export const LAST_SUCCESSFUL_SOURCE = i18n.translate( + 'xpack.secops.authenticationsTable.lastSuccessfulSource', + { + defaultMessage: 'Last Successful Source', + } +); -export const TO = i18n.translate('xpack.secops.authenticationsTable.to', { - defaultMessage: 'To', -}); +export const LAST_SUCCESSFUL_DESTINATION = i18n.translate( + 'xpack.secops.authenticationsTable.lastSuccessfulDestination', + { + defaultMessage: 'Last Successful Destination', + } +); + +export const LAST_SUCCESSFUL_TIME = i18n.translate( + 'xpack.secops.authenticationsTable.lastSuccessfulTime', + { + defaultMessage: 'Last Success', + } +); + +export const LAST_FAILED_SOURCE = i18n.translate( + 'xpack.secops.authenticationsTable.lastFailedSource', + { + defaultMessage: 'Last Failed Source', + } +); + +export const LAST_FAILED_DESTINATION = i18n.translate( + 'xpack.secops.authenticationsTable.lastFailedDestination', + { + defaultMessage: 'Last Failed Destination', + } +); -export const FROM = i18n.translate('xpack.secops.authenticationsTable.from', { - defaultMessage: 'From', +export const LAST_FAILED_TIME = i18n.translate('xpack.secops.authenticationsTable.lastFailedTime', { + defaultMessage: 'Last Failure', }); export const SUCCESSES = i18n.translate('xpack.secops.authenticationsTable.successes', { @@ -30,10 +57,10 @@ export const USER = i18n.translate('xpack.secops.authenticationsTable.user', { defaultMessage: 'User', }); -export const AUTHENTICATION_FAILURES = i18n.translate( +export const AUTHENTICATIONS = i18n.translate( 'xpack.secops.authenticationsTable.authenticationFailures', { - defaultMessage: 'Authentication Failures', + defaultMessage: 'Authentications', } ); diff --git a/x-pack/plugins/secops/public/containers/authentications/index.gql_query.ts b/x-pack/plugins/secops/public/containers/authentications/index.gql_query.ts index 951f6d9febf09..bf26dd490d53f 100644 --- a/x-pack/plugins/secops/public/containers/authentications/index.gql_query.ts +++ b/x-pack/plugins/secops/public/containers/authentications/index.gql_query.ts @@ -25,14 +25,26 @@ export const authenticationsQuery = gql` user { name } - source { - ip + lastSuccess { + timestamp + source { + ip + } + host { + id + name + } } - host { - id - name + lastFailure { + timestamp + source { + ip + } + host { + id + name + } } - latest } cursor { value diff --git a/x-pack/plugins/secops/public/containers/authentications/index.tsx b/x-pack/plugins/secops/public/containers/authentications/index.tsx index dbd701d809c17..8db7fb69bdd63 100644 --- a/x-pack/plugins/secops/public/containers/authentications/index.tsx +++ b/x-pack/plugins/secops/public/containers/authentications/index.tsx @@ -87,7 +87,7 @@ const AuthenticationsComponentQuery = pure( variables: { pagination: { cursor: newCursor, - limit, + limit: limit + parseInt(newCursor, 10), }, }, updateQuery: (prev, { fetchMoreResult }) => { diff --git a/x-pack/plugins/secops/public/containers/uncommon_processes/index.tsx b/x-pack/plugins/secops/public/containers/uncommon_processes/index.tsx index 3a8d334fe6a6c..fa11443185086 100644 --- a/x-pack/plugins/secops/public/containers/uncommon_processes/index.tsx +++ b/x-pack/plugins/secops/public/containers/uncommon_processes/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getOr, isEmpty, set } from 'lodash/fp'; +import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; import { pure } from 'recompose'; @@ -39,7 +39,6 @@ export interface OwnProps { export interface UncommonProcessesComponentReduxProps { limit: number; - upperLimit: number; } type UncommonProcessesProps = OwnProps & UncommonProcessesComponentReduxProps; @@ -53,7 +52,6 @@ const UncommonProcessesComponentQuery = pure( startDate, endDate, limit, - upperLimit, cursor, poll, }) => ( @@ -70,51 +68,55 @@ const UncommonProcessesComponentQuery = pure( to: endDate, }, pagination: { - limit: upperLimit, + limit, cursor, tiebreaker: null, }, filterQuery, }} > - {({ data, loading, refetch, updateQuery }) => { + {({ data, loading, fetchMore, refetch }) => { const uncommonProcesses = getOr([], 'source.UncommonProcesses.edges', data); - const pageInfo = getOr( - { endCursor: { value: '' } }, - 'source.UncommonProcesses.pageInfo', - data - ); - - let endCursor = String(limit); - if (!isEmpty(pageInfo.endCursor.value)) { - endCursor = pageInfo.endCursor.value; - } - - const hasNextPage = hasMoreData(parseInt(endCursor, 10), upperLimit, uncommonProcesses); - const slicedData = uncommonProcesses.slice(0, parseInt(endCursor, 10)); return children({ id, loading, refetch, totalCount: getOr(0, 'source.UncommonProcesses.totalCount', data), - uncommonProcesses: slicedData, - pageInfo: { hasNextPage, endCursor: { value: String(parseInt(endCursor, 10) + limit) } }, + uncommonProcesses, + pageInfo: getOr({}, 'source.UncommonProcesses.pageInfo', data), loadMore: (newCursor: string) => - updateQuery(prev => - set('source.UncommonProcesses.pageInfo.endCursor.value', newCursor, prev) - ), + fetchMore({ + variables: { + pagination: { + cursor: newCursor, + limit: limit + parseInt(newCursor, 10), + }, + }, + updateQuery: (prev, { fetchMoreResult }) => { + if (!fetchMoreResult) { + return prev; + } + return { + ...fetchMoreResult, + source: { + ...fetchMoreResult.source, + UncommonProcesses: { + ...fetchMoreResult.source.UncommonProcesses, + edges: [ + ...prev.source.UncommonProcesses.edges, + ...fetchMoreResult.source.UncommonProcesses.edges, + ], + }, + }, + }; + }, + }), }); }} ) ); -export const hasMoreData = ( - limit: number, - upperLimit: number, - data: UncommonProcessesEdges[] -): boolean => limit < upperLimit && limit < data.length; - const mapStateToProps = (state: State) => uncommonProcessesSelector(state); export const UncommonProcessesQuery = connect(mapStateToProps)(UncommonProcessesComponentQuery); diff --git a/x-pack/plugins/secops/public/graphql/introspection.json b/x-pack/plugins/secops/public/graphql/introspection.json index e6106ad804964..1fdf7ec6b13b7 100644 --- a/x-pack/plugins/secops/public/graphql/introspection.json +++ b/x-pack/plugins/secops/public/graphql/introspection.json @@ -919,50 +919,30 @@ "deprecationReason": null }, { - "name": "latest", + "name": "user", "description": "", "args": [], "type": { "kind": "NON_NULL", "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } + "ofType": { "kind": "OBJECT", "name": "UserEcsFields", "ofType": null } }, "isDeprecated": false, "deprecationReason": null }, { - "name": "source", + "name": "lastSuccess", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "SourceEcsFields", "ofType": null } - }, + "type": { "kind": "OBJECT", "name": "LastSourceHost", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "host", + "name": "lastFailure", "description": "", "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "HostEcsFields", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "user", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "UserEcsFields", "ofType": null } - }, + "type": { "kind": "OBJECT", "name": "LastSourceHost", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -974,11 +954,19 @@ }, { "kind": "OBJECT", - "name": "SourceEcsFields", + "name": "UserEcsFields", "description": "", "fields": [ { - "name": "ip", + "name": "id", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "name", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -986,10 +974,34 @@ "deprecationReason": null }, { - "name": "port", + "name": "full_name", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "email", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "hash", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "group", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -1001,19 +1013,11 @@ }, { "kind": "OBJECT", - "name": "HostEcsFields", + "name": "LastSourceHost", "description": "", "fields": [ { - "name": "id", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ip", + "name": "timestamp", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -1021,18 +1025,18 @@ "deprecationReason": null }, { - "name": "name", + "name": "source", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "SourceEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, { - "name": "os", + "name": "host", "description": "", "args": [], - "type": { "kind": "OBJECT", "name": "OsEcsFields", "ofType": null }, + "type": { "kind": "OBJECT", "name": "HostEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -1044,11 +1048,11 @@ }, { "kind": "OBJECT", - "name": "OsEcsFields", + "name": "SourceEcsFields", "description": "", "fields": [ { - "name": "platform", + "name": "ip", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -1056,15 +1060,26 @@ "deprecationReason": null }, { - "name": "name", + "name": "port", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, "isDeprecated": false, "deprecationReason": null - }, + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "HostEcsFields", + "description": "", + "fields": [ { - "name": "full", + "name": "id", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -1072,7 +1087,7 @@ "deprecationReason": null }, { - "name": "family", + "name": "ip", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -1080,7 +1095,7 @@ "deprecationReason": null }, { - "name": "version", + "name": "name", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -1088,10 +1103,10 @@ "deprecationReason": null }, { - "name": "kernel", + "name": "os", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "type": { "kind": "OBJECT", "name": "OsEcsFields", "ofType": null }, "isDeprecated": false, "deprecationReason": null } @@ -1103,14 +1118,14 @@ }, { "kind": "OBJECT", - "name": "UserEcsFields", + "name": "OsEcsFields", "description": "", "fields": [ { - "name": "id", + "name": "platform", "description": "", "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, "isDeprecated": false, "deprecationReason": null }, @@ -1123,7 +1138,7 @@ "deprecationReason": null }, { - "name": "full_name", + "name": "full", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -1131,7 +1146,7 @@ "deprecationReason": null }, { - "name": "email", + "name": "family", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -1139,7 +1154,7 @@ "deprecationReason": null }, { - "name": "hash", + "name": "version", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, @@ -1147,7 +1162,7 @@ "deprecationReason": null }, { - "name": "group", + "name": "kernel", "description": "", "args": [], "type": { "kind": "SCALAR", "name": "String", "ofType": null }, diff --git a/x-pack/plugins/secops/public/graphql/types.ts b/x-pack/plugins/secops/public/graphql/types.ts index da75b4dbfee5d..ac112e31b872d 100644 --- a/x-pack/plugins/secops/public/graphql/types.ts +++ b/x-pack/plugins/secops/public/graphql/types.ts @@ -120,13 +120,33 @@ export interface AuthenticationItem { successes: number; - latest: string; + user: UserEcsFields; - source: SourceEcsFields; + lastSuccess?: LastSourceHost | null; - host: HostEcsFields; + lastFailure?: LastSourceHost | null; +} - user: UserEcsFields; +export interface UserEcsFields { + id?: number | null; + + name?: string | null; + + full_name?: string | null; + + email?: string | null; + + hash?: string | null; + + group?: string | null; +} + +export interface LastSourceHost { + timestamp?: string | null; + + source?: SourceEcsFields | null; + + host?: HostEcsFields | null; } export interface SourceEcsFields { @@ -159,20 +179,6 @@ export interface OsEcsFields { kernel?: string | null; } -export interface UserEcsFields { - id?: number | null; - - name?: string | null; - - full_name?: string | null; - - email?: string | null; - - hash?: string | null; - - group?: string | null; -} - export interface CursorType { value: string; @@ -492,11 +498,9 @@ export namespace GetAuthenticationsQuery { user: User; - source: _Source; + lastSuccess?: LastSuccess | null; - host: Host; - - latest: string; + lastFailure?: LastFailure | null; }; export type User = { @@ -505,6 +509,16 @@ export namespace GetAuthenticationsQuery { name?: string | null; }; + export type LastSuccess = { + __typename?: 'LastSourceHost'; + + timestamp?: string | null; + + source?: _Source | null; + + host?: Host | null; + }; + export type _Source = { __typename?: 'SourceEcsFields'; @@ -519,6 +533,30 @@ export namespace GetAuthenticationsQuery { name?: string | null; }; + export type LastFailure = { + __typename?: 'LastSourceHost'; + + timestamp?: string | null; + + source?: __Source | null; + + host?: _Host | null; + }; + + export type __Source = { + __typename?: 'SourceEcsFields'; + + ip?: string | null; + }; + + export type _Host = { + __typename?: 'HostEcsFields'; + + id?: string | null; + + name?: string | null; + }; + export type Cursor = { __typename?: 'CursorType'; diff --git a/x-pack/plugins/secops/public/mock/global_state.ts b/x-pack/plugins/secops/public/mock/global_state.ts index a2bc0f9c9e13e..606d0983afcfa 100644 --- a/x-pack/plugins/secops/public/mock/global_state.ts +++ b/x-pack/plugins/secops/public/mock/global_state.ts @@ -27,7 +27,6 @@ export const mockGlobalState: State = { }, uncommonProcesses: { limit: 0, - upperLimit: 0, }, }, }, diff --git a/x-pack/plugins/secops/public/store/local/hosts/actions.ts b/x-pack/plugins/secops/public/store/local/hosts/actions.ts index 8ebaabab81036..443f8aef73715 100644 --- a/x-pack/plugins/secops/public/store/local/hosts/actions.ts +++ b/x-pack/plugins/secops/public/store/local/hosts/actions.ts @@ -19,7 +19,3 @@ export const updateEventsLimit = actionCreator<{ limit: number }>('UPDATE_EVENTS export const updateUncommonProcessesLimit = actionCreator<{ limit: number }>( 'UPDATE_UNCOMMONPROCESSES_LIMIT' ); - -export const updateUncommonProcessesUpperLimit = actionCreator<{ upperLimit: number }>( - 'UPDATE_UNCOMMONPROCESSES_UPPER_LIMIT' -); diff --git a/x-pack/plugins/secops/public/store/local/hosts/model.ts b/x-pack/plugins/secops/public/store/local/hosts/model.ts index c0939f821a64e..2f5bdb517b181 100644 --- a/x-pack/plugins/secops/public/store/local/hosts/model.ts +++ b/x-pack/plugins/secops/public/store/local/hosts/model.ts @@ -7,15 +7,11 @@ export interface BasicQuery { limit: number; } -export interface InMemoryPaginationQuery extends BasicQuery { - upperLimit: number; -} - export interface HostsModel { query: { authentications: BasicQuery; hosts: BasicQuery; events: BasicQuery; - uncommonProcesses: InMemoryPaginationQuery; + uncommonProcesses: BasicQuery; }; } diff --git a/x-pack/plugins/secops/public/store/local/hosts/reducer.ts b/x-pack/plugins/secops/public/store/local/hosts/reducer.ts index 3eb71b631c63d..917399acd6584 100644 --- a/x-pack/plugins/secops/public/store/local/hosts/reducer.ts +++ b/x-pack/plugins/secops/public/store/local/hosts/reducer.ts @@ -11,7 +11,6 @@ import { updateEventsLimit, updateHostsLimit, updateUncommonProcessesLimit, - updateUncommonProcessesUpperLimit, } from './actions'; import { HostsModel } from './model'; @@ -32,7 +31,6 @@ export const initialHostsState: HostsState = { }, uncommonProcesses: { limit: DEFAULT_LIMIT, - upperLimit: 100, }, }, }; @@ -75,14 +73,4 @@ export const hostsReducer = reducerWithInitialState(initialHostsState) }, }, })) - .case(updateUncommonProcessesUpperLimit, (state, { upperLimit }) => ({ - ...state, - query: { - ...state.query, - uncommonProcesses: { - ...state.query.uncommonProcesses, - upperLimit, - }, - }, - })) .build(); diff --git a/x-pack/plugins/secops/server/graphql/authentications/authentications.mock.ts b/x-pack/plugins/secops/server/graphql/authentications/authentications.mock.ts index b1fe7332b55fa..bff2f3d48ac73 100644 --- a/x-pack/plugins/secops/server/graphql/authentications/authentications.mock.ts +++ b/x-pack/plugins/secops/server/graphql/authentications/authentications.mock.ts @@ -19,12 +19,25 @@ export const mockAuthenticationsData: { Authentications: AuthenticationsData } = failures: 10, successes: 0, user: { name: 'Evan Hassanabad' }, - source: { ip: '127.0.0.1' }, - latest: '2019-01-11T06:18:30.745Z', - host: { - id: '123', - name: 'host-computer-1', - ip: '192.168.0.1', + lastSuccess: { + timestamp: '2019-01-23T22:35:32.222Z', + source: { + ip: '127.0.0.1', + }, + host: { + id: 'host-id-1', + name: 'host-1', + }, + }, + lastFailure: { + timestamp: '2019-01-23T22:35:32.222Z', + source: { + ip: '8.8.8.8', + }, + host: { + id: 'host-id-1', + name: 'host-2', + }, }, }, cursor: { @@ -37,12 +50,25 @@ export const mockAuthenticationsData: { Authentications: AuthenticationsData } = failures: 10, successes: 0, user: { name: 'Braden Hassanabad' }, - source: { ip: '127.0.0.1' }, - latest: '2019-01-11T06:18:30.745Z', - host: { - id: '234', - name: 'host-computer-2', - ip: '192.168.0.1', + lastSuccess: { + timestamp: '2019-01-23T22:35:32.222Z', + source: { + ip: '127.0.0.1', + }, + host: { + id: 'host-id-1', + name: 'host-1', + }, + }, + lastFailure: { + timestamp: '2019-01-23T22:35:32.222Z', + source: { + ip: '8.8.8.8', + }, + host: { + id: 'host-id-1', + name: 'host-2', + }, }, }, cursor: { diff --git a/x-pack/plugins/secops/server/graphql/authentications/schema.gql.ts b/x-pack/plugins/secops/server/graphql/authentications/schema.gql.ts index 5e7f26a5dcda3..724a8e4d1d905 100644 --- a/x-pack/plugins/secops/server/graphql/authentications/schema.gql.ts +++ b/x-pack/plugins/secops/server/graphql/authentications/schema.gql.ts @@ -7,14 +7,19 @@ import gql from 'graphql-tag'; export const authenticationsSchema = gql` + type LastSourceHost { + timestamp: String + source: SourceEcsFields + host: HostEcsFields + } + type AuthenticationItem { _id: String! failures: Float! successes: Float! - latest: String! - source: SourceEcsFields! - host: HostEcsFields! user: UserEcsFields! + lastSuccess: LastSourceHost + lastFailure: LastSourceHost } type AuthenticationsEdges { diff --git a/x-pack/plugins/secops/server/graphql/authentications/schema.test.ts b/x-pack/plugins/secops/server/graphql/authentications/schema.test.ts index d9c0feedf799a..cc295a172c268 100644 --- a/x-pack/plugins/secops/server/graphql/authentications/schema.test.ts +++ b/x-pack/plugins/secops/server/graphql/authentications/schema.test.ts @@ -31,17 +31,28 @@ const testCaseSource = { user { name } - source { - ip + lastSuccess { + timestamp + source { + ip + } + host { + id + name + } } - host { - id - ip - name + lastFailure { + timestamp + source { + ip + } + host { + id + name + } } - latest } - cursor{ + cursor { value } } diff --git a/x-pack/plugins/secops/server/graphql/types.ts b/x-pack/plugins/secops/server/graphql/types.ts index 385478ad24bd6..eba4983b1e781 100644 --- a/x-pack/plugins/secops/server/graphql/types.ts +++ b/x-pack/plugins/secops/server/graphql/types.ts @@ -149,13 +149,33 @@ export interface AuthenticationItem { successes: number; - latest: string; + user: UserEcsFields; - source: SourceEcsFields; + lastSuccess?: LastSourceHost | null; - host: HostEcsFields; + lastFailure?: LastSourceHost | null; +} - user: UserEcsFields; +export interface UserEcsFields { + id?: number | null; + + name?: string | null; + + full_name?: string | null; + + email?: string | null; + + hash?: string | null; + + group?: string | null; +} + +export interface LastSourceHost { + timestamp?: string | null; + + source?: SourceEcsFields | null; + + host?: HostEcsFields | null; } export interface SourceEcsFields { @@ -188,20 +208,6 @@ export interface OsEcsFields { kernel?: string | null; } -export interface UserEcsFields { - id?: number | null; - - name?: string | null; - - full_name?: string | null; - - email?: string | null; - - hash?: string | null; - - group?: string | null; -} - export interface CursorType { value: string; @@ -842,13 +848,11 @@ export namespace AuthenticationItemResolvers { successes?: SuccessesResolver; - latest?: LatestResolver; - - source?: SourceResolver; + user?: UserResolver; - host?: HostResolver; + lastSuccess?: LastSuccessResolver; - user?: UserResolver; + lastFailure?: LastFailureResolver; } export type IdResolver< @@ -866,24 +870,92 @@ export namespace AuthenticationItemResolvers { Parent = AuthenticationItem, Context = SecOpsContext > = Resolver; - export type LatestResolver< - R = string, + export type UserResolver< + R = UserEcsFields, Parent = AuthenticationItem, Context = SecOpsContext > = Resolver; - export type SourceResolver< - R = SourceEcsFields, + export type LastSuccessResolver< + R = LastSourceHost | null, Parent = AuthenticationItem, Context = SecOpsContext > = Resolver; - export type HostResolver< - R = HostEcsFields, + export type LastFailureResolver< + R = LastSourceHost | null, Parent = AuthenticationItem, Context = SecOpsContext > = Resolver; - export type UserResolver< - R = UserEcsFields, - Parent = AuthenticationItem, +} + +export namespace UserEcsFieldsResolvers { + export interface Resolvers { + id?: IdResolver; + + name?: NameResolver; + + full_name?: FullNameResolver; + + email?: EmailResolver; + + hash?: HashResolver; + + group?: GroupResolver; + } + + export type IdResolver< + R = number | null, + Parent = UserEcsFields, + Context = SecOpsContext + > = Resolver; + export type NameResolver< + R = string | null, + Parent = UserEcsFields, + Context = SecOpsContext + > = Resolver; + export type FullNameResolver< + R = string | null, + Parent = UserEcsFields, + Context = SecOpsContext + > = Resolver; + export type EmailResolver< + R = string | null, + Parent = UserEcsFields, + Context = SecOpsContext + > = Resolver; + export type HashResolver< + R = string | null, + Parent = UserEcsFields, + Context = SecOpsContext + > = Resolver; + export type GroupResolver< + R = string | null, + Parent = UserEcsFields, + Context = SecOpsContext + > = Resolver; +} + +export namespace LastSourceHostResolvers { + export interface Resolvers { + timestamp?: TimestampResolver; + + source?: SourceResolver; + + host?: HostResolver; + } + + export type TimestampResolver< + R = string | null, + Parent = LastSourceHost, + Context = SecOpsContext + > = Resolver; + export type SourceResolver< + R = SourceEcsFields | null, + Parent = LastSourceHost, + Context = SecOpsContext + > = Resolver; + export type HostResolver< + R = HostEcsFields | null, + Parent = LastSourceHost, Context = SecOpsContext > = Resolver; } @@ -987,53 +1059,6 @@ export namespace OsEcsFieldsResolvers { > = Resolver; } -export namespace UserEcsFieldsResolvers { - export interface Resolvers { - id?: IdResolver; - - name?: NameResolver; - - full_name?: FullNameResolver; - - email?: EmailResolver; - - hash?: HashResolver; - - group?: GroupResolver; - } - - export type IdResolver< - R = number | null, - Parent = UserEcsFields, - Context = SecOpsContext - > = Resolver; - export type NameResolver< - R = string | null, - Parent = UserEcsFields, - Context = SecOpsContext - > = Resolver; - export type FullNameResolver< - R = string | null, - Parent = UserEcsFields, - Context = SecOpsContext - > = Resolver; - export type EmailResolver< - R = string | null, - Parent = UserEcsFields, - Context = SecOpsContext - > = Resolver; - export type HashResolver< - R = string | null, - Parent = UserEcsFields, - Context = SecOpsContext - > = Resolver; - export type GroupResolver< - R = string | null, - Parent = UserEcsFields, - Context = SecOpsContext - > = Resolver; -} - export namespace CursorTypeResolvers { export interface Resolvers { value?: ValueResolver; diff --git a/x-pack/plugins/secops/server/lib/authentications/elastic_adapter.test.ts b/x-pack/plugins/secops/server/lib/authentications/elastic_adapter.test.ts index 2d76765fb1e56..041b29689a853 100644 --- a/x-pack/plugins/secops/server/lib/authentications/elastic_adapter.test.ts +++ b/x-pack/plugins/secops/server/lib/authentications/elastic_adapter.test.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import { AuthenticationsEdges } from '../../graphql/types'; import { formatAuthenticationData } from './elasticsearch_adapter'; import { auditdFieldsMap } from './query.dsl'; @@ -17,14 +18,6 @@ describe('authentications elasticsearch_adapter', () => { _score: 10, _source: { '@timestamp': 'time-1', - source: { - ip: '192.168.0.1', - }, - host: { - id: 'host-id-1', - ip: '127.0.0.1', - name: 'host-name-1', - }, }, cursor: 'cursor-1', sort: [0], @@ -45,17 +38,9 @@ describe('authentications elasticsearch_adapter', () => { _id: 'id-123', failures: 10, successes: 20, - latest: '', - host: { - id: '', - name: '', - }, user: { name: 'Evan', }, - source: { - ip: '', - }, }, }; @@ -63,7 +48,7 @@ describe('authentications elasticsearch_adapter', () => { }); test('it formats a authentications with a source ip correctly', () => { - const fields: ReadonlyArray = ['source.ip']; + const fields: ReadonlyArray = ['lastSuccess.source.ip']; const data = formatAuthenticationData(fields, hit, auditdFieldsMap); const expected: AuthenticationsEdges = { cursor: { @@ -74,17 +59,9 @@ describe('authentications elasticsearch_adapter', () => { _id: 'id-123', failures: 10, successes: 20, - latest: '', - host: { - id: '', - name: '', - }, user: { name: 'Evan', }, - source: { - ip: '192.168.0.1', - }, }, }; @@ -92,7 +69,7 @@ describe('authentications elasticsearch_adapter', () => { }); test('it formats a authentications with a host name only', () => { - const fields: ReadonlyArray = ['host.name']; + const fields: ReadonlyArray = ['lastSuccess.host.name']; const data = formatAuthenticationData(fields, hit, auditdFieldsMap); const expected: AuthenticationsEdges = { cursor: { @@ -103,17 +80,9 @@ describe('authentications elasticsearch_adapter', () => { _id: 'id-123', failures: 10, successes: 20, - latest: '', - host: { - id: '', - name: 'host-name-1', - }, user: { name: 'Evan', }, - source: { - ip: '', - }, }, }; @@ -121,7 +90,7 @@ describe('authentications elasticsearch_adapter', () => { }); test('it formats a authentications with a host id only', () => { - const fields: ReadonlyArray = ['host.id']; + const fields: ReadonlyArray = ['lastSuccess.host.id']; const data = formatAuthenticationData(fields, hit, auditdFieldsMap); const expected: AuthenticationsEdges = { cursor: { @@ -132,17 +101,9 @@ describe('authentications elasticsearch_adapter', () => { _id: 'id-123', failures: 10, successes: 20, - latest: '', - host: { - id: 'host-id-1', - name: '', - }, user: { name: 'Evan', }, - source: { - ip: '', - }, }, }; @@ -150,7 +111,7 @@ describe('authentications elasticsearch_adapter', () => { }); test('it formats a authentications with a host name and id correctly', () => { - const fields: ReadonlyArray = ['host.name', 'host.id']; + const fields: ReadonlyArray = ['lastSuccess.host.name', 'lastSuccess.host.id']; const data = formatAuthenticationData(fields, hit, auditdFieldsMap); const expected: AuthenticationsEdges = { cursor: { @@ -161,17 +122,9 @@ describe('authentications elasticsearch_adapter', () => { _id: 'id-123', failures: 10, successes: 20, - latest: '', - host: { - id: 'host-id-1', - name: 'host-name-1', - }, user: { name: 'Evan', }, - source: { - ip: '', - }, }, }; diff --git a/x-pack/plugins/secops/server/lib/authentications/elasticsearch_adapter.ts b/x-pack/plugins/secops/server/lib/authentications/elasticsearch_adapter.ts index 320a5fa2bdf0a..f7c92854c28bf 100644 --- a/x-pack/plugins/secops/server/lib/authentications/elasticsearch_adapter.ts +++ b/x-pack/plugins/secops/server/lib/authentications/elasticsearch_adapter.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get, getOr, head, last } from 'lodash/fp'; - +import { getOr } from 'lodash/fp'; import { AuthenticationsData, AuthenticationsEdges } from '../../graphql/types'; import { mergeFieldsWithHit } from '../../utils/build_query'; import { FrameworkAdapter, FrameworkRequest, RequestOptions } from '../framework'; @@ -30,16 +29,19 @@ export class ElasticsearchAuthenticationAdapter implements AuthenticationsAdapte 'search', buildQuery(options) ); - const { limit } = options.pagination; + const { cursor, limit } = options.pagination; const totalCount = getOr(0, 'aggregations.user_count.value', response); - const hits: AuthenticationHit[] = getOr( [], 'aggregations.group_by_users.buckets', response ).map((bucket: AuthenticationBucket) => ({ - ...head(bucket.authentication.hits.hits), - user: bucket.key.user_uid, + _id: bucket.authentication.hits.hits[0]._id, + _source: { + lastSuccess: getOr(null, 'successes.lastSuccess.hits.hits[0]._source', bucket), + lastFailure: getOr(null, 'failures.lastFailure.hits.hits[0]._source', bucket), + }, + user: bucket.key, cursor: bucket.key.user_uid, failures: bucket.failures.doc_count, successes: bucket.successes.doc_count, @@ -50,14 +52,17 @@ export class ElasticsearchAuthenticationAdapter implements AuthenticationsAdapte ); const hasNextPage = authenticationEdges.length === limit + 1; - const edges = hasNextPage ? authenticationEdges.splice(0, limit) : authenticationEdges; - const lastCursor = get('cursor', last(edges)); + const beginning = cursor != null ? parseInt(cursor!, 10) : 0; + const edges = authenticationEdges.splice(beginning, limit); return { edges, totalCount, pageInfo: { hasNextPage, - endCursor: lastCursor, + endCursor: { + value: String(limit), + tiebreaker: null, + }, }, }; } @@ -76,12 +81,6 @@ export const formatAuthenticationData = ( user: { name: '', }, - source: { ip: '' }, - latest: '', - host: { - id: '', - name: '', - }, }, cursor: { value: '', diff --git a/x-pack/plugins/secops/server/lib/authentications/query.dsl.ts b/x-pack/plugins/secops/server/lib/authentications/query.dsl.ts index caf27e9d01720..a1d812763cceb 100644 --- a/x-pack/plugins/secops/server/lib/authentications/query.dsl.ts +++ b/x-pack/plugins/secops/server/lib/authentications/query.dsl.ts @@ -4,24 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ -import { merge } from 'lodash/fp'; import { createQueryFilterClauses } from '../../utils/build_query'; import { reduceFields } from '../../utils/build_query/reduce_fields'; import { hostFieldsMap, sourceFieldsMap } from '../ecs_fields'; +import { extendMap } from '../ecs_fields/extend_map'; import { RequestOptions } from '../framework'; import { FilterQuery } from '../types'; export const auditdFieldsMap: Readonly> = { latest: '@timestamp', - ...{ ...sourceFieldsMap }, - ...{ ...hostFieldsMap }, + 'lastSuccess.timestamp': 'lastSuccess.@timestamp', + 'lastFailure.timestamp': 'lastFailure.@timestamp', + ...{ ...extendMap('lastSuccess', sourceFieldsMap) }, + ...{ ...extendMap('lastSuccess', hostFieldsMap) }, + ...{ ...extendMap('lastFailure', sourceFieldsMap) }, + ...{ ...extendMap('lastFailure', hostFieldsMap) }, }; export const buildQuery = (options: RequestOptions) => { const { to, from } = options.timerange; - const { limit, cursor } = options.pagination; + const { limit } = options.pagination; const { fields, filterQuery } = options; - const esFields = reduceFields(fields, auditdFieldsMap); + const esFields = reduceFields(fields, { ...hostFieldsMap, ...sourceFieldsMap }); const filter = [ ...createQueryFilterClauses(filterQuery as FilterQuery), @@ -54,9 +58,10 @@ export const buildQuery = (options: RequestOptions) => { aggregations: { ...agg, group_by_users: { - composite: { + terms: { size: limit + 1, - sources: [{ user_uid: { terms: { field: 'auditd.data.acct' } } }], + field: 'auditd.data.acct', + order: { 'failures.doc_count': 'desc' }, }, aggs: { failures: { @@ -65,6 +70,15 @@ export const buildQuery = (options: RequestOptions) => { 'auditd.result': 'fail', }, }, + aggs: { + lastFailure: { + top_hits: { + size: 1, + _source: esFields, + sort: [{ '@timestamp': { order: 'desc' } }], + }, + }, + }, }, successes: { filter: { @@ -72,6 +86,15 @@ export const buildQuery = (options: RequestOptions) => { 'auditd.result': 'success', }, }, + aggs: { + lastSuccess: { + top_hits: { + size: 1, + _source: esFields, + sort: [{ '@timestamp': { order: 'desc' } }], + }, + }, + }, }, authentication: { top_hits: { @@ -93,20 +116,5 @@ export const buildQuery = (options: RequestOptions) => { track_total_hits: false, }; - if (cursor) { - return merge(dslQuery, { - body: { - aggregations: { - group_by_users: { - composite: { - after: { - user_uid: cursor, - }, - }, - }, - }, - }, - }); - } return dslQuery; }; diff --git a/x-pack/plugins/secops/server/lib/authentications/types.ts b/x-pack/plugins/secops/server/lib/authentications/types.ts index 22e43bafe37a4..f120bcdfbf506 100644 --- a/x-pack/plugins/secops/server/lib/authentications/types.ts +++ b/x-pack/plugins/secops/server/lib/authentications/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AuthenticationsData } from '../../graphql/types'; +import { AuthenticationsData, LastSourceHost } from '../../graphql/types'; import { FrameworkRequest, RequestOptions } from '../framework'; import { Hit, SearchHit, TotalHit } from '../types'; @@ -16,14 +16,8 @@ type StringOrNumber = string | number; export interface AuthenticationHit extends Hit { _source: { '@timestamp': string; - source: { - ip: string; - }; - host: { - id: string; - ip: string; - name: string; - }; + lastSuccess?: LastSourceHost; + lastFailure?: LastSourceHost; }; user: string; failures: number; diff --git a/x-pack/plugins/secops/server/lib/ecs_fields/extend_map.test.ts b/x-pack/plugins/secops/server/lib/ecs_fields/extend_map.test.ts new file mode 100644 index 0000000000000..9ba22e83b4b4d --- /dev/null +++ b/x-pack/plugins/secops/server/lib/ecs_fields/extend_map.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { extendMap } from './extend_map'; + +describe('ecs_fields test', () => { + describe('extendMap', () => { + test('it should extend a record', () => { + const osFieldsMap: Readonly> = { + 'os.platform': 'os.platform', + 'os.full': 'os.full', + 'os.family': 'os.family', + 'os.version': 'os.version', + 'os.kernel': 'os.kernel', + }; + const expected: Record = { + 'host.os.family': 'host.os.family', + 'host.os.full': 'host.os.full', + 'host.os.kernel': 'host.os.kernel', + 'host.os.platform': 'host.os.platform', + 'host.os.version': 'host.os.version', + }; + expect(extendMap('host', osFieldsMap)).toEqual(expected); + }); + + test('it should extend a sample hosts record', () => { + const hostMap: Record = { + 'host.id': 'host.id', + 'host.ip': 'host.ip', + 'host.name': 'host.name', + }; + const osFieldsMap: Readonly> = { + 'os.platform': 'os.platform', + 'os.full': 'os.full', + 'os.family': 'os.family', + 'os.version': 'os.version', + 'os.kernel': 'os.kernel', + }; + const expected: Record = { + 'host.id': 'host.id', + 'host.ip': 'host.ip', + 'host.name': 'host.name', + 'host.os.family': 'host.os.family', + 'host.os.full': 'host.os.full', + 'host.os.kernel': 'host.os.kernel', + 'host.os.platform': 'host.os.platform', + 'host.os.version': 'host.os.version', + }; + const output = { ...hostMap, ...extendMap('host', osFieldsMap) }; + expect(output).toEqual(expected); + }); + }); +}); diff --git a/x-pack/plugins/secops/server/lib/ecs_fields/extend_map.ts b/x-pack/plugins/secops/server/lib/ecs_fields/extend_map.ts new file mode 100644 index 0000000000000..f21066819caa5 --- /dev/null +++ b/x-pack/plugins/secops/server/lib/ecs_fields/extend_map.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const extendMap = ( + path: string, + map: Readonly> +): Readonly> => { + const init: Record = {}; + return Object.entries(map).reduce((accum, [key, value]) => { + accum[`${path}.${key}`] = `${path}.${value}`; + return accum; + }, init); +}; diff --git a/x-pack/plugins/secops/server/lib/ecs_fields/index.ts b/x-pack/plugins/secops/server/lib/ecs_fields/index.ts index e5cc54d26c7bf..0c341b854effb 100644 --- a/x-pack/plugins/secops/server/lib/ecs_fields/index.ts +++ b/x-pack/plugins/secops/server/lib/ecs_fields/index.ts @@ -4,16 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ +import { extendMap } from './extend_map'; + +export const osFieldsMap: Readonly> = { + 'os.platform': 'os.platform', + 'os.name': 'os.name', + 'os.full': 'os.full', + 'os.family': 'os.family', + 'os.version': 'os.version', + 'os.kernel': 'os.kernel', +}; + export const hostFieldsMap: Readonly> = { 'host.id': 'host.id', 'host.ip': 'host.ip', 'host.name': 'host.name', - 'host.os.platform': 'host.os.platform', - 'host.os.name': 'host.os.name', - 'host.os.full': 'host.os.full', - 'host.os.family': 'host.os.family', - 'host.os.version': 'host.os.version', - 'host.os.kernel': 'host.os.kernel', + ...extendMap('host', osFieldsMap), }; export const processFieldsMap: Readonly> = { diff --git a/x-pack/plugins/secops/server/lib/hosts/types.ts b/x-pack/plugins/secops/server/lib/hosts/types.ts index 14b48aa76137e..54eb1847246bd 100644 --- a/x-pack/plugins/secops/server/lib/hosts/types.ts +++ b/x-pack/plugins/secops/server/lib/hosts/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { HostsData } from '../../graphql/types'; +import { HostEcsFields, HostsData } from '../../graphql/types'; import { FrameworkRequest, RequestOptions } from '../framework'; import { Hit, Hits, SearchHit } from '../types'; @@ -17,14 +17,7 @@ type StringOrNumber = string | number; export interface HostHit extends Hit { _source: { '@timestamp'?: string; - host: { - id?: string; - name: string; - os?: { - name: string; - version: string; - }; - }; + host: HostEcsFields; }; cursor?: string; sort?: StringOrNumber[]; diff --git a/x-pack/plugins/secops/server/lib/uncommon_processes/elasticsearch_adapter.ts b/x-pack/plugins/secops/server/lib/uncommon_processes/elasticsearch_adapter.ts index c47298de47b66..34cc9ff3e4768 100644 --- a/x-pack/plugins/secops/server/lib/uncommon_processes/elasticsearch_adapter.ts +++ b/x-pack/plugins/secops/server/lib/uncommon_processes/elasticsearch_adapter.ts @@ -30,7 +30,7 @@ export class ElasticsearchUncommonProcessesAdapter implements UncommonProcessesA 'search', buildQuery(options) ); - const { limit } = options.pagination; + const { cursor, limit } = options.pagination; const totalCount = getOr(0, 'aggregations.process_count.value', response); const buckets = getOr([], 'aggregations.group_by_process.buckets', response); const hits = getHits(buckets); @@ -38,13 +38,18 @@ export class ElasticsearchUncommonProcessesAdapter implements UncommonProcessesA const uncommonProcessesEdges = hits.map(hit => formatUncommonProcessesData(options.fields, hit, processFieldsMap) ); - const edges = uncommonProcessesEdges.splice(0, limit); + const hasNextPage = uncommonProcessesEdges.length === limit + 1; + const beginning = cursor != null ? parseInt(cursor!, 10) : 0; + const edges = uncommonProcessesEdges.splice(beginning, limit); return { edges, totalCount, pageInfo: { - hasNextPage: null, - endCursor: { value: '' }, + hasNextPage, + endCursor: { + value: String(limit), + tiebreaker: null, + }, }, }; } @@ -68,7 +73,7 @@ export const getHits = ( export const getHosts = (buckets: ReadonlyArray<{ key: string; host: HostHits }>) => buckets.map(bucket => ({ id: bucket.key, - name: bucket.host.hits.hits[0]._source.host.name, + name: bucket.host.hits.hits[0]._source.host.name!, })); export const formatUncommonProcessesData = ( diff --git a/x-pack/plugins/secops/server/lib/uncommon_processes/query.dsl.ts b/x-pack/plugins/secops/server/lib/uncommon_processes/query.dsl.ts index dd93e1612b382..469a25459417c 100644 --- a/x-pack/plugins/secops/server/lib/uncommon_processes/query.dsl.ts +++ b/x-pack/plugins/secops/server/lib/uncommon_processes/query.dsl.ts @@ -45,7 +45,7 @@ export const buildQuery = (options: RequestOptions) => { ...agg, group_by_process: { terms: { - size: limit, + size: limit + 1, field: 'process.name', order: [ { diff --git a/x-pack/plugins/secops/server/lib/uncommon_processes/types.ts b/x-pack/plugins/secops/server/lib/uncommon_processes/types.ts index b5e341421f670..694681488ae5d 100644 --- a/x-pack/plugins/secops/server/lib/uncommon_processes/types.ts +++ b/x-pack/plugins/secops/server/lib/uncommon_processes/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UncommonProcessesData } from '../../graphql/types'; +import { ProcessEcsFields, UncommonProcessesData } from '../../graphql/types'; import { FrameworkRequest, RequestOptions } from '../framework'; import { Hit, Hits, HostHits, SearchHit, TotalHit } from '../types'; @@ -21,10 +21,7 @@ export interface UncommonProcessHit extends Hit { host: Array<{ id: string; name: string }>; _source: { '@timestamp': string; - process: { - name: string; - title: string; - }; + process: ProcessEcsFields; }; cursor: string; sort: StringOrNumber[]; diff --git a/x-pack/test/api_integration/apis/secops/authentications.ts b/x-pack/test/api_integration/apis/secops/authentications.ts index ff7287e2c735f..5121527d9cabf 100644 --- a/x-pack/test/api_integration/apis/secops/authentications.ts +++ b/x-pack/test/api_integration/apis/secops/authentications.ts @@ -65,7 +65,7 @@ const authenticationsTests: KbnTestProvider = ({ getService }) => { expect(authorizations.edges.length).to.be(1); expect(authorizations.totalCount).to.be(2); - expect(authorizations.edges[0]!.node.host.name).to.be('siem-kibana'); + expect(authorizations.edges[0]!.node.lastFailure!.host!.name).to.be('siem-kibana'); }); }); }); diff --git a/x-pack/test/api_integration/apis/secops/index.js b/x-pack/test/api_integration/apis/secops/index.js index 8c5e8ba07f818..3d47e68dced0e 100644 --- a/x-pack/test/api_integration/apis/secops/index.js +++ b/x-pack/test/api_integration/apis/secops/index.js @@ -6,7 +6,7 @@ export default function ({ loadTestFile }) { describe('SecOps GraphQL Endpoints', () => { - loadTestFile(require.resolve('./authorizations')); + loadTestFile(require.resolve('./authentications')); loadTestFile(require.resolve('./events')); loadTestFile(require.resolve('./hosts')); loadTestFile(require.resolve('./kpi_events')); From 308d536251247bedb67d3e3bc08baa6a8cb37148 Mon Sep 17 00:00:00 2001 From: FrankHassanabad Date: Sun, 27 Jan 2019 21:25:45 -0700 Subject: [PATCH 2/4] Updated from more feedback and fixed more types and other misc issues --- .../components/drag_and_drop/helpers.test.ts | 13 + .../components/drag_and_drop/helpers.ts | 2 + .../components/empty_page/index.test.tsx | 2 +- .../empty_value/empty_value.test.tsx | 120 ++++++++- .../public/components/empty_value/index.tsx | 7 + .../public/components/flyout/index.test.tsx | 5 +- .../secops/public/components/flyout/index.tsx | 3 +- .../components/flyout/resize_handle.tsx | 27 +-- .../components/load_more_table/index.mock.tsx | 17 +- .../components/load_more_table/index.test.tsx | 36 +-- .../hosts/authentications_table/index.tsx | 229 ++++++++++++++---- .../page/hosts/events_table/index.tsx | 47 ++-- .../page/hosts/hosts_table/index.tsx | 41 ++-- .../hosts/uncommon_process_table/index.tsx | 57 +++-- .../components/range_date_picker/index.tsx | 18 +- .../body/renderers/plain_column_renderer.tsx | 4 +- .../renderers/suricata_column_renderer.tsx | 8 +- .../renderers/unknown_column_renderer.tsx | 6 +- .../public/containers/global_time/index.tsx | 5 +- .../public/containers/timeline/index.tsx | 3 +- .../secops/public/store/local/inputs/epic.ts | 4 +- .../public/store/local/inputs/reducer.ts | 2 +- 22 files changed, 451 insertions(+), 205 deletions(-) diff --git a/x-pack/plugins/secops/public/components/drag_and_drop/helpers.test.ts b/x-pack/plugins/secops/public/components/drag_and_drop/helpers.test.ts index b331494a78754..9d96519da1901 100644 --- a/x-pack/plugins/secops/public/components/drag_and_drop/helpers.test.ts +++ b/x-pack/plugins/secops/public/components/drag_and_drop/helpers.test.ts @@ -13,6 +13,7 @@ import { droppableIdPrefix, droppableTimelineFlyoutButtonPrefix, droppableTimelineProvidersPrefix, + escapeDataProviderId, getDraggableId, getDroppableId, getProviderIdFromDraggable, @@ -444,4 +445,16 @@ describe('helpers', () => { ).toEqual(false); }); }); + + describe('#escapeDataProviderId', () => { + test('it should escape dotted notation', () => { + const escaped = escapeDataProviderId('hello.how.are.you'); + expect(escaped).toEqual('hello_how_are_you'); + }); + + test('it should not escape a string without dotted notation', () => { + const escaped = escapeDataProviderId('hello how are you?'); + expect(escaped).toEqual('hello how are you?'); + }); + }); }); diff --git a/x-pack/plugins/secops/public/components/drag_and_drop/helpers.ts b/x-pack/plugins/secops/public/components/drag_and_drop/helpers.ts index eca47a5297211..00f086c729df0 100644 --- a/x-pack/plugins/secops/public/components/drag_and_drop/helpers.ts +++ b/x-pack/plugins/secops/public/components/drag_and_drop/helpers.ts @@ -56,6 +56,8 @@ export const getTimelineIdFromDestination = (result: DropResult): string => export const getProviderIdFromDraggable = (result: DropResult): string => result.draggableId.substring(result.draggableId.lastIndexOf('.') + 1); +export const escapeDataProviderId = (path: string) => path.replace(/\./g, '_'); + export const providerWasDroppedOnTimeline = (result: DropResult): boolean => reasonIsDrop(result) && draggableIsContent(result) && diff --git a/x-pack/plugins/secops/public/components/empty_page/index.test.tsx b/x-pack/plugins/secops/public/components/empty_page/index.test.tsx index bfaa53c390745..37f3958ec53bd 100644 --- a/x-pack/plugins/secops/public/components/empty_page/index.test.tsx +++ b/x-pack/plugins/secops/public/components/empty_page/index.test.tsx @@ -9,7 +9,7 @@ import toJson from 'enzyme-to-json'; import React from 'react'; import { EmptyPage } from './index'; -it('renders correctly', () => { +test('renders correctly', () => { const EmptyComponent = shallow( { describe('#getEmptyValue', () => - it('should return an empty value', () => expect(getEmptyValue()).toBe('--'))); + test('should return an empty value', () => expect(getEmptyValue()).toBe('--'))); - describe('#getOr', () => { - it('should default empty value when a deep rooted value is null', () => { + describe('#getEmptyTagValue', () => { + const wrapper = mount(

{getEmptyTagValue()}

); + test('should return an empty tag value', () => expect(wrapper.text()).toBe('--')); + }); + + describe('#getOrEmpty', () => { + test('should default empty value when a deep rooted value is null', () => { const test = { a: { b: { @@ -22,7 +36,7 @@ describe('EmptyValue', () => { expect(getOrEmpty('a.b.c', test)).toBe(getEmptyValue()); }); - it('should default empty value when a deep rooted value is undefined', () => { + test('should default empty value when a deep rooted value is undefined', () => { const test = { a: { b: { @@ -33,7 +47,7 @@ describe('EmptyValue', () => { expect(getOrEmpty('a.b.c', test)).toBe(getEmptyValue()); }); - it('should default empty value when a deep rooted value is missing', () => { + test('should default empty value when a deep rooted value is missing', () => { const test = { a: { b: {}, @@ -42,7 +56,7 @@ describe('EmptyValue', () => { expect(getOrEmpty('a.b.c', test)).toBe(getEmptyValue()); }); - it('should return a deep path value', () => { + test('should return a deep path value', () => { const test = { a: { b: { @@ -54,8 +68,56 @@ describe('EmptyValue', () => { }); }); + describe('#getOrEmptyTag', () => { + test('should default empty value when a deep rooted value is null', () => { + const test = { + a: { + b: { + c: null, + }, + }, + }; + const wrapper = mount(

{getOrEmptyTag('a.b.c', test)}

); + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('should default empty value when a deep rooted value is undefined', () => { + const test = { + a: { + b: { + c: undefined, + }, + }, + }; + const wrapper = mount(

{getOrEmptyTag('a.b.c', test)}

); + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('should default empty value when a deep rooted value is missing', () => { + const test = { + a: { + b: {}, + }, + }; + const wrapper = mount(

{getOrEmptyTag('a.b.c', test)}

); + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('should return a deep path value', () => { + const test = { + a: { + b: { + c: 1, + }, + }, + }; + const wrapper = mount(

{getOrEmptyTag('a.b.c', test)}

); + expect(wrapper.text()).toBe('1'); + }); + }); + describe('#defaultToEmpty', () => { - it('should default to an empty value when a deep rooted value is null', () => { + test('should default to an empty value when a deep rooted value is null', () => { const test = { a: { b: { @@ -66,7 +128,7 @@ describe('EmptyValue', () => { expect(defaultToEmpty(test.a.b.c)).toBe(getEmptyValue()); }); - it('should default to an empty value when a deep rooted value is undefined', () => { + test('should default to an empty value when a deep rooted value is undefined', () => { const test = { a: { b: { @@ -77,7 +139,7 @@ describe('EmptyValue', () => { expect(defaultToEmpty(test.a.b.c)).toBe(getEmptyValue()); }); - it('should return a deep path value', () => { + test('should return a deep path value', () => { const test = { a: { b: { @@ -88,4 +150,42 @@ describe('EmptyValue', () => { expect(defaultToEmpty(test.a.b.c)).toBe(1); }); }); + + describe('#defaultToEmptyTag', () => { + test('should default to an empty value when a deep rooted value is null', () => { + const test = { + a: { + b: { + c: null, + }, + }, + }; + const wrapper = mount(

{defaultToEmptyTag(test.a.b.c)}

); + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('should default to an empty value when a deep rooted value is undefined', () => { + const test = { + a: { + b: { + c: undefined, + }, + }, + }; + const wrapper = mount(

{defaultToEmptyTag(test.a.b.c)}

); + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('should return a deep path value', () => { + const test = { + a: { + b: { + c: 1, + }, + }, + }; + const wrapper = mount(

{defaultToEmptyTag(test.a.b.c)}

); + expect(wrapper.text()).toBe('1'); + }); + }); }); diff --git a/x-pack/plugins/secops/public/components/empty_value/index.tsx b/x-pack/plugins/secops/public/components/empty_value/index.tsx index f6e11d4283c60..2228da7160300 100644 --- a/x-pack/plugins/secops/public/components/empty_value/index.tsx +++ b/x-pack/plugins/secops/public/components/empty_value/index.tsx @@ -5,10 +5,17 @@ */ import { defaultTo, get } from 'lodash/fp'; +import React from 'react'; + +export const getEmptyTagValue = () => <>{getEmptyValue()}; export const getEmptyValue = () => '--'; +export const getOrEmptyTag = (path: string, item: unknown) => <>{getOrEmpty(path, item)}; + export const getOrEmpty = (path: string, item: unknown) => get(path, item) != null ? get(path, item) : getEmptyValue(); +export const defaultToEmptyTag = (item: T) => <>{defaultToEmpty(item)}; + export const defaultToEmpty = (item: T) => defaultTo(getEmptyValue(), item); diff --git a/x-pack/plugins/secops/public/components/flyout/index.test.tsx b/x-pack/plugins/secops/public/components/flyout/index.test.tsx index 896f83c36fdd2..9c57cebefc379 100644 --- a/x-pack/plugins/secops/public/components/flyout/index.test.tsx +++ b/x-pack/plugins/secops/public/components/flyout/index.test.tsx @@ -11,6 +11,7 @@ import { Provider as ReduxStoreProvider } from 'react-redux'; import { ThemeProvider } from 'styled-components'; import * as euiVars from '@elastic/eui/dist/eui_theme_k6_light.json'; +import { ActionCreator } from 'typescript-fsa'; import { Flyout, FlyoutComponent, flyoutHeaderHeight } from '.'; import { mockGlobalState } from '../../mock'; import { createStore, State } from '../../store'; @@ -204,7 +205,7 @@ describe('Flyout', () => { }); test('should call the onOpen when the mouse is clicked for rendering', () => { - const showTimeline = jest.fn(); + const showTimeline = (jest.fn() as unknown) as ActionCreator<{ id: string; show: boolean }>; const wrapper = mount( @@ -234,7 +235,7 @@ describe('Flyout', () => { const stateShowIsTrue = set('local.timeline.timelineById.test.show', true, state); const storeShowIsTrue = createStore(stateShowIsTrue); - const showTimeline = jest.fn(); + const showTimeline = (jest.fn() as unknown) as ActionCreator<{ id: string; show: boolean }>; const wrapper = mount( diff --git a/x-pack/plugins/secops/public/components/flyout/index.tsx b/x-pack/plugins/secops/public/components/flyout/index.tsx index 1708f03f1e497..62c546391aa47 100644 --- a/x-pack/plugins/secops/public/components/flyout/index.tsx +++ b/x-pack/plugins/secops/public/components/flyout/index.tsx @@ -11,6 +11,7 @@ import { connect } from 'react-redux'; import { pure } from 'recompose'; import styled from 'styled-components'; +import { ActionCreator } from 'typescript-fsa'; import { State, timelineActions } from '../../store'; import { themeSelector } from '../../store/local/app'; import { Theme } from '../../store/local/app/model'; @@ -44,7 +45,7 @@ interface OwnProps { } interface DispatchProps { - showTimeline?: ({ id, show }: { id: string; show: boolean }) => void; + showTimeline?: ActionCreator<{ id: string; show: boolean }>; applyDeltaToWidth?: ( { id, diff --git a/x-pack/plugins/secops/public/components/flyout/resize_handle.tsx b/x-pack/plugins/secops/public/components/flyout/resize_handle.tsx index e759b8cca31c0..c3f4c3a268051 100644 --- a/x-pack/plugins/secops/public/components/flyout/resize_handle.tsx +++ b/x-pack/plugins/secops/public/components/flyout/resize_handle.tsx @@ -6,10 +6,11 @@ import * as React from 'react'; import { connect } from 'react-redux'; -import styled from 'styled-components'; - import { fromEvent, Observable } from 'rxjs'; import { mergeMap, takeUntil } from 'rxjs/operators'; +import styled from 'styled-components'; +import { ActionCreator } from 'typescript-fsa'; + import { timelineActions } from '../../store'; interface OwnProps { @@ -18,21 +19,13 @@ interface OwnProps { } interface DispatchProps { - applyDeltaToWidth: ( - { - id, - delta, - bodyClientWidthPixels, - maxWidthPercent, - minWidthPixels, - }: { - id: string; - delta: number; - bodyClientWidthPixels: number; - maxWidthPercent: number; - minWidthPixels: number; - } - ) => void; + applyDeltaToWidth: ActionCreator<{ + id: string; + delta: number; + bodyClientWidthPixels: number; + maxWidthPercent: number; + minWidthPixels: number; + }>; } type Props = OwnProps & DispatchProps; diff --git a/x-pack/plugins/secops/public/components/load_more_table/index.mock.tsx b/x-pack/plugins/secops/public/components/load_more_table/index.mock.tsx index dc3b713c032ab..48ef3ecd56098 100644 --- a/x-pack/plugins/secops/public/components/load_more_table/index.mock.tsx +++ b/x-pack/plugins/secops/public/components/load_more_table/index.mock.tsx @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; -import { HostItem } from '../../graphql/types'; -import { getOrEmpty } from '../empty_value'; -import { ItemsPerRow } from './index'; +import { HostsEdges } from '../../graphql/types'; +import { getOrEmptyTag } from '../empty_value'; +import { Columns, ItemsPerRow } from './index'; export const mockData = { Hosts: { @@ -47,30 +46,30 @@ export const mockData = { }, }; -export const getHostsColumns = () => [ +export const getHostsColumns = (): Array> => [ { name: 'Host', truncateText: false, hideForMobile: false, - render: (item: HostItem) => <>{getOrEmpty('host.name', item)}, + render: node => getOrEmptyTag('host.name', node), }, { name: 'First seen', truncateText: false, hideForMobile: false, - render: (item: HostItem) => <>{getOrEmpty('host.firstSeen', item)}, + render: node => getOrEmptyTag('host.firstSeen', node), }, { name: 'OS', truncateText: false, hideForMobile: false, - render: (item: HostItem) => <>{getOrEmpty('host.os', item)}, + render: node => getOrEmptyTag('host.os', node), }, { name: 'Version', truncateText: false, hideForMobile: false, - render: (item: HostItem) => <>{getOrEmpty('host.version', item)}, + render: node => getOrEmptyTag('host.version', node), }, ]; diff --git a/x-pack/plugins/secops/public/components/load_more_table/index.test.tsx b/x-pack/plugins/secops/public/components/load_more_table/index.test.tsx index dbf339b6ca487..8d089aa456732 100644 --- a/x-pack/plugins/secops/public/components/load_more_table/index.test.tsx +++ b/x-pack/plugins/secops/public/components/load_more_table/index.test.tsx @@ -26,9 +26,7 @@ describe('Load More Table Component', () => { limit={1} hasNextPage={mockData.Hosts.pageInfo.hasNextPage!} itemsPerRow={rowItems} - updateLimitPagination={newlimit => { - updateLimitPagination({ limit: newlimit }); - }} + updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })} title={

Hosts

} /> ); @@ -47,9 +45,7 @@ describe('Load More Table Component', () => { limit={1} hasNextPage={mockData.Hosts.pageInfo.hasNextPage!} itemsPerRow={rowItems} - updateLimitPagination={newlimit => { - updateLimitPagination({ limit: newlimit }); - }} + updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })} title={

Hosts

} /> ); @@ -68,9 +64,7 @@ describe('Load More Table Component', () => { limit={1} hasNextPage={mockData.Hosts.pageInfo.hasNextPage!} itemsPerRow={rowItems} - updateLimitPagination={newlimit => { - updateLimitPagination({ limit: newlimit }); - }} + updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })} title={

Hosts

} /> ); @@ -94,9 +88,7 @@ describe('Load More Table Component', () => { limit={1} hasNextPage={mockData.Hosts.pageInfo.hasNextPage!} itemsPerRow={rowItems} - updateLimitPagination={newlimit => { - updateLimitPagination({ limit: newlimit }); - }} + updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })} title={

Hosts

} /> ); @@ -121,9 +113,7 @@ describe('Load More Table Component', () => { limit={2} hasNextPage={false} itemsPerRow={rowItems} - updateLimitPagination={newlimit => { - updateLimitPagination({ limit: newlimit }); - }} + updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })} title={

Hosts

} /> ); @@ -142,9 +132,7 @@ describe('Load More Table Component', () => { limit={2} hasNextPage={true} itemsPerRow={rowItems} - updateLimitPagination={newlimit => { - updateLimitPagination({ limit: newlimit }); - }} + updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })} title={

Hosts

} /> ); @@ -167,9 +155,7 @@ describe('Load More Table Component', () => { limit={2} hasNextPage={true} itemsPerRow={[]} - updateLimitPagination={newlimit => { - updateLimitPagination({ limit: newlimit }); - }} + updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })} title={

Hosts

} /> ); @@ -190,9 +176,7 @@ describe('Load More Table Component', () => { limit={1} hasNextPage={mockData.Hosts.pageInfo.hasNextPage!} itemsPerRow={rowItems} - updateLimitPagination={newlimit => { - updateLimitPagination({ limit: newlimit }); - }} + updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })} title={

Hosts

} /> ); @@ -216,9 +200,7 @@ describe('Load More Table Component', () => { limit={2} hasNextPage={true} itemsPerRow={rowItems} - updateLimitPagination={newlimit => { - updateLimitPagination({ limit: newlimit }); - }} + updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })} title={

Hosts

} /> ); diff --git a/x-pack/plugins/secops/public/components/page/hosts/authentications_table/index.tsx b/x-pack/plugins/secops/public/components/page/hosts/authentications_table/index.tsx index 71cd46f1fa349..cab4055a34490 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/authentications_table/index.tsx +++ b/x-pack/plugins/secops/public/components/page/hosts/authentications_table/index.tsx @@ -6,17 +6,18 @@ import { EuiBadge } from '@elastic/eui'; import { FormattedRelative } from '@kbn/i18n/react'; +import { get, has } from 'lodash/fp'; import React from 'react'; import { connect } from 'react-redux'; import { pure } from 'recompose'; -import { has } from 'lodash/fp'; -import moment from 'moment'; +import { ActionCreator } from 'typescript-fsa'; import { AuthenticationsEdges } from '../../../../graphql/types'; import { authenticationsSelector, hostsActions, State } from '../../../../store'; import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; -import { defaultToEmpty, getEmptyValue, getOrEmpty } from '../../../empty_value'; -import { ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; +import { escapeDataProviderId } from '../../../drag_and_drop/helpers'; +import { defaultToEmptyTag, getEmptyTagValue } from '../../../empty_value'; +import { Columns, ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; import { Provider } from '../../../timeline/data_providers/provider'; import * as i18n from './translations'; @@ -35,7 +36,7 @@ interface AuthenticationTableReduxProps { } interface AuthenticationTableDispatchProps { - updateLimitPagination: (param: { limit: number }) => void; + updateLimitPagination: ActionCreator<{ limit: number }>; } type AuthenticationTableProps = OwnProps & @@ -82,7 +83,7 @@ const AuthenticationTableComponent = pure( limit={limit} hasNextPage={hasNextPage} itemsPerRow={rowItems} - updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })} + updateLimitPagination={newLimit => updateLimitPagination({ limit: newLimit })} title={

{i18n.AUTHENTICATIONS} {totalCount} @@ -101,30 +102,30 @@ export const AuthenticationTable = connect( } )(AuthenticationTableComponent); -const getAuthenticationColumns = (startDate: number) => [ +const getAuthenticationColumns = (startDate: number): Array> => [ { name: i18n.USER, truncateText: false, hideForMobile: false, - render: ({ node }: AuthenticationsEdges) => { - const userName = defaultToEmpty(node.user.name); - return ( - <> + render: ({ node }) => { + const userName: string | null = get('user.name', node); + if (userName != null) { + return ( @@ -137,76 +138,212 @@ const getAuthenticationColumns = (startDate: number) => [ ) } /> - - ); + ); + } else { + return getEmptyTagValue(); + } }, }, { name: i18n.FAILURES, truncateText: false, hideForMobile: false, - render: ({ node }: AuthenticationsEdges) => <>{defaultToEmpty(node.failures)}, + render: ({ node }) => defaultToEmptyTag(node.failures), }, { name: i18n.LAST_FAILED_TIME, truncateText: false, hideForMobile: false, - render: ({ node }: AuthenticationsEdges) => { - return ( - <> - {has('lastFailure.timestamp', node) ? ( - - ) : ( - getEmptyValue() - )} - - ); - }, + render: ({ node }) => + has('lastFailure.timestamp', node) ? ( + + ) : ( + getEmptyTagValue() + ), }, { name: i18n.LAST_FAILED_SOURCE, truncateText: false, hideForMobile: false, - render: ({ node }: AuthenticationsEdges) => <>{getOrEmpty('lastFailure.source.ip', node)}, + render: ({ node }) => { + const sourceIp: string | null = get('lastFailure.source.ip', node); + if (sourceIp != null) { + return ( + + snapshot.isDragging ? ( + + + + ) : ( + sourceIp + ) + } + /> + ); + } else { + return getEmptyTagValue(); + } + }, }, { name: i18n.LAST_FAILED_DESTINATION, truncateText: false, hideForMobile: false, - render: ({ node }: AuthenticationsEdges) => <>{getOrEmpty('lastFailure.host.name', node)}, + render: ({ node }) => { + const hostName: string | null = get('lastFailure.host.name', node); + if (hostName != null) { + return ( + + snapshot.isDragging ? ( + + + + ) : ( + hostName + ) + } + /> + ); + } else { + return getEmptyTagValue(); + } + }, }, { name: i18n.SUCCESSES, truncateText: false, hideForMobile: false, - render: ({ node }: AuthenticationsEdges) => <>{defaultToEmpty(node.successes)}, + render: ({ node }) => defaultToEmptyTag(node.successes), }, { name: i18n.LAST_SUCCESSFUL_TIME, truncateText: false, hideForMobile: false, - render: ({ node }: AuthenticationsEdges) => { - return ( - <> - {has('lastSuccess.timestamp', node) ? ( - - ) : ( - getEmptyValue() - )} - - ); - }, + render: ({ node }) => + has('lastSuccess.timestamp', node) ? ( + + ) : ( + getEmptyTagValue() + ), }, { name: i18n.LAST_SUCCESSFUL_SOURCE, truncateText: false, hideForMobile: false, - render: ({ node }: AuthenticationsEdges) => <>{getOrEmpty('lastSuccess.source.ip', node)}, + render: ({ node }) => { + const sourceIp: string | null = get('lastSuccess.source.ip', node); + if (sourceIp != null) { + return ( + + snapshot.isDragging ? ( + + + + ) : ( + sourceIp + ) + } + /> + ); + } else { + return getEmptyTagValue(); + } + }, }, { name: i18n.LAST_SUCCESSFUL_DESTINATION, truncateText: false, hideForMobile: false, - render: ({ node }: AuthenticationsEdges) => <>{getOrEmpty('lastSuccess.host.name', node)}, + render: ({ node }) => { + const hostName: string | null = get('lastSuccess.host.name', node); + if (hostName != null) { + return ( + + snapshot.isDragging ? ( + + + + ) : ( + hostName + ) + } + /> + ); + } else { + return getEmptyTagValue(); + } + }, }, ]; diff --git a/x-pack/plugins/secops/public/components/page/hosts/events_table/index.tsx b/x-pack/plugins/secops/public/components/page/hosts/events_table/index.tsx index c267f22a5c673..009e6b8b9d2c6 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/events_table/index.tsx +++ b/x-pack/plugins/secops/public/components/page/hosts/events_table/index.tsx @@ -5,17 +5,18 @@ */ import { EuiBadge } from '@elastic/eui'; -import { has } from 'lodash/fp'; -import moment from 'moment'; +import { get, has } from 'lodash/fp'; import React from 'react'; import { connect } from 'react-redux'; import { pure } from 'recompose'; +import { ActionCreator } from 'typescript-fsa'; import { Ecs, EcsEdges } from '../../../../graphql/types'; import { eventsSelector, hostsActions, State } from '../../../../store'; import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; -import { getEmptyValue, getOrEmpty } from '../../../empty_value'; -import { ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; +import { escapeDataProviderId } from '../../../drag_and_drop/helpers'; +import { getEmptyTagValue, getEmptyValue, getOrEmpty, getOrEmptyTag } from '../../../empty_value'; +import { Columns, ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; import { Provider } from '../../../timeline/data_providers/provider'; import * as i18n from './translations'; @@ -35,7 +36,7 @@ interface EventsTableReduxProps { } interface EventsTableDispatchProps { - updateLimitPagination: (param: { limit: number }) => void; + updateLimitPagination: ActionCreator<{ limit: number }>; } type EventsTableProps = OwnProps & EventsTableReduxProps & EventsTableDispatchProps; @@ -63,7 +64,7 @@ const EventsTableComponent = pure( ({ data, hasNextPage, - limit = 5, + limit, loading, loadMore, tiebreaker, @@ -79,11 +80,9 @@ const EventsTableComponent = pure( pageOfItems={data} loadMore={() => loadMore(nextCursor, tiebreaker)} limit={limit} - hasNextPage={hasNextPage!} + hasNextPage={hasNextPage} itemsPerRow={rowItems} - updateLimitPagination={newlimit => { - updateLimitPagination({ limit: newlimit }); - }} + updateLimitPagination={newLimit => updateLimitPagination({ limit: newLimit })} title={

{i18n.EVENTS} {totalCount} @@ -102,21 +101,21 @@ export const EventsTable = connect( } )(EventsTableComponent); -const getEventsColumns = (startDate: number) => [ +const getEventsColumns = (startDate: number): Array> => [ { name: i18n.HOST_NAME, sortable: true, truncateText: false, hideForMobile: false, - render: ({ node }: EcsEdges) => { - const hostName = getOrEmpty('host.name', node); - return ( - <> + render: ({ node }) => { + const hostName: string | null = get('host.name', node); + if (hostName != null) { + return ( [ }, queryDate: { from: startDate, - to: moment().valueOf(), + to: Date.now(), }, }} render={(dataProvider, _, snapshot) => @@ -141,8 +140,10 @@ const getEventsColumns = (startDate: number) => [ ) } /> - - ); + ); + } else { + return getEmptyTagValue(); + } }, }, { @@ -150,12 +151,12 @@ const getEventsColumns = (startDate: number) => [ sortable: true, truncateText: true, hideForMobile: true, - render: ({ node }: EcsEdges) => <>{getOrEmpty('event.type', node)}, + render: ({ node }) => getOrEmptyTag('event.type', node), }, { name: i18n.SOURCE, truncateText: true, - render: ({ node }: EcsEdges) => ( + render: ({ node }) => ( <> {formatSafely('source.ip', node)} : {getOrEmpty('source.port', node)} @@ -165,7 +166,7 @@ const getEventsColumns = (startDate: number) => [ name: i18n.DESTINATION, sortable: true, truncateText: true, - render: ({ node }: EcsEdges) => ( + render: ({ node }) => ( <> {formatSafely('destination.ip', node)} : {getOrEmpty('destination.port', node)} @@ -175,7 +176,7 @@ const getEventsColumns = (startDate: number) => [ name: i18n.LOCATION, sortable: true, truncateText: true, - render: ({ node }: EcsEdges) => ( + render: ({ node }) => ( <> {getOrEmpty('geo.region_name', node)} : {getOrEmpty('geo.country_iso_code', node)} diff --git a/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.tsx b/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.tsx index 325efd8ab7588..ac008a026b67b 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.tsx +++ b/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.tsx @@ -6,16 +6,17 @@ import { EuiBadge, EuiLink } from '@elastic/eui'; import { get, isNil } from 'lodash/fp'; -import moment from 'moment'; import React from 'react'; import { connect } from 'react-redux'; import { pure } from 'recompose'; +import { ActionCreator } from 'typescript-fsa'; import { HostsEdges } from '../../../../graphql/types'; import { hostsActions, hostsSelector, State } from '../../../../store'; import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; -import { defaultToEmpty, getOrEmpty } from '../../../empty_value'; -import { ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; +import { escapeDataProviderId } from '../../../drag_and_drop/helpers'; +import { defaultToEmptyTag, getEmptyTagValue, getOrEmptyTag } from '../../../empty_value'; +import { Columns, ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; import { Provider } from '../../../timeline/data_providers/provider'; import * as i18n from './translations'; interface OwnProps { @@ -32,7 +33,7 @@ interface HostsTableReduxProps { } interface HostsTableDispatchProps { - updateLimitPagination: (param: { limit: number }) => void; + updateLimitPagination: ActionCreator<{ limit: number }>; } type HostsTableProps = OwnProps & HostsTableReduxProps & HostsTableDispatchProps; @@ -78,11 +79,9 @@ const HostsTableComponent = pure( pageOfItems={data} loadMore={() => loadMore(nextCursor)} limit={limit} - hasNextPage={hasNextPage!} + hasNextPage={hasNextPage} itemsPerRow={rowItems} - updateLimitPagination={newlimit => { - updateLimitPagination({ limit: newlimit }); - }} + updateLimitPagination={newLimit => updateLimitPagination({ limit: newLimit })} title={

{i18n.HOSTS} {totalCount} @@ -101,21 +100,21 @@ export const HostsTable = connect( } )(HostsTableComponent); -const getHostsColumns = () => [ +const getHostsColumns = (): Array> => [ { name: i18n.NAME, truncateText: false, hideForMobile: false, render: ({ node }: HostsEdges) => { - const hostName = getOrEmpty('host.name', node); - return ( - <> + const hostName: string | null = get('host.name', node); + if (hostName != null) { + return ( [ value: node.host!.id!, }, queryDate: { - from: moment(node.firstSeen!).valueOf(), - to: moment().valueOf(), + from: new Date(node.firstSeen!).valueOf(), + to: Date.now(), }, }} render={(dataProvider, _, snapshot) => @@ -143,26 +142,28 @@ const getHostsColumns = () => [ ) } /> - - ); + ); + } else { + return getEmptyTagValue(); + } }, }, { name: i18n.FIRST_SEEN, truncateText: false, hideForMobile: false, - render: ({ node }: HostsEdges) => <>{defaultToEmpty(node.firstSeen)}, + render: ({ node }) => defaultToEmptyTag(node.firstSeen), }, { name: i18n.OS, truncateText: false, hideForMobile: false, - render: ({ node }: HostsEdges) => <>{getOrEmpty('host.os.name', node)}, + render: ({ node }) => getOrEmptyTag('host.os.name', node), }, { name: i18n.VERSION, truncateText: false, hideForMobile: false, - render: ({ node }: HostsEdges) => <>{getOrEmpty('host.os.version', node)}, + render: ({ node }) => getOrEmptyTag('host.os.version', node), }, ]; diff --git a/x-pack/plugins/secops/public/components/page/hosts/uncommon_process_table/index.tsx b/x-pack/plugins/secops/public/components/page/hosts/uncommon_process_table/index.tsx index ed0e9c344e939..fbfb9a30b3447 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/uncommon_process_table/index.tsx +++ b/x-pack/plugins/secops/public/components/page/hosts/uncommon_process_table/index.tsx @@ -5,16 +5,23 @@ */ import { EuiBadge } from '@elastic/eui'; -import moment from 'moment'; +import { get } from 'lodash/fp'; import React from 'react'; import { connect } from 'react-redux'; import { pure } from 'recompose'; +import { ActionCreator } from 'typescript-fsa'; import { HostEcsFields, UncommonProcessesEdges } from '../../../../graphql/types'; import { hostsActions, State, uncommonProcessesSelector } from '../../../../store'; import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; -import { defaultToEmpty, getEmptyValue, getOrEmpty } from '../../../empty_value'; -import { ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; +import { escapeDataProviderId } from '../../../drag_and_drop/helpers'; +import { + defaultToEmptyTag, + getEmptyTagValue, + getEmptyValue, + getOrEmptyTag, +} from '../../../empty_value'; +import { Columns, ItemsPerRow, LoadMoreTable } from '../../../load_more_table'; import { Provider } from '../../../timeline/data_providers/provider'; import * as i18n from './translations'; @@ -33,7 +40,7 @@ interface UncommonProcessTableReduxProps { } interface UncommonProcessTableDispatchProps { - updateLimitPagination: (param: { limit: number }) => void; + updateLimitPagination: ActionCreator<{ limit: number }>; } type UncommonProcessTableProps = OwnProps & @@ -80,7 +87,7 @@ const UncommonProcessTableComponent = pure( limit={limit} hasNextPage={hasNextPage} itemsPerRow={rowItems} - updateLimitPagination={newlimit => updateLimitPagination({ limit: newlimit })} + updateLimitPagination={newLimit => updateLimitPagination({ limit: newLimit })} title={

{i18n.UNCOMMON_PROCESSES} {totalCount} @@ -101,30 +108,32 @@ export const UncommonProcessTable = connect( const extractHostNames = (hosts: HostEcsFields[]) => hosts.map(host => host.name).join(', '); -const getUncommonColumns = (startDate: number) => [ +const getUncommonColumns = (startDate: number): Array> => [ { name: i18n.NAME, truncateText: false, hideForMobile: false, - render: ({ node }: UncommonProcessesEdges) => { - const processName = defaultToEmpty(node.process.name); - return ( - <> + render: ({ node }) => { + const processName: string | null = get('process.name', node); + if (processName != null) { + return ( @@ -137,42 +146,40 @@ const getUncommonColumns = (startDate: number) => [ ) } /> - - ); + ); + } else { + return getEmptyTagValue(); + } }, }, { name: i18n.USER, truncateText: false, hideForMobile: false, - render: ({ node }: UncommonProcessesEdges) => <>{getOrEmpty('user.name', node)}, + render: ({ node }) => getOrEmptyTag('user.name', node), }, { name: i18n.COMMAND_LINE, truncateText: false, hideForMobile: false, - render: ({ node }: UncommonProcessesEdges) => <>{defaultToEmpty(node.process.title)}, + render: ({ node }) => defaultToEmptyTag(node.process.title), }, { name: i18n.NUMBER_OF_INSTANCES, truncateText: false, hideForMobile: false, - render: ({ node }: UncommonProcessesEdges) => <>{defaultToEmpty(node.instances)}, + render: ({ node }) => defaultToEmptyTag(node.instances), }, { name: i18n.NUMBER_OF_HOSTS, truncateText: false, hideForMobile: false, - render: ({ node }: UncommonProcessesEdges) => ( - <>{node.host != null ? node.host.length : getEmptyValue()} - ), + render: ({ node }) => <>{node.host != null ? node.host.length : getEmptyValue()}, }, { name: i18n.HOSTS, truncateText: false, hideForMobile: false, - render: ({ node }: UncommonProcessesEdges) => ( - <>{node.host != null ? extractHostNames(node.host) : getEmptyValue()} - ), + render: ({ node }) => <>{node.host != null ? extractHostNames(node.host) : getEmptyValue()}, }, ]; diff --git a/x-pack/plugins/secops/public/components/range_date_picker/index.tsx b/x-pack/plugins/secops/public/components/range_date_picker/index.tsx index 4d208c5cd114b..a1da47ba2bf10 100644 --- a/x-pack/plugins/secops/public/components/range_date_picker/index.tsx +++ b/x-pack/plugins/secops/public/components/range_date_picker/index.tsx @@ -9,6 +9,7 @@ import { get, getOr, has, isEqual } from 'lodash/fp'; import moment, { Moment } from 'moment'; import React from 'react'; import { connect } from 'react-redux'; +import { ActionCreator } from 'typescript-fsa'; import { inputsActions, inputsModel, State } from '../../store'; import { GlobalDateButton } from './global_date_button'; import { QuickSelectPopover } from './quick_select_popover'; @@ -26,13 +27,16 @@ interface RangeDatePickerStateRedux { } interface RangeDatePickerDispatchProps { - setAbsoluteRangeDatePicker: (params: { id: string; from: number; to: number }) => void; - setRelativeRangeDatePicker: ( - params: { id: string; option: string; from: number; to: number } - ) => void; - startAutoReload: (params: { id: string }) => void; - stopAutoReload: (params: { id: string }) => void; - setDuration: (params: { id: string; duration: number }) => void; + setAbsoluteRangeDatePicker: ActionCreator<{ id: string; from: number; to: number }>; + setRelativeRangeDatePicker: ActionCreator<{ + id: string; + option: string; + from: number; + to: number; + }>; + startAutoReload: ActionCreator<{ id: string }>; + stopAutoReload: ActionCreator<{ id: string }>; + setDuration: ActionCreator<{ id: string; duration: number }>; } interface OwnProps { id: string; diff --git a/x-pack/plugins/secops/public/components/timeline/body/renderers/plain_column_renderer.tsx b/x-pack/plugins/secops/public/components/timeline/body/renderers/plain_column_renderer.tsx index d223b366091cc..9a6dc77a55f6e 100644 --- a/x-pack/plugins/secops/public/components/timeline/body/renderers/plain_column_renderer.tsx +++ b/x-pack/plugins/secops/public/components/timeline/body/renderers/plain_column_renderer.tsx @@ -10,7 +10,7 @@ import React from 'react'; import { ColumnRenderer } from '.'; import { Ecs } from '../../../../graphql/types'; -import { getOrEmpty } from '../../../empty_value'; +import { getOrEmptyTag } from '../../../empty_value'; export const dataExistsAtColumn = (columnName: string, data: Ecs): boolean => has(columnName, data); @@ -19,7 +19,7 @@ export const plainColumnRenderer: ColumnRenderer = { renderColumn: (columnName: string, data: Ecs) => { return columnName !== 'timestamp' ? ( - <>{getOrEmpty(columnName, data)} + getOrEmptyTag(columnName, data) ) : ( <>{moment(data!.timestamp!).format()} ); diff --git a/x-pack/plugins/secops/public/components/timeline/body/renderers/suricata_column_renderer.tsx b/x-pack/plugins/secops/public/components/timeline/body/renderers/suricata_column_renderer.tsx index 441bd3816567f..fb2fd1558a0dc 100644 --- a/x-pack/plugins/secops/public/components/timeline/body/renderers/suricata_column_renderer.tsx +++ b/x-pack/plugins/secops/public/components/timeline/body/renderers/suricata_column_renderer.tsx @@ -9,7 +9,7 @@ import React from 'react'; import { ColumnRenderer, getSuricataCVEFromSignature } from '.'; import { Ecs } from '../../../../graphql/types'; -import { getEmptyValue, getOrEmpty } from '../../../empty_value'; +import { getEmptyTagValue, getOrEmptyTag } from '../../../empty_value'; const suricataColumnsOverridden = ['event.id']; @@ -30,16 +30,16 @@ export const suricataColumnRenderer: ColumnRenderer = { renderColumn: (columnName: string, data: Ecs) => { switch (columnName) { case 'event.id': - const signature = get('suricata.eve.alert.signature', data) as string; + const signature: string = get('suricata.eve.alert.signature', data); const cve = getSuricataCVEFromSignature(signature); if (cve != null) { return <>{cve}; } else { - return <>{getOrEmpty('event.id', data)}; + return getOrEmptyTag('event.id', data); } default: // unknown column name - return <>{getEmptyValue()}; + return getEmptyTagValue(); } }, }; diff --git a/x-pack/plugins/secops/public/components/timeline/body/renderers/unknown_column_renderer.tsx b/x-pack/plugins/secops/public/components/timeline/body/renderers/unknown_column_renderer.tsx index df3247c10b457..d05dc9da00a6b 100644 --- a/x-pack/plugins/secops/public/components/timeline/body/renderers/unknown_column_renderer.tsx +++ b/x-pack/plugins/secops/public/components/timeline/body/renderers/unknown_column_renderer.tsx @@ -4,13 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; - import { ColumnRenderer } from '.'; import { Ecs } from '../../../../graphql/types'; -import { getEmptyValue } from '../../../empty_value'; +import { getEmptyTagValue } from '../../../empty_value'; export const unknownColumnRenderer: ColumnRenderer = { isInstance: (columnName: string, data: Ecs) => true, - renderColumn: (columnName: string, data: Ecs) => <>{getEmptyValue()}, + renderColumn: (columnName: string, data: Ecs) => getEmptyTagValue(), }; diff --git a/x-pack/plugins/secops/public/containers/global_time/index.tsx b/x-pack/plugins/secops/public/containers/global_time/index.tsx index e59c99cc63343..79ae64c47c932 100644 --- a/x-pack/plugins/secops/public/containers/global_time/index.tsx +++ b/x-pack/plugins/secops/public/containers/global_time/index.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { pure } from 'recompose'; +import { ActionCreator } from 'typescript-fsa'; import { globalPolicySelector, globalTimeRangeSelector, @@ -19,7 +20,7 @@ interface GlobalTimeArgs { poll: number; from: number; to: number; - setQuery: (params: { id: string; loading: boolean; refetch: inputsModel.Refetch }) => void; + setQuery: ActionCreator<{ id: string; loading: boolean; refetch: inputsModel.Refetch }>; } interface OwnProps { @@ -27,7 +28,7 @@ interface OwnProps { } interface GlobalTimeDispatch { - setQuery: (params: { id: string; loading: boolean; refetch: inputsModel.Refetch }) => void; + setQuery: ActionCreator<{ id: string; loading: boolean; refetch: inputsModel.Refetch }>; } interface GlobalTimeReduxState { diff --git a/x-pack/plugins/secops/public/containers/timeline/index.tsx b/x-pack/plugins/secops/public/containers/timeline/index.tsx index e121b00693639..cf9e3958b1a77 100644 --- a/x-pack/plugins/secops/public/containers/timeline/index.tsx +++ b/x-pack/plugins/secops/public/containers/timeline/index.tsx @@ -5,7 +5,6 @@ */ import { getOr } from 'lodash/fp'; -import moment from 'moment'; import React from 'react'; import { Query } from 'react-apollo'; import { pure } from 'recompose'; @@ -87,7 +86,7 @@ export const TimelineQuery = pure( }; }, }), - updatedAt: moment().valueOf(), + updatedAt: Date.now(), }); }} diff --git a/x-pack/plugins/secops/public/store/local/inputs/epic.ts b/x-pack/plugins/secops/public/store/local/inputs/epic.ts index fe567f8fc5f01..dd2dd10c594cc 100644 --- a/x-pack/plugins/secops/public/store/local/inputs/epic.ts +++ b/x-pack/plugins/secops/public/store/local/inputs/epic.ts @@ -48,7 +48,7 @@ export const createGlobalTimeEpic = (): Epic< return setRelativeRangeDatePicker({ id: 'global', option, - to: moment().valueOf(), + to: Date.now(), from: moment() .subtract(diff, 'ms') .valueOf(), @@ -57,7 +57,7 @@ export const createGlobalTimeEpic = (): Epic< return setRelativeRangeDatePicker({ id: 'global', option, - to: moment().valueOf(), + to: Date.now(), from: timerange.from, }); }), diff --git a/x-pack/plugins/secops/public/store/local/inputs/reducer.ts b/x-pack/plugins/secops/public/store/local/inputs/reducer.ts index 3f23d72f6ecce..41b12a0b493e2 100644 --- a/x-pack/plugins/secops/public/store/local/inputs/reducer.ts +++ b/x-pack/plugins/secops/public/store/local/inputs/reducer.ts @@ -27,7 +27,7 @@ export const initialInputsState: InputsState = { from: moment() .subtract(1, 'day') // subtracts 24 hours from 'now' .valueOf(), - to: moment().valueOf(), + to: Date.now(), }, query: [], policy: { From 78b962c248607ffa735a348306e504d8e3668ba4 Mon Sep 17 00:00:00 2001 From: FrankHassanabad Date: Sun, 27 Jan 2019 22:02:04 -0700 Subject: [PATCH 3/4] Fixed ordering of imports --- .../components/page/hosts/authentications_table/index.tsx | 2 +- .../secops/public/components/page/hosts/hosts_table/index.tsx | 2 +- .../components/page/hosts/uncommon_process_table/index.tsx | 2 +- x-pack/plugins/secops/public/containers/global_time/index.tsx | 1 + .../secops/public/containers/uncommon_processes/index.tsx | 3 +-- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/secops/public/components/page/hosts/authentications_table/index.tsx b/x-pack/plugins/secops/public/components/page/hosts/authentications_table/index.tsx index cab4055a34490..22e5f7ad21c55 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/authentications_table/index.tsx +++ b/x-pack/plugins/secops/public/components/page/hosts/authentications_table/index.tsx @@ -10,8 +10,8 @@ import { get, has } from 'lodash/fp'; import React from 'react'; import { connect } from 'react-redux'; import { pure } from 'recompose'; - import { ActionCreator } from 'typescript-fsa'; + import { AuthenticationsEdges } from '../../../../graphql/types'; import { authenticationsSelector, hostsActions, State } from '../../../../store'; import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; diff --git a/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.tsx b/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.tsx index ac008a026b67b..7d90a0a5bc6a9 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.tsx +++ b/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.tsx @@ -9,8 +9,8 @@ import { get, isNil } from 'lodash/fp'; import React from 'react'; import { connect } from 'react-redux'; import { pure } from 'recompose'; - import { ActionCreator } from 'typescript-fsa'; + import { HostsEdges } from '../../../../graphql/types'; import { hostsActions, hostsSelector, State } from '../../../../store'; import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; diff --git a/x-pack/plugins/secops/public/components/page/hosts/uncommon_process_table/index.tsx b/x-pack/plugins/secops/public/components/page/hosts/uncommon_process_table/index.tsx index fbfb9a30b3447..b3a6431c369ad 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/uncommon_process_table/index.tsx +++ b/x-pack/plugins/secops/public/components/page/hosts/uncommon_process_table/index.tsx @@ -9,8 +9,8 @@ import { get } from 'lodash/fp'; import React from 'react'; import { connect } from 'react-redux'; import { pure } from 'recompose'; - import { ActionCreator } from 'typescript-fsa'; + import { HostEcsFields, UncommonProcessesEdges } from '../../../../graphql/types'; import { hostsActions, State, uncommonProcessesSelector } from '../../../../store'; import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; diff --git a/x-pack/plugins/secops/public/containers/global_time/index.tsx b/x-pack/plugins/secops/public/containers/global_time/index.tsx index 79ae64c47c932..4f747a9ad31e4 100644 --- a/x-pack/plugins/secops/public/containers/global_time/index.tsx +++ b/x-pack/plugins/secops/public/containers/global_time/index.tsx @@ -8,6 +8,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { pure } from 'recompose'; import { ActionCreator } from 'typescript-fsa'; + import { globalPolicySelector, globalTimeRangeSelector, diff --git a/x-pack/plugins/secops/public/containers/uncommon_processes/index.tsx b/x-pack/plugins/secops/public/containers/uncommon_processes/index.tsx index fa11443185086..6f0edee642a8a 100644 --- a/x-pack/plugins/secops/public/containers/uncommon_processes/index.tsx +++ b/x-pack/plugins/secops/public/containers/uncommon_processes/index.tsx @@ -7,11 +7,10 @@ import { getOr } from 'lodash/fp'; import React from 'react'; import { Query } from 'react-apollo'; +import { connect } from 'react-redux'; import { pure } from 'recompose'; import { GetUncommonProcessesQuery, PageInfo, UncommonProcessesEdges } from '../../graphql/types'; - -import { connect } from 'react-redux'; import { inputsModel, State } from '../../store'; import { uncommonProcessesSelector } from '../../store'; import { uncommonProcessesQuery } from './index.gql_query'; From 8b5766ee9dda094cfd88e78ade1af66c4de758c6 Mon Sep 17 00:00:00 2001 From: FrankHassanabad Date: Sun, 27 Jan 2019 22:29:49 -0700 Subject: [PATCH 4/4] Add forgotten type change. --- .../secops/public/components/page/hosts/hosts_table/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.tsx b/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.tsx index 7d90a0a5bc6a9..b56980c16bef9 100644 --- a/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.tsx +++ b/x-pack/plugins/secops/public/components/page/hosts/hosts_table/index.tsx @@ -105,7 +105,7 @@ const getHostsColumns = (): Array> => [ name: i18n.NAME, truncateText: false, hideForMobile: false, - render: ({ node }: HostsEdges) => { + render: ({ node }) => { const hostName: string | null = get('host.name', node); if (hostName != null) { return (