Skip to content

Commit

Permalink
[Endpoint] Policy List UI route and initial view (#56918)
Browse files Browse the repository at this point in the history
* Initial Policy List view

* Add `endpoint/policy` route and displays Policy List
* test cases (both unit and functional)

Does not yet interact with API (Ingest).
  • Loading branch information
paul-tavares authored Feb 14, 2020
1 parent 73cb0aa commit fe21356
Show file tree
Hide file tree
Showing 22 changed files with 660 additions and 4 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/endpoint/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,4 @@ export interface EndpointMetadata {
/**
* The PageId type is used for the payload when firing userNavigatedToPage actions
*/
export type PageId = 'alertsPage' | 'managementPage';
export type PageId = 'alertsPage' | 'managementPage' | 'policyListPage';
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* 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 styled from 'styled-components';

export const TruncateText = styled.div`
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
`;
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { Store } from 'redux';
import { appStoreFactory } from './store';
import { AlertIndex } from './view/alerts';
import { ManagementList } from './view/managing';
import { PolicyList } from './view/policy';

/**
* This module will be loaded asynchronously to reduce the bundle size of your plugin's main bundle.
Expand Down Expand Up @@ -51,6 +52,7 @@ const AppRoot: React.FunctionComponent<RouterProps> = React.memo(({ basename, st
/>
<Route path="/management" component={ManagementList} />
<Route path="/alerts" component={AlertIndex} />
<Route path="/policy" exact component={PolicyList} />
<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 @@ -7,5 +7,6 @@
import { ManagementAction } from './managing';
import { AlertAction } from './alerts';
import { RoutingAction } from './routing';
import { PolicyListAction } from './policy_list';

export type AppAction = ManagementAction | AlertAction | RoutingAction;
export type AppAction = ManagementAction | AlertAction | RoutingAction | PolicyListAction;
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { CoreStart } from 'kibana/public';
import { appReducer } from './reducer';
import { alertMiddlewareFactory } from './alerts/middleware';
import { managementMiddlewareFactory } from './managing';
import { policyListMiddlewareFactory } from './policy_list';
import { GlobalState } from '../types';
import { AppAction } from './action';

Expand Down Expand Up @@ -56,6 +57,10 @@ export const appStoreFactory = (coreStart: CoreStart): Store => {
substateMiddlewareFactory(
globalState => globalState.managementList,
managementMiddlewareFactory(coreStart)
),
substateMiddlewareFactory(
globalState => globalState.policyList,
policyListMiddlewareFactory(coreStart)
)
)
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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 { PolicyData } from '../../types';

interface ServerReturnedPolicyListData {
type: 'serverReturnedPolicyListData';
payload: {
policyItems: PolicyData[];
total: number;
pageSize: number;
pageIndex: number;
};
}

interface UserPaginatedPolicyListTable {
type: 'userPaginatedPolicyListTable';
payload: {
pageSize: number;
pageIndex: number;
};
}

export type PolicyListAction = ServerReturnedPolicyListData | UserPaginatedPolicyListTable;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* 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.
*/

// !!!! Should be deleted when https://github.com/elastic/endpoint-app-team/issues/150
// is implemented

const dateOffsets = [
0,
1000,
300000, // 5 minutes
3.6e6, // 1 hour
86340000, // 23h, 59m
9e7, // 25h
9e7 * 5, // 5d
];

const randomNumbers = [5, 50, 500, 5000, 50000];

const getRandomDateIsoString = () => {
const randomIndex = Math.floor(Math.random() * Math.floor(dateOffsets.length));
return new Date(Date.now() - dateOffsets[randomIndex]).toISOString();
};

const getRandomNumber = () => {
const randomIndex = Math.floor(Math.random() * Math.floor(randomNumbers.length));
return randomNumbers[randomIndex];
};

export const getFakeDatasourceApiResponse = async (page: number, pageSize: number) => {
await new Promise(resolve => setTimeout(resolve, 500));

// Emulates the API response - see PR:
// https://github.com/elastic/kibana/pull/56567/files#diff-431549a8739efe0c56763f164c32caeeR25
return {
items: Array.from({ length: pageSize }, (x, i) => ({
name: `policy with some protections ${i + 1}`,
total: getRandomNumber(),
pending: getRandomNumber(),
failed: getRandomNumber(),
created_by: `admin ABC`,
created: getRandomDateIsoString(),
updated_by: 'admin 123',
updated: getRandomDateIsoString(),
})),
success: true,
total: pageSize * 10,
page,
perPage: pageSize,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 { PolicyListState } from '../../types';
import { applyMiddleware, createStore, Dispatch, Store } from 'redux';
import { AppAction } from '../action';
import { policyListReducer } from './reducer';
import { policyListMiddlewareFactory } from './middleware';
import { coreMock } from '../../../../../../../../src/core/public/mocks';
import { CoreStart } from 'kibana/public';
import { selectIsLoading } from './selectors';

describe('policy list store concerns', () => {
const sleep = () => new Promise(resolve => setTimeout(resolve, 1000));
let fakeCoreStart: jest.Mocked<CoreStart>;
let store: Store<PolicyListState>;
let getState: typeof store['getState'];
let dispatch: Dispatch<AppAction>;

beforeEach(() => {
fakeCoreStart = coreMock.createStart({ basePath: '/mock' });
store = createStore(
policyListReducer,
applyMiddleware(policyListMiddlewareFactory(fakeCoreStart))
);
getState = store.getState;
dispatch = store.dispatch;
});

test('it sets `isLoading` when `userNavigatedToPage`', async () => {
expect(selectIsLoading(getState())).toBe(false);
dispatch({ type: 'userNavigatedToPage', payload: 'policyListPage' });
expect(selectIsLoading(getState())).toBe(true);
await sleep();
expect(selectIsLoading(getState())).toBe(false);
});

test('it sets `isLoading` when `userPaginatedPolicyListTable`', async () => {
expect(selectIsLoading(getState())).toBe(false);
dispatch({
type: 'userPaginatedPolicyListTable',
payload: {
pageSize: 10,
pageIndex: 1,
},
});
expect(selectIsLoading(getState())).toBe(true);
await sleep();
expect(selectIsLoading(getState())).toBe(false);
});

test('it resets state on `userNavigatedFromPage` action', async () => {
dispatch({
type: 'serverReturnedPolicyListData',
payload: {
policyItems: [],
pageIndex: 20,
pageSize: 50,
total: 200,
},
});
dispatch({ type: 'userNavigatedFromPage', payload: 'policyListPage' });
expect(getState()).toEqual({
policyItems: [],
isLoading: false,
pageIndex: 0,
pageSize: 10,
total: 0,
});
});
});
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.
*/

export { policyListReducer } from './reducer';
export { PolicyListAction } from './action';
export { policyListMiddlewareFactory } from './middleware';
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* 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 { MiddlewareFactory, PolicyListState } from '../../types';

export const policyListMiddlewareFactory: MiddlewareFactory<PolicyListState> = coreStart => {
return ({ getState, dispatch }) => next => async action => {
next(action);

if (
(action.type === 'userNavigatedToPage' && action.payload === 'policyListPage') ||
action.type === 'userPaginatedPolicyListTable'
) {
const state = getState();
let pageSize: number;
let pageIndex: number;

if (action.type === 'userPaginatedPolicyListTable') {
pageSize = action.payload.pageSize;
pageIndex = action.payload.pageIndex;
} else {
pageSize = state.pageSize;
pageIndex = state.pageIndex;
}

// Need load data from API and remove fake data below
// Refactor tracked via: https://github.com/elastic/endpoint-app-team/issues/150
const { getFakeDatasourceApiResponse } = await import('./fake_data');
const { items: policyItems, total } = await getFakeDatasourceApiResponse(pageIndex, pageSize);

dispatch({
type: 'serverReturnedPolicyListData',
payload: {
policyItems,
pageIndex,
pageSize,
total,
},
});
}
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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 { PolicyListState } from '../../types';
import { AppAction } from '../action';

const initialPolicyListState = (): PolicyListState => {
return {
policyItems: [],
isLoading: false,
pageIndex: 0,
pageSize: 10,
total: 0,
};
};

export const policyListReducer: Reducer<PolicyListState, AppAction> = (
state = initialPolicyListState(),
action
) => {
if (action.type === 'serverReturnedPolicyListData') {
return {
...state,
...action.payload,
isLoading: false,
};
}

if (
action.type === 'userPaginatedPolicyListTable' ||
(action.type === 'userNavigatedToPage' && action.payload === 'policyListPage')
) {
return {
...state,
isLoading: true,
};
}

if (action.type === 'userNavigatedFromPage' && action.payload === 'policyListPage') {
return initialPolicyListState();
}

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

export const selectPolicyItems = (state: PolicyListState) => state.policyItems;

export const selectPageIndex = (state: PolicyListState) => state.pageIndex;

export const selectPageSize = (state: PolicyListState) => state.pageSize;

export const selectTotal = (state: PolicyListState) => state.total;

export const selectIsLoading = (state: PolicyListState) => state.isLoading;
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import { managementListReducer } from './managing';
import { AppAction } from './action';
import { alertListReducer } from './alerts';
import { GlobalState } from '../types';
import { policyListReducer } from './policy_list';

export const appReducer: Reducer<GlobalState, AppAction> = combineReducers({
managementList: managementListReducer,
alertList: alertListReducer,
policyList: policyListReducer,
});
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ interface UserNavigatedToPage {
readonly payload: PageId;
}

export type RoutingAction = UserNavigatedToPage;
interface UserNavigatedFromPage {
readonly type: 'userNavigatedFromPage';
readonly payload: PageId;
}

export type RoutingAction = UserNavigatedToPage | UserNavigatedFromPage;
Loading

0 comments on commit fe21356

Please sign in to comment.