diff --git a/.buildkite/scripts/pipelines/pull_request/pipeline.js b/.buildkite/scripts/pipelines/pull_request/pipeline.js index 78dc6e1b29b6d..028c90020a0b8 100644 --- a/.buildkite/scripts/pipelines/pull_request/pipeline.js +++ b/.buildkite/scripts/pipelines/pull_request/pipeline.js @@ -55,21 +55,20 @@ const uploadPipeline = (pipelineContent) => { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/base.yml', false)); if ( - await doAnyChangesMatch([ + (await doAnyChangesMatch([ /^x-pack\/plugins\/security_solution/, /^x-pack\/test\/security_solution_cypress/, /^x-pack\/plugins\/triggers_actions_ui\/public\/application\/sections\/action_connector_form/, /^x-pack\/plugins\/triggers_actions_ui\/public\/application\/context\/actions_connectors_context\.tsx/, - ]) || process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') + ])) || + process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') ) { pipeline.push(getPipeline('.buildkite/pipelines/pull_request/security_solution.yml')); } - // Disabled for now, these are failing/disabled in Jenkins currently as well // if ( - // await doAnyChangesMatch([ - // /^x-pack\/plugins\/apm/, - // ]) || process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') + // (await doAnyChangesMatch([/^x-pack\/plugins\/apm/])) || + // process.env.GITHUB_PR_LABELS.includes('ci:all-cypress-suites') // ) { // pipeline.push(getPipeline('.buildkite/pipelines/pull_request/apm_cypress.yml')); // } diff --git a/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts b/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts index 31f763101c258..bf3d066d59f25 100644 --- a/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts +++ b/packages/kbn-securitysolution-io-ts-list-types/src/typescript_types/index.ts @@ -43,6 +43,7 @@ export interface UseExceptionListsProps { initialPagination?: Pagination; showTrustedApps: boolean; showEventFilters: boolean; + showHostIsolationExceptions: boolean; } export interface UseExceptionListProps { diff --git a/packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts b/packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts index c0a5325377dc0..55c1d4dfaa853 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_exception_lists/index.ts @@ -41,6 +41,7 @@ const DEFAULT_PAGINATION = { * @param notifications kibana service for displaying toasters * @param showTrustedApps boolean - include/exclude trusted app lists * @param showEventFilters boolean - include/exclude event filters lists + * @param showHostIsolationExceptions boolean - include/exclude host isolation exceptions lists * @param initialPagination * */ @@ -53,6 +54,7 @@ export const useExceptionLists = ({ notifications, showTrustedApps = false, showEventFilters = false, + showHostIsolationExceptions = false, }: UseExceptionListsProps): ReturnExceptionLists => { const [exceptionLists, setExceptionLists] = useState([]); const [pagination, setPagination] = useState(initialPagination); @@ -62,8 +64,14 @@ export const useExceptionLists = ({ const namespaceTypesAsString = useMemo(() => namespaceTypes.join(','), [namespaceTypes]); const filters = useMemo( (): string => - getFilters({ filters: filterOptions, namespaceTypes, showTrustedApps, showEventFilters }), - [namespaceTypes, filterOptions, showTrustedApps, showEventFilters] + getFilters({ + filters: filterOptions, + namespaceTypes, + showTrustedApps, + showEventFilters, + showHostIsolationExceptions, + }), + [namespaceTypes, filterOptions, showTrustedApps, showEventFilters, showHostIsolationExceptions] ); const fetchData = useCallback(async (): Promise => { diff --git a/packages/kbn-securitysolution-list-utils/src/get_filters/index.test.ts b/packages/kbn-securitysolution-list-utils/src/get_filters/index.test.ts index bfaad52ee8147..6484ac002d56d 100644 --- a/packages/kbn-securitysolution-list-utils/src/get_filters/index.test.ts +++ b/packages/kbn-securitysolution-list-utils/src/get_filters/index.test.ts @@ -10,68 +10,86 @@ import { getFilters } from '.'; describe('getFilters', () => { describe('single', () => { - test('it properly formats when no filters passed and "showTrustedApps" is false', () => { + test('it properly formats when no filters passed "showTrustedApps", "showEventFilters", and "showHostIsolationExceptions" is false', () => { const filter = getFilters({ filters: {}, namespaceTypes: ['single'], showTrustedApps: false, showEventFilters: false, + showHostIsolationExceptions: false, }); expect(filter).toEqual( - '(not exception-list.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters*)' + '(not exception-list.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - - test('it properly formats when no filters passed and "showTrustedApps" is true', () => { + test('it properly formats when no filters passed "showTrustedApps", "showEventFilters", and "showHostIsolationExceptions" is true', () => { const filter = getFilters({ filters: {}, namespaceTypes: ['single'], showTrustedApps: true, - showEventFilters: false, + showEventFilters: true, + showHostIsolationExceptions: true, }); expect(filter).toEqual( - '(exception-list.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters*)' + '(exception-list.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters*) AND (exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it properly formats when filters passed and "showTrustedApps" is false', () => { + test('it properly formats when filters passed and "showTrustedApps", "showEventFilters" and "showHostIsolationExceptions" is false', () => { const filter = getFilters({ filters: { created_by: 'moi', name: 'Sample' }, namespaceTypes: ['single'], showTrustedApps: false, showEventFilters: false, + showHostIsolationExceptions: false, }); expect(filter).toEqual( - '(exception-list.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample) AND (not exception-list.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters*)' + '(exception-list.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample) AND (not exception-list.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it if filters passed and "showTrustedApps" is true', () => { + test('it properly formats when filters passed and "showTrustedApps", "showEventFilters" and "showHostIsolationExceptions" is true', () => { const filter = getFilters({ filters: { created_by: 'moi', name: 'Sample' }, namespaceTypes: ['single'], showTrustedApps: true, - showEventFilters: false, + showEventFilters: true, + showHostIsolationExceptions: true, }); expect(filter).toEqual( - '(exception-list.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample) AND (exception-list.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters*)' + '(exception-list.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample) AND (exception-list.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters*) AND (exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it properly formats when no filters passed and "showEventFilters" is false', () => { + test('it properly formats when no filters passed and "showTrustedApps" is true', () => { const filter = getFilters({ filters: {}, namespaceTypes: ['single'], - showTrustedApps: false, + showTrustedApps: true, + showEventFilters: false, + showHostIsolationExceptions: false, + }); + + expect(filter).toEqual( + '(exception-list.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)' + ); + }); + + test('it if filters passed and "showTrustedApps" is true', () => { + const filter = getFilters({ + filters: { created_by: 'moi', name: 'Sample' }, + namespaceTypes: ['single'], + showTrustedApps: true, showEventFilters: false, + showHostIsolationExceptions: false, }); expect(filter).toEqual( - '(not exception-list.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters*)' + '(exception-list.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample) AND (exception-list.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); @@ -81,103 +99,138 @@ describe('getFilters', () => { namespaceTypes: ['single'], showTrustedApps: false, showEventFilters: true, + showHostIsolationExceptions: false, }); expect(filter).toEqual( - '(not exception-list.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters*)' + '(not exception-list.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it properly formats when filters passed and "showEventFilters" is false', () => { + test('it if filters passed and "showEventFilters" is true', () => { const filter = getFilters({ filters: { created_by: 'moi', name: 'Sample' }, namespaceTypes: ['single'], showTrustedApps: false, + showEventFilters: true, + showHostIsolationExceptions: false, + }); + + expect(filter).toEqual( + '(exception-list.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample) AND (not exception-list.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)' + ); + }); + + test('it properly formats when no filters passed and "showHostIsolationExceptions" is true', () => { + const filter = getFilters({ + filters: {}, + namespaceTypes: ['single'], + showTrustedApps: false, showEventFilters: false, + showHostIsolationExceptions: true, }); expect(filter).toEqual( - '(exception-list.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample) AND (not exception-list.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters*)' + '(not exception-list.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters*) AND (exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it if filters passed and "showEventFilters" is true', () => { + test('it if filters passed and "showHostIsolationExceptions" is true', () => { const filter = getFilters({ filters: { created_by: 'moi', name: 'Sample' }, namespaceTypes: ['single'], showTrustedApps: false, - showEventFilters: true, + showEventFilters: false, + showHostIsolationExceptions: true, }); expect(filter).toEqual( - '(exception-list.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample) AND (not exception-list.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters*)' + '(exception-list.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample) AND (not exception-list.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters*) AND (exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); }); describe('agnostic', () => { - test('it properly formats when no filters passed and "showTrustedApps" is false', () => { + test('it properly formats when no filters passed and "showTrustedApps", "showEventFilters" and "showHostIsolationExceptions" is false', () => { const filter = getFilters({ filters: {}, namespaceTypes: ['agnostic'], showTrustedApps: false, showEventFilters: false, + showHostIsolationExceptions: false, }); expect(filter).toEqual( - '(not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it properly formats when no filters passed and "showTrustedApps" is true', () => { + test('it properly formats when no filters passed and "showTrustedApps", "showEventFilters" and "showHostIsolationExceptions" is true', () => { const filter = getFilters({ filters: {}, namespaceTypes: ['agnostic'], showTrustedApps: true, - showEventFilters: false, + showEventFilters: true, + showHostIsolationExceptions: true, }); expect(filter).toEqual( - '(exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it properly formats when filters passed and "showTrustedApps" is false', () => { + test('it properly formats when filters passed and "showTrustedApps", "showEventFilters" and "showHostIsolationExceptions" is false', () => { const filter = getFilters({ filters: { created_by: 'moi', name: 'Sample' }, namespaceTypes: ['agnostic'], showTrustedApps: false, showEventFilters: false, + showHostIsolationExceptions: false, }); expect(filter).toEqual( - '(exception-list-agnostic.attributes.created_by:moi) AND (exception-list-agnostic.attributes.name.text:Sample) AND (not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(exception-list-agnostic.attributes.created_by:moi) AND (exception-list-agnostic.attributes.name.text:Sample) AND (not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - - test('it if filters passed and "showTrustedApps" is true', () => { + test('it properly formats when filters passed and "showTrustedApps", "showEventFilters" and "showHostIsolationExceptions" is true', () => { const filter = getFilters({ filters: { created_by: 'moi', name: 'Sample' }, namespaceTypes: ['agnostic'], showTrustedApps: true, - showEventFilters: false, + showEventFilters: true, + showHostIsolationExceptions: true, }); expect(filter).toEqual( - '(exception-list-agnostic.attributes.created_by:moi) AND (exception-list-agnostic.attributes.name.text:Sample) AND (exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(exception-list-agnostic.attributes.created_by:moi) AND (exception-list-agnostic.attributes.name.text:Sample) AND (exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it properly formats when no filters passed and "showEventFilters" is false', () => { + test('it properly formats when no filters passed and "showTrustedApps" is true', () => { const filter = getFilters({ filters: {}, namespaceTypes: ['agnostic'], - showTrustedApps: false, + showTrustedApps: true, + showEventFilters: false, + showHostIsolationExceptions: false, + }); + + expect(filter).toEqual( + '(exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' + ); + }); + + test('it if filters passed and "showTrustedApps" is true', () => { + const filter = getFilters({ + filters: { created_by: 'moi', name: 'Sample' }, + namespaceTypes: ['agnostic'], + showTrustedApps: true, showEventFilters: false, + showHostIsolationExceptions: false, }); expect(filter).toEqual( - '(not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(exception-list-agnostic.attributes.created_by:moi) AND (exception-list-agnostic.attributes.name.text:Sample) AND (exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); @@ -187,103 +240,138 @@ describe('getFilters', () => { namespaceTypes: ['agnostic'], showTrustedApps: false, showEventFilters: true, + showHostIsolationExceptions: false, }); expect(filter).toEqual( - '(not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it properly formats when filters passed and "showEventFilters" is false', () => { + test('it if filters passed and "showEventFilters" is true', () => { const filter = getFilters({ filters: { created_by: 'moi', name: 'Sample' }, namespaceTypes: ['agnostic'], showTrustedApps: false, + showEventFilters: true, + showHostIsolationExceptions: false, + }); + + expect(filter).toEqual( + '(exception-list-agnostic.attributes.created_by:moi) AND (exception-list-agnostic.attributes.name.text:Sample) AND (not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' + ); + }); + + test('it properly formats when no filters passed and "showHostIsolationExceptions" is true', () => { + const filter = getFilters({ + filters: {}, + namespaceTypes: ['agnostic'], + showTrustedApps: false, showEventFilters: false, + showHostIsolationExceptions: true, }); expect(filter).toEqual( - '(exception-list-agnostic.attributes.created_by:moi) AND (exception-list-agnostic.attributes.name.text:Sample) AND (not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it if filters passed and "showEventFilters" is true', () => { + test('it if filters passed and "showHostIsolationExceptions" is true', () => { const filter = getFilters({ filters: { created_by: 'moi', name: 'Sample' }, namespaceTypes: ['agnostic'], showTrustedApps: false, - showEventFilters: true, + showEventFilters: false, + showHostIsolationExceptions: true, }); expect(filter).toEqual( - '(exception-list-agnostic.attributes.created_by:moi) AND (exception-list-agnostic.attributes.name.text:Sample) AND (not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(exception-list-agnostic.attributes.created_by:moi) AND (exception-list-agnostic.attributes.name.text:Sample) AND (not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); }); describe('single, agnostic', () => { - test('it properly formats when no filters passed and "showTrustedApps" is false', () => { + test('it properly formats when no filters passed and "showTrustedApps", "showEventFilters" and "showHostIsolationExceptions" is false', () => { const filter = getFilters({ filters: {}, namespaceTypes: ['single', 'agnostic'], showTrustedApps: false, showEventFilters: false, + showHostIsolationExceptions: false, }); expect(filter).toEqual( - '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - - test('it properly formats when no filters passed and "showTrustedApps" is true', () => { + test('it properly formats when no filters passed and "showTrustedApps", "showEventFilters" and "showHostIsolationExceptions" is true', () => { const filter = getFilters({ filters: {}, namespaceTypes: ['single', 'agnostic'], showTrustedApps: true, - showEventFilters: false, + showEventFilters: true, + showHostIsolationExceptions: true, }); expect(filter).toEqual( - '(exception-list.attributes.list_id: endpoint_trusted_apps* OR exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(exception-list.attributes.list_id: endpoint_trusted_apps* OR exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters* OR exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (exception-list.attributes.list_id: endpoint_host_isolation_exceptions* OR exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it properly formats when filters passed and "showTrustedApps" is false', () => { + test('it properly formats when filters passed and "showTrustedApps", "showEventFilters" and "showHostIsolationExceptions" is false', () => { const filter = getFilters({ filters: { created_by: 'moi', name: 'Sample' }, namespaceTypes: ['single', 'agnostic'], showTrustedApps: false, showEventFilters: false, + showHostIsolationExceptions: false, }); expect(filter).toEqual( - '(exception-list.attributes.created_by:moi OR exception-list-agnostic.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample OR exception-list-agnostic.attributes.name.text:Sample) AND (not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(exception-list.attributes.created_by:moi OR exception-list-agnostic.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample OR exception-list-agnostic.attributes.name.text:Sample) AND (not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it properly formats when filters passed and "showTrustedApps" is true', () => { + test('it properly formats when filters passed and "showTrustedApps", "showEventFilters" and "showHostIsolationExceptions" is true', () => { const filter = getFilters({ filters: { created_by: 'moi', name: 'Sample' }, namespaceTypes: ['single', 'agnostic'], showTrustedApps: true, - showEventFilters: false, + showEventFilters: true, + showHostIsolationExceptions: true, }); expect(filter).toEqual( - '(exception-list.attributes.created_by:moi OR exception-list-agnostic.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample OR exception-list-agnostic.attributes.name.text:Sample) AND (exception-list.attributes.list_id: endpoint_trusted_apps* OR exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(exception-list.attributes.created_by:moi OR exception-list-agnostic.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample OR exception-list-agnostic.attributes.name.text:Sample) AND (exception-list.attributes.list_id: endpoint_trusted_apps* OR exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters* OR exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (exception-list.attributes.list_id: endpoint_host_isolation_exceptions* OR exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it properly formats when no filters passed and "showEventFilters" is false', () => { + test('it properly formats when no filters passed and "showTrustedApps" is true', () => { const filter = getFilters({ filters: {}, namespaceTypes: ['single', 'agnostic'], - showTrustedApps: false, + showTrustedApps: true, + showEventFilters: false, + showHostIsolationExceptions: false, + }); + + expect(filter).toEqual( + '(exception-list.attributes.list_id: endpoint_trusted_apps* OR exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' + ); + }); + + test('it properly formats when filters passed and "showTrustedApps" is true', () => { + const filter = getFilters({ + filters: { created_by: 'moi', name: 'Sample' }, + namespaceTypes: ['single', 'agnostic'], + showTrustedApps: true, showEventFilters: false, + showHostIsolationExceptions: false, }); expect(filter).toEqual( - '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(exception-list.attributes.created_by:moi OR exception-list-agnostic.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample OR exception-list-agnostic.attributes.name.text:Sample) AND (exception-list.attributes.list_id: endpoint_trusted_apps* OR exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); @@ -293,36 +381,52 @@ describe('getFilters', () => { namespaceTypes: ['single', 'agnostic'], showTrustedApps: false, showEventFilters: true, + showHostIsolationExceptions: false, }); expect(filter).toEqual( - '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters* OR exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters* OR exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it properly formats when filters passed and "showEventFilters" is false', () => { + test('it properly formats when filters passed and "showEventFilters" is true', () => { const filter = getFilters({ filters: { created_by: 'moi', name: 'Sample' }, namespaceTypes: ['single', 'agnostic'], showTrustedApps: false, + showEventFilters: true, + showHostIsolationExceptions: false, + }); + + expect(filter).toEqual( + '(exception-list.attributes.created_by:moi OR exception-list-agnostic.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample OR exception-list-agnostic.attributes.name.text:Sample) AND (not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters* OR exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' + ); + }); + test('it properly formats when no filters passed and "showHostIsolationExceptions" is true', () => { + const filter = getFilters({ + filters: {}, + namespaceTypes: ['single', 'agnostic'], + showTrustedApps: false, showEventFilters: false, + showHostIsolationExceptions: true, }); expect(filter).toEqual( - '(exception-list.attributes.created_by:moi OR exception-list-agnostic.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample OR exception-list-agnostic.attributes.name.text:Sample) AND (not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (exception-list.attributes.list_id: endpoint_host_isolation_exceptions* OR exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); - test('it properly formats when filters passed and "showEventFilters" is true', () => { + test('it properly formats when filters passed and "showHostIsolationExceptions" is true', () => { const filter = getFilters({ filters: { created_by: 'moi', name: 'Sample' }, namespaceTypes: ['single', 'agnostic'], showTrustedApps: false, - showEventFilters: true, + showEventFilters: false, + showHostIsolationExceptions: true, }); expect(filter).toEqual( - '(exception-list.attributes.created_by:moi OR exception-list-agnostic.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample OR exception-list-agnostic.attributes.name.text:Sample) AND (not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters* OR exception-list-agnostic.attributes.list_id: endpoint_event_filters*)' + '(exception-list.attributes.created_by:moi OR exception-list-agnostic.attributes.created_by:moi) AND (exception-list.attributes.name.text:Sample OR exception-list-agnostic.attributes.name.text:Sample) AND (not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (exception-list.attributes.list_id: endpoint_host_isolation_exceptions* OR exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' ); }); }); diff --git a/packages/kbn-securitysolution-list-utils/src/get_filters/index.ts b/packages/kbn-securitysolution-list-utils/src/get_filters/index.ts index 238ae5541343c..e8e9e6a581828 100644 --- a/packages/kbn-securitysolution-list-utils/src/get_filters/index.ts +++ b/packages/kbn-securitysolution-list-utils/src/get_filters/index.ts @@ -11,12 +11,14 @@ import { getGeneralFilters } from '../get_general_filters'; import { getSavedObjectTypes } from '../get_saved_object_types'; import { getTrustedAppsFilter } from '../get_trusted_apps_filter'; import { getEventFiltersFilter } from '../get_event_filters_filter'; +import { getHostIsolationExceptionsFilter } from '../get_host_isolation_exceptions_filter'; export interface GetFiltersParams { filters: ExceptionListFilter; namespaceTypes: NamespaceType[]; showTrustedApps: boolean; showEventFilters: boolean; + showHostIsolationExceptions: boolean; } export const getFilters = ({ @@ -24,12 +26,17 @@ export const getFilters = ({ namespaceTypes, showTrustedApps, showEventFilters, + showHostIsolationExceptions, }: GetFiltersParams): string => { const namespaces = getSavedObjectTypes({ namespaceType: namespaceTypes }); const generalFilters = getGeneralFilters(filters, namespaces); const trustedAppsFilter = getTrustedAppsFilter(showTrustedApps, namespaces); const eventFiltersFilter = getEventFiltersFilter(showEventFilters, namespaces); - return [generalFilters, trustedAppsFilter, eventFiltersFilter] + const hostIsolationExceptionsFilter = getHostIsolationExceptionsFilter( + showHostIsolationExceptions, + namespaces + ); + return [generalFilters, trustedAppsFilter, eventFiltersFilter, hostIsolationExceptionsFilter] .filter((filter) => filter.trim() !== '') .join(' AND '); }; diff --git a/packages/kbn-securitysolution-list-utils/src/get_host_isolation_exceptions_filter/index.test.ts b/packages/kbn-securitysolution-list-utils/src/get_host_isolation_exceptions_filter/index.test.ts new file mode 100644 index 0000000000000..30466f459cf65 --- /dev/null +++ b/packages/kbn-securitysolution-list-utils/src/get_host_isolation_exceptions_filter/index.test.ts @@ -0,0 +1,49 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { getHostIsolationExceptionsFilter } from '.'; + +describe('getHostIsolationExceptionsFilter', () => { + test('it returns filter to search for "exception-list" namespace host isolation exceptions', () => { + const filter = getHostIsolationExceptionsFilter(true, ['exception-list']); + + expect(filter).toEqual( + '(exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)' + ); + }); + + test('it returns filter to search for "exception-list" and "agnostic" namespace host isolation exceptions', () => { + const filter = getHostIsolationExceptionsFilter(true, [ + 'exception-list', + 'exception-list-agnostic', + ]); + + expect(filter).toEqual( + '(exception-list.attributes.list_id: endpoint_host_isolation_exceptions* OR exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' + ); + }); + + test('it returns filter to exclude "exception-list" namespace host isolation exceptions', () => { + const filter = getHostIsolationExceptionsFilter(false, ['exception-list']); + + expect(filter).toEqual( + '(not exception-list.attributes.list_id: endpoint_host_isolation_exceptions*)' + ); + }); + + test('it returns filter to exclude "exception-list" and "agnostic" namespace host isolation exceptions', () => { + const filter = getHostIsolationExceptionsFilter(false, [ + 'exception-list', + 'exception-list-agnostic', + ]); + + expect(filter).toEqual( + '(not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)' + ); + }); +}); diff --git a/packages/kbn-securitysolution-list-utils/src/get_host_isolation_exceptions_filter/index.ts b/packages/kbn-securitysolution-list-utils/src/get_host_isolation_exceptions_filter/index.ts new file mode 100644 index 0000000000000..d61f8fe7dac19 --- /dev/null +++ b/packages/kbn-securitysolution-list-utils/src/get_host_isolation_exceptions_filter/index.ts @@ -0,0 +1,27 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID } from '@kbn/securitysolution-list-constants'; +import { SavedObjectType } from '../types'; + +export const getHostIsolationExceptionsFilter = ( + showFilter: boolean, + namespaceTypes: SavedObjectType[] +): string => { + if (showFilter) { + const filters = namespaceTypes.map((namespace) => { + return `${namespace}.attributes.list_id: ${ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID}*`; + }); + return `(${filters.join(' OR ')})`; + } else { + const filters = namespaceTypes.map((namespace) => { + return `not ${namespace}.attributes.list_id: ${ENDPOINT_HOST_ISOLATION_EXCEPTIONS_LIST_ID}*`; + }); + return `(${filters.join(' AND ')})`; + } +}; diff --git a/packages/kbn-test/src/functional_tests/lib/babel_register_for_test_plugins.js b/packages/kbn-test/src/functional_tests/lib/babel_register_for_test_plugins.js index fcc2b0b0e3ca9..2ded0e509c253 100644 --- a/packages/kbn-test/src/functional_tests/lib/babel_register_for_test_plugins.js +++ b/packages/kbn-test/src/functional_tests/lib/babel_register_for_test_plugins.js @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -const Fs = require('fs'); const Path = require('path'); const { REPO_ROOT } = require('@kbn/dev-utils'); @@ -23,7 +22,7 @@ require('@babel/register')({ // TODO: should should probably remove this link back to the source Path.resolve(REPO_ROOT, 'x-pack/plugins/task_manager/server/config.ts'), Path.resolve(REPO_ROOT, 'src/core/utils/default_app_categories.ts'), - ].map((path) => Fs.realpathSync(path)), + ], babelrc: false, presets: [require.resolve('@kbn/babel-preset/node_preset')], extensions: ['.js', '.ts', '.tsx'], diff --git a/src/core/server/saved_objects/migrationsv2/integration_tests/batch_size_bytes.test.ts b/src/core/server/saved_objects/migrationsv2/integration_tests/batch_size_bytes.test.ts index 8e79e6342c0d5..b39c0b80cf42b 100644 --- a/src/core/server/saved_objects/migrationsv2/integration_tests/batch_size_bytes.test.ts +++ b/src/core/server/saved_objects/migrationsv2/integration_tests/batch_size_bytes.test.ts @@ -26,6 +26,30 @@ async function removeLogFile() { // ignore errors if it doesn't exist await fs.unlink(logFilePath).catch(() => void 0); } +function sortByTypeAndId(a: { type: string; id: string }, b: { type: string; id: string }) { + return a.type.localeCompare(b.type) || a.id.localeCompare(b.id); +} + +async function fetchDocuments(esClient: ElasticsearchClient, index: string) { + const { body } = await esClient.search({ + index, + body: { + query: { + match_all: {}, + }, + _source: ['type', 'id'], + }, + }); + + return body.hits.hits + .map((h) => ({ + ...h._source, + id: h._id, + })) + .sort(sortByTypeAndId); +} + +const assertMigratedDocuments = (arr: any[], target: any[]) => target.every((v) => arr.includes(v)); describe('migration v2', () => { let esServer: kbnTestServer.TestElasticsearchUtils; @@ -72,16 +96,11 @@ describe('migration v2', () => { await new Promise((resolve) => setTimeout(resolve, 5000)); const esClient: ElasticsearchClient = esServer.es.getClient(); - const migratedIndexResponse = await esClient.count({ - index: targetIndex, - }); - const oldIndexResponse = await esClient.count({ - index: '.kibana_7.14.0_001', - }); - - // Use a >= comparison since once Kibana has started it might create new - // documents like telemetry tasks - expect(migratedIndexResponse.body.count).toBeGreaterThanOrEqual(oldIndexResponse.body.count); + + // assert that the docs from the original index have been migrated rather than comparing a doc count after startup + const originalDocs = await fetchDocuments(esClient, '.kibana_7.14.0_001'); + const migratedDocs = await fetchDocuments(esClient, targetIndex); + expect(assertMigratedDocuments(migratedDocs, originalDocs)); }); it('fails with a descriptive message when a single document exceeds maxBatchSizeBytes', async () => { diff --git a/src/core/server/saved_objects/serialization/serializer.test.ts b/src/core/server/saved_objects/serialization/serializer.test.ts index 3fdeb4aa088e1..24eded55615c4 100644 --- a/src/core/server/saved_objects/serialization/serializer.test.ts +++ b/src/core/server/saved_objects/serialization/serializer.test.ts @@ -491,6 +491,52 @@ describe('#rawToSavedObject', () => { expect(actual).toHaveProperty('namespaces', ['baz']); }); }); + + describe('throws if provided invalid type', () => { + expect(() => + singleNamespaceSerializer.rawToSavedObject({ + _id: 'foo:bar', + _source: { + // @ts-expect-error expects a string + // eslint-disable-next-line + type: new String('foo'), + }, + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Expected saved object type to be a string but given [String] with [foo] value."` + ); + + expect(() => + singleNamespaceSerializer.rawToSavedObject({ + _id: 'foo:bar', + _source: { + // @ts-expect-error expects astring + type: { + toString() { + return 'foo'; + }, + }, + }, + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Expected saved object type to be a string but given [Object] with [foo] value."` + ); + }); + + describe('throws if provided invalid id', () => { + expect(() => + singleNamespaceSerializer.rawToSavedObject({ + // @ts-expect-error expects a string + // eslint-disable-next-line + _id: new String('foo:bar'), + _source: { + type: 'foo', + }, + }) + ).toThrowErrorMatchingInlineSnapshot( + `"Expected document id to be a string but given [String] with [foo:bar] value."` + ); + }); }); describe('#savedObjectToRaw', () => { diff --git a/src/core/server/saved_objects/serialization/serializer.ts b/src/core/server/saved_objects/serialization/serializer.ts index 5e27b3de24409..9d9d65e735866 100644 --- a/src/core/server/saved_objects/serialization/serializer.ts +++ b/src/core/server/saved_objects/serialization/serializer.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import typeDetect from 'type-detect'; import { LEGACY_URL_ALIAS_TYPE } from '../object_types'; import { decodeVersion, encodeVersion } from '../version'; import { ISavedObjectTypeRegistry } from '../saved_objects_type_registry'; @@ -236,6 +236,8 @@ function checkIdMatchesPrefix(id: string, prefix: string) { function assertNonEmptyString(value: string, name: string) { if (!value || typeof value !== 'string') { - throw new TypeError(`Expected "${value}" to be a ${name}`); + throw new TypeError( + `Expected ${name} to be a string but given [${typeDetect(value)}] with [${value}] value.` + ); } } diff --git a/src/core/server/ui_settings/saved_objects/migrations.test.ts b/src/core/server/ui_settings/saved_objects/migrations.test.ts index c454338f44c79..2d374b0c98424 100644 --- a/src/core/server/ui_settings/saved_objects/migrations.test.ts +++ b/src/core/server/ui_settings/saved_objects/migrations.test.ts @@ -162,4 +162,28 @@ describe('ui_settings 8.0.0 migrations', () => { migrationVersion: {}, }); }); + test('removes telemetry:optIn and xPackMonitoring:allowReport from ui_settings', () => { + const doc = { + type: 'config', + id: '8.0.0', + attributes: { + buildNum: 9007199254740991, + 'telemetry:optIn': false, + 'xPackMonitoring:allowReport': false, + }, + references: [], + updated_at: '2020-06-09T20:18:20.349Z', + migrationVersion: {}, + }; + expect(migration(doc)).toEqual({ + type: 'config', + id: '8.0.0', + attributes: { + buildNum: 9007199254740991, + }, + references: [], + updated_at: '2020-06-09T20:18:20.349Z', + migrationVersion: {}, + }); + }); }); diff --git a/src/core/server/ui_settings/saved_objects/migrations.ts b/src/core/server/ui_settings/saved_objects/migrations.ts index e5d1a6bd1aa25..88632923e5514 100644 --- a/src/core/server/ui_settings/saved_objects/migrations.ts +++ b/src/core/server/ui_settings/saved_objects/migrations.ts @@ -78,13 +78,16 @@ export const migrations = { '8.0.0': (doc: SavedObjectUnsanitizedDoc): SavedObjectSanitizedDoc => ({ ...doc, ...(doc.attributes && { - // owner: Team:Geo attributes: Object.keys(doc.attributes).reduce( (acc, key) => [ + // owner: Team:Geo 'visualization:regionmap:showWarnings', 'visualization:tileMap:WMSdefaults', 'visualization:tileMap:maxPrecision', + // owner: Team:Core + 'telemetry:optIn', + 'xPackMonitoring:allowReport', ].includes(key) ? { ...acc, diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index 4a8f9df4c4044..02d4046ca1a22 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -177,7 +177,6 @@ kibana_vars=( telemetry.allowChangingOptInStatus telemetry.enabled telemetry.optIn - telemetry.optInStatusUrl telemetry.sendUsageTo telemetry.sendUsageFrom tilemap.options.attribution diff --git a/src/plugins/telemetry/common/constants.ts b/src/plugins/telemetry/common/constants.ts index f6b99badca492..4493d0e3ba31c 100644 --- a/src/plugins/telemetry/common/constants.ts +++ b/src/plugins/telemetry/common/constants.ts @@ -6,24 +6,6 @@ * Side Public License, v 1. */ -import { i18n } from '@kbn/i18n'; - -/** - * config options opt into telemetry - */ -export const CONFIG_TELEMETRY = 'telemetry:optIn'; - -/** - * config description for opting into telemetry - */ -export const getConfigTelemetryDesc = () => { - // Can't find where it's used but copying it over from the legacy code just in case... - return i18n.translate('telemetry.telemetryConfigDescription', { - defaultMessage: - 'Help us improve the Elastic Stack by providing usage statistics for basic features. We will not share this data outside of Elastic.', - }); -}; - /** * The amount of time, in milliseconds, to wait between reports when enabled. * Currently 24 hours. diff --git a/src/plugins/telemetry/server/config/config.ts b/src/plugins/telemetry/server/config/config.ts index 8d75f0aba1726..166598371fe36 100644 --- a/src/plugins/telemetry/server/config/config.ts +++ b/src/plugins/telemetry/server/config/config.ts @@ -9,8 +9,6 @@ import { schema, TypeOf, Type } from '@kbn/config-schema'; import { getConfigPath } from '@kbn/utils'; import { PluginConfigDescriptor } from 'kibana/server'; -import { TELEMETRY_ENDPOINT } from '../../common/constants'; -import { deprecateEndpointConfigs } from './deprecations'; const clusterEnvSchema: [Type<'prod'>, Type<'staging'>] = [ schema.literal('prod'), @@ -36,34 +34,6 @@ const configSchema = schema.object({ schema.oneOf(clusterEnvSchema, { defaultValue: 'staging' }), schema.oneOf(clusterEnvSchema, { defaultValue: 'prod' }) ), - /** - * REMOVE IN 8.0 - INTERNAL CONFIG DEPRECATED IN 7.15 - * REPLACED WITH `telemetry.sendUsageTo: staging | prod` - */ - url: schema.conditional( - schema.contextRef('dist'), - schema.literal(false), // Point to staging if it's not a distributable release - schema.string({ - defaultValue: TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING, - }), - schema.string({ - defaultValue: TELEMETRY_ENDPOINT.MAIN_CHANNEL.PROD, - }) - ), - /** - * REMOVE IN 8.0 - INTERNAL CONFIG DEPRECATED IN 7.15 - * REPLACED WITH `telemetry.sendUsageTo: staging | prod` - */ - optInStatusUrl: schema.conditional( - schema.contextRef('dist'), - schema.literal(false), // Point to staging if it's not a distributable release - schema.string({ - defaultValue: TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.STAGING, - }), - schema.string({ - defaultValue: TELEMETRY_ENDPOINT.OPT_IN_STATUS_CHANNEL.PROD, - }) - ), sendUsageFrom: schema.oneOf([schema.literal('server'), schema.literal('browser')], { defaultValue: 'server', }), @@ -81,5 +51,4 @@ export const config: PluginConfigDescriptor = { sendUsageFrom: true, sendUsageTo: true, }, - deprecations: () => [deprecateEndpointConfigs], }; diff --git a/src/plugins/telemetry/server/config/deprecations.test.ts b/src/plugins/telemetry/server/config/deprecations.test.ts deleted file mode 100644 index 567ef69e8991c..0000000000000 --- a/src/plugins/telemetry/server/config/deprecations.test.ts +++ /dev/null @@ -1,197 +0,0 @@ -/* - * 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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { configDeprecationsMock } from '../../../../core/server/mocks'; -import { deprecateEndpointConfigs } from './deprecations'; -import type { TelemetryConfigType } from './config'; -import { TELEMETRY_ENDPOINT } from '../../common/constants'; - -describe('deprecateEndpointConfigs', () => { - const fromPath = 'telemetry'; - const mockAddDeprecation = jest.fn(); - const deprecationContext = configDeprecationsMock.createContext(); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - function createMockRawConfig(telemetryConfig?: Partial) { - return { - elasticsearch: { username: 'kibana_system', password: 'changeme' }, - plugins: { paths: [] }, - server: { port: 5603, basePath: '/hln', rewriteBasePath: true }, - logging: { json: false }, - ...(telemetryConfig ? { telemetry: telemetryConfig } : {}), - }; - } - - it('returns void if telemetry.* config is not set', () => { - const rawConfig = createMockRawConfig(); - const result = deprecateEndpointConfigs( - rawConfig, - fromPath, - mockAddDeprecation, - deprecationContext - ); - expect(result).toBe(undefined); - }); - - it('sets "telemetryConfig.sendUsageTo: staging" if "telemetry.url" uses the staging endpoint', () => { - const rawConfig = createMockRawConfig({ - url: TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING, - }); - const result = deprecateEndpointConfigs( - rawConfig, - fromPath, - mockAddDeprecation, - deprecationContext - ); - expect(result).toMatchInlineSnapshot(` - Object { - "set": Array [ - Object { - "path": "telemetry.sendUsageTo", - "value": "staging", - }, - ], - "unset": Array [ - Object { - "path": "telemetry.url", - }, - ], - } - `); - }); - - it('sets "telemetryConfig.sendUsageTo: prod" if "telemetry.url" uses the non-staging endpoint', () => { - const rawConfig = createMockRawConfig({ - url: 'random-endpoint', - }); - const result = deprecateEndpointConfigs( - rawConfig, - fromPath, - mockAddDeprecation, - deprecationContext - ); - expect(result).toMatchInlineSnapshot(` - Object { - "set": Array [ - Object { - "path": "telemetry.sendUsageTo", - "value": "prod", - }, - ], - "unset": Array [ - Object { - "path": "telemetry.url", - }, - ], - } - `); - }); - - it('sets "telemetryConfig.sendUsageTo: staging" if "telemetry.optInStatusUrl" uses the staging endpoint', () => { - const rawConfig = createMockRawConfig({ - optInStatusUrl: TELEMETRY_ENDPOINT.MAIN_CHANNEL.STAGING, - }); - const result = deprecateEndpointConfigs( - rawConfig, - fromPath, - mockAddDeprecation, - deprecationContext - ); - expect(result).toMatchInlineSnapshot(` - Object { - "set": Array [ - Object { - "path": "telemetry.sendUsageTo", - "value": "staging", - }, - ], - "unset": Array [ - Object { - "path": "telemetry.optInStatusUrl", - }, - ], - } - `); - }); - - it('sets "telemetryConfig.sendUsageTo: prod" if "telemetry.optInStatusUrl" uses the non-staging endpoint', () => { - const rawConfig = createMockRawConfig({ - optInStatusUrl: 'random-endpoint', - }); - const result = deprecateEndpointConfigs( - rawConfig, - fromPath, - mockAddDeprecation, - deprecationContext - ); - expect(result).toMatchInlineSnapshot(` - Object { - "set": Array [ - Object { - "path": "telemetry.sendUsageTo", - "value": "prod", - }, - ], - "unset": Array [ - Object { - "path": "telemetry.optInStatusUrl", - }, - ], - } - `); - }); - - it('registers deprecation when "telemetry.url" is set', () => { - const rawConfig = createMockRawConfig({ - url: TELEMETRY_ENDPOINT.MAIN_CHANNEL.PROD, - }); - deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation, deprecationContext); - expect(mockAddDeprecation).toBeCalledTimes(1); - expect(mockAddDeprecation.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "configPath": "telemetry.url", - "correctiveActions": Object { - "manualSteps": Array [ - "Remove \\"telemetry.url\\" from the Kibana configuration.", - "To send usage to the staging endpoint add \\"telemetry.sendUsageTo: staging\\" to the Kibana configuration.", - ], - }, - "message": "\\"telemetry.url\\" has been deprecated. Set \\"telemetry.sendUsageTo: staging\\" to the Kibana configurations to send usage to the staging endpoint.", - "title": "Setting \\"telemetry.url\\" is deprecated", - }, - ] - `); - }); - - it('registers deprecation when "telemetry.optInStatusUrl" is set', () => { - const rawConfig = createMockRawConfig({ - optInStatusUrl: 'random-endpoint', - }); - deprecateEndpointConfigs(rawConfig, fromPath, mockAddDeprecation, deprecationContext); - expect(mockAddDeprecation).toBeCalledTimes(1); - expect(mockAddDeprecation.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "configPath": "telemetry.optInStatusUrl", - "correctiveActions": Object { - "manualSteps": Array [ - "Remove \\"telemetry.optInStatusUrl\\" from the Kibana configuration.", - "To send usage to the staging endpoint add \\"telemetry.sendUsageTo: staging\\" to the Kibana configuration.", - ], - }, - "message": "\\"telemetry.optInStatusUrl\\" has been deprecated. Set \\"telemetry.sendUsageTo: staging\\" to the Kibana configurations to send usage to the staging endpoint.", - "title": "Setting \\"telemetry.optInStatusUrl\\" is deprecated", - }, - ] - `); - }); -}); diff --git a/src/plugins/telemetry/server/config/deprecations.ts b/src/plugins/telemetry/server/config/deprecations.ts deleted file mode 100644 index 38553be7d5774..0000000000000 --- a/src/plugins/telemetry/server/config/deprecations.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import type { ConfigDeprecation } from 'kibana/server'; -import type { TelemetryConfigType } from './config'; - -export const deprecateEndpointConfigs: ConfigDeprecation = ( - rawConfig, - fromPath, - addDeprecation -) => { - const telemetryConfig: TelemetryConfigType = rawConfig[fromPath]; - if (!telemetryConfig) { - return; - } - - const unset: Array<{ path: string }> = []; - const endpointConfigPaths = ['url', 'optInStatusUrl'] as const; - let useStaging = telemetryConfig.sendUsageTo === 'staging' ? true : false; - - for (const configPath of endpointConfigPaths) { - const configValue = telemetryConfig[configPath]; - const fullConfigPath = `telemetry.${configPath}`; - if (typeof configValue !== 'undefined') { - unset.push({ path: fullConfigPath }); - - if (/telemetry-staging\.elastic\.co/i.test(configValue)) { - useStaging = true; - } - - addDeprecation({ - configPath: fullConfigPath, - title: i18n.translate('telemetry.endpointConfigs.deprecationTitle', { - defaultMessage: 'Setting "{configPath}" is deprecated', - values: { configPath: fullConfigPath }, - }), - message: i18n.translate('telemetry.endpointConfigs.deprecationMessage', { - defaultMessage: - '"{configPath}" has been deprecated. Set "telemetry.sendUsageTo: staging" to the Kibana configurations to send usage to the staging endpoint.', - values: { configPath: fullConfigPath }, - }), - correctiveActions: { - manualSteps: [ - i18n.translate('telemetry.endpointConfigs.deprecationManualStep1', { - defaultMessage: 'Remove "{configPath}" from the Kibana configuration.', - values: { configPath: fullConfigPath }, - }), - i18n.translate('telemetry.endpointConfigs.deprecationManualStep2', { - defaultMessage: - 'To send usage to the staging endpoint add "telemetry.sendUsageTo: staging" to the Kibana configuration.', - }), - ], - }, - }); - } - } - - return { - set: [{ path: 'telemetry.sendUsageTo', value: useStaging ? 'staging' : 'prod' }], - unset, - }; -}; diff --git a/src/plugins/telemetry/server/handle_old_settings/handle_old_settings.ts b/src/plugins/telemetry/server/handle_old_settings/handle_old_settings.ts deleted file mode 100644 index 3f1908c6668e8..0000000000000 --- a/src/plugins/telemetry/server/handle_old_settings/handle_old_settings.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** - * Clean up any old, deprecated settings and determine if we should continue. - * - * This will update the latest telemetry setting if necessary. - * - * @param {Object} config The advanced settings config object. - * @return {Boolean} {@code true} if the banner should still be displayed. {@code false} if the banner should not be displayed. - */ - -import { IUiSettingsClient, SavedObjectsClientContract } from 'kibana/server'; -import { CONFIG_TELEMETRY } from '../../common/constants'; -import { updateTelemetrySavedObject } from '../telemetry_repository'; - -const CONFIG_ALLOW_REPORT = 'xPackMonitoring:allowReport'; - -export async function handleOldSettings( - savedObjectsClient: SavedObjectsClientContract, - uiSettingsClient: IUiSettingsClient -) { - const oldTelemetrySetting = await uiSettingsClient.get(CONFIG_TELEMETRY); - const oldAllowReportSetting = await uiSettingsClient.get(CONFIG_ALLOW_REPORT); - let legacyOptInValue = null; - - if (typeof oldTelemetrySetting === 'boolean') { - legacyOptInValue = oldTelemetrySetting; - } else if ( - typeof oldAllowReportSetting === 'boolean' && - uiSettingsClient.isOverridden(CONFIG_ALLOW_REPORT) - ) { - legacyOptInValue = oldAllowReportSetting; - } - - if (legacyOptInValue !== null) { - await updateTelemetrySavedObject(savedObjectsClient, { - enabled: legacyOptInValue, - }); - } -} diff --git a/src/plugins/telemetry/server/handle_old_settings/index.ts b/src/plugins/telemetry/server/handle_old_settings/index.ts deleted file mode 100644 index e747c2547c6e3..0000000000000 --- a/src/plugins/telemetry/server/handle_old_settings/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * 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 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { handleOldSettings } from './handle_old_settings'; diff --git a/src/plugins/telemetry/server/plugin.ts b/src/plugins/telemetry/server/plugin.ts index d38f054a4402e..21fd85018d6db 100644 --- a/src/plugins/telemetry/server/plugin.ts +++ b/src/plugins/telemetry/server/plugin.ts @@ -7,7 +7,7 @@ */ import { URL } from 'url'; -import { AsyncSubject, Observable } from 'rxjs'; +import { Observable } from 'rxjs'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { TelemetryCollectionManagerPluginSetup, @@ -22,7 +22,6 @@ import { SavedObjectsClient, Plugin, Logger, - UiSettingsServiceStart, } from '../../../core/server'; import { registerRoutes } from './routes'; import { registerCollection } from './telemetry_collection'; @@ -32,7 +31,6 @@ import { } from './collectors'; import type { TelemetryConfigType } from './config'; import { FetcherTask } from './fetcher'; -import { handleOldSettings } from './handle_old_settings'; import { getTelemetrySavedObject } from './telemetry_repository'; import { getTelemetryOptIn, getTelemetryChannelEndpoint } from '../common/telemetry_config'; @@ -79,7 +77,6 @@ export class TelemetryPlugin implements Plugin) { @@ -126,19 +123,16 @@ export class TelemetryPlugin implements Plugin { - await this.oldUiSettingsHandled$.pipe(take(1)).toPromise(); // Wait for the old settings to be handled const internalRepository = new SavedObjectsClient(savedObjectsInternalRepository); const telemetrySavedObject = await getTelemetrySavedObject(internalRepository); + const config = await this.config$.pipe(take(1)).toPromise(); const allowChangingOptInStatus = config.allowChangingOptInStatus; const configTelemetryOptIn = typeof config.optIn === 'undefined' ? null : config.optIn; @@ -155,24 +149,11 @@ export class TelemetryPlugin implements Plugin `--plugin-path=${resolve(KIBANA_ROOT, 'examples', exampleDir)}` ), diff --git a/vars/tasks.groovy b/vars/tasks.groovy index 5a015bddc8fbc..da18d73e5b36c 100644 --- a/vars/tasks.groovy +++ b/vars/tasks.groovy @@ -146,7 +146,6 @@ def functionalXpack(Map params = [:]) { } } - //temporarily disable apm e2e test since it's breaking. // whenChanged([ // 'x-pack/plugins/apm/', // ]) { diff --git a/x-pack/plugins/apm/dev_docs/local_setup.md b/x-pack/plugins/apm/dev_docs/local_setup.md index eaa99560400e6..21d861fbb4e0b 100644 --- a/x-pack/plugins/apm/dev_docs/local_setup.md +++ b/x-pack/plugins/apm/dev_docs/local_setup.md @@ -1,6 +1,4 @@ -## Local environment setup - -### Kibana +# Start Kibana ``` git clone git@github.com:elastic/kibana.git @@ -9,25 +7,35 @@ yarn kbn bootstrap yarn start --no-base-path ``` -### APM Server, Elasticsearch and data +# Elasticsearch, APM Server and data generators To access an elasticsearch instance that has live data you have two options: -#### A. Connect to Elasticsearch on Cloud (internal devs only) +## A. Cloud-based ES Cluster (internal devs only) -Find the credentials for the cluster [here](https://github.com/elastic/observability-dev/blob/master/docs/observability-clusters.md) +Use the [oblt-cli](https://github.com/elastic/observability-test-environments/blob/master/tools/oblt_cli/README.md) to connect to a cloud-based ES cluster. -#### B. Start Elastic Stack and APM data generators +## B. Local ES Cluster +### Start Elasticsearch and APM data generators +_Docker Compose is required_ ``` git clone git@github.com:elastic/apm-integration-testing.git cd apm-integration-testing/ ./scripts/compose.py start master --all --no-kibana ``` -_Docker Compose is required_ +### Connect Kibana to Elasticsearch -### Setup default APM users +Update `config/kibana.dev.yml` with: + +```yml +elasticsearch.hosts: http://localhost:9200 +elasticsearch.username: admin +elasticsearch.password: changeme +``` + +# Setup default APM users APM behaves differently depending on which the role and permissions a logged in user has. To create the users run: @@ -37,11 +45,10 @@ node x-pack/plugins/apm/scripts/create-apm-users-and-roles.js --username admin - This will create: -**apm_read_user**: Read only user - -**apm_power_user**: Read+write user. + - **apm_read_user**: Read only user + - **apm_power_user**: Read+write user. -## Debugging Elasticsearch queries +# Debugging Elasticsearch queries All APM api endpoints accept `_inspect=true` as a query param that will output all Elasticsearch queries performed in that request. It will be available in the browser response and on localhost it is also available in the Kibana Node.js process output. diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/rules/error_count.spec.ts b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/rules/error_count.spec.ts index 42da37aa7ef57..5b4a48b65b33f 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/rules/error_count.spec.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/integration/power_user/rules/error_count.spec.ts @@ -5,6 +5,27 @@ * 2.0. */ +function deleteAllRules() { + cy.request({ + log: false, + method: 'GET', + url: '/api/alerting/rules/_find', + }).then(({ body }) => { + if (body.data.length > 0) { + cy.log(`Deleting rules`); + } + + body.data.map(({ id }: { id: string }) => { + cy.request({ + headers: { 'kbn-xsrf': 'true' }, + log: false, + method: 'DELETE', + url: `/api/alerting/rule/${id}`, + }); + }); + }); +} + describe('Rules', () => { describe('Error count', () => { const ruleName = 'Error count threshold'; @@ -12,59 +33,30 @@ describe('Rules', () => { '.euiPopover__panel-isOpen [data-test-subj=comboBoxSearchInput]'; const confirmModalButtonSelector = '.euiModal button[data-test-subj=confirmModalConfirmButton]'; - const deleteButtonSelector = - '[data-test-subj=deleteActionHoverButton]:first'; - const editButtonSelector = '[data-test-subj=editActionHoverButton]:first'; describe('when created from APM', () => { describe('when created from Service Inventory', () => { before(() => { cy.loginAsPowerUser(); + deleteAllRules(); }); - it('creates and updates a rule', () => { + after(() => { + deleteAllRules(); + }); + + it('creates a rule', () => { // Create a rule in APM cy.visit('/app/apm/services'); cy.contains('Alerts and rules').click(); cy.contains('Error count').click(); cy.contains('Create threshold rule').click(); - // Change the environment to "testing" - cy.contains('Environment All').click(); - cy.get(comboBoxInputSelector).type('testing{enter}'); - // Save, with no actions cy.contains('button:not(:disabled)', 'Save').click(); cy.get(confirmModalButtonSelector).click(); cy.contains(`Created rule "${ruleName}`); - - // Go to Stack Management - cy.contains('Alerts and rules').click(); - cy.contains('Manage rules').click(); - - // Edit the rule, changing the environment to "All" - cy.get(editButtonSelector).click(); - cy.contains('Environment testing').click(); - cy.get(comboBoxInputSelector).type('All{enter}'); - cy.contains('button:not(:disabled)', 'Save').click(); - - cy.contains(`Updated '${ruleName}'`); - - // Wait for the table to be ready for next edit click - cy.get('.euiBasicTable').not('.euiBasicTable-loading'); - - // Ensure the rule now shows "All" for the environment - cy.get(editButtonSelector).click(); - cy.contains('Environment All'); - cy.contains('button', 'Cancel').click(); - - // Delete the rule - cy.get(deleteButtonSelector).click(); - cy.get(confirmModalButtonSelector).click(); - - // Ensure the table is empty - cy.contains('Create your first rule'); }); }); }); @@ -72,14 +64,29 @@ describe('Rules', () => { describe('when created from Stack management', () => { before(() => { cy.loginAsPowerUser(); + deleteAllRules(); + cy.intercept( + 'GET', + '/api/alerting/rules/_find?page=1&per_page=10&default_search_operator=AND&sort_field=name&sort_order=asc' + ).as('list rules API call'); + }); + + after(() => { + deleteAllRules(); }); it('creates a rule', () => { // Go to stack management cy.visit('/app/management/insightsAndAlerting/triggersActions/rules'); + // Wait for this call to finish so the create rule button does not disappear. + // The timeout is set high because at this point we're also waiting for the + // full page load. + cy.wait('@list rules API call', { timeout: 30000 }); + // Create a rule cy.contains('button', 'Create rule').click(); + cy.get('[name=name]').type(ruleName); cy.contains('.euiFlyout button', ruleName).click(); @@ -92,16 +99,6 @@ describe('Rules', () => { cy.get(confirmModalButtonSelector).click(); cy.contains(`Created rule "${ruleName}`); - - // Wait for the table to be ready for next delete click - cy.get('.euiBasicTable').not('.euiBasicTable-loading'); - - // Delete the rule - cy.get(deleteButtonSelector).click(); - cy.get(confirmModalButtonSelector).click(); - - // Ensure the table is empty - cy.contains('Create your first rule'); }); }); }); diff --git a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts index 93dbe4ba51226..519cb0aa31cdb 100644 --- a/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts +++ b/x-pack/plugins/apm/ftr_e2e/cypress/support/commands.ts @@ -21,6 +21,7 @@ Cypress.Commands.add( cy.log(`Logging in as ${username}`); const kibanaUrl = Cypress.env('KIBANA_URL'); cy.request({ + log: false, method: 'POST', url: `${kibanaUrl}/internal/security/login`, body: { diff --git a/x-pack/plugins/apm/public/components/shared/time_comparison/get_comparison_types.ts b/x-pack/plugins/apm/public/components/shared/time_comparison/get_comparison_types.ts index a7520fa65a162..0115718ac07a9 100644 --- a/x-pack/plugins/apm/public/components/shared/time_comparison/get_comparison_types.ts +++ b/x-pack/plugins/apm/public/components/shared/time_comparison/get_comparison_types.ts @@ -16,14 +16,14 @@ export function getComparisonTypes({ start?: string; end?: string; }) { - const momentStart = moment(start); - const momentEnd = moment(end); + const momentStart = moment(start).startOf('second'); + const momentEnd = moment(end).startOf('second'); const dateDiff = getDateDifference({ start: momentStart, end: momentEnd, - unitOfTime: 'days', precise: true, + unitOfTime: 'days', }); // Less than or equals to one day diff --git a/x-pack/plugins/apm/public/components/shared/time_comparison/index.test.tsx b/x-pack/plugins/apm/public/components/shared/time_comparison/index.test.tsx index c29d258b37541..ce7d05d467291 100644 --- a/x-pack/plugins/apm/public/components/shared/time_comparison/index.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/time_comparison/index.test.tsx @@ -96,6 +96,19 @@ describe('TimeComparison', () => { TimeRangeComparisonType.WeekBefore.valueOf(), ]); }); + + it('shows week and day before when 24 hours is selected but milliseconds are different', () => { + expect( + getComparisonTypes({ + start: '2021-10-15T00:52:59.554Z', + end: '2021-10-14T00:52:59.553Z', + }) + ).toEqual([ + TimeRangeComparisonType.DayBefore.valueOf(), + TimeRangeComparisonType.WeekBefore.valueOf(), + ]); + }); + it('shows week before when 25 hours is selected', () => { expect( getComparisonTypes({ diff --git a/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts b/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts index 52c1c71446d64..b27248a3cb933 100644 --- a/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts +++ b/x-pack/plugins/fleet/server/services/managed_package_policies.test.ts @@ -7,9 +7,12 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks'; -import { upgradeManagedPackagePolicies } from './managed_package_policies'; +import type { Installation, PackageInfo } from '../../common'; +import { AUTO_UPDATE_PACKAGES } from '../../common'; + +import { shouldUpgradePolicies, upgradeManagedPackagePolicies } from './managed_package_policies'; import { packagePolicyService } from './package_policy'; -import { getPackageInfo } from './epm/packages'; +import { getPackageInfo, getInstallation } from './epm/packages'; jest.mock('./package_policy'); jest.mock('./epm/packages'); @@ -24,11 +27,12 @@ jest.mock('./app_context', () => { }; }); -describe('managed package policies', () => { +describe('upgradeManagedPackagePolicies', () => { afterEach(() => { (packagePolicyService.get as jest.Mock).mockReset(); (packagePolicyService.getUpgradeDryRunDiff as jest.Mock).mockReset(); (getPackageInfo as jest.Mock).mockReset(); + (getInstallation as jest.Mock).mockReset(); (packagePolicyService.upgrade as jest.Mock).mockReset(); }); @@ -50,7 +54,7 @@ describe('managed package policies', () => { package: { name: 'non-managed-package', title: 'Non-Managed Package', - version: '0.0.1', + version: '1.0.0', }, }; } @@ -74,6 +78,11 @@ describe('managed package policies', () => { }) ); + (getInstallation as jest.Mock).mockResolvedValueOnce({ + id: 'test-installation', + version: '0.0.1', + }); + await upgradeManagedPackagePolicies(soClient, esClient, ['non-managed-package-id']); expect(packagePolicyService.upgrade).not.toBeCalled(); @@ -121,6 +130,11 @@ describe('managed package policies', () => { }) ); + (getInstallation as jest.Mock).mockResolvedValueOnce({ + id: 'test-installation', + version: '1.0.0', + }); + await upgradeManagedPackagePolicies(soClient, esClient, ['managed-package-id']); expect(packagePolicyService.upgrade).toBeCalledWith(soClient, esClient, ['managed-package-id']); @@ -172,6 +186,11 @@ describe('managed package policies', () => { }) ); + (getInstallation as jest.Mock).mockResolvedValueOnce({ + id: 'test-installation', + version: '1.0.0', + }); + const result = await upgradeManagedPackagePolicies(soClient, esClient, [ 'conflicting-package-policy', ]); @@ -206,3 +225,133 @@ describe('managed package policies', () => { }); }); }); + +describe('shouldUpgradePolicies', () => { + describe('package is marked as AUTO_UPDATE', () => { + describe('keep_policies_up_to_date is true', () => { + it('returns false', () => { + const packageInfo = { + version: '1.0.0', + keepPoliciesUpToDate: true, + name: AUTO_UPDATE_PACKAGES[0].name, + }; + + const installedPackage = { + version: '1.0.0', + }; + + const result = shouldUpgradePolicies( + packageInfo as PackageInfo, + installedPackage as Installation + ); + + expect(result).toBe(false); + }); + }); + + describe('keep_policies_up_to_date is false', () => { + it('returns false', () => { + const packageInfo = { + version: '1.0.0', + keepPoliciesUpToDate: false, + name: AUTO_UPDATE_PACKAGES[0].name, + }; + + const installedPackage = { + version: '1.0.0', + }; + + const result = shouldUpgradePolicies( + packageInfo as PackageInfo, + installedPackage as Installation + ); + + expect(result).toBe(false); + }); + }); + }); + + describe('package policy is up-to-date', () => { + describe('keep_policies_up_to_date is true', () => { + it('returns false', () => { + const packageInfo = { + version: '1.0.0', + keepPoliciesUpToDate: true, + }; + + const installedPackage = { + version: '1.0.0', + }; + + const result = shouldUpgradePolicies( + packageInfo as PackageInfo, + installedPackage as Installation + ); + + expect(result).toBe(false); + }); + }); + + describe('keep_policies_up_to_date is false', () => { + it('returns false', () => { + const packageInfo = { + version: '1.0.0', + keepPoliciesUpToDate: false, + }; + + const installedPackage = { + version: '1.0.0', + }; + + const result = shouldUpgradePolicies( + packageInfo as PackageInfo, + installedPackage as Installation + ); + + expect(result).toBe(false); + }); + }); + }); + + describe('package policy is out-of-date', () => { + describe('keep_policies_up_to_date is true', () => { + it('returns true', () => { + const packageInfo = { + version: '1.0.0', + keepPoliciesUpToDate: true, + }; + + const installedPackage = { + version: '1.1.0', + }; + + const result = shouldUpgradePolicies( + packageInfo as PackageInfo, + installedPackage as Installation + ); + + expect(result).toBe(true); + }); + }); + + describe('keep_policies_up_to_date is false', () => { + it('returns false', () => { + const packageInfo = { + version: '1.0.0', + keepPoliciesUpToDate: false, + }; + + const installedPackage = { + version: '1.1.0', + }; + + const result = shouldUpgradePolicies( + packageInfo as PackageInfo, + installedPackage as Installation + ); + + expect(result).toBe(false); + }); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/managed_package_policies.ts b/x-pack/plugins/fleet/server/services/managed_package_policies.ts index 25e2482892712..306725ae01953 100644 --- a/x-pack/plugins/fleet/server/services/managed_package_policies.ts +++ b/x-pack/plugins/fleet/server/services/managed_package_policies.ts @@ -6,9 +6,13 @@ */ import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server'; +import semverGte from 'semver/functions/gte'; -import type { UpgradePackagePolicyDryRunResponseItem } from '../../common'; -import { AUTO_UPDATE_PACKAGES } from '../../common'; +import type { + Installation, + PackageInfo, + UpgradePackagePolicyDryRunResponseItem, +} from '../../common'; import { appContextService } from './app_context'; import { getInstallation, getPackageInfo } from './epm/packages'; @@ -16,7 +20,7 @@ import { packagePolicyService } from './package_policy'; export interface UpgradeManagedPackagePoliciesResult { packagePolicyId: string; - diff: UpgradePackagePolicyDryRunResponseItem['diff']; + diff?: UpgradePackagePolicyDryRunResponseItem['diff']; errors: any; } @@ -49,15 +53,16 @@ export const upgradeManagedPackagePolicies = async ( pkgName: packagePolicy.package.name, }); - const isPolicyVersionAlignedWithInstalledVersion = - packageInfo.version === installedPackage?.version; + if (!installedPackage) { + results.push({ + packagePolicyId, + errors: [`${packagePolicy.package.name} is not installed`], + }); - const shouldUpgradePolicies = - !isPolicyVersionAlignedWithInstalledVersion && - (AUTO_UPDATE_PACKAGES.some((pkg) => pkg.name === packageInfo.name) || - packageInfo.keepPoliciesUpToDate); + continue; + } - if (shouldUpgradePolicies) { + if (shouldUpgradePolicies(packageInfo, installedPackage)) { // Since upgrades don't report diffs/errors, we need to perform a dry run first in order // to notify the user of any granular policy upgrade errors that occur during Fleet's // preconfiguration check @@ -91,3 +96,15 @@ export const upgradeManagedPackagePolicies = async ( return results; }; + +export function shouldUpgradePolicies( + packageInfo: PackageInfo, + installedPackage: Installation +): boolean { + const isPolicyVersionGteInstalledVersion = semverGte( + packageInfo.version, + installedPackage.version + ); + + return !isPolicyVersionGteInstalledVersion && !!packageInfo.keepPoliciesUpToDate; +} diff --git a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts index b24defcdcd79c..8ee05bfa5d322 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/helpers/test_subjects.ts @@ -25,6 +25,8 @@ export type TestSubjects = | 'ilmPolicyLink' | 'includeStatsSwitch' | 'includeManagedSwitch' + | 'indexActionsContextMenuButton' + | 'indexContextMenu' | 'indexManagementHeaderContent' | 'indexTable' | 'indexTableIncludeHiddenIndicesToggle' diff --git a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts index 01593200a6cd5..900f7ddbf084b 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/home/indices_tab.helpers.ts @@ -28,6 +28,8 @@ export interface IndicesTestBed extends TestBed { getIncludeHiddenIndicesToggleStatus: () => boolean; clickIncludeHiddenIndicesToggle: () => void; clickDataStreamAt: (index: number) => void; + clickManageContextMenuButton: () => void; + clickContextMenuOption: (optionDataTestSubject: string) => void; }; findDataStreamDetailPanel: () => ReactWrapper; findDataStreamDetailPanelTitle: () => string; @@ -44,11 +46,22 @@ export const setup = async (overridingDependencies: any = {}): Promise { + const { find } = testBed; + const contextMenu = find('indexContextMenu'); + contextMenu.find(`button[data-test-subj="${optionDataTestSubject}"]`).simulate('click'); + }; + const clickIncludeHiddenIndicesToggle = () => { const { find } = testBed; find('indexTableIncludeHiddenIndicesToggle').simulate('click'); }; + const clickManageContextMenuButton = () => { + const { find } = testBed; + find('indexActionsContextMenuButton').simulate('click'); + }; + const getIncludeHiddenIndicesToggleStatus = () => { const { find } = testBed; const props = find('indexTableIncludeHiddenIndicesToggle').props(); @@ -95,6 +108,8 @@ export const setup = async (overridingDependencies: any = {}): Promise', () => { expect(latestRequest.url).toBe(`${API_BASE_PATH}/settings/${encodeURIComponent(indexName)}`); }); }); + + describe('index actions', () => { + const indexName = 'testIndex'; + beforeEach(async () => { + const index = { + health: 'green', + status: 'open', + primary: 1, + replica: 1, + documents: 10000, + documents_deleted: 100, + size: '156kb', + primary_size: '156kb', + name: indexName, + }; + + httpRequestsMockHelpers.setLoadIndicesResponse([index]); + testBed = await setup(); + const { find, component } = testBed; + component.update(); + + find('indexTableIndexNameLink').at(0).simulate('click'); + }); + + test('should be able to flush index', async () => { + const { actions } = testBed; + await actions.clickManageContextMenuButton(); + await actions.clickContextMenuOption('flushIndexMenuButton'); + + const latestRequest = server.requests[server.requests.length - 1]; + expect(latestRequest.url).toBe(`${API_BASE_PATH}/indices/flush`); + }); + }); }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts index 810fcaa15494f..bb4ad821b39cc 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts +++ b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts @@ -49,6 +49,7 @@ describe('useExceptionLists', () => { namespaceTypes: ['single', 'agnostic'], notifications: mockKibanaNotificationsService, showEventFilters: false, + showHostIsolationExceptions: false, showTrustedApps: false, }) ); @@ -86,6 +87,7 @@ describe('useExceptionLists', () => { namespaceTypes: ['single', 'agnostic'], notifications: mockKibanaNotificationsService, showEventFilters: false, + showHostIsolationExceptions: false, showTrustedApps: false, }) ); @@ -127,6 +129,7 @@ describe('useExceptionLists', () => { namespaceTypes: ['single', 'agnostic'], notifications: mockKibanaNotificationsService, showEventFilters: false, + showHostIsolationExceptions: false, showTrustedApps: true, }) ); @@ -137,7 +140,7 @@ describe('useExceptionLists', () => { expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({ filters: - '(exception-list.attributes.list_id: endpoint_trusted_apps* OR exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)', + '(exception-list.attributes.list_id: endpoint_trusted_apps* OR exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)', http: mockKibanaHttpService, namespaceTypes: 'single,agnostic', pagination: { page: 1, perPage: 20 }, @@ -163,6 +166,7 @@ describe('useExceptionLists', () => { namespaceTypes: ['single', 'agnostic'], notifications: mockKibanaNotificationsService, showEventFilters: false, + showHostIsolationExceptions: false, showTrustedApps: false, }) ); @@ -173,7 +177,7 @@ describe('useExceptionLists', () => { expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({ filters: - '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)', + '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)', http: mockKibanaHttpService, namespaceTypes: 'single,agnostic', pagination: { page: 1, perPage: 20 }, @@ -199,6 +203,7 @@ describe('useExceptionLists', () => { namespaceTypes: ['single', 'agnostic'], notifications: mockKibanaNotificationsService, showEventFilters: true, + showHostIsolationExceptions: false, showTrustedApps: false, }) ); @@ -209,7 +214,7 @@ describe('useExceptionLists', () => { expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({ filters: - '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters* OR exception-list-agnostic.attributes.list_id: endpoint_event_filters*)', + '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (exception-list.attributes.list_id: endpoint_event_filters* OR exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)', http: mockKibanaHttpService, namespaceTypes: 'single,agnostic', pagination: { page: 1, perPage: 20 }, @@ -235,6 +240,7 @@ describe('useExceptionLists', () => { namespaceTypes: ['single', 'agnostic'], notifications: mockKibanaNotificationsService, showEventFilters: false, + showHostIsolationExceptions: false, showTrustedApps: false, }) ); @@ -245,7 +251,81 @@ describe('useExceptionLists', () => { expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({ filters: - '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)', + '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)', + http: mockKibanaHttpService, + namespaceTypes: 'single,agnostic', + pagination: { page: 1, perPage: 20 }, + signal: new AbortController().signal, + }); + }); + }); + + test('fetches host isolation exceptions lists if "hostIsolationExceptionsFilter" is true', async () => { + const spyOnfetchExceptionLists = jest.spyOn(api, 'fetchExceptionLists'); + + await act(async () => { + const { waitForNextUpdate } = renderHook(() => + useExceptionLists({ + errorMessage: 'Uh oh', + filterOptions: {}, + http: mockKibanaHttpService, + initialPagination: { + page: 1, + perPage: 20, + total: 0, + }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, + showEventFilters: false, + showHostIsolationExceptions: true, + showTrustedApps: false, + }) + ); + // NOTE: First `waitForNextUpdate` is initialization + // Second call applies the params + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({ + filters: + '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (exception-list.attributes.list_id: endpoint_host_isolation_exceptions* OR exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)', + http: mockKibanaHttpService, + namespaceTypes: 'single,agnostic', + pagination: { page: 1, perPage: 20 }, + signal: new AbortController().signal, + }); + }); + }); + + test('does not fetch host isolation exceptions lists if "showHostIsolationExceptions" is false', async () => { + const spyOnfetchExceptionLists = jest.spyOn(api, 'fetchExceptionLists'); + + await act(async () => { + const { waitForNextUpdate } = renderHook(() => + useExceptionLists({ + errorMessage: 'Uh oh', + filterOptions: {}, + http: mockKibanaHttpService, + initialPagination: { + page: 1, + perPage: 20, + total: 0, + }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, + showEventFilters: false, + showHostIsolationExceptions: false, + showTrustedApps: false, + }) + ); + // NOTE: First `waitForNextUpdate` is initialization + // Second call applies the params + await waitForNextUpdate(); + await waitForNextUpdate(); + + expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({ + filters: + '(not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)', http: mockKibanaHttpService, namespaceTypes: 'single,agnostic', pagination: { page: 1, perPage: 20 }, @@ -274,6 +354,7 @@ describe('useExceptionLists', () => { namespaceTypes: ['single', 'agnostic'], notifications: mockKibanaNotificationsService, showEventFilters: false, + showHostIsolationExceptions: false, showTrustedApps: false, }) ); @@ -284,7 +365,7 @@ describe('useExceptionLists', () => { expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({ filters: - '(exception-list.attributes.created_by:Moi OR exception-list-agnostic.attributes.created_by:Moi) AND (exception-list.attributes.name.text:Sample Endpoint OR exception-list-agnostic.attributes.name.text:Sample Endpoint) AND (not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*)', + '(exception-list.attributes.created_by:Moi OR exception-list-agnostic.attributes.created_by:Moi) AND (exception-list.attributes.name.text:Sample Endpoint OR exception-list-agnostic.attributes.name.text:Sample Endpoint) AND (not exception-list.attributes.list_id: endpoint_trusted_apps* AND not exception-list-agnostic.attributes.list_id: endpoint_trusted_apps*) AND (not exception-list.attributes.list_id: endpoint_event_filters* AND not exception-list-agnostic.attributes.list_id: endpoint_event_filters*) AND (not exception-list.attributes.list_id: endpoint_host_isolation_exceptions* AND not exception-list-agnostic.attributes.list_id: endpoint_host_isolation_exceptions*)', http: mockKibanaHttpService, namespaceTypes: 'single,agnostic', pagination: { page: 1, perPage: 20 }, @@ -318,6 +399,7 @@ describe('useExceptionLists', () => { namespaceTypes, notifications, showEventFilters, + showHostIsolationExceptions: false, showTrustedApps, }), { @@ -333,6 +415,7 @@ describe('useExceptionLists', () => { namespaceTypes: ['single'], notifications: mockKibanaNotificationsService, showEventFilters: false, + showHostIsolationExceptions: false, showTrustedApps: false, }, } @@ -354,6 +437,7 @@ describe('useExceptionLists', () => { namespaceTypes: ['single', 'agnostic'], notifications: mockKibanaNotificationsService, showEventFilters: false, + showHostIsolationExceptions: false, showTrustedApps: false, }); // NOTE: Only need one call here because hook already initilaized @@ -382,6 +466,7 @@ describe('useExceptionLists', () => { namespaceTypes: ['single', 'agnostic'], notifications: mockKibanaNotificationsService, showEventFilters: false, + showHostIsolationExceptions: false, showTrustedApps: false, }) ); @@ -421,6 +506,7 @@ describe('useExceptionLists', () => { namespaceTypes: ['single', 'agnostic'], notifications: mockKibanaNotificationsService, showEventFilters: false, + showHostIsolationExceptions: false, showTrustedApps: false, }) ); diff --git a/x-pack/plugins/security_solution/cypress/screens/alerts.ts b/x-pack/plugins/security_solution/cypress/screens/alerts.ts index 0a815705f5b21..c9660668f488b 100644 --- a/x-pack/plugins/security_solution/cypress/screens/alerts.ts +++ b/x-pack/plugins/security_solution/cypress/screens/alerts.ts @@ -58,7 +58,7 @@ export const TAKE_ACTION_POPOVER_BTN = '[data-test-subj="selectedShowBulkActions export const TIMELINE_CONTEXT_MENU_BTN = '[data-test-subj="timeline-context-menu-button"]'; -export const ATTACH_ALERT_TO_CASE_BUTTON = '[data-test-subj="attach-alert-to-case-button"]'; +export const ATTACH_ALERT_TO_CASE_BUTTON = '[data-test-subj="add-existing-case-menu-item"]'; export const ALERT_COUNT_TABLE_FIRST_ROW_COUNT = '[data-test-subj="alertsCountTable"] tr:nth-child(1) td:nth-child(2) .euiTableCellContent__text'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx index 5c2d5f5d62b5c..8528d64b7261d 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/exceptions/exceptions_table.tsx @@ -85,6 +85,7 @@ export const ExceptionListsTable = React.memo(() => { notifications, showTrustedApps: false, showEventFilters: false, + showHostIsolationExceptions: false, }); const [loadingTableInfo, exceptionListsWithRuleRefs, exceptionsListsRef] = useAllExceptionLists({ exceptionLists: exceptions ?? [], diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx index 0ccdf9bcb388d..ee52e1210a481 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/empty/policy_trusted_apps_empty_unassigned.tsx @@ -10,6 +10,7 @@ import { EuiEmptyPrompt, EuiButton, EuiPageTemplate, EuiLink } from '@elastic/eu import { FormattedMessage } from '@kbn/i18n/react'; import { usePolicyDetailsNavigateCallback } from '../../policy_hooks'; import { useGetLinkTo } from './use_policy_trusted_apps_empty_hooks'; +import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/use_endpoint_privileges'; interface CommonProps { policyId: string; @@ -17,6 +18,7 @@ interface CommonProps { } export const PolicyTrustedAppsEmptyUnassigned = memo(({ policyId, policyName }) => { + const { isPlatinumPlus } = useEndpointPrivileges(); const navigateCallback = usePolicyDetailsNavigateCallback(); const { onClickHandler, toRouteUrl } = useGetLinkTo(policyId, policyName); const onClickPrimaryButtonHandler = useCallback( @@ -47,12 +49,21 @@ export const PolicyTrustedAppsEmptyUnassigned = memo(({ policyId, p /> } actions={[ - - - , + ...(isPlatinumPlus + ? [ + + + , + ] + : []), // eslint-disable-next-line @elastic/eui/href-or-on-click { title={ } /> diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx index 5d5d36d41aaf8..8ae0d9d45c236 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/trusted_apps/layout/policy_trusted_apps_layout.test.tsx @@ -19,8 +19,20 @@ import { createLoadedResourceState, isLoadedResourceState } from '../../../../.. import { getPolicyDetailsArtifactsListPath } from '../../../../../common/routing'; import { EndpointDocGenerator } from '../../../../../../../common/endpoint/generate_data'; import { policyListApiPathHandlers } from '../../../store/test_mock_utils'; +import { licenseService } from '../../../../../../common/hooks/use_license'; jest.mock('../../../../trusted_apps/service'); +jest.mock('../../../../../../common/hooks/use_license', () => { + const licenseServiceInstance = { + isPlatinumPlus: jest.fn(), + }; + return { + licenseService: licenseServiceInstance, + useLicense: () => { + return licenseServiceInstance; + }, + }; +}); let mockedContext: AppContextTestRender; let waitForAction: MiddlewareActionSpyHelper['waitForAction']; @@ -106,4 +118,31 @@ describe('Policy trusted apps layout', () => { expect(component.getByTestId('policyDetailsTrustedAppsCount')).not.toBeNull(); }); + + it('should hide assign button on empty state with unassigned policies when downgraded to a gold or below license', async () => { + (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false); + const component = render(); + mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234')); + + await waitForAction('assignedTrustedAppsListStateChanged'); + + mockedContext.store.dispatch({ + type: 'policyArtifactsDeosAnyTrustedAppExists', + payload: createLoadedResourceState(true), + }); + expect(component.queryByTestId('assign-ta-button')).toBeNull(); + }); + it('should hide the `Assign trusted applications` button when there is data and the license is downgraded to gold or below', async () => { + (licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false); + TrustedAppsHttpServiceMock.mockImplementation(() => { + return { + getTrustedAppsList: () => getMockListResponse(), + }; + }); + const component = render(); + mockedContext.history.push(getPolicyDetailsArtifactsListPath('1234')); + + await waitForAction('assignedTrustedAppsListStateChanged'); + expect(component.queryByTestId('assignTrustedAppButton')).toBeNull(); + }); }); 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 64e40e330ad2b..2421602f4e5af 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 @@ -25,6 +25,7 @@ import { import { usePolicyDetailsNavigateCallback, usePolicyDetailsSelector } from '../../policy_hooks'; import { PolicyTrustedAppsFlyout } from '../flyout'; import { PolicyTrustedAppsList } from '../list/policy_trusted_apps_list'; +import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/use_endpoint_privileges'; export const PolicyTrustedAppsLayout = React.memo(() => { const location = usePolicyDetailsSelector(getCurrentArtifactsLocation); @@ -33,6 +34,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => { const policyItem = usePolicyDetailsSelector(policyDetails); const navigateCallback = usePolicyDetailsNavigateCallback(); const hasAssignedTrustedApps = usePolicyDetailsSelector(doesPolicyHaveTrustedApps); + const { isPlatinumPlus } = useEndpointPrivileges(); const showListFlyout = location.show === 'list'; @@ -41,6 +43,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => { navigateCallback({ show: 'list', @@ -88,7 +91,7 @@ export const PolicyTrustedAppsLayout = React.memo(() => { - {assignTrustedAppButton} + {isPlatinumPlus && assignTrustedAppButton} ) : null} { )} - {showListFlyout ? : null} + {isPlatinumPlus && showListFlyout ? : null} ) : 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 ff94e3befe8c8..316b70064d9db 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 @@ -21,6 +21,13 @@ import { } from '../../../../../state'; import { fireEvent, within, act, waitFor } from '@testing-library/react'; import { APP_ID } from '../../../../../../../common/constants'; +import { + EndpointPrivileges, + useEndpointPrivileges, +} from '../../../../../../common/components/user_privileges/use_endpoint_privileges'; + +jest.mock('../../../../../../common/components/user_privileges/use_endpoint_privileges'); +const mockUseEndpointPrivileges = useEndpointPrivileges as jest.Mock; describe('when rendering the PolicyTrustedAppsList', () => { // The index (zero based) of the card created by the generator that is policy specific @@ -32,6 +39,16 @@ describe('when rendering the PolicyTrustedAppsList', () => { let mockedApis: ReturnType; let waitForAction: AppContextTestRender['middlewareSpy']['waitForAction']; + const loadedUserEndpointPrivilegesState = ( + endpointOverrides: Partial = {} + ): EndpointPrivileges => ({ + loading: false, + canAccessFleet: true, + canAccessEndpointManagement: true, + isPlatinumPlus: true, + ...endpointOverrides, + }); + const getCardByIndexPosition = (cardIndex: number = 0) => { const card = renderResult.getAllByTestId('policyTrustedAppsGrid-card')[cardIndex]; @@ -66,8 +83,12 @@ describe('when rendering the PolicyTrustedAppsList', () => { ); }; + afterAll(() => { + mockUseEndpointPrivileges.mockReset(); + }); beforeEach(() => { appTestContext = createAppRootMockRenderer(); + mockUseEndpointPrivileges.mockReturnValue(loadedUserEndpointPrivilegesState()); mockedApis = policyDetailsPageAllApiHttpMocks(appTestContext.coreStart.http); appTestContext.setExperimentalFlag({ trustedAppsByPolicyEnabled: true }); @@ -297,4 +318,16 @@ describe('when rendering the PolicyTrustedAppsList', () => { }) ); }); + + it('does not show remove option in actions menu if license is downgraded to gold or below', async () => { + await render(); + mockUseEndpointPrivileges.mockReturnValue( + loadedUserEndpointPrivilegesState({ + isPlatinumPlus: false, + }) + ); + await toggleCardActionMenu(POLICY_SPECIFIC_CARD_INDEX); + + expect(renderResult.queryByTestId('policyTrustedAppsGrid-removeAction')).toBeNull(); + }); }); 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 5d6c9731c7070..8ab2f5fd465e0 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 @@ -38,6 +38,7 @@ import { ContextMenuItemNavByRouterProps } from '../../../../../components/conte import { ArtifactEntryCollapsibleCardProps } from '../../../../../components/artifact_entry_card'; import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator'; import { RemoveTrustedAppFromPolicyModal } from './remove_trusted_app_from_policy_modal'; +import { useEndpointPrivileges } from '../../../../../../common/components/user_privileges/use_endpoint_privileges'; const DATA_TEST_SUBJ = 'policyTrustedAppsGrid'; @@ -46,6 +47,7 @@ export const PolicyTrustedAppsList = memo(() => { const toasts = useToasts(); const history = useHistory(); const { getAppUrl } = useAppUrl(); + const { isPlatinumPlus } = useEndpointPrivileges(); const policyId = usePolicyDetailsSelector(policyIdFromParams); const hasTrustedApps = usePolicyDetailsSelector(doesPolicyHaveTrustedApps); const isLoading = usePolicyDetailsSelector(isPolicyTrustedAppListLoading); @@ -132,44 +134,50 @@ export const PolicyTrustedAppsList = memo(() => { return byIdPolicies; }, {}); + const fullDetailsAction: ArtifactCardGridCardComponentProps['actions'] = [ + { + icon: 'controlsHorizontal', + children: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedApps.list.viewAction', + { defaultMessage: 'View full details' } + ), + href: getAppUrl({ appId: APP_ID, path: viewUrlPath }), + navigateAppId: APP_ID, + navigateOptions: { path: viewUrlPath }, + 'data-test-subj': getTestId('viewFullDetailsAction'), + }, + ]; const thisTrustedAppCardProps: ArtifactCardGridCardComponentProps = { expanded: Boolean(isCardExpanded[trustedApp.id]), - actions: [ - { - icon: 'controlsHorizontal', - children: i18n.translate( - 'xpack.securitySolution.endpoint.policy.trustedApps.list.viewAction', - { defaultMessage: 'View full details' } - ), - href: getAppUrl({ appId: APP_ID, path: viewUrlPath }), - navigateAppId: APP_ID, - navigateOptions: { path: viewUrlPath }, - 'data-test-subj': getTestId('viewFullDetailsAction'), - }, - { - icon: 'trash', - children: i18n.translate( - 'xpack.securitySolution.endpoint.policy.trustedApps.list.removeAction', - { defaultMessage: 'Remove from policy' } - ), - onClick: () => { - setTrustedAppsForRemoval([trustedApp]); - setShowRemovalModal(true); - }, - disabled: isGlobal, - toolTipContent: isGlobal - ? i18n.translate( - 'xpack.securitySolution.endpoint.policy.trustedApps.list.removeActionNotAllowed', - { - defaultMessage: - 'Globally applied trusted applications cannot be removed from policy.', - } - ) - : undefined, - toolTipPosition: 'top', - 'data-test-subj': getTestId('removeAction'), - }, - ], + actions: isPlatinumPlus + ? [ + ...fullDetailsAction, + { + icon: 'trash', + children: i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedApps.list.removeAction', + { defaultMessage: 'Remove from policy' } + ), + onClick: () => { + setTrustedAppsForRemoval([trustedApp]); + setShowRemovalModal(true); + }, + disabled: isGlobal, + toolTipContent: isGlobal + ? i18n.translate( + 'xpack.securitySolution.endpoint.policy.trustedApps.list.removeActionNotAllowed', + { + defaultMessage: + 'Globally applied trusted applications cannot be removed from policy.', + } + ) + : undefined, + toolTipPosition: 'top', + 'data-test-subj': getTestId('removeAction'), + }, + ] + : fullDetailsAction, + policies: assignedPoliciesMenuItems, }; @@ -177,7 +185,7 @@ export const PolicyTrustedAppsList = memo(() => { } return newCardProps; - }, [allPoliciesById, getAppUrl, getTestId, isCardExpanded, trustedAppItems]); + }, [allPoliciesById, getAppUrl, getTestId, isCardExpanded, trustedAppItems, isPlatinumPlus]); const provideCardProps = useCallback['cardComponentProps']>( (item) => { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 00fc646f237ea..6a97078082117 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4509,9 +4509,6 @@ "telemetry.callout.errorUnprivilegedUserDescription": "暗号化されていないクラスター統計を表示するアクセス権がありません。", "telemetry.callout.errorUnprivilegedUserTitle": "クラスター統計の表示エラー", "telemetry.clusterData": "クラスターデータ", - "telemetry.endpointConfigs.deprecationManualStep1": "Kibana構成から\"{configPath}\"を削除します。", - "telemetry.endpointConfigs.deprecationManualStep2": "使用状況をステージングエンドポイントに送信するには、「telemetry.sendUsageTo: staging」をKibana構成に追加します。", - "telemetry.endpointConfigs.deprecationMessage": "\"{configPath}\"は廃止予定にされました。「telemetry.sendUsageTo: staging」をKibana構成に設定し、使用状況をステージングエンドポイントに送信します。", "telemetry.optInErrorToastText": "使用状況統計設定の設定中にエラーが発生しました。", "telemetry.optInErrorToastTitle": "エラー", "telemetry.optInNoticeSeenErrorTitle": "エラー", @@ -4524,7 +4521,6 @@ "telemetry.securityData": "Endpoint Security データ", "telemetry.telemetryBannerDescription": "Elastic Stackの改善にご協力ください使用状況データの収集は現在無効です。使用状況データの収集を有効にすると、製品とサービスを管理して改善することができます。詳細については、{privacyStatementLink}をご覧ください。", "telemetry.telemetryConfigAndLinkDescription": "使用状況データの収集を有効にすると、製品とサービスを管理して改善することができます。詳細については、{privacyStatementLink}をご覧ください。", - "telemetry.telemetryConfigDescription": "基本的な機能の利用状況に関する統計情報を提供して、Elastic Stack の改善にご協力ください。このデータは Elastic 社外と共有されません。", "telemetry.telemetryOptedInDisableUsage": "ここで使用状況データを無効にする", "telemetry.telemetryOptedInDismissMessage": "閉じる", "telemetry.telemetryOptedInNoticeDescription": "使用状況データがどのように製品とサービスの管理と改善につながるのかに関する詳細については、{privacyStatementLink}をご覧ください。収集を停止するには、{disableLink}。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 05880e703120c..6e09814d015cc 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4554,9 +4554,6 @@ "telemetry.callout.errorUnprivilegedUserDescription": "您无权查看未加密的集群统计信息。", "telemetry.callout.errorUnprivilegedUserTitle": "显示集群统计信息时出错", "telemetry.clusterData": "集群数据", - "telemetry.endpointConfigs.deprecationManualStep1": "从 Kibana 配置中移除“{configPath}”。", - "telemetry.endpointConfigs.deprecationManualStep2": "要将使用数据发送给暂存终端,请将“telemetry.sendUsageTo: staging”添加到 Kibana 配置中。", - "telemetry.endpointConfigs.deprecationMessage": "“{configPath}”已弃用。在 Kibana 配置中设置“telemetry.sendUsageTo: staging”可将使用数据发送到暂存终端。", "telemetry.optInErrorToastText": "尝试设置使用情况统计信息首选项时发生错误。", "telemetry.optInErrorToastTitle": "错误", "telemetry.optInNoticeSeenErrorTitle": "错误", @@ -4569,7 +4566,6 @@ "telemetry.securityData": "终端安全数据", "telemetry.telemetryBannerDescription": "想帮助我们改进 Elastic Stack?数据使用情况收集当前已禁用。启用使用情况数据收集可帮助我们管理并改善产品和服务。有关更多详情,请参阅我们的{privacyStatementLink}。", "telemetry.telemetryConfigAndLinkDescription": "启用使用情况数据收集可帮助我们管理并改善产品和服务。有关更多详情,请参阅我们的{privacyStatementLink}。", - "telemetry.telemetryConfigDescription": "通过提供基本功能的使用情况统计信息,来帮助我们改进 Elastic Stack。我们不会在 Elastic 之外共享此数据。", "telemetry.telemetryOptedInDisableUsage": "请在此禁用使用情况数据", "telemetry.telemetryOptedInDismissMessage": "关闭", "telemetry.telemetryOptedInNoticeDescription": "要了解使用情况数据如何帮助我们管理和改善产品和服务,请参阅我们的{privacyStatementLink}。要停止收集,{disableLink}。", diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts index 8caae0afe746e..c15a7d39a6cf6 100644 --- a/x-pack/test/apm_api_integration/tests/index.ts +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -229,6 +229,10 @@ export default function apmApiIntegrationTests(providerContext: FtrProviderConte loadTestFile(require.resolve('./historical_data/has_data')); }); + describe('latency/service_apis', function () { + loadTestFile(require.resolve('./latency/service_apis')); + }); + registry.run(providerContext); }); } diff --git a/x-pack/test/apm_api_integration/tests/latency/service_apis.ts b/x-pack/test/apm_api_integration/tests/latency/service_apis.ts new file mode 100644 index 0000000000000..a09442cd73a2a --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/latency/service_apis.ts @@ -0,0 +1,191 @@ +/* + * 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 { service, timerange } from '@elastic/apm-generator'; +import expect from '@kbn/expect'; +import { meanBy, sumBy } from 'lodash'; +import { LatencyAggregationType } from '../../../../plugins/apm/common/latency_aggregation_types'; +import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number'; +import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const apmApiClient = getService('apmApiClient'); + const traceData = getService('traceData'); + + const serviceName = 'synth-go'; + const start = new Date('2021-01-01T00:00:00.000Z').getTime(); + const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1; + + async function getLatencyValues({ + processorEvent, + latencyAggregationType = LatencyAggregationType.avg, + }: { + processorEvent: 'transaction' | 'metric'; + latencyAggregationType?: LatencyAggregationType; + }) { + const commonQuery = { + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + environment: 'ENVIRONMENT_ALL', + }; + const [ + serviceInventoryAPIResponse, + serviceLantencyAPIResponse, + transactionsGroupDetailsAPIResponse, + serviceInstancesAPIResponse, + ] = await Promise.all([ + apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services', + params: { + query: { + ...commonQuery, + kuery: `service.name : "${serviceName}" and processor.event : "${processorEvent}"`, + }, + }, + }), + apmApiClient.readUser({ + endpoint: 'GET /internal/apm/services/{serviceName}/transactions/charts/latency', + params: { + path: { serviceName }, + query: { + ...commonQuery, + kuery: `processor.event : "${processorEvent}"`, + latencyAggregationType, + transactionType: 'request', + }, + }, + }), + apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/transactions/groups/main_statistics`, + params: { + path: { serviceName }, + query: { + ...commonQuery, + kuery: `processor.event : "${processorEvent}"`, + transactionType: 'request', + latencyAggregationType: 'avg' as LatencyAggregationType, + }, + }, + }), + apmApiClient.readUser({ + endpoint: `GET /internal/apm/services/{serviceName}/service_overview_instances/main_statistics`, + params: { + path: { serviceName }, + query: { + ...commonQuery, + kuery: `processor.event : "${processorEvent}"`, + transactionType: 'request', + latencyAggregationType: 'avg' as LatencyAggregationType, + }, + }, + }), + ]); + + const serviceInventoryLatency = serviceInventoryAPIResponse.body.items[0].latency; + + const latencyChartApiMean = meanBy( + serviceLantencyAPIResponse.body.currentPeriod.latencyTimeseries.filter( + (item) => isFiniteNumber(item.y) && item.y > 0 + ), + 'y' + ); + + const transactionsGroupLatencySum = sumBy( + transactionsGroupDetailsAPIResponse.body.transactionGroups, + 'latency' + ); + + const serviceInstancesLatencySum = sumBy( + serviceInstancesAPIResponse.body.currentPeriod, + 'latency' + ); + + return { + serviceInventoryLatency, + latencyChartApiMean, + transactionsGroupLatencySum, + serviceInstancesLatencySum, + }; + } + + let latencyMetricValues: PromiseReturnType; + let latencyTransactionValues: PromiseReturnType; + + registry.when('Services APIs', { config: 'basic', archives: ['apm_8.0.0_empty'] }, () => { + describe('when data is loaded ', () => { + const GO_PROD_RATE = 80; + const GO_DEV_RATE = 20; + const GO_PROD_DURATION = 1000; + const GO_DEV_DURATION = 500; + before(async () => { + const serviceGoProdInstance = service(serviceName, 'production', 'go').instance( + 'instance-a' + ); + const serviceGoDevInstance = service(serviceName, 'development', 'go').instance( + 'instance-b' + ); + await traceData.index([ + ...timerange(start, end) + .interval('1m') + .rate(GO_PROD_RATE) + .flatMap((timestamp) => + serviceGoProdInstance + .transaction('GET /api/product/list') + .duration(GO_PROD_DURATION) + .timestamp(timestamp) + .serialize() + ), + ...timerange(start, end) + .interval('1m') + .rate(GO_DEV_RATE) + .flatMap((timestamp) => + serviceGoDevInstance + .transaction('GET /api/product/:id') + .duration(GO_DEV_DURATION) + .timestamp(timestamp) + .serialize() + ), + ]); + }); + + after(() => traceData.clean()); + + describe('compare latency value between service inventory, latency chart, service inventory and transactions apis', () => { + before(async () => { + [latencyTransactionValues, latencyMetricValues] = await Promise.all([ + getLatencyValues({ processorEvent: 'transaction' }), + getLatencyValues({ processorEvent: 'metric' }), + ]); + }); + + it('returns same avg latency value for Transaction-based and Metric-based data', () => { + const expectedLatencyAvgValueMs = + ((GO_PROD_RATE * GO_PROD_DURATION + GO_DEV_RATE * GO_DEV_DURATION) / + (GO_PROD_RATE + GO_DEV_RATE)) * + 1000; + [ + latencyTransactionValues.latencyChartApiMean, + latencyTransactionValues.serviceInventoryLatency, + latencyMetricValues.latencyChartApiMean, + latencyMetricValues.serviceInventoryLatency, + ].forEach((value) => expect(value).to.be.equal(expectedLatencyAvgValueMs)); + }); + + it('returns same sum latency value for Transaction-based and Metric-based data', () => { + const expectedLatencySumValueMs = (GO_PROD_DURATION + GO_DEV_DURATION) * 1000; + [ + latencyTransactionValues.transactionsGroupLatencySum, + latencyTransactionValues.serviceInstancesLatencySum, + latencyMetricValues.transactionsGroupLatencySum, + latencyMetricValues.serviceInstancesLatencySum, + ].forEach((value) => expect(value).to.be.equal(expectedLatencySumValueMs)); + }); + }); + }); + }); +} diff --git a/x-pack/test/functional/apps/maps/documents_source/top_hits.js b/x-pack/test/functional/apps/maps/documents_source/top_hits.js index fa93d657aa3dd..b1998936316de 100644 --- a/x-pack/test/functional/apps/maps/documents_source/top_hits.js +++ b/x-pack/test/functional/apps/maps/documents_source/top_hits.js @@ -15,7 +15,8 @@ export default function ({ getPageObjects, getService }) { const find = getService('find'); const security = getService('security'); - describe('geo top hits', () => { + // Failing: See https://github.com/elastic/kibana/issues/115262 + describe.skip('geo top hits', () => { describe('split on string field', () => { before(async () => { await security.testUser.setRoles(['global_maps_all', 'test_logstash_reader'], false); diff --git a/x-pack/test/functional/apps/monitoring/elasticsearch/node_detail_mb.js b/x-pack/test/functional/apps/monitoring/elasticsearch/node_detail_mb.js index 9130ce91e7b4d..70c9b42b37f42 100644 --- a/x-pack/test/functional/apps/monitoring/elasticsearch/node_detail_mb.js +++ b/x-pack/test/functional/apps/monitoring/elasticsearch/node_detail_mb.js @@ -14,7 +14,8 @@ export default function ({ getService, getPageObjects }) { const nodesList = getService('monitoringElasticsearchNodes'); const nodeDetail = getService('monitoringElasticsearchNodeDetail'); - describe('Elasticsearch node detail mb', () => { + // Failing: See https://github.com/elastic/kibana/issues/115255 + describe.skip('Elasticsearch node detail mb', () => { describe('Active Nodes', () => { const { setup, tearDown } = getLifecycleMethods(getService, getPageObjects);