Skip to content

Commit

Permalink
[Endpoint] add policy details route (#59951)
Browse files Browse the repository at this point in the history
Adds policy details route as a target for the Policy forms
  • Loading branch information
kevinlog authored Mar 13, 2020
1 parent 71400f9 commit 4dbe261
Show file tree
Hide file tree
Showing 17 changed files with 272 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { appStoreFactory } from './store';
import { AlertIndex } from './view/alerts';
import { ManagementList } from './view/managing';
import { PolicyList } from './view/policy';
import { PolicyDetails } from './view/policy';
import { HeaderNavigation } from './components/header_nav';

/**
Expand Down Expand Up @@ -70,6 +71,7 @@ const AppRoot: React.FunctionComponent<RouterProps> = React.memo(
<Route path="/management" component={ManagementList} />
<Route path="/alerts" component={AlertIndex} />
<Route path="/policy" exact component={PolicyList} />
<Route path="/policy/:id" exact component={PolicyDetails} />
<Route
render={() => (
<FormattedMessage
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,11 @@ import { ManagementAction } from './managing';
import { AlertAction } from './alerts';
import { RoutingAction } from './routing';
import { PolicyListAction } from './policy_list';
import { PolicyDetailsAction } from './policy_details';

export type AppAction = ManagementAction | AlertAction | RoutingAction | PolicyListAction;
export type AppAction =
| ManagementAction
| AlertAction
| RoutingAction
| PolicyListAction
| PolicyDetailsAction;
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { appReducer } from './reducer';
import { alertMiddlewareFactory } from './alerts/middleware';
import { managementMiddlewareFactory } from './managing';
import { policyListMiddlewareFactory } from './policy_list';
import { policyDetailsMiddlewareFactory } from './policy_details';
import { GlobalState } from '../types';
import { AppAction } from './action';
import { EndpointPluginStartDependencies } from '../../../plugin';
Expand Down Expand Up @@ -75,6 +76,10 @@ export const appStoreFactory: (middlewareDeps?: {
globalState => globalState.policyList,
policyListMiddlewareFactory(coreStart, depsStart)
),
substateMiddlewareFactory(
globalState => globalState.policyDetails,
policyDetailsMiddlewareFactory(coreStart, depsStart)
),
substateMiddlewareFactory(
globalState => globalState.alertList,
alertMiddlewareFactory(coreStart, depsStart)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*/

import { PolicyData } from '../../types';

interface ServerReturnedPolicyDetailsData {
type: 'serverReturnedPolicyDetailsData';
payload: {
policyItem: PolicyData | undefined;
};
}

export type PolicyDetailsAction = ServerReturnedPolicyDetailsData;
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 { policyDetailsMiddlewareFactory } from './middleware';
export { PolicyDetailsAction } from './action';
export { policyDetailsReducer } from './reducer';
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 { MiddlewareFactory, PolicyDetailsState } from '../../types';
import { selectPolicyIdFromParams, isOnPolicyDetailsPage } from './selectors';

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

if (action.type === 'userChangedUrl' && isOnPolicyDetailsPage(state)) {
const id = selectPolicyIdFromParams(state);

const { getFakeDatasourceDetailsApiResponse } = await import('../policy_list/fake_data');
const policyItem = await getFakeDatasourceDetailsApiResponse(id);

dispatch({
type: 'serverReturnedPolicyDetailsData',
payload: {
policyItem,
},
});
}
};
};
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 { PolicyDetailsState } from '../../types';
import { AppAction } from '../action';

const initialPolicyDetailsState = (): PolicyDetailsState => {
return {
policyItem: {
name: '',
total: 0,
pending: 0,
failed: 0,
id: '',
created_by: '',
created: '',
updated_by: '',
updated: '',
},
isLoading: false,
};
};

export const policyDetailsReducer: Reducer<PolicyDetailsState, AppAction> = (
state = initialPolicyDetailsState(),
action
) => {
if (action.type === 'serverReturnedPolicyDetailsData') {
return {
...state,
...action.payload,
isLoading: false,
};
}

if (action.type === 'userChangedUrl') {
return {
...state,
location: action.payload,
};
}

return state;
};
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 { createSelector } from 'reselect';
import { PolicyDetailsState } from '../../types';

export const selectPolicyDetails = (state: PolicyDetailsState) => state.policyItem;

export const isOnPolicyDetailsPage = (state: PolicyDetailsState) => {
if (state.location) {
const pathnameParts = state.location.pathname.split('/');
return pathnameParts[1] === 'policy' && pathnameParts[2];
} else {
return false;
}
};

export const selectPolicyIdFromParams: (state: PolicyDetailsState) => string = createSelector(
(state: PolicyDetailsState) => state.location,
(location: PolicyDetailsState['location']) => {
if (location) {
return location.pathname.split('/')[2];
}
return '';
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -29,25 +29,35 @@ const getRandomNumber = () => {
return randomNumbers[randomIndex];
};

const policyItem = (id: string) => {
return {
name: `policy with some protections ${id}`,
total: getRandomNumber(),
pending: getRandomNumber(),
failed: getRandomNumber(),
id: `${id}`,
created_by: `admin ABC`,
created: getRandomDateIsoString(),
updated_by: 'admin 123',
updated: getRandomDateIsoString(),
};
};

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(),
})),
items: Array.from({ length: pageSize }, (x, i) => policyItem(`${i + 1}`)),
success: true,
total: pageSize * 10,
page,
perPage: pageSize,
};
};

export const getFakeDatasourceDetailsApiResponse = async (id: string) => {
await new Promise(resolve => setTimeout(resolve, 500));
return policyItem(id);
};
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import { AppAction } from './action';
import { alertListReducer } from './alerts';
import { GlobalState } from '../types';
import { policyListReducer } from './policy_list';
import { policyDetailsReducer } from './policy_details';

export const appReducer: Reducer<GlobalState, AppAction> = combineReducers({
managementList: managementListReducer,
alertList: alertListReducer,
policyList: policyListReducer,
policyDetails: policyDetailsReducer,
});
14 changes: 14 additions & 0 deletions x-pack/plugins/endpoint/public/applications/endpoint/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export interface PolicyData {
total: number;
pending: number;
failed: number;
id: string;
created_by: string;
created: string;
updated_by: string;
Expand All @@ -78,10 +79,23 @@ export interface PolicyListState {
isLoading: boolean;
}

/**
* Policy list store state
*/
export interface PolicyDetailsState {
/** A single policy item */
policyItem: PolicyData | undefined;
/** data is being retrieved from server */
isLoading: boolean;
/** current location of the application */
location?: Immutable<EndpointAppLocation>;
}

export interface GlobalState {
readonly managementList: ManagementListState;
readonly alertList: AlertListState;
readonly policyList: PolicyListState;
readonly policyDetails: PolicyDetailsState;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@
*/

export * from './policy_list';
export * from './policy_details';
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* 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 from 'react';
import { EuiTitle } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import { usePolicyDetailsSelector } from './policy_hooks';
import { selectPolicyDetails } from '../../store/policy_details/selectors';

export const PolicyDetails = React.memo(() => {
const policyItem = usePolicyDetailsSelector(selectPolicyDetails);

function policyName() {
if (policyItem) {
return <span data-test-subj="policyDetailsName">{policyItem.name}</span>;
} else {
return (
<span data-test-subj="policyDetailsNotFound">
<FormattedMessage
id="xpack.endpoint.policyDetails.notFound"
defaultMessage="Policy Not Found"
/>
</span>
);
}
}

return (
<EuiTitle size="l">
<h1 data-test-subj="policyDetailsViewTitle">{policyName()}</h1>
</EuiTitle>
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@
*/

import { useSelector } from 'react-redux';
import { GlobalState, PolicyListState } from '../../types';
import { GlobalState, PolicyListState, PolicyDetailsState } from '../../types';

export function usePolicyListSelector<TSelected>(selector: (state: PolicyListState) => TSelected) {
return useSelector((state: GlobalState) => selector(state.policyList));
}

export function usePolicyDetailsSelector<TSelected>(
selector: (state: PolicyDetailsState) => TSelected
) {
return useSelector((state: GlobalState) => selector(state.policyDetails));
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
EuiText,
EuiTableFieldDataColumnType,
EuiToolTip,
EuiLink,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import {
Expand All @@ -28,6 +29,7 @@ import {
} from '@kbn/i18n/react';
import { useDispatch } from 'react-redux';
import styled from 'styled-components';
import { useHistory } from 'react-router-dom';
import { usePageId } from '../use_page_id';
import {
selectIsLoading,
Expand Down Expand Up @@ -70,6 +72,25 @@ const FormattedDateAndTime: React.FC<{ date: Date }> = ({ date }) => {
);
};

const PolicyLink: React.FC<{ name: string; route: string }> = ({ name, route }) => {
const history = useHistory();

return (
<EuiLink
onClick={(event: React.MouseEvent) => {
event.preventDefault();
history.push(route);
}}
>
{name}
</EuiLink>
);
};

const renderPolicyNameLink = (value: string, _item: PolicyData) => {
return <PolicyLink name={value} route={`/policy/${_item.id}`} />;
};

const renderDate = (date: string, _item: PolicyData) => (
<TruncateTooltipText>
<EuiToolTip content={date}>
Expand Down Expand Up @@ -124,6 +145,7 @@ export const PolicyList = React.memo(() => {
name: i18n.translate('xpack.endpoint.policyList.nameField', {
defaultMessage: 'Policy Name',
}),
render: renderPolicyNameLink,
truncateText: true,
},
{
Expand Down
1 change: 1 addition & 0 deletions x-pack/test/functional/apps/endpoint/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default function({ loadTestFile }: FtrProviderContext) {
loadTestFile(require.resolve('./header_nav'));
loadTestFile(require.resolve('./management'));
loadTestFile(require.resolve('./policy_list'));
loadTestFile(require.resolve('./policy_details'));
loadTestFile(require.resolve('./alert_list'));
});
}
Loading

0 comments on commit 4dbe261

Please sign in to comment.