Skip to content

Commit

Permalink
allow Policy Details to support route state
Browse files Browse the repository at this point in the history
  • Loading branch information
paul-tavares committed Jul 6, 2020
1 parent 2c27bad commit 478ba73
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 33 deletions.
29 changes: 25 additions & 4 deletions x-pack/plugins/security_solution/common/endpoint/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,28 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { PackageConfig, NewPackageConfig } from '../../../ingest_manager/common';
import { ApplicationStart } from 'kibana/public';
import { NewPackageConfig, PackageConfig } from '../../../ingest_manager/common';
import { ManifestSchema } from './schema/manifest';

/**
* Supported React-Router state for the Policy Details page
*/
export interface PolicyDetailsRouteState {
/**
* Where the user should be redirected to when the `Save` button is clicked and the update was successful
*/
onSaveNavigateTo?: Parameters<ApplicationStart['navigateToApp']>;
/**
* Where the user should be redirected to when the `Cancel` button is clicked
*/
onCancelNavigateTo?: Parameters<ApplicationStart['navigateToApp']>;
/**
* The URL to be used for the `Cancel` button (should use opt to open it in a different tab)
*/
cancelUrl?: string;
}

/**
* Object that allows you to maintain stateful information in the location object across navigation events
*
Expand All @@ -17,9 +36,11 @@ export interface AppLocation {
search: string;
hash: string;
key?: string;
state?: {
isTabChange?: boolean;
};
state?:
| {
isTabChange?: boolean;
}
| PolicyDetailsRouteState;
}

/**
Expand Down
50 changes: 33 additions & 17 deletions x-pack/plugins/security_solution/public/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,50 @@
*/

import { History } from 'history';
import React, { FC, memo } from 'react';
import React, { FC, memo, useEffect } from 'react';
import { Route, Router, Switch } from 'react-router-dom';

import { useDispatch } from 'react-redux';
import { NotFoundPage } from './404';
import { HomePage } from './home';
import { ManageRoutesSpy } from '../common/utils/route/manage_spy_routes';
import { RouteCapture } from '../common/components/endpoint/route_capture';
import { AppAction } from '../common/store/actions';

interface RouterProps {
children: React.ReactNode;
history: History;
}

const PageRouterComponent: FC<RouterProps> = ({ history, children }) => (
<ManageRoutesSpy>
<Router history={history}>
<RouteCapture>
<Switch>
<Route path="/">
<HomePage>{children}</HomePage>
</Route>
<Route>
<NotFoundPage />
</Route>
</Switch>
</RouteCapture>
</Router>
</ManageRoutesSpy>
);
const PageRouterComponent: FC<RouterProps> = ({ history, children }) => {
const dispatch = useDispatch<(action: AppAction) => void>();
useEffect(() => {
return () => {
// When app is dismounted via a non-router method (ex. using Kibana's `services.application.navigateToApp()`)
// ensure that one last `userChangedUrl` store action is dispatched, which will help trigger state reset logic
dispatch({
type: 'userChangedUrl',
payload: { pathname: '', search: '', hash: '' },
});
};
}, [dispatch]);

return (
<ManageRoutesSpy>
<Router history={history}>
<RouteCapture>
<Switch>
<Route path="/">
<HomePage>{children}</HomePage>
</Route>
<Route>
<NotFoundPage />
</Route>
</Switch>
</RouteCapture>
</Router>
</ManageRoutesSpy>
);
};

export const PageRouter = memo(PageRouterComponent);
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
*/

import {
PolicyData,
AppLocation,
Immutable,
MalwareFields,
PolicyData,
UIPolicyConfig,
AppLocation,
} from '../../../../common/endpoint/types';
import { ServerApiError } from '../../../common/types';
import {
GetAgentStatusResponse,
GetPackageConfigsResponse,
GetOnePackageConfigResponse,
GetPackageConfigsResponse,
GetPackagesResponse,
UpdatePackageConfigResponse,
} from '../../../../../ingest_manager/common';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import React, { memo } from 'react';
import React, { memo, useMemo } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { EuiCallOut, EuiText, EuiTitle, EuiSpacer } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
Expand All @@ -15,18 +15,39 @@ import {
} from '../../../../../../../ingest_manager/public';
import { getPolicyDetailPath } from '../../../../common/routing';
import { MANAGEMENT_APP_ID } from '../../../../common/constants';
import { PolicyDetailsRouteState } from '../../../../../../common/endpoint/types';

/**
* Exports Endpoint-specific package config instructions
* for use in the Ingest app create / edit package config
*/
export const ConfigureEndpointPackageConfig = memo<CustomConfigurePackageConfigContent>(
({ from, packageConfigId }: CustomConfigurePackageConfigProps) => {
({
from,
packageConfigId,
packageConfig: { config_id: agentConfigId },
}: CustomConfigurePackageConfigProps) => {
let policyUrl = '';
if (from === 'edit' && packageConfigId) {
// Cannot use formalUrl here since the code is called in Ingest, which does not use redux
policyUrl = getPolicyDetailPath(packageConfigId);
}

const policyDetailRouteState = useMemo((): undefined | PolicyDetailsRouteState => {
if (from !== 'edit') {
return undefined;
}
const navigateTo = [
'ingestManager',
{ path: `#/configs/${agentConfigId}/edit-integration/${packageConfigId}` },
];
return {
onSaveNavigateTo: navigateTo,
onCancelNavigateTo: navigateTo,
cancelUrl: '',
};
}, [agentConfigId, from, packageConfigId]);

return (
<>
<EuiTitle size="xs">
Expand Down Expand Up @@ -63,7 +84,7 @@ export const ConfigureEndpointPackageConfig = memo<CustomConfigurePackageConfigC
appId={MANAGEMENT_APP_ID}
className="editLinkToPolicyDetails"
appPath={policyUrl}
// Cannot use formalUrl here since the code is called in Ingest, which does not use redux
appState={policyDetailRouteState}
>
<FormattedMessage
id="xpack.securitySolution.endpoint.ingestManager.editPackageConfig.configurePolicyLink"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
import { useDispatch } from 'react-redux';
import { useLocation } from 'react-router-dom';
import { usePolicyDetailsSelector } from './policy_hooks';
import {
policyDetails,
Expand All @@ -41,11 +42,20 @@ import { SpyRoute } from '../../../../common/utils/route/spy_routes';
import { SecurityPageName } from '../../../../app/types';
import { getPoliciesPath } from '../../../common/routing';
import { useFormatUrl } from '../../../../common/components/link_to';
import { useNavigateToAppEventHandler } from '../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { MANAGEMENT_APP_ID } from '../../../common/constants';
import { PolicyDetailsRouteState } from '../../../../../common/endpoint/types';

export const PolicyDetails = React.memo(() => {
const dispatch = useDispatch<(action: AppAction) => void>();
const { notifications } = useKibana();
const {
notifications,
services: {
application: { navigateToApp },
},
} = useKibana();
const { formatUrl, search } = useFormatUrl(SecurityPageName.management);
const { state: locationRouteState } = useLocation<PolicyDetailsRouteState>();

// Store values
const policyItem = usePolicyDetailsSelector(policyDetails);
Expand All @@ -56,6 +66,7 @@ export const PolicyDetails = React.memo(() => {

// Local state
const [showConfirm, setShowConfirm] = useState<boolean>(false);
const [routeState, setRouteState] = useState<PolicyDetailsRouteState>();
const policyName = policyItem?.name ?? '';

// Handle showing update statuses
Expand All @@ -80,6 +91,10 @@ export const PolicyDetails = React.memo(() => {
</span>
),
});

if (routeState && routeState.onSaveNavigateTo) {
navigateToApp(...routeState.onSaveNavigateTo);
}
} else {
notifications.toasts.danger({
toastLifeTimeMs: 10000,
Expand All @@ -90,10 +105,16 @@ export const PolicyDetails = React.memo(() => {
});
}
}
}, [notifications.toasts, policyName, policyUpdateStatus]);
}, [navigateToApp, notifications.toasts, policyName, policyUpdateStatus, routeState]);

const handleBackToListOnClick = useNavigateByRouterEventHandler(getPoliciesPath());

const handleCancelOnClick = useNavigateToAppEventHandler(
...(routeState && routeState.onCancelNavigateTo
? routeState.onCancelNavigateTo
: [MANAGEMENT_APP_ID, { path: getPoliciesPath() }]) // << FIXME: PT - cache this array or input to the `useNavigateToAppEventHandler()`
);

const handleSaveOnClick = useCallback(() => {
setShowConfirm(true);
}, []);
Expand All @@ -109,6 +130,12 @@ export const PolicyDetails = React.memo(() => {
setShowConfirm(false);
}, []);

useEffect(() => {
if (!routeState && locationRouteState) {
setRouteState(locationRouteState);
}
}, [locationRouteState, routeState]);

// Before proceeding - check if we have a policy data.
// If not, and we are still loading, show spinner.
// Else, if we have an error, then show error on the page.
Expand Down Expand Up @@ -159,10 +186,7 @@ export const PolicyDetails = React.memo(() => {
<VerticalDivider spacing="l" />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
onClick={handleBackToListOnClick}
data-test-subj="policyDetailsCancelButton"
>
<EuiButtonEmpty onClick={handleCancelOnClick} data-test-subj="policyDetailsCancelButton">
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.details.cancel"
defaultMessage="Cancel"
Expand Down

0 comments on commit 478ba73

Please sign in to comment.