From 1610e32972f51b17a34e2a1f3d8b465bc18fd5c3 Mon Sep 17 00:00:00 2001 From: Mark Hopkin Date: Thu, 24 Aug 2023 12:05:44 +0100 Subject: [PATCH 01/58] [Fleet] Change 'Out-of-date' to 'Outdated policy' in agent list table (#164673) ## Summary Closes #164575. Bonus: also fix the bug where the warning goes under the policy revision I thought that looked a bit weird. Double bonus: Updated the create agent test script to be able to create outdated agents + to use nicer policy names so the table doesnt get messy Before: Screenshot 2023-08-24 at 10 10 58 After: Screenshot 2023-08-24 at 10 09 53 --- .../components/agent_list_table.tsx | 8 +- .../scripts/create_agents/create_agents.ts | 100 ++++++++++++++++-- 2 files changed, 96 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx index 294b3a69644c4..8709d0fc9adcf 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_list_page/components/agent_list_table.tsx @@ -180,9 +180,11 @@ export const AgentListTable: React.FC = (props: Props) => { const showWarning = agent.policy_revision && agentPolicy?.revision > agent.policy_revision; return ( - + {agentPolicy && ( - + + + )} {showWarning && ( @@ -191,7 +193,7 @@ export const AgentListTable: React.FC = (props: Props) => {   diff --git a/x-pack/plugins/fleet/scripts/create_agents/create_agents.ts b/x-pack/plugins/fleet/scripts/create_agents/create_agents.ts index 4436c07cc07b9..d8dc5bc4ba13e 100644 --- a/x-pack/plugins/fleet/scripts/create_agents/create_agents.ts +++ b/x-pack/plugins/fleet/scripts/create_agents/create_agents.ts @@ -8,6 +8,7 @@ import fetch from 'node-fetch'; import { ToolingLog } from '@kbn/tooling-log'; import { v4 as uuidv4 } from 'uuid'; import yargs from 'yargs'; +import { omit } from 'lodash'; import type { AgentStatus } from '../../common'; import type { Agent } from '../../common'; @@ -27,6 +28,7 @@ const printUsage = () => [--password]: password for kibana, defaults to changeme [--batches]: run the script in batches, defaults to 1 e.g if count is 50 and batches is 10, 500 agents will be created and 10 agent policies [--concurrentBatches]: how many batches to run concurrently, defaults to 10 + [--outdated]: agents will show as outdated (their revision is below the policies), defaults to false `); const DEFAULT_KIBANA_URL = 'http://localhost:5601'; @@ -51,7 +53,8 @@ const { agentVersion: agentVersionArg, username: kbnUsername = DEFAULT_KIBANA_USERNAME, password: kbnPassword = DEFAULT_KIBANA_PASSWORD, - batches: batchesArg, + batches: batchesArg = 1, + outdated: outdatedArg = false, concurrentBatches: concurrentBatchesArg = 10, // ignore yargs positional args, we only care about named args _, @@ -133,10 +136,12 @@ function createAgentWithStatus({ policyId, status, version, + hostname, }: { policyId: string; status: AgentStatus; version: string; + hostname: string; }) { const baseAgent = { access_api_key_id: 'api-key-1', @@ -153,7 +158,7 @@ function createAgentWithStatus({ version, }, }, - host: { hostname: uuidv4() }, + host: { hostname }, }, user_provided_metadata: {}, enrolled_at: new Date().toISOString(), @@ -167,7 +172,8 @@ function createAgentWithStatus({ function createAgentsWithStatuses( statusMap: Partial<{ [status in AgentStatus]: number }>, policyId: string, - version: string + version: string, + namePrefix?: string ) { // loop over statuses and create agents with that status const agents = []; @@ -176,13 +182,35 @@ function createAgentsWithStatuses( const currentAgentStatus = currentStatus as AgentStatus; const statusCount = statusMap[currentAgentStatus] || 0; for (let i = 0; i < statusCount; i++) { - agents.push(createAgentWithStatus({ policyId, status: currentAgentStatus, version })); + const hostname = `${namePrefix ? namePrefix + '-' : ''}${currentAgentStatus}-${i}`; + agents.push( + createAgentWithStatus({ policyId, status: currentAgentStatus, version, hostname }) + ); } } return agents; } +async function getAgentPolicy(id: string) { + const res = await fetch(`${kibanaUrl}/api/fleet/agent_policies/${id}`, { + method: 'get', + headers: { + Authorization: kbnAuth, + 'Content-Type': 'application/json', + 'kbn-xsrf': 'kibana', + 'x-elastic-product-origin': 'fleet', + }, + }); + const data = await res.json(); + + if (!data.item) { + logger.error('Agent policy not found, API response: ' + JSON.stringify(data)); + process.exit(1); + } + return data; +} + async function deleteAgents() { const auth = 'Basic ' + Buffer.from(ES_SUPERUSER + ':' + ES_PASSWORD).toString('base64'); const res = await fetch(`${ES_URL}/.fleet-agents/_delete_by_query`, { @@ -256,12 +284,12 @@ async function createSuperUser() { return { role, user }; } -async function createAgentPolicy(id: string) { +async function createAgentPolicy(id: string, name: string) { const res = await fetch(`${kibanaUrl}/api/fleet/agent_policies`, { method: 'post', body: JSON.stringify({ id, - name: id, + name, namespace: 'default', description: '', monitoring_enabled: ['logs', 'metrics'], @@ -277,12 +305,56 @@ async function createAgentPolicy(id: string) { const data = await res.json(); if (!data.item) { + if (data.message.includes('already exists')) { + // use regex to get the id from the error message, id is the first string in single quotes + const idRegex = /'([^']+)'/; + const idMatch = data.message.match(idRegex); + if (!idMatch || !idMatch[1]) { + logger.error('Cannot extract id from error message, API response: ' + JSON.stringify(data)); + process.exit(1); + } + logger.info(`Agent policy ${idMatch[1]} already exists, using existing policy`); + return getAgentPolicy(idMatch![1]); + } logger.error('Agent policy not created, API response: ' + JSON.stringify(data)); process.exit(1); } return data; } +async function bumpAgentPolicyRevision(id: string, policy: any) { + const res = await fetch(`${kibanaUrl}/api/fleet/agent_policies/${id}`, { + method: 'put', + body: JSON.stringify({ + ...omit(policy, [ + 'id', + 'updated_at', + 'updated_by', + 'revision', + 'status', + 'schema_version', + 'package_policies', + 'agents', + ]), + monitoring_enabled: ['logs'], // change monitoring to add a revision + }), + headers: { + Authorization: kbnAuth, + 'Content-Type': 'application/json', + 'kbn-xsrf': 'kibana', + 'x-elastic-product-origin': 'fleet', + }, + }); + + const data = await res.json(); + + if (!data.item) { + logger.error('Agent policy not updated, API response: ' + JSON.stringify(data)); + process.exit(1); + } + return data; +} + function logStatusMap(statusMap: Partial<{ [status in AgentStatus]: number }>) { const statuses = Object.keys(statusMap); logger.info( @@ -337,14 +409,24 @@ export async function run() { Array(currentBatchSize) .fill(0) .map(async (__, i) => { - const agentPolicyId = uuidv4(); - const agentPolicy = await createAgentPolicy(agentPolicyId); + let agentPolicyId = uuidv4(); + const agentPolicy = await createAgentPolicy(agentPolicyId, `Policy ${i}`); + agentPolicyId = agentPolicy.item.id; logger.info(`Created agent policy ${agentPolicy.item.id}`); const statusMap = statusesArg.reduce((acc, status) => ({ ...acc, [status]: count }), {}); logStatusMap(statusMap); - const agents = createAgentsWithStatuses(statusMap, agentPolicyId, agentVersion); + const agents = createAgentsWithStatuses( + statusMap, + agentPolicyId, + agentVersion, + i > 0 ? `batch-${i}` : undefined + ); const createRes = await createAgentDocsBulk(agents); + if (outdatedArg) { + logger.info(`Bumping agent policy revision so that agents will have outdated policies`); + bumpAgentPolicyRevision(agentPolicyId, agentPolicy.item); + } logger.info( `Batch complete, created ${createRes.items.length} agent docs, took ${createRes.took}, errors: ${createRes.errors}` ); From 75644797c3a32cedf49086903c03fb5b81583462 Mon Sep 17 00:00:00 2001 From: Pablo Machado Date: Thu, 24 Aug 2023 13:18:19 +0200 Subject: [PATCH 02/58] Create upselling package and implement EntityAnalytics serverless upselling (#164136) UX: https://github.com/elastic/security-team/issues/7310 ## Summary * It creates an Upselling package to share the service and components between ESS and Serverless plugins * It implements upselling for entity analytics on serverless by replicating the ESS approach ESS Screenshot 2023-08-17 at 13 34 59 Serverless Screenshot 2023-08-17 at 13 39 25 We are not displaying the upgrade button because we still don't know how to link to the cloud settings page. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- .github/CODEOWNERS | 1 + package.json | 1 + tsconfig.base.json | 2 + .../security-solution/upselling/README.mdx | 3 + .../upselling}/images/entity_paywall.png | Bin .../upselling/jest.config.js | 12 +++ .../security-solution/upselling/kibana.jsonc | 5 ++ .../upselling/messages/index.tsx} | 2 +- .../security-solution/upselling/package.json | 6 ++ .../upselling/pages/entity_analytics.test.tsx | 74 ++++++++++++++++++ .../upselling/pages/entity_analytics.tsx} | 58 +++++++++----- .../upselling/pages/translations.ts | 47 +++++++++++ .../upselling/service}/index.ts | 7 +- .../upselling/service}/types.ts | 2 +- .../service}/upselling_service.test.tsx | 2 +- .../upselling/service}/upselling_service.ts | 2 +- .../security-solution/upselling/tsconfig.json | 27 +++++++ .../components/navigation/helpers.test.tsx | 2 +- .../upselling_provider/upselling_provider.tsx | 2 +- .../common/hooks/use_upselling.test.tsx | 2 +- .../public/common/hooks/use_upselling.ts | 6 +- .../common/lib/kibana/kibana_react.mock.ts | 2 +- .../public/common/links/links.test.tsx | 2 +- .../public/common/links/types.ts | 2 +- .../utils/timeline/use_show_timeline.test.tsx | 2 +- .../risk_details_tab_body/index.tsx | 19 ++--- .../explore/hosts/pages/details/index.tsx | 5 +- .../public/explore/hosts/pages/hosts.tsx | 4 +- .../navigation/host_risk_score_tab_body.tsx | 5 -- .../explore/users/pages/details/index.tsx | 5 +- .../navigation/user_risk_score_tab_body.tsx | 5 -- .../public/explore/users/pages/users.tsx | 7 +- .../plugins/security_solution/public/index.ts | 7 -- .../render_context_providers.tsx | 2 +- .../view/ingest_manager_integration/types.ts | 2 +- .../policy_settings_form.test.tsx | 2 +- .../plugins/security_solution/public/mocks.ts | 2 +- .../public/overview/links.ts | 2 +- .../public/plugin_contract.ts | 2 +- .../plugins/security_solution/public/types.ts | 2 +- .../plugins/security_solution/tsconfig.json | 1 + .../public/upselling/pages/translations.ts | 34 -------- .../public/upselling/register_upsellings.tsx | 26 +++--- .../security_solution_ess/tsconfig.json | 2 +- .../public/plugin.ts | 3 +- .../public/upselling/lazy_upselling.tsx | 4 + .../investigation_guide_upselling.tsx | 24 ------ .../pages/generic_upselling_page.tsx | 43 ---------- .../public/upselling/register_upsellings.tsx | 26 ++++-- .../sections/generic_upselling_section.tsx | 43 ---------- .../tsconfig.json | 3 + yarn.lock | 4 + 52 files changed, 319 insertions(+), 236 deletions(-) create mode 100644 x-pack/packages/security-solution/upselling/README.mdx rename x-pack/{plugins/security_solution_ess/public/common => packages/security-solution/upselling}/images/entity_paywall.png (100%) create mode 100644 x-pack/packages/security-solution/upselling/jest.config.js create mode 100644 x-pack/packages/security-solution/upselling/kibana.jsonc rename x-pack/{plugins/security_solution_ess/public/upselling/messages/investigation_guide_upselling.tsx => packages/security-solution/upselling/messages/index.tsx} (87%) create mode 100644 x-pack/packages/security-solution/upselling/package.json create mode 100644 x-pack/packages/security-solution/upselling/pages/entity_analytics.test.tsx rename x-pack/{plugins/security_solution_ess/public/upselling/pages/entity_analytics_upselling.tsx => packages/security-solution/upselling/pages/entity_analytics.tsx} (63%) create mode 100644 x-pack/packages/security-solution/upselling/pages/translations.ts rename x-pack/{plugins/security_solution/public/common/lib/upsellings => packages/security-solution/upselling/service}/index.ts (72%) rename x-pack/{plugins/security_solution/public/common/lib/upsellings => packages/security-solution/upselling/service}/types.ts (90%) rename x-pack/{plugins/security_solution/public/common/lib/upsellings => packages/security-solution/upselling/service}/upselling_service.test.tsx (97%) rename x-pack/{plugins/security_solution/public/common/lib/upsellings => packages/security-solution/upselling/service}/upselling_service.ts (97%) create mode 100644 x-pack/packages/security-solution/upselling/tsconfig.json delete mode 100644 x-pack/plugins/security_solution_ess/public/upselling/pages/translations.ts delete mode 100644 x-pack/plugins/security_solution_serverless/public/upselling/messages/investigation_guide_upselling.tsx delete mode 100644 x-pack/plugins/security_solution_serverless/public/upselling/pages/generic_upselling_page.tsx delete mode 100644 x-pack/plugins/security_solution_serverless/public/upselling/sections/generic_upselling_section.tsx diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 67ec6e220fa8b..911bfb5161dc0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -604,6 +604,7 @@ x-pack/plugins/security_solution @elastic/security-solution x-pack/plugins/security_solution_serverless @elastic/security-solution x-pack/packages/security-solution/side_nav @elastic/security-threat-hunting-explore x-pack/packages/security-solution/storybook/config @elastic/security-threat-hunting-explore +x-pack/packages/security-solution/upselling @elastic/security-threat-hunting-explore x-pack/test/security_functional/plugins/test_endpoints @elastic/kibana-security packages/kbn-securitysolution-autocomplete @elastic/security-detection-engine x-pack/packages/security-solution/data_table @elastic/security-threat-hunting-investigations diff --git a/package.json b/package.json index 6540464687c2a..7a7a5fa36b381 100644 --- a/package.json +++ b/package.json @@ -608,6 +608,7 @@ "@kbn/security-solution-serverless": "link:x-pack/plugins/security_solution_serverless", "@kbn/security-solution-side-nav": "link:x-pack/packages/security-solution/side_nav", "@kbn/security-solution-storybook-config": "link:x-pack/packages/security-solution/storybook/config", + "@kbn/security-solution-upselling": "link:x-pack/packages/security-solution/upselling", "@kbn/security-test-endpoints-plugin": "link:x-pack/test/security_functional/plugins/test_endpoints", "@kbn/securitysolution-autocomplete": "link:packages/kbn-securitysolution-autocomplete", "@kbn/securitysolution-data-table": "link:x-pack/packages/security-solution/data_table", diff --git a/tsconfig.base.json b/tsconfig.base.json index d13cc53e6f546..cf7d73b7a5a9c 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1202,6 +1202,8 @@ "@kbn/security-solution-side-nav/*": ["x-pack/packages/security-solution/side_nav/*"], "@kbn/security-solution-storybook-config": ["x-pack/packages/security-solution/storybook/config"], "@kbn/security-solution-storybook-config/*": ["x-pack/packages/security-solution/storybook/config/*"], + "@kbn/security-solution-upselling": ["x-pack/packages/security-solution/upselling"], + "@kbn/security-solution-upselling/*": ["x-pack/packages/security-solution/upselling/*"], "@kbn/security-test-endpoints-plugin": ["x-pack/test/security_functional/plugins/test_endpoints"], "@kbn/security-test-endpoints-plugin/*": ["x-pack/test/security_functional/plugins/test_endpoints/*"], "@kbn/securitysolution-autocomplete": ["packages/kbn-securitysolution-autocomplete"], diff --git a/x-pack/packages/security-solution/upselling/README.mdx b/x-pack/packages/security-solution/upselling/README.mdx new file mode 100644 index 0000000000000..62dd77f659062 --- /dev/null +++ b/x-pack/packages/security-solution/upselling/README.mdx @@ -0,0 +1,3 @@ +## Security Solution Upselling + +This package contains the upselling service that registers pages/component/messages and shared upselling components for ESS and Serverless plugins. diff --git a/x-pack/plugins/security_solution_ess/public/common/images/entity_paywall.png b/x-pack/packages/security-solution/upselling/images/entity_paywall.png similarity index 100% rename from x-pack/plugins/security_solution_ess/public/common/images/entity_paywall.png rename to x-pack/packages/security-solution/upselling/images/entity_paywall.png diff --git a/x-pack/packages/security-solution/upselling/jest.config.js b/x-pack/packages/security-solution/upselling/jest.config.js new file mode 100644 index 0000000000000..014db69e5a69d --- /dev/null +++ b/x-pack/packages/security-solution/upselling/jest.config.js @@ -0,0 +1,12 @@ +/* + * 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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../../..', + roots: ['/x-pack/packages/security-solution/upselling'], +}; diff --git a/x-pack/packages/security-solution/upselling/kibana.jsonc b/x-pack/packages/security-solution/upselling/kibana.jsonc new file mode 100644 index 0000000000000..0a47f6429d8aa --- /dev/null +++ b/x-pack/packages/security-solution/upselling/kibana.jsonc @@ -0,0 +1,5 @@ +{ + "type": "shared-common", + "id": "@kbn/security-solution-upselling", + "owner": "@elastic/security-threat-hunting-explore" +} \ No newline at end of file diff --git a/x-pack/plugins/security_solution_ess/public/upselling/messages/investigation_guide_upselling.tsx b/x-pack/packages/security-solution/upselling/messages/index.tsx similarity index 87% rename from x-pack/plugins/security_solution_ess/public/upselling/messages/investigation_guide_upselling.tsx rename to x-pack/packages/security-solution/upselling/messages/index.tsx index 8dd16883f5088..633f44d21b770 100644 --- a/x-pack/plugins/security_solution_ess/public/upselling/messages/investigation_guide_upselling.tsx +++ b/x-pack/packages/security-solution/upselling/messages/index.tsx @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; export const UPGRADE_INVESTIGATION_GUIDE = (requiredLicense: string) => - i18n.translate('xpack.securitySolutionEss.markdown.insight.upsell', { + i18n.translate('securitySolutionPackages.markdown.insight.upsell', { defaultMessage: 'Upgrade to {requiredLicense} to make use of insights in investigation guides', values: { requiredLicense, diff --git a/x-pack/packages/security-solution/upselling/package.json b/x-pack/packages/security-solution/upselling/package.json new file mode 100644 index 0000000000000..92bd9e35078fa --- /dev/null +++ b/x-pack/packages/security-solution/upselling/package.json @@ -0,0 +1,6 @@ +{ + "name": "@kbn/security-solution-upselling", + "private": true, + "version": "1.0.0", + "license": "Elastic License 2.0" +} \ No newline at end of file diff --git a/x-pack/packages/security-solution/upselling/pages/entity_analytics.test.tsx b/x-pack/packages/security-solution/upselling/pages/entity_analytics.test.tsx new file mode 100644 index 0000000000000..244441d69eed8 --- /dev/null +++ b/x-pack/packages/security-solution/upselling/pages/entity_analytics.test.tsx @@ -0,0 +1,74 @@ +/* + * 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 { render } from '@testing-library/react'; +import EntityAnalyticsUpsellingComponent from './entity_analytics'; + +jest.mock('@kbn/security-solution-navigation', () => { + const original = jest.requireActual('@kbn/security-solution-navigation'); + return { + ...original, + useNavigation: () => ({ + navigateTo: jest.fn(), + }), + }; +}); + +describe('EntityAnalyticsUpselling', () => { + it('should render', () => { + const { getByTestId } = render( + + ); + expect(getByTestId('paywallCardDescription')).toBeInTheDocument(); + }); + + it('should throw exception when requiredLicense and requiredProduct are not provided', () => { + expect(() => render()).toThrow(); + }); + + it('should show product message when requiredProduct is provided', () => { + const { getByTestId } = render( + + ); + + expect(getByTestId('paywallCardDescription')).toHaveTextContent( + 'Entity risk scoring capability is available in our TEST PRODUCT license tier' + ); + }); + + it('should show product badge when requiredProduct is provided', () => { + const { getByText } = render( + + ); + + expect(getByText('TEST PRODUCT')).toBeInTheDocument(); + }); + + it('should show license message when requiredLicense is provided', () => { + const { getByTestId } = render( + + ); + + expect(getByTestId('paywallCardDescription')).toHaveTextContent( + 'This feature is available with TEST LICENSE or higher subscription' + ); + }); + + it('should show license badge when requiredLicense is provided', () => { + const { getByText } = render( + + ); + + expect(getByText('TEST LICENSE')).toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/security_solution_ess/public/upselling/pages/entity_analytics_upselling.tsx b/x-pack/packages/security-solution/upselling/pages/entity_analytics.tsx similarity index 63% rename from x-pack/plugins/security_solution_ess/public/upselling/pages/entity_analytics_upselling.tsx rename to x-pack/packages/security-solution/upselling/pages/entity_analytics.tsx index c7c4cd915fc03..56507dd0f2b46 100644 --- a/x-pack/plugins/security_solution_ess/public/upselling/pages/entity_analytics_upselling.tsx +++ b/x-pack/packages/security-solution/upselling/pages/entity_analytics.tsx @@ -23,7 +23,7 @@ import styled from '@emotion/styled'; import { useNavigation } from '@kbn/security-solution-navigation'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import * as i18n from './translations'; -import paywallPng from '../../common/images/entity_paywall.png'; +import paywallPng from '../images/entity_paywall.png'; const PaywallDiv = styled.div` max-width: 75%; @@ -33,7 +33,7 @@ const PaywallDiv = styled.div` width: auto; } } - .platinumCardDescription { + .paywallCardDescription { padding: 0 15%; } `; @@ -45,15 +45,31 @@ const StyledEuiCard = styled(EuiCard)` } `; -const EntityAnalyticsUpsellingComponent = () => { - const { getAppUrl, navigateTo } = useNavigation(); - const subscriptionUrl = getAppUrl({ - appId: 'management', - path: 'stack/license_management', - }); +const EntityAnalyticsUpsellingComponent = ({ + requiredLicense, + requiredProduct, + subscriptionUrl, +}: { + requiredLicense?: string; + requiredProduct?: string; + subscriptionUrl?: string; +}) => { + const { navigateTo } = useNavigation(); + const goToSubscription = useCallback(() => { navigateTo({ url: subscriptionUrl }); }, [navigateTo, subscriptionUrl]); + + if (!requiredProduct && !requiredLicense) { + throw new Error('requiredProduct or requiredLicense must be defined'); + } + + const upgradeMessage = requiredProduct + ? i18n.UPGRADE_PRODUCT_MESSAGE(requiredProduct) + : i18n.UPGRADE_LICENSE_MESSAGE(requiredLicense ?? ''); + + const requiredProductOrLicense = requiredProduct ?? requiredLicense ?? ''; + return ( @@ -61,8 +77,7 @@ const EntityAnalyticsUpsellingComponent = () => { } display="subdued" title={ @@ -73,26 +88,33 @@ const EntityAnalyticsUpsellingComponent = () => { description={false} paddingSize="xl" > - +

- {i18n.UPGRADE_MESSAGE} + {upgradeMessage}

-
- - {i18n.UPGRADE_BUTTON} - -
+ {subscriptionUrl && ( +
+ + {i18n.UPGRADE_BUTTON(requiredProductOrLicense)} + +
+ )}
- +
diff --git a/x-pack/packages/security-solution/upselling/pages/translations.ts b/x-pack/packages/security-solution/upselling/pages/translations.ts new file mode 100644 index 0000000000000..f4dd5b5c081c8 --- /dev/null +++ b/x-pack/packages/security-solution/upselling/pages/translations.ts @@ -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 { i18n } from '@kbn/i18n'; + +export const UPGRADE_LICENSE_MESSAGE = (requiredLicense: string) => + i18n.translate('securitySolutionPackages.entityAnalytics.paywall.upgradeLicenseMessage', { + defaultMessage: 'This feature is available with {requiredLicense} or higher subscription', + values: { + requiredLicense, + }, + }); + +export const UPGRADE_PRODUCT_MESSAGE = (requiredProduct: string) => + i18n.translate('securitySolutionPackages.entityAnalytics.paywall.upgradeProductMessage', { + defaultMessage: + 'Entity risk scoring capability is available in our {requiredProduct} license tier', + values: { + requiredProduct, + }, + }); + +export const UPGRADE_BUTTON = (requiredLicenseOrProduct: string) => + i18n.translate('securitySolutionPackages.entityAnalytics.paywall.upgradeButton', { + defaultMessage: 'Upgrade to {requiredLicenseOrProduct}', + values: { + requiredLicenseOrProduct, + }, + }); + +export const ENTITY_ANALYTICS_LICENSE_DESC = i18n.translate( + 'securitySolutionPackages.entityAnalytics.pageDesc', + { + defaultMessage: 'Detect threats from users and hosts within your network with Entity Analytics', + } +); + +export const ENTITY_ANALYTICS_TITLE = i18n.translate( + 'securitySolutionPackages.entityAnalytics.navigation', + { + defaultMessage: 'Entity Analytics', + } +); diff --git a/x-pack/plugins/security_solution/public/common/lib/upsellings/index.ts b/x-pack/packages/security-solution/upselling/service/index.ts similarity index 72% rename from x-pack/plugins/security_solution/public/common/lib/upsellings/index.ts rename to x-pack/packages/security-solution/upselling/service/index.ts index cd77e31c143fe..6a71fd9dde2ca 100644 --- a/x-pack/plugins/security_solution/public/common/lib/upsellings/index.ts +++ b/x-pack/packages/security-solution/upselling/service/index.ts @@ -5,4 +5,9 @@ * 2.0. */ export { UpsellingService } from './upselling_service'; -export type { PageUpsellings, SectionUpsellings, UpsellingSectionId } from './types'; +export type { + PageUpsellings, + SectionUpsellings, + UpsellingSectionId, + UpsellingMessageId, +} from './types'; diff --git a/x-pack/plugins/security_solution/public/common/lib/upsellings/types.ts b/x-pack/packages/security-solution/upselling/service/types.ts similarity index 90% rename from x-pack/plugins/security_solution/public/common/lib/upsellings/types.ts rename to x-pack/packages/security-solution/upselling/service/types.ts index 53c371a6f2153..2f5a06c391471 100644 --- a/x-pack/plugins/security_solution/public/common/lib/upsellings/types.ts +++ b/x-pack/packages/security-solution/upselling/service/types.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { SecurityPageName } from '../../../../common'; +import type { SecurityPageName } from '@kbn/security-solution-navigation'; export type PageUpsellings = Partial>; export type MessageUpsellings = Partial>; diff --git a/x-pack/plugins/security_solution/public/common/lib/upsellings/upselling_service.test.tsx b/x-pack/packages/security-solution/upselling/service/upselling_service.test.tsx similarity index 97% rename from x-pack/plugins/security_solution/public/common/lib/upsellings/upselling_service.test.tsx rename to x-pack/packages/security-solution/upselling/service/upselling_service.test.tsx index 2083e6c21f5c2..a584fcc70f887 100644 --- a/x-pack/plugins/security_solution/public/common/lib/upsellings/upselling_service.test.tsx +++ b/x-pack/packages/security-solution/upselling/service/upselling_service.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; import { firstValueFrom } from 'rxjs'; -import { SecurityPageName } from '../../../../common'; +import { SecurityPageName } from '@kbn/security-solution-navigation'; import { UpsellingService } from './upselling_service'; const TestComponent = () =>
{'TEST component'}
; diff --git a/x-pack/plugins/security_solution/public/common/lib/upsellings/upselling_service.ts b/x-pack/packages/security-solution/upselling/service/upselling_service.ts similarity index 97% rename from x-pack/plugins/security_solution/public/common/lib/upsellings/upselling_service.ts rename to x-pack/packages/security-solution/upselling/service/upselling_service.ts index 27d15c1d12768..c362c8be6b9e1 100644 --- a/x-pack/plugins/security_solution/public/common/lib/upsellings/upselling_service.ts +++ b/x-pack/packages/security-solution/upselling/service/upselling_service.ts @@ -7,7 +7,7 @@ import type { Observable } from 'rxjs'; import { BehaviorSubject } from 'rxjs'; -import type { SecurityPageName } from '../../../../common'; +import type { SecurityPageName } from '@kbn/security-solution-navigation'; import type { SectionUpsellings, PageUpsellings, diff --git a/x-pack/packages/security-solution/upselling/tsconfig.json b/x-pack/packages/security-solution/upselling/tsconfig.json new file mode 100644 index 0000000000000..70b145ed91b7d --- /dev/null +++ b/x-pack/packages/security-solution/upselling/tsconfig.json @@ -0,0 +1,27 @@ +{ + "extends": "../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "target/types", + "types": [ + "jest", + "node", + "react", + "@emotion/react/types/css-prop", + "@testing-library/jest-dom", + "@testing-library/react", + "@kbn/ambient-ui-types" + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx" + ], + "kbn_references": [ + "@kbn/i18n", + "@kbn/security-solution-navigation", + "@kbn/shared-ux-page-kibana-template", + ], + "exclude": [ + "target/**/*" + ] +} diff --git a/x-pack/plugins/security_solution/public/common/components/navigation/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/navigation/helpers.test.tsx index 732c6d5a0857f..31dc79605b167 100644 --- a/x-pack/plugins/security_solution/public/common/components/navigation/helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/navigation/helpers.test.tsx @@ -11,7 +11,7 @@ import type { AppLinkItems } from '../../links'; import { updateAppLinks } from '../../links'; import { mockGlobalState } from '../../mock'; import type { Capabilities } from '@kbn/core-capabilities-common'; -import { UpsellingService } from '../../lib/upsellings'; +import { UpsellingService } from '@kbn/security-solution-upselling/service'; const defaultAppLinks: AppLinkItems = [ { diff --git a/x-pack/plugins/security_solution/public/common/components/upselling_provider/upselling_provider.tsx b/x-pack/plugins/security_solution/public/common/components/upselling_provider/upselling_provider.tsx index 34668ea9583a3..3372500e95749 100644 --- a/x-pack/plugins/security_solution/public/common/components/upselling_provider/upselling_provider.tsx +++ b/x-pack/plugins/security_solution/public/common/components/upselling_provider/upselling_provider.tsx @@ -6,7 +6,7 @@ */ import React, { memo, useContext } from 'react'; -import type { UpsellingService } from '../../..'; +import type { UpsellingService } from '@kbn/security-solution-upselling/service'; export const UpsellingProviderContext = React.createContext(null); diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_upselling.test.tsx b/x-pack/plugins/security_solution/public/common/hooks/use_upselling.test.tsx index cd70445dcebae..835959a85bbd9 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_upselling.test.tsx +++ b/x-pack/plugins/security_solution/public/common/hooks/use_upselling.test.tsx @@ -8,7 +8,7 @@ import { renderHook } from '@testing-library/react-hooks'; import React from 'react'; import { SecurityPageName } from '../../../common'; -import { UpsellingService } from '../lib/upsellings'; +import { UpsellingService } from '@kbn/security-solution-upselling/service'; import { useUpsellingComponent, useUpsellingMessage, useUpsellingPage } from './use_upselling'; import { UpsellingProvider } from '../components/upselling_provider'; diff --git a/x-pack/plugins/security_solution/public/common/hooks/use_upselling.ts b/x-pack/plugins/security_solution/public/common/hooks/use_upselling.ts index 03f5935a85e0f..8ef01b5b56a25 100644 --- a/x-pack/plugins/security_solution/public/common/hooks/use_upselling.ts +++ b/x-pack/plugins/security_solution/public/common/hooks/use_upselling.ts @@ -8,10 +8,12 @@ import { useMemo } from 'react'; import useObservable from 'react-use/lib/useObservable'; import type React from 'react'; +import type { + UpsellingSectionId, + UpsellingMessageId, +} from '@kbn/security-solution-upselling/service'; import { useUpsellingService } from '../components/upselling_provider'; -import type { UpsellingSectionId } from '../lib/upsellings'; import type { SecurityPageName } from '../../../common'; -import type { UpsellingMessageId } from '../lib/upsellings/types'; export const useUpsellingComponent = (id: UpsellingSectionId): React.ComponentType | null => { const upselling = useUpsellingService(); diff --git a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts index 3dce598b1b205..9910470072d2e 100644 --- a/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts +++ b/x-pack/plugins/security_solution/public/common/lib/kibana/kibana_react.mock.ts @@ -48,7 +48,7 @@ import { cloudExperimentsMock } from '@kbn/cloud-experiments-plugin/common/mocks import { guidedOnboardingMock } from '@kbn/guided-onboarding-plugin/public/mocks'; import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks'; import { of } from 'rxjs'; -import { UpsellingService } from '../upsellings'; +import { UpsellingService } from '@kbn/security-solution-upselling/service'; import { cloudMock } from '@kbn/cloud-plugin/public/mocks'; import { NavigationProvider } from '@kbn/security-solution-navigation'; diff --git a/x-pack/plugins/security_solution/public/common/links/links.test.tsx b/x-pack/plugins/security_solution/public/common/links/links.test.tsx index 7e00e39f75437..e24c3920014c3 100644 --- a/x-pack/plugins/security_solution/public/common/links/links.test.tsx +++ b/x-pack/plugins/security_solution/public/common/links/links.test.tsx @@ -21,7 +21,7 @@ import { } from './links'; import { createCapabilities } from './test_utils'; import { hasCapabilities } from '../lib/capabilities'; -import { UpsellingService } from '../lib/upsellings'; +import { UpsellingService } from '@kbn/security-solution-upselling/service'; import React from 'react'; const defaultAppLinks: AppLinkItems = [ diff --git a/x-pack/plugins/security_solution/public/common/links/types.ts b/x-pack/plugins/security_solution/public/common/links/types.ts index 7e87d1d0bc098..53b5adc93b774 100644 --- a/x-pack/plugins/security_solution/public/common/links/types.ts +++ b/x-pack/plugins/security_solution/public/common/links/types.ts @@ -14,8 +14,8 @@ import type { LinkCategory as GenericLinkCategory, LinkCategories as GenericLinkCategories, } from '@kbn/security-solution-navigation'; +import type { UpsellingService } from '@kbn/security-solution-upselling/service'; import type { ExperimentalFeatures } from '../../../common/experimental_features'; -import type { UpsellingService } from '../lib/upsellings'; import type { RequiredCapabilities } from '../lib/capabilities'; /** diff --git a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx index 4711d802f4853..30f9483169b13 100644 --- a/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx +++ b/x-pack/plugins/security_solution/public/common/utils/timeline/use_show_timeline.test.tsx @@ -7,7 +7,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { allowedExperimentalValues } from '../../../../common/experimental_features'; -import { UpsellingService } from '../../lib/upsellings'; +import { UpsellingService } from '@kbn/security-solution-upselling/service'; import { updateAppLinks } from '../../links'; import { links } from '../../links/app_links'; import { useShowTimeline } from './use_show_timeline'; diff --git a/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_details_tab_body/index.tsx b/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_details_tab_body/index.tsx index a708b390be534..cdff32acef9fd 100644 --- a/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_details_tab_body/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/components/risk_score/risk_details_tab_body/index.tsx @@ -87,14 +87,13 @@ const RiskDetailsTabBodyComponent: React.FC< [entityName, riskEntity] ); - const { data, loading, refetch, inspect, isDeprecated, isModuleEnabled, isAuthorized } = - useRiskScore({ - filterQuery, - onlyLatest: false, - riskEntity, - skip: !overTimeToggleStatus && !contributorsToggleStatus, - timerange, - }); + const { data, loading, refetch, inspect, isDeprecated, isModuleEnabled } = useRiskScore({ + filterQuery, + onlyLatest: false, + riskEntity, + skip: !overTimeToggleStatus && !contributorsToggleStatus, + timerange, + }); const { data: riskScoreEngineStatus } = useRiskEngineStatus(); @@ -136,10 +135,6 @@ const RiskDetailsTabBodyComponent: React.FC< isDeprecated: isDeprecated && !loading, }; - if (!isAuthorized) { - return <>{'TODO: Add RiskScore Upsell'}; - } - if (riskScoreEngineStatus?.isUpdateAvailable) { return ; } diff --git a/x-pack/plugins/security_solution/public/explore/hosts/pages/details/index.tsx b/x-pack/plugins/security_solution/public/explore/hosts/pages/details/index.tsx index 26f7eb6a8af1c..bfffcebf8a1eb 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/pages/details/index.tsx @@ -66,6 +66,7 @@ import { LandingPageComponent } from '../../../../common/components/landing_page import { AlertCountByRuleByStatus } from '../../../../common/components/alert_count_by_status'; import { useLicense } from '../../../../common/hooks/use_license'; import { ResponderActionButton } from '../../../../detections/components/endpoint_responder/responder_action_button'; +import { useHasSecurityCapability } from '../../../../helper_hooks'; const ES_HOST_FIELD = 'host.name'; const HostOverviewManage = manageQuery(HostOverview); @@ -152,7 +153,7 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta dispatch(setHostDetailsTablesActivePageToZero()); }, [dispatch, detailName]); - const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; + const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); const { hasKibanaREAD, hasIndexRead } = useAlertsPrivileges(); const canReadAlerts = hasKibanaREAD && hasIndexRead; @@ -252,7 +253,7 @@ const HostDetailsComponent: React.FC = ({ detailName, hostDeta { }); const isEnterprisePlus = useLicense().isEnterprise(); + const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); const onSkipFocusBeforeEventsTable = useCallback(() => { containerElement.current @@ -215,7 +217,7 @@ const HostsComponent = () => { diff --git a/x-pack/plugins/security_solution/public/explore/hosts/pages/navigation/host_risk_score_tab_body.tsx b/x-pack/plugins/security_solution/public/explore/hosts/pages/navigation/host_risk_score_tab_body.tsx index 5511706a349fe..055a02d9c072e 100644 --- a/x-pack/plugins/security_solution/public/explore/hosts/pages/navigation/host_risk_score_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/explore/hosts/pages/navigation/host_risk_score_tab_body.tsx @@ -74,7 +74,6 @@ export const HostRiskScoreQueryTabBody = ({ isModuleEnabled, loading, refetch, - isAuthorized, totalCount, } = useRiskScore({ filterQuery, @@ -96,10 +95,6 @@ export const HostRiskScoreQueryTabBody = ({ isDeprecated: isDeprecated && !loading, }; - if (!isAuthorized) { - return <>{'TODO: Add RiskScore Upsell'}; - } - if (riskScoreEngineStatus?.isUpdateAvailable) { return ; } diff --git a/x-pack/plugins/security_solution/public/explore/users/pages/details/index.tsx b/x-pack/plugins/security_solution/public/explore/users/pages/details/index.tsx index 3612fd784d518..b751bc0e73b9f 100644 --- a/x-pack/plugins/security_solution/public/explore/users/pages/details/index.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/pages/details/index.tsx @@ -64,6 +64,7 @@ import { UsersType } from '../../store/model'; import { hasMlUserPermissions } from '../../../../../common/machine_learning/has_ml_user_permissions'; import { useMlCapabilities } from '../../../../common/components/ml/hooks/use_ml_capabilities'; import { LandingPageComponent } from '../../../../common/components/landing_page'; +import { useHasSecurityCapability } from '../../../../helper_hooks'; const QUERY_ID = 'UsersDetailsQueryId'; const ES_USER_FIELD = 'user.name'; @@ -73,7 +74,7 @@ const UsersDetailsComponent: React.FC = ({ usersDetailsPagePath, }) => { const dispatch = useDispatch(); - const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; + const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); const getTable = useMemo(() => dataTableSelectors.getTableByIdSelector(), []); const graphEventId = useShallowEqualSelector( (state) => (getTable(state, TableId.hostsPageEvents) ?? timelineDefaults).graphEventId @@ -241,7 +242,7 @@ const UsersDetailsComponent: React.FC = ({ navTabs={navTabsUsersDetails( detailName, hasMlUserPermissions(capabilities), - isPlatinumOrTrialLicense + hasEntityAnalyticsCapability )} /> diff --git a/x-pack/plugins/security_solution/public/explore/users/pages/navigation/user_risk_score_tab_body.tsx b/x-pack/plugins/security_solution/public/explore/users/pages/navigation/user_risk_score_tab_body.tsx index 0c1aa2abb9cd8..c1652a49036c2 100644 --- a/x-pack/plugins/security_solution/public/explore/users/pages/navigation/user_risk_score_tab_body.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/pages/navigation/user_risk_score_tab_body.tsx @@ -75,7 +75,6 @@ export const UserRiskScoreQueryTabBody = ({ loading, refetch, totalCount, - isAuthorized, } = useRiskScore({ filterQuery, pagination, @@ -96,10 +95,6 @@ export const UserRiskScoreQueryTabBody = ({ isDeprecated: isDeprecated && !loading, }; - if (!isAuthorized) { - return <>{'TODO: Add RiskScore Upsell'}; - } - if (riskScoreEngineStatus?.isUpdateAvailable) { return ; } diff --git a/x-pack/plugins/security_solution/public/explore/users/pages/users.tsx b/x-pack/plugins/security_solution/public/explore/users/pages/users.tsx index 7e91dcf859948..0666e34e6155e 100644 --- a/x-pack/plugins/security_solution/public/explore/users/pages/users.tsx +++ b/x-pack/plugins/security_solution/public/explore/users/pages/users.tsx @@ -52,6 +52,7 @@ import { hasMlUserPermissions } from '../../../../common/machine_learning/has_ml import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities'; import { LandingPageComponent } from '../../../common/components/landing_page'; import { userNameExistsFilter } from './details/helpers'; +import { useHasSecurityCapability } from '../../../helper_hooks'; const ID = 'UsersQueryId'; @@ -175,10 +176,10 @@ const UsersComponent = () => { ); const capabilities = useMlCapabilities(); - const isPlatinumOrTrialLicense = useMlCapabilities().isPlatinumOrTrialLicense; + const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics'); const navTabs = useMemo( - () => navTabsUsers(hasMlUserPermissions(capabilities), isPlatinumOrTrialLicense), - [capabilities, isPlatinumOrTrialLicense] + () => navTabsUsers(hasMlUserPermissions(capabilities), hasEntityAnalyticsCapability), + [capabilities, hasEntityAnalyticsCapability] ); return ( diff --git a/x-pack/plugins/security_solution/public/index.ts b/x-pack/plugins/security_solution/public/index.ts index 987189ec2d722..28b99af129b1d 100644 --- a/x-pack/plugins/security_solution/public/index.ts +++ b/x-pack/plugins/security_solution/public/index.ts @@ -12,13 +12,6 @@ import type { PluginSetup, PluginStart } from './types'; export type { TimelineModel } from './timelines/store/timeline/model'; export type { LinkItem } from './common/links'; -export type { - UpsellingService, - PageUpsellings, - SectionUpsellings, - UpsellingSectionId, -} from './common/lib/upsellings'; - export const plugin = (context: PluginInitializerContext): Plugin => new Plugin(context); export type { PluginSetup, PluginStart }; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/render_context_providers.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/render_context_providers.tsx index 972da1a436678..4ad30f3945ee6 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/render_context_providers.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/components/with_security_context/render_context_providers.tsx @@ -10,6 +10,7 @@ import React, { memo } from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; import type { Store } from 'redux'; import { NavigationProvider } from '@kbn/security-solution-navigation'; +import type { UpsellingService } from '@kbn/security-solution-upselling/service'; import { UpsellingProvider } from '../../../../../../../common/components/upselling_provider'; import { UserPrivilegesProvider } from '../../../../../../../common/components/user_privileges/user_privileges_context'; import type { SecuritySolutionQueryClient } from '../../../../../../../common/containers/query_client/query_client_provider'; @@ -18,7 +19,6 @@ import { SecuritySolutionStartDependenciesContext } from '../../../../../../../c import { CurrentLicense } from '../../../../../../../common/components/current_license'; import type { StartPlugins } from '../../../../../../../types'; import { useKibana } from '../../../../../../../common/lib/kibana'; -import type { UpsellingService } from '../../../../../../..'; export type RenderContextProvidersProps = PropsWithChildren<{ store: Store; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/types.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/types.ts index cc27971de63ab..1e5b4260b2148 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/types.ts @@ -6,8 +6,8 @@ */ import type { CoreStart } from '@kbn/core-lifecycle-browser'; +import type { UpsellingService } from '@kbn/security-solution-upselling/service'; import type { StartPlugins } from '../../../../../types'; -import type { UpsellingService } from '../../../../..'; export interface FleetUiExtensionGetterOptions { coreStart: CoreStart; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/policy_settings_form.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/policy_settings_form.test.tsx index c055ed3281a09..9197b029a8b59 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/policy_settings_form.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_settings_form/policy_settings_form.test.tsx @@ -12,7 +12,7 @@ import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint'; import type { PolicySettingsFormProps } from './policy_settings_form'; import { PolicySettingsForm } from './policy_settings_form'; import { FleetPackagePolicyGenerator } from '../../../../../../common/endpoint/data_generators/fleet_package_policy_generator'; -import type { UpsellingService } from '../../../../../common/lib/upsellings'; +import type { UpsellingService } from '@kbn/security-solution-upselling/service'; jest.mock('../../../../../common/hooks/use_license'); diff --git a/x-pack/plugins/security_solution/public/mocks.ts b/x-pack/plugins/security_solution/public/mocks.ts index d40cda3294802..a16c0bd4a0d65 100644 --- a/x-pack/plugins/security_solution/public/mocks.ts +++ b/x-pack/plugins/security_solution/public/mocks.ts @@ -6,9 +6,9 @@ */ import { BehaviorSubject } from 'rxjs'; +import { UpsellingService } from '@kbn/security-solution-upselling/service'; import type { BreadcrumbsNav } from './common/breadcrumbs'; import type { NavigationLink } from './common/links/types'; -import { UpsellingService } from './common/lib/upsellings'; import type { PluginStart, PluginSetup } from './types'; const setupMock = (): PluginSetup => ({ diff --git a/x-pack/plugins/security_solution/public/overview/links.ts b/x-pack/plugins/security_solution/public/overview/links.ts index a9a2bbe6c7640..a03447adbc732 100644 --- a/x-pack/plugins/security_solution/public/overview/links.ts +++ b/x-pack/plugins/security_solution/public/overview/links.ts @@ -88,7 +88,7 @@ export const entityAnalyticsLinks: LinkItem = { 'Entity analytics, anomalies, and threats to narrow down the monitoring surface area.', }), path: ENTITY_ANALYTICS_PATH, - capabilities: [`${SERVER_APP_ID}.show`], + capabilities: [`${SERVER_APP_ID}.entity-analytics`], isBeta: false, licenseType: 'platinum', globalSearchKeywords: [ENTITY_ANALYTICS], diff --git a/x-pack/plugins/security_solution/public/plugin_contract.ts b/x-pack/plugins/security_solution/public/plugin_contract.ts index 06d72e736b041..4d066a3981e04 100644 --- a/x-pack/plugins/security_solution/public/plugin_contract.ts +++ b/x-pack/plugins/security_solution/public/plugin_contract.ts @@ -7,7 +7,7 @@ import { BehaviorSubject } from 'rxjs'; import type { RouteProps } from 'react-router-dom'; -import { UpsellingService } from './common/lib/upsellings'; +import { UpsellingService } from '@kbn/security-solution-upselling/service'; import type { ContractStartServices, PluginSetup, PluginStart } from './types'; import type { AppLinksSwitcher } from './common/links'; import { navLinks$ } from './common/links/nav_links'; diff --git a/x-pack/plugins/security_solution/public/types.ts b/x-pack/plugins/security_solution/public/types.ts index ce754bdc29307..513a7d25bae2e 100644 --- a/x-pack/plugins/security_solution/public/types.ts +++ b/x-pack/plugins/security_solution/public/types.ts @@ -54,6 +54,7 @@ import type { RouteProps } from 'react-router-dom'; import type { DiscoverStart } from '@kbn/discover-plugin/public'; import type { NavigationPublicPluginStart } from '@kbn/navigation-plugin/public'; import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public'; +import type { UpsellingService } from '@kbn/security-solution-upselling/service'; import type { ResolverPluginSetup } from './resolver/types'; import type { Inspect } from '../common/search_strategy'; import type { Detections } from './detections'; @@ -74,7 +75,6 @@ import type { EntityAnalytics } from './entity_analytics'; import type { TelemetryClientStart } from './common/lib/telemetry'; import type { Dashboards } from './dashboards'; -import type { UpsellingService } from './common/lib/upsellings'; import type { BreadcrumbsNav } from './common/breadcrumbs/types'; import type { TopValuesPopoverService } from './app/components/top_values_popover/top_values_popover_service'; diff --git a/x-pack/plugins/security_solution/tsconfig.json b/x-pack/plugins/security_solution/tsconfig.json index fbcfe9df91c5a..0e5a3ec9752cc 100644 --- a/x-pack/plugins/security_solution/tsconfig.json +++ b/x-pack/plugins/security_solution/tsconfig.json @@ -163,6 +163,7 @@ "@kbn/cloud-chat-plugin", "@kbn/alerts-ui-shared", "@kbn/security-solution-navigation", + "@kbn/security-solution-upselling", "@kbn/discover-plugin", "@kbn/data-view-editor-plugin", "@kbn/navigation-plugin", diff --git a/x-pack/plugins/security_solution_ess/public/upselling/pages/translations.ts b/x-pack/plugins/security_solution_ess/public/upselling/pages/translations.ts deleted file mode 100644 index 5ec6b838fbd46..0000000000000 --- a/x-pack/plugins/security_solution_ess/public/upselling/pages/translations.ts +++ /dev/null @@ -1,34 +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 { i18n } from '@kbn/i18n'; - -export const PLATINUM = i18n.translate('xpack.securitySolutionEss.paywall.platinum', { - defaultMessage: 'Platinum', -}); - -export const UPGRADE_MESSAGE = i18n.translate('xpack.securitySolutionEss.paywall.upgradeMessage', { - defaultMessage: 'This feature is available with Platinum or higher subscription', -}); - -export const UPGRADE_BUTTON = i18n.translate('xpack.securitySolutionEss.paywall.upgradeButton', { - defaultMessage: 'Upgrade to Platinum', -}); - -export const ENTITY_ANALYTICS_LICENSE_DESC = i18n.translate( - 'xpack.securitySolutionEss.entityAnalytics.pageDesc', - { - defaultMessage: 'Detect threats from users and hosts within your network with Entity Analytics', - } -); - -export const ENTITY_ANALYTICS_TITLE = i18n.translate( - 'xpack.securitySolutionEss.navigation.entityAnalytics', - { - defaultMessage: 'Entity Analytics', - } -); diff --git a/x-pack/plugins/security_solution_ess/public/upselling/register_upsellings.tsx b/x-pack/plugins/security_solution_ess/public/upselling/register_upsellings.tsx index f750507d17bc8..930f8443369f8 100644 --- a/x-pack/plugins/security_solution_ess/public/upselling/register_upsellings.tsx +++ b/x-pack/plugins/security_solution_ess/public/upselling/register_upsellings.tsx @@ -13,14 +13,15 @@ import type { SectionUpsellings, UpsellingMessageId, UpsellingSectionId, -} from '@kbn/security-solution-plugin/public/common/lib/upsellings/types'; +} from '@kbn/security-solution-upselling/service/types'; import type { ILicense, LicenseType } from '@kbn/licensing-plugin/public'; -import { lazy } from 'react'; -import type React from 'react'; -import { UPGRADE_INVESTIGATION_GUIDE } from './messages/investigation_guide_upselling'; +import React, { lazy } from 'react'; +import { UPGRADE_INVESTIGATION_GUIDE } from '@kbn/security-solution-upselling/messages'; import type { Services } from '../common/services'; import { withServicesProvider } from '../common/services'; -const EntityAnalyticsUpsellingLazy = lazy(() => import('./pages/entity_analytics_upselling')); +const EntityAnalyticsUpsellingLazy = lazy( + () => import('@kbn/security-solution-upselling/pages/entity_analytics') +); interface UpsellingsConfig { minimumLicenseRequired: LicenseType; @@ -42,7 +43,7 @@ export const registerUpsellings = ( license: ILicense, services: Services ) => { - const upsellingPagesToRegister = upsellingPages.reduce( + const upsellingPagesToRegister = upsellingPages(services).reduce( (pageUpsellings, { pageName, minimumLicenseRequired, component }) => { if (!license.hasAtLeast(minimumLicenseRequired)) { pageUpsellings[pageName] = withServicesProvider(component, services); @@ -78,12 +79,19 @@ export const registerUpsellings = ( }; // Upsellings for entire pages, linked to a SecurityPageName -export const upsellingPages: UpsellingPages = [ +export const upsellingPages: (services: Services) => UpsellingPages = (services) => [ // It is highly advisable to make use of lazy loaded components to minimize bundle size. { pageName: SecurityPageName.entityAnalytics, minimumLicenseRequired: 'platinum', - component: EntityAnalyticsUpsellingLazy, + component: () => ( + + ), }, ]; @@ -97,6 +105,6 @@ export const upsellingMessages: UpsellingMessages = [ { id: 'investigation_guide', minimumLicenseRequired: 'platinum', - message: UPGRADE_INVESTIGATION_GUIDE('platinum'), + message: UPGRADE_INVESTIGATION_GUIDE('Platinum'), }, ]; diff --git a/x-pack/plugins/security_solution_ess/tsconfig.json b/x-pack/plugins/security_solution_ess/tsconfig.json index 08c7e49b6a166..57c520bf896c1 100644 --- a/x-pack/plugins/security_solution_ess/tsconfig.json +++ b/x-pack/plugins/security_solution_ess/tsconfig.json @@ -21,6 +21,6 @@ "@kbn/kibana-react-plugin", "@kbn/security-solution-navigation", "@kbn/licensing-plugin", - "@kbn/shared-ux-page-kibana-template", + "@kbn/security-solution-upselling", ] } diff --git a/x-pack/plugins/security_solution_serverless/public/plugin.ts b/x-pack/plugins/security_solution_serverless/public/plugin.ts index 946e84c895f74..47b5891b87cb4 100644 --- a/x-pack/plugins/security_solution_serverless/public/plugin.ts +++ b/x-pack/plugins/security_solution_serverless/public/plugin.ts @@ -54,7 +54,8 @@ export class SecuritySolutionServerlessPlugin const services = createServices(core, startDeps); - registerUpsellings(securitySolution.getUpselling(), this.config.productTypes); + registerUpsellings(securitySolution.getUpselling(), this.config.productTypes, services); + securitySolution.setGetStartedPage(getSecurityGetStartedComponent(services, productTypes)); configureNavigation(services, this.config); diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/lazy_upselling.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/lazy_upselling.tsx index a3640195544e0..e24434ea0b9e8 100644 --- a/x-pack/plugins/security_solution_serverless/public/upselling/lazy_upselling.tsx +++ b/x-pack/plugins/security_solution_serverless/public/upselling/lazy_upselling.tsx @@ -26,3 +26,7 @@ export const ThreatIntelligencePaywallLazy = withSuspenseUpsell( export const OsqueryResponseActionsUpsellingSectionLazy = withSuspenseUpsell( lazy(() => import('./pages/osquery_automated_response_actions')) ); + +export const EntityAnalyticsUpsellingLazy = withSuspenseUpsell( + lazy(() => import('@kbn/security-solution-upselling/pages/entity_analytics')) +); diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/messages/investigation_guide_upselling.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/messages/investigation_guide_upselling.tsx deleted file mode 100644 index 591e979fbfbbe..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/upselling/messages/investigation_guide_upselling.tsx +++ /dev/null @@ -1,24 +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 type { AppFeatureKey } from '@kbn/security-solution-plugin/common'; -import { i18n } from '@kbn/i18n'; -import { getProductTypeByPLI } from '../hooks/use_product_type_by_pli'; - -export const UPGRADE_INVESTIGATION_GUIDE = (productTypeRequired: string) => - i18n.translate('xpack.securitySolutionServerless.markdown.insight.upsell', { - defaultMessage: - 'Upgrade to {productTypeRequired} to make use of insights in investigation guides', - values: { - productTypeRequired, - }, - }); - -export const investigationGuideUpselling = (requiredPLI: AppFeatureKey): string => { - const productTypeRequired = getProductTypeByPLI(requiredPLI); - return productTypeRequired ? UPGRADE_INVESTIGATION_GUIDE(productTypeRequired) : ''; -}; diff --git a/x-pack/plugins/security_solution_serverless/public/upselling/pages/generic_upselling_page.tsx b/x-pack/plugins/security_solution_serverless/public/upselling/pages/generic_upselling_page.tsx deleted file mode 100644 index cf0d93e82dec7..0000000000000 --- a/x-pack/plugins/security_solution_serverless/public/upselling/pages/generic_upselling_page.tsx +++ /dev/null @@ -1,43 +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 React from 'react'; -import { EuiEmptyPrompt, EuiLink } from '@elastic/eui'; -import type { AppFeatureKey } from '@kbn/security-solution-plugin/common'; -import { getProductTypeByPLI } from '../hooks/use_product_type_by_pli'; - -export const GenericUpsellingPage: React.FC<{ requiredPLI: AppFeatureKey }> = React.memo( - function GenericUpsellingPage({ requiredPLI }) { - const productTypeRequired = getProductTypeByPLI(requiredPLI); - - return ( - {'This is a testing component for a Serverless upselling prompt.'}} - body={ - <> - {'Get'} {productTypeRequired} {'to enable this feature'} -
-
-