[SECURITY_SOLUTION] Enable usage of the Endpoint Policy form from Fleet (#84684)

* Endpoint: add `withSecurityContext` HOC + refactor endpoint policy edit lazy component to use it
* Endpoint: refactor Policy Details to separate form from view
* Endpoint: Enable the Redux store for the Policy form when displayed via Fleet
* Fleet: Allow partial package policy updates to be sent via `onChange()`
This commit is contained in:
Paul Tavares 2020-12-02 14:37:36 -05:00 committed by GitHub
parent 0e43beed4f
commit 2ffdf75b6e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 323 additions and 122 deletions

View file

@ -143,6 +143,7 @@ export const StepDefinePackagePolicy: React.FunctionComponent<{
description: e.target.value,
})
}
data-test-subj="packagePolicyDescriptionInput"
/>
</EuiFormRow>
<EuiSpacer size="m" />

View file

@ -244,6 +244,7 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
packagePolicyName: packagePolicy.name,
},
}),
'data-test-subj': 'policyUpdateSuccessToast',
text:
agentCount && agentPolicy
? i18n.translate('xpack.fleet.editPackagePolicy.updatedNotificationMessage', {
@ -406,6 +407,7 @@ export const EditPackagePolicyPage: React.FunctionComponent = () => {
iconType="save"
color="primary"
fill
data-test-subj="saveIntegration"
>
<FormattedMessage
id="xpack.fleet.editPackagePolicy.saveButton"

View file

@ -28,13 +28,17 @@ export interface PackagePolicyEditExtensionComponentProps {
newPolicy: NewPackagePolicy;
/**
* A callback that should be executed anytime a change to the Integration Policy needs to
* be reported back to the Fleet Policy Edit page
* be reported back to the Fleet Policy Edit page.
*
* **NOTE:**
* this callback will be recreated everytime the policy data changes, thus logic around its
* invocation should take that into consideration in order to avoid an endless loop.
*/
onChange: (opts: {
/** is current form state is valid */
isValid: boolean;
/** The updated Integration Policy to be merged back and included in the API call */
updatedPolicy: NewPackagePolicy;
updatedPolicy: Partial<NewPackagePolicy>;
}) => void;
}

View file

@ -58,12 +58,12 @@ export const ConfigForm: FC<ConfigFormProps> = memo(
<ConfigFormHeading>{TITLES.type}</ConfigFormHeading>
<EuiText size="m">{type}</EuiText>
</EuiFlexItem>
<EuiFlexItem grow={2}>
<EuiFlexItem>
<ConfigFormHeading>{TITLES.os}</ConfigFormHeading>
<EuiText>{supportedOss.map((os) => OS_TITLES[os]).join(', ')}</EuiText>
</EuiFlexItem>
<EuiShowFor sizes={['m', 'l', 'xl']}>
<EuiFlexItem>
<EuiFlexItem grow={2}>
<EuiFlexGroup direction="row" gutterSize="none" justifyContent="flexEnd">
<EuiFlexItem grow={false}>{rightCorner}</EuiFlexItem>
</EuiFlexGroup>

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import React, { memo, useCallback, useMemo, useState } from 'react';
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import {
EuiCallOut,
@ -19,9 +19,11 @@ import {
EuiContextMenuPanelProps,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { useDispatch } from 'react-redux';
import {
pagePathGetters,
PackagePolicyEditExtensionComponentProps,
NewPackagePolicy,
} from '../../../../../../../fleet/public';
import { getPolicyDetailPath, getTrustedAppsListPath } from '../../../../common/routing';
import { MANAGEMENT_APP_ID } from '../../../../common/constants';
@ -31,13 +33,17 @@ import {
} from '../../../../../../common/endpoint/types';
import { useKibana } from '../../../../../common/lib/kibana';
import { useNavigateToAppEventHandler } from '../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { PolicyDetailsForm } from '../policy_details_form';
import { AppAction } from '../../../../../common/store/actions';
import { usePolicyDetailsSelector } from '../policy_hooks';
import { policyDetailsForUpdate } from '../../store/policy_details/selectors';
/**
* Exports Endpoint-specific package policy instructions
* for use in the Ingest app create / edit package policy
*/
export const EndpointPolicyEditExtension = memo<PackagePolicyEditExtensionComponentProps>(
({ policy }) => {
({ policy, onChange }) => {
return (
<>
<EuiSpacer size="m" />
@ -46,12 +52,81 @@ export const EndpointPolicyEditExtension = memo<PackagePolicyEditExtensionCompon
<EditFlowMessage agentPolicyId={policy.policy_id} integrationPolicyId={policy.id} />
</EuiText>
</EuiCallOut>
<EuiSpacer size="m" />
<WrappedPolicyDetailsForm policyId={policy.id} onChange={onChange} />
</>
);
}
);
EndpointPolicyEditExtension.displayName = 'EndpointPolicyEditExtension';
const WrappedPolicyDetailsForm = memo<{
policyId: string;
onChange: PackagePolicyEditExtensionComponentProps['onChange'];
}>(({ policyId, onChange }) => {
const dispatch = useDispatch<(a: AppAction) => void>();
const updatedPolicy = usePolicyDetailsSelector(policyDetailsForUpdate);
const [, setLastUpdatedPolicy] = useState(updatedPolicy);
// When the form is initially displayed, trigger the Redux middleware which is based on
// the location information stored via the `userChangedUrl` action.
useEffect(() => {
dispatch({
type: 'userChangedUrl',
payload: {
hash: '',
pathname: getPolicyDetailPath(policyId, ''),
search: '',
},
});
// When form is unloaded, reset the redux store
return () => {
dispatch({
type: 'userChangedUrl',
payload: {
hash: '',
pathname: '/',
search: '',
},
});
};
}, [dispatch, policyId]);
useEffect(() => {
// Currently, the `onChange` callback provided by the fleet UI extension is regenerated every
// time the policy data is updated, which means this will go into a continious loop if we don't
// actually check to see if an update should be reported back to fleet
setLastUpdatedPolicy((prevState) => {
if (prevState === updatedPolicy) {
return prevState;
}
if (updatedPolicy) {
onChange({
isValid: true,
// send up only the updated policy data which is stored in the `inputs` section.
// All other attributes (like name, id) are updated from the Fleet form, so we want to
// ensure we don't override it.
updatedPolicy: {
// Casting is needed due to the use of `Immutable<>` in our store data
inputs: (updatedPolicy.inputs as unknown) as NewPackagePolicy['inputs'],
},
});
}
return updatedPolicy;
});
}, [onChange, updatedPolicy]);
return (
<div data-test-subj="endpointIntegrationPolicyForm">
<PolicyDetailsForm />
</div>
);
});
WrappedPolicyDetailsForm.displayName = 'WrappedPolicyDetailsForm';
const EditFlowMessage = memo<{
agentPolicyId: string;
integrationPolicyId: string;
@ -82,17 +157,6 @@ const EditFlowMessage = memo<{
const handleClosePopup = useCallback(() => setIsMenuOpen(false), []);
const handleSecurityPolicyAction = useNavigateToAppEventHandler<PolicyDetailsRouteState>(
MANAGEMENT_APP_ID,
{
path: getPolicyDetailPath(integrationPolicyId),
state: {
onSaveNavigateTo: navigateBackToIngest,
onCancelNavigateTo: navigateBackToIngest,
},
}
);
const handleTrustedAppsAction = useNavigateToAppEventHandler<TrustedAppsListPageRouteState>(
MANAGEMENT_APP_ID,
{
@ -129,16 +193,6 @@ const EditFlowMessage = memo<{
const actionItems = useMemo<EuiContextMenuPanelProps['items']>(() => {
return [
<EuiContextMenuItem
key="policyDetails"
onClick={handleSecurityPolicyAction}
data-test-subj="securityPolicy"
>
<FormattedMessage
id="xpack.securitySolution.endpoint.fleet.editPackagePolicy.actionSecurityPolicy"
defaultMessage="Edit Policy"
/>
</EuiContextMenuItem>,
<EuiContextMenuItem
key="trustedApps"
onClick={handleTrustedAppsAction}
@ -150,7 +204,7 @@ const EditFlowMessage = memo<{
/>
</EuiContextMenuItem>,
];
}, [handleSecurityPolicyAction, handleTrustedAppsAction]);
}, [handleTrustedAppsAction]);
return (
<EuiFlexGroup>

View file

@ -5,14 +5,29 @@
*/
import { lazy } from 'react';
import { PackagePolicyEditExtensionComponent } from '../../../../../../../fleet/public';
import { CoreStart } from 'kibana/public';
import {
PackagePolicyEditExtensionComponent,
PackagePolicyEditExtensionComponentProps,
} from '../../../../../../../fleet/public';
import { StartPlugins } from '../../../../../types';
export const getLazyEndpointPolicyEditExtension = (
coreStart: CoreStart,
depsStart: Pick<StartPlugins, 'data' | 'fleet'>
) => {
return lazy<PackagePolicyEditExtensionComponent>(async () => {
const [{ withSecurityContext }, { EndpointPolicyEditExtension }] = await Promise.all([
import('./with_security_context'),
import('./endpoint_policy_edit_extension'),
]);
export const LazyEndpointPolicyEditExtension = lazy<PackagePolicyEditExtensionComponent>(
async () => {
const { EndpointPolicyEditExtension } = await import('./endpoint_policy_edit_extension');
return {
// FIXME: remove casting once old UI component registration is removed
default: (EndpointPolicyEditExtension as unknown) as PackagePolicyEditExtensionComponent,
default: withSecurityContext<PackagePolicyEditExtensionComponentProps>({
coreStart,
depsStart,
WrappedComponent: EndpointPolicyEditExtension,
}),
};
}
);
});
};

View file

@ -0,0 +1,64 @@
/*
* 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, { ComponentType, memo } from 'react';
import { CoreStart } from 'kibana/public';
import { combineReducers, createStore, compose, applyMiddleware } from 'redux';
import { Provider as ReduxStoreProvider } from 'react-redux';
import { StartPlugins } from '../../../../../types';
import { managementReducer } from '../../../../store/reducer';
import { managementMiddlewareFactory } from '../../../../store/middleware';
type ComposeType = typeof compose;
declare global {
interface Window {
__REDUX_DEVTOOLS_EXTENSION_COMPOSE__: ComposeType;
}
}
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
interface WithSecurityContextProps<P extends {}> {
coreStart: CoreStart;
depsStart: Pick<StartPlugins, 'data' | 'fleet'>;
WrappedComponent: ComponentType<P>;
}
/**
* Returns a new component that wraps the provided `WrappedComponent` in a bare minimum set of rendering context
* needed to render Security Solution components that may be dependent on a Redux store and/or Security Solution
* specific context based functionality
*
* @param coreStart
* @param depsStart
* @param WrappedComponent
*/
export const withSecurityContext = <P extends {}>({
coreStart,
depsStart,
WrappedComponent,
}: WithSecurityContextProps<P>): ComponentType<P> => {
let store: ReturnType<typeof createStore>; // created on first render
return memo((props) => {
if (!store) {
// Most of the code here was copied form
// x-pack/plugins/security_solution/public/management/index.ts
store = createStore(
combineReducers({
management: managementReducer,
}),
{ management: undefined },
composeEnhancers(applyMiddleware(...managementMiddlewareFactory(coreStart, depsStart)))
);
}
return (
<ReduxStoreProvider store={store}>
<WrappedComponent {...props} />
</ReduxStoreProvider>
);
});
};

View file

@ -10,7 +10,6 @@ import {
EuiFlexItem,
EuiButton,
EuiButtonEmpty,
EuiText,
EuiSpacer,
EuiOverlayMask,
EuiConfirmModal,
@ -34,9 +33,6 @@ import {
import { useKibana, toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public';
import { AgentsSummary } from './agents_summary';
import { VerticalDivider } from './vertical_divider';
import { WindowsEvents, MacEvents, LinuxEvents } from './policy_forms/events';
import { MalwareProtections } from './policy_forms/protections/malware';
import { AntivirusRegistrationForm } from './components/antivirus_registration_form';
import { useToasts } from '../../../../common/lib/kibana';
import { AppAction } from '../../../../common/store/actions';
import { SpyRoute } from '../../../../common/utils/route/spy_routes';
@ -48,7 +44,7 @@ import { MANAGEMENT_APP_ID } from '../../../common/constants';
import { PolicyDetailsRouteState } from '../../../../../common/endpoint/types';
import { WrapperPage } from '../../../../common/components/wrapper_page';
import { HeaderPage } from '../../../../common/components/header_page';
import { AdvancedPolicyForms } from './policy_advanced';
import { PolicyDetailsForm } from './policy_details_form';
export const PolicyDetails = React.memo(() => {
const dispatch = useDispatch<(action: AppAction) => void>();
@ -71,7 +67,6 @@ export const PolicyDetails = React.memo(() => {
// Local state
const [showConfirm, setShowConfirm] = useState<boolean>(false);
const [routeState, setRouteState] = useState<PolicyDetailsRouteState>();
const [showAdvancedPolicy, setShowAdvancedPolicy] = useState<boolean>(false);
const policyName = policyItem?.name ?? '';
const hostListRouterPath = getEndpointListPath({ name: 'endpointList' });
@ -111,9 +106,11 @@ export const PolicyDetails = React.memo(() => {
}
}, [navigateToApp, toasts, policyName, policyUpdateStatus, routeState]);
const routingOnCancelNavigateTo = routeState?.onCancelNavigateTo;
const navigateToAppArguments = useMemo((): Parameters<ApplicationStart['navigateToApp']> => {
return routeState?.onCancelNavigateTo ?? [MANAGEMENT_APP_ID, { path: hostListRouterPath }];
}, [hostListRouterPath, routeState?.onCancelNavigateTo]);
return routingOnCancelNavigateTo ?? [MANAGEMENT_APP_ID, { path: hostListRouterPath }];
}, [hostListRouterPath, routingOnCancelNavigateTo]);
const handleCancelOnClick = useNavigateToAppEventHandler(...navigateToAppArguments);
const handleSaveOnClick = useCallback(() => {
@ -131,10 +128,6 @@ export const PolicyDetails = React.memo(() => {
setShowConfirm(false);
}, []);
const handleAdvancedPolicyClick = useCallback(() => {
setShowAdvancedPolicy(!showAdvancedPolicy);
}, [showAdvancedPolicy]);
useEffect(() => {
if (!routeState && locationRouteState) {
setRouteState(locationRouteState);
@ -224,48 +217,7 @@ export const PolicyDetails = React.memo(() => {
{headerRightContent}
</HeaderPage>
<EuiText size="xs" color="subdued">
<h4>
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.details.protections"
defaultMessage="Protections"
/>
</h4>
</EuiText>
<EuiSpacer size="xs" />
<MalwareProtections />
<EuiSpacer size="l" />
<EuiText size="xs" color="subdued">
<h4>
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.details.settings"
defaultMessage="Settings"
/>
</h4>
</EuiText>
<EuiSpacer size="xs" />
<WindowsEvents />
<EuiSpacer size="l" />
<MacEvents />
<EuiSpacer size="l" />
<LinuxEvents />
<EuiSpacer size="l" />
<AntivirusRegistrationForm />
<EuiSpacer size="l" />
<EuiButtonEmpty data-test-subj="advancedPolicyButton" onClick={handleAdvancedPolicyClick}>
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.advanced.show"
defaultMessage="{action} advanced settings"
values={{ action: showAdvancedPolicy ? 'Hide' : 'Show' }}
/>
</EuiButtonEmpty>
<EuiSpacer size="l" />
{showAdvancedPolicy && <AdvancedPolicyForms />}
<PolicyDetailsForm />
</WrapperPage>
<SpyRoute pageName={SecurityPageName.administration} />

View file

@ -0,0 +1,68 @@
/*
* 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 { EuiButtonEmpty, EuiSpacer, EuiText } from '@elastic/eui';
import React, { memo, useCallback, useState } from 'react';
import { FormattedMessage } from '@kbn/i18n/react';
import { MalwareProtections } from './policy_forms/protections/malware';
import { LinuxEvents, MacEvents, WindowsEvents } from './policy_forms/events';
import { AdvancedPolicyForms } from './policy_advanced';
import { AntivirusRegistrationForm } from './components/antivirus_registration_form';
export const PolicyDetailsForm = memo(() => {
const [showAdvancedPolicy, setShowAdvancedPolicy] = useState<boolean>(false);
const handleAdvancedPolicyClick = useCallback(() => {
setShowAdvancedPolicy(!showAdvancedPolicy);
}, [showAdvancedPolicy]);
return (
<>
<EuiText size="xs" color="subdued">
<h4>
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.details.protections"
defaultMessage="Protections"
/>
</h4>
</EuiText>
<EuiSpacer size="xs" />
<MalwareProtections />
<EuiSpacer size="l" />
<EuiText size="xs" color="subdued">
<h4>
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.details.settings"
defaultMessage="Settings"
/>
</h4>
</EuiText>
<EuiSpacer size="xs" />
<WindowsEvents />
<EuiSpacer size="l" />
<MacEvents />
<EuiSpacer size="l" />
<LinuxEvents />
<EuiSpacer size="l" />
<AntivirusRegistrationForm />
<EuiSpacer size="l" />
<EuiButtonEmpty data-test-subj="advancedPolicyButton" onClick={handleAdvancedPolicyClick}>
<FormattedMessage
id="xpack.securitySolution.endpoint.policy.advanced.show"
defaultMessage="{action} advanced settings"
values={{ action: showAdvancedPolicy ? 'Hide' : 'Show' }}
/>
</EuiButtonEmpty>
<EuiSpacer size="l" />
{showAdvancedPolicy && <AdvancedPolicyForms />}
</>
);
});
PolicyDetailsForm.displayName = 'PolicyDetailsForm';

View file

@ -61,7 +61,7 @@ import {
import { SecurityAppStore } from './common/store/store';
import { getCaseConnectorUI } from './cases/components/connectors';
import { licenseService } from './common/hooks/use_license';
import { LazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension';
import { getLazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension';
import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension';
export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, StartPlugins> {
@ -337,7 +337,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
registerExtension({
package: 'endpoint',
view: 'package-policy-edit',
component: LazyEndpointPolicyEditExtension,
component: getLazyEndpointPolicyEditExtension(core, plugins),
});
registerExtension({

View file

@ -281,7 +281,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await actionsButton.click();
const menuPanel = await testSubjects.find('endpointActionsMenuPanel');
const actionItems = await menuPanel.findAllByTagName<'button'>('button');
const expectedItems = ['Edit Policy', 'Edit Trusted Applications'];
const expectedItems = ['Edit Trusted Applications'];
for (const action of actionItems) {
const buttonText = await action.getVisibleText();
@ -289,27 +289,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
}
});
it('should navigate to Policy Details when the edit security policy action is clicked', async () => {
await pageObjects.ingestManagerCreatePackagePolicy.selectEndpointAction('policy');
await pageObjects.policy.ensureIsOnDetailsPage();
});
it('should allow the user to navigate, edit, save Policy Details and be redirected back to ingest', async () => {
await pageObjects.ingestManagerCreatePackagePolicy.selectEndpointAction('policy');
await pageObjects.policy.ensureIsOnDetailsPage();
await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns');
await pageObjects.policy.confirmAndSave();
await testSubjects.existOrFail('policyDetailsSuccessMessage');
await pageObjects.ingestManagerCreatePackagePolicy.ensureOnEditPageOrFail();
});
it('should navigate back to Ingest Policy Edit package page on click of cancel button', async () => {
await pageObjects.ingestManagerCreatePackagePolicy.selectEndpointAction('policy');
await (await pageObjects.policy.findCancelButton()).click();
await pageObjects.ingestManagerCreatePackagePolicy.ensureOnEditPageOrFail();
});
it('should navigate to Trusted Apps', async () => {
await pageObjects.ingestManagerCreatePackagePolicy.selectEndpointAction('trustedApps');
await pageObjects.trustedApps.ensureIsOnTrustedAppsListPage();
@ -321,6 +300,53 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await backButton.click();
await pageObjects.ingestManagerCreatePackagePolicy.ensureOnEditPageOrFail();
});
it('should show the endpoint policy form', async () => {
await testSubjects.existOrFail('endpointIntegrationPolicyForm');
});
it('should allow updates to policy items', async () => {
const winDnsEventingCheckbox = await testSubjects.find('policyWindowsEvent_dns');
await pageObjects.ingestManagerCreatePackagePolicy.scrollToCenterOfWindow(
winDnsEventingCheckbox
);
expect(await winDnsEventingCheckbox.isSelected()).to.be(true);
await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns');
expect(await winDnsEventingCheckbox.isSelected()).to.be(false);
});
it('should preserve updates done from the Fleet form', async () => {
await pageObjects.ingestManagerCreatePackagePolicy.setPackagePolicyDescription(
'protect everything'
);
const winDnsEventingCheckbox = await testSubjects.find('policyWindowsEvent_dns');
await pageObjects.ingestManagerCreatePackagePolicy.scrollToCenterOfWindow(
winDnsEventingCheckbox
);
await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns');
expect(
await pageObjects.ingestManagerCreatePackagePolicy.getPackagePolicyDescriptionValue()
).to.be('protect everything');
});
it('should include updated endpoint data when saved', async () => {
const winDnsEventingCheckbox = await testSubjects.find('policyWindowsEvent_dns');
await pageObjects.ingestManagerCreatePackagePolicy.scrollToCenterOfWindow(
winDnsEventingCheckbox
);
await pageObjects.endpointPageUtils.clickOnEuiCheckbox('policyWindowsEvent_dns');
const wasSelected = await winDnsEventingCheckbox.isSelected();
await (await pageObjects.ingestManagerCreatePackagePolicy.findSaveButton(true)).click();
await pageObjects.ingestManagerCreatePackagePolicy.waitForSaveSuccessNotification(true);
await pageObjects.ingestManagerCreatePackagePolicy.navigateToAgentPolicyEditPackagePolicy(
policyInfo.agentPolicy.id,
policyInfo.packagePolicy.id
);
expect(await testSubjects.isSelected('policyWindowsEvent_dns')).to.be(wasSelected);
});
});
});
}

View file

@ -140,7 +140,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const newPolicyName = `endpoint policy ${Date.now()}`;
await pageObjects.ingestManagerCreatePackagePolicy.selectAgentPolicy();
await pageObjects.ingestManagerCreatePackagePolicy.setPackagePolicyName(newPolicyName);
await (await pageObjects.ingestManagerCreatePackagePolicy.findDSaveButton()).click();
await (await pageObjects.ingestManagerCreatePackagePolicy.findSaveButton()).click();
await pageObjects.ingestManagerCreatePackagePolicy.waitForSaveSuccessNotification();
await pageObjects.policy.ensureIsOnPolicyPage();
await policyTestResources.deletePolicyByName(newPolicyName);

View file

@ -41,8 +41,10 @@ export function IngestManagerCreatePackagePolicy({
/**
* Finds and returns the save button on the sticky bottom bar
*/
async findDSaveButton() {
return await testSubjects.find('createPackagePolicySaveButton');
async findSaveButton(forEditPage: boolean = false) {
return await testSubjects.find(
forEditPage ? 'saveIntegration' : 'createPackagePolicySaveButton'
);
},
/**
@ -80,11 +82,22 @@ export function IngestManagerCreatePackagePolicy({
await testSubjects.setValue('packagePolicyNameInput', name);
},
async getPackagePolicyDescriptionValue() {
return await testSubjects.getAttribute('packagePolicyDescriptionInput', 'value');
},
async setPackagePolicyDescription(desc: string) {
await this.scrollToCenterOfWindow('packagePolicyDescriptionInput');
await testSubjects.setValue('packagePolicyDescriptionInput', desc);
},
/**
* Waits for the save Notification toast to be visible
*/
async waitForSaveSuccessNotification() {
await testSubjects.existOrFail('packagePolicyCreateSuccessToast');
async waitForSaveSuccessNotification(forEditPage: boolean = false) {
await testSubjects.existOrFail(
forEditPage ? 'policyUpdateSuccessToast' : 'packagePolicyCreateSuccessToast'
);
},
/**
@ -115,11 +128,13 @@ export function IngestManagerCreatePackagePolicy({
/**
* Center a given Element on the Window viewport
* @param element
* @param element if defined as a string, it should be the test subject to find
*/
async scrollToCenterOfWindow(element: WebElementWrapper) {
async scrollToCenterOfWindow(element: WebElementWrapper | string) {
const ele = typeof element === 'string' ? await testSubjects.find(element) : element;
const [elementPosition, windowSize] = await Promise.all([
element.getPosition(),
ele.getPosition(),
browser.getWindowSize(),
]);
await browser.execute(