diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap index 5d077dba447fa..6d8ea6b346eff 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/__snapshots__/page_view.test.tsx.snap @@ -2,11 +2,11 @@ exports[`PageView component should display body header custom element 1`] = ` .c0.endpoint--isListView { - padding: 0; + padding: 0 70px 0 24px; } .c0.endpoint--isListView .endpoint-header { - padding: 24px; + padding: 24px 0; margin-bottom: 0; } @@ -97,11 +97,11 @@ exports[`PageView component should display body header custom element 1`] = ` exports[`PageView component should display body header wrapped in EuiTitle 1`] = ` .c0.endpoint--isListView { - padding: 0; + padding: 0 70px 0 24px; } .c0.endpoint--isListView .endpoint-header { - padding: 24px; + padding: 24px 0; margin-bottom: 0; } @@ -195,11 +195,11 @@ exports[`PageView component should display body header wrapped in EuiTitle 1`] = exports[`PageView component should display header left and right 1`] = ` .c0.endpoint--isListView { - padding: 0; + padding: 0 70px 0 24px; } .c0.endpoint--isListView .endpoint-header { - padding: 24px; + padding: 24px 0; margin-bottom: 0; } @@ -308,11 +308,11 @@ exports[`PageView component should display header left and right 1`] = ` exports[`PageView component should display only body if not header props used 1`] = ` .c0.endpoint--isListView { - padding: 0; + padding: 0 70px 0 24px; } .c0.endpoint--isListView .endpoint-header { - padding: 24px; + padding: 24px 0; margin-bottom: 0; } @@ -380,11 +380,11 @@ exports[`PageView component should display only body if not header props used 1` exports[`PageView component should display only header left 1`] = ` .c0.endpoint--isListView { - padding: 0; + padding: 0 70px 0 24px; } .c0.endpoint--isListView .endpoint-header { - padding: 24px; + padding: 24px 0; margin-bottom: 0; } @@ -482,11 +482,11 @@ exports[`PageView component should display only header left 1`] = ` exports[`PageView component should display only header right but include an empty left side 1`] = ` .c0.endpoint--isListView { - padding: 0; + padding: 0 70px 0 24px; } .c0.endpoint--isListView .endpoint-header { - padding: 24px; + padding: 24px 0; margin-bottom: 0; } @@ -581,11 +581,11 @@ exports[`PageView component should display only header right but include an empt exports[`PageView component should pass through EuiPage props 1`] = ` .c0.endpoint--isListView { - padding: 0; + padding: 0 70px 0 24px; } .c0.endpoint--isListView .endpoint-header { - padding: 24px; + padding: 24px 0; margin-bottom: 0; } @@ -670,11 +670,11 @@ exports[`PageView component should pass through EuiPage props 1`] = ` exports[`PageView component should use custom element for header left and not wrap in EuiTitle 1`] = ` .c0.endpoint--isListView { - padding: 0; + padding: 0 70px 0 24px; } .c0.endpoint--isListView .endpoint-header { - padding: 24px; + padding: 24px 0; margin-bottom: 0; } diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx index 6fe15310fc88e..65c536fe12085 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/page_view.tsx @@ -21,13 +21,14 @@ import { import React, { memo, MouseEventHandler, ReactNode, useMemo } from 'react'; import styled from 'styled-components'; import { EuiTabProps } from '@elastic/eui/src/components/tabs/tab'; +import { gutterTimeline } from '../../lib/helpers'; const StyledEuiPage = styled(EuiPage)` &.endpoint--isListView { - padding: 0; + padding: 0 ${gutterTimeline} 0 ${(props) => props.theme.eui.euiSizeL}; .endpoint-header { - padding: ${(props) => props.theme.eui.euiSizeL}; + padding: ${(props) => props.theme.eui.euiSizeL} 0; margin-bottom: 0; } .endpoint-page-content { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/action.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/action.ts index 6866bcbf31f89..e14e39bf45c93 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/action.ts @@ -6,6 +6,7 @@ import { PolicyData } from '../../../../../../common/endpoint/types'; import { ServerApiError } from '../../../../../common/types'; +import { GetAgentStatusResponse } from '../../../../../../../ingest_manager/common/types/rest_spec'; interface ServerReturnedPolicyListData { type: 'serverReturnedPolicyListData'; @@ -22,4 +23,42 @@ interface ServerFailedToReturnPolicyListData { payload: ServerApiError; } -export type PolicyListAction = ServerReturnedPolicyListData | ServerFailedToReturnPolicyListData; +interface UserClickedPolicyListDeleteButton { + type: 'userClickedPolicyListDeleteButton'; + payload: { policyId: string }; +} + +interface UserOpenedPolicyListDeleteModal { + type: 'userOpenedPolicyListDeleteModal'; + payload: { agentConfigId: string }; +} + +interface ServerDeletedPolicyFailure { + type: 'serverDeletedPolicyFailure'; + payload: ServerApiError; +} + +interface ServerDeletedPolicy { + type: 'serverDeletedPolicy'; + payload: { id: string; success: boolean }; +} + +interface ServerReturnedPolicyAgentsSummaryForDeleteFailure { + type: 'serverReturnedPolicyAgentsSummaryForDeleteFailure'; + payload: ServerApiError; +} + +interface ServerReturnedPolicyAgentsSummaryForDelete { + type: 'serverReturnedPolicyAgentsSummaryForDelete'; + payload: { agentStatusSummary: GetAgentStatusResponse['results'] }; +} + +export type PolicyListAction = + | ServerReturnedPolicyListData + | ServerFailedToReturnPolicyListData + | UserClickedPolicyListDeleteButton + | ServerDeletedPolicyFailure + | ServerDeletedPolicy + | UserOpenedPolicyListDeleteModal + | ServerReturnedPolicyAgentsSummaryForDeleteFailure + | ServerReturnedPolicyAgentsSummaryForDelete; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts index a312134bbcd22..ce81c58893a7b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/index.test.ts @@ -13,7 +13,12 @@ import { DATASOURCE_SAVED_OBJECT_TYPE } from '../../../../../../../ingest_manage import { policyListReducer } from './reducer'; import { policyListMiddlewareFactory } from './middleware'; -import { isOnPolicyListPage, selectIsLoading, urlSearchParams } from './selectors'; +import { + isOnPolicyListPage, + selectIsLoading, + urlSearchParams, + selectIsDeleting, +} from './selectors'; import { DepsStartMock, depsStartMock } from '../../../../../common/mock/endpoint'; import { setPolicyListApiMockImplementation } from './test_mock_utils'; import { INGEST_API_DATASOURCES } from './services/ingest'; @@ -85,6 +90,33 @@ describe('policy list store concerns', () => { expect(selectIsLoading(store.getState())).toBe(false); }); + it('it sets `isDeleting` when `userClickedPolicyListDeleteButton`', async () => { + expect(selectIsDeleting(store.getState())).toBe(false); + store.dispatch({ + type: 'userClickedPolicyListDeleteButton', + payload: { + policyId: '123', + }, + }); + expect(selectIsDeleting(store.getState())).toBe(true); + await waitForAction('serverDeletedPolicy'); + expect(selectIsDeleting(store.getState())).toBe(false); + }); + + it('it sets refreshes policy data when `serverDeletedPolicy`', async () => { + expect(selectIsLoading(store.getState())).toBe(false); + store.dispatch({ + type: 'serverDeletedPolicy', + payload: { + policyId: '', + success: true, + }, + }); + expect(selectIsLoading(store.getState())).toBe(true); + await waitForAction('serverReturnedPolicyListData'); + expect(selectIsLoading(store.getState())).toBe(false); + }); + it('it resets state on `userChangedUrl` and pathname is NOT `/policy`', async () => { store.dispatch({ type: 'userChangedUrl', @@ -108,9 +140,18 @@ describe('policy list store concerns', () => { location: undefined, policyItems: [], isLoading: false, + isDeleting: false, + deleteStatus: undefined, pageIndex: 0, pageSize: 10, total: 0, + agentStatusSummary: { + error: 0, + events: 0, + offline: 0, + online: 0, + total: 0, + }, }); }); it('uses default pagination params when not included in url', async () => { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts index 66962c378537f..39c685da3ec46 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/middleware.ts @@ -5,10 +5,19 @@ */ import { GetPolicyListResponse, PolicyListState } from '../../types'; -import { sendGetEndpointSpecificDatasources } from './services/ingest'; +import { + sendGetEndpointSpecificDatasources, + sendDeleteDatasource, + sendGetFleetAgentStatusForConfig, +} from './services/ingest'; import { isOnPolicyListPage, urlSearchParams } from './selectors'; import { ImmutableMiddlewareFactory } from '../../../../../common/store'; import { initialPolicyListState } from './reducer'; +import { + DeleteDatasourcesResponse, + DeleteDatasourcesRequest, + GetAgentStatusResponse, +} from '../../../../../../../ingest_manager/common'; export const policyListMiddlewareFactory: ImmutableMiddlewareFactory = ( coreStart @@ -19,7 +28,10 @@ export const policyListMiddlewareFactory: ImmutableMiddlewareFactory Immutable = () => ({ policyItems: [], isLoading: false, + isDeleting: false, + deleteStatus: undefined, apiError: undefined, pageIndex: 0, pageSize: 10, total: 0, location: undefined, + agentStatusSummary: { + error: 0, + events: 0, + offline: 0, + online: 0, + total: 0, + }, }); export const policyListReducer: ImmutableReducer = ( @@ -33,6 +42,7 @@ export const policyListReducer: ImmutableReducer = ( ...state, ...action.payload, isLoading: false, + isDeleting: false, }; } @@ -41,6 +51,47 @@ export const policyListReducer: ImmutableReducer = ( ...state, apiError: action.payload, isLoading: false, + isDeleting: false, + }; + } + + if (action.type === 'serverDeletedPolicyFailure') { + return { + ...state, + ...action.payload, + isLoading: false, + isDeleting: false, + }; + } + + if (action.type === 'serverDeletedPolicy') { + return { + ...state, + deleteStatus: action.payload.success, + isLoading: true, + isDeleting: false, + }; + } + + if (action.type === 'userClickedPolicyListDeleteButton') { + return { + ...state, + isLoading: false, + isDeleting: true, + }; + } + + if (action.type === 'serverReturnedPolicyAgentsSummaryForDelete') { + return { + ...state, + ...action.payload, + }; + } + + if (action.type === 'serverReturnedPolicyAgentsSummaryForDeleteFailure') { + return { + ...state, + ...action.payload, }; } @@ -60,6 +111,7 @@ export const policyListReducer: ImmutableReducer = ( ...newState, apiError: undefined, isLoading: true, + isDeleting: false, }; } return newState; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/selectors.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/selectors.ts index c900ceb186f69..089c97b5520a2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/selectors.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/selectors.ts @@ -25,6 +25,13 @@ export const selectIsLoading = (state: Immutable) => state.isLo export const selectApiError = (state: Immutable) => state.apiError; +export const selectIsDeleting = (state: Immutable) => state.isDeleting; + +export const selectDeleteStatus = (state: Immutable) => state.deleteStatus; + +export const selectAgentStatusSummary = (state: Immutable) => + state.agentStatusSummary; + export const isOnPolicyListPage = (state: Immutable) => { return ( matchPath(state.location?.pathname ?? '', { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts index db482e2a6bdb6..cece3f1b4c8f2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_list/services/ingest.ts @@ -8,6 +8,8 @@ import { HttpFetchOptions, HttpStart } from 'kibana/public'; import { GetDatasourcesRequest, GetAgentStatusResponse, + DeleteDatasourcesResponse, + DeleteDatasourcesRequest, DATASOURCE_SAVED_OBJECT_TYPE, } from '../../../../../../../../ingest_manager/common'; import { GetPolicyListResponse, GetPolicyResponse, UpdatePolicyResponse } from '../../../types'; @@ -17,6 +19,7 @@ const INGEST_API_ROOT = `/api/ingest_manager`; export const INGEST_API_DATASOURCES = `${INGEST_API_ROOT}/datasources`; const INGEST_API_FLEET = `${INGEST_API_ROOT}/fleet`; const INGEST_API_FLEET_AGENT_STATUS = `${INGEST_API_FLEET}/agent-status`; +const INGEST_API_DELETE_DATASOURCE = `${INGEST_API_DATASOURCES}/delete`; /** * Retrieves a list of endpoint specific datasources (those created with a `package.name` of @@ -53,6 +56,23 @@ export const sendGetDatasource = ( return http.get(`${INGEST_API_DATASOURCES}/${datasourceId}`, options); }; +/** + * Retrieves a single datasource based on ID from ingest + * @param http + * @param datasourceId + * @param options + */ +export const sendDeleteDatasource = ( + http: HttpStart, + body: DeleteDatasourcesRequest, + options?: HttpFetchOptions +) => { + return http.post(INGEST_API_DELETE_DATASOURCE, { + ...options, + body: JSON.stringify(body.body), + }); +}; + /** * Updates a datasources * diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts index f8cc0d5cd0508..4d798d3717ce4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts @@ -37,6 +37,12 @@ export interface PolicyListState { isLoading: boolean; /** current location information */ location?: Immutable; + /** policy is being deleted */ + isDeleting: boolean; + /** Deletion status */ + deleteStatus?: boolean; + /** A summary of stats for the agents associated with a given Fleet Agent Configuration */ + agentStatusSummary?: GetAgentStatusResponse['results']; } /** diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx index d34a6d2d93893..24254530f75db 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_list.tsx @@ -17,12 +17,17 @@ import { EuiContextMenuItem, EuiButtonIcon, EuiContextMenuPanel, + EuiOverlayMask, + EuiConfirmModal, + EuiCallOut, + EuiSpacer, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { useDispatch } from 'react-redux'; import { useLocation, useHistory } from 'react-router-dom'; import { createStructuredSelector } from 'reselect'; +import styled from 'styled-components'; import { CreateStructuredSelector } from '../../../../common/store'; import * as selectors from '../store/policy_list/selectors'; import { usePolicyListSelector } from './policy_hooks'; @@ -52,6 +57,10 @@ const NO_WRAP_TRUNCATE_STYLE: CSSProperties = Object.freeze({ whiteSpace: 'nowrap', }); +const DangerEuiContextMenuItem = styled(EuiContextMenuItem)` + color: ${(props) => props.theme.eui.textColors.danger}; +`; + // eslint-disable-next-line react/display-name export const TableRowActions = React.memo<{ items: EuiContextMenuPanelProps['items'] }>( ({ items }) => { @@ -63,8 +72,10 @@ export const TableRowActions = React.memo<{ items: EuiContextMenuPanelProps['ite - + ); } @@ -106,6 +117,9 @@ export const PolicyList = React.memo(() => { const history = useHistory(); const location = useLocation(); + const [showDelete, setShowDelete] = useState(false); + const [policyIdToDelete, setPolicyIdToDelete] = useState(''); + const dispatch = useDispatch<(action: PolicyListAction) => void>(); const { selectPolicyItems: policyItems, @@ -114,6 +128,9 @@ export const PolicyList = React.memo(() => { selectTotal: totalItemCount, selectIsLoading: loading, selectApiError: apiError, + selectIsDeleting: isDeleting, + selectDeleteStatus: deleteStatus, + selectAgentStatusSummary: agentStatusSummary, } = usePolicyListSelector(selector); useEffect(() => { @@ -126,6 +143,38 @@ export const PolicyList = React.memo(() => { } }, [apiError, dispatch, notifications.toasts]); + // Handle showing update statuses + useEffect(() => { + if (deleteStatus !== undefined) { + if (deleteStatus === true) { + setPolicyIdToDelete(''); + setShowDelete(false); + notifications.toasts.success({ + toastLifeTimeMs: 10000, + title: i18n.translate('xpack.securitySolution.endpoint.policyList.deleteSuccessToast', { + defaultMessage: 'Success!', + }), + body: ( + + ), + }); + } else { + notifications.toasts.danger({ + toastLifeTimeMs: 10000, + title: i18n.translate('xpack.securitySolution.endpoint.policyList.deleteFailedToast', { + defaultMessage: 'Failed!', + }), + body: i18n.translate('xpack.securitySolution.endpoint.policyList.deleteFailedToastBody', { + defaultMessage: 'Failed to delete policy', + }), + }); + } + } + }, [notifications.toasts, deleteStatus]); + const paginationSetup = useMemo(() => { return { pageIndex, @@ -143,6 +192,36 @@ export const PolicyList = React.memo(() => { [history, location.pathname] ); + const handleDeleteOnClick = useCallback( + ({ policyId, agentConfigId }: { policyId: string; agentConfigId: string }) => { + dispatch({ + type: 'userOpenedPolicyListDeleteModal', + payload: { + agentConfigId, + }, + }); + setPolicyIdToDelete(policyId); + setShowDelete(true); + }, + [dispatch] + ); + + const handleDeleteConfirmation = useCallback( + ({ policyId }: { policyId: string }) => { + dispatch({ + type: 'userClickedPolicyListDeleteButton', + payload: { + policyId, + }, + }); + }, + [dispatch] + ); + + const handleDeleteCancel = useCallback(() => { + setShowDelete(false); + }, []); + const columns: Array>> = useMemo( () => [ { @@ -248,6 +327,19 @@ export const PolicyList = React.memo(() => { /> , + { + handleDeleteOnClick({ agentConfigId: item.config_id, policyId: item.id }); + }} + > + + , ]} /> ); @@ -256,38 +348,122 @@ export const PolicyList = React.memo(() => { ], }, ], - [services.application] + [services.application, handleDeleteOnClick] ); return ( - + <> + {showDelete && ( + { + handleDeleteConfirmation({ policyId: policyIdToDelete }); + }} + /> + )} + + + + } + > + [...policyItems], [policyItems])} + columns={columns} + loading={loading} + pagination={paginationSetup} + onChange={handleTableChange} + data-test-subj="policyTable" + hasActions={false} + /> + + + + ); +}); + +PolicyList.displayName = 'PolicyList'; + +const ConfirmDelete = React.memo<{ + hostCount: number; + isDeleting: boolean; + onConfirm: () => void; + onCancel: () => void; +}>(({ hostCount, isDeleting, onCancel, onConfirm }) => { + return ( + + + ) : ( + + ) + } + confirmButtonDisabled={isDeleting} + cancelButtonText={i18n.translate( + 'xpack.securitySolution.endpoint.policyList.deleteConfirm.cancelButtonTitle', + { + defaultMessage: 'Cancel', + } + )} + > + {hostCount > 0 && ( + <> + + + + + + )} +

- - } - > - [...policyItems], [policyItems])} - columns={columns} - loading={loading} - pagination={paginationSetup} - onChange={handleTableChange} - data-test-subj="policyTable" - hasActions={false} - /> - - +

+
+
); }); -PolicyList.displayName = 'PolicyList'; +ConfirmDelete.displayName = 'ConfirmDelete'; diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts index a5efd604d63ee..7ce1a07ad5a17 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_list.ts @@ -81,12 +81,24 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(relativeDate).to.match(RELATIVE_DATE_FORMAT); }); }); - it('should show policy name as link', async () => { - const policyNameLink = await testSubjects.find('policyNameLink'); - expect(await policyNameLink.getTagName()).to.equal('a'); - expect(await policyNameLink.getAttribute('href')).to.match( - new RegExp(`\/management\/policy\/${policyInfo.datasource.id}$`) + + it('should show agent config action as a link', async () => { + await (await pageObjects.policy.findFirstActionsButton()).click(); + const agentConfigLink = await testSubjects.find('agentConfigLink'); + expect(await agentConfigLink.getAttribute('href')).to.match( + new RegExp(`\/ingestManager#\/configs\/${policyInfo.agentConfig.id}$`) ); + // Close action menu + await (await pageObjects.policy.findFirstActionsButton()).click(); + }); + + it('should delete a policy', async () => { + await pageObjects.policy.launchAndFindDeleteModal(); + await testSubjects.existOrFail('policyListDeleteModal'); + await pageObjects.common.clickConfirmOnModal(); + await pageObjects.endpoint.waitForTableToNotHaveData('policyTable'); + const policyTotal = await testSubjects.getVisibleText('policyTotalCount'); + expect(policyTotal).to.equal('0 Policies'); }); }); }); diff --git a/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts b/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts index 0f1c9527b717b..830c5ec5a42d1 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/endpoint_page.ts @@ -33,6 +33,16 @@ export function EndpointPageProvider({ getService, getPageObjects }: FtrProvider }); }, + async waitForTableToNotHaveData(dataTestSubj: string) { + await retry.waitForWithTimeout('table to not have data', 2000, async () => { + const tableData = await pageObjects.endpointPageUtils.tableData(dataTestSubj); + if (tableData[1][0] === 'No items found') { + return true; + } + return false; + }); + }, + async waitForVisibleTextToChange(dataTestSubj: string, currentText: string) { await retry.waitForWithTimeout('visible text to change', 2000, async () => { const detailFlyoutTitle = await testSubjects.getVisibleText(dataTestSubj); diff --git a/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts b/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts index aa9c5361de846..eb481cdfc99c4 100644 --- a/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts +++ b/x-pack/test/security_solution_endpoint/page_objects/policy_page.ts @@ -19,6 +19,32 @@ export function EndpointPolicyPageProvider({ getService, getPageObjects }: FtrPr await pageObjects.header.waitUntilLoadingHasFinished(); }, + /** + * Finds and returns the Policy Details Page Save button + */ + async findFirstActionsButton() { + await this.ensureIsOnPolicyPage(); + return (await testSubjects.findAll('policyActionsButton'))[0]; + }, + + /** + * Finds and returns the Policy Details Page Save button + */ + async launchAndFindDeleteModal() { + const actionsButton = await this.findFirstActionsButton(); + await actionsButton.click(); + const deleteAction = await testSubjects.find('policyDeleteButton'); + await deleteAction.click(); + return await testSubjects.find('policyListDeleteModal'); + }, + + /** + * ensures that the Policy Page is the currently display view + */ + async ensureIsOnPolicyPage() { + await testSubjects.existOrFail('policyTable'); + }, + /** * Navigates to the Endpoint Policy Details page *