From 436c74a9ceb3b15550a63b74bb2c2773e3c60592 Mon Sep 17 00:00:00 2001 From: Xavier Mouligneau <189600+XavierM@users.noreply.github.com> Date: Mon, 25 Oct 2021 18:16:52 -0400 Subject: [PATCH 1/4] [SECURITY SOLUTION] [CASES] Allow cases to be there when security solutions privileges is none (#113573) * allow case to itself when security solutions privileges is none * bring back the right owner for cases * bring no privilege msg when needed it * fix types * fix test * adding test * review * deepLinks generation fixed * register home solution with old app id * fix get deep links * fix home link * fix unit test * add test * fix telemetry Co-authored-by: semd Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- packages/kbn-optimizer/limits.yml | 2 +- .../collectors/application_usage/schema.ts | 2 +- src/plugins/telemetry/schema/oss_plugins.json | 2 +- .../security_solution/common/constants.ts | 1 + .../public/app/deep_links/index.test.ts | 36 ++- .../public/app/deep_links/index.ts | 59 ++-- .../app/home/template_wrapper/index.tsx | 8 +- .../security_solution/public/app/index.tsx | 27 +- .../public/app/no_privileges.tsx | 47 +++ .../public/app/translations.ts | 18 ++ .../security_solution/public/app/types.ts | 4 +- .../cases/components/all_cases/index.tsx | 8 +- .../cases/components/case_view/index.tsx | 12 +- .../cases/components/create/index.test.tsx | 6 +- .../public/cases/components/create/index.tsx | 6 +- .../public/cases/pages/case_details.tsx | 4 +- .../public/cases/pages/configure_cases.tsx | 4 +- .../public/cases/pages/create_case.tsx | 4 +- .../public/cases/pages/utils.ts | 8 +- .../components/endpoint/link_to_app.tsx | 4 +- .../components/event_details/reason.tsx | 4 +- .../public/common/components/links/index.tsx | 12 +- .../navigation/breadcrumbs/index.test.ts | 72 ++--- .../navigation/breadcrumbs/index.ts | 4 +- .../navigation/tab_navigation/index.test.tsx | 2 +- .../index.test.tsx | 54 ++-- .../use_navigation_items.tsx | 79 +++-- .../components/user_privileges/index.tsx | 15 +- .../common/lib/kibana/__mocks__/index.ts | 4 +- .../public/common/lib/kibana/hooks.ts | 6 +- .../mock/endpoint/app_context_render.tsx | 6 +- .../public/common/mock/test_providers.tsx | 6 +- .../alerts_histogram_panel/index.test.tsx | 2 +- .../alerts_histogram_panel/index.tsx | 4 +- .../use_add_to_case_actions.tsx | 4 +- .../rules/rule_actions_overflow/index.tsx | 4 +- .../rules/step_rule_actions/index.tsx | 4 +- .../components/user_info/index.test.tsx | 4 +- .../use_fetch_detection_engine_privileges.ts | 6 +- .../use_fetch_list_privileges.ts | 6 +- .../detection_engine/rules/all/actions.tsx | 4 +- .../detection_engine/rules/all/columns.tsx | 6 +- .../detection_engine/rules/create/index.tsx | 8 +- .../detection_engine/rules/details/index.tsx | 6 +- .../detection_engine/rules/edit/index.tsx | 10 +- .../pages/detection_engine/rules/index.tsx | 6 +- .../pages/detection_engine/rules/utils.ts | 6 +- .../security_solution/public/helpers.test.ts | 62 ---- .../security_solution/public/helpers.test.tsx | 276 ++++++++++++++++++ .../public/{helpers.ts => helpers.tsx} | 80 ++++- .../public/hosts/pages/details/utils.ts | 6 +- .../context_menu_with_router_support.test.tsx | 6 +- .../view/hooks/use_endpoint_action_items.tsx | 8 +- .../pages/policy/view/policy_details.tsx | 4 +- .../components/policy_form_layout.test.tsx | 2 +- .../components/policy_form_layout.tsx | 4 +- .../policy_forms/protections/behavior.tsx | 4 +- .../view/policy_forms/protections/malware.tsx | 4 +- .../view/policy_forms/protections/memory.tsx | 4 +- .../policy_forms/protections/ransomware.tsx | 4 +- .../use_policy_trusted_apps_empty_hooks.ts | 8 +- .../list/policy_trusted_apps_list.test.tsx | 4 +- .../list/policy_trusted_apps_list.tsx | 8 +- .../components/trusted_apps_grid/index.tsx | 7 +- .../public/network/pages/details/utils.ts | 6 +- .../components/alerts_by_category/index.tsx | 4 +- .../components/endpoint_notice/index.tsx | 6 +- .../components/events_by_dataset/index.tsx | 4 +- .../components/overview_host/index.tsx | 4 +- .../overview_network/index.test.tsx | 2 +- .../components/overview_network/index.tsx | 4 +- .../navigate_to_host.tsx | 4 +- .../components/recent_cases/index.tsx | 8 +- .../components/recent_timelines/index.tsx | 4 +- .../security_solution/public/plugin.tsx | 43 +-- .../flyout/add_to_case_button/index.test.tsx | 4 +- .../flyout/add_to_case_button/index.tsx | 6 +- .../expandable_host.test.tsx.snap | 30 +- .../renderers/formatted_field_helpers.tsx | 6 +- .../public/timelines/pages/index.tsx | 4 +- .../plugins/security_solution/public/types.ts | 5 +- .../public/ueba/pages/details/utils.ts | 6 +- 82 files changed, 800 insertions(+), 447 deletions(-) create mode 100644 x-pack/plugins/security_solution/public/app/no_privileges.tsx delete mode 100644 x-pack/plugins/security_solution/public/helpers.test.ts create mode 100644 x-pack/plugins/security_solution/public/helpers.test.tsx rename x-pack/plugins/security_solution/public/{helpers.ts => helpers.tsx} (63%) diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index d1491ba63e6e6..453e5c174452d 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -94,7 +94,7 @@ pageLoadAssetSize: expressionShape: 34008 interactiveSetup: 80000 expressionTagcloud: 27505 - securitySolution: 231753 + securitySolution: 273763 customIntegrations: 28810 expressionMetricVis: 23121 visTypeMetric: 23332 diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts index 5f268a6fdfee7..7c112083875d1 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/schema.ts @@ -156,7 +156,7 @@ export const applicationUsageSchema = { security_login: commonSchema, security_logout: commonSchema, security_overwritten_session: commonSchema, - securitySolution: commonSchema, + securitySolutionUI: commonSchema, siem: commonSchema, space_selector: commonSchema, uptime: commonSchema, diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index f9ca99a26ec19..437d50ad82473 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -5018,7 +5018,7 @@ } } }, - "securitySolution": { + "securitySolutionUI": { "properties": { "appId": { "type": "keyword", diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 515f2beb53980..618497d8ea11b 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -12,6 +12,7 @@ import { ENABLE_CASE_CONNECTOR } from '../../cases/common'; import { METADATA_TRANSFORMS_PATTERN } from './endpoint/constants'; export const APP_ID = 'securitySolution'; +export const APP_UI_ID = 'securitySolutionUI'; export const CASES_FEATURE_ID = 'securitySolutionCases'; export const SERVER_APP_ID = 'siem'; export const APP_NAME = 'Security'; diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts index a3dc6565b19c6..479ff4753dd75 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.test.ts @@ -8,7 +8,7 @@ import { getDeepLinks, PREMIUM_DEEP_LINK_IDS } from '.'; import { AppDeepLink, Capabilities } from '../../../../../../src/core/public'; import { SecurityPageName } from '../types'; import { mockGlobalState } from '../../common/mock'; -import { CASES_FEATURE_ID } from '../../../common/constants'; +import { CASES_FEATURE_ID, SERVER_APP_ID } from '../../../common/constants'; const findDeepLink = (id: string, deepLinks: AppDeepLink[]): AppDeepLink | null => deepLinks.reduce((deepLinkFound: AppDeepLink | null, deepLink) => { @@ -24,10 +24,11 @@ const findDeepLink = (id: string, deepLinks: AppDeepLink[]): AppDeepLink | null return null; }, null); +const basicLicense = 'basic'; +const platinumLicense = 'platinum'; + describe('deepLinks', () => { it('should return a subset of links for basic license and the full set for platinum', () => { - const basicLicense = 'basic'; - const platinumLicense = 'platinum'; const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense); const platinumLinks = getDeepLinks(mockGlobalState.app.enableExperimental, platinumLicense); @@ -57,26 +58,25 @@ describe('deepLinks', () => { }); it('should return case links for basic license with only read_cases capabilities', () => { - const basicLicense = 'basic'; const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, { [CASES_FEATURE_ID]: { read_cases: true, crud_cases: false }, + [SERVER_APP_ID]: { show: true }, } as unknown as Capabilities); - expect(findDeepLink(SecurityPageName.case, basicLinks)).toBeTruthy(); }); it('should return case links with NO deepLinks for basic license with only read_cases capabilities', () => { - const basicLicense = 'basic'; const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, { [CASES_FEATURE_ID]: { read_cases: true, crud_cases: false }, + [SERVER_APP_ID]: { show: true }, } as unknown as Capabilities); expect(findDeepLink(SecurityPageName.case, basicLinks)?.deepLinks?.length === 0).toBeTruthy(); }); it('should return case links with deepLinks for basic license with crud_cases capabilities', () => { - const basicLicense = 'basic'; const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, { [CASES_FEATURE_ID]: { read_cases: true, crud_cases: true }, + [SERVER_APP_ID]: { show: true }, } as unknown as Capabilities); expect( @@ -84,17 +84,32 @@ describe('deepLinks', () => { ).toBeTruthy(); }); + it('should return case links with deepLinks for basic license with crud_cases capabilities and security disabled', () => { + const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, platinumLicense, { + [CASES_FEATURE_ID]: { read_cases: true, crud_cases: true }, + [SERVER_APP_ID]: { show: false }, + } as unknown as Capabilities); + expect(findDeepLink(SecurityPageName.case, basicLinks)).toBeTruthy(); + }); + it('should return NO case links for basic license with NO read_cases capabilities', () => { - const basicLicense = 'basic'; const basicLinks = getDeepLinks(mockGlobalState.app.enableExperimental, basicLicense, { [CASES_FEATURE_ID]: { read_cases: false, crud_cases: false }, + [SERVER_APP_ID]: { show: true }, } as unknown as Capabilities); - expect(findDeepLink(SecurityPageName.case, basicLinks)).toBeFalsy(); }); + it('should return empty links for any license', () => { + const emptyDeepLinks = getDeepLinks( + mockGlobalState.app.enableExperimental, + basicLicense, + {} as unknown as Capabilities + ); + expect(emptyDeepLinks.length).toBe(0); + }); + it('should return case links for basic license with undefined capabilities', () => { - const basicLicense = 'basic'; const basicLinks = getDeepLinks( mockGlobalState.app.enableExperimental, basicLicense, @@ -105,7 +120,6 @@ describe('deepLinks', () => { }); it('should return case deepLinks for basic license with undefined capabilities', () => { - const basicLicense = 'basic'; const basicLinks = getDeepLinks( mockGlobalState.app.enableExperimental, basicLicense, diff --git a/x-pack/plugins/security_solution/public/app/deep_links/index.ts b/x-pack/plugins/security_solution/public/app/deep_links/index.ts index aaa8ce789591f..8daec76f280b2 100644 --- a/x-pack/plugins/security_solution/public/app/deep_links/index.ts +++ b/x-pack/plugins/security_solution/public/app/deep_links/index.ts @@ -6,16 +6,11 @@ */ import { i18n } from '@kbn/i18n'; -import { Subject } from 'rxjs'; +import { isEmpty } from 'lodash'; import { LicenseType } from '../../../../licensing/common/types'; import { SecurityPageName } from '../types'; -import { - AppDeepLink, - ApplicationStart, - AppNavLinkStatus, - AppUpdater, -} from '../../../../../../src/core/public'; +import { AppDeepLink, ApplicationStart, AppNavLinkStatus } from '../../../../../../src/core/public'; import { OVERVIEW, DETECT, @@ -50,6 +45,7 @@ import { UEBA_PATH, CASES_FEATURE_ID, HOST_ISOLATION_EXCEPTIONS_PATH, + SERVER_APP_ID, } from '../../../common/constants'; import { ExperimentalFeatures } from '../../../common/experimental_features'; @@ -356,25 +352,18 @@ export function getDeepLinks( ): AppDeepLink[] { const isPremium = isPremiumLicense(licenseType); + /** + * Recursive DFS function to filter deepLinks by permissions (licence and capabilities). + * Checks "end" deepLinks with no children first, the other parent deepLinks will be included if + * they still have children deepLinks after filtering + */ const filterDeepLinks = (deepLinks: AppDeepLink[]): AppDeepLink[] => { return deepLinks - .filter((deepLink) => { - if (!isPremium && PREMIUM_DEEP_LINK_IDS.has(deepLink.id)) { - return false; - } - if (deepLink.id === SecurityPageName.case) { - return capabilities == null || capabilities[CASES_FEATURE_ID].read_cases === true; - } - if (deepLink.id === SecurityPageName.ueba) { - return enableExperimental.uebaEnabled; - } - return true; - }) .map((deepLink) => { if ( deepLink.id === SecurityPageName.case && capabilities != null && - capabilities[CASES_FEATURE_ID].crud_cases === false + capabilities[CASES_FEATURE_ID]?.crud_cases === false ) { return { ...deepLink, @@ -388,6 +377,21 @@ export function getDeepLinks( }; } return deepLink; + }) + .filter((deepLink) => { + if (!isPremium && PREMIUM_DEEP_LINK_IDS.has(deepLink.id)) { + return false; + } + if (deepLink.path && deepLink.path.startsWith(CASES_PATH)) { + return capabilities == null || capabilities[CASES_FEATURE_ID]?.read_cases === true; + } + if (deepLink.id === SecurityPageName.ueba) { + return enableExperimental.uebaEnabled; + } + if (!isEmpty(deepLink.deepLinks)) { + return true; + } + return capabilities == null || capabilities[SERVER_APP_ID]?.show === true; }); }; @@ -402,18 +406,3 @@ export function isPremiumLicense(licenseType?: LicenseType): boolean { licenseType === 'trial' ); } - -export function updateGlobalNavigation({ - capabilities, - updater$, - enableExperimental, -}: { - capabilities: ApplicationStart['capabilities']; - updater$: Subject; - enableExperimental: ExperimentalFeatures; -}) { - updater$.next(() => ({ - navLinkStatus: AppNavLinkStatus.hidden, // needed to prevent showing main nav link - deepLinks: getDeepLinks(enableExperimental, undefined, capabilities), - })); -} diff --git a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx index 1803ab2b67455..8588539c47a60 100644 --- a/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx +++ b/x-pack/plugins/security_solution/public/app/home/template_wrapper/index.tsx @@ -78,8 +78,12 @@ export const SecuritySolutionTemplateWrapper: React.FC void) => { const ApplicationUsageTrackingProvider = usageCollection?.components.ApplicationUsageTrackingProvider ?? React.Fragment; @@ -36,25 +35,9 @@ export const renderApp = ({ > - {[ - ...subPlugins.overview.routes, - ...subPlugins.alerts.routes, - ...subPlugins.rules.routes, - ...subPlugins.exceptions.routes, - ...subPlugins.hosts.routes, - ...subPlugins.network.routes, - // will be undefined if enabledExperimental.uebaEnabled === false - ...(subPlugins.ueba != null ? subPlugins.ueba.routes : []), - ...subPlugins.timelines.routes, - ...subPlugins.cases.routes, - ...subPlugins.management.routes, - ].map((route, index) => ( - - ))} - - - - + {subPluginRoutes.map((route, index) => { + return ; + })} diff --git a/x-pack/plugins/security_solution/public/app/no_privileges.tsx b/x-pack/plugins/security_solution/public/app/no_privileges.tsx new file mode 100644 index 0000000000000..354e6eaf27198 --- /dev/null +++ b/x-pack/plugins/security_solution/public/app/no_privileges.tsx @@ -0,0 +1,47 @@ +/* + * 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 React, { useMemo } from 'react'; + +import { EuiPageTemplate } from '@elastic/eui'; +import { SecuritySolutionPageWrapper } from '../common/components/page_wrapper'; +import { EmptyPage } from '../common/components/empty_page'; +import { useKibana } from '../common/lib/kibana'; +import * as i18n from './translations'; + +interface NoPrivilegesPageProps { + subPluginKey: string; +} + +export const NoPrivilegesPage = React.memo(({ subPluginKey }) => { + const { docLinks } = useKibana().services; + const emptyPageActions = useMemo( + () => ({ + feature: { + icon: 'documents', + label: i18n.GO_TO_DOCUMENTATION, + url: `${docLinks.links.siem.privileges}`, + target: '_blank', + }, + }), + [docLinks] + ); + return ( + + + + + + ); +}); + +NoPrivilegesPage.displayName = 'NoPrivilegePage'; diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index e383725a7e40c..7287739566e68 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -80,3 +80,21 @@ export const INVESTIGATE = i18n.translate('xpack.securitySolution.navigation.inv export const MANAGE = i18n.translate('xpack.securitySolution.navigation.manage', { defaultMessage: 'Manage', }); + +export const GO_TO_DOCUMENTATION = i18n.translate( + 'xpack.securitySolution.goToDocumentationButton', + { + defaultMessage: 'View documentation', + } +); + +export const NO_PERMISSIONS_MSG = (subPluginKey: string) => + i18n.translate('xpack.securitySolution.noPermissionsMessage', { + values: { subPluginKey }, + defaultMessage: + 'To view {subPluginKey}, you must update privileges. For more information, contact your Kibana administrator.', + }); + +export const NO_PERMISSIONS_TITLE = i18n.translate('xpack.securitySolution.noPermissionsTitle', { + defaultMessage: 'Privileges required', +}); diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts index 1942d2f836b1c..52d69d7c4e7d9 100644 --- a/x-pack/plugins/security_solution/public/app/types.ts +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -18,7 +18,7 @@ import { import { RouteProps } from 'react-router-dom'; import { AppMountParameters } from '../../../../../src/core/public'; import { UsageCollectionSetup } from '../../../../../src/plugins/usage_collection/public'; -import { StartedSubPlugins, StartServices } from '../types'; +import { StartServices } from '../types'; /** * The React properties used to render `SecurityApp` as well as the `element` to render it into. @@ -26,7 +26,7 @@ import { StartedSubPlugins, StartServices } from '../types'; export interface RenderAppProps extends AppMountParameters { services: StartServices; store: Store; - subPlugins: StartedSubPlugins; + subPluginRoutes: RouteProps[]; usageCollection?: UsageCollectionSetup; } diff --git a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx index 3c788e0553079..81eeae9866b7f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/all_cases/index.tsx @@ -15,7 +15,7 @@ import { } from '../../../common/components/link_to'; import { SecurityPageName } from '../../../app/types'; import { useKibana } from '../../../common/lib/kibana'; -import { APP_ID } from '../../../../common/constants'; +import { APP_ID, APP_UI_ID } from '../../../../common/constants'; export interface AllCasesNavProps { detailName: string; @@ -36,7 +36,7 @@ export const AllCases = React.memo(({ userCanCrud }) => { const goToCreateCase = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(APP_ID, { + return navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(urlSearch), }); @@ -47,7 +47,7 @@ export const AllCases = React.memo(({ userCanCrud }) => { const goToCaseConfigure = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(APP_ID, { + return navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getConfigureCasesUrl(urlSearch), }); @@ -61,7 +61,7 @@ export const AllCases = React.memo(({ userCanCrud }) => { return formatUrl(getCaseDetailsUrl({ id: detailName, subCaseId })); }, onClick: async ({ detailName, subCaseId, search }: AllCasesNavProps) => { - return navigateToApp(APP_ID, { + return navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: detailName, search, subCaseId }), }); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 47ba9e1e9cb8f..bdf4a1dbc1579 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -19,7 +19,7 @@ import { Case, CaseViewRefreshPropInterface } from '../../../../../cases/common' import { TimelineId } from '../../../../common/types/timeline'; import { SecurityPageName } from '../../../app/types'; import { useKibana } from '../../../common/lib/kibana'; -import { APP_ID } from '../../../../common/constants'; +import { APP_UI_ID } from '../../../../common/constants'; import { timelineActions } from '../../../timelines/store/timeline'; import { useSourcererScope } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; @@ -153,7 +153,7 @@ export const CaseView = React.memo( if (e) { e.preventDefault(); } - return navigateToApp(APP_ID, { + return navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: allCasesLink, }); @@ -165,7 +165,7 @@ export const CaseView = React.memo( if (e) { e.preventDefault(); } - return navigateToApp(APP_ID, { + return navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: caseId }), }); @@ -178,7 +178,7 @@ export const CaseView = React.memo( if (e) { e.preventDefault(); } - return navigateToApp(APP_ID, { + return navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getConfigureCasesUrl(search), }); @@ -193,7 +193,7 @@ export const CaseView = React.memo( if (e) { e.preventDefault(); } - return navigateToApp(APP_ID, { + return navigateToApp(APP_UI_ID, { path: getEndpointDetailsPath({ name: 'endpointActivityLog', selected_endpoint: endpointId, @@ -207,7 +207,7 @@ export const CaseView = React.memo( if (e) { e.preventDefault(); } - return navigateToApp(APP_ID, { + return navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(ruleId ?? ''), }); diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx index 42579c6fbc0ac..2ce5f2904cb3b 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.test.tsx @@ -16,7 +16,7 @@ import { Create } from '.'; import { useKibana } from '../../../common/lib/kibana'; import { Case } from '../../../../../cases/public/containers/types'; import { basicCase } from '../../../../../cases/public/containers/mock'; -import { APP_ID, SecurityPageName } from '../../../../common/constants'; +import { APP_ID, APP_UI_ID, SecurityPageName } from '../../../../common/constants'; import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; jest.mock('../use_insert_timeline'); @@ -71,7 +71,7 @@ describe('Create case', () => { ); await waitFor(() => - expect(mockNavigateToApp).toHaveBeenCalledWith(APP_ID, { + expect(mockNavigateToApp).toHaveBeenCalledWith(APP_UI_ID, { path: `?${mockRes}`, deepLinkId: SecurityPageName.case, }) @@ -96,7 +96,7 @@ describe('Create case', () => { ); await waitFor(() => - expect(mockNavigateToApp).toHaveBeenNthCalledWith(1, APP_ID, { + expect(mockNavigateToApp).toHaveBeenNthCalledWith(1, APP_UI_ID, { path: `/basic-case-id?${mockRes}`, deepLinkId: SecurityPageName.case, }) diff --git a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx index 72a41acf1d456..6e6a536cd8437 100644 --- a/x-pack/plugins/security_solution/public/cases/components/create/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/create/index.tsx @@ -11,7 +11,7 @@ import { getCaseDetailsUrl, getCaseUrl } from '../../../common/components/link_t import { useKibana } from '../../../common/lib/kibana'; import * as timelineMarkdownPlugin from '../../../common/components/markdown_editor/plugins/timeline'; import { useInsertTimeline } from '../use_insert_timeline'; -import { APP_ID } from '../../../../common/constants'; +import { APP_ID, APP_UI_ID } from '../../../../common/constants'; import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; import { navTabs } from '../../../app/home/home_navigations'; import { SecurityPageName } from '../../../app/types'; @@ -24,7 +24,7 @@ export const Create = React.memo(() => { const search = useGetUrlSearch(navTabs.case); const onSuccess = useCallback( async ({ id }) => - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id, search }), }), @@ -32,7 +32,7 @@ export const Create = React.memo(() => { ); const handleSetIsCancel = useCallback( async () => - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCaseUrl(search), }), diff --git a/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx index ea8205cddad59..6f35209ee1c9f 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/case_details.tsx @@ -16,7 +16,7 @@ import { useGetUserCasesPermissions, useKibana } from '../../common/lib/kibana'; import { getCaseUrl } from '../../common/components/link_to'; import { navTabs } from '../../app/home/home_navigations'; import { CaseView } from '../components/case_view'; -import { APP_ID } from '../../../common/constants'; +import { APP_UI_ID } from '../../../common/constants'; import { Case } from '../../../../cases/common'; export const CaseDetailsPage = React.memo(() => { @@ -32,7 +32,7 @@ export const CaseDetailsPage = React.memo(() => { useEffect(() => { if (userPermissions != null && !userPermissions.read) { - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCaseUrl(search), }); diff --git a/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx b/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx index c5ed3454f1ca5..b5feb3dc698b7 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/configure_cases.tsx @@ -18,7 +18,7 @@ import { navTabs } from '../../app/home/home_navigations'; import { CaseHeaderPage } from '../components/case_header_page'; import { WhitePageWrapper, SectionWrapper } from '../components/wrappers'; import * as i18n from './translations'; -import { APP_ID } from '../../../common/constants'; +import { APP_ID, APP_UI_ID } from '../../../common/constants'; const ConfigureCasesPageComponent: React.FC = () => { const { @@ -39,7 +39,7 @@ const ConfigureCasesPageComponent: React.FC = () => { useEffect(() => { if (userPermissions != null && !userPermissions.read) { - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCaseUrl(search), }); diff --git a/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx b/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx index 933e890ea2d9f..2d7d83cb1b50c 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/create_case.tsx @@ -17,7 +17,7 @@ import { navTabs } from '../../app/home/home_navigations'; import { CaseHeaderPage } from '../components/case_header_page'; import { Create } from '../components/create'; import * as i18n from './translations'; -import { APP_ID } from '../../../common/constants'; +import { APP_UI_ID } from '../../../common/constants'; export const CreateCasePage = React.memo(() => { const userPermissions = useGetUserCasesPermissions(); @@ -37,7 +37,7 @@ export const CreateCasePage = React.memo(() => { useEffect(() => { if (userPermissions != null && !userPermissions.crud) { - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCaseUrl(search), }); diff --git a/x-pack/plugins/security_solution/public/cases/pages/utils.ts b/x-pack/plugins/security_solution/public/cases/pages/utils.ts index 968712009e110..f791bc677d5c6 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/utils.ts +++ b/x-pack/plugins/security_solution/public/cases/pages/utils.ts @@ -13,7 +13,7 @@ import { getCaseDetailsUrl, getCreateCaseUrl } from '../../common/components/lin import { RouteSpyState } from '../../common/utils/route/types'; import * as i18n from './translations'; import { GetUrlForApp } from '../../common/components/navigation/types'; -import { APP_ID } from '../../../common/constants'; +import { APP_UI_ID } from '../../../common/constants'; import { SecurityPageName } from '../../app/types'; export const getBreadcrumbs = ( @@ -26,7 +26,7 @@ export const getBreadcrumbs = ( let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: getUrlForApp(APP_ID, { + href: getUrlForApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: queryParameters, }), @@ -37,7 +37,7 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: i18n.CREATE_BC_TITLE, - href: getUrlForApp(APP_ID, { + href: getUrlForApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(queryParameters), }), @@ -48,7 +48,7 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: params.state?.caseTitle ?? '', - href: getUrlForApp(APP_ID, { + href: getUrlForApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: params.detailName, search: queryParameters }), }), diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx index 201738d3293b2..dba8dce6d2df7 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/link_to_app.tsx @@ -8,7 +8,7 @@ import React, { memo, MouseEventHandler } from 'react'; import { EuiLink, EuiLinkProps, EuiButton, EuiButtonProps } from '@elastic/eui'; import { useNavigateToAppEventHandler } from '../../hooks/endpoint/use_navigate_to_app_event_handler'; -import { APP_ID } from '../../../../common/constants'; +import { APP_UI_ID } from '../../../../common/constants'; export type LinkToAppProps = (EuiLinkProps | EuiButtonProps) & { /** the app id - normally the value of the `id` in that plugin's `kibana.json` */ @@ -30,7 +30,7 @@ export type LinkToAppProps = (EuiLinkProps | EuiButtonProps) & { */ export const LinkToApp = memo( ({ - appId = APP_ID, + appId = APP_UI_ID, deepLinkId, appPath: path, appState: state, diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/reason.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/reason.tsx index aab0e86681783..88672e5e2f5dc 100644 --- a/x-pack/plugins/security_solution/public/common/components/event_details/reason.tsx +++ b/x-pack/plugins/security_solution/public/common/components/event_details/reason.tsx @@ -14,7 +14,7 @@ import * as i18n from './translations'; import { TimelineEventsDetailsItem } from '../../../../common'; import { LinkAnchor } from '../links'; import { useKibana } from '../../lib/kibana'; -import { APP_ID, SecurityPageName } from '../../../../common/constants'; +import { APP_UI_ID, SecurityPageName } from '../../../../common/constants'; import { EVENT_DETAILS_PLACEHOLDER } from '../../../timelines/components/side_panel/event_details/translations'; import { getFieldValue } from '../../../detections/components/host_isolation/helpers'; @@ -64,7 +64,7 @@ export const ReasonComponent: React.FC = ({ eventId, data }) => { data-test-subj="ruleName" onClick={(ev: { preventDefault: () => void }) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(ruleId), }); diff --git a/x-pack/plugins/security_solution/public/common/components/links/index.tsx b/x-pack/plugins/security_solution/public/common/components/links/index.tsx index c74791b8b3aa7..8b9188af7725c 100644 --- a/x-pack/plugins/security_solution/public/common/components/links/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/links/index.tsx @@ -16,7 +16,7 @@ import { import React, { useMemo, useCallback, SyntheticEvent } from 'react'; import { isNil } from 'lodash/fp'; -import { IP_REPUTATION_LINKS_SETTING, APP_ID } from '../../../../common/constants'; +import { IP_REPUTATION_LINKS_SETTING, APP_UI_ID } from '../../../../common/constants'; import { DefaultFieldRendererOverflow, DEFAULT_MORE_MAX_HEIGHT, @@ -56,7 +56,7 @@ const UebaDetailsLinkComponent: React.FC<{ const goToUebaDetails = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.ueba, path: getUebaDetailsUrl(encodeURIComponent(hostName), search), }); @@ -99,7 +99,7 @@ const HostDetailsLinkComponent: React.FC<{ const goToHostDetails = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.hosts, path: getHostDetailsUrl(encodeURIComponent(hostName), search), }); @@ -183,7 +183,7 @@ const NetworkDetailsLinkComponent: React.FC<{ const goToNetworkDetails = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.network, path: getNetworkDetailsUrl(encodeURIComponent(encodeIpv6(ip)), flowTarget, search), }); @@ -229,7 +229,7 @@ const CaseDetailsLinkComponent: React.FC<{ const goToCaseDetails = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(APP_ID, { + return navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: detailName, search, subCaseId }), }); @@ -257,7 +257,7 @@ export const CreateCaseLink = React.memo<{ children: React.ReactNode }>(({ child const goToCreateCase = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(APP_ID, { + return navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(search), }); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts index 08f2ef2267f97..de934e00c9117 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.test.ts @@ -158,11 +158,11 @@ describe('Navigation Breadcrumbs', () => { ); expect(breadcrumbs).toEqual([ { - href: 'securitySolution/overview', + href: 'securitySolutionUI/overview', text: 'Security', }, { - href: "securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", text: 'Hosts', }, { @@ -178,10 +178,10 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Network', - href: "securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Flows', @@ -196,10 +196,10 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Timelines', - href: "securitySolution/timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/timelines?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -210,14 +210,14 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Hosts', - href: "securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'siem-kibana', - href: "securitySolution/hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Authentications', href: '' }, ]); @@ -229,14 +229,14 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Network', - href: "securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: ipv4, - href: `securitySolution/network/ip/${ipv4}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolutionUI/network/ip/${ipv4}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, { text: 'Flows', href: '' }, ]); @@ -248,14 +248,14 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Network', - href: "securitySolution/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/network?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: ipv6, - href: `securitySolution/network/ip/${ipv6Encoded}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolutionUI/network/ip/${ipv6Encoded}/source?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, { text: 'Flows', href: '' }, ]); @@ -267,7 +267,7 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Alerts', href: '', @@ -281,7 +281,7 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Exceptions', href: '', @@ -295,10 +295,10 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Rules', - href: "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -309,10 +309,10 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Rules', - href: "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'Create', @@ -335,14 +335,14 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Rules', - href: "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: mockRuleName, - href: `securitySolution/rules/id/${mockDetailName}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolutionUI/rules/id/${mockDetailName}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, ]); }); @@ -361,14 +361,14 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Rules', - href: "securitySolution/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/rules?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: 'ALERT_RULE_NAME', - href: `securitySolution/rules/id/${mockDetailName}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolutionUI/rules/id/${mockDetailName}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, { text: 'Edit', @@ -383,10 +383,10 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Cases', - href: "securitySolution/case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, ]); }); @@ -403,14 +403,14 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Cases', - href: "securitySolution/case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/case?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", }, { text: sampleCase.name, - href: `securitySolution/case/${sampleCase.id}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, + href: `securitySolutionUI/case/${sampleCase.id}?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))`, }, ]); }); @@ -420,7 +420,7 @@ describe('Navigation Breadcrumbs', () => { getUrlForAppMock ); expect(breadcrumbs).toEqual([ - { text: 'Security', href: 'securitySolution/overview' }, + { text: 'Security', href: 'securitySolutionUI/overview' }, { text: 'Endpoints', href: '', @@ -442,17 +442,17 @@ describe('Navigation Breadcrumbs', () => { expect(setBreadcrumbsMock).toBeCalledWith([ expect.objectContaining({ text: 'Security', - href: 'securitySolution/overview', + href: 'securitySolutionUI/overview', onClick: expect.any(Function), }), expect.objectContaining({ text: 'Hosts', - href: "securitySolution/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/hosts?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", onClick: expect.any(Function), }), expect.objectContaining({ text: 'siem-kibana', - href: "securitySolution/hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", + href: "securitySolutionUI/hosts/siem-kibana?sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2019-05-16T23:10:43.696Z',fromStr:now-24h,kind:relative,to:'2019-05-17T23:10:43.697Z',toStr:now)))", onClick: expect.any(Function), }), { diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts index 7262264d72103..029dc70c398e3 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/navigation/breadcrumbs/index.ts @@ -9,7 +9,7 @@ import { getOr, omit } from 'lodash/fp'; import { useDispatch } from 'react-redux'; import { ChromeBreadcrumb } from '../../../../../../../../src/core/public'; -import { APP_NAME, APP_ID } from '../../../../../common/constants'; +import { APP_NAME, APP_UI_ID } from '../../../../../common/constants'; import { StartServices } from '../../../../types'; import { getBreadcrumbs as getHostDetailsBreadcrumbs } from '../../../../hosts/pages/details/utils'; import { getBreadcrumbs as getIPDetailsBreadcrumbs } from '../../../../network/pages/details'; @@ -92,7 +92,7 @@ export const getBreadcrumbsForRoute = ( getUrlForApp: GetUrlForApp ): ChromeBreadcrumb[] | null => { const spyState: RouteSpyState = omit('navTabs', object); - const overviewPath = getUrlForApp(APP_ID, { deepLinkId: SecurityPageName.overview }); + const overviewPath = getUrlForApp(APP_UI_ID, { deepLinkId: SecurityPageName.overview }); const siemRootBreadcrumb: ChromeBreadcrumb = { text: APP_NAME, href: getAppOverviewUrl(overviewPath), diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx index 18dd07a99824e..b123a26257683 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/tab_navigation/index.test.tsx @@ -116,7 +116,7 @@ describe('Table Navigation', () => { `EuiTab[data-test-subj="navigation-${HostsTableType.authentications}"]` ); expect(firstTab.props().href).toBe( - `/app/securitySolution/hosts/siem-window/authentications${SEARCH_QUERY}` + `/app/securitySolutionUI/hosts/siem-window/authentications${SEARCH_QUERY}` ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx index 396f431a3232d..3db485f87a68f 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/index.test.tsx @@ -18,6 +18,7 @@ import { UrlInputsModel } from '../../../store/inputs/model'; import { useRouteSpy } from '../../../utils/route/use_route_spy'; import { useIsExperimentalFeatureEnabled } from '../../../hooks/use_experimental_features'; import { TestProviders } from '../../../mock'; +import { CASES_FEATURE_ID } from '../../../../../common/constants'; import { useCanSeeHostIsolationExceptionsMenu } from '../../../../management/pages/host_isolation_exceptions/view/hooks'; jest.mock('../../../lib/kibana/kibana_react'); @@ -88,9 +89,10 @@ describe('useSecuritySolutionNavigation', () => { `${appId}/${options?.deepLinkId ?? ''}${options?.path ?? ''}`, capabilities: { siem: { - crud_alerts: true, - read_alerts: true, + show: true, + crud: true, }, + [CASES_FEATURE_ID]: { read_cases: true, crud_cases: false }, }, }, chrome: { @@ -114,10 +116,10 @@ describe('useSecuritySolutionNavigation', () => { "id": "main", "items": Array [ Object { - "data-href": "securitySolution/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolutionUI/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-overview", "disabled": false, - "href": "securitySolution/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolutionUI/overview?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "overview", "isSelected": false, "name": "Overview", @@ -130,30 +132,30 @@ describe('useSecuritySolutionNavigation', () => { "id": "detect", "items": Array [ Object { - "data-href": "securitySolution/alerts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolutionUI/alerts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-alerts", "disabled": false, - "href": "securitySolution/alerts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolutionUI/alerts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "alerts", "isSelected": false, "name": "Alerts", "onClick": [Function], }, Object { - "data-href": "securitySolution/rules?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolutionUI/rules?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-rules", "disabled": false, - "href": "securitySolution/rules?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolutionUI/rules?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "rules", "isSelected": false, "name": "Rules", "onClick": [Function], }, Object { - "data-href": "securitySolution/exceptions?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolutionUI/exceptions?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-exceptions", "disabled": false, - "href": "securitySolution/exceptions?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolutionUI/exceptions?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "exceptions", "isSelected": false, "name": "Exceptions", @@ -166,20 +168,20 @@ describe('useSecuritySolutionNavigation', () => { "id": "explore", "items": Array [ Object { - "data-href": "securitySolution/hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolutionUI/hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-hosts", "disabled": false, - "href": "securitySolution/hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolutionUI/hosts?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "hosts", "isSelected": true, "name": "Hosts", "onClick": [Function], }, Object { - "data-href": "securitySolution/network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolutionUI/network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-network", "disabled": false, - "href": "securitySolution/network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolutionUI/network?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "network", "isSelected": false, "name": "Network", @@ -192,10 +194,10 @@ describe('useSecuritySolutionNavigation', () => { "id": "investigate", "items": Array [ Object { - "data-href": "securitySolution/timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolutionUI/timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-timelines", "disabled": false, - "href": "securitySolution/timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolutionUI/timelines?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "timelines", "isSelected": false, "name": "Timelines", @@ -208,40 +210,40 @@ describe('useSecuritySolutionNavigation', () => { "id": "manage", "items": Array [ Object { - "data-href": "securitySolution/endpoints", + "data-href": "securitySolutionUI/endpoints", "data-test-subj": "navigation-endpoints", "disabled": false, - "href": "securitySolution/endpoints", + "href": "securitySolutionUI/endpoints", "id": "endpoints", "isSelected": false, "name": "Endpoints", "onClick": [Function], }, Object { - "data-href": "securitySolution/trusted_apps", + "data-href": "securitySolutionUI/trusted_apps", "data-test-subj": "navigation-trusted_apps", "disabled": false, - "href": "securitySolution/trusted_apps", + "href": "securitySolutionUI/trusted_apps", "id": "trusted_apps", "isSelected": false, "name": "Trusted applications", "onClick": [Function], }, Object { - "data-href": "securitySolution/event_filters", + "data-href": "securitySolutionUI/event_filters", "data-test-subj": "navigation-event_filters", "disabled": false, - "href": "securitySolution/event_filters", + "href": "securitySolutionUI/event_filters", "id": "event_filters", "isSelected": false, "name": "Event filters", "onClick": [Function], }, Object { - "data-href": "securitySolution/host_isolation_exceptions", + "data-href": "securitySolutionUI/host_isolation_exceptions", "data-test-subj": "navigation-host_isolation_exceptions", "disabled": false, - "href": "securitySolution/host_isolation_exceptions", + "href": "securitySolutionUI/host_isolation_exceptions", "id": "host_isolation_exceptions", "isSelected": false, "name": "Host isolation exceptions", @@ -299,10 +301,10 @@ describe('useSecuritySolutionNavigation', () => { ); expect(caseNavItem).toMatchInlineSnapshot(` Object { - "data-href": "securitySolution/case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "data-href": "securitySolutionUI/case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "data-test-subj": "navigation-case", "disabled": false, - "href": "securitySolution/case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", + "href": "securitySolutionUI/case?query=(language:kuery,query:'host.name:%22security-solution-es%22')&sourcerer=()&timerange=(global:(linkTo:!(timeline),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)),timeline:(linkTo:!(global),timerange:(from:'2020-07-07T08:20:18.966Z',fromStr:now-24h,kind:relative,to:'2020-07-08T08:20:18.966Z',toStr:now)))", "id": "case", "isSelected": false, "name": "Cases", diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx index a1be69dd077ad..961090a01ba19 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/use_security_solution_navigation/use_navigation_items.tsx @@ -11,6 +11,7 @@ import { EuiSideNavItemType } from '@elastic/eui/src/components/side_nav/side_na import { securityNavGroup } from '../../../../app/home/home_navigations'; import { getSearch } from '../helpers'; import { PrimaryNavigationItemsProps } from './types'; +import { useKibana } from '../../../lib/kibana/kibana_react'; import { useGetUserCasesPermissions } from '../../../lib/kibana'; import { useNavigation } from '../../../lib/kibana/hooks'; import { NavTab } from '../types'; @@ -64,34 +65,52 @@ export const usePrimaryNavigationItems = ({ function usePrimaryNavigationItemsToDisplay(navTabs: Record) { const hasCasesReadPermissions = useGetUserCasesPermissions()?.read; const canSeeHostIsolationExceptions = useCanSeeHostIsolationExceptionsMenu(); - return useMemo(() => { - return [ - { - id: 'main', - name: '', - items: [navTabs.overview], - }, - { - ...securityNavGroup.detect, - items: [navTabs.alerts, navTabs.rules, navTabs.exceptions], - }, - { - ...securityNavGroup.explore, - items: [navTabs.hosts, navTabs.network, ...(navTabs.ueba != null ? [navTabs.ueba] : [])], - }, - { - ...securityNavGroup.investigate, - items: hasCasesReadPermissions ? [navTabs.timelines, navTabs.case] : [navTabs.timelines], - }, - { - ...securityNavGroup.manage, - items: [ - navTabs.endpoints, - navTabs.trusted_apps, - navTabs.event_filters, - ...(canSeeHostIsolationExceptions ? [navTabs.host_isolation_exceptions] : []), - ], - }, - ]; - }, [navTabs, hasCasesReadPermissions, canSeeHostIsolationExceptions]); + const uiCapabilities = useKibana().services.application.capabilities; + return useMemo( + () => + uiCapabilities.siem.show + ? [ + { + id: 'main', + name: '', + items: [navTabs.overview], + }, + { + ...securityNavGroup.detect, + items: [navTabs.alerts, navTabs.rules, navTabs.exceptions], + }, + { + ...securityNavGroup.explore, + items: [ + navTabs.hosts, + navTabs.network, + ...(navTabs.ueba != null ? [navTabs.ueba] : []), + ], + }, + { + ...securityNavGroup.investigate, + items: hasCasesReadPermissions + ? [navTabs.timelines, navTabs.case] + : [navTabs.timelines], + }, + { + ...securityNavGroup.manage, + items: [ + navTabs.endpoints, + navTabs.trusted_apps, + navTabs.event_filters, + ...(canSeeHostIsolationExceptions ? [navTabs.host_isolation_exceptions] : []), + ], + }, + ] + : hasCasesReadPermissions + ? [ + { + ...securityNavGroup.investigate, + items: [navTabs.case], + }, + ] + : [], + [uiCapabilities.siem.show, navTabs, hasCasesReadPermissions, canSeeHostIsolationExceptions] + ); } diff --git a/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx b/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx index bc0640296b33d..05ccadeaf67ac 100644 --- a/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/user_privileges/index.tsx @@ -40,15 +40,16 @@ export const UserPrivilegesProvider = ({ kibanaCapabilities, children, }: UserPrivilegesProviderProps) => { - const listPrivileges = useFetchListPrivileges(); - const detectionEnginePrivileges = useFetchDetectionEnginePrivileges(); - const endpointPrivileges = useEndpointPrivileges(); - const [kibanaSecuritySolutionsPrivileges, setKibanaSecuritySolutionsPrivileges] = useState({ - crud: false, - read: false, - }); const crud: boolean = kibanaCapabilities[SERVER_APP_ID].crud === true; const read: boolean = kibanaCapabilities[SERVER_APP_ID].show === true; + const [kibanaSecuritySolutionsPrivileges, setKibanaSecuritySolutionsPrivileges] = useState({ + crud, + read, + }); + + const listPrivileges = useFetchListPrivileges(read); + const detectionEnginePrivileges = useFetchDetectionEnginePrivileges(read); + const endpointPrivileges = useEndpointPrivileges(); useEffect(() => { setKibanaSecuritySolutionsPrivileges((currPrivileges) => { diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts index 61ce5a8238b52..5975977988a50 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/__mocks__/index.ts @@ -17,7 +17,7 @@ import { createStartServicesMock, createWithKibanaMock, } from '../kibana_react.mock'; -import { APP_ID } from '../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../common/constants'; const mockStartServicesMock = createStartServicesMock(); export const KibanaServices = { get: jest.fn(), getKibanaVersion: jest.fn(() => '8.0.0') }; @@ -65,7 +65,7 @@ export const useGetUserCasesPermissions = jest.fn(); export const useAppUrl = jest.fn().mockReturnValue({ getAppUrl: jest .fn() - .mockImplementation(({ appId = APP_ID, ...options }) => + .mockImplementation(({ appId = APP_UI_ID, ...options }) => mockStartServicesMock.application.getUrlForApp(appId, options) ), }); diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts index fdaed64ba91d7..411dd5542038b 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/hooks.ts @@ -13,7 +13,7 @@ import { i18n } from '@kbn/i18n'; import { camelCase, isArray, isObject } from 'lodash'; import { set } from '@elastic/safer-lodash-set'; import { - APP_ID, + APP_UI_ID, CASES_FEATURE_ID, DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ, @@ -174,7 +174,7 @@ export const useAppUrl = () => { const getAppUrl = useCallback( ({ - appId = APP_ID, + appId = APP_UI_ID, ...options }: { appId?: string; @@ -197,7 +197,7 @@ export const useNavigateTo = () => { const navigateTo = useCallback( ({ url, - appId = APP_ID, + appId = APP_UI_ID, ...options }: { url?: string; diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx index ed2a2252bd0d2..56f5dc28652aa 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx @@ -22,7 +22,7 @@ import { createStartServicesMock } from '../../lib/kibana/kibana_react.mock'; import { SUB_PLUGINS_REDUCER, mockGlobalState, createSecuritySolutionStorageMock } from '..'; import { ExperimentalFeatures } from '../../../../common/experimental_features'; import { PLUGIN_ID } from '../../../../../fleet/common'; -import { APP_ID, APP_PATH } from '../../../../common/constants'; +import { APP_UI_ID, APP_PATH } from '../../../../common/constants'; import { KibanaContextProvider, KibanaServices } from '../../lib/kibana'; import { getDeepLinks } from '../../../app/deep_links'; import { fleetGetPackageListHttpMock } from '../../../management/pages/mocks'; @@ -176,7 +176,7 @@ const createCoreStartMock = ( switch (appId) { case PLUGIN_ID: return '/app/fleet'; - case APP_ID: + case APP_UI_ID: return `${APP_PATH}${ deepLinkId && deepLinkPaths[deepLinkId] ? deepLinkPaths[deepLinkId] : '' }${path ?? ''}`; @@ -186,7 +186,7 @@ const createCoreStartMock = ( }); coreStart.application.navigateToApp.mockImplementation((appId, { deepLinkId, path } = {}) => { - if (appId === APP_ID) { + if (appId === APP_UI_ID) { history.push( `${deepLinkId && deepLinkPaths[deepLinkId] ? deepLinkPaths[deepLinkId] : ''}${path ?? ''}` ); diff --git a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx index 975487bb2b384..7ea93bb7ce8fb 100644 --- a/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/test_providers.tsx @@ -26,6 +26,7 @@ import { FieldHook } from '../../shared_imports'; import { SUB_PLUGINS_REDUCER } from './utils'; import { createSecuritySolutionStorageMock, localStorageMock } from './mock_local_storage'; import { UserPrivilegesProvider } from '../components/user_privileges'; +import { CASES_FEATURE_ID } from '../../../common/constants'; const state: State = mockGlobalState; @@ -76,7 +77,10 @@ const TestProvidersWithPrivilegesComponent: React.FC = ({ ({ eui: euiDarkVars, darkMode: true })}> {children} diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx index 484cd66575005..54964de684ed7 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.test.tsx @@ -122,7 +122,7 @@ describe('AlertsHistogramPanel', () => { preventDefault: jest.fn(), }); - expect(mockNavigateToApp).toBeCalledWith('securitySolution', { + expect(mockNavigateToApp).toBeCalledWith('securitySolutionUI', { deepLinkId: SecurityPageName.alerts, path: '', }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx index 07fa81f27684c..147d41e8533df 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_kpis/alerts_histogram_panel/index.tsx @@ -14,7 +14,7 @@ import { isEmpty } from 'lodash/fp'; import uuid from 'uuid'; import { useGlobalTime } from '../../../../common/containers/use_global_time'; -import { DEFAULT_NUMBER_FORMAT, APP_ID } from '../../../../../common/constants'; +import { DEFAULT_NUMBER_FORMAT, APP_UI_ID } from '../../../../../common/constants'; import type { UpdateDateRange } from '../../../../common/components/charts/common'; import type { LegendItem } from '../../../../common/components/charts/draggable_legend_item'; import { escapeDataProviderId } from '../../../../common/components/drag_and_drop/helpers'; @@ -147,7 +147,7 @@ export const AlertsHistogramPanel = memo( const goToDetectionEngine = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.alerts, path: getDetectionEngineUrl(urlSearch), }); diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx index a342b01b038ca..e14c3e916a35e 100644 --- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/timeline_actions/use_add_to_case_actions.tsx @@ -8,7 +8,7 @@ import { useMemo } from 'react'; import { useGetUserCasesPermissions, useKibana } from '../../../../common/lib/kibana'; import { TimelineId, TimelineNonEcsData } from '../../../../../common'; -import { APP_ID } from '../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../common/constants'; import { useInsertTimeline } from '../../../../cases/components/use_insert_timeline'; import { Ecs } from '../../../../../common/ecs'; @@ -39,7 +39,7 @@ export const useAddToCaseActions = ({ event: { data: nonEcsData ?? [], ecs: ecsData, _id: ecsData?._id }, useInsertTimeline: insertTimelineHook, casePermissions, - appId: APP_ID, + appId: APP_UI_ID, onClose: afterCaseSelection, } : null, diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx index 9f2728e0813f4..eac1c2800955f 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/rule_actions_overflow/index.tsx @@ -30,7 +30,7 @@ import { getRulesUrl } from '../../../../common/components/link_to/redirect_to_d import { getToolTipContent } from '../../../../common/utils/privileges'; import { useBoolState } from '../../../../common/hooks/use_bool_state'; import { useKibana } from '../../../../common/lib/kibana'; -import { APP_ID, SecurityPageName } from '../../../../../common/constants'; +import { APP_UI_ID, SecurityPageName } from '../../../../../common/constants'; const MyEuiButtonIcon = styled(EuiButtonIcon)` &.euiButtonIcon { @@ -62,7 +62,7 @@ const RuleActionsOverflowComponent = ({ const [, dispatchToaster] = useStateToaster(); const onRuleDeletedCallback = useCallback(() => { - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRulesUrl(), }); diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx index 2c7ca301002e1..72730deec6a19 100644 --- a/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/rules/step_rule_actions/index.tsx @@ -35,7 +35,7 @@ import { RuleActionsField } from '../rule_actions_field'; import { useKibana } from '../../../../common/lib/kibana'; import { getSchema } from './schema'; import * as I18n from './translations'; -import { APP_ID } from '../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../common/constants'; import { useManageCaseAction } from './use_manage_case_action'; interface StepRuleActionsProps extends RuleStepProps { @@ -80,7 +80,7 @@ const StepRuleActionsComponent: FC = ({ } = useKibana(); const kibanaAbsoluteUrl = useMemo( () => - application.getUrlForApp(`${APP_ID}`, { + application.getUrlForApp(`${APP_UI_ID}`, { absolute: true, }), [application] diff --git a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx index 1a8588017e4d6..0447130e1bd14 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/components/user_info/index.test.tsx @@ -69,9 +69,7 @@ describe('useUserInfo', () => { const wrapper = ({ children }: { children: JSX.Element }) => ( {children} diff --git a/x-pack/plugins/security_solution/public/detections/components/user_privileges/use_fetch_detection_engine_privileges.ts b/x-pack/plugins/security_solution/public/detections/components/user_privileges/use_fetch_detection_engine_privileges.ts index 259245d25c401..81fc83f3fdb1a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_privileges/use_fetch_detection_engine_privileges.ts +++ b/x-pack/plugins/security_solution/public/detections/components/user_privileges/use_fetch_detection_engine_privileges.ts @@ -13,7 +13,7 @@ import * as i18n from './translations'; export const useFetchPrivileges = () => useAsync(withOptionalSignal(getUserPrivilege)); -export const useFetchDetectionEnginePrivileges = () => { +export const useFetchDetectionEnginePrivileges = (isAppAvailable: boolean = true) => { const { start, ...detectionEnginePrivileges } = useFetchPrivileges(); const { addError } = useAppToasts(); const abortCtrlRef = useRef(new AbortController()); @@ -21,12 +21,12 @@ export const useFetchDetectionEnginePrivileges = () => { useEffect(() => { const { loading, result, error } = detectionEnginePrivileges; - if (!loading && !(result || error)) { + if (isAppAvailable && !loading && !(result || error)) { abortCtrlRef.current.abort(); abortCtrlRef.current = new AbortController(); start({ signal: abortCtrlRef.current.signal }); } - }, [start, detectionEnginePrivileges]); + }, [start, detectionEnginePrivileges, isAppAvailable]); useEffect(() => { return () => { diff --git a/x-pack/plugins/security_solution/public/detections/components/user_privileges/use_fetch_list_privileges.ts b/x-pack/plugins/security_solution/public/detections/components/user_privileges/use_fetch_list_privileges.ts index 2d0e6ee0248e7..e1d6a90da1b0a 100644 --- a/x-pack/plugins/security_solution/public/detections/components/user_privileges/use_fetch_list_privileges.ts +++ b/x-pack/plugins/security_solution/public/detections/components/user_privileges/use_fetch_list_privileges.ts @@ -22,7 +22,7 @@ interface ListPrivileges { }; } -export const useFetchListPrivileges = () => { +export const useFetchListPrivileges = (isAppAvailable: boolean = true) => { const http = useHttp(); const { lists } = useKibana().services; const { start: fetchListPrivileges, ...listPrivileges } = useReadListPrivileges(); @@ -32,12 +32,12 @@ export const useFetchListPrivileges = () => { useEffect(() => { const { loading, result, error } = listPrivileges; - if (lists && !loading && !(result || error)) { + if (isAppAvailable && lists && !loading && !(result || error)) { abortCtrlRef.current.abort(); abortCtrlRef.current = new AbortController(); fetchListPrivileges({ http, signal: abortCtrlRef.current.signal }); } - }, [http, lists, fetchListPrivileges, listPrivileges]); + }, [http, lists, fetchListPrivileges, listPrivileges, isAppAvailable]); useEffect(() => { return () => { diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx index 214a7ac24da8a..06e026ba8f6e1 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/actions.tsx @@ -7,7 +7,7 @@ import React, { Dispatch } from 'react'; import { NavigateToAppOptions } from '../../../../../../../../../src/core/public'; -import { APP_ID } from '../../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../../common/constants'; import { BulkAction } from '../../../../../../common/detection_engine/schemas/common/schemas'; import { CreateRulesSchema } from '../../../../../../common/detection_engine/schemas/request'; import { SecurityPageName } from '../../../../../app/types'; @@ -37,7 +37,7 @@ export const editRuleAction = ( ruleId: string, navigateToApp: (appId: string, options?: NavigateToAppOptions | undefined) => Promise ) => { - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getEditRuleUrl(ruleId ?? ''), }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx index 6b05ee6403db3..6ca987a8d005c 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/all/columns.tsx @@ -42,7 +42,7 @@ import { getToolTipContent, canEditRuleWithActions } from '../../../../../common import { PopoverTooltip } from './popover_tooltip'; import { TagsDisplay } from './tag_display'; import { getRuleStatusText } from '../../../../../../common/detection_engine/utils'; -import { APP_ID, SecurityPageName } from '../../../../../../common/constants'; +import { APP_UI_ID, SecurityPageName } from '../../../../../../common/constants'; import { DocLinksStart, NavigateToAppOptions } from '../../../../../../../../../src/core/public'; export const getActions = ( @@ -164,7 +164,7 @@ export const getColumns = ({ data-test-subj="ruleName" onClick={(ev: { preventDefault: () => void }) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(item.id), }); @@ -329,7 +329,7 @@ export const getMonitoringColumns = ( data-test-subj="ruleName" onClick={(ev: { preventDefault: () => void }) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(item.id), }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx index d37acaeb0ffee..db41df644b3dc 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/index.tsx @@ -47,7 +47,7 @@ import { formatRule, stepIsValid } from './helpers'; import * as i18n from './translations'; import { SecurityPageName } from '../../../../../app/types'; import { ruleStepsOrder } from '../utils'; -import { APP_ID } from '../../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../../common/constants'; import { useKibana } from '../../../../../common/lib/kibana'; const formHookNoop = async (): Promise => undefined; @@ -269,7 +269,7 @@ const CreateRulePageComponent: React.FC = () => { if (ruleName && ruleId) { displaySuccessToast(i18n.SUCCESSFULLY_CREATED_RULES(ruleName), dispatchToaster); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(ruleId), }); @@ -284,13 +284,13 @@ const CreateRulePageComponent: React.FC = () => { needsListsConfiguration ) ) { - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.alerts, path: getDetectionEngineUrl(), }); return null; } else if (!userHasPermissions(canUserCRUD)) { - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRulesUrl(), }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx index 774b9463bed69..ca71ac6783f59 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/details/index.tsx @@ -89,7 +89,7 @@ import { LinkButton } from '../../../../../common/components/links'; import { useFormatUrl } from '../../../../../common/components/link_to'; import { ExceptionsViewer } from '../../../../../common/components/exceptions/viewer'; import { - APP_ID, + APP_UI_ID, DEFAULT_INDEX_PATTERN, DEFAULT_INDEX_PATTERN_EXPERIMENTAL, } from '../../../../../../common/constants'; @@ -574,7 +574,7 @@ const RuleDetailsPageComponent: React.FC = ({ const goToEditRule = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getEditRuleUrl(ruleId ?? ''), }); @@ -683,7 +683,7 @@ const RuleDetailsPageComponent: React.FC = ({ needsListsConfiguration ) ) { - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.alerts, path: getDetectionEngineUrl(), }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx index cea97aed28cc1..784290ad80d47 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/edit/index.tsx @@ -56,7 +56,7 @@ import * as i18n from './translations'; import { SecurityPageName } from '../../../../../app/types'; import { ruleStepsOrder } from '../utils'; import { useKibana } from '../../../../../common/lib/kibana'; -import { APP_ID } from '../../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../../common/constants'; const formHookNoop = async (): Promise => undefined; @@ -300,7 +300,7 @@ const EditRulePageComponent: FC = () => { const goToDetailsRule = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(ruleId ?? ''), }); @@ -318,7 +318,7 @@ const EditRulePageComponent: FC = () => { if (isSaved) { displaySuccessToast(i18n.SUCCESSFULLY_SAVED_RULE(rule?.name ?? ''), dispatchToaster); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(ruleId ?? ''), }); @@ -333,13 +333,13 @@ const EditRulePageComponent: FC = () => { needsListsConfiguration ) ) { - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.alerts, path: getDetectionEngineUrl(), }); return null; } else if (!userHasPermissions(canUserCRUD)) { - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(ruleId ?? ''), }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx index f957f77ac4c1a..15ffd3579614a 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx @@ -36,7 +36,7 @@ import { useFormatUrl } from '../../../../common/components/link_to'; import { NeedAdminForUpdateRulesCallOut } from '../../../components/callouts/need_admin_for_update_callout'; import { MlJobCompatibilityCallout } from '../../../components/callouts/ml_job_compatibility_callout'; import { MissingPrivilegesCallOut } from '../../../components/callouts/missing_privileges_callout'; -import { APP_ID } from '../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../common/constants'; import { useKibana } from '../../../../common/lib/kibana'; type Func = () => Promise; @@ -125,7 +125,7 @@ const RulesPageComponent: React.FC = () => { const goToNewRule = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { deepLinkId: SecurityPageName.rules, path: getCreateRuleUrl() }); + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getCreateRuleUrl() }); }, [navigateToApp] ); @@ -156,7 +156,7 @@ const RulesPageComponent: React.FC = () => { needsListsConfiguration ) ) { - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.alerts, path: getDetectionEngineUrl(), }); diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts index 92c828b6cbf79..d4ce5da4a1413 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/utils.ts @@ -16,7 +16,7 @@ import * as i18nRules from './translations'; import { RouteSpyState } from '../../../../common/utils/route/types'; import { GetUrlForApp } from '../../../../common/components/navigation/types'; import { SecurityPageName } from '../../../../app/types'; -import { APP_ID, RULES_PATH } from '../../../../../common/constants'; +import { APP_UI_ID, RULES_PATH } from '../../../../../common/constants'; import { RuleStep, RuleStepsOrder } from './types'; export const ruleStepsOrder: RuleStepsOrder = [ @@ -32,7 +32,7 @@ const getRulesBreadcrumb = (pathname: string, search: string[], getUrlForApp: Ge if (tabPath === 'rules') { return { text: i18nRules.PAGE_TITLE, - href: getUrlForApp(APP_ID, { + href: getUrlForApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRulesUrl(!isEmpty(search[0]) ? search[0] : ''), }), @@ -64,7 +64,7 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: params.state.ruleName, - href: getUrlForApp(APP_ID, { + href: getUrlForApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(params.detailName, !isEmpty(search[0]) ? search[0] : ''), }), diff --git a/x-pack/plugins/security_solution/public/helpers.test.ts b/x-pack/plugins/security_solution/public/helpers.test.ts deleted file mode 100644 index f2e37cd995a4f..0000000000000 --- a/x-pack/plugins/security_solution/public/helpers.test.ts +++ /dev/null @@ -1,62 +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; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { parseRoute, getHostRiskIndex } from './helpers'; - -describe('public helpers parseRoute', () => { - it('should properly parse hash route', () => { - const hashSearch = - '?timerange=(global:(linkTo:!(timeline),timerange:(from:%272020-09-06T11:43:55.814Z%27,fromStr:now-24h,kind:relative,to:%272020-09-07T11:43:55.814Z%27,toStr:now)),timeline:(linkTo:!(global),timerange:(from:%272020-09-06T11:43:55.814Z%27,fromStr:now-24h,kind:relative,to:%272020-09-07T11:43:55.814Z%27,toStr:now)))'; - const hashLocation = { - hash: `#/detections/rules/id/78acc090-bbaa-4a86-916b-ea44784324ae/edit${hashSearch}`, - pathname: '/app/siem', - search: '', - }; - - expect(parseRoute(hashLocation)).toEqual({ - pageName: 'detections', - path: `/rules/id/78acc090-bbaa-4a86-916b-ea44784324ae/edit${hashSearch}`, - search: hashSearch, - }); - }); - - it('should properly parse non-hash route', () => { - const nonHashLocation = { - hash: '', - pathname: '/app/security/detections/rules/id/78acc090-bbaa-4a86-916b-ea44784324ae/edit', - search: - '?timerange=(global:(linkTo:!(timeline),timerange:(from:%272020-09-06T11:43:55.814Z%27,fromStr:now-24h,kind:relative,to:%272020-09-07T11:43:55.814Z%27,toStr:now)),timeline:(linkTo:!(global),timerange:(from:%272020-09-06T11:43:55.814Z%27,fromStr:now-24h,kind:relative,to:%272020-09-07T11:43:55.814Z%27,toStr:now)))', - }; - - expect(parseRoute(nonHashLocation)).toEqual({ - pageName: 'detections', - path: `/rules/id/78acc090-bbaa-4a86-916b-ea44784324ae/edit${nonHashLocation.search}`, - search: nonHashLocation.search, - }); - }); - - it('should properly parse non-hash subplugin route', () => { - const nonHashLocation = { - hash: '', - pathname: '/app/security/detections', - search: - '?timerange=(global:(linkTo:!(timeline),timerange:(from:%272020-09-06T11:43:55.814Z%27,fromStr:now-24h,kind:relative,to:%272020-09-07T11:43:55.814Z%27,toStr:now)),timeline:(linkTo:!(global),timerange:(from:%272020-09-06T11:43:55.814Z%27,fromStr:now-24h,kind:relative,to:%272020-09-07T11:43:55.814Z%27,toStr:now)))', - }; - - expect(parseRoute(nonHashLocation)).toEqual({ - pageName: 'detections', - path: `${nonHashLocation.search}`, - search: nonHashLocation.search, - }); - }); -}); - -describe('public helpers export getHostRiskIndex', () => { - it('should properly return index if space is specified', () => { - expect(getHostRiskIndex('testName')).toEqual('ml_host_risk_score_latest_testName'); - }); -}); diff --git a/x-pack/plugins/security_solution/public/helpers.test.tsx b/x-pack/plugins/security_solution/public/helpers.test.tsx new file mode 100644 index 0000000000000..3475ac7c28f7a --- /dev/null +++ b/x-pack/plugins/security_solution/public/helpers.test.tsx @@ -0,0 +1,276 @@ +/* + * 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 React from 'react'; +import { shallow } from 'enzyme'; +import { Capabilities } from '../../../../src/core/public'; +import { CASES_FEATURE_ID, SERVER_APP_ID } from '../common/constants'; +import { + parseRoute, + getHostRiskIndex, + isSubPluginAvailable, + getSubPluginRoutesByCapabilities, + RedirectRoute, +} from './helpers'; +import { StartedSubPlugins } from './types'; + +describe('public helpers parseRoute', () => { + it('should properly parse hash route', () => { + const hashSearch = + '?timerange=(global:(linkTo:!(timeline),timerange:(from:%272020-09-06T11:43:55.814Z%27,fromStr:now-24h,kind:relative,to:%272020-09-07T11:43:55.814Z%27,toStr:now)),timeline:(linkTo:!(global),timerange:(from:%272020-09-06T11:43:55.814Z%27,fromStr:now-24h,kind:relative,to:%272020-09-07T11:43:55.814Z%27,toStr:now)))'; + const hashLocation = { + hash: `#/detections/rules/id/78acc090-bbaa-4a86-916b-ea44784324ae/edit${hashSearch}`, + pathname: '/app/siem', + search: '', + }; + + expect(parseRoute(hashLocation)).toEqual({ + pageName: 'detections', + path: `/rules/id/78acc090-bbaa-4a86-916b-ea44784324ae/edit${hashSearch}`, + search: hashSearch, + }); + }); + + it('should properly parse non-hash route', () => { + const nonHashLocation = { + hash: '', + pathname: '/app/security/detections/rules/id/78acc090-bbaa-4a86-916b-ea44784324ae/edit', + search: + '?timerange=(global:(linkTo:!(timeline),timerange:(from:%272020-09-06T11:43:55.814Z%27,fromStr:now-24h,kind:relative,to:%272020-09-07T11:43:55.814Z%27,toStr:now)),timeline:(linkTo:!(global),timerange:(from:%272020-09-06T11:43:55.814Z%27,fromStr:now-24h,kind:relative,to:%272020-09-07T11:43:55.814Z%27,toStr:now)))', + }; + + expect(parseRoute(nonHashLocation)).toEqual({ + pageName: 'detections', + path: `/rules/id/78acc090-bbaa-4a86-916b-ea44784324ae/edit${nonHashLocation.search}`, + search: nonHashLocation.search, + }); + }); + + it('should properly parse non-hash subplugin route', () => { + const nonHashLocation = { + hash: '', + pathname: '/app/security/detections', + search: + '?timerange=(global:(linkTo:!(timeline),timerange:(from:%272020-09-06T11:43:55.814Z%27,fromStr:now-24h,kind:relative,to:%272020-09-07T11:43:55.814Z%27,toStr:now)),timeline:(linkTo:!(global),timerange:(from:%272020-09-06T11:43:55.814Z%27,fromStr:now-24h,kind:relative,to:%272020-09-07T11:43:55.814Z%27,toStr:now)))', + }; + + expect(parseRoute(nonHashLocation)).toEqual({ + pageName: 'detections', + path: `${nonHashLocation.search}`, + search: nonHashLocation.search, + }); + }); +}); + +describe('public helpers export getHostRiskIndex', () => { + it('should properly return index if space is specified', () => { + expect(getHostRiskIndex('testName')).toEqual('ml_host_risk_score_latest_testName'); + }); +}); + +describe('#getSubPluginRoutesByCapabilities', () => { + const mockRender = () => null; + const mockSubPlugins = { + alerts: { routes: [{ path: 'alerts', render: mockRender }] }, + cases: { routes: [{ path: 'cases', render: mockRender }] }, + } as unknown as StartedSubPlugins; + it('cases routes should return NoPrivilegesPage component when cases plugin is NOT available ', () => { + const routes = getSubPluginRoutesByCapabilities(mockSubPlugins, { + [SERVER_APP_ID]: { show: true, crud: false }, + [CASES_FEATURE_ID]: { read_cases: false, crud_cases: false }, + } as unknown as Capabilities); + const casesRoute = routes.find((r) => r.path === 'cases'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const CasesView = (casesRoute?.component ?? mockRender) as React.ComponentType; + expect(shallow()).toMatchInlineSnapshot(` + + `); + }); + + it('alerts should return NoPrivilegesPage component when siem plugin is NOT available ', () => { + const routes = getSubPluginRoutesByCapabilities(mockSubPlugins, { + [SERVER_APP_ID]: { show: false, crud: false }, + [CASES_FEATURE_ID]: { read_cases: true, crud_cases: false }, + } as unknown as Capabilities); + const alertsRoute = routes.find((r) => r.path === 'alerts'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const AlertsView = (alertsRoute?.component ?? mockRender) as React.ComponentType; + expect(shallow()).toMatchInlineSnapshot(` + + `); + }); + + it('should return NoPrivilegesPage for each route when both plugins are NOT available ', () => { + const routes = getSubPluginRoutesByCapabilities(mockSubPlugins, { + [SERVER_APP_ID]: { show: false, crud: false }, + [CASES_FEATURE_ID]: { read_cases: false, crud_cases: false }, + } as unknown as Capabilities); + const casesRoute = routes.find((r) => r.path === 'cases'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const CasesView = (casesRoute?.component ?? mockRender) as React.ComponentType; + + const alertsRoute = routes.find((r) => r.path === 'alerts'); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const AlertsView = (alertsRoute?.component ?? mockRender) as React.ComponentType; + + expect(shallow()).toMatchInlineSnapshot(` + + `); + expect(shallow()).toMatchInlineSnapshot(` + + `); + }); +}); + +describe('#isSubPluginAvailable', () => { + it('plugin outsides of cases should be available if siem privilege is all and independently of cases privileges', () => { + expect( + isSubPluginAvailable('pluginKey', { + [SERVER_APP_ID]: { show: true, crud: true }, + [CASES_FEATURE_ID]: { read_cases: false, crud_cases: false }, + } as unknown as Capabilities) + ).toBeTruthy(); + }); + + it('plugin outsides of cases should be available if siem privilege is read and independently of cases privileges', () => { + expect( + isSubPluginAvailable('pluginKey', { + [SERVER_APP_ID]: { show: true, crud: false }, + [CASES_FEATURE_ID]: { read_cases: false, crud_cases: false }, + } as unknown as Capabilities) + ).toBeTruthy(); + }); + + it('plugin outsides of cases should NOT be available if siem privilege is none and independently of cases privileges', () => { + expect( + isSubPluginAvailable('pluginKey', { + [SERVER_APP_ID]: { show: false, crud: false }, + [CASES_FEATURE_ID]: { read_cases: false, crud_cases: false }, + } as unknown as Capabilities) + ).toBeFalsy(); + }); + + it('cases plugin should be available if cases privilege is all and independently of siem privileges', () => { + expect( + isSubPluginAvailable('cases', { + [SERVER_APP_ID]: { show: false, crud: false }, + [CASES_FEATURE_ID]: { read_cases: true, crud_cases: true }, + } as unknown as Capabilities) + ).toBeTruthy(); + }); + + it('cases plugin should be available if cases privilege is read and independently of siem privileges', () => { + expect( + isSubPluginAvailable('cases', { + [SERVER_APP_ID]: { show: false, crud: false }, + [CASES_FEATURE_ID]: { read_cases: true, crud_cases: false }, + } as unknown as Capabilities) + ).toBeTruthy(); + }); + + it('cases plugin should NOT be available if cases privilege is none independently of siem privileges', () => { + expect( + isSubPluginAvailable('pluginKey', { + [SERVER_APP_ID]: { show: false, crud: false }, + [CASES_FEATURE_ID]: { read_cases: false, crud_cases: false }, + } as unknown as Capabilities) + ).toBeFalsy(); + }); +}); + +describe('RedirectRoute', () => { + it('RedirectRoute should redirect to overview page when siem and case privileges are all', () => { + const mockCapabilitities = { + [SERVER_APP_ID]: { show: true, crud: true }, + [CASES_FEATURE_ID]: { read_cases: true, crud_cases: true }, + } as unknown as Capabilities; + expect(shallow()).toMatchInlineSnapshot(` + + `); + }); + + it('RedirectRoute should redirect to overview page when siem and case privileges are read', () => { + const mockCapabilitities = { + [SERVER_APP_ID]: { show: true, crud: false }, + [CASES_FEATURE_ID]: { read_cases: true, crud_cases: false }, + } as unknown as Capabilities; + expect(shallow()).toMatchInlineSnapshot(` + + `); + }); + + it('RedirectRoute should redirect to overview page when siem and case privileges are off', () => { + const mockCapabilitities = { + [SERVER_APP_ID]: { show: false, crud: false }, + [CASES_FEATURE_ID]: { read_cases: false, crud_cases: false }, + } as unknown as Capabilities; + expect(shallow()).toMatchInlineSnapshot(` + + `); + }); + + it('RedirectRoute should redirect to overview page when siem privilege is read and case privilege is all', () => { + const mockCapabilitities = { + [SERVER_APP_ID]: { show: true, crud: false }, + [CASES_FEATURE_ID]: { read_cases: true, crud_cases: true }, + } as unknown as Capabilities; + expect(shallow()).toMatchInlineSnapshot(` + + `); + }); + + it('RedirectRoute should redirect to overview page when siem privilege is read and case privilege is read', () => { + const mockCapabilitities = { + [SERVER_APP_ID]: { show: true, crud: false }, + [CASES_FEATURE_ID]: { read_cases: true, crud_cases: true }, + } as unknown as Capabilities; + expect(shallow()).toMatchInlineSnapshot(` + + `); + }); + + it('RedirectRoute should redirect to cases page when siem privilege is none and case privilege is read', () => { + const mockCapabilitities = { + [SERVER_APP_ID]: { show: false, crud: false }, + [CASES_FEATURE_ID]: { read_cases: true, crud_cases: false }, + } as unknown as Capabilities; + expect(shallow()).toMatchInlineSnapshot(` + + `); + }); + + it('RedirectRoute should redirect to cases page when siem privilege is none and case privilege is all', () => { + const mockCapabilitities = { + [SERVER_APP_ID]: { show: false, crud: false }, + [CASES_FEATURE_ID]: { read_cases: true, crud_cases: true }, + } as unknown as Capabilities; + expect(shallow()).toMatchInlineSnapshot(` + + `); + }); +}); diff --git a/x-pack/plugins/security_solution/public/helpers.ts b/x-pack/plugins/security_solution/public/helpers.tsx similarity index 63% rename from x-pack/plugins/security_solution/public/helpers.ts rename to x-pack/plugins/security_solution/public/helpers.tsx index aba46cffee193..066e6a4cb4684 100644 --- a/x-pack/plugins/security_solution/public/helpers.ts +++ b/x-pack/plugins/security_solution/public/helpers.tsx @@ -6,24 +6,30 @@ */ import { isEmpty } from 'lodash/fp'; -import { matchPath } from 'react-router-dom'; +import React from 'react'; +import { matchPath, RouteProps, Redirect } from 'react-router-dom'; -import { CoreStart } from '../../../../src/core/public'; +import { Capabilities, CoreStart } from '../../../../src/core/public'; import { ALERTS_PATH, - APP_ID, + APP_UI_ID, EXCEPTIONS_PATH, RULES_PATH, UEBA_PATH, RISKY_HOSTS_INDEX_PREFIX, + SERVER_APP_ID, + CASES_FEATURE_ID, + OVERVIEW_PATH, + CASES_PATH, } from '../common/constants'; import { FactoryQueryTypes, StrategyResponseType, } from '../common/search_strategy/security_solution'; import { TimelineEqlResponse } from '../common/search_strategy/timeline'; +import { NoPrivilegesPage } from './app/no_privileges'; import { SecurityPageName } from './app/types'; -import { InspectResponse } from './types'; +import { CASES_SUB_PLUGIN_KEY, InspectResponse, StartedSubPlugins } from './types'; export const parseRoute = (location: Pick) => { if (!isEmpty(location.hash)) { @@ -59,49 +65,49 @@ export const manageOldSiemRoutes = async (coreStart: CoreStart) => { switch (pageName) { case SecurityPageName.overview: - application.navigateToApp(APP_ID, { + application.navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.overview, replace: true, path, }); break; case 'ml-hosts': - application.navigateToApp(APP_ID, { + application.navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.hosts, replace: true, path: `/ml-hosts${path}`, }); break; case SecurityPageName.hosts: - application.navigateToApp(APP_ID, { + application.navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.hosts, replace: true, path, }); break; case 'ml-network': - application.navigateToApp(APP_ID, { + application.navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.network, replace: true, path: `/ml-network${path}`, }); break; case SecurityPageName.network: - application.navigateToApp(APP_ID, { + application.navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.network, replace: true, path, }); break; case SecurityPageName.timelines: - application.navigateToApp(APP_ID, { + application.navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.timelines, replace: true, path, }); break; case SecurityPageName.case: - application.navigateToApp(APP_ID, { + application.navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, replace: true, path, @@ -109,28 +115,28 @@ export const manageOldSiemRoutes = async (coreStart: CoreStart) => { break; case SecurityPageName.detections: case SecurityPageName.alerts: - application.navigateToApp(APP_ID, { + application.navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.alerts, replace: true, path, }); break; case SecurityPageName.rules: - application.navigateToApp(APP_ID, { + application.navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, replace: true, path, }); break; case SecurityPageName.exceptions: - application.navigateToApp(APP_ID, { + application.navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.exceptions, replace: true, path, }); break; default: - application.navigateToApp(APP_ID, { + application.navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.overview, replace: true, path, @@ -158,3 +164,47 @@ export const isDetectionsPath = (pathname: string): boolean => { export const getHostRiskIndex = (spaceId: string): string => { return `${RISKY_HOSTS_INDEX_PREFIX}${spaceId}`; }; + +export const getSubPluginRoutesByCapabilities = ( + subPlugins: StartedSubPlugins, + capabilities: Capabilities +): RouteProps[] => { + return [ + ...Object.entries(subPlugins).reduce((acc, [key, value]) => { + if (isSubPluginAvailable(key, capabilities)) { + return [...acc, ...value.routes]; + } + return [ + ...acc, + ...value.routes.map((route: RouteProps) => ({ + path: route.path, + component: () => , + })), + ]; + }, []), + { + path: '', + component: () => , + }, + ]; +}; + +export const isSubPluginAvailable = (pluginKey: string, capabilities: Capabilities): boolean => { + if (CASES_SUB_PLUGIN_KEY === pluginKey) { + return capabilities[CASES_FEATURE_ID].read_cases === true; + } + return capabilities[SERVER_APP_ID].show === true; +}; + +export const RedirectRoute = React.memo<{ capabilities: Capabilities }>(({ capabilities }) => { + const overviewAvailable = isSubPluginAvailable('overview', capabilities); + const casesAvailable = isSubPluginAvailable(CASES_SUB_PLUGIN_KEY, capabilities); + if (overviewAvailable) { + return ; + } + if (casesAvailable) { + return ; + } + return ; +}); +RedirectRoute.displayName = 'RedirectRoute'; diff --git a/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts b/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts index f4e14605cab47..3a584f7fefb50 100644 --- a/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts +++ b/x-pack/plugins/security_solution/public/hosts/pages/details/utils.ts @@ -15,7 +15,7 @@ import { getHostDetailsUrl } from '../../../common/components/link_to/redirect_t import * as i18n from '../translations'; import { HostRouteSpyState } from '../../../common/utils/route/types'; import { GetUrlForApp } from '../../../common/components/navigation/types'; -import { APP_ID } from '../../../../common/constants'; +import { APP_UI_ID } from '../../../../common/constants'; import { SecurityPageName } from '../../../app/types'; export const type = hostsModel.HostsType.details; @@ -37,7 +37,7 @@ export const getBreadcrumbs = ( let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: getUrlForApp(APP_ID, { + href: getUrlForApp(APP_UI_ID, { path: !isEmpty(search[0]) ? search[0] : '', deepLinkId: SecurityPageName.hosts, }), @@ -49,7 +49,7 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: params.detailName, - href: getUrlForApp(APP_ID, { + href: getUrlForApp(APP_UI_ID, { path: getHostDetailsUrl(params.detailName, !isEmpty(search[0]) ? search[0] : ''), deepLinkId: SecurityPageName.hosts, }), diff --git a/x-pack/plugins/security_solution/public/management/components/context_menu_with_router_support/context_menu_with_router_support.test.tsx b/x-pack/plugins/security_solution/public/management/components/context_menu_with_router_support/context_menu_with_router_support.test.tsx index ae343a57c734f..288c0c074e93d 100644 --- a/x-pack/plugins/security_solution/public/management/components/context_menu_with_router_support/context_menu_with_router_support.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/context_menu_with_router_support/context_menu_with_router_support.test.tsx @@ -13,7 +13,7 @@ import { ContextMenuWithRouterSupportProps, } from './context_menu_with_router_support'; import { act, fireEvent, waitForElementToBeRemoved } from '@testing-library/react'; -import { APP_ID } from '../../../../common/constants'; +import { APP_UI_ID } from '../../../../common/constants'; describe('When using the ContextMenuWithRouterSupport component', () => { let appTestContext: AppContextTestRender; @@ -42,7 +42,7 @@ describe('When using the ContextMenuWithRouterSupport component', () => { }, { children: 'click me 2', - navigateAppId: APP_ID, + navigateAppId: APP_UI_ID, navigateOptions: { path: '/one/two/three', }, @@ -126,7 +126,7 @@ describe('When using the ContextMenuWithRouterSupport component', () => { }); expect(appTestContext.coreStart.application.navigateToApp).toHaveBeenCalledWith( - APP_ID, + APP_UI_ID, expect.objectContaining({ path: '/one/two/three' }) ); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx index 81432edbdd5fe..a766b9a23082a 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx @@ -7,7 +7,7 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { APP_ID } from '../../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../../common/constants'; import { pagePathGetters } from '../../../../../../../fleet/public'; import { getEndpointDetailsPath } from '../../../../common/routing'; import { HostMetadata, MaybeImmutable } from '../../../../../../common/endpoint/types'; @@ -67,7 +67,7 @@ export const useEndpointActionItems = ( 'data-test-subj': 'unIsolateLink', icon: 'logoSecurity', key: 'unIsolateHost', - navigateAppId: APP_ID, + navigateAppId: APP_UI_ID, navigateOptions: { path: endpointUnIsolatePath, }, @@ -85,7 +85,7 @@ export const useEndpointActionItems = ( 'data-test-subj': 'isolateLink', icon: 'logoSecurity', key: 'isolateHost', - navigateAppId: APP_ID, + navigateAppId: APP_UI_ID, navigateOptions: { path: endpointIsolatePath, }, @@ -105,7 +105,7 @@ export const useEndpointActionItems = ( 'data-test-subj': 'hostLink', icon: 'logoSecurity', key: 'hostDetailsLink', - navigateAppId: APP_ID, + navigateAppId: APP_UI_ID, navigateOptions: { path: `/hosts/${endpointHostName}` }, href: getAppUrl({ path: `/hosts/${endpointHostName}` }), children: ( diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx index 65308012df080..6b5d6f03aca28 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.tsx @@ -28,7 +28,7 @@ import { import { PolicyDetailsRouteState } from '../../../../../common/endpoint/types'; import { getEndpointListPath } from '../../../common/routing'; import { useAppUrl } from '../../../../common/lib/kibana'; -import { APP_ID } from '../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../common/constants'; export const PolicyDetails = React.memo(() => { // TODO: Remove this and related code when removing FF @@ -68,7 +68,7 @@ export const PolicyDetails = React.memo(() => { ), backButtonUrl: getAppUrl({ path: endpointListPath }), onBackButtonNavigateTo: [ - APP_ID, + APP_UI_ID, { path: endpointListPath, }, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx index 650bf6115c9d9..28f2ecf7eb98e 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.test.tsx @@ -118,7 +118,7 @@ describe('Policy Form Layout', () => { cancelbutton.simulate('click', { button: 0 }); const navigateToAppMockedCalls = coreStart.application.navigateToApp.mock.calls; expect(navigateToAppMockedCalls[navigateToAppMockedCalls.length - 1]).toEqual([ - 'securitySolution', + 'securitySolutionUI', { path: endpointListPath }, ]); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.tsx index bae2c21242d97..2345deabb5101 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/components/policy_form_layout.tsx @@ -35,7 +35,7 @@ import { SpyRoute } from '../../../../../../common/utils/route/spy_routes'; import { SecurityPageName } from '../../../../../../app/types'; import { getEndpointListPath } from '../../../../../common/routing'; import { useNavigateToAppEventHandler } from '../../../../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; -import { APP_ID } from '../../../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../../../common/constants'; import { PolicyDetailsRouteState } from '../../../../../../../common/endpoint/types'; import { SecuritySolutionPageWrapper } from '../../../../../../common/components/page_wrapper'; import { PolicyDetailsForm } from '../../policy_details_form'; @@ -65,7 +65,7 @@ export const PolicyFormLayout = React.memo(() => { const routingOnCancelNavigateTo = routeState?.onCancelNavigateTo; const navigateToAppArguments = useMemo((): Parameters => { - return routingOnCancelNavigateTo ?? [APP_ID, { path: hostListRouterPath }]; + return routingOnCancelNavigateTo ?? [APP_UI_ID, { path: hostListRouterPath }]; }, [hostListRouterPath, routingOnCancelNavigateTo]); // Handle showing update statuses diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/behavior.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/behavior.tsx index 06cf666f2950e..ccb19da4a4ada 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/behavior.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/behavior.tsx @@ -15,7 +15,7 @@ import { ConfigForm } from '../../components/config_form'; import { RadioButtons } from '../components/radio_buttons'; import { UserNotification } from '../components/user_notification'; import { ProtectionSwitch } from '../components/protection_switch'; -import { APP_ID } from '../../../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../../../common/constants'; import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app'; import { SecurityPageName } from '../../../../../../app/types'; @@ -51,7 +51,7 @@ export const BehaviorProtection = React.memo(() => { defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page." values={{ detectionRulesLink: ( - + { defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page." values={{ detectionRulesLink: ( - + { defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page." values={{ detectionRulesLink: ( - + { defaultMessage="View {detectionRulesLink}. Prebuilt rules are tagged “Elastic” on the Detection Rules page." values={{ detectionRulesLink: ( - + { const { getAppUrl } = useAppUrl(); @@ -35,19 +35,19 @@ export const useGetLinkTo = (policyId: string, policyName: string) => { } ), onBackButtonNavigateTo: [ - APP_ID, + APP_UI_ID, { path: policyTrustedAppsPath, }, ], backButtonUrl: getAppUrl({ - appId: APP_ID, + appId: APP_UI_ID, path: policyTrustedAppsPath, }), }; }, [getAppUrl, policyName, policyTrustedAppsPath]); - const onClickHandler = useNavigateToAppEventHandler(APP_ID, { + const onClickHandler = useNavigateToAppEventHandler(APP_UI_ID, { state: policyTrustedAppRouteState, path: toRoutePath, }); 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 9165aec3bef8d..b136eef9566eb 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 @@ -20,7 +20,7 @@ import { isLoadedResourceState, } from '../../../../../state'; import { fireEvent, within, act, waitFor } from '@testing-library/react'; -import { APP_ID } from '../../../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../../../common/constants'; import { EndpointPrivileges, useEndpointPrivileges, @@ -203,7 +203,7 @@ describe('when rendering the PolicyTrustedAppsList', () => { }); expect(appTestContext.coreStart.application.navigateToApp).toHaveBeenCalledWith( - APP_ID, + APP_UI_ID, expect.objectContaining({ path: '/administration/trusted_apps?filter=89f72d8a-05b5-4350-8cad-0dc3661d6e67', }) 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 89ff6bd099be4..48f66806b46b4 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 @@ -33,7 +33,7 @@ import { } from '../../../../../common/routing'; import { Immutable, TrustedApp } from '../../../../../../../common/endpoint/types'; import { useAppUrl, useToasts } from '../../../../../../common/lib/kibana'; -import { APP_ID } from '../../../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../../../common/constants'; import { ContextMenuItemNavByRouterProps } from '../../../../../components/context_menu_with_router_support/context_menu_item_nav_by_router'; import { ArtifactEntryCollapsibleCardProps } from '../../../../../components/artifact_entry_card'; import { useTestIdGenerator } from '../../../../../components/hooks/use_test_id_generator'; @@ -130,7 +130,7 @@ export const PolicyTrustedAppsList = memo( const policyDetailsPath = getPolicyDetailPath(trustedAppAssignedPolicyId); const thisPolicyMenuProps: ContextMenuItemNavByRouterProps = { - navigateAppId: APP_ID, + navigateAppId: APP_UI_ID, navigateOptions: { path: policyDetailsPath, }, @@ -150,8 +150,8 @@ export const PolicyTrustedAppsList = memo( 'xpack.securitySolution.endpoint.policy.trustedApps.list.viewAction', { defaultMessage: 'View full details' } ), - href: getAppUrl({ appId: APP_ID, path: viewUrlPath }), - navigateAppId: APP_ID, + href: getAppUrl({ appId: APP_UI_ID, path: viewUrlPath }), + navigateAppId: APP_UI_ID, navigateOptions: { path: viewUrlPath }, 'data-test-subj': getTestId('viewFullDetailsAction'), }, diff --git a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx index dd9a15eb3266a..72b52b0f35278 100644 --- a/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/trusted_apps/view/components/trusted_apps_grid/index.tsx @@ -37,7 +37,7 @@ import { ArtifactEntryCardProps, } from '../../../../../components/artifact_entry_card'; import { AppAction } from '../../../../../../common/store/actions'; -import { APP_ID } from '../../../../../../../common/constants'; +import { APP_UI_ID } from '../../../../../../../common/constants'; import { useAppUrl } from '../../../../../../common/lib/kibana'; export interface PaginationBarProps { @@ -115,7 +115,7 @@ export const TrustedAppsGrid = memo(() => { backLink: { label: BACK_TO_TRUSTED_APPS_LABEL, navigateTo: [ - APP_ID, + APP_UI_ID, { path: currentPagePath, }, @@ -123,7 +123,7 @@ export const TrustedAppsGrid = memo(() => { href: getAppUrl({ path: currentPagePath }), }, onCancelNavigateTo: [ - APP_ID, + APP_UI_ID, { path: currentPagePath, }, @@ -131,6 +131,7 @@ export const TrustedAppsGrid = memo(() => { }; policyToNavOptionsMap[policyId] = { + navigateAppId: APP_UI_ID, navigateOptions: { path: policyDetailsPath, state: routeState, diff --git a/x-pack/plugins/security_solution/public/network/pages/details/utils.ts b/x-pack/plugins/security_solution/public/network/pages/details/utils.ts index 637180203c6d1..7bcf79ba4b434 100644 --- a/x-pack/plugins/security_solution/public/network/pages/details/utils.ts +++ b/x-pack/plugins/security_solution/public/network/pages/details/utils.ts @@ -15,7 +15,7 @@ import * as i18n from '../translations'; import { NetworkRouteType } from '../navigation/types'; import { NetworkRouteSpyState } from '../../../common/utils/route/types'; import { GetUrlForApp } from '../../../common/components/navigation/types'; -import { APP_ID } from '../../../../common/constants'; +import { APP_UI_ID } from '../../../../common/constants'; import { SecurityPageName } from '../../../app/types'; export const type = networkModel.NetworkType.details; @@ -36,7 +36,7 @@ export const getBreadcrumbs = ( let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: getUrlForApp(APP_ID, { + href: getUrlForApp(APP_UI_ID, { deepLinkId: SecurityPageName.network, path: !isEmpty(search[0]) ? search[0] : '', }), @@ -47,7 +47,7 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: decodeIpv6(params.detailName), - href: getUrlForApp(APP_ID, { + href: getUrlForApp(APP_UI_ID, { deepLinkId: SecurityPageName.network, path: getNetworkDetailsUrl( params.detailName, diff --git a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx index 11270fe377733..e74e8f82d8244 100644 --- a/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/alerts_by_category/index.tsx @@ -9,7 +9,7 @@ import numeral from '@elastic/numeral'; import React, { useEffect, useMemo, useCallback } from 'react'; import { Position } from '@elastic/charts'; -import { DEFAULT_NUMBER_FORMAT, APP_ID } from '../../../../common/constants'; +import { DEFAULT_NUMBER_FORMAT, APP_UI_ID } from '../../../../common/constants'; import { SHOWING, UNIT } from '../../../common/components/alerts_viewer/translations'; import { MatrixHistogram } from '../../../common/components/matrix_histogram'; import { useKibana, useUiSetting$ } from '../../../common/lib/kibana'; @@ -68,7 +68,7 @@ const AlertsByCategoryComponent: React.FC = ({ const goToHostAlerts = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.hosts, path: getTabsOnHostsUrl(HostsTableType.alerts, urlSearch), }); diff --git a/x-pack/plugins/security_solution/public/overview/components/endpoint_notice/index.tsx b/x-pack/plugins/security_solution/public/overview/components/endpoint_notice/index.tsx index 715663b60c7dc..39193742021f1 100644 --- a/x-pack/plugins/security_solution/public/overview/components/endpoint_notice/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/endpoint_notice/index.tsx @@ -9,15 +9,15 @@ import React, { memo } from 'react'; import { EuiCallOut, EuiButton, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { useKibana } from '../../../common/lib/kibana'; -import { APP_ID } from '../../../../common/constants'; +import { APP_UI_ID } from '../../../../common/constants'; import { getEndpointListPath } from '../../../management/common/routing'; import { useNavigateToAppEventHandler } from '../../../common/hooks/endpoint/use_navigate_to_app_event_handler'; export const EndpointNotice = memo<{ onDismiss: () => void }>(({ onDismiss }) => { const { getUrlForApp } = useKibana().services.application; const endpointsPath = getEndpointListPath({ name: 'endpointList' }); - const endpointsLink = getUrlForApp(APP_ID, { path: endpointsPath }); - const handleGetStartedClick = useNavigateToAppEventHandler(APP_ID, { + const endpointsLink = getUrlForApp(APP_UI_ID, { path: endpointsPath }); + const handleGetStartedClick = useNavigateToAppEventHandler(APP_UI_ID, { path: endpointsPath, }); diff --git a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx index 7de1d9d9f40c1..562d1d1fd7aad 100644 --- a/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/events_by_dataset/index.tsx @@ -10,7 +10,7 @@ import numeral from '@elastic/numeral'; import React, { useEffect, useMemo, useCallback } from 'react'; import uuid from 'uuid'; -import { DEFAULT_NUMBER_FORMAT, APP_ID } from '../../../../common/constants'; +import { DEFAULT_NUMBER_FORMAT, APP_UI_ID } from '../../../../common/constants'; import { SHOWING, UNIT } from '../../../common/components/events_viewer/translations'; import { getTabsOnHostsUrl } from '../../../common/components/link_to/redirect_to_hosts'; import { MatrixHistogram } from '../../../common/components/matrix_histogram'; @@ -101,7 +101,7 @@ const EventsByDatasetComponent: React.FC = ({ const goToHostEvents = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.hosts, path: getTabsOnHostsUrl(HostsTableType.events, urlSearch), }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx index a0307380ce802..723c768190cb5 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_host/index.tsx @@ -11,7 +11,7 @@ import numeral from '@elastic/numeral'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useMemo, useCallback } from 'react'; -import { DEFAULT_NUMBER_FORMAT, APP_ID } from '../../../../common/constants'; +import { DEFAULT_NUMBER_FORMAT, APP_UI_ID } from '../../../../common/constants'; import { ESQuery } from '../../../../common/typed_json'; import { ID as OverviewHostQueryId, useHostOverview } from '../../containers/overview_host'; import { HeaderSection } from '../../../common/components/header_section'; @@ -57,7 +57,7 @@ const OverviewHostComponent: React.FC = ({ const goToHost = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.hosts, path: getHostDetailsUrl('allHosts', urlSearch), }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx index 08b2392f60488..dfc144be8e5bb 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.test.tsx @@ -138,7 +138,7 @@ describe('OverviewNetwork', () => { preventDefault: jest.fn(), }); - expect(mockNavigateToApp).toBeCalledWith('securitySolution', { + expect(mockNavigateToApp).toBeCalledWith('securitySolutionUI', { path: '', deepLinkId: SecurityPageName.network, }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx index 214cd7b3f055b..dd779f1656e92 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_network/index.tsx @@ -11,7 +11,7 @@ import numeral from '@elastic/numeral'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useMemo, useCallback } from 'react'; -import { DEFAULT_NUMBER_FORMAT, APP_ID } from '../../../../common/constants'; +import { DEFAULT_NUMBER_FORMAT, APP_UI_ID } from '../../../../common/constants'; import { ESQuery } from '../../../../common/typed_json'; import { HeaderSection } from '../../../common/components/header_section'; import { useUiSetting$, useKibana } from '../../../common/lib/kibana'; @@ -59,7 +59,7 @@ const OverviewNetworkComponent: React.FC = ({ const goToNetwork = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.network, path: getNetworkUrl(urlSearch), }); diff --git a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/navigate_to_host.tsx b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/navigate_to_host.tsx index 4680aedc0ba60..2c8e281b376d6 100644 --- a/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/navigate_to_host.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/overview_risky_host_links/navigate_to_host.tsx @@ -7,7 +7,7 @@ import React, { useCallback } from 'react'; import { EuiButtonEmpty, EuiText } from '@elastic/eui'; -import { APP_ID, SecurityPageName } from '../../../../common/constants'; +import { APP_UI_ID, SecurityPageName } from '../../../../common/constants'; import { useKibana } from '../../../common/lib/kibana'; export const NavigateToHost: React.FC<{ name: string }> = ({ name }): JSX.Element => { @@ -27,7 +27,7 @@ export const NavigateToHost: React.FC<{ name: string }> = ({ name }): JSX.Elemen query: { match_phrase: { 'host.name': name } }, }, ]); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.hosts, }); }, diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx index 207c6ef16bd16..e2d7d8264e9f9 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/recent_cases/index.tsx @@ -14,7 +14,7 @@ import { } from '../../../common/components/link_to/redirect_to_case'; import { useFormatUrl } from '../../../common/components/link_to'; import { useGetUserCasesPermissions, useKibana } from '../../../common/lib/kibana'; -import { APP_ID } from '../../../../common/constants'; +import { APP_ID, APP_UI_ID } from '../../../../common/constants'; import { SecurityPageName } from '../../../app/types'; import { AllCasesNavProps } from '../../../cases/components/all_cases'; @@ -33,7 +33,7 @@ const RecentCasesComponent = () => { href: formatUrl(getCaseUrl()), onClick: async (e) => { e?.preventDefault(); - return navigateToApp(APP_ID, { deepLinkId: SecurityPageName.case }); + return navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case }); }, }, caseDetailsNavigation: { @@ -42,7 +42,7 @@ const RecentCasesComponent = () => { }, onClick: async ({ detailName, subCaseId, search }, e) => { e?.preventDefault(); - return navigateToApp(APP_ID, { + return navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: detailName, search, subCaseId }), }); @@ -52,7 +52,7 @@ const RecentCasesComponent = () => { href: formatUrl(getCreateCaseUrl()), onClick: async (e) => { e?.preventDefault(); - return navigateToApp(APP_ID, { + return navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(), }); diff --git a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx index dcd8783688ca7..ed59918ad4499 100644 --- a/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx +++ b/x-pack/plugins/security_solution/public/overview/components/recent_timelines/index.tsx @@ -25,7 +25,7 @@ import { LoadingPlaceholders } from '../loading_placeholders'; import { useTimelineStatus } from '../../../timelines/components/open_timeline/use_timeline_status'; import { useKibana } from '../../../common/lib/kibana'; import { SecurityPageName } from '../../../app/types'; -import { APP_ID } from '../../../../common/constants'; +import { APP_UI_ID } from '../../../../common/constants'; import { useFormatUrl } from '../../../common/components/link_to'; import { LinkAnchor } from '../../../common/components/links'; import { Direction } from '../../../../common/search_strategy'; @@ -61,7 +61,7 @@ const StatefulRecentTimelinesComponent: React.FC = ({ filterBy }) => { const goToTimelines = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.timelines, }); }, diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index f016c9712c650..6167aa72a47b4 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -37,16 +37,16 @@ import { SOLUTION_NAME } from './common/translations'; import { APP_ID, - OVERVIEW_PATH, - APP_OVERVIEW_PATH, + APP_UI_ID, APP_PATH, DEFAULT_INDEX_KEY, APP_ICON_SOLUTION, DETECTION_ENGINE_INDEX_URL, + SERVER_APP_ID, } from '../common/constants'; -import { getDeepLinks, updateGlobalNavigation } from './app/deep_links'; -import { manageOldSiemRoutes } from './helpers'; +import { getDeepLinks } from './app/deep_links'; +import { getSubPluginRoutesByCapabilities, manageOldSiemRoutes } from './helpers'; import { IndexFieldsStrategyRequest, IndexFieldsStrategyResponse, @@ -98,7 +98,7 @@ export class Plugin implements IPlugin ({ + navLinkStatus: AppNavLinkStatus.hidden, // workaround to prevent main navLink to switch to visible after update. should not be needed + deepLinks: getDeepLinks( + this.experimentalFeatures, + undefined, + core.application.capabilities + ), + })); } return {}; @@ -313,9 +318,9 @@ export class Plugin implements IPlugin { wrapper.find(`[data-test-subj="attach-timeline-case-button"]`).first().simulate('click'); wrapper.find(`[data-test-subj="attach-timeline-existing-case"]`).first().simulate('click'); - expect(navigateToApp).toHaveBeenCalledWith('securitySolution', { + expect(navigateToApp).toHaveBeenCalledWith('securitySolutionUI', { path: '/create', deepLinkId: SecurityPageName.case, }); @@ -84,7 +84,7 @@ describe('AddToCaseButton', () => { wrapper.find(`[data-test-subj="attach-timeline-case-button"]`).first().simulate('click'); wrapper.find(`[data-test-subj="attach-timeline-existing-case"]`).first().simulate('click'); - expect(navigateToApp).toHaveBeenCalledWith('securitySolution', { + expect(navigateToApp).toHaveBeenCalledWith('securitySolutionUI', { path: '/case-id', deepLinkId: SecurityPageName.case, }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx index 553b827f2a64c..ff8746f4729d7 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.tsx @@ -11,7 +11,7 @@ import React, { useCallback, useMemo, useState } from 'react'; import { useDispatch } from 'react-redux'; import { Case, SubCase } from '../../../../../../cases/common'; -import { APP_ID } from '../../../../../common/constants'; +import { APP_ID, APP_UI_ID } from '../../../../../common/constants'; import { timelineSelectors } from '../../../../timelines/store/timeline'; import { setInsertTimeline, showTimeline } from '../../../store/timeline/actions'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; @@ -55,7 +55,7 @@ const AddToCaseButtonComponent: React.FC = ({ timelineId }) => { const onRowClick = useCallback( async (theCase?: Case | SubCase) => { openCaseModal(false); - await navigateToApp(APP_ID, { + await navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: theCase != null ? getCaseDetailsUrl({ id: theCase.id }) : getCreateCaseUrl(), }); @@ -90,7 +90,7 @@ const AddToCaseButtonComponent: React.FC = ({ timelineId }) => { const handleNewCaseClick = useCallback(() => { handlePopoverClose(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(), }).then(() => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap index 01ef89cd35c9f..ad149cbcd63d0 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/timelines/components/side_panel/host_details/__snapshots__/expandable_host.test.tsx.snap @@ -33,21 +33,6 @@ exports[`Expandable Host Component ExpandableHostDetails: rendering it should re opacity: 1; } -.c4 { - padding: 16px; - background: rgba(250,251,253,0.9); - bottom: 0; - left: 0; - position: absolute; - right: 0; - top: 0; - z-index: 1000; -} - -.c5 { - height: 100%; -} - .c2 dt { font-size: 12px !important; } @@ -75,6 +60,21 @@ exports[`Expandable Host Component ExpandableHostDetails: rendering it should re z-index: 2; } +.c4 { + padding: 16px; + background: rgba(250,251,253,0.9); + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; + z-index: 1000; +} + +.c5 { + height: 100%; +} + = ({ const goToRuleDetails = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(APP_ID, { + navigateToApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(ruleId ?? '', search), }); @@ -83,7 +83,7 @@ export const RenderRuleName: React.FC = ({ const href = useMemo( () => - getUrlForApp(APP_ID, { + getUrlForApp(APP_UI_ID, { deepLinkId: SecurityPageName.rules, path: getRuleDetailsUrl(ruleId ?? '', search), }), diff --git a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx index 2bf6e1259ff75..58049ef164e15 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx @@ -18,7 +18,7 @@ import { TimelinesPage } from './timelines_page'; import { PAGE_TITLE } from './translations'; import { appendSearch } from '../../common/components/link_to/helpers'; import { GetUrlForApp } from '../../common/components/navigation/types'; -import { APP_ID, TIMELINES_PATH } from '../../../common/constants'; +import { APP_UI_ID, TIMELINES_PATH } from '../../../common/constants'; import { SecurityPageName } from '../../app/types'; const timelinesPagePath = `${TIMELINES_PATH}/:tabName(${TimelineType.default}|${TimelineType.template})`; @@ -31,7 +31,7 @@ export const getBreadcrumbs = ( ): ChromeBreadcrumb[] => [ { text: PAGE_TITLE, - href: getUrlForApp(APP_ID, { + href: getUrlForApp(APP_UI_ID, { deepLinkId: SecurityPageName.timelines, path: !isEmpty(search[0]) ? search[0] : '', }), diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 61813d1a122b4..376cfbf31afe2 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -87,11 +87,12 @@ export interface AppObservableLibs { export type InspectResponse = Inspect & { response: string[] }; +export const CASES_SUB_PLUGIN_KEY = 'cases'; export interface SubPlugins { alerts: Detections; rules: Rules; exceptions: Exceptions; - cases: Cases; + [CASES_SUB_PLUGIN_KEY]: Cases; hosts: Hosts; network: Network; ueba: Ueba; @@ -105,7 +106,7 @@ export interface StartedSubPlugins { alerts: ReturnType; rules: ReturnType; exceptions: ReturnType; - cases: ReturnType; + [CASES_SUB_PLUGIN_KEY]: ReturnType; hosts: ReturnType; network: ReturnType; ueba: ReturnType; diff --git a/x-pack/plugins/security_solution/public/ueba/pages/details/utils.ts b/x-pack/plugins/security_solution/public/ueba/pages/details/utils.ts index d5f346d3ece64..8fcc0fce0b7a3 100644 --- a/x-pack/plugins/security_solution/public/ueba/pages/details/utils.ts +++ b/x-pack/plugins/security_solution/public/ueba/pages/details/utils.ts @@ -15,7 +15,7 @@ import { getUebaDetailsUrl } from '../../../common/components/link_to/redirect_t import * as i18n from '../translations'; import { UebaRouteSpyState } from '../../../common/utils/route/types'; import { GetUrlForApp } from '../../../common/components/navigation/types'; -import { APP_ID } from '../../../../common/constants'; +import { APP_UI_ID } from '../../../../common/constants'; import { SecurityPageName } from '../../../app/types'; export const type = uebaModel.UebaType.details; @@ -35,7 +35,7 @@ export const getBreadcrumbs = ( let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: getUrlForApp(APP_ID, { + href: getUrlForApp(APP_UI_ID, { path: !isEmpty(search[0]) ? search[0] : '', deepLinkId: SecurityPageName.ueba, }), @@ -47,7 +47,7 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: params.detailName, - href: getUrlForApp(APP_ID, { + href: getUrlForApp(APP_UI_ID, { path: getUebaDetailsUrl(params.detailName, !isEmpty(search[0]) ? search[0] : ''), deepLinkId: SecurityPageName.ueba, }), From 6d6cb5c836270f8d1c19bc3e175ed80b4ae546d8 Mon Sep 17 00:00:00 2001 From: Chris Roberson Date: Mon, 25 Oct 2021 19:10:03 -0400 Subject: [PATCH 2/4] Remove ability to configure index (#114558) Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../advanced/running-elasticsearch.asciidoc | 1 - docs/settings/task-manager-settings.asciidoc | 3 --- ...task-manager-production-considerations.asciidoc | 2 +- .../resources/base/bin/kibana-docker | 1 - x-pack/plugins/task_manager/server/config.test.ts | 14 -------------- x-pack/plugins/task_manager/server/config.ts | 9 --------- x-pack/plugins/task_manager/server/constants.ts | 7 +++++++ .../server/ephemeral_task_lifecycle.test.ts | 1 - x-pack/plugins/task_manager/server/index.test.ts | 11 ----------- .../managed_configuration.test.ts | 1 - .../monitoring/configuration_statistics.test.ts | 1 - .../monitoring/monitoring_stats_stream.test.ts | 1 - x-pack/plugins/task_manager/server/plugin.test.ts | 3 --- x-pack/plugins/task_manager/server/plugin.ts | 5 +++-- .../task_manager/server/saved_objects/index.ts | 5 +++-- 15 files changed, 14 insertions(+), 51 deletions(-) create mode 100644 x-pack/plugins/task_manager/server/constants.ts diff --git a/docs/developer/advanced/running-elasticsearch.asciidoc b/docs/developer/advanced/running-elasticsearch.asciidoc index 324d2af2ed3af..36f9ee420d41d 100644 --- a/docs/developer/advanced/running-elasticsearch.asciidoc +++ b/docs/developer/advanced/running-elasticsearch.asciidoc @@ -76,7 +76,6 @@ If many other users will be interacting with your remote cluster, you'll want to [source,bash] ---- kibana.index: '.{YourGitHubHandle}-kibana' -xpack.task_manager.index: '.{YourGitHubHandle}-task-manager-kibana' ---- ==== Running remote clusters diff --git a/docs/settings/task-manager-settings.asciidoc b/docs/settings/task-manager-settings.asciidoc index ef45c262f897b..c61ef83953347 100644 --- a/docs/settings/task-manager-settings.asciidoc +++ b/docs/settings/task-manager-settings.asciidoc @@ -22,9 +22,6 @@ Task Manager runs background tasks by polling for work on an interval. You can | `xpack.task_manager.request_capacity` | How many requests can Task Manager buffer before it rejects new requests. Defaults to 1000. -| `xpack.task_manager.index` - | The name of the index used to store task information. Defaults to `.kibana_task_manager`. - | `xpack.task_manager.max_workers` | The maximum number of tasks that this Kibana instance will run simultaneously. Defaults to 10. Starting in 8.0, it will not be possible to set the value greater than 100. diff --git a/docs/user/production-considerations/task-manager-production-considerations.asciidoc b/docs/user/production-considerations/task-manager-production-considerations.asciidoc index 0de32bf00948b..672c310f138e9 100644 --- a/docs/user/production-considerations/task-manager-production-considerations.asciidoc +++ b/docs/user/production-considerations/task-manager-production-considerations.asciidoc @@ -12,7 +12,7 @@ This has three major benefits: [IMPORTANT] ============================================== -Task definitions for alerts and actions are stored in the index specified by <>. The default is `.kibana_task_manager`. +Task definitions for alerts and actions are stored in the index called `.kibana_task_manager`. You must have at least one replica of this index for production deployments. 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 3a38789fbcac6..ad66e1a16e04c 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 @@ -365,7 +365,6 @@ kibana_vars=( xpack.securitySolution.prebuiltRulesFromFileSystem xpack.securitySolution.prebuiltRulesFromSavedObjects xpack.spaces.maxSpaces - xpack.task_manager.index xpack.task_manager.max_attempts xpack.task_manager.max_poll_inactivity_cycles xpack.task_manager.max_workers diff --git a/x-pack/plugins/task_manager/server/config.test.ts b/x-pack/plugins/task_manager/server/config.test.ts index 8d7a6c7872e7e..4c4db2aba7128 100644 --- a/x-pack/plugins/task_manager/server/config.test.ts +++ b/x-pack/plugins/task_manager/server/config.test.ts @@ -16,7 +16,6 @@ describe('config validation', () => { "enabled": false, "request_capacity": 10, }, - "index": ".kibana_task_manager", "max_attempts": 3, "max_poll_inactivity_cycles": 10, "max_workers": 10, @@ -44,17 +43,6 @@ describe('config validation', () => { `); }); - test('the ElastiSearch Tasks index cannot be used for task manager', () => { - const config: Record = { - index: '.tasks', - }; - expect(() => { - configSchema.validate(config); - }).toThrowErrorMatchingInlineSnapshot( - `"[index]: \\".tasks\\" is an invalid Kibana Task Manager index, as it is already in use by the ElasticSearch Tasks Manager"` - ); - }); - test('the required freshness of the monitored stats config must always be less-than-equal to the poll interval', () => { const config: Record = { monitored_stats_required_freshness: 100, @@ -74,7 +62,6 @@ describe('config validation', () => { "enabled": false, "request_capacity": 10, }, - "index": ".kibana_task_manager", "max_attempts": 3, "max_poll_inactivity_cycles": 10, "max_workers": 10, @@ -119,7 +106,6 @@ describe('config validation', () => { "enabled": false, "request_capacity": 10, }, - "index": ".kibana_task_manager", "max_attempts": 3, "max_poll_inactivity_cycles": 10, "max_workers": 10, diff --git a/x-pack/plugins/task_manager/server/config.ts b/x-pack/plugins/task_manager/server/config.ts index f2026ecac3adc..5a58e45a70d96 100644 --- a/x-pack/plugins/task_manager/server/config.ts +++ b/x-pack/plugins/task_manager/server/config.ts @@ -64,15 +64,6 @@ export const configSchema = schema.object( defaultValue: 1000, min: 1, }), - /* The name of the index used to store task information. */ - index: schema.string({ - defaultValue: '.kibana_task_manager', - validate: (val) => { - if (val.toLowerCase() === '.tasks') { - return `"${val}" is an invalid Kibana Task Manager index, as it is already in use by the ElasticSearch Tasks Manager`; - } - }, - }), /* The maximum number of tasks that this Kibana instance will run simultaneously. */ max_workers: schema.number({ defaultValue: DEFAULT_MAX_WORKERS, diff --git a/x-pack/plugins/task_manager/server/constants.ts b/x-pack/plugins/task_manager/server/constants.ts new file mode 100644 index 0000000000000..e843a0b4815d1 --- /dev/null +++ b/x-pack/plugins/task_manager/server/constants.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ +export const TASK_MANAGER_INDEX = '.kibana_task_manager'; diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts index ec6f25b7f1b61..639bb834eeb4c 100644 --- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts +++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts @@ -43,7 +43,6 @@ describe('EphemeralTaskLifecycle', () => { executionContext, config: { max_workers: 10, - index: 'foo', max_attempts: 9, poll_interval: 6000000, version_conflict_threshold: 80, diff --git a/x-pack/plugins/task_manager/server/index.test.ts b/x-pack/plugins/task_manager/server/index.test.ts index ad2d598fe1082..6fb512bebbd85 100644 --- a/x-pack/plugins/task_manager/server/index.test.ts +++ b/x-pack/plugins/task_manager/server/index.test.ts @@ -37,17 +37,6 @@ const applyTaskManagerDeprecations = (settings: Record = {}) => }; describe('deprecations', () => { - ['.foo', '.kibana_task_manager'].forEach((index) => { - it('logs a warning if index is set', () => { - const { messages } = applyTaskManagerDeprecations({ index }); - expect(messages).toMatchInlineSnapshot(` - Array [ - "\\"xpack.task_manager.index\\" is deprecated. Multitenancy by changing \\"kibana.index\\" will not be supported starting in 8.0. See https://ela.st/kbn-remove-legacy-multitenancy for more details", - ] - `); - }); - }); - it('logs a warning if max_workers is over limit', () => { const { messages } = applyTaskManagerDeprecations({ max_workers: 1000 }); expect(messages).toMatchInlineSnapshot(` diff --git a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts index c9cc5be2d5cd6..271d24d73357b 100644 --- a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts +++ b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts @@ -30,7 +30,6 @@ describe('managed configuration', () => { const context = coreMock.createPluginInitializerContext({ max_workers: 10, - index: 'foo', max_attempts: 9, poll_interval: 3000, version_conflict_threshold: 80, diff --git a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts index bbd5bc217ae3b..77fd9a8f11fab 100644 --- a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts @@ -14,7 +14,6 @@ describe('Configuration Statistics Aggregator', () => { test('merges the static config with the merged configs', async () => { const configuration: TaskManagerConfig = { max_workers: 10, - index: 'foo', max_attempts: 9, poll_interval: 6000000, version_conflict_threshold: 80, diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts index e29dbc978c64a..8aa2d54d89623 100644 --- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts +++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts @@ -18,7 +18,6 @@ beforeEach(() => { describe('createMonitoringStatsStream', () => { const configuration: TaskManagerConfig = { max_workers: 10, - index: 'foo', max_attempts: 9, poll_interval: 6000000, version_conflict_threshold: 80, diff --git a/x-pack/plugins/task_manager/server/plugin.test.ts b/x-pack/plugins/task_manager/server/plugin.test.ts index c2345d7bf8193..20e5f211a5b4e 100644 --- a/x-pack/plugins/task_manager/server/plugin.test.ts +++ b/x-pack/plugins/task_manager/server/plugin.test.ts @@ -17,7 +17,6 @@ describe('TaskManagerPlugin', () => { test('throws if no valid UUID is available', async () => { const pluginInitializerContext = coreMock.createPluginInitializerContext({ max_workers: 10, - index: 'foo', max_attempts: 9, poll_interval: 3000, version_conflict_threshold: 80, @@ -59,7 +58,6 @@ describe('TaskManagerPlugin', () => { test('throws if setup methods are called after start', async () => { const pluginInitializerContext = coreMock.createPluginInitializerContext({ max_workers: 10, - index: 'foo', max_attempts: 9, poll_interval: 3000, version_conflict_threshold: 80, @@ -130,7 +128,6 @@ describe('TaskManagerPlugin', () => { test('it logs a warning when the unsafe `exclude_task_types` config is used', async () => { const pluginInitializerContext = coreMock.createPluginInitializerContext({ max_workers: 10, - index: 'foo', max_attempts: 9, poll_interval: 3000, version_conflict_threshold: 80, diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts index 4c812c82b2cae..9a43cd5b8c1d6 100644 --- a/x-pack/plugins/task_manager/server/plugin.ts +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -31,6 +31,7 @@ import { createMonitoringStats, MonitoringStats } from './monitoring'; import { EphemeralTaskLifecycle } from './ephemeral_task_lifecycle'; import { EphemeralTask } from './task'; import { registerTaskManagerUsageCollector } from './usage'; +import { TASK_MANAGER_INDEX } from './constants'; export interface TaskManagerSetupContract { /** @@ -135,7 +136,7 @@ export class TaskManagerPlugin } return { - index: this.config.index, + index: TASK_MANAGER_INDEX, addMiddleware: (middleware: Middleware) => { this.assertStillInSetup('add Middleware'); this.middleware = addMiddlewareToChain(this.middleware, middleware); @@ -159,7 +160,7 @@ export class TaskManagerPlugin serializer, savedObjectsRepository, esClient: elasticsearch.createClient('taskManager').asInternalUser, - index: this.config!.index, + index: TASK_MANAGER_INDEX, definitions: this.definitions, taskManagerId: `kibana:${this.taskManagerId!}`, }); diff --git a/x-pack/plugins/task_manager/server/saved_objects/index.ts b/x-pack/plugins/task_manager/server/saved_objects/index.ts index abbd1af73b55a..bb8b247af87b8 100644 --- a/x-pack/plugins/task_manager/server/saved_objects/index.ts +++ b/x-pack/plugins/task_manager/server/saved_objects/index.ts @@ -11,6 +11,7 @@ import mappings from './mappings.json'; import { getMigrations } from './migrations'; import { TaskManagerConfig } from '../config.js'; import { getOldestIdleActionTask } from '../queries/oldest_idle_action_task'; +import { TASK_MANAGER_INDEX } from '../constants'; export function setupSavedObjects( savedObjects: SavedObjectsServiceSetup, @@ -23,11 +24,11 @@ export function setupSavedObjects( convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id; ctx._source.remove("kibana")`, mappings: mappings.task as SavedObjectsTypeMappingDefinition, migrations: getMigrations(), - indexPattern: config.index, + indexPattern: TASK_MANAGER_INDEX, excludeOnUpgrade: async ({ readonlyEsClient }) => { const oldestNeededActionParams = await getOldestIdleActionTask( readonlyEsClient, - config.index + TASK_MANAGER_INDEX ); // Delete all action tasks that have failed and are no longer needed From edc43c0ff2b1684ac821159f3f455ff618b4ee4c Mon Sep 17 00:00:00 2001 From: Corey Robertson Date: Mon, 25 Oct 2021 19:21:02 -0400 Subject: [PATCH 3/4] [Dashboard] Make Dashboard Saved Objects multiple-isolated (#115817) * Make Dashboard SO multiple-isolated * Fix integration tests * Fix Saved Objects API Integration Tests * Fix more tests * Fix even more tests Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com> Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../server/saved_objects/dashboard.ts | 3 +- .../apis/saved_objects_management/find.ts | 2 +- .../saved_objects_management/relationships.ts | 2 +- .../saved_objects/spaces/data.json | 9 ++-- .../common/suites/import.ts | 2 +- .../security_and_spaces/apis/import.ts | 7 +-- .../saved_objects/spaces/data.json | 35 ++++++-------- .../spaces_test_plugin/server/plugin.ts | 17 +++++++ .../common/suites/copy_to_space.ts | 35 +++++++++----- .../common/suites/delete.ts | 8 ++-- .../common/suites/get_shareable_references.ts | 2 +- .../suites/resolve_copy_to_space_conflicts.ts | 46 +++++++++++++++---- 12 files changed, 107 insertions(+), 61 deletions(-) diff --git a/src/plugins/dashboard/server/saved_objects/dashboard.ts b/src/plugins/dashboard/server/saved_objects/dashboard.ts index 068883c429e61..944ceda3b33b3 100644 --- a/src/plugins/dashboard/server/saved_objects/dashboard.ts +++ b/src/plugins/dashboard/server/saved_objects/dashboard.ts @@ -19,7 +19,8 @@ export const createDashboardSavedObjectType = ({ }): SavedObjectsType => ({ name: 'dashboard', hidden: false, - namespaceType: 'single', + namespaceType: 'multiple-isolated', + convertToMultiNamespaceTypeVersion: '8.0.0', management: { icon: 'dashboardApp', defaultSearchField: 'title', diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts index ea7f297dfeb08..d877a62eedc82 100644 --- a/test/api_integration/apis/saved_objects_management/find.ts +++ b/test/api_integration/apis/saved_objects_management/find.ts @@ -202,7 +202,7 @@ export default function ({ getService }: FtrProviderContext) { path: '/app/dashboards#/view/b70c7ae0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'dashboard.show', }, - namespaceType: 'single', + namespaceType: 'multiple-isolated', }); })); diff --git a/test/api_integration/apis/saved_objects_management/relationships.ts b/test/api_integration/apis/saved_objects_management/relationships.ts index 838bc05346dda..cc14ce0c76068 100644 --- a/test/api_integration/apis/saved_objects_management/relationships.ts +++ b/test/api_integration/apis/saved_objects_management/relationships.ts @@ -301,7 +301,7 @@ export default function ({ getService }: FtrProviderContext) { path: '/app/dashboards#/view/b70c7ae0-3224-11e8-a572-ffca06da1357', uiCapabilitiesPath: 'dashboard.show', }, - namespaceType: 'single', + namespaceType: 'multiple-isolated', hiddenType: false, }, }, diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json index d83c550c15ff6..56785f913262a 100644 --- a/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json +++ b/x-pack/test/saved_object_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json @@ -134,6 +134,7 @@ "uiStateJSON": "{}", "version": 1 }, + "namespaces": ["default"], "type": "dashboard", "updated_at": "2017-09-21T18:57:40.826Z" }, @@ -205,7 +206,7 @@ { "type": "doc", "value": { - "id": "space_1:dashboard:space1-dashboard-id", + "id": "dashboard:space1-dashboard-id", "index": ".kibana", "source": { "dashboard": { @@ -228,7 +229,7 @@ "uiStateJSON": "{}", "version": 1 }, - "namespace": "space_1", + "namespaces": ["space_1"], "type": "dashboard", "updated_at": "2017-09-21T18:57:40.826Z" }, @@ -300,7 +301,7 @@ { "type": "doc", "value": { - "id": "space_2:dashboard:space2-dashboard-id", + "id": "dashboard:space2-dashboard-id", "index": ".kibana", "source": { "dashboard": { @@ -323,7 +324,7 @@ "uiStateJSON": "{}", "version": 1 }, - "namespace": "space_2", + "namespaces": ["space_2"], "type": "dashboard", "updated_at": "2017-09-21T18:57:40.826Z" }, diff --git a/x-pack/test/saved_object_api_integration/common/suites/import.ts b/x-pack/test/saved_object_api_integration/common/suites/import.ts index 4e1b783d69841..69b3b9925c651 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/import.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/import.ts @@ -52,7 +52,7 @@ export const TEST_CASES: Record = Object.freeze({ expectedNewId: `${CID}3`, }), CONFLICT_4_OBJ: Object.freeze({ type: 'sharedtype', id: `${CID}4`, expectedNewId: `${CID}4a` }), - NEW_SINGLE_NAMESPACE_OBJ: Object.freeze({ type: 'dashboard', id: 'new-dashboard-id' }), + NEW_SINGLE_NAMESPACE_OBJ: Object.freeze({ type: 'isolatedtype', id: 'new-isolatedtype-id' }), NEW_MULTI_NAMESPACE_OBJ: Object.freeze({ type: 'sharedtype', id: 'new-sharedtype-id' }), NEW_NAMESPACE_AGNOSTIC_OBJ: Object.freeze({ type: 'globaltype', id: 'new-globaltype-id' }), }); diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/import.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/import.ts index 42464af05a0b7..1992dd6fea224 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/import.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/import.ts @@ -130,7 +130,6 @@ export default function ({ getService }: FtrProviderContext) { spaceId, singleRequest, responseBodyOverride: expectSavedObjectForbidden([ - 'dashboard', 'globaltype', 'isolatedtype', 'sharedtype', @@ -152,11 +151,7 @@ export default function ({ getService }: FtrProviderContext) { overwrite, spaceId, singleRequest, - responseBodyOverride: expectSavedObjectForbidden([ - 'dashboard', - 'globaltype', - 'isolatedtype', - ]), + responseBodyOverride: expectSavedObjectForbidden(['globaltype', 'isolatedtype']), }), createTestDefinitions(group2, true, { overwrite, spaceId, singleRequest }), createTestDefinitions(group3, true, { overwrite, spaceId, singleRequest }), diff --git a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json index c1525409cfa3f..c9b09456a9a49 100644 --- a/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json +++ b/x-pack/test/spaces_api_integration/common/fixtures/es_archiver/saved_objects/spaces/data.json @@ -56,15 +56,11 @@ { "type": "_doc", "value": { - "id": "space_2:dashboard:my_dashboard", + "id": "isolatedtype:my_isolated_object", "index": ".kibana", "source": { - "dashboard": { - "description": "Space 2", - "title": "This is the second test space" - }, "namespace": "space_2", - "type": "dashboard", + "type": "isolatedtype", "updated_at": "2017-09-21T18:49:16.270Z" }, "type": "_doc" @@ -74,15 +70,11 @@ { "type": "_doc", "value": { - "id": "space_1:dashboard:my_dashboard", + "id": "isolatedtype:my_isolated_object", "index": ".kibana", "source": { - "dashboard": { - "description": "Space 1", - "title": "This is the second test space" - }, "namespace": "space_1", - "type": "dashboard", + "type": "isolatedtype", "updated_at": "2017-09-21T18:49:16.270Z" }, "type": "_doc" @@ -92,14 +84,10 @@ { "type": "_doc", "value": { - "id": "dashboard:my_dashboard", + "id": "isolatedtype:my_isolated_object", "index": ".kibana", "source": { - "dashboard": { - "description": "Default Space", - "title": "This is the default test space" - }, - "type": "dashboard", + "type": "isolatedtype", "updated_at": "2017-09-21T18:49:16.270Z" }, "type": "_doc" @@ -109,9 +97,10 @@ { "type": "_doc", "value": { - "id": "dashboard:cts_dashboard", + "id": "dashboard:cts_dashboard_default", "index": ".kibana", "source": { + "originId": "cts_dashboard", "dashboard": { "description": "Copy to Space Dashboard from the default space", "title": "This is the default test space CTS dashboard" @@ -130,7 +119,8 @@ "name": "CTS Vis 3" }], "type": "dashboard", - "updated_at": "2017-09-21T18:49:16.270Z" + "updated_at": "2017-09-21T18:49:16.270Z", + "namespaces": ["default"] }, "type": "_doc" } @@ -227,9 +217,10 @@ { "type": "_doc", "value": { - "id": "space_1:dashboard:cts_dashboard", + "id": "dashboard:cts_dashboard_space_1", "index": ".kibana", "source": { + "originId": "cts_dashboard", "dashboard": { "description": "Copy to Space Dashboard from space_1 space", "title": "This is the space_1 test space CTS dashboard" @@ -253,7 +244,7 @@ ], "type": "dashboard", "updated_at": "2017-09-21T18:49:16.270Z", - "namespace": "space_1" + "namespaces": ["space_1"] }, "type": "_doc" } diff --git a/x-pack/test/spaces_api_integration/common/fixtures/spaces_test_plugin/server/plugin.ts b/x-pack/test/spaces_api_integration/common/fixtures/spaces_test_plugin/server/plugin.ts index a0021e9eedb5e..88c168b6f6297 100644 --- a/x-pack/test/spaces_api_integration/common/fixtures/spaces_test_plugin/server/plugin.ts +++ b/x-pack/test/spaces_api_integration/common/fixtures/spaces_test_plugin/server/plugin.ts @@ -29,6 +29,23 @@ export class Plugin { }, }, }); + core.savedObjects.registerType({ + name: 'isolatedtype', + hidden: false, + namespaceType: 'single', + management: { + icon: 'beaker', + importableAndExportable: true, + getTitle(obj) { + return obj.attributes.title; + }, + }, + mappings: { + properties: { + title: { type: 'text' }, + }, + }, + }); } public start() { diff --git a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts index 23136838f3002..644200a0636ec 100644 --- a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts +++ b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts @@ -64,9 +64,8 @@ interface SpaceBucket { } const INITIAL_COUNTS: Record> = { - [DEFAULT_SPACE_ID]: { dashboard: 2, visualization: 3, 'index-pattern': 1 }, - space_1: { dashboard: 2, visualization: 3, 'index-pattern': 1 }, - space_2: { dashboard: 1 }, + [DEFAULT_SPACE_ID]: { dashboard: 1, visualization: 3, 'index-pattern': 1 }, + space_1: { dashboard: 1, visualization: 3, 'index-pattern': 1 }, }; const UUID_PATTERN = new RegExp( /^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i @@ -148,18 +147,23 @@ export function copyToSpaceTestSuiteFactory( (spaceId: string, destination: string, expectedDashboardCount: number) => async (resp: TestResponse) => { const result = resp.body as CopyResponse; + + const dashboardDestinationId = result[destination].successResults![0].destinationId; + expect(dashboardDestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID + expect(result).to.eql({ [destination]: { success: true, successCount: 1, successResults: [ { - id: 'cts_dashboard', + id: `cts_dashboard_${spaceId}`, type: 'dashboard', meta: { title: `This is the ${spaceId} test space CTS dashboard`, icon: 'dashboardApp', }, + destinationId: dashboardDestinationId, }, ], }, @@ -172,7 +176,7 @@ export function copyToSpaceTestSuiteFactory( }; const expectNoConflictsWithoutReferencesResult = (spaceId: string = DEFAULT_SPACE_ID) => - createExpectNoConflictsWithoutReferencesForSpace(spaceId, getDestinationWithoutConflicts(), 2); + createExpectNoConflictsWithoutReferencesForSpace(spaceId, getDestinationWithoutConflicts(), 1); const expectNoConflictsForNonExistentSpaceResult = (spaceId: string = DEFAULT_SPACE_ID) => createExpectNoConflictsWithoutReferencesForSpace(spaceId, 'non_existent_space', 1); @@ -191,6 +195,8 @@ export function copyToSpaceTestSuiteFactory( expect(vis2DestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID const vis3DestinationId = result[destination].successResults![3].destinationId; expect(vis3DestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID + const dashboardDestinationId = result[destination].successResults![4].destinationId; + expect(dashboardDestinationId).to.match(UUID_PATTERN); // this was copied to space 2 and hit an unresolvable conflict, so the object ID was regenerated silently / the destinationId is a UUID expect(result).to.eql({ [destination]: { @@ -225,12 +231,13 @@ export function copyToSpaceTestSuiteFactory( destinationId: vis3DestinationId, }, { - id: 'cts_dashboard', + id: `cts_dashboard_${spaceId}`, type: 'dashboard', meta: { icon: 'dashboardApp', title: `This is the ${spaceId} test space CTS dashboard`, }, + destinationId: dashboardDestinationId, }, ], }, @@ -238,7 +245,7 @@ export function copyToSpaceTestSuiteFactory( // Query ES to ensure that we copied everything we expected await assertSpaceCounts(destination, { - dashboard: 2, + dashboard: 1, visualization: 3, 'index-pattern': 1, }); @@ -353,13 +360,14 @@ export function copyToSpaceTestSuiteFactory( destinationId: `cts_vis_3_${destination}`, // this conflicted with another visualization in the destination space because of a shared originId }, { - id: 'cts_dashboard', + id: `cts_dashboard_${spaceId}`, type: 'dashboard', meta: { icon: 'dashboardApp', title: `This is the ${spaceId} test space CTS dashboard`, }, overwrite: true, + destinationId: `cts_dashboard_${destination}`, // this conflicted with another dashboard in the destination space because of a shared originId }, ], }, @@ -367,7 +375,7 @@ export function copyToSpaceTestSuiteFactory( // Query ES to ensure that we copied everything we expected await assertSpaceCounts(destination, { - dashboard: 2, + dashboard: 1, visualization: 5, 'index-pattern': 1, }); @@ -403,8 +411,11 @@ export function copyToSpaceTestSuiteFactory( ]; const expectedErrors = [ { - error: { type: 'conflict' }, - id: 'cts_dashboard', + error: { + type: 'conflict', + destinationId: `cts_dashboard_${destination}`, // this conflicted with another dashboard in the destination space because of a shared originId + }, + id: `cts_dashboard_${spaceId}`, title: `This is the ${spaceId} test space CTS dashboard`, type: 'dashboard', meta: { @@ -662,7 +673,7 @@ export function copyToSpaceTestSuiteFactory( ) ); - const dashboardObject = { type: 'dashboard', id: 'cts_dashboard' }; + const dashboardObject = { type: 'dashboard', id: `cts_dashboard_${spaceId}` }; it(`should return ${tests.noConflictsWithoutReferences.statusCode} when copying to space without conflicts or references`, async () => { const destination = getDestinationWithoutConflicts(); diff --git a/x-pack/test/spaces_api_integration/common/suites/delete.ts b/x-pack/test/spaces_api_integration/common/suites/delete.ts index 4bf44d88db8e0..f6fe05682e2da 100644 --- a/x-pack/test/spaces_api_integration/common/suites/delete.ts +++ b/x-pack/test/spaces_api_integration/common/suites/delete.ts @@ -65,28 +65,28 @@ export function deleteTestSuiteFactory( const expectedBuckets = [ { key: 'default', - doc_count: 8, + doc_count: 7, countByType: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ { key: 'visualization', doc_count: 3 }, - { key: 'dashboard', doc_count: 2 }, { key: 'space', doc_count: 2 }, // since space objects are namespace-agnostic, they appear in the "default" agg bucket + { key: 'dashboard', doc_count: 1 }, { key: 'index-pattern', doc_count: 1 }, // legacy-url-alias objects cannot exist for the default space ], }, }, { - doc_count: 7, + doc_count: 6, key: 'space_1', countByType: { doc_count_error_upper_bound: 0, sum_other_doc_count: 0, buckets: [ { key: 'visualization', doc_count: 3 }, - { key: 'dashboard', doc_count: 2 }, + { key: 'dashboard', doc_count: 1 }, { key: 'index-pattern', doc_count: 1 }, { key: 'legacy-url-alias', doc_count: 1 }, // alias (1) ], diff --git a/x-pack/test/spaces_api_integration/common/suites/get_shareable_references.ts b/x-pack/test/spaces_api_integration/common/suites/get_shareable_references.ts index ed704a9a8bdcc..883c1230f5d10 100644 --- a/x-pack/test/spaces_api_integration/common/suites/get_shareable_references.ts +++ b/x-pack/test/spaces_api_integration/common/suites/get_shareable_references.ts @@ -43,7 +43,7 @@ const { export const TEST_CASE_OBJECTS: Record = deepFreeze({ SHAREABLE_TYPE: { type: 'sharedtype', id: CASES.EACH_SPACE.id }, // contains references to four other objects SHAREABLE_TYPE_DOES_NOT_EXIST: { type: 'sharedtype', id: 'does-not-exist' }, - NON_SHAREABLE_TYPE: { type: 'dashboard', id: 'my_dashboard' }, // one of these exists in each space + NON_SHAREABLE_TYPE: { type: 'isolatedtype', id: 'my_isolated_object' }, // one of these exists in each space }); // Expected results for each space are defined here since they are used in multiple test suites export const EXPECTED_RESULTS: Record = { diff --git a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts index 72130743b69d9..1d9d5325cbabf 100644 --- a/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts +++ b/x-pack/test/spaces_api_integration/common/suites/resolve_copy_to_space_conflicts.ts @@ -63,7 +63,7 @@ export function resolveCopyToSpaceConflictsSuite( }; const getDashboardAtSpace = async (spaceId: string): Promise> => { return supertestWithAuth - .get(`${getUrlPrefix(spaceId)}/api/saved_objects/dashboard/cts_dashboard`) + .get(`${getUrlPrefix(spaceId)}/api/saved_objects/dashboard/cts_dashboard_${spaceId}`) .then((response: any) => response.body); }; @@ -124,12 +124,13 @@ export function resolveCopyToSpaceConflictsSuite( successCount: 1, successResults: [ { - id: 'cts_dashboard', + id: `cts_dashboard_${sourceSpaceId}`, type: 'dashboard', meta: { title: `This is the ${sourceSpaceId} test space CTS dashboard`, icon: 'dashboardApp', }, + destinationId: `cts_dashboard_${destinationSpaceId}`, // this conflicted with another dashboard in the destination space because of a shared originId overwrite: true, }, ], @@ -204,8 +205,11 @@ export function resolveCopyToSpaceConflictsSuite( successCount: 0, errors: [ { - error: { type: 'conflict' }, - id: 'cts_dashboard', + error: { + type: 'conflict', + destinationId: `cts_dashboard_${destination}`, // this conflicted with another visualization in the destination space because of a shared originId + }, + id: `cts_dashboard_${sourceSpaceId}`, type: 'dashboard', title: `This is the ${sourceSpaceId} test space CTS dashboard`, meta: { @@ -442,7 +446,7 @@ export function resolveCopyToSpaceConflictsSuite( ) ); - const dashboardObject = { type: 'dashboard', id: 'cts_dashboard' }; + const dashboardObject = { type: 'dashboard', id: `cts_dashboard_${spaceId}` }; const visualizationObject = { type: 'visualization', id: `cts_vis_3_${spaceId}` }; const indexPatternObject = { type: 'index-pattern', id: `cts_ip_1_${spaceId}` }; @@ -514,7 +518,15 @@ export function resolveCopyToSpaceConflictsSuite( objects: [dashboardObject], includeReferences: false, createNewCopies: false, - retries: { [destination]: [{ ...dashboardObject, overwrite: true }] }, + retries: { + [destination]: [ + { + ...dashboardObject, + destinationId: `cts_dashboard_${destination}`, + overwrite: true, + }, + ], + }, }) .expect(tests.withoutReferencesOverwriting.statusCode) .then(tests.withoutReferencesOverwriting.response); @@ -530,7 +542,15 @@ export function resolveCopyToSpaceConflictsSuite( objects: [dashboardObject], includeReferences: false, createNewCopies: false, - retries: { [destination]: [{ ...dashboardObject, overwrite: false }] }, + retries: { + [destination]: [ + { + ...dashboardObject, + destinationId: `cts_dashboard_${destination}`, + overwrite: false, + }, + ], + }, }) .expect(tests.withoutReferencesNotOverwriting.statusCode) .then(tests.withoutReferencesNotOverwriting.response); @@ -546,7 +566,17 @@ export function resolveCopyToSpaceConflictsSuite( objects: [dashboardObject], includeReferences: false, createNewCopies: false, - retries: { [destination]: [{ ...dashboardObject, overwrite: true }] }, + retries: { + [destination]: [ + { + ...dashboardObject, + destinationId: `cts_dashboard_${destination}`, + // realistically a retry wouldn't use a destinationId, because it wouldn't have an origin conflict with another + // object in a non-existent space, but for the simplicity of testing we'll use this here + overwrite: true, + }, + ], + }, }) .expect(tests.nonExistentSpace.statusCode) .then(tests.nonExistentSpace.response); From de2ca1822665eeefa76c6fca2c98d1f9fd995e83 Mon Sep 17 00:00:00 2001 From: Chris Donaher Date: Mon, 25 Oct 2021 17:40:36 -0600 Subject: [PATCH 4/4] Insights telemetry collection -- Alert Status Updates (#115471) * Added security plugin to signals route * Added insights payload construction to status route * Pass cloud plugin setup to route to test if cloud is enabled * Incorrectly getting username from authenticated user * Test needs cloud setup passed to mock * Mistakenly added sender to migration * Just pass cloudEnabled boolean to route * Remove cloud specific checks from telemetry forwarding * Populate sessionId from request, hash+salt with clusterID * Converted payload construction to map * Added logger to route, found that ui sometimes passes alert_ids in query * Properly pass logger into test * Change deep nested query field access to lodash get * Fixed some import issues * Addressed some comments from @pjhamptom * Added fields to mock to ensure that the testTelemetrySender has the proper interface * Wrapped awaits in Promise.all, abstract and remove async fetchClusterInfo calls since clusterInfo is immutable * Missed some references to fetchClusterInfo() * Removed references to rules, changed 'page' to 'route', made insights functions not methods Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> --- .../routes/signals/open_close_signals.test.ts | 14 +++- .../signals/open_close_signals_route.ts | 41 +++++++++- .../server/lib/telemetry/__mocks__/index.ts | 3 +- .../server/lib/telemetry/constants.ts | 2 + .../server/lib/telemetry/insights/index.ts | 7 ++ .../server/lib/telemetry/insights/insights.ts | 82 +++++++++++++++++++ .../server/lib/telemetry/receiver.ts | 8 +- .../server/lib/telemetry/sender.ts | 18 ++-- .../security_solution/server/plugin.ts | 1 + .../security_solution/server/routes/index.ts | 4 +- 10 files changed, 166 insertions(+), 14 deletions(-) create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/insights/index.ts create mode 100644 x-pack/plugins/security_solution/server/lib/telemetry/insights/insights.ts diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts index 07c3bc37e7d72..39ccf9f158422 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals.test.ts @@ -15,16 +15,21 @@ import { getSuccessfulSignalUpdateResponse, } from '../__mocks__/request_responses'; import { requestContextMock, serverMock, requestMock } from '../__mocks__'; +import { SetupPlugins } from '../../../../plugin'; +import { createMockTelemetryEventsSender } from '../../../telemetry/__mocks__'; import { setSignalsStatusRoute } from './open_close_signals_route'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { elasticsearchClientMock } from 'src/core/server/elasticsearch/client/mocks'; +import { loggingSystemMock } from 'src/core/server/mocks'; describe('set signal status', () => { let server: ReturnType; let { context } = requestContextMock.createTools(); + let logger: ReturnType; beforeEach(() => { server = serverMock.create(); + logger = loggingSystemMock.createLogger(); ({ context } = requestContextMock.createTools()); context.core.elasticsearch.client.asCurrentUser.updateByQuery.mockResolvedValue( @@ -32,8 +37,13 @@ describe('set signal status', () => { getSuccessfulSignalUpdateResponse() ) ); - - setSignalsStatusRoute(server.router); + const telemetrySenderMock = createMockTelemetryEventsSender(); + const securityMock = { + authc: { + getCurrentUser: jest.fn().mockReturnValue({ user: { username: 'my-username' } }), + }, + } as unknown as SetupPlugins['security']; + setSignalsStatusRoute(server.router, logger, securityMock, telemetrySenderMock); }); describe('status on signal', () => { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts index e54cc94b886f6..c29a9d9a5d7eb 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/routes/signals/open_close_signals_route.ts @@ -5,8 +5,10 @@ * 2.0. */ +import { get } from 'lodash'; import { transformError } from '@kbn/securitysolution-es-utils'; import { ALERT_WORKFLOW_STATUS } from '@kbn/rule-data-utils'; +import { Logger } from 'src/core/server'; import { setSignalStatusValidateTypeDependents } from '../../../../../common/detection_engine/schemas/request/set_signal_status_type_dependents'; import { SetSignalsStatusSchemaDecoded, @@ -15,10 +17,21 @@ import { import type { SecuritySolutionPluginRouter } from '../../../../types'; import { DETECTION_ENGINE_SIGNALS_STATUS_URL } from '../../../../../common/constants'; import { buildSiemResponse } from '../utils'; - +import { TelemetryEventsSender } from '../../../telemetry/sender'; +import { INSIGHTS_CHANNEL } from '../../../telemetry/constants'; +import { SetupPlugins } from '../../../../plugin'; import { buildRouteValidation } from '../../../../utils/build_validation/route_validation'; +import { + getSessionIDfromKibanaRequest, + createAlertStatusPayloads, +} from '../../../telemetry/insights'; -export const setSignalsStatusRoute = (router: SecuritySolutionPluginRouter) => { +export const setSignalsStatusRoute = ( + router: SecuritySolutionPluginRouter, + logger: Logger, + security: SetupPlugins['security'], + sender: TelemetryEventsSender +) => { router.post( { path: DETECTION_ENGINE_SIGNALS_STATUS_URL, @@ -46,6 +59,30 @@ export const setSignalsStatusRoute = (router: SecuritySolutionPluginRouter) => { return siemResponse.error({ statusCode: 404 }); } + const clusterId = sender.getClusterID(); + const [isTelemetryOptedIn, username] = await Promise.all([ + sender.isTelemetryOptedIn(), + security?.authc.getCurrentUser(request)?.username, + ]); + if (isTelemetryOptedIn && clusterId) { + // Sometimes the ids are in the query not passed in the request? + const toSendAlertIds = get(query, 'bool.filter.terms._id') || signalIds; + // Get Context for Insights Payloads + const sessionId = getSessionIDfromKibanaRequest(clusterId, request); + if (username && toSendAlertIds && sessionId && status) { + const insightsPayloads = createAlertStatusPayloads( + clusterId, + toSendAlertIds, + sessionId, + username, + DETECTION_ENGINE_SIGNALS_STATUS_URL, + status + ); + logger.debug(`Sending Insights Payloads ${JSON.stringify(insightsPayloads)}`); + await sender.sendOnDemand(INSIGHTS_CHANNEL, insightsPayloads); + } + } + let queryObject; if (signalIds) { queryObject = { ids: { values: signalIds } }; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts index 45ceb98ed0dc1..b6657e7753364 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/__mocks__/index.ts @@ -21,12 +21,14 @@ export const createMockTelemetryEventsSender = ( setup: jest.fn(), start: jest.fn(), stop: jest.fn(), + getClusterID: jest.fn(), fetchTelemetryUrl: jest.fn(), queueTelemetryEvents: jest.fn(), processEvents: jest.fn(), isTelemetryOptedIn: jest.fn().mockReturnValue(enableTelemetry ?? jest.fn()), sendIfDue: jest.fn(), sendEvents: jest.fn(), + sendOnDemand: jest.fn(), } as unknown as jest.Mocked; }; @@ -35,7 +37,6 @@ export const createMockTelemetryReceiver = ( ): jest.Mocked => { return { start: jest.fn(), - fetchClusterInfo: jest.fn(), fetchLicenseInfo: jest.fn(), copyLicenseFields: jest.fn(), fetchFleetAgents: jest.fn(), diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts b/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts index ec1505ec314d1..af02c98f32c55 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/constants.ts @@ -24,3 +24,5 @@ export const LIST_ENDPOINT_EXCEPTION = 'endpoint_exception'; export const LIST_ENDPOINT_EVENT_FILTER = 'endpoint_event_filter'; export const LIST_TRUSTED_APPLICATION = 'trusted_application'; + +export const INSIGHTS_CHANNEL = 'security-insights-v1'; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/insights/index.ts b/x-pack/plugins/security_solution/server/lib/telemetry/insights/index.ts new file mode 100644 index 0000000000000..8f300c6089d29 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/insights/index.ts @@ -0,0 +1,7 @@ +/* + * 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. + */ +export { getSessionIDfromKibanaRequest, createAlertStatusPayloads } from './insights'; diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/insights/insights.ts b/x-pack/plugins/security_solution/server/lib/telemetry/insights/insights.ts new file mode 100644 index 0000000000000..367eb92824117 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/telemetry/insights/insights.ts @@ -0,0 +1,82 @@ +/* + * 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 moment from 'moment'; +import { KibanaRequest } from 'src/core/server'; +import { sha256 } from 'js-sha256'; + +interface AlertContext { + alert_id: string; +} + +interface AlertStatusAction { + alert_status: string; + action_timestamp: string; +} + +export interface InsightsPayload { + state: { + route: string; + cluster_id: string; + user_id: string; + session_id: string; + context: AlertContext; + }; + action: AlertStatusAction; +} +export function getSessionIDfromKibanaRequest(clusterId: string, request: KibanaRequest): string { + const rawCookieHeader = request.headers.cookie; + if (!rawCookieHeader) { + return ''; + } + const cookieHeaders = Array.isArray(rawCookieHeader) ? rawCookieHeader : [rawCookieHeader]; + let tokenPackage: string | undefined; + + cookieHeaders + .flatMap((rawHeader) => rawHeader.split('; ')) + .forEach((rawCookie) => { + const [cookieName, cookieValue] = rawCookie.split('='); + if (cookieName === 'sid') tokenPackage = cookieValue; + }); + + if (tokenPackage) { + return getClusterHashSalt(clusterId, tokenPackage); + } else { + return ''; + } +} + +function getClusterHashSalt(clusterId: string, toHash: string): string { + const concatValue = toHash + clusterId; + const sha = sha256.create().update(concatValue).hex(); + return sha; +} + +export function createAlertStatusPayloads( + clusterId: string, + alertIds: string[], + sessionId: string, + username: string, + route: string, + status: string +): InsightsPayload[] { + return alertIds.map((alertId) => ({ + state: { + route, + cluster_id: clusterId, + user_id: getClusterHashSalt(clusterId, username), + session_id: sessionId, + context: { + alert_id: alertId, + }, + }, + action: { + alert_status: status, + action_timestamp: moment().toISOString(), + }, + })); +} diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts index 94aa6c867304f..5246b649ebaa1 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/receiver.ts @@ -38,6 +38,7 @@ export class TelemetryReceiver { private exceptionListClient?: ExceptionListClient; private soClient?: SavedObjectsClientContract; private kibanaIndex?: string; + private clusterInfo?: ESClusterInfo; private readonly max_records = 10_000; constructor(logger: Logger) { @@ -57,6 +58,11 @@ export class TelemetryReceiver { this.exceptionListClient = exceptionListClient; this.soClient = core?.savedObjects.createInternalRepository() as unknown as SavedObjectsClientContract; + this.clusterInfo = await this.fetchClusterInfo(); + } + + public getClusterInfo(): ESClusterInfo | undefined { + return this.clusterInfo; } public async fetchFleetAgents() { @@ -304,7 +310,7 @@ export class TelemetryReceiver { }; } - public async fetchClusterInfo(): Promise { + private async fetchClusterInfo(): Promise { if (this.esClient === undefined || this.esClient === null) { throw Error('elasticsearch client is unavailable: cannot retrieve cluster infomation'); } diff --git a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts index 7c9906d0eae48..3a8d503c9311f 100644 --- a/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts +++ b/x-pack/plugins/security_solution/server/lib/telemetry/sender.ts @@ -67,6 +67,10 @@ export class TelemetryEventsSender { } } + public getClusterID(): string | undefined { + return this.receiver?.getClusterInfo()?.cluster_uuid; + } + public start( telemetryStart?: TelemetryPluginStart, taskManager?: TaskManagerStartContract, @@ -149,9 +153,10 @@ export class TelemetryEventsSender { return; } - const [telemetryUrl, clusterInfo, licenseInfo] = await Promise.all([ + const clusterInfo = this.receiver?.getClusterInfo(); + + const [telemetryUrl, licenseInfo] = await Promise.all([ this.fetchTelemetryUrl('alerts-endpoint'), - this.receiver?.fetchClusterInfo(), this.receiver?.fetchLicenseInfo(), ]); @@ -198,10 +203,10 @@ export class TelemetryEventsSender { * @param toSend telemetry events */ public async sendOnDemand(channel: string, toSend: unknown[]) { + const clusterInfo = this.receiver?.getClusterInfo(); try { - const [telemetryUrl, clusterInfo, licenseInfo] = await Promise.all([ + const [telemetryUrl, licenseInfo] = await Promise.all([ this.fetchTelemetryUrl(channel), - this.receiver?.fetchClusterInfo(), this.receiver?.fetchLicenseInfo(), ]); @@ -255,6 +260,7 @@ export class TelemetryEventsSender { const ndjson = transformDataToNdjson(events); try { + this.logger.debug(`Sending ${events.length} telemetry events to ${channel}`); const resp = await axios.post(telemetryUrl, ndjson, { headers: { 'Content-Type': 'application/x-ndjson', @@ -275,9 +281,7 @@ export class TelemetryEventsSender { }); this.logger.debug(`Events sent!. Response: ${resp.status} ${JSON.stringify(resp.data)}`); } catch (err) { - this.logger.warn( - `Error sending events: ${err.response.status} ${JSON.stringify(err.response.data)}` - ); + this.logger.debug(`Error sending events: ${err}`); this.telemetryUsageCounter?.incrementCounter({ counterName: createUsageCounterLabel(usageLabelPrefix.concat(['payloads', channel])), counterType: 'docs_lost', diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index 39aa1fb069f20..e9f2e305b6556 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -236,6 +236,7 @@ export class Plugin implements ISecuritySolutionPlugin { config, plugins.encryptedSavedObjects?.canEncrypt === true, plugins.security, + this.telemetryEventsSender, plugins.ml, logger, isRuleRegistryEnabled, diff --git a/x-pack/plugins/security_solution/server/routes/index.ts b/x-pack/plugins/security_solution/server/routes/index.ts index 26dbd80a03db4..60c5e8a62d7c5 100644 --- a/x-pack/plugins/security_solution/server/routes/index.ts +++ b/x-pack/plugins/security_solution/server/routes/index.ts @@ -55,6 +55,7 @@ import { persistPinnedEventRoute } from '../lib/timeline/routes/pinned_events'; import { SetupPlugins } from '../plugin'; import { ConfigType } from '../config'; +import { TelemetryEventsSender } from '../lib/telemetry/sender'; import { installPrepackedTimelinesRoute } from '../lib/timeline/routes/prepackaged_timelines/install_prepackaged_timelines'; import { previewRulesRoute } from '../lib/detection_engine/routes/rules/preview_rules_route'; import { CreateRuleOptions } from '../lib/detection_engine/rule_types/types'; @@ -67,6 +68,7 @@ export const initRoutes = ( config: ConfigType, hasEncryptionKey: boolean, security: SetupPlugins['security'], + telemetrySender: TelemetryEventsSender, ml: SetupPlugins['ml'], logger: Logger, isRuleRegistryEnabled: boolean, @@ -120,7 +122,7 @@ export const initRoutes = ( // Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals // POST /api/detection_engine/signals/status // Example usage can be found in security_solution/server/lib/detection_engine/scripts/signals - setSignalsStatusRoute(router); + setSignalsStatusRoute(router, logger, security, telemetrySender); querySignalsRoute(router, config); getSignalsMigrationStatusRoute(router); createSignalsMigrationRoute(router, security);