diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/index.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/index.tsx index e631ff53f6a51..e05b225226305 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/index.tsx @@ -5,14 +5,13 @@ * 2.0. */ -import React, { memo, useMemo } from 'react'; +import React, { memo } from 'react'; import { useSourcererDataView } from '../../containers/sourcerer'; -import { getOnboardingComponent } from './onboarding'; +import { Onboarding } from './onboarding'; export const LandingPageComponent = memo(() => { const { indicesExist } = useSourcererDataView(); - const OnBoarding = useMemo(() => getOnboardingComponent(), []); - return ; + return ; }); LandingPageComponent.displayName = 'LandingPageComponent'; diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/__mocks__/index.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/__mocks__/index.tsx index dc8523ef0a772..4491d62637144 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/__mocks__/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/__mocks__/index.tsx @@ -6,6 +6,6 @@ */ import React from 'react'; -export const getOnboardingComponent = jest +export const Onboarding = jest .fn() - .mockReturnValue(() =>
); + .mockReturnValue(
); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/index.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/index.tsx index 819f8f9167660..45001a2d9ce0d 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/index.tsx @@ -4,12 +4,15 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ +import React, { lazy, Suspense } from 'react'; +import { EuiLoadingLogo } from '@elastic/eui'; -import React from 'react'; +const OnboardingLazy = lazy(() => import('./onboarding_with_settings')); -import { Onboarding } from './lazy'; +const centerLogoStyle = { display: 'flex', margin: 'auto' }; -export const getOnboardingComponent = (): React.ComponentType<{ indicesExist?: boolean }> => - function OnBoardingComponent({ indicesExist }: { indicesExist?: boolean }) { - return ; - }; +export const Onboarding = ({ indicesExist }: { indicesExist?: boolean }) => ( + }> + + +); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/lazy.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/lazy.tsx deleted file mode 100644 index 45001a2d9ce0d..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/lazy.tsx +++ /dev/null @@ -1,18 +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, { lazy, Suspense } from 'react'; -import { EuiLoadingLogo } from '@elastic/eui'; - -const OnboardingLazy = lazy(() => import('./onboarding_with_settings')); - -const centerLogoStyle = { display: 'flex', margin: 'auto' }; - -export const Onboarding = ({ indicesExist }: { indicesExist?: boolean }) => ( - }> - - -); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.test.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.test.tsx index 5b86cbeaf1947..f42c735addc28 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/onboarding.test.tsx @@ -18,6 +18,13 @@ import { ProductLine, ProductTier } from './configs'; jest.mock('./toggle_panel'); jest.mock('./hooks/use_project_features_url'); jest.mock('./hooks/use_projects_url'); +jest.mock('../../../lib/kibana', () => { + const original = jest.requireActual('../../../lib/kibana'); + return { + ...original, + useAppUrl: jest.fn().mockReturnValue({ getAppUrl: jest.fn().mockReturnValue('mock url') }), + }; +}); jest.mock('@elastic/eui', () => { const original = jest.requireActual('@elastic/eui'); return { diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/current_plan.styles.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/current_plan.styles.ts new file mode 100644 index 0000000000000..560d4456a63d4 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/current_plan.styles.ts @@ -0,0 +1,40 @@ +/* + * 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 { useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/css'; +import { useMemo } from 'react'; + +export const useCurrentPlanStyles = () => { + const { euiTheme } = useEuiTheme(); + const styles = useMemo(() => { + return { + currentPlanWrapperStyles: css({ + backgroundColor: euiTheme.colors.lightestShade, + borderRadius: '56px', + padding: `${euiTheme.size.xs} ${euiTheme.size.s} ${euiTheme.size.xs} ${euiTheme.size.m}`, + height: euiTheme.size.xl, + }), + currentPlanTextStyles: css({ + fontSize: euiTheme.size.m, + fontWeight: euiTheme.font.weight.bold, + paddingRight: euiTheme.size.xs, + }), + projectFeaturesUrlStyles: css({ + marginLeft: euiTheme.size.xs, + }), + }; + }, [ + euiTheme.colors.lightestShade, + euiTheme.font.weight.bold, + euiTheme.size.m, + euiTheme.size.s, + euiTheme.size.xl, + euiTheme.size.xs, + ]); + + return styles; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/current_plan_badge.styles.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/current_plan_badge.styles.ts new file mode 100644 index 0000000000000..6fee540af502e --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/current_plan_badge.styles.ts @@ -0,0 +1,26 @@ +/* + * 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 { useEuiTheme } from '@elastic/eui'; +import { css } from '@emotion/css'; +import { useMemo } from 'react'; + +export const useCurrentPlanBadgeStyles = () => { + const { euiTheme } = useEuiTheme(); + const styles = useMemo(() => { + return { + wrapperStyles: css({ + fontSize: euiTheme.size.m, + lineHeight: euiTheme.size.m, + }), + textStyles: css({ + textTransform: 'capitalize', + }), + }; + }, [euiTheme.size.m]); + + return styles; +}; diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/welcome_header.styles.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/welcome_header.styles.ts index ef50739d4f7e9..78b7c5bc9110d 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/welcome_header.styles.ts +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/styles/welcome_header.styles.ts @@ -45,34 +45,16 @@ export const useWelcomeHeaderStyles = () => { headerContentStyles: css({ width: `${CONTENT_WIDTH / 2}px`, }), - currentPlanWrapperStyles: css({ - backgroundColor: euiTheme.colors.lightestShade, - borderRadius: '56px', - padding: `${euiTheme.size.xs} ${euiTheme.size.s} ${euiTheme.size.xs} ${euiTheme.size.m}`, - height: euiTheme.size.xl, - }), - currentPlanTextStyles: css({ - fontSize: euiTheme.size.m, - fontWeight: euiTheme.font.weight.bold, - paddingRight: euiTheme.size.xs, - }), - projectFeaturesUrlStyles: css({ - paddingLeft: euiTheme.size.xs, - }), }; }, [ euiTheme.base, euiTheme.colors.darkShade, - euiTheme.colors.lightestShade, euiTheme.colors.subduedText, euiTheme.colors.title, euiTheme.font.weight.bold, euiTheme.font.weight.regular, euiTheme.size.l, - euiTheme.size.m, euiTheme.size.s, - euiTheme.size.xl, - euiTheme.size.xs, ]); return welcomeHeaderStyles; }; diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/translations.ts b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/translations.ts index 721c5300e9adc..81342be4c46dd 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/translations.ts +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/translations.ts @@ -34,6 +34,13 @@ export const CURRENT_PLAN_LABEL = i18n.translate( } ); +export const CURRENT_TIER_LABEL = i18n.translate( + 'xpack.securitySolution.onboarding.currentTier.label', + { + defaultMessage: 'Current tier:', + } +); + export const PROGRESS_TRACKER_LABEL = i18n.translate( 'xpack.securitySolution.onboarding.progressTracker.progressBar.label', { defaultMessage: 'PROGRESS' } diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/current_plan.test.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/current_plan.test.tsx new file mode 100644 index 0000000000000..dcb7315a5deda --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/current_plan.test.tsx @@ -0,0 +1,95 @@ +/* + * 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 { useLicense } from '../../../../hooks/use_license'; +import { useAppUrl } from '../../../../lib/kibana'; +import { CurrentPlan } from './current_plan'; +import type { LicenseService } from '../../../../../../common/license'; + +jest.mock('../../../../hooks/use_license'); +jest.mock('../../../../lib/kibana'); + +const mockUseLicense = useLicense as jest.MockedFunction; +const mockUseAppUrl = useAppUrl as jest.MockedFunction; +const mockManagementUrl = 'https://management'; +const mockProjectFeaturesUrl = 'https://projectFeatures'; + +describe('CurrentPlan', () => { + beforeEach(() => { + mockUseAppUrl.mockReturnValue({ + getAppUrl: jest.fn(), + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('renders nothing if license is enterprise', () => { + mockUseLicense.mockReturnValue({ + getLicenseType: jest.fn().mockReturnValue('enterprise'), + isEnterprise: jest.fn().mockReturnValue(true), + } as unknown as LicenseService); + + const { container } = render( + + ); + + expect(container.firstChild).toBeNull(); + }); + + test('renders nothing if license type is enterprise', () => { + mockUseLicense.mockReturnValue({ + getLicenseType: jest.fn().mockReturnValue('enterprise'), + isEnterprise: jest.fn().mockReturnValue(true), + } as unknown as LicenseService); + + const { container } = render( + + ); + + expect(container.firstChild).toBeNull(); + }); + + test('renders component with valid productTier and projectFeaturesUrl', () => { + mockUseLicense.mockReturnValue({ + getLicenseType: jest.fn().mockReturnValue('basic'), + isEnterprise: jest.fn().mockReturnValue(false), + } as unknown as LicenseService); + + mockUseAppUrl.mockReturnValue({ + getAppUrl: jest.fn().mockReturnValue(mockManagementUrl), + }); + + const { getByTestId } = render( + + ); + + expect(getByTestId('currentPlanLabel')).toHaveTextContent('Current tier:'); + expect(getByTestId('currentPlanLink').getAttribute('href')).toEqual(mockProjectFeaturesUrl); + }); + + test('renders component without productTier and projectFeaturesUrl', () => { + mockUseLicense.mockReturnValue({ + getLicenseType: jest.fn().mockReturnValue('basic'), + isEnterprise: jest.fn().mockReturnValue(false), + } as unknown as LicenseService); + + mockUseAppUrl.mockReturnValue({ + getAppUrl: jest.fn().mockReturnValue(mockManagementUrl), + }); + + const { getByTestId } = render( + + ); + + expect(getByTestId('currentPlanLabel')).toHaveTextContent('Current plan:'); + expect(getByTestId('currentPlanLink').getAttribute('href')).toEqual(mockManagementUrl); + }); +}); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/current_plan.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/current_plan.tsx new file mode 100644 index 0000000000000..e61ae0ab5b7cc --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/current_plan.tsx @@ -0,0 +1,80 @@ +/* + * 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 { EuiButtonIcon, EuiSpacer } from '@elastic/eui'; +import classnames from 'classnames'; +import { isEmpty } from 'lodash/fp'; +import React from 'react'; +import { useLicense } from '../../../../hooks/use_license'; +import { useAppUrl } from '../../../../lib/kibana'; +import { useCurrentPlanStyles } from '../styles/current_plan.styles'; +import { CURRENT_PLAN_LABEL, CURRENT_TIER_LABEL } from '../translations'; +import { CurrentPlanBadge } from './current_plan_badge'; + +const CurrentPlanComponent = ({ + productTier, + projectFeaturesUrl, +}: { + productTier: string | undefined; + projectFeaturesUrl: string | undefined; +}) => { + const licenseService = useLicense(); + const licenseType = licenseService.getLicenseType(); + const isTrial = licenseType === 'trial'; + const isEnterprise = licenseService.isEnterprise() && !isTrial; + + const { getAppUrl } = useAppUrl(); + const licenseManagementUrl = getAppUrl({ + appId: 'management', + path: 'stack/license_management/home', + }); + + const currentPlan = productTier ? productTier : !isEmpty(licenseType) ? licenseType : undefined; + const label = productTier ? CURRENT_TIER_LABEL : CURRENT_PLAN_LABEL; + const link = productTier ? projectFeaturesUrl : licenseManagementUrl; + + const { currentPlanWrapperStyles, currentPlanTextStyles, projectFeaturesUrlStyles } = + useCurrentPlanStyles(); + const currentPlanWrapperClassNames = classnames( + 'eui-displayInlineBlock', + currentPlanWrapperStyles + ); + const projectFeaturesUrlClassNames = classnames('eui-alignMiddle', projectFeaturesUrlStyles); + + if (isEnterprise && productTier == null) { + return null; + } + + return ( + <> + +
+
+ + {label} + + + + {link && ( + + )} +
+
+ + ); +}; + +export const CurrentPlan = React.memo(CurrentPlanComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/product_tier_badge.test.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/current_plan_badge.test.tsx similarity index 66% rename from x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/product_tier_badge.test.tsx rename to x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/current_plan_badge.test.tsx index ed15685f8cf59..cc67c68ad5525 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/product_tier_badge.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/current_plan_badge.test.tsx @@ -6,23 +6,23 @@ */ import React from 'react'; import { render } from '@testing-library/react'; -import { ProductTierBadge } from './product_tier_badge'; +import { CurrentPlanBadge } from './current_plan_badge'; import { ProductTier } from '../configs'; -describe('ProductTierBadge', () => { +describe('CurrentPlanBadge', () => { it('renders nothing when productTier is undefined', () => { - const { container } = render(); + const { container } = render(); expect(container.firstChild).toBeNull(); }); - it('renders the badge with the correct text when productTier is essential', () => { - const { getByTestId } = render(); + it('renders the badge with the correct text when ProductTier is essential', () => { + const { getByTestId } = render(); const badge = getByTestId('product-tier-badge'); expect(badge).toHaveTextContent('Essential'); }); - it('renders the badge with the correct text when productTier is complete', () => { - const { getByTestId } = render(); + it('renders the badge with the correct text when ProductTier is complete', () => { + const { getByTestId } = render(); const badge = getByTestId('product-tier-badge'); expect(badge).toHaveTextContent('Complete'); }); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/current_plan_badge.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/current_plan_badge.tsx new file mode 100644 index 0000000000000..6586b0840c6ef --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/current_plan_badge.tsx @@ -0,0 +1,31 @@ +/* + * 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 { EuiBadge } from '@elastic/eui'; + +import classnames from 'classnames'; +import { PRODUCT_TIER_ESSENTIAL, PRODUCT_TIER_COMPLETE } from './translations'; +import { ProductTier } from '../configs'; +import { useCurrentPlanBadgeStyles } from '../styles/current_plan_badge.styles'; + +const PRODUCT_TIER_TRANSLATES: { [key: string]: string } = { + [ProductTier.essentials]: PRODUCT_TIER_ESSENTIAL, + [ProductTier.complete]: PRODUCT_TIER_COMPLETE, +}; + +const CurrentPlanBadgeComponent = ({ currentPlan }: { currentPlan: string | undefined }) => { + const { wrapperStyles, textStyles } = useCurrentPlanBadgeStyles(); + const wrapperClassNames = classnames('eui-alignMiddle', wrapperStyles); + return currentPlan ? ( + + {PRODUCT_TIER_TRANSLATES[currentPlan] ?? currentPlan} + + ) : null; +}; + +export const CurrentPlanBadge = React.memo(CurrentPlanBadgeComponent); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/index.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/index.tsx index e33860d5408ee..b03829c29c8cc 100644 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/index.tsx +++ b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/index.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle } from '@elastic/eui'; import React from 'react'; import classnames from 'classnames'; @@ -13,35 +13,27 @@ import { GET_STARTED_PAGE_TITLE, GET_STARTED_PAGE_SUBTITLE, GET_STARTED_PAGE_DESCRIPTION, - CURRENT_PLAN_LABEL, } from '../translations'; -import { ProductTierBadge } from './product_tier_badge'; import { useWelcomeHeaderStyles } from '../styles/welcome_header.styles'; import type { ProductTier } from '../configs'; import { useProjectFeaturesUrl } from '../hooks/use_project_features_url'; import { useCurrentUser } from '../../../../lib/kibana'; +import { CurrentPlan } from './current_plan'; const WelcomeHeaderComponent: React.FC<{ productTier?: ProductTier }> = ({ productTier }) => { const userName = useCurrentUser(); const projectFeaturesUrl = useProjectFeaturesUrl(); + const { headerContentStyles, headerStyles, headerTitleStyles, headerSubtitleStyles, headerDescriptionStyles, - currentPlanWrapperStyles, - currentPlanTextStyles, - projectFeaturesUrlStyles, } = useWelcomeHeaderStyles(); const headerSubtitleClassNames = classnames('eui-displayBlock', headerSubtitleStyles); const headerDescriptionClassNames = classnames('eui-displayBlock', headerDescriptionStyles); - const currentPlanWrapperClassNames = classnames( - 'eui-displayInlineBlock', - currentPlanWrapperStyles - ); - const projectFeaturesUrlClassNames = classnames('eui-alignMiddle', projectFeaturesUrlStyles); return ( @@ -55,27 +47,7 @@ const WelcomeHeaderComponent: React.FC<{ productTier?: ProductTier }> = ({ produ {GET_STARTED_PAGE_SUBTITLE} {GET_STARTED_PAGE_DESCRIPTION} - {productTier && projectFeaturesUrl && ( - <> - -
-
- {CURRENT_PLAN_LABEL} - - - -
-
- - )} +
); diff --git a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/product_tier_badge.tsx b/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/product_tier_badge.tsx deleted file mode 100644 index a0df046d89406..0000000000000 --- a/x-pack/plugins/security_solution/public/common/components/landing_page/onboarding/welcome_header/product_tier_badge.tsx +++ /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 React from 'react'; -import { EuiBadge, useEuiTheme } from '@elastic/eui'; -import { css } from '@emotion/css'; -import { PRODUCT_TIER_ESSENTIAL, PRODUCT_TIER_COMPLETE } from './translations'; -import { ProductTier } from '../configs'; - -const ProductTierBadgeComponent = ({ productTier }: { productTier: ProductTier | undefined }) => { - const { euiTheme } = useEuiTheme(); - return productTier ? ( - - - {productTier === ProductTier.essentials && PRODUCT_TIER_ESSENTIAL} - {productTier === ProductTier.complete && PRODUCT_TIER_COMPLETE} - - - ) : null; -}; - -export const ProductTierBadge = React.memo(ProductTierBadgeComponent);