From 433c378e1f02db673e4fa7d13daafd640efc09c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20S=C3=A1nchez?= Date: Tue, 30 Nov 2021 16:29:48 +0100 Subject: [PATCH 01/11] [Security Solution] [Endpoint] User is able to filter TA in policy details view (#119290) * Initial commit to add search bar for trusted apps in policy view page * Retrieve all assigned trusted apps to ensure if there are something assigned without the search filters or not. Also fixes unit tests * remove useless if condition * Adds more unit tests and fixes some pr suggestions * Fix weird bug when loading empty state * Fix ts errors due changes in api mocks * Fixes unit test * Remove grid loader to use paginated results one. Fix selectors and tests * Remove unused imports due ts errors * remove unused import Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../action/policy_trusted_apps_action.ts | 8 +- .../policy_trusted_apps_middleware.ts | 80 ++++++++++++++++--- .../reducer/initial_policy_details_state.ts | 1 + .../reducer/trusted_apps_reducer.ts | 10 +++ .../selectors/trusted_apps_selectors.ts | 25 +++++- .../public/management/pages/policy/types.ts | 4 +- .../flyout/policy_trusted_apps_flyout.tsx | 3 - .../policy_trusted_apps_layout.test.tsx | 45 ++++++++++- .../layout/policy_trusted_apps_layout.tsx | 45 ++++++----- .../list/policy_trusted_apps_list.test.tsx | 42 ++++++---- .../list/policy_trusted_apps_list.tsx | 38 ++++----- 11 files changed, 224 insertions(+), 77 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_trusted_apps_action.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_trusted_apps_action.ts index b3bdfe32ef091..3b27c7cd1b27c 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_trusted_apps_action.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action/policy_trusted_apps_action.ts @@ -45,7 +45,12 @@ export interface PolicyArtifactsAssignableListPageDataFilter { export interface PolicyArtifactsDeosAnyTrustedAppExists { type: 'policyArtifactsDeosAnyTrustedAppExists'; - payload: AsyncResourceState; + payload: AsyncResourceState; +} + +export interface PolicyArtifactsHasTrustedApps { + type: 'policyArtifactsHasTrustedApps'; + payload: AsyncResourceState; } export interface AssignedTrustedAppsListStateChanged @@ -78,6 +83,7 @@ export type PolicyTrustedAppsAction = | PolicyArtifactsAssignableListExistDataChanged | PolicyArtifactsAssignableListPageDataFilter | PolicyArtifactsDeosAnyTrustedAppExists + | PolicyArtifactsHasTrustedApps | AssignedTrustedAppsListStateChanged | PolicyDetailsListOfAllPoliciesStateChanged | PolicyDetailsTrustedAppsForceListDataRefresh diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts index 8bb13d6fcd3b8..e9cbda1f487cb 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware/policy_trusted_apps_middleware.ts @@ -46,6 +46,7 @@ import { createLoadingResourceState, isLoadingResourceState, isUninitialisedResourceState, + isLoadedResourceState, } from '../../../../../state'; import { parseQueryFilterToKQL } from '../../../../../common/utils'; import { SEARCHABLE_FIELDS } from '../../../../trusted_apps/constants'; @@ -135,6 +136,53 @@ const checkIfThereAreAssignableTrustedApps = async ( } }; +const checkIfPolicyHasTrustedAppsAssigned = async ( + store: ImmutableMiddlewareAPI, + trustedAppsService: TrustedAppsService +) => { + const state = store.getState(); + if (isLoadingResourceState(state.artifacts.hasTrustedApps)) { + return; + } + if (isLoadedResourceState(state.artifacts.hasTrustedApps)) { + store.dispatch({ + type: 'policyArtifactsHasTrustedApps', + payload: createLoadingResourceState(state.artifacts.hasTrustedApps), + }); + } else { + store.dispatch({ + type: 'policyArtifactsHasTrustedApps', + payload: createLoadingResourceState(), + }); + } + try { + const policyId = policyIdFromParams(state); + const kuery = `exception-list-agnostic.attributes.tags:"policy:${policyId}" OR exception-list-agnostic.attributes.tags:"policy:all"`; + const trustedApps = await trustedAppsService.getTrustedAppsList({ + page: 1, + per_page: 100, + kuery, + }); + + if ( + !trustedApps.total && + isUninitialisedResourceState(state.artifacts.doesAnyTrustedAppExists) + ) { + await checkIfAnyTrustedApp(store, trustedAppsService); + } + + store.dispatch({ + type: 'policyArtifactsHasTrustedApps', + payload: createLoadedResourceState(trustedApps), + }); + } catch (err) { + store.dispatch({ + type: 'policyArtifactsHasTrustedApps', + payload: createFailedResourceState(err.body ?? err), + }); + } +}; + const checkIfAnyTrustedApp = async ( store: ImmutableMiddlewareAPI, trustedAppsService: TrustedAppsService @@ -145,7 +193,7 @@ const checkIfAnyTrustedApp = async ( } store.dispatch({ type: 'policyArtifactsDeosAnyTrustedAppExists', - payload: createLoadingResourceState(), + payload: createLoadingResourceState(), }); try { const trustedApps = await trustedAppsService.getTrustedAppsList({ @@ -155,12 +203,12 @@ const checkIfAnyTrustedApp = async ( store.dispatch({ type: 'policyArtifactsDeosAnyTrustedAppExists', - payload: createLoadedResourceState(!isEmpty(trustedApps.data)), + payload: createLoadedResourceState(trustedApps), }); } catch (err) { store.dispatch({ type: 'policyArtifactsDeosAnyTrustedAppExists', - payload: createFailedResourceState(err.body ?? err), + payload: createFailedResourceState(err.body ?? err), }); } }; @@ -185,7 +233,9 @@ const searchTrustedApps = async ( if (filter) { const filterKuery = parseQueryFilterToKQL(filter, SEARCHABLE_FIELDS) || undefined; - if (filterKuery) kuery.push(filterKuery); + if (filterKuery) { + kuery.push(filterKuery); + } } const trustedApps = await trustedAppsService.getTrustedAppsList({ @@ -228,6 +278,7 @@ const updateTrustedApps = async ( policyId, trustedApps ); + await checkIfPolicyHasTrustedAppsAssigned(store, trustedAppsService); store.dispatch({ type: 'policyArtifactsUpdateTrustedAppsChanged', @@ -265,12 +316,21 @@ const fetchPolicyTrustedAppsIfNeeded = async ( try { const urlLocationData = getCurrentUrlLocationPaginationParams(state); const policyId = policyIdFromParams(state); + const kuery = [ + `((exception-list-agnostic.attributes.tags:"policy:${policyId}") OR (exception-list-agnostic.attributes.tags:"policy:all"))`, + ]; + + if (urlLocationData.filter) { + const filterKuery = + parseQueryFilterToKQL(urlLocationData.filter, SEARCHABLE_FIELDS) || undefined; + if (filterKuery) { + kuery.push(filterKuery); + } + } const fetchResponse = await trustedAppsService.getTrustedAppsList({ page: urlLocationData.page_index + 1, per_page: urlLocationData.page_size, - kuery: `((exception-list-agnostic.attributes.tags:"policy:${policyId}") OR (exception-list-agnostic.attributes.tags:"policy:all"))${ - urlLocationData.filter ? ` AND (${urlLocationData.filter})` : '' - }`, + kuery: kuery.join(' AND '), }); dispatch({ @@ -280,8 +340,9 @@ const fetchPolicyTrustedAppsIfNeeded = async ( artifacts: fetchResponse, }), }); - if (!fetchResponse.total) { - await checkIfAnyTrustedApp({ getState, dispatch }, trustedAppsService); + + if (isUninitialisedResourceState(state.artifacts.hasTrustedApps)) { + await checkIfPolicyHasTrustedAppsAssigned({ getState, dispatch }, trustedAppsService); } } catch (error) { dispatch({ @@ -362,6 +423,7 @@ const removeTrustedAppsFromPolicy = async ( currentPolicyId, trustedApps ); + await checkIfPolicyHasTrustedAppsAssigned({ getState, dispatch }, trustedAppsService); dispatch({ type: 'policyDetailsTrustedAppsRemoveListStateChanged', diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/initial_policy_details_state.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/initial_policy_details_state.ts index 008bcd262ceff..a1e63bc889dd6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/initial_policy_details_state.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/initial_policy_details_state.ts @@ -38,6 +38,7 @@ export const initialPolicyDetailsState: () => Immutable = () trustedAppsToUpdate: createUninitialisedResourceState(), assignableListEntriesExist: createUninitialisedResourceState(), doesAnyTrustedAppExists: createUninitialisedResourceState(), + hasTrustedApps: createUninitialisedResourceState(), assignedList: createUninitialisedResourceState(), policies: createUninitialisedResourceState(), removeList: createUninitialisedResourceState(), diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.ts index f9d090647b1b5..f601e3ef0afb4 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer/trusted_apps_reducer.ts @@ -70,6 +70,16 @@ export const policyTrustedAppsReducer: ImmutableReducer { return { @@ -114,7 +114,7 @@ export const getUpdateArtifacts = ( export const getDoesTrustedAppExists = (state: Immutable): boolean => { return ( isLoadedResourceState(state.artifacts.doesAnyTrustedAppExists) && - state.artifacts.doesAnyTrustedAppExists.data + !!state.artifacts.doesAnyTrustedAppExists.data.total ); }; @@ -132,6 +132,11 @@ export const getCurrentPolicyAssignedTrustedAppsState: PolicyDetailsSelector< return state.artifacts.assignedList; }; +/** Returns current filter value */ +export const getCurrentPolicyArtifactsFilter: PolicyDetailsSelector = (state) => { + return state.artifacts.location.filter; +}; + export const getLatestLoadedPolicyAssignedTrustedAppsState: PolicyDetailsSelector< undefined | LoadedResourceState > = createSelector(getCurrentPolicyAssignedTrustedAppsState, (currentAssignedTrustedAppsState) => { @@ -183,6 +188,12 @@ export const getPolicyTrustedAppsListPagination: PolicyDetailsSelector +): number => { + return getLastLoadedResourceState(state.artifacts.hasTrustedApps)?.data.total || 0; +}; + export const getTrustedAppsPolicyListState: PolicyDetailsSelector< PolicyDetailsState['artifacts']['policies'] > = (state) => state.artifacts.policies; @@ -203,6 +214,16 @@ export const getTrustedAppsAllPoliciesById: PolicyDetailsSelector< }, {}) as Immutable>>; }); +export const getHasTrustedApps: PolicyDetailsSelector = (state) => { + return !!getLastLoadedResourceState(state.artifacts.hasTrustedApps)?.data.total; +}; + +export const getIsLoadedHasTrustedApps: PolicyDetailsSelector = (state) => + !!getLastLoadedResourceState(state.artifacts.hasTrustedApps); + +export const getHasTrustedAppsIsLoading: PolicyDetailsSelector = (state) => + isLoadingResourceState(state.artifacts.hasTrustedApps); + export const getDoesAnyTrustedAppExists: PolicyDetailsSelector< PolicyDetailsState['artifacts']['doesAnyTrustedAppExists'] > = (state) => state.artifacts.doesAnyTrustedAppExists; 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 ad06f027542df..e59a339b662b1 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 @@ -98,7 +98,9 @@ export interface PolicyArtifactsState { /** A list of trusted apps going to be updated */ trustedAppsToUpdate: AsyncResourceState; /** Represents if there is any trusted app existing */ - doesAnyTrustedAppExists: AsyncResourceState; + doesAnyTrustedAppExists: AsyncResourceState; + /** Represents if there is any trusted app existing assigned to the policy (without filters) */ + hasTrustedApps: AsyncResourceState; /** List of artifacts currently assigned to the policy (body specific and global) */ assignedList: AsyncResourceState; /** A list of all available polices */ diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.tsx index c8aa18cf2086a..a5bbff4a644b2 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/flyout/policy_trusted_apps_flyout.tsx @@ -28,7 +28,6 @@ import { import { Dispatch } from 'redux'; import { policyDetails, - getCurrentArtifactsLocation, getAssignableArtifactsList, getAssignableArtifactsListIsLoading, getUpdateArtifactsIsLoading, @@ -50,7 +49,6 @@ export const PolicyTrustedAppsFlyout = React.memo(() => { usePolicyTrustedAppsNotification(); const dispatch = useDispatch>(); const [selectedArtifactIds, setSelectedArtifactIds] = useState([]); - const location = usePolicyDetailsSelector(getCurrentArtifactsLocation); const policyItem = usePolicyDetailsSelector(policyDetails); const assignableArtifactsList = usePolicyDetailsSelector(getAssignableArtifactsList); const isAssignableArtifactsListLoading = usePolicyDetailsSelector( @@ -175,7 +173,6 @@ export const PolicyTrustedAppsFlyout = React.memo(() => { {(assignableArtifactsList?.total || 0) > 100 ? searchWarningMessage : null} { mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234')); const component = render(); - await waitForAction('policyArtifactsDeosAnyTrustedAppExists', { + await waitForAction('policyArtifactsHasTrustedApps', { validate: (action) => isLoadedResourceState(action.payload), }); @@ -107,11 +109,13 @@ describe('Policy trusted apps layout', () => { mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234')); const component = render(); - await waitForAction('assignedTrustedAppsListStateChanged'); + await waitForAction('policyArtifactsHasTrustedApps', { + validate: (action) => isLoadedResourceState(action.payload), + }); mockedContext.store.dispatch({ type: 'policyArtifactsDeosAnyTrustedAppExists', - payload: createLoadedResourceState(true), + payload: createLoadedResourceState({ data: [], total: 1 }), }); expect(component.getByTestId('policy-trusted-apps-empty-unassigned')).not.toBeNull(); @@ -121,11 +125,44 @@ describe('Policy trusted apps layout', () => { mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234')); const component = render(); - await waitForAction('assignedTrustedAppsListStateChanged'); + await waitForAction('policyArtifactsHasTrustedApps', { + validate: (action) => isLoadedResourceState(action.payload), + }); expect(component.getAllByTestId('policyTrustedAppsGrid-card')).toHaveLength(10); }); + it('should renders layout with data but no results', async () => { + mockedApis.responseProvider.trustedAppsList.mockImplementation( + (options: HttpFetchOptionsWithPath) => { + const hasAnyQuery = + 'exception-list-agnostic.attributes.tags:"policy:1234" OR exception-list-agnostic.attributes.tags:"policy:all"'; + if (options.query?.filter === hasAnyQuery) { + const exceptionsGenerator = new ExceptionsListItemGenerator('seed'); + return { + data: Array.from({ length: 10 }, () => exceptionsGenerator.generate()), + total: 10, + page: 0, + per_page: 10, + }; + } else { + return { data: [], total: 0, page: 0, per_page: 10 }; + } + } + ); + + const component = render(); + mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234', { filter: 'search' })); + + await waitForAction('policyArtifactsHasTrustedApps', { + validate: (action) => isLoadedResourceState(action.payload), + }); + + expect(component.queryAllByTestId('policyTrustedAppsGrid-card')).toHaveLength(0); + expect(component.queryByTestId('policy-trusted-apps-empty-unassigned')).toBeNull(); + expect(component.queryByTestId('policy-trusted-apps-empty-unexisting')).toBeNull(); + }); + it('should hide assign button on empty state with unassigned policies when downgraded to a gold or below license', async () => { mockUseEndpointPrivileges.mockReturnValue( getEndpointPrivilegesInitialStateMock({ diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx index 1d00c09393d55..f39b080e56e30 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.tsx @@ -17,15 +17,17 @@ import { EuiText, EuiSpacer, EuiLink, + EuiProgress, } from '@elastic/eui'; import { PolicyTrustedAppsEmptyUnassigned, PolicyTrustedAppsEmptyUnexisting } from '../empty'; import { getCurrentArtifactsLocation, getDoesTrustedAppExists, policyDetails, - doesPolicyHaveTrustedApps, doesTrustedAppExistsLoading, - getPolicyTrustedAppsListPagination, + getTotalPolicyTrustedAppsListPagination, + getHasTrustedApps, + getIsLoadedHasTrustedApps, } from '../../../store/policy_details/selectors'; import { usePolicyDetailsNavigateCallback, usePolicyDetailsSelector } from '../../policy_hooks'; import { PolicyTrustedAppsFlyout } from '../flyout'; @@ -42,11 +44,10 @@ export const PolicyTrustedAppsLayout = React.memo(() => { const isDoesTrustedAppExistsLoading = usePolicyDetailsSelector(doesTrustedAppExistsLoading); const policyItem = usePolicyDetailsSelector(policyDetails); const navigateCallback = usePolicyDetailsNavigateCallback(); - const hasAssignedTrustedApps = usePolicyDetailsSelector(doesPolicyHaveTrustedApps); const { isPlatinumPlus } = useEndpointPrivileges(); - const totalAssignedCount = usePolicyDetailsSelector( - getPolicyTrustedAppsListPagination - ).totalItemCount; + const totalAssignedCount = usePolicyDetailsSelector(getTotalPolicyTrustedAppsListPagination); + const hasTrustedApps = usePolicyDetailsSelector(getHasTrustedApps); + const isLoadedHasTrustedApps = usePolicyDetailsSelector(getIsLoadedHasTrustedApps); const showListFlyout = location.show === 'list'; @@ -73,21 +74,19 @@ export const PolicyTrustedAppsLayout = React.memo(() => { [navigateCallback] ); + const isDisplaysEmptyStateLoading = useMemo( + () => !isLoadedHasTrustedApps || isDoesTrustedAppExistsLoading, + [isLoadedHasTrustedApps, isDoesTrustedAppExistsLoading] + ); + const displaysEmptyState = useMemo( - () => - !isDoesTrustedAppExistsLoading && - !hasAssignedTrustedApps.loading && - !hasAssignedTrustedApps.hasTrustedApps, - [ - hasAssignedTrustedApps.hasTrustedApps, - hasAssignedTrustedApps.loading, - isDoesTrustedAppExistsLoading, - ] + () => !isDisplaysEmptyStateLoading && !hasTrustedApps, + [isDisplaysEmptyStateLoading, hasTrustedApps] ); - const displaysEmptyStateIsLoading = useMemo( - () => isDoesTrustedAppExistsLoading || hasAssignedTrustedApps.loading, - [hasAssignedTrustedApps.loading, isDoesTrustedAppExistsLoading] + const displayHeaderAndContent = useMemo( + () => !isDisplaysEmptyStateLoading && !displaysEmptyState && isLoadedHasTrustedApps, + [displaysEmptyState, isDisplaysEmptyStateLoading, isLoadedHasTrustedApps] ); const aboutInfo = useMemo(() => { @@ -117,7 +116,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => { return policyItem ? (
- {!displaysEmptyStateIsLoading && !displaysEmptyState ? ( + {displayHeaderAndContent ? ( <> @@ -142,7 +141,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => { {isPlatinumPlus && assignTrustedAppButton} - + ) : null} { color="transparent" borderRadius="none" > - {displaysEmptyState ? ( + {displaysEmptyState && !isDoesTrustedAppExistsLoading ? ( doesTrustedAppExists ? ( { policyName={policyItem.name} /> ) + ) : displayHeaderAndContent ? ( + ) : ( - + )} {isPlatinumPlus && showListFlyout ? : null} diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx index 549b829f44a23..7410dd20d9286 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.test.tsx @@ -13,11 +13,7 @@ import { getPolicyDetailsArtifactsListPath } from '../../../../../common/routing import { PolicyTrustedAppsList, PolicyTrustedAppsListProps } from './policy_trusted_apps_list'; import React from 'react'; import { policyDetailsPageAllApiHttpMocks } from '../../../test_utils'; -import { - createLoadingResourceState, - isFailedResourceState, - isLoadedResourceState, -} from '../../../../../state'; +import { isFailedResourceState, isLoadedResourceState } from '../../../../../state'; import { fireEvent, within, act, waitFor } from '@testing-library/react'; import { APP_UI_ID } from '../../../../../../../common/constants'; import { @@ -105,25 +101,22 @@ describe('when rendering the PolicyTrustedAppsList', () => { }) : Promise.resolve(); + const checkTrustedAppDataAssignedReceived = waitForLoadedState + ? waitForAction('policyArtifactsHasTrustedApps', { + validate({ payload }) { + return isLoadedResourceState(payload); + }, + }) + : Promise.resolve(); + renderResult = appTestContext.render(); + await checkTrustedAppDataAssignedReceived; await trustedAppDataReceived; return renderResult; }; }); - it('should show loading spinner if checking to see if trusted apps exist', async () => { - await render(); - act(() => { - appTestContext.store.dispatch({ - type: 'policyArtifactsDeosAnyTrustedAppExists', - payload: createLoadingResourceState(), - }); - }); - - expect(renderResult.getByTestId('policyTrustedAppsGrid-loading')).not.toBeNull(); - }); - it('should show total number of of items being displayed', async () => { await render(); @@ -334,4 +327,19 @@ describe('when rendering the PolicyTrustedAppsList', () => { expect(renderResult.queryByTestId('policyTrustedAppsGrid-removeAction')).toBeNull(); }); + + it('should handle search changes', async () => { + await render(); + + expect(appTestContext.history.location.search).not.toBeTruthy(); + + act(() => { + fireEvent.change(renderResult.getByTestId('searchField'), { + target: { value: 'search' }, + }); + fireEvent.submit(renderResult.getByTestId('searchField')); + }); + + expect(appTestContext.history.location.search).toMatch('?filter=search'); + }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx index 48f66806b46b4..3453bc529b272 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/list/policy_trusted_apps_list.tsx @@ -6,7 +6,7 @@ */ import React, { memo, useCallback, useEffect, useMemo, useState } from 'react'; -import { EuiLoadingSpinner, EuiSpacer, EuiText, Pagination, EuiPageTemplate } from '@elastic/eui'; +import { EuiSpacer, EuiText, Pagination } from '@elastic/eui'; import { useHistory } from 'react-router-dom'; import { i18n } from '@kbn/i18n'; import { @@ -14,9 +14,8 @@ import { ArtifactCardGridCardComponentProps, ArtifactCardGridProps, } from '../../../../../components/artifact_card_grid'; -import { usePolicyDetailsSelector } from '../../policy_hooks'; +import { usePolicyDetailsSelector, usePolicyDetailsNavigateCallback } from '../../policy_hooks'; import { - doesPolicyHaveTrustedApps, getCurrentArtifactsLocation, getPolicyTrustedAppList, getPolicyTrustedAppListError, @@ -24,7 +23,7 @@ import { getTrustedAppsAllPoliciesById, isPolicyTrustedAppListLoading, policyIdFromParams, - doesTrustedAppExistsLoading, + getCurrentPolicyArtifactsFilter, } from '../../../store/policy_details/selectors'; import { getPolicyDetailPath, @@ -34,6 +33,7 @@ import { import { Immutable, TrustedApp } from '../../../../../../../common/endpoint/types'; import { useAppUrl, useToasts } from '../../../../../../common/lib/kibana'; import { APP_UI_ID } from '../../../../../../../common/constants'; +import { SearchExceptions } from '../../../../../components/search_exceptions'; import { ContextMenuItemNavByRouterProps } from '../../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router'; import { ArtifactEntryCollapsibleCardProps } from '../../../../../components/artifact_entry_card'; import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator'; @@ -54,14 +54,14 @@ export const PolicyTrustedAppsList = memo( const { getAppUrl } = useAppUrl(); const { isPlatinumPlus } = useEndpointPrivileges(); const policyId = usePolicyDetailsSelector(policyIdFromParams); - const hasTrustedApps = usePolicyDetailsSelector(doesPolicyHaveTrustedApps); const isLoading = usePolicyDetailsSelector(isPolicyTrustedAppListLoading); - const isTrustedAppExistsCheckLoading = usePolicyDetailsSelector(doesTrustedAppExistsLoading); + const defaultFilter = usePolicyDetailsSelector(getCurrentPolicyArtifactsFilter); const trustedAppItems = usePolicyDetailsSelector(getPolicyTrustedAppList); const pagination = usePolicyDetailsSelector(getPolicyTrustedAppsListPagination); const urlParams = usePolicyDetailsSelector(getCurrentArtifactsLocation); const allPoliciesById = usePolicyDetailsSelector(getTrustedAppsAllPoliciesById); const trustedAppsApiError = usePolicyDetailsSelector(getPolicyTrustedAppListError); + const navigateCallback = usePolicyDetailsNavigateCallback(); const [isCardExpanded, setCardExpanded] = useState>({}); const [trustedAppsForRemoval, setTrustedAppsForRemoval] = useState([]); @@ -227,20 +227,22 @@ export const PolicyTrustedAppsList = memo( } }, [toasts, trustedAppsApiError]); - if (hasTrustedApps.loading || isTrustedAppExistsCheckLoading) { - return ( - - - - ); - } - return ( <> + { + navigateCallback({ filter }); + }} + /> + {!hideTotalShowingLabel && ( {totalItemsCountLabel} From 40eb1a370e6c1949a701b01b18b17fded20e3b40 Mon Sep 17 00:00:00 2001 From: Claudio Procida Date: Tue, 30 Nov 2021 16:37:12 +0100 Subject: [PATCH 02/11] chore: Refactors alerts search bar (#119104) * Refactors alerts search bar * Fixes bad merge --- .../observability/public/config/translations.ts | 5 +++++ .../pages/alerts/components/alerts_search_bar.tsx | 12 ++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/observability/public/config/translations.ts b/x-pack/plugins/observability/public/config/translations.ts index 265787ede4473..db1378d5b5a54 100644 --- a/x-pack/plugins/observability/public/config/translations.ts +++ b/x-pack/plugins/observability/public/config/translations.ts @@ -101,4 +101,9 @@ export const translations = { defaultMessage: 'View in app', }), }, + alertsSearchBar: { + placeholder: i18n.translate('xpack.observability.alerts.searchBarPlaceholder', { + defaultMessage: 'Search alerts (e.g. kibana.alert.evaluation.threshold > 75)', + }), + }, }; diff --git a/x-pack/plugins/observability/public/pages/alerts/components/alerts_search_bar.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alerts_search_bar.tsx index 14d47d1e7e9d3..230574fba94de 100644 --- a/x-pack/plugins/observability/public/pages/alerts/components/alerts_search_bar.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/alerts_search_bar.tsx @@ -6,10 +6,12 @@ */ import { IndexPatternBase } from '@kbn/es-query'; -import { i18n } from '@kbn/i18n'; import React, { useMemo, useState } from 'react'; import { SearchBar, TimeHistory } from '../../../../../../../src/plugins/data/public'; import { Storage } from '../../../../../../../src/plugins/kibana_utils/public'; +import { translations } from '../../../config'; + +type QueryLanguageType = 'lucene' | 'kuery'; export function AlertsSearchBar({ dynamicIndexPatterns, @@ -30,7 +32,7 @@ export function AlertsSearchBar({ const timeHistory = useMemo(() => { return new TimeHistory(new Storage(localStorage)); }, []); - const [queryLanguage, setQueryLanguage] = useState<'lucene' | 'kuery'>('kuery'); + const [queryLanguage, setQueryLanguage] = useState('kuery'); const compatibleIndexPatterns = useMemo( () => @@ -45,9 +47,7 @@ export function AlertsSearchBar({ return ( 75)', - })} + placeholder={translations.alertsSearchBar.placeholder} query={{ query: query ?? '', language: queryLanguage }} timeHistory={timeHistory} dateRangeFrom={rangeFrom} @@ -60,7 +60,7 @@ export function AlertsSearchBar({ dateRange, query: typeof nextQuery?.query === 'string' ? nextQuery.query : '', }); - setQueryLanguage((nextQuery?.language || 'kuery') as 'kuery' | 'lucene'); + setQueryLanguage((nextQuery?.language ?? 'kuery') as QueryLanguageType); }} displayStyle="inPage" /> From d3d61d3482f410fec9ad42dbc9eb84c1a3ca33a0 Mon Sep 17 00:00:00 2001 From: Gloria Hornero Date: Tue, 30 Nov 2021 17:14:07 +0100 Subject: [PATCH 03/11] [Security Solution] Fix attach to case test (#119589) * updates the detections script in order to take into consideration the new alerts index * refactors and unskips attach to case test Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../detection_alerts/attach_to_case.spec.ts | 15 +++++++++------ .../security_solution/cypress/tasks/alerts.ts | 4 ++++ .../detections_admin/detections_role.json | 1 + .../roles_users/hunter/detections_role.json | 2 +- .../platform_engineer/detections_role.json | 2 +- .../roles_users/reader/detections_role.json | 3 ++- .../roles_users/rule_author/detections_role.json | 2 +- .../roles_users/soc_manager/detections_role.json | 2 +- .../roles_users/t1_analyst/detections_role.json | 2 +- .../roles_users/t2_analyst/detections_role.json | 2 +- 10 files changed, 22 insertions(+), 13 deletions(-) diff --git a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts index e5b2c4eed3b00..d7a5ce6799230 100644 --- a/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts +++ b/x-pack/plugins/security_solution/cypress/integration/detection_alerts/attach_to_case.spec.ts @@ -8,7 +8,11 @@ import { getNewRule } from '../../objects/rule'; import { ROLES } from '../../../common/test'; -import { waitForAlertsIndexToBeCreated, waitForAlertsPanelToBeLoaded } from '../../tasks/alerts'; +import { + expandFirstAlertActions, + waitForAlertsIndexToBeCreated, + waitForAlertsPanelToBeLoaded, +} from '../../tasks/alerts'; import { createCustomRuleActivated } from '../../tasks/api_calls/rules'; import { cleanKibana } from '../../tasks/common'; import { waitForAlertsToPopulate } from '../../tasks/create_new_rule'; @@ -16,7 +20,7 @@ import { login, loginAndWaitForPage, waitForPageWithoutDateRange } from '../../t import { refreshPage } from '../../tasks/security_header'; import { ALERTS_URL } from '../../urls/navigation'; -import { ATTACH_ALERT_TO_CASE_BUTTON, TIMELINE_CONTEXT_MENU_BTN } from '../../screens/alerts'; +import { ATTACH_ALERT_TO_CASE_BUTTON } from '../../screens/alerts'; const loadDetectionsPage = (role: ROLES) => { waitForPageWithoutDateRange(ALERTS_URL, role); @@ -44,7 +48,7 @@ describe('Alerts timeline', () => { }); it('should not allow user with read only privileges to attach alerts to cases', () => { - cy.get(TIMELINE_CONTEXT_MENU_BTN).first().click({ force: true }); + expandFirstAlertActions(); cy.get(ATTACH_ALERT_TO_CASE_BUTTON).should('not.exist'); }); }); @@ -54,9 +58,8 @@ describe('Alerts timeline', () => { loadDetectionsPage(ROLES.platform_engineer); }); - // Skipping due to alerts not refreshing for platform_engineer despite being returned from API? - it.skip('should allow a user with crud privileges to attach alerts to cases', () => { - cy.get(TIMELINE_CONTEXT_MENU_BTN).first().click({ force: true }); + it('should allow a user with crud privileges to attach alerts to cases', () => { + expandFirstAlertActions(); cy.get(ATTACH_ALERT_TO_CASE_BUTTON).first().should('not.be.disabled'); }); }); diff --git a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts index 56f3e6821f5f5..5cb39ea3e1b4d 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/alerts.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/alerts.ts @@ -60,6 +60,10 @@ export const closeAlerts = () => { .should('not.be.visible'); }; +export const expandFirstAlertActions = () => { + cy.get(TIMELINE_CONTEXT_MENU_BTN).first().click({ force: true }); +}; + export const expandFirstAlert = () => { cy.get(EXPAND_ALERT_BTN).should('exist'); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json index e6fbef08d25ee..e0219dbc941a9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json @@ -5,6 +5,7 @@ { "names": [ ".siem-signals-*", + ".alerts-security*", ".lists*", ".items*", "apm-*-transaction*", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json index af12a2cb674d5..5f7d1091cdb36 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json @@ -16,7 +16,7 @@ "privileges": ["read", "write"] }, { - "names": [".siem-signals-*"], + "names": [".alerts-security*", ".siem-signals-*"], "privileges": ["read", "write"] }, { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json index 18effae645c42..bb26dec6decbb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json @@ -23,7 +23,7 @@ "privileges": ["all"] }, { - "names": [".siem-signals-*"], + "names": [".alerts-security*", ".siem-signals-*"], "privileges": ["all"] } ] diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json index 8f9434d9a3623..e351227fb173e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/detections_role.json @@ -4,7 +4,8 @@ "indices": [ { "names" : [ - ".siem-signals*", + ".siem-signals-*", + ".alerts-security*", ".lists*", ".items*", "metrics-endpoint.metadata_current_*", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json index d6bee8ce9dc16..bf2d948519564 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json @@ -18,7 +18,7 @@ "privileges": ["read", "write"] }, { - "names": [".siem-signals-*"], + "names": [".alerts-security*", ".siem-signals-*"], "privileges": ["read", "write", "maintenance", "view_index_metadata"] }, { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json index 46f7ca1d0067d..36e811c5a7ac2 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json @@ -18,7 +18,7 @@ "privileges": ["read", "write"] }, { - "names": [".siem-signals-*"], + "names": [".alerts-security*", ".siem-signals-*"], "privileges": ["read", "write", "manage"] }, { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json index ea3bd7b97e3ca..bd7f211f16d93 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json @@ -2,7 +2,7 @@ "elasticsearch": { "cluster": [], "indices": [ - { "names": [".siem-signals-*"], "privileges": ["read", "write", "maintenance"] }, + { "names": [".alerts-security*", ".siem-signals-*"], "privileges": ["read", "write", "maintenance"] }, { "names": [ "apm-*-transaction*", diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json index 209e57eba2cfd..d97cd39a11421 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json @@ -2,7 +2,7 @@ "elasticsearch": { "cluster": [], "indices": [ - { "names": [".siem-signals-*"], "privileges": ["read", "write", "maintenance"] }, + { "names": [".alerts-security*", ".siem-signals-*"], "privileges": ["read", "write", "maintenance"] }, { "names": [ ".lists*", From badc730264d7927947eb0d2b6cc4953dd43621f3 Mon Sep 17 00:00:00 2001 From: gchaps <33642766+gchaps@users.noreply.github.com> Date: Tue, 30 Nov 2021 08:25:59 -0800 Subject: [PATCH 04/11] [DOCS] Fixes index pattern page (#119904) --- docs/concepts/data-views.asciidoc | 7 ++++--- docs/index-extra-title-page.html | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/concepts/data-views.asciidoc b/docs/concepts/data-views.asciidoc index 954581faa2460..870b923f20cf4 100644 --- a/docs/concepts/data-views.asciidoc +++ b/docs/concepts/data-views.asciidoc @@ -87,11 +87,12 @@ For an example, refer to <: +: ``` To query {ls} indices across two {es} clusters diff --git a/docs/index-extra-title-page.html b/docs/index-extra-title-page.html index ff1c879c0f409..b70e3a985f22b 100644 --- a/docs/index-extra-title-page.html +++ b/docs/index-extra-title-page.html @@ -63,7 +63,7 @@ >
  • - Create a data view
  • From b71f78a19d9ef556a06821685f22f9c7d68f6fe3 Mon Sep 17 00:00:00 2001 From: Shahzad Date: Tue, 30 Nov 2021 17:49:55 +0100 Subject: [PATCH 05/11] [Uptime] Route to get service locations and a handler (#119964) --- .../uptime/common/constants/rest_api.ts | 1 + x-pack/plugins/uptime/common/types/index.ts | 16 ++++++ .../get_service_locations.test.ts | 50 +++++++++++++++++++ .../get_service_locations.ts | 30 +++++++++++ .../plugins/uptime/server/rest_api/index.ts | 2 + .../get_service_locations.ts | 17 +++++++ 6 files changed, 116 insertions(+) create mode 100644 x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.test.ts create mode 100644 x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.ts create mode 100644 x-pack/plugins/uptime/server/rest_api/synthetics_service/get_service_locations.ts diff --git a/x-pack/plugins/uptime/common/constants/rest_api.ts b/x-pack/plugins/uptime/common/constants/rest_api.ts index 9c8098390d129..fe9712e17827a 100644 --- a/x-pack/plugins/uptime/common/constants/rest_api.ts +++ b/x-pack/plugins/uptime/common/constants/rest_api.ts @@ -38,5 +38,6 @@ export enum API_URLS { // Service end points INDEX_TEMPLATES = '/internal/uptime/service/index_templates', + SERVICE_LOCATIONS = '/internal/uptime/service/locations', SYNTHETICS_MONITORS = '/internal/uptime/service/monitors', } diff --git a/x-pack/plugins/uptime/common/types/index.ts b/x-pack/plugins/uptime/common/types/index.ts index 734cfcc5f42d4..e013fb11c2d68 100644 --- a/x-pack/plugins/uptime/common/types/index.ts +++ b/x-pack/plugins/uptime/common/types/index.ts @@ -44,3 +44,19 @@ export type SyntheticsMonitorSavedObject = SimpleSavedObject<{ }; }; }>; + +interface LocationGeo { + lat: number; + lon: number; +} + +export interface ManifestLocation { + url: string; + geo: { + name: string; + location: LocationGeo; + }; + status: string; +} + +export type ServiceLocations = Array<{ id: string; label: string; geo: LocationGeo }>; diff --git a/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.test.ts b/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.test.ts new file mode 100644 index 0000000000000..375ceffe492da --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +import axios from 'axios'; +import { getServiceLocations } from './get_service_locations'; +jest.mock('axios'); +const mockedAxios = axios as jest.Mocked; + +describe('getServiceLocations', function () { + mockedAxios.get.mockRejectedValue('Network error: Something went wrong'); + mockedAxios.get.mockResolvedValue({ + data: { + locations: { + us_central: { + url: 'https://local.dev', + geo: { + name: 'US Central', + location: { lat: 41.25, lon: -95.86 }, + }, + status: 'beta', + }, + }, + }, + }); + it('should return parsed locations', async () => { + const locations = await getServiceLocations({ + config: { + unsafe: { + service: { + manifestUrl: 'http://local.dev', + }, + }, + }, + }); + + expect(locations).toEqual([ + { + geo: { + lat: 41.25, + lon: -95.86, + }, + id: 'us_central', + label: 'US Central', + }, + ]); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.ts b/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.ts new file mode 100644 index 0000000000000..fdd24ed2394b2 --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/synthetics_service/get_service_locations.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import axios from 'axios'; +import { UptimeConfig } from '../../../common/config'; +import { ManifestLocation, ServiceLocations } from '../../../common/types'; + +export async function getServiceLocations({ config }: { config: UptimeConfig }) { + const manifestURL = config.unsafe.service.manifestUrl; + const locations: ServiceLocations = []; + try { + const { data } = await axios.get>(manifestURL); + + Object.entries(data.locations).forEach(([locationId, location]) => { + locations.push({ + id: locationId, + label: location.geo.name, + geo: location.geo.location, + }); + }); + + return locations; + } catch (e) { + return []; + } +} diff --git a/x-pack/plugins/uptime/server/rest_api/index.ts b/x-pack/plugins/uptime/server/rest_api/index.ts index 4eb6ae3071256..b4dac68a78e0d 100644 --- a/x-pack/plugins/uptime/server/rest_api/index.ts +++ b/x-pack/plugins/uptime/server/rest_api/index.ts @@ -28,6 +28,7 @@ import { createNetworkEventsRoute } from './network_events'; import { createJourneyFailedStepsRoute } from './pings/journeys'; import { createLastSuccessfulStepRoute } from './synthetics/last_successful_step'; import { installIndexTemplatesRoute } from './synthetics_service/install_index_templates'; +import { getServiceLocationsRoute } from './synthetics_service/get_service_locations'; import { getAllSyntheticsMonitorRoute, getSyntheticsMonitorRoute, @@ -60,6 +61,7 @@ export const restApiRoutes: UMRestApiRouteFactory[] = [ createLastSuccessfulStepRoute, createJourneyScreenshotBlocksRoute, installIndexTemplatesRoute, + getServiceLocationsRoute, getSyntheticsMonitorRoute, getAllSyntheticsMonitorRoute, addSyntheticsMonitorRoute, diff --git a/x-pack/plugins/uptime/server/rest_api/synthetics_service/get_service_locations.ts b/x-pack/plugins/uptime/server/rest_api/synthetics_service/get_service_locations.ts new file mode 100644 index 0000000000000..b96b98870e38e --- /dev/null +++ b/x-pack/plugins/uptime/server/rest_api/synthetics_service/get_service_locations.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { UMRestApiRouteFactory } from '../types'; +import { API_URLS } from '../../../common/constants'; +import { getServiceLocations } from '../../lib/synthetics_service/get_service_locations'; + +export const getServiceLocationsRoute: UMRestApiRouteFactory = () => ({ + method: 'GET', + path: API_URLS.SERVICE_LOCATIONS, + validate: {}, + handler: async ({ server }): Promise => getServiceLocations({ config: server.config }), +}); From 84ab37720ede46eeb16718dbaf2337f33def01cf Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 30 Nov 2021 11:59:11 -0500 Subject: [PATCH 06/11] Make console log outputs multiline. (#119675) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../components/synthetics/executed_step.test.tsx | 15 +++++++++++++++ .../components/synthetics/executed_step.tsx | 9 +-------- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/synthetics/executed_step.test.tsx b/x-pack/plugins/uptime/public/components/synthetics/executed_step.test.tsx index f9876593a03db..e14b32fc8da9f 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/executed_step.test.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/executed_step.test.tsx @@ -82,4 +82,19 @@ describe('ExecutedStep', () => { expect(getByText('Console output')); expect(getByText(browserConsole[0])); }); + + it('renders multi-line console output', () => { + const browserConsole = ['line1', 'line2', 'line3']; + + const { getByText } = render( + + ); + + expect(getByText('Console output')); + + const codeBlock = getByText('line1 line2', { exact: false }); + expect(codeBlock.innerHTML).toEqual(`line1 +line2 +line3`); + }); }); diff --git a/x-pack/plugins/uptime/public/components/synthetics/executed_step.tsx b/x-pack/plugins/uptime/public/components/synthetics/executed_step.tsx index 57b94544e5983..80786730505a9 100644 --- a/x-pack/plugins/uptime/public/components/synthetics/executed_step.tsx +++ b/x-pack/plugins/uptime/public/components/synthetics/executed_step.tsx @@ -88,14 +88,7 @@ export const ExecutedStep: FC = ({ loading, step, index, brow language="javascript" initialIsOpen={!isSucceeded} > - <> - {browserConsoles?.map((browserConsole) => ( - <> - {browserConsole} - - - ))} - + {browserConsoles?.join('\n')} From 608258622e338730647acad8b101ea4f0e44a40e Mon Sep 17 00:00:00 2001 From: Trevor Pierce <1Copenut@users.noreply.github.com> Date: Tue, 30 Nov 2021 11:19:42 -0600 Subject: [PATCH 07/11] Adding HTML tag and impact level to axe-core CI violation reporter (#119903) * Changed the axe-report information to include HTML tag and impact level. * One further addition to the ASCIIDOC description of elements. --- .../contributing/development-accessibility-tests.asciidoc | 4 ++-- test/accessibility/services/a11y/axe_report.ts | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/developer/contributing/development-accessibility-tests.asciidoc b/docs/developer/contributing/development-accessibility-tests.asciidoc index 584d779bc7de6..2fe2682a3e365 100644 --- a/docs/developer/contributing/development-accessibility-tests.asciidoc +++ b/docs/developer/contributing/development-accessibility-tests.asciidoc @@ -90,7 +90,7 @@ Failures can seem confusing if you've never seen one before. Here is a breakdown [aria-hidden-focus]: Ensures aria-hidden elements do not contain focusable elements Help: https://dequeuniversity.com/rules/axe/3.5/aria-hidden-focus?application=axeAPI Elements: - - #example + - at Accessibility.testAxeReport (test/accessibility/services/a11y/a11y.ts:90:15) at Accessibility.testAppSnapshot (test/accessibility/services/a11y/a11y.ts:58:18) at process._tickCallback (internal/process/next_tick.js:68:7) @@ -100,5 +100,5 @@ Failures can seem confusing if you've never seen one before. Here is a breakdown * "Dashboard" and "create dashboard button" are the names of the test suite and specific test that failed. * Always in brackets, "[aria-hidden-focus]" is the name of the axe rule that failed, followed by a short description. * "Help: " links to the axe documentation for that rule, including severity, remediation tips, and good and bad code examples. -* "Elements:" points to where in the DOM the failure originated (using CSS selector syntax). In this example, the problem came from an element with the ID `example`. If the selector is too complicated to find the source of the problem, use the browser plugins mentioned earlier to locate it. If you have a general idea where the issue is coming from, you can also try adding unique IDs to the page to narrow down the location. +* "Elements:" points to where in the DOM the failure originated (using HTML syntax). In this example, the problem came from a span with the `aria-hidden="true"` attribute and a nested `