Skip to content

Commit

Permalink
Basic Functionality Alert List (#55800)
Browse files Browse the repository at this point in the history
* sets up initial grid and data type

* data feeds in from backend but doesnt update

* sample data feeding in correctly

* Fix combineReducers issue by importing Redux type from 'redux' package

* Add usePageId hook that fires action when user navigates to page

* Strict typing for middleware

* addresses comments and uses better types

* move types to common/types.ts

* Move types to endpoint/types.ts, address PR comments

blah 2

Co-authored-by: Pedro Jaramillo <peluja1012@gmail.com>
  • Loading branch information
dplumlee and peluja1012 committed Jan 29, 2020
1 parent 0d2ac94 commit 1ca013a
Show file tree
Hide file tree
Showing 22 changed files with 11,716 additions and 16 deletions.
52 changes: 52 additions & 0 deletions x-pack/plugins/endpoint/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@
* you may not use this file except in compliance with the Elastic License.
*/

/**
* A deep readonly type that will make all children of a given object readonly recursively
*/
export type Immutable<T> = T extends undefined | null | boolean | string | number
? T
: T extends Array<infer U>
? ImmutableArray<U>
: T extends Map<infer K, infer V>
? ImmutableMap<K, V>
: T extends Set<infer M>
? ImmutableSet<M>
: ImmutableObject<T>;

export type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
export type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
export type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
export type ImmutableObject<T> = { readonly [K in keyof T]: Immutable<T[K]> };

export class EndpointAppConstants {
static ENDPOINT_INDEX_NAME = 'endpoint-agent*';
}
Expand Down Expand Up @@ -44,3 +62,37 @@ export interface EndpointMetadata {
};
};
}

export interface AlertData {
value: {
source: {
endgame: {
data: {
file_operation: string;
malware_classification: {
score: number;
};
};
metadata: {
key: string;
};
timestamp_utc: Date;
};
labels: {
endpoint_id: string;
};
host: {
hostname: string;
ip: string;
os: {
name: string;
};
};
};
};
}

/**
* The PageId type is used for the payload when firing userNavigatedToPage actions
*/
export type PageId = 'alertsPage' | 'endpointListPage';
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Route, BrowserRouter, Switch } from 'react-router-dom';
import { Provider } from 'react-redux';
import { Store } from 'redux';
import { appStoreFactory } from './store';
import { AlertIndex } from './view/alerts';

/**
* This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle.
Expand Down Expand Up @@ -64,6 +65,7 @@ const AppRoot: React.FunctionComponent<RouterProps> = React.memo(({ basename, st
);
}}
/>
<Route path="/alerts" component={AlertIndex} />
<Route
render={() => (
<FormattedMessage id="xpack.endpoint.notFound" defaultMessage="Page Not Found" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { AnyAction, Dispatch, Middleware, MiddlewareAPI } from 'redux';
import { GlobalState } from '../store';
import { GlobalState } from '../types';

interface QueuedAction<TAction = AnyAction> {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* 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 { EndpointListAction } from './endpoint_list';
import { AlertAction } from './alerts';
import { RoutingAction } from './routing';

export type AppAction = EndpointListAction | AlertAction | RoutingAction;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 { AlertData, Immutable } from '../../../../../common/types';

type ServerReturnedAlertsData = Immutable<{
type: 'serverReturnedAlertsData';
payload: AlertData[];
}>;

export type AlertAction = ServerReturnedAlertsData;
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { EndpointListAction } from './endpoint_list';

export type AppAction = EndpointListAction;
export { alertListReducer } from './reducer';
export { AlertAction } from './action';
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* 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 { AlertData, ImmutableArray } from '../../../../../common/types';
import { AppAction } from '../action';
import { MiddlewareFactory } from '../../types';

export const alertMiddlewareFactory: MiddlewareFactory = coreStart => {
return api => next => async (action: AppAction) => {
next(action);
if (action.type === 'userNavigatedToPage' && action.payload === 'alertsPage') {
const response: ImmutableArray<AlertData> = await coreStart.http.get('/api/endpoint/alerts');
api.dispatch({ type: 'serverReturnedAlertsData', payload: response });
}
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* 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 { Reducer } from 'redux';
import { AlertListState } from '../../types';
import { AppAction } from '../action';

const initialState = (): AlertListState => {
return {
alerts: [],
};
};

export const alertListReducer: Reducer<AlertListState, AppAction> = (
state = initialState(),
action
) => {
if (action.type === 'serverReturnedAlertsData') {
return {
...state,
alerts: action.payload,
};
}

return state;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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 { AlertListState } from '../../types';

export const alertListData = (state: AlertListState) => state.alerts;
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { Reducer } from 'redux';
import { EndpointListState } from './types';
import { EndpointListAction } from './action';
import { AppAction } from '../action';

const initialState = (): EndpointListState => {
return {
Expand All @@ -16,7 +17,10 @@ const initialState = (): EndpointListState => {
};
};

export const endpointListReducer = (state = initialState(), action: EndpointListAction) => {
export const endpointListReducer: Reducer<EndpointListState, AppAction> = (
state = initialState(),
action
) => {
if (action.type === 'serverReturnedEndpointList') {
return {
...state,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import { createStore, compose, applyMiddleware, Store } from 'redux';
import { CoreStart } from 'kibana/public';
import { appSagaFactory } from './saga';
import { appReducer } from './reducer';

export { GlobalState } from './reducer';
import { alertMiddlewareFactory } from './alerts/middleware';

const composeWithReduxDevTools = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ name: 'EndpointApp' })
Expand All @@ -19,7 +18,9 @@ export const appStoreFactory = (coreStart: CoreStart): [Store, () => void] => {
const sagaReduxMiddleware = appSagaFactory(coreStart);
const store = createStore(
appReducer,
composeWithReduxDevTools(applyMiddleware(sagaReduxMiddleware))
composeWithReduxDevTools(
applyMiddleware(alertMiddlewareFactory(coreStart), appSagaFactory(coreStart))
)
);

sagaReduxMiddleware.start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { combineReducers, Reducer } from 'redux';
import { endpointListReducer, EndpointListState } from './endpoint_list';
import { AppAction } from './actions';

export interface GlobalState {
endpointList: EndpointListState;
}
import { endpointListReducer } from './endpoint_list';
import { AppAction } from './action';
import { alertListReducer } from './alerts';
import { GlobalState } from '../types';

export const appReducer: Reducer<GlobalState, AppAction> = combineReducers({
endpointList: endpointListReducer,
alertList: alertListReducer,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* 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 { PageId } from '../../../../../common/types';

interface UserNavigatedToPage {
readonly type: 'userNavigatedToPage';
readonly payload: PageId;
}

export type RoutingAction = UserNavigatedToPage;
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* 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 { RoutingAction } from './action';
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* 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 { GlobalState } from '../types';
import * as alertListSelectors from './alerts/selectors';

export const alertListData = composeSelectors(
alertListStateSelector,
alertListSelectors.alertListData
);

/**
* Returns the alert list state from within Global State
*/
function alertListStateSelector(state: GlobalState) {
return state.alertList;
}

/**
* Calls the `secondSelector` with the result of the `selector`. Use this when re-exporting a
* concern-specific selector. `selector` should return the concern-specific state.
*/
function composeSelectors<OuterState, InnerState, ReturnValue>(
selector: (state: OuterState) => InnerState,
secondSelector: (state: InnerState) => ReturnValue
): (state: OuterState) => ReturnValue {
return state => secondSelector(selector(state));
}
26 changes: 26 additions & 0 deletions x-pack/plugins/endpoint/public/applications/endpoint/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* 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 { Dispatch, MiddlewareAPI } from 'redux';
import { CoreStart } from 'kibana/public';
import { Immutable, AlertData } from '../../../common/types';
import { EndpointListState } from './store/endpoint_list';
import { AppAction } from './store/action';

export type MiddlewareFactory = (
coreStart: CoreStart
) => (
api: MiddlewareAPI<Dispatch<AppAction>, GlobalState>
) => (next: Dispatch<AppAction>) => (action: AppAction) => unknown;

export type AlertListState = Immutable<{
alerts: AlertData[];
}>;

export interface GlobalState {
readonly endpointList: EndpointListState;
readonly alertList: AlertListState;
}
Loading

0 comments on commit 1ca013a

Please sign in to comment.