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 3 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 @@ -95,4 +95,4 @@ export interface AlertData {
/**
* The PageId type is used for the payload when firing userNavigatedToPage actions
*/
export type PageId = 'alertsPage' | 'endpointListPage';
export type PageId = 'alertsPage' | 'policyListPage' | 'endpointListPage';
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Provider } from 'react-redux';
import { Store } from 'redux';
import { appStoreFactory } from './store';
import { AlertIndex } from './view/alerts';
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 @@ -66,6 +67,7 @@ const AppRoot: React.FunctionComponent<RouterProps> = React.memo(({ basename, st
}}
/>
<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 { EndpointListAction } from './endpoint_list';
import { AlertAction } from './alerts';
import { RoutingAction } from './routing';
import { PolicyListAction } from './policy_list/action';

export type AppAction = EndpointListAction | AlertAction | RoutingAction;
export type AppAction = EndpointListAction | AlertAction | RoutingAction | PolicyListAction;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { CoreStart } from 'kibana/public';
import { appSagaFactory } from './saga';
import { appReducer } from './reducer';
import { alertMiddlewareFactory } from './alerts/middleware';
import { policyListMiddlewareFactory } from './policy_list';

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

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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.
*/

interface ServerReturnedPolicyListData {
type: 'serverReturnedPolicyListData';
payload: {
policyItems: object[];
// tbd...
};
}

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,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,21 @@
/*
* 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 } from '../../types';

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

if (
(action.type === 'userNavigatedToPage' && action.payload === 'policyListPage') ||
action.type === 'userPaginatedPolicyListTable'
) {
// load data from API
// 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

}
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* 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: [],
pageIndex: 0,
pageSize: 0,
total: 0,
};
};

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

if (action.type === 'userPaginatedPolicyListTable') {
return {
...state,
...action.payload,
};
}

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

return state;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* 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;
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ import { endpointListReducer } from './endpoint_list';
import { AppAction } from './action';
import { alertListReducer } from './alerts';
import { GlobalState } from '../types';
import { policyListReducer } from './policy_list';

export const appReducer: Reducer<GlobalState, AppAction> = combineReducers({
endpointList: endpointListReducer,
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;
15 changes: 15 additions & 0 deletions x-pack/plugins/endpoint/public/applications/endpoint/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,22 @@ export type AlertListState = Immutable<{
alerts: AlertData[];
}>;

/**
* Policy list store state
*/
export interface PolicyListState {
/** Array of policy items */
policyItems: object[];
paul-tavares marked this conversation as resolved.
Show resolved Hide resolved
/** total number of policies */
total: number;
/** Number of policies per page */
pageSize: number;
/** page number (zero based) */
pageIndex: number;
}

export interface GlobalState {
readonly endpointList: EndpointListState;
readonly alertList: AlertListState;
readonly policyList: PolicyListState;
}
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 * from './policy_list';
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/*
* 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 { useSelector } from 'react-redux';
import { GlobalState, PolicyListState } from '../../types';

export function usePolicyListSelector<TSelected>(selector: (state: PolicyListState) => TSelected) {
return useSelector((state: GlobalState) => selector(state.policyList));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/*
* 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 React, { useCallback, useMemo } from 'react';
import {
EuiPage,
EuiPageBody,
EuiPageContent,
EuiPageContentBody,
EuiPageContentHeader,
EuiPageContentHeaderSection,
EuiTitle,
EuiBasicTable,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { useDispatch } from 'react-redux';
import { usePageId } from '../use_page_id';
import {
selectPageIndex,
selectPageSize,
selectPolicyItems,
selectTotal,
} from '../../store/policy_list/selectors';
import { usePolicyListSelector } from './policy_hooks';
import { PolicyListAction } from '../../store/policy_list';

interface TTableChangeCallbackArguments {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is TT on purpose or spelling?

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 kind of like that some types initial letter alludes the type. When I'm import'ing, I find it harder to determine what is executable code and what is a Type (even with the fact that my IDE provides different icons). But - I think this is anti-pattern nowadays right? to name your types starting with I (interface), T (type)?

I will rename it to remove the initial T.

page: { index: number; size: number };
}

export const PolicyList = React.memo(() => {
usePageId('policyListPage');

const dispatch = useDispatch<(action: PolicyListAction) => void>();
const policyItems = usePolicyListSelector(selectPolicyItems);
const pageIndex = usePolicyListSelector(selectPageIndex);
const pageSize = usePolicyListSelector(selectPageSize);
const totalItemCount = usePolicyListSelector(selectTotal);
const loading = true;

const paginationSetup = useMemo(() => {
return {
pageIndex,
pageSize,
totalItemCount,
pageSizeOptions: [10, 20, 50],
hidePerPageOptions: false,
};
}, [pageIndex, pageSize, totalItemCount]);

const handleTableChange = useCallback(
({ page: { index, size } }: TTableChangeCallbackArguments) => {
dispatch({
type: 'userPaginatedPolicyListTable',
payload: {
pageIndex: index,
pageSize: size,
},
});
},
[dispatch]
);

const columns = useMemo(
() => [
{
field: 'name',
name: i18n.translate('xpack.endpoint.policyList.nameField', {
defaultMessage: 'Policy Name',
}),
},
{
field: 'total',
name: i18n.translate('xpack.endpoint.policyList.totalField', {
defaultMessage: 'Total',
}),
},
{
field: 'pending',
name: i18n.translate('xpack.endpoint.policyList.pendingField', {
defaultMessage: 'Pending',
}),
},
{
field: 'Failed',
name: i18n.translate('xpack.endpoint.policyList.failedField', {
defaultMessage: 'Failed',
}),
},
{
field: 'created_by',
name: i18n.translate('xpack.endpoint.policyList.createdByField', {
defaultMessage: 'Created By',
}),
},
{
field: 'created',
name: i18n.translate('xpack.endpoint.policyList.createdField', {
defaultMessage: 'Created',
}),
},
{
field: 'updated_by',
name: i18n.translate('xpack.endpoint.policyList.updatedByField', {
defaultMessage: 'Last Updated By',
}),
},
{
field: 'updated',
name: i18n.translate('xpack.endpoint.policyList.updatedField', {
defaultMessage: 'Last Updated',
}),
},
],
[]
);

return (
<EuiPage>
<EuiPageBody>
<EuiPageContent>
<EuiPageContentHeader>
<EuiPageContentHeaderSection>
<EuiTitle>
<h2 data-test-subj="policyViewTitle">
<FormattedMessage
id="xpack.endpoint.policyList.viewTitle"
defaultMessage="Policies"
/>
</h2>
</EuiTitle>
</EuiPageContentHeaderSection>
</EuiPageContentHeader>
<EuiPageContentBody>
<EuiBasicTable
items={policyItems}
columns={columns}
loading={loading}
pagination={paginationSetup}
onChange={handleTableChange}
/>
</EuiPageContentBody>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
);
});
Loading