Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Endpoint] Policy List UI route and initial view #56918

Merged
merged 39 commits into from
Feb 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
7b91e2f
Initial Policy List view
paul-tavares Feb 4, 2020
4822fd2
Add `userNavigatedFromPage` action.ts
paul-tavares Feb 5, 2020
91db958
Policy List store setup
paul-tavares Feb 5, 2020
9e29674
Merge remote-tracking branch 'upstream/master' into task/EMT141-Polic…
paul-tavares Feb 5, 2020
9a45a8c
Added Formatted dates + temp type for policy data
paul-tavares Feb 7, 2020
5cc2f9d
Merge remote-tracking branch 'upstream/master' into task/EMT141-Polic…
paul-tavares Feb 7, 2020
ad57e4c
Fix TS error on EuiBasicTable
paul-tavares Feb 7, 2020
515099e
Fix Type definition for `renderDate` method
paul-tavares Feb 7, 2020
c2f1769
Policy List Functional Tests
paul-tavares Feb 7, 2020
ab82e1d
Added `serverReturnedPolicyListData` dispatch in middleware
paul-tavares Feb 7, 2020
e656b0c
Added store Middleware wrapper + adjusted MiddlwareFactory type
paul-tavares Feb 10, 2020
e3675c1
Adjust Functional test that checks Policy list is loaded
paul-tavares Feb 10, 2020
01a5e2e
Unit Tests for Policy List store concerns
paul-tavares Feb 10, 2020
8c66c60
Merge remote-tracking branch 'upstream/master' into task/EMT141-Polic…
paul-tavares Feb 10, 2020
a735e16
Functional tests: add beforeEach, move navigateToUrlWithBrowserHistor…
paul-tavares Feb 10, 2020
c04f61f
Functional test to validate table headers
paul-tavares Feb 10, 2020
1cb9d2c
Merge remote-tracking branch 'upstream/master' into task/EMT141-Polic…
paul-tavares Feb 11, 2020
8e18b5a
Adjust font sizes for page title/total + fix time display string
paul-tavares Feb 11, 2020
0492ff9
Added text truncate to table column that don't have a render() method
paul-tavares Feb 11, 2020
f296fd5
Added common `TruncateText` component. Added usage of it to policy list
paul-tavares Feb 11, 2020
ee6aa46
Added tooltip to Date fields
paul-tavares Feb 11, 2020
438cb77
Initial implementation of showing relative duration on date fields
paul-tavares Feb 12, 2020
5d09b57
added fake data for Policy list
paul-tavares Feb 13, 2020
da416d4
Rename type (remove leading `T`)
paul-tavares Feb 13, 2020
d7c9ea4
Remove unnecessary type casting
paul-tavares Feb 13, 2020
a1b5a77
Merge remote-tracking branch 'upstream/master' into task/EMT141-Polic…
paul-tavares Feb 13, 2020
cd5488d
Merge branch 'master' into task/EMT141-Policy-UI-Route
elasticmachine Feb 13, 2020
5d276d1
Fix test failure issue
paul-tavares Feb 13, 2020
78bc8b4
Merge remote-tracking branch 'upstream/master' into task/EMT141-Polic…
paul-tavares Feb 13, 2020
8812f32
Merge remote-tracking branch 'origin/task/EMT141-Policy-UI-Route' int…
paul-tavares Feb 13, 2020
31676c0
Added `width` to some table columns
paul-tavares Feb 13, 2020
13920fd
Merge remote-tracking branch 'upstream/master' into task/EMT141-Polic…
paul-tavares Feb 13, 2020
2d58030
Merge remote-tracking branch 'upstream/master' into task/EMT141-Polic…
paul-tavares Feb 14, 2020
546a193
Rename input arg of `renderDate`
paul-tavares Feb 14, 2020
b14f88a
Merge remote-tracking branch 'upstream/master' into task/EMT141-Polic…
paul-tavares Feb 14, 2020
15193b1
Merge remote-tracking branch 'upstream/master' into task/EMT141-Polic…
paul-tavares Feb 14, 2020
1fc8d65
Merge remote-tracking branch 'upstream/master' into task/EMT141-Polic…
paul-tavares Feb 14, 2020
04974e1
Remove extra `<EuiTitle`
paul-tavares Feb 14, 2020
adfa203
Merge remote-tracking branch 'upstream/master' into task/EMT141-Polic…
paul-tavares Feb 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this cause the mock core start to return response?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't actually make the API call at this time in the middleware, so not yet.
But to your question: yes, you can setup the mocked interface to return a specific response. Here is an example in Candace's PR - https://github.com/elastic/kibana/pull/55623/files#diff-a4d4b2d400e6092b6e6a5ca7a6d9a8eeR69

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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice

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 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@peluja1012 would like your feedback here if you get a chance. I added this Routing action, which is dispatched when the component that used usePageId is un-mounted. See changes below to the hook. In Policy List, I'm using it to reset state when the user leaves the view.

Copy link
Contributor

@peluja1012 peluja1012 Feb 11, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is good. Though in our next PR we are adding a "routing" middleware, which keeps track of url changes. In the alerts page, we removed usePageId in favor of this "routing" middleware. #57142

readonly type: 'userNavigatedFromPage';
readonly payload: PageId;
}

export type RoutingAction = UserNavigatedToPage | UserNavigatedFromPage;
Loading