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 13d9060529004..0c180553ff436 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 @@ -158,9 +158,7 @@ export const applicationUsageSchema = { security_login: commonSchema, security_logout: commonSchema, security_overwritten_session: commonSchema, - securitySolution: commonSchema, // It's a forward app so we'll likely never report it - 'securitySolution:timelines': commonSchema, - 'securitySolution:case': commonSchema, + securitySolution: 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 09a8e500e26b6..8c4fef93bc87c 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -5411,268 +5411,6 @@ } } }, - "securitySolution:timelines": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "Always `main`" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 90 days" - } - }, - "views": { - "type": "array", - "items": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "The application view being tracked" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application sub view since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application sub view is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" - } - } - } - } - } - } - }, - "securitySolution:case": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "Always `main`" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen over the last 90 days" - } - }, - "views": { - "type": "array", - "items": { - "properties": { - "appId": { - "type": "keyword", - "_meta": { - "description": "The application being tracked" - } - }, - "viewId": { - "type": "keyword", - "_meta": { - "description": "The application view being tracked" - } - }, - "clicks_total": { - "type": "long", - "_meta": { - "description": "General number of clicks in the application sub view since we started counting them" - } - }, - "clicks_7_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 7 days" - } - }, - "clicks_30_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 30 days" - } - }, - "clicks_90_days": { - "type": "long", - "_meta": { - "description": "General number of clicks in the active application sub view over the last 90 days" - } - }, - "minutes_on_screen_total": { - "type": "float", - "_meta": { - "description": "Minutes the application sub view is active and on-screen since we started counting them." - } - }, - "minutes_on_screen_7_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 7 days" - } - }, - "minutes_on_screen_30_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 30 days" - } - }, - "minutes_on_screen_90_days": { - "type": "float", - "_meta": { - "description": "Minutes the application is active and on-screen active application sub view over the last 90 days" - } - } - } - } - } - } - }, "siem": { "properties": { "appId": { diff --git a/x-pack/plugins/security_solution/common/constants.ts b/x-pack/plugins/security_solution/common/constants.ts index 8acd47643965e..a4a0dc536c2f7 100644 --- a/x-pack/plugins/security_solution/common/constants.ts +++ b/x-pack/plugins/security_solution/common/constants.ts @@ -85,6 +85,8 @@ export enum SecurityPageGroupName { manage = 'manage', } +export const TIMELINES_PATH = '/timelines'; +export const CASES_PATH = '/cases'; export const OVERVIEW_PATH = '/overview'; export const ALERTS_PATH = '/alerts'; export const RULES_PATH = '/rules'; 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 d43d83571c665..94f91d5fdee08 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 @@ -11,7 +11,6 @@ import { LicenseType } from '../../../../licensing/common/types'; import { SecurityDeepLinkName, SecurityDeepLinks, SecurityPageName } from '../types'; import { App, AppDeepLink, AppNavLinkStatus } from '../../../../../../src/core/public'; import { - ADMINISTRATION, OVERVIEW, DETECTION_ENGINE, ALERTS, @@ -19,6 +18,9 @@ import { EXCEPTIONS, HOSTS, NETWORK, + TIMELINES, + CASE, + ADMINISTRATION, } from '../translations'; import { APP_ICON_SOLUTION, @@ -28,6 +30,8 @@ import { EXCEPTIONS_PATH, HOSTS_PATH, NETWORK_PATH, + TIMELINES_PATH, + CASES_PATH, ENDPOINTS_PATH, TRUSTED_APPS_PATH, EVENT_FILTERS_PATH, @@ -86,6 +90,32 @@ export const topDeepLinks: AppDeepLink[] = [ order: 9003, euiIconType: APP_ICON_SOLUTION, }, + { + id: SecurityPageName.timelines, + title: TIMELINES, + path: TIMELINES_PATH, + navLinkStatus: AppNavLinkStatus.visible, + keywords: [ + i18n.translate('xpack.securitySolution.search.timelines', { + defaultMessage: 'Timelines', + }), + ], + order: 9002, + euiIconType: APP_ICON_SOLUTION, + }, + { + id: SecurityPageName.case, + title: CASE, + path: CASES_PATH, + navLinkStatus: AppNavLinkStatus.visible, + keywords: [ + i18n.translate('xpack.securitySolution.search.cases', { + defaultMessage: 'Cases', + }), + ], + order: 9002, + euiIconType: APP_ICON_SOLUTION, + }, { id: SecurityPageName.administration, title: ADMINISTRATION, @@ -290,35 +320,6 @@ const nestedDeepLinks: SecurityDeepLinks = { }, }; -/** - * TODO: [1101] remove function - * A function that generates a subPlugin's meta tag - * @param subPluginName SubPluginName of the app to retrieve meta information for. - * @param licenseType optional string for license level, if not provided basic is assumed. - */ -export function getDeepLinksAndKeywords( - subPluginName: SecurityDeepLinkName, - licenseType?: LicenseType -): Pick { - const baseRoutes = [...nestedDeepLinks[subPluginName].base]; - if ( - licenseType === 'gold' || - licenseType === 'platinum' || - licenseType === 'enterprise' || - licenseType === 'trial' - ) { - const premiumRoutes = nestedDeepLinks[subPluginName] && nestedDeepLinks[subPluginName].premium; - if (premiumRoutes !== undefined) { - return { - deepLinks: [...baseRoutes, ...premiumRoutes], - }; - } - } - return { - deepLinks: baseRoutes, - }; -} - /** * A function that generates the plugin deepLinks * @param licenseType optional string for license level, if not provided basic is assumed. diff --git a/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx index f4bcaa1f20c82..271eea47840dc 100644 --- a/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx +++ b/x-pack/plugins/security_solution/public/app/home/home_navigations.tsx @@ -87,24 +87,23 @@ export const navTabs: SiemNavTab = { disabled: false, urlKey: SecurityPageName.administration, }, - // TODO: [1101] i18n names [SecurityPageName.endpoints]: { id: SecurityPageName.endpoints, - name: 'Endpoints', + name: i18n.ENDPOINTS, href: APP_ENDPOINTS_PATH, disabled: false, urlKey: SecurityPageName.administration, }, [SecurityPageName.trustedApps]: { id: SecurityPageName.trustedApps, - name: 'Trusted Applications', + name: i18n.TRUSTED_APPLICATIONS, href: APP_TRUSTED_APPS_PATH, disabled: false, urlKey: SecurityPageName.administration, }, [SecurityPageName.eventFilters]: { id: SecurityPageName.eventFilters, - name: 'Event Filters', + name: i18n.EVENT_FILTERS, href: APP_EVENT_FILTERS_PATH, disabled: false, urlKey: SecurityPageName.administration, diff --git a/x-pack/plugins/security_solution/public/app/index.tsx b/x-pack/plugins/security_solution/public/app/index.tsx index 3592ddcad269c..bba484703cdfd 100644 --- a/x-pack/plugins/security_solution/public/app/index.tsx +++ b/x-pack/plugins/security_solution/public/app/index.tsx @@ -14,31 +14,6 @@ import { NotFoundPage } from '../app/404'; import { SecurityApp } from './app'; import { RenderAppProps, RenderAppPropsOld } from './types'; -// TODO: [1101] remove renderAppOld when all sections migrated -export const renderAppOld = ({ - element, - history, - onAppLeave, - setHeaderActionMenu, - services, - store, - SubPluginRoutes, -}: RenderAppPropsOld): (() => void) => { - render( - - - , - element - ); - return () => unmountComponentAtNode(element); -}; - export const renderApp = ({ element, history, @@ -57,15 +32,16 @@ export const renderApp = ({ store={store} > - {/* TODO: [1101] add subPlugins routes here when migrating sections, once all migrated we will be able to inject all subPlugins routes at once */} {[ - ...subPlugins.overview.routes!, - ...subPlugins.alerts.routes!, - ...subPlugins.rules.routes!, - ...subPlugins.exceptions.routes!, - ...subPlugins.hosts.routes!, - ...subPlugins.network.routes!, - ...subPlugins.management.routes!, + ...subPlugins.overview.routes, + ...subPlugins.alerts.routes, + ...subPlugins.rules.routes, + ...subPlugins.exceptions.routes, + ...subPlugins.hosts.routes, + ...subPlugins.network.routes, + ...subPlugins.timelines.routes, + ...subPlugins.cases.routes, + ...subPlugins.management.routes, ].map((route, index) => ( ))} diff --git a/x-pack/plugins/security_solution/public/app/translations.ts b/x-pack/plugins/security_solution/public/app/translations.ts index 3b781011eeb9d..df0e4782ad7f2 100644 --- a/x-pack/plugins/security_solution/public/app/translations.ts +++ b/x-pack/plugins/security_solution/public/app/translations.ts @@ -49,6 +49,21 @@ export const CASE = i18n.translate('xpack.securitySolution.navigation.case', { export const ADMINISTRATION = i18n.translate('xpack.securitySolution.navigation.administration', { defaultMessage: 'Administration', }); +export const ENDPOINTS = i18n.translate('xpack.securitySolution.search.administration.endpoints', { + defaultMessage: 'Endpoints', +}); +export const TRUSTED_APPLICATIONS = i18n.translate( + 'xpack.securitySolution.search.administration.trustedApps', + { + defaultMessage: 'Trusted Applications', + } +); +export const EVENT_FILTERS = i18n.translate( + 'xpack.securitySolution.search.administration.eventFilters', + { + defaultMessage: 'Event Filters', + } +); export const DETECT = i18n.translate('xpack.securitySolution.navigation.detect', { defaultMessage: 'Detect', diff --git a/x-pack/plugins/security_solution/public/app/types.ts b/x-pack/plugins/security_solution/public/app/types.ts index 25660267f6033..0e8d164548843 100644 --- a/x-pack/plugins/security_solution/public/app/types.ts +++ b/x-pack/plugins/security_solution/public/app/types.ts @@ -20,15 +20,6 @@ import { RouteProps } from 'react-router-dom'; import { AppMountParameters, AppDeepLink } from '../../../../../src/core/public'; import { StartedSubPlugins, StartServices } from '../types'; -/** - * The React properties used to render `SecurityApp` as well as the `element` to render it into. - */ -// TODO: [1101] remove RenderAppPropsOld when all sections migrated -export interface RenderAppPropsOld extends AppMountParameters { - services: StartServices; - store: Store; - SubPluginRoutes: React.FC; -} /** * The React properties used to render `SecurityApp` as well as the `element` to render it into. */ @@ -53,10 +44,8 @@ export interface SecuritySubPluginStore export type SecuritySubPluginRoutes = RouteProps[]; -// TODO: [1101] remove SubPluginRoutes and make routes required export interface SecuritySubPlugin { - SubPluginRoutes: React.FC; - routes?: SecuritySubPluginRoutes; + routes: SecuritySubPluginRoutes; storageTimelines?: Pick; } 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 aa84f639c4577..3c788e0553079 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, CASES_APP_ID } from '../../../../common/constants'; +import { APP_ID } from '../../../../common/constants'; export interface AllCasesNavProps { detailName: string; @@ -36,7 +36,8 @@ export const AllCases = React.memo(({ userCanCrud }) => { const goToCreateCase = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(urlSearch), }); }, @@ -46,7 +47,8 @@ export const AllCases = React.memo(({ userCanCrud }) => { const goToCaseConfigure = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getConfigureCasesUrl(urlSearch), }); }, @@ -59,7 +61,8 @@ export const AllCases = React.memo(({ userCanCrud }) => { return formatUrl(getCaseDetailsUrl({ id: detailName, subCaseId })); }, onClick: async ({ detailName, subCaseId, search }: AllCasesNavProps) => { - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_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 ab850f880ccf7..684389eb0f889 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 @@ -23,11 +23,7 @@ import { Case } from '../../../../../cases/common'; import { TimelineId } from '../../../../common/types/timeline'; import { SecurityPageName } from '../../../app/types'; import { KibanaServices, useKibana } from '../../../common/lib/kibana'; -import { - APP_ID, - CASES_APP_ID, - DETECTION_ENGINE_QUERY_SIGNALS_URL, -} from '../../../../common/constants'; +import { APP_ID, DETECTION_ENGINE_QUERY_SIGNALS_URL } from '../../../../common/constants'; import { timelineActions } from '../../../timelines/store/timeline'; import { useSourcererScope } from '../../../common/containers/sourcerer'; import { SourcererScopeName } from '../../../common/store/sourcerer/model'; @@ -185,7 +181,8 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = if (e) { e.preventDefault(); } - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: allCasesLink, }); }, @@ -196,7 +193,8 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = if (e) { e.preventDefault(); } - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: caseId }), }); }, @@ -208,7 +206,8 @@ export const CaseView = React.memo(({ caseId, subCaseId, userCanCrud }: Props) = if (e) { e.preventDefault(); } - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getConfigureCasesUrl(search), }); }, 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 0b6e98e1badcc..42579c6fbc0ac 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, CASES_APP_ID } from '../../../../common/constants'; +import { APP_ID, SecurityPageName } from '../../../../common/constants'; import { useGetUrlSearch } from '../../../common/components/navigation/use_get_url_search'; jest.mock('../use_insert_timeline'); @@ -71,8 +71,9 @@ describe('Create case', () => { ); await waitFor(() => - expect(mockNavigateToApp).toHaveBeenCalledWith(CASES_APP_ID, { + expect(mockNavigateToApp).toHaveBeenCalledWith(APP_ID, { path: `?${mockRes}`, + deepLinkId: SecurityPageName.case, }) ); }); @@ -95,8 +96,9 @@ describe('Create case', () => { ); await waitFor(() => - expect(mockNavigateToApp).toHaveBeenNthCalledWith(1, CASES_APP_ID, { + expect(mockNavigateToApp).toHaveBeenNthCalledWith(1, APP_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 dfd53ae5cc0b0..5e2b7e27fb1e5 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 @@ -12,9 +12,10 @@ 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, CASES_APP_ID } from '../../../../common/constants'; +import { APP_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'; export const Create = React.memo(() => { const { @@ -24,14 +25,16 @@ export const Create = React.memo(() => { const search = useGetUrlSearch(navTabs.case); const onSuccess = useCallback( async ({ id }) => - navigateToApp(CASES_APP_ID, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id, search }), }), [navigateToApp, search] ); const handleSetIsCancel = useCallback( async () => - navigateToApp(CASES_APP_ID, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseUrl(search), }), [navigateToApp, search] diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx index 77fa9e8b3cc8c..ff411941c8ce9 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.test.tsx @@ -15,6 +15,7 @@ import { TestProviders } from '../../../common/mock'; import { AddToCaseAction } from './add_to_case_action'; import { basicCase } from '../../../../../cases/public/containers/mock'; import { Case, SECURITY_SOLUTION_OWNER } from '../../../../../cases/common'; +import { APP_ID, SecurityPageName } from '../../../../common/constants'; jest.mock('../../../common/lib/kibana'); jest.mock('../../../common/components/link_to', () => { @@ -177,8 +178,9 @@ describe('AddToCaseAction', () => { .first() .simulate('click'); - expect(mockNavigateToApp).toHaveBeenCalledWith('securitySolution:case', { + expect(mockNavigateToApp).toHaveBeenCalledWith(APP_ID, { path: '/basic-case-id', + deepLinkId: SecurityPageName.case, }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx index eaad912a4dc51..2aadfad21d753 100644 --- a/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/timeline_actions/add_to_case_action.tsx @@ -17,7 +17,7 @@ import { } from '@elastic/eui'; import { Case, CaseStatuses, StatusAll } from '../../../../../cases/common'; -import { APP_ID, CASES_APP_ID } from '../../../../common/constants'; +import { APP_ID } from '../../../../common/constants'; import { Ecs } from '../../../../common/ecs'; import { SecurityPageName } from '../../../app/types'; import { @@ -80,7 +80,8 @@ const AddToCaseActionComponent: React.FC = ({ const onViewCaseClick = useCallback( (id) => { - navigateToApp(CASES_APP_ID, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id }), }); }, @@ -134,7 +135,8 @@ const AddToCaseActionComponent: React.FC = ({ const goToCreateCase = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(urlSearch), }); }, diff --git a/x-pack/plugins/security_solution/public/cases/components/use_insert_timeline/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/use_insert_timeline/index.test.tsx index 386a095feac6e..144fd8b4bfd37 100644 --- a/x-pack/plugins/security_solution/public/cases/components/use_insert_timeline/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/use_insert_timeline/index.test.tsx @@ -65,7 +65,7 @@ describe('useInsertTimeline', () => { }); expect(onChange).toHaveBeenCalledWith( - `[Test rule](?timeline=(id:'ef579e40-jibber-jabber',isOpen:!t))` + `[Test rule](/timelines/?timeline=(id:'ef579e40-jibber-jabber',isOpen:!t))` ); }); @@ -73,16 +73,19 @@ describe('useInsertTimeline', () => { renderHook(() => useInsertTimeline('New value', onChange)); expect(onChange).toHaveBeenCalledWith( - `New value [Test rule](?timeline=(id:'ef579e40-jibber-jabber',isOpen:!t))` + `New value [Test rule](/timelines/?timeline=(id:'ef579e40-jibber-jabber',isOpen:!t))` ); }); it('calls formatUrl with correct options', async () => { renderHook(() => useInsertTimeline('', onChange)); - expect(formatUrl).toHaveBeenCalledWith(`?timeline=(id:'ef579e40-jibber-jabber',isOpen:!t)`, { - absolute: true, - skipSearch: true, - }); + expect(formatUrl).toHaveBeenCalledWith( + `/timelines/?timeline=(id:'ef579e40-jibber-jabber',isOpen:!t)`, + { + absolute: true, + skipSearch: true, + } + ); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/index.ts b/x-pack/plugins/security_solution/public/cases/index.ts index a520932b48660..631276a6bf6b4 100644 --- a/x-pack/plugins/security_solution/public/cases/index.ts +++ b/x-pack/plugins/security_solution/public/cases/index.ts @@ -6,14 +6,14 @@ */ import { SecuritySubPlugin } from '../app/types'; -import { CasesRoutes } from './routes'; +import { routes } from './routes'; export class Cases { public setup() {} public start(): SecuritySubPlugin { return { - SubPluginRoutes: CasesRoutes, + routes, }; } } 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 2da0c42a20409..dee14b8ff4dc8 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 @@ -17,7 +17,7 @@ import { getCaseUrl } from '../../common/components/link_to'; import { navTabs } from '../../app/home/home_navigations'; import { CaseView } from '../components/case_view'; import { permissionsReadOnlyErrorMessage, CaseCallOut } from '../components/callout'; -import { CASES_APP_ID } from '../../../common/constants'; +import { APP_ID } from '../../../common/constants'; export const CaseDetailsPage = React.memo(() => { const { @@ -31,7 +31,10 @@ export const CaseDetailsPage = React.memo(() => { const search = useGetUrlSearch(navTabs.case); if (userPermissions != null && !userPermissions.read) { - navigateToApp(CASES_APP_ID, { path: getCaseUrl(search) }); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, + path: getCaseUrl(search), + }); return null; } 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 3238e436ed09f..121c33c854138 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, CASES_APP_ID } from '../../../common/constants'; +import { APP_ID } from '../../../common/constants'; const ConfigureCasesPageComponent: React.FC = () => { const { @@ -38,7 +38,10 @@ const ConfigureCasesPageComponent: React.FC = () => { ); if (userPermissions != null && !userPermissions.read) { - navigateToApp(CASES_APP_ID, { path: getCaseUrl(search) }); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, + path: getCaseUrl(search), + }); return null; } 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 1a039bb13a379..08c9a546d6610 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 { CASES_APP_ID } from '../../../common/constants'; +import { APP_ID } from '../../../common/constants'; export const CreateCasePage = React.memo(() => { const userPermissions = useGetUserCasesPermissions(); @@ -35,7 +35,8 @@ export const CreateCasePage = React.memo(() => { ); if (userPermissions != null && !userPermissions.crud) { - navigateToApp(CASES_APP_ID, { + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseUrl(search), }); return null; diff --git a/x-pack/plugins/security_solution/public/cases/pages/index.tsx b/x-pack/plugins/security_solution/public/cases/pages/index.tsx index 314bdc9bfd117..c5cdebda9e350 100644 --- a/x-pack/plugins/security_solution/public/cases/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/pages/index.tsx @@ -12,14 +12,14 @@ import { CaseDetailsPage } from './case_details'; import { CasesPage } from './case'; import { CreateCasePage } from './create_case'; import { ConfigureCasesPage } from './configure_cases'; +import { CASES_PATH } from '../../../common/constants'; -const casesPagePath = ''; -const caseDetailsPagePath = `${casesPagePath}/:detailName`; +const caseDetailsPagePath = `${CASES_PATH}/:detailName`; const subCaseDetailsPagePath = `${caseDetailsPagePath}/sub-cases/:subCaseId`; const caseDetailsPagePathWithCommentId = `${caseDetailsPagePath}/:commentId`; const subCaseDetailsPagePathWithCommentId = `${subCaseDetailsPagePath}/:commentId`; -const createCasePagePath = `${casesPagePath}/create`; -const configureCasesPagePath = `${casesPagePath}/configure`; +const createCasePagePath = `${CASES_PATH}/create`; +const configureCasesPagePath = `${CASES_PATH}/configure`; const CaseContainerComponent: React.FC = () => ( @@ -41,7 +41,7 @@ const CaseContainerComponent: React.FC = () => ( - + 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 1c848190cbef5..968712009e110 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,8 @@ 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 { CASES_APP_ID } from '../../../common/constants'; +import { APP_ID } from '../../../common/constants'; +import { SecurityPageName } from '../../app/types'; export const getBreadcrumbs = ( params: RouteSpyState, @@ -25,7 +26,8 @@ export const getBreadcrumbs = ( let breadcrumb = [ { text: i18n.PAGE_TITLE, - href: getUrlForApp(CASES_APP_ID, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: queryParameters, }), }, @@ -35,7 +37,8 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: i18n.CREATE_BC_TITLE, - href: getUrlForApp(CASES_APP_ID, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(queryParameters), }), }, @@ -45,7 +48,8 @@ export const getBreadcrumbs = ( ...breadcrumb, { text: params.state?.caseTitle ?? '', - href: getUrlForApp(CASES_APP_ID, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: params.detailName, search: queryParameters }), }), }, diff --git a/x-pack/plugins/security_solution/public/cases/routes.tsx b/x-pack/plugins/security_solution/public/cases/routes.tsx index c937631e9474f..8ea30e60379ca 100644 --- a/x-pack/plugins/security_solution/public/cases/routes.tsx +++ b/x-pack/plugins/security_solution/public/cases/routes.tsx @@ -6,16 +6,21 @@ */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; +import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public'; +import { SecurityPageName, SecuritySubPluginRoutes } from '../app/types'; +import { CASES_PATH } from '../../common/constants'; import { Case } from './pages'; -import { NotFoundPage } from '../app/404'; -export const CasesRoutes: React.FC = () => ( - - - - - } /> - +export const CasesRoutes = () => ( + + + ); + +export const routes: SecuritySubPluginRoutes = [ + { + path: CASES_PATH, + render: CasesRoutes, + }, +]; diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts b/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts index 3fe8e68dd04c1..baf4965467b51 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/link_to/__mocks__/index.ts @@ -11,7 +11,7 @@ export { getDetectionEngineUrl } from '../redirect_to_detection_engine'; export { getAppOverviewUrl } from '../redirect_to_overview'; export { getHostDetailsUrl, getHostsUrl } from '../redirect_to_hosts'; export { getNetworkUrl, getNetworkDetailsUrl } from '../redirect_to_network'; -export { getTimelinesUrl, getTimelineTabsUrl, getTimelineUrl } from '../redirect_to_timelines'; +export { getTimelineTabsUrl, getTimelineUrl } from '../redirect_to_timelines'; export { getCaseDetailsUrl, getCaseUrl, diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts index a6894a64ca4e6..b7defcc8c2af9 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/index.ts +++ b/x-pack/plugins/security_solution/public/common/components/link_to/index.ts @@ -17,7 +17,7 @@ export { getDetectionEngineUrl, getRuleDetailsUrl } from './redirect_to_detectio export { getAppOverviewUrl } from './redirect_to_overview'; export { getHostDetailsUrl, getHostsUrl } from './redirect_to_hosts'; export { getNetworkUrl, getNetworkDetailsUrl } from './redirect_to_network'; -export { getTimelinesUrl, getTimelineTabsUrl, getTimelineUrl } from './redirect_to_timelines'; +export { getTimelineTabsUrl, getTimelineUrl } from './redirect_to_timelines'; export { getCaseDetailsUrl, getCaseUrl, @@ -48,21 +48,10 @@ export const useFormatUrl = (page: SiemNavTabKey) => { ? '' : `?${pathArr[1]}` }`; - // TODO: [1101] remove conditional and use always deepLinkId - return page === 'overview' || - page === 'alerts' || - page === 'rules' || - page === 'exceptions' || - page === 'hosts' || - page === 'network' || - page === 'administration' || - page === 'endpoints' || - page === 'trusted_apps' || - page === 'event_filters' - ? getUrlForApp(APP_ID, { deepLinkId: page, path: formattedPath, absolute }) - : getUrlForApp(`${APP_ID}:${page}`, { path: formattedPath, absolute }); + return getUrlForApp(APP_ID, { deepLinkId: page, path: formattedPath, absolute }); }, [getUrlForApp, page, search] ); + return { formatUrl, search }; }; diff --git a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx index adbc2e3a9b670..0059e040e07d4 100644 --- a/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx +++ b/x-pack/plugins/security_solution/public/common/components/link_to/redirect_to_timelines.tsx @@ -9,12 +9,10 @@ import { isEmpty } from 'lodash/fp'; import { TimelineTypeLiteral } from '../../../../common/types/timeline'; import { appendSearch } from './helpers'; -export const getTimelinesUrl = (search?: string) => `${appendSearch(search)}`; - export const getTimelineTabsUrl = (tabName: TimelineTypeLiteral, search?: string) => `/${tabName}${appendSearch(search)}`; export const getTimelineUrl = (id: string, graphEventId?: string) => - `?timeline=(id:'${id}',isOpen:!t${ + `/?timeline=(id:'${id}',isOpen:!t${ isEmpty(graphEventId) ? ')' : `,graphEventId:'${graphEventId}')` }`; 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 e64549a74bc1f..0b6b77aab00e4 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 @@ -20,7 +20,7 @@ import React, { useMemo, useCallback } from 'react'; import { isNil } from 'lodash/fp'; import styled from 'styled-components'; -import { IP_REPUTATION_LINKS_SETTING, APP_ID, CASES_APP_ID } from '../../../../common/constants'; +import { IP_REPUTATION_LINKS_SETTING, APP_ID } from '../../../../common/constants'; import { DefaultFieldRendererOverflow, DEFAULT_MORE_MAX_HEIGHT, @@ -181,7 +181,8 @@ const CaseDetailsLinkComponent: React.FC<{ const goToCaseDetails = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: detailName, search, subCaseId }), }); }, @@ -208,7 +209,8 @@ export const CreateCaseLink = React.memo<{ children: React.ReactNode }>(({ child const goToCreateCase = useCallback( async (ev) => { ev.preventDefault(); - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_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 62ab09465556d..0917ca172d512 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 @@ -176,7 +176,7 @@ describe('Navigation Breadcrumbs', () => { { 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)))", + "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)))", }, ]); }); @@ -264,7 +264,7 @@ describe('Navigation Breadcrumbs', () => { { 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)))", + "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)))", }, ]); }); @@ -285,11 +285,11 @@ describe('Navigation Breadcrumbs', () => { { 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)))", + "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)))", }, { 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: `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)))`, }, ]); }); 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 d375299d9c816..be46d3848910d 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 @@ -30,38 +30,11 @@ export const usePrimaryNavigationItems = ({ const handleClick = (ev: React.MouseEvent) => { ev.preventDefault(); - // TODO: [1101] remove conditional and use always deepLinkId - if ( - id === 'overview' || - id === 'alerts' || - id === 'rules' || - id === 'exceptions' || - id === 'hosts' || - id === 'network' || - id === 'endpoints' || - id === 'trusted_apps' || - id === 'event_filters' - ) { - navigateToApp(APP_ID, { deepLinkId: id, path: urlSearch }); - } else { - navigateToApp(`${APP_ID}:${id}`, { path: urlSearch }); - } + navigateToApp(APP_ID, { deepLinkId: id, path: urlSearch }); track(METRIC_TYPE.CLICK, `${TELEMETRY_EVENT.TAB_CLICKED}${id}`); }; - // TODO: [1101] remove conditional and use always deepLinkId - const appHref = - id === 'overview' || - id === 'alerts' || - id === 'rules' || - id === 'exceptions' || - id === 'hosts' || - id === 'network' || - id === 'endpoints' || - id === 'trusted_apps' || - id === 'event_filters' - ? getUrlForApp(APP_ID, { deepLinkId: id, path: urlSearch }) - : getUrlForApp(`${APP_ID}:${id}`, { path: urlSearch }); + const appHref = getUrlForApp(APP_ID, { deepLinkId: id, path: urlSearch }); return { 'data-href': appHref, @@ -82,12 +55,7 @@ export const usePrimaryNavigationItems = ({ { id: APP_ID, name: '', - items: [ - getSideNav(navTabs.overview), - // TODO: [1101] Move the following nav items to its group - getSideNav(navTabs.timelines), - getSideNav(navTabs.case), - ], + items: [getSideNav(navTabs.overview)], }, { ...navTabGroups.detect, @@ -103,7 +71,7 @@ export const usePrimaryNavigationItems = ({ }, { ...navTabGroups.investigate, - items: [], + items: [getSideNav(navTabs.timelines), getSideNav(navTabs.case)], }, { ...navTabGroups.manage, diff --git a/x-pack/plugins/security_solution/public/detections/index.ts b/x-pack/plugins/security_solution/public/detections/index.ts index 84d896e193dc1..ca35efe4d9294 100644 --- a/x-pack/plugins/security_solution/public/detections/index.ts +++ b/x-pack/plugins/security_solution/public/detections/index.ts @@ -21,7 +21,6 @@ export class Detections { public start(storage: Storage): SecuritySubPlugin { return { - SubPluginRoutes: () => null, // TODO: [1101] remove when typings cleaned storageTimelines: { timelineById: getTimelinesInStorageByIds(storage, DETECTIONS_TIMELINE_IDS), }, diff --git a/x-pack/plugins/security_solution/public/exceptions/index.ts b/x-pack/plugins/security_solution/public/exceptions/index.ts index 5113ec9848a8c..eccb2ba7578c7 100644 --- a/x-pack/plugins/security_solution/public/exceptions/index.ts +++ b/x-pack/plugins/security_solution/public/exceptions/index.ts @@ -16,7 +16,6 @@ export class Exceptions { public start(storage: Storage): SecuritySubPlugin { return { - SubPluginRoutes: () => null, // TODO: [1101] remove when typings cleaned storageTimelines: { timelineById: getTimelinesInStorageByIds(storage, DETECTIONS_TIMELINE_IDS), }, diff --git a/x-pack/plugins/security_solution/public/helpers.ts b/x-pack/plugins/security_solution/public/helpers.ts index 8e61a64e56f40..67c1fd0684682 100644 --- a/x-pack/plugins/security_solution/public/helpers.ts +++ b/x-pack/plugins/security_solution/public/helpers.ts @@ -86,13 +86,15 @@ export const manageOldSiemRoutes = async (coreStart: CoreStart) => { }); break; case SecurityPageName.timelines: - application.navigateToApp(`${APP_ID}:${SecurityPageName.timelines}`, { + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.timelines, replace: true, path, }); break; case SecurityPageName.case: - application.navigateToApp(`${APP_ID}:${SecurityPageName.case}`, { + application.navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, replace: true, path, }); diff --git a/x-pack/plugins/security_solution/public/hosts/index.ts b/x-pack/plugins/security_solution/public/hosts/index.ts index 0a213fcf8d1e0..cbb539f8e4107 100644 --- a/x-pack/plugins/security_solution/public/hosts/index.ts +++ b/x-pack/plugins/security_solution/public/hosts/index.ts @@ -22,7 +22,6 @@ export class Hosts { public start(storage: Storage): SecuritySubPluginWithStore<'hosts', HostsState> { return { - SubPluginRoutes: () => null, // TODO: [1101] remove when typings cleaned routes, storageTimelines: { timelineById: getTimelinesInStorageByIds(storage, HOST_TIMELINE_IDS), diff --git a/x-pack/plugins/security_solution/public/lazy_application_dependencies.tsx b/x-pack/plugins/security_solution/public/lazy_application_dependencies.tsx index ee0e9e7d0085b..daf2c55a44333 100644 --- a/x-pack/plugins/security_solution/public/lazy_application_dependencies.tsx +++ b/x-pack/plugins/security_solution/public/lazy_application_dependencies.tsx @@ -10,8 +10,7 @@ * By loading these later we can reduce the initial bundle size and allow users to delay loading these dependencies until they are needed. */ -import { renderApp, renderAppOld } from './app'; +import { renderApp } from './app'; import { createStore, createInitialState } from './common/store'; -// TODO: [1101] remove renderAppOld when all sections migrated -export { renderApp, renderAppOld, createStore, createInitialState }; +export { renderApp, createStore, createInitialState }; diff --git a/x-pack/plugins/security_solution/public/management/index.ts b/x-pack/plugins/security_solution/public/management/index.ts index 9581c99c934d4..326f8471aa621 100644 --- a/x-pack/plugins/security_solution/public/management/index.ts +++ b/x-pack/plugins/security_solution/public/management/index.ts @@ -39,7 +39,6 @@ export class Management { plugins: StartPlugins ): SecuritySubPluginWithStore<'management', ManagementState> { return { - SubPluginRoutes: () => null, // TODO: [1101] remove routes, store: { initialState: { diff --git a/x-pack/plugins/security_solution/public/network/index.ts b/x-pack/plugins/security_solution/public/network/index.ts index 358562845e5de..f34ebcc6e33b9 100644 --- a/x-pack/plugins/security_solution/public/network/index.ts +++ b/x-pack/plugins/security_solution/public/network/index.ts @@ -17,7 +17,6 @@ export class Network { public start(storage: Storage): SecuritySubPluginWithStore<'network', NetworkState> { return { - SubPluginRoutes: () => null, // TODO: [1101] remove when typings cleaned routes, storageTimelines: { timelineById: getTimelinesInStorageByIds(storage, [TimelineId.networkPageExternalAlerts]), 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 996835296fcc4..bc72d1418e6f2 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 { useKibana } from '../../../common/lib/kibana'; -import { APP_ID, CASES_APP_ID } from '../../../../common/constants'; +import { APP_ID } from '../../../../common/constants'; import { SecurityPageName } from '../../../app/types'; import { AllCasesNavProps } from '../../../cases/components/all_cases'; @@ -33,7 +33,7 @@ const RecentCasesComponent = () => { if (e) { e.preventDefault(); } - return navigateToApp(CASES_APP_ID); + return navigateToApp(APP_ID, { deepLinkId: SecurityPageName.case }); }, }, caseDetailsNavigation: { @@ -44,7 +44,8 @@ const RecentCasesComponent = () => { if (e) { e.preventDefault(); } - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCaseDetailsUrl({ id: detailName, search, subCaseId }), }); }, @@ -55,7 +56,8 @@ const RecentCasesComponent = () => { if (e) { e.preventDefault(); } - return navigateToApp(CASES_APP_ID, { + return navigateToApp(APP_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 1d9b039e02258..f76d71600d0e7 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 @@ -60,7 +60,9 @@ const StatefulRecentTimelinesComponent: React.FC = ({ filterBy }) => { const goToTimelines = useCallback( (ev) => { ev.preventDefault(); - navigateToApp(`${APP_ID}:${SecurityPageName.timelines}`); + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.timelines, + }); }, [navigateToApp] ); diff --git a/x-pack/plugins/security_solution/public/overview/index.ts b/x-pack/plugins/security_solution/public/overview/index.ts index 4921caa910775..3aa6c4185f6da 100644 --- a/x-pack/plugins/security_solution/public/overview/index.ts +++ b/x-pack/plugins/security_solution/public/overview/index.ts @@ -13,7 +13,6 @@ export class Overview { public start(): SecuritySubPlugin { return { - SubPluginRoutes: () => null, // TODO: [1101] remove when typings cleaned routes, }; } diff --git a/x-pack/plugins/security_solution/public/plugin.tsx b/x-pack/plugins/security_solution/public/plugin.tsx index 35a09346ec2eb..987f980f5bba6 100644 --- a/x-pack/plugins/security_solution/public/plugin.tsx +++ b/x-pack/plugins/security_solution/public/plugin.tsx @@ -34,22 +34,16 @@ import { KibanaServices } from './common/lib/kibana/services'; import { APP_ID, - APP_ICON_SOLUTION, OVERVIEW_PATH, APP_OVERVIEW_PATH, - APP_TIMELINES_PATH, - APP_CASES_PATH, APP_PATH, - CASES_APP_ID, DEFAULT_INDEX_KEY, DETECTION_ENGINE_INDEX_URL, DEFAULT_ALERTS_INDEX, } from '../common/constants'; -import { SecurityPageName } from './app/types'; -import { getDeepLinks, getDeepLinksAndKeywords } from './app/deep_links'; +import { getDeepLinks } from './app/deep_links'; import { manageOldSiemRoutes } from './helpers'; -import { TIMELINES, CASE } from './app/translations'; import { IndexFieldsStrategyRequest, IndexFieldsStrategyResponse, @@ -71,8 +65,6 @@ export class Plugin implements IPlugin(); this.kibanaVersion = initializerContext.env.packageInfo.version; } - private caseUpdater$ = new Subject(); - // TODO: [1101] remove all previous updaters and use only appUpdater$ private appUpdater$ = new Subject(); private storage = new Storage(localStorage); @@ -144,48 +136,6 @@ export class Plugin implements IPlugin { - const [coreStart, startPlugins] = await core.getStartServices(); - const { timelines: subPlugin } = await this.subPlugins(); - const { renderAppOld } = await this.lazyApplicationDependencies(); - return renderAppOld({ - ...params, - services: await startServices, - store: await this.store(coreStart, startPlugins), - SubPluginRoutes: subPlugin.start().SubPluginRoutes, - }); - }, - }); - - core.application.register({ - id: CASES_APP_ID, - title: CASE, - order: 9002, - euiIconType: APP_ICON_SOLUTION, - category: DEFAULT_APP_CATEGORIES.security, - appRoute: APP_CASES_PATH, - updater$: this.caseUpdater$, - mount: async (params: AppMountParameters) => { - const [coreStart, startPlugins] = await core.getStartServices(); - const { cases: subPlugin } = await this.subPlugins(); - const { renderAppOld } = await this.lazyApplicationDependencies(); - return renderAppOld({ - ...params, - services: await startServices, - store: await this.store(coreStart, startPlugins), - SubPluginRoutes: subPlugin.start().SubPluginRoutes, - }); - }, - }); - core.application.register({ id: APP_ID, title: APP_NAME, @@ -358,7 +308,7 @@ export class Plugin implements IPlugin { if (!this._store) { const experimentalFeatures = parseExperimentalConfigValue( @@ -368,20 +318,10 @@ export class Plugin implements IPlugin( { indices: defaultIndicesName, onlyCheckIfIndicesExist: true }, @@ -410,29 +350,16 @@ export class Plugin implements IPlugin null, // TODO: [1101] remove when typings cleaned storageTimelines: { timelineById: getTimelinesInStorageByIds(storage, DETECTIONS_TIMELINE_IDS), }, diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx index bc9876b207284..717b338aa2535 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/add_to_case_button/index.test.tsx @@ -12,6 +12,7 @@ import { useKibana } from '../../../../common/lib/kibana'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; import { mockTimelineModel, TestProviders } from '../../../../common/mock'; import { AddToCaseButton } from '.'; +import { SecurityPageName } from '../../../../../common/constants'; jest.mock('../../../../common/components/link_to', () => { const original = jest.requireActual('../../../../common/components/link_to'); @@ -61,7 +62,10 @@ 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:case', { path: '/create' }); + expect(navigateToApp).toHaveBeenCalledWith('securitySolution', { + path: '/create', + deepLinkId: SecurityPageName.case, + }); }); it('navigates to the correct path with id', async () => { @@ -80,6 +84,9 @@ 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:case', { path: '/case-id' }); + expect(navigateToApp).toHaveBeenCalledWith('securitySolution', { + 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 47935347b96ac..553b827f2a64c 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, CASES_APP_ID } from '../../../../../common/constants'; +import { APP_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,8 @@ const AddToCaseButtonComponent: React.FC = ({ timelineId }) => { const onRowClick = useCallback( async (theCase?: Case | SubCase) => { openCaseModal(false); - await navigateToApp(CASES_APP_ID, { + await navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: theCase != null ? getCaseDetailsUrl({ id: theCase.id }) : getCreateCaseUrl(), }); dispatch( @@ -88,7 +89,9 @@ const AddToCaseButtonComponent: React.FC = ({ timelineId }) => { const handleNewCaseClick = useCallback(() => { handlePopoverClose(); - navigateToApp(CASES_APP_ID, { + + navigateToApp(APP_ID, { + deepLinkId: SecurityPageName.case, path: getCreateCaseUrl(), }).then(() => { dispatch( diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.test.tsx index 1d39dd169ffaa..5402210d22cce 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.test.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.test.tsx @@ -31,6 +31,22 @@ jest.mock('../../../common/components/link_to', () => { }; }); +jest.mock('../../../../../../../src/plugins/kibana_react/public', () => { + const originalModule = jest.requireActual('../../../../../../../src/plugins/kibana_react/public'); + const useKibana = jest.fn().mockImplementation(() => ({ + services: { + application: { + navigateToUrl: jest.fn(), + }, + }, + })); + + return { + ...originalModule, + useKibana, + }; +}); + describe('useTimelineTypes', () => { it('init', async () => { await act(async () => { diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx index a66fe43d305f1..ca8b443309e12 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/use_timeline_types.tsx @@ -6,7 +6,7 @@ */ import React, { useState, useCallback, useMemo } from 'react'; -import { useParams, useHistory } from 'react-router-dom'; +import { useParams } from 'react-router-dom'; import { EuiTabs, EuiTab, EuiSpacer } from '@elastic/eui'; import { noop } from 'lodash/fp'; @@ -15,7 +15,7 @@ import { SecurityPageName } from '../../../app/types'; import { getTimelineTabsUrl, useFormatUrl } from '../../../common/components/link_to'; import * as i18n from './translations'; import { TimelineTabsStyle, TimelineTab } from './types'; - +import { useKibana } from '../../../common/lib/kibana'; export interface UseTimelineTypesArgs { defaultTimelineCount?: number | null; templateTimelineCount?: number | null; @@ -31,8 +31,8 @@ export const useTimelineTypes = ({ defaultTimelineCount, templateTimelineCount, }: UseTimelineTypesArgs): UseTimelineTypesResult => { - const history = useHistory(); const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.timelines); + const { navigateToUrl } = useKibana().services.application; const { tabName } = useParams<{ pageName: SecurityPageName; tabName: string }>(); const [timelineType, setTimelineTypes] = useState( tabName === TimelineType.default || tabName === TimelineType.template @@ -40,27 +40,30 @@ export const useTimelineTypes = ({ : TimelineType.default ); + const timelineUrl = formatUrl(getTimelineTabsUrl(TimelineType.default, urlSearch)); + const templateUrl = formatUrl(getTimelineTabsUrl(TimelineType.template, urlSearch)); + const goToTimeline = useCallback( (ev) => { ev.preventDefault(); - history.push(getTimelineTabsUrl(TimelineType.default, urlSearch)); + navigateToUrl(timelineUrl); }, - [history, urlSearch] + [navigateToUrl, timelineUrl] ); const goToTemplateTimeline = useCallback( (ev) => { ev.preventDefault(); - history.push(getTimelineTabsUrl(TimelineType.template, urlSearch)); + navigateToUrl(templateUrl); }, - [history, urlSearch] + [navigateToUrl, templateUrl] ); const getFilterOrTabs: (timelineTabsStyle: TimelineTabsStyle) => TimelineTab[] = useCallback( (timelineTabsStyle: TimelineTabsStyle) => [ { id: TimelineType.default, name: i18n.TAB_TIMELINES, - href: formatUrl(getTimelineTabsUrl(TimelineType.default, urlSearch)), + href: timelineUrl, disabled: false, onClick: timelineTabsStyle === TimelineTabsStyle.tab ? goToTimeline : noop, @@ -68,13 +71,13 @@ export const useTimelineTypes = ({ { id: TimelineType.template, name: i18n.TAB_TEMPLATES, - href: formatUrl(getTimelineTabsUrl(TimelineType.template, urlSearch)), + href: templateUrl, disabled: false, onClick: timelineTabsStyle === TimelineTabsStyle.tab ? goToTemplateTimeline : noop, }, ], - [urlSearch, formatUrl, goToTimeline, goToTemplateTimeline] + [timelineUrl, templateUrl, goToTimeline, goToTemplateTimeline] ); const onFilterClicked = useCallback( diff --git a/x-pack/plugins/security_solution/public/timelines/index.ts b/x-pack/plugins/security_solution/public/timelines/index.ts index 224eec7568c6b..8725072e61849 100644 --- a/x-pack/plugins/security_solution/public/timelines/index.ts +++ b/x-pack/plugins/security_solution/public/timelines/index.ts @@ -6,7 +6,7 @@ */ import { SecuritySubPluginWithStore } from '../app/types'; -import { TimelinesRoutes } from './routes'; +import { routes } from './routes'; import { initialTimelineState, timelineReducer } from './store/timeline/reducer'; import { TimelineState } from './store/timeline/types'; @@ -15,7 +15,7 @@ export class Timelines { public start(): SecuritySubPluginWithStore<'timeline', TimelineState> { return { - SubPluginRoutes: TimelinesRoutes, + routes, store: { initialState: { timeline: initialTimelineState }, reducer: { timeline: timelineReducer }, 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 806ac57df1f65..2bf6e1259ff75 100644 --- a/x-pack/plugins/security_solution/public/timelines/pages/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/pages/index.tsx @@ -7,7 +7,7 @@ import { isEmpty } from 'lodash/fp'; import React from 'react'; -import { Switch, Route, useHistory } from 'react-router-dom'; +import { Switch, Route, Redirect } from 'react-router-dom'; import { ChromeBreadcrumb } from '../../../../../../src/core/public'; @@ -18,11 +18,11 @@ 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 } from '../../../common/constants'; +import { APP_ID, TIMELINES_PATH } from '../../../common/constants'; import { SecurityPageName } from '../../app/types'; -const timelinesPagePath = `/:tabName(${TimelineType.default}|${TimelineType.template})`; -const timelinesDefaultPath = `/${TimelineType.default}`; +const timelinesPagePath = `${TIMELINES_PATH}/:tabName(${TimelineType.default}|${TimelineType.template})`; +const timelinesDefaultPath = `${TIMELINES_PATH}/${TimelineType.default}`; export const getBreadcrumbs = ( params: TimelineRouteSpyState, @@ -31,28 +31,25 @@ export const getBreadcrumbs = ( ): ChromeBreadcrumb[] => [ { text: PAGE_TITLE, - href: getUrlForApp(`${APP_ID}:${SecurityPageName.timelines}`, { + href: getUrlForApp(APP_ID, { + deepLinkId: SecurityPageName.timelines, path: !isEmpty(search[0]) ? search[0] : '', }), }, ]; -export const Timelines = React.memo(() => { - const history = useHistory(); - return ( - - - - - { - history.replace(`${timelinesDefaultPath}${appendSearch(search)}`); - return null; - }} - /> - - ); -}); +export const Timelines = React.memo(() => ( + + + + + ( + + )} + /> + +)); Timelines.displayName = 'Timelines'; diff --git a/x-pack/plugins/security_solution/public/timelines/routes.tsx b/x-pack/plugins/security_solution/public/timelines/routes.tsx index 010a022bfefb7..0bc95b3b4959f 100644 --- a/x-pack/plugins/security_solution/public/timelines/routes.tsx +++ b/x-pack/plugins/security_solution/public/timelines/routes.tsx @@ -6,20 +6,21 @@ */ import React from 'react'; -import { Route, Switch } from 'react-router-dom'; - +import { TrackApplicationView } from '../../../../../src/plugins/usage_collection/public'; import { Timelines } from './pages'; -import { NotFoundPage } from '../app/404'; +import { TIMELINES_PATH } from '../../common/constants'; + +import { SecurityPageName, SecuritySubPluginRoutes } from '../app/types'; -const TimelinesRoutesComponent = () => ( - - - - - - - - +const TimelinesRoutes = () => ( + + + ); -export const TimelinesRoutes = React.memo(TimelinesRoutesComponent); +export const routes: SecuritySubPluginRoutes = [ + { + path: TIMELINES_PATH, + render: TimelinesRoutes, + }, +]; diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index 36b1cbfc0d87b..b348d95b96f06 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -92,7 +92,7 @@ export interface SubPlugins { management: Management; } -// TODO: [1101] find a better way to get those types +// TODO: find a better way to defined these types export interface StartedSubPlugins { alerts: ReturnType; rules: ReturnType; diff --git a/x-pack/plugins/security_solution/server/plugin.ts b/x-pack/plugins/security_solution/server/plugin.ts index e4f5dd9fff330..cd93737ef81e6 100644 --- a/x-pack/plugins/security_solution/server/plugin.ts +++ b/x-pack/plugins/security_solution/server/plugin.ts @@ -128,14 +128,6 @@ export interface PluginSetup {} // eslint-disable-next-line @typescript-eslint/no-empty-interface export interface PluginStart {} - -// TODO: [1101] remove securitySubPlugins and use APP_ID directly when all sections migrated -const securitySubPlugins = [ - APP_ID, - `${APP_ID}:${SecurityPageName.timelines}`, - `${APP_ID}:${SecurityPageName.case}`, -]; - export class Plugin implements IPlugin { private readonly logger: Logger; private readonly config: ConfigType; @@ -301,7 +293,7 @@ export class Plugin implements IPlugin