mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[SecuritySolution] Show upselling component for entity risk score tab (#183517)
## Summary ### Before Serverless: The tab is hidden  ESS: We displayed the unauthorized banner  ### After Display the upsell banner  ## How to test it * For every license and tier * Go to host/user page inside, explore menu item * Verify what is displayed inside the risk score tab * Run security serverless with "Security Analytics Essentials" tier * You should see the upsell component * Run security serverless with "Security Analytics Complete" tier * You should NOT see the upsell component * Run kibana ESS with platinum license * You should NOT see the upsell component * Run kibana ESS with basic license * You should see the upsell component ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
This commit is contained in:
parent
8189e1a753
commit
9d70accb2e
32 changed files with 368 additions and 306 deletions
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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, { Suspense } from 'react';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
|
||||
export const withSuspenseUpsell = <T extends object = {}>(
|
||||
Component: React.ComponentType<T>
|
||||
): React.FC<T> =>
|
||||
function WithSuspenseUpsell(props) {
|
||||
return (
|
||||
<Suspense fallback={<EuiLoadingSpinner size="s" />}>
|
||||
<Component {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import EntityAnalyticsUpsellingComponent from './entity_analytics';
|
||||
import { EntityAnalyticsUpsellingPage } from './entity_analytics';
|
||||
|
||||
jest.mock('@kbn/security-solution-navigation', () => {
|
||||
const original = jest.requireActual('@kbn/security-solution-navigation');
|
||||
|
@ -21,54 +21,33 @@ jest.mock('@kbn/security-solution-navigation', () => {
|
|||
describe('EntityAnalyticsUpselling', () => {
|
||||
it('should render', () => {
|
||||
const { getByTestId } = render(
|
||||
<EntityAnalyticsUpsellingComponent requiredLicense="TEST LICENSE" />
|
||||
<EntityAnalyticsUpsellingPage
|
||||
upgradeMessage="test upgrade message"
|
||||
upgradeToLabel="TEST LICENSE"
|
||||
/>
|
||||
);
|
||||
expect(getByTestId('paywallCardDescription')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should throw exception when requiredLicense and requiredProduct are not provided', () => {
|
||||
expect(() => render(<EntityAnalyticsUpsellingComponent />)).toThrow();
|
||||
});
|
||||
|
||||
it('should show product message when requiredProduct is provided', () => {
|
||||
const { getByTestId } = render(
|
||||
<EntityAnalyticsUpsellingComponent
|
||||
requiredProduct="TEST PRODUCT"
|
||||
requiredLicense="TEST LICENSE"
|
||||
/>
|
||||
);
|
||||
|
||||
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', () => {
|
||||
it('should show upgrade label badge', () => {
|
||||
const { getByText } = render(
|
||||
<EntityAnalyticsUpsellingComponent
|
||||
requiredProduct="TEST PRODUCT"
|
||||
requiredLicense="TEST LICENSE"
|
||||
<EntityAnalyticsUpsellingPage
|
||||
upgradeToLabel="TEST PRODUCT"
|
||||
upgradeMessage="test upgrade message"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getByText('TEST PRODUCT')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should show license message when requiredLicense is provided', () => {
|
||||
it('should show license message', () => {
|
||||
const { getByTestId } = render(
|
||||
<EntityAnalyticsUpsellingComponent requiredLicense="TEST LICENSE" />
|
||||
<EntityAnalyticsUpsellingPage
|
||||
upgradeToLabel="TEST PRODUCT"
|
||||
upgradeMessage="test upgrade message"
|
||||
/>
|
||||
);
|
||||
|
||||
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(
|
||||
<EntityAnalyticsUpsellingComponent requiredLicense="TEST LICENSE" />
|
||||
);
|
||||
|
||||
expect(getByText('TEST LICENSE')).toBeInTheDocument();
|
||||
expect(getByTestId('paywallCardDescription')).toHaveTextContent('test upgrade message');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,25 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import {
|
||||
EuiCard,
|
||||
EuiIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiButton,
|
||||
EuiTextColor,
|
||||
EuiImage,
|
||||
EuiPageHeader,
|
||||
EuiSpacer,
|
||||
} from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiImage, EuiPageHeader, EuiSpacer } from '@elastic/eui';
|
||||
|
||||
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 '../images/entity_paywall.png';
|
||||
import { EntityAnalyticsUpsellingSection } from '../sections/entity_analytics';
|
||||
|
||||
const PaywallDiv = styled.div`
|
||||
max-width: 75%;
|
||||
|
@ -37,81 +27,27 @@ const PaywallDiv = styled.div`
|
|||
padding: 0 15%;
|
||||
}
|
||||
`;
|
||||
const StyledEuiCard = styled(EuiCard)`
|
||||
span.euiTitle {
|
||||
max-width: 540px;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`;
|
||||
|
||||
const EntityAnalyticsUpsellingComponent = ({
|
||||
requiredLicense,
|
||||
requiredProduct,
|
||||
subscriptionUrl,
|
||||
upgradeMessage,
|
||||
upgradeHref,
|
||||
upgradeToLabel,
|
||||
}: {
|
||||
requiredLicense?: string;
|
||||
requiredProduct?: string;
|
||||
subscriptionUrl?: string;
|
||||
upgradeMessage: string;
|
||||
upgradeToLabel: string;
|
||||
upgradeHref?: 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 (
|
||||
<KibanaPageTemplate restrictWidth={false} contentBorder={false} grow={true}>
|
||||
<KibanaPageTemplate.Section>
|
||||
<EuiPageHeader pageTitle={i18n.ENTITY_ANALYTICS_TITLE} />
|
||||
<EuiSpacer size="xl" />
|
||||
<PaywallDiv>
|
||||
<StyledEuiCard
|
||||
betaBadgeProps={{ label: requiredProductOrLicense }}
|
||||
icon={<EuiIcon size="xl" type="lock" />}
|
||||
display="subdued"
|
||||
title={
|
||||
<h3>
|
||||
<strong>{i18n.ENTITY_ANALYTICS_LICENSE_DESC}</strong>
|
||||
</h3>
|
||||
}
|
||||
description={false}
|
||||
paddingSize="xl"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
data-test-subj="paywallCardDescription"
|
||||
className="paywallCardDescription"
|
||||
direction="column"
|
||||
gutterSize="none"
|
||||
>
|
||||
<EuiText>
|
||||
<EuiFlexItem>
|
||||
<p>
|
||||
<EuiTextColor color="subdued">{upgradeMessage}</EuiTextColor>
|
||||
</p>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{subscriptionUrl && (
|
||||
<div>
|
||||
<EuiButton onClick={goToSubscription} fill>
|
||||
{i18n.UPGRADE_BUTTON(requiredProductOrLicense)}
|
||||
</EuiButton>
|
||||
</div>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiText>
|
||||
</EuiFlexGroup>
|
||||
</StyledEuiCard>
|
||||
<EntityAnalyticsUpsellingSection
|
||||
upgradeMessage={upgradeMessage}
|
||||
upgradeHref={upgradeHref}
|
||||
upgradeToLabel={upgradeToLabel}
|
||||
/>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiImage alt={upgradeMessage} src={paywallPng} size="fullWidth" />
|
||||
|
@ -125,5 +61,4 @@ const EntityAnalyticsUpsellingComponent = ({
|
|||
|
||||
EntityAnalyticsUpsellingComponent.displayName = 'EntityAnalyticsUpsellingComponent';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default React.memo(EntityAnalyticsUpsellingComponent);
|
||||
export const EntityAnalyticsUpsellingPage = React.memo(EntityAnalyticsUpsellingComponent);
|
||||
|
|
|
@ -7,23 +7,6 @@
|
|||
|
||||
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}',
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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, { memo, useCallback } from 'react';
|
||||
import {
|
||||
EuiCard,
|
||||
EuiIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiText,
|
||||
EuiTextColor,
|
||||
EuiButton,
|
||||
} from '@elastic/eui';
|
||||
import styled from '@emotion/styled';
|
||||
|
||||
import { useNavigation } from '@kbn/security-solution-navigation';
|
||||
import * as i18n from '../pages/translations';
|
||||
|
||||
const StyledEuiCard = styled(EuiCard)`
|
||||
span.euiTitle {
|
||||
max-width: 540px;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
}
|
||||
`;
|
||||
|
||||
export const EntityAnalyticsUpsellingSection = memo(
|
||||
({
|
||||
upgradeMessage,
|
||||
upgradeHref,
|
||||
upgradeToLabel,
|
||||
}: {
|
||||
upgradeMessage: string;
|
||||
upgradeToLabel: string;
|
||||
upgradeHref?: string;
|
||||
}) => {
|
||||
const { navigateTo } = useNavigation();
|
||||
const goToSubscription = useCallback(() => {
|
||||
navigateTo({ url: upgradeHref });
|
||||
}, [navigateTo, upgradeHref]);
|
||||
|
||||
return (
|
||||
<StyledEuiCard
|
||||
betaBadgeProps={{ label: upgradeToLabel }}
|
||||
icon={<EuiIcon size="xl" type="lock" />}
|
||||
display="subdued"
|
||||
title={
|
||||
<h3>
|
||||
<strong>{i18n.ENTITY_ANALYTICS_LICENSE_DESC}</strong>
|
||||
</h3>
|
||||
}
|
||||
description={false}
|
||||
paddingSize="xl"
|
||||
>
|
||||
<EuiFlexGroup
|
||||
data-test-subj="paywallCardDescription"
|
||||
className="paywallCardDescription"
|
||||
direction="column"
|
||||
gutterSize="none"
|
||||
>
|
||||
<EuiText>
|
||||
<EuiFlexItem>
|
||||
<p>
|
||||
<EuiTextColor color="subdued">{upgradeMessage}</EuiTextColor>
|
||||
</p>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
{upgradeHref && (
|
||||
<div>
|
||||
{/* eslint-disable-next-line @elastic/eui/href-or-on-click*/}
|
||||
<EuiButton href={upgradeHref} onClick={goToSubscription} fill>
|
||||
{i18n.UPGRADE_BUTTON(upgradeToLabel)}
|
||||
</EuiButton>
|
||||
</div>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiText>
|
||||
</EuiFlexGroup>
|
||||
</StyledEuiCard>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
EntityAnalyticsUpsellingSection.displayName = 'EntityAnalyticsUpsellingSection';
|
|
@ -41,14 +41,12 @@ const hostName = 'siem-window';
|
|||
|
||||
describe('Table Navigation', () => {
|
||||
const mockHasMlUserPermissions = true;
|
||||
const mockRiskyHostEnabled = true;
|
||||
mockUseRouteSpy.mockReturnValue([{ tabName: HostsTableType.authentications }]);
|
||||
|
||||
const mockProps: TabNavigationProps = {
|
||||
navTabs: navTabsHostDetails({
|
||||
hostName,
|
||||
hasMlUserPermissions: mockHasMlUserPermissions,
|
||||
isRiskyHostsEnabled: mockRiskyHostEnabled,
|
||||
}),
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
|||
import React, { useCallback, useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useUpsellingComponent } from '../../../common/hooks/use_upselling';
|
||||
import { RISKY_HOSTS_DASHBOARD_TITLE, RISKY_USERS_DASHBOARD_TITLE } from '../risk_score/constants';
|
||||
import { EnableRiskScore } from '../enable_risk_score';
|
||||
import { useDeepEqualSelector } from '../../../common/hooks/use_selector';
|
||||
|
@ -130,6 +131,11 @@ const RiskDetailsTabBodyComponent: React.FC<
|
|||
|
||||
const privileges = useMissingRiskEnginePrivileges();
|
||||
|
||||
const RiskScoreUpsell = useUpsellingComponent('entity_analytics_panel');
|
||||
if (RiskScoreUpsell) {
|
||||
return <RiskScoreUpsell />;
|
||||
}
|
||||
|
||||
if (!privileges.isLoading && !privileges.hasAllRequiredPrivileges) {
|
||||
return (
|
||||
<EuiPanel hasBorder>
|
||||
|
|
|
@ -26,6 +26,7 @@ import { useRiskEngineStatus } from '../api/hooks/use_risk_engine_status';
|
|||
import { RiskScoreUpdatePanel } from './risk_score_update_panel';
|
||||
import { useMissingRiskEnginePrivileges } from '../hooks/use_missing_risk_engine_privileges';
|
||||
import { RiskEnginePrivilegesCallOut } from './risk_engine_privileges_callout';
|
||||
import { useUpsellingComponent } from '../../common/hooks/use_upselling';
|
||||
|
||||
const UserRiskScoreTableManage = manageQuery(UserRiskScoreTable);
|
||||
|
||||
|
@ -97,6 +98,11 @@ export const UserRiskScoreQueryTabBody = ({
|
|||
isDeprecated: isDeprecated && !loading,
|
||||
};
|
||||
|
||||
const RiskScoreUpsell = useUpsellingComponent('entity_analytics_panel');
|
||||
if (RiskScoreUpsell) {
|
||||
return <RiskScoreUpsell />;
|
||||
}
|
||||
|
||||
if (!privileges.isLoading && !privileges.hasAllRequiredPrivileges) {
|
||||
return (
|
||||
<EuiPanel hasBorder>
|
||||
|
|
|
@ -78,7 +78,6 @@ import { EmptyPrompt } from '../../../../common/components/empty_prompt';
|
|||
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';
|
||||
import { useRefetchOverviewPageRiskScore } from '../../../../entity_analytics/api/hooks/use_refetch_overview_page_risk_score';
|
||||
|
||||
const ES_HOST_FIELD = 'host.name';
|
||||
|
@ -167,8 +166,6 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta
|
|||
dispatch(setHostDetailsTablesActivePageToZero());
|
||||
}, [dispatch, detailName]);
|
||||
|
||||
const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics');
|
||||
|
||||
const { hasKibanaREAD, hasIndexRead } = useAlertsPrivileges();
|
||||
const canReadAlerts = hasKibanaREAD && hasIndexRead;
|
||||
|
||||
|
@ -296,7 +293,6 @@ const HostDetailsComponent: React.FC<HostDetailsProps> = ({ detailName, hostDeta
|
|||
<TabNavigation
|
||||
navTabs={navTabsHostDetails({
|
||||
hasMlUserPermissions: hasMlUserPermissions(capabilities),
|
||||
isRiskyHostsEnabled: hasEntityAnalyticsCapability,
|
||||
hostName: detailName,
|
||||
isEnterprise: isEnterprisePlus,
|
||||
})}
|
||||
|
|
|
@ -14,37 +14,8 @@ describe('navTabsHostDetails', () => {
|
|||
test('it should skip anomalies tab if without mlUserPermission', () => {
|
||||
const tabs = navTabsHostDetails({
|
||||
hasMlUserPermissions: false,
|
||||
isRiskyHostsEnabled: false,
|
||||
hostName: mockHostName,
|
||||
});
|
||||
expect(tabs).toHaveProperty(HostsTableType.authentications);
|
||||
expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses);
|
||||
expect(tabs).not.toHaveProperty(HostsTableType.anomalies);
|
||||
expect(tabs).toHaveProperty(HostsTableType.events);
|
||||
expect(tabs).not.toHaveProperty(HostsTableType.risk);
|
||||
});
|
||||
|
||||
test('it should display anomalies tab if with mlUserPermission', () => {
|
||||
const tabs = navTabsHostDetails({
|
||||
hasMlUserPermissions: true,
|
||||
isRiskyHostsEnabled: false,
|
||||
hostName: mockHostName,
|
||||
});
|
||||
|
||||
expect(tabs).toHaveProperty(HostsTableType.authentications);
|
||||
expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses);
|
||||
expect(tabs).toHaveProperty(HostsTableType.anomalies);
|
||||
expect(tabs).toHaveProperty(HostsTableType.events);
|
||||
expect(tabs).not.toHaveProperty(HostsTableType.risk);
|
||||
});
|
||||
|
||||
test('it should display risky hosts tab if when risky hosts is enabled', () => {
|
||||
const tabs = navTabsHostDetails({
|
||||
hasMlUserPermissions: false,
|
||||
isRiskyHostsEnabled: true,
|
||||
hostName: mockHostName,
|
||||
});
|
||||
|
||||
expect(tabs).toHaveProperty(HostsTableType.authentications);
|
||||
expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses);
|
||||
expect(tabs).not.toHaveProperty(HostsTableType.anomalies);
|
||||
|
@ -52,10 +23,22 @@ describe('navTabsHostDetails', () => {
|
|||
expect(tabs).toHaveProperty(HostsTableType.risk);
|
||||
});
|
||||
|
||||
test('it should display anomalies tab if with mlUserPermission', () => {
|
||||
const tabs = navTabsHostDetails({
|
||||
hasMlUserPermissions: true,
|
||||
hostName: mockHostName,
|
||||
});
|
||||
|
||||
expect(tabs).toHaveProperty(HostsTableType.authentications);
|
||||
expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses);
|
||||
expect(tabs).toHaveProperty(HostsTableType.anomalies);
|
||||
expect(tabs).toHaveProperty(HostsTableType.events);
|
||||
expect(tabs).toHaveProperty(HostsTableType.risk);
|
||||
});
|
||||
|
||||
test('it should display sessions tab when users are on Enterprise and above license', () => {
|
||||
const tabs = navTabsHostDetails({
|
||||
hasMlUserPermissions: false,
|
||||
isRiskyHostsEnabled: true,
|
||||
hostName: mockHostName,
|
||||
isEnterprise: true,
|
||||
});
|
||||
|
@ -70,7 +53,6 @@ describe('navTabsHostDetails', () => {
|
|||
test('it should not display sessions tab when users are not on Enterprise and above license', () => {
|
||||
const tabs = navTabsHostDetails({
|
||||
hasMlUserPermissions: false,
|
||||
isRiskyHostsEnabled: true,
|
||||
hostName: mockHostName,
|
||||
isEnterprise: false,
|
||||
});
|
||||
|
|
|
@ -16,13 +16,11 @@ const getTabsOnHostDetailsUrl = (hostName: string, tabName: HostsTableType) =>
|
|||
|
||||
export const navTabsHostDetails = ({
|
||||
hasMlUserPermissions,
|
||||
isRiskyHostsEnabled,
|
||||
hostName,
|
||||
isEnterprise,
|
||||
}: {
|
||||
hostName: string;
|
||||
hasMlUserPermissions: boolean;
|
||||
isRiskyHostsEnabled: boolean;
|
||||
isEnterprise?: boolean;
|
||||
}): HostDetailsNavTab => {
|
||||
const hiddenTabs = [];
|
||||
|
@ -71,10 +69,6 @@ export const navTabsHostDetails = ({
|
|||
hiddenTabs.push(HostsTableType.anomalies);
|
||||
}
|
||||
|
||||
if (!isRiskyHostsEnabled) {
|
||||
hiddenTabs.push(HostsTableType.risk);
|
||||
}
|
||||
|
||||
if (!isEnterprise) {
|
||||
hiddenTabs.push(HostsTableType.sessions);
|
||||
}
|
||||
|
|
|
@ -53,7 +53,6 @@ import { ID } from '../containers/hosts';
|
|||
import { EmptyPrompt } from '../../../common/components/empty_prompt';
|
||||
import { fieldNameExistsFilter } from '../../../common/components/visualization_actions/utils';
|
||||
import { useLicense } from '../../../common/hooks/use_license';
|
||||
import { useHasSecurityCapability } from '../../../helper_hooks';
|
||||
|
||||
/**
|
||||
* Need a 100% height here to account for the graph/analyze tool, which sets no explicit height parameters, but fills the available space.
|
||||
|
@ -138,7 +137,6 @@ const HostsComponent = () => {
|
|||
});
|
||||
|
||||
const isEnterprisePlus = useLicense().isEnterprise();
|
||||
const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics');
|
||||
|
||||
const onSkipFocusBeforeEventsTable = useCallback(() => {
|
||||
containerElement.current
|
||||
|
@ -190,7 +188,6 @@ const HostsComponent = () => {
|
|||
<TabNavigation
|
||||
navTabs={navTabsHosts({
|
||||
hasMlUserPermissions: hasMlUserPermissions(capabilities),
|
||||
isRiskyHostsEnabled: hasEntityAnalyticsCapability,
|
||||
isEnterprise: isEnterprisePlus,
|
||||
})}
|
||||
/>
|
||||
|
|
|
@ -12,7 +12,6 @@ describe('navTabsHosts', () => {
|
|||
test('it should skip anomalies tab if without mlUserPermission', () => {
|
||||
const tabs = navTabsHosts({
|
||||
hasMlUserPermissions: false,
|
||||
isRiskyHostsEnabled: false,
|
||||
});
|
||||
expect(tabs).toHaveProperty(HostsTableType.hosts);
|
||||
expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses);
|
||||
|
@ -23,7 +22,6 @@ describe('navTabsHosts', () => {
|
|||
test('it should display anomalies tab if with mlUserPermission', () => {
|
||||
const tabs = navTabsHosts({
|
||||
hasMlUserPermissions: true,
|
||||
isRiskyHostsEnabled: false,
|
||||
});
|
||||
expect(tabs).toHaveProperty(HostsTableType.hosts);
|
||||
expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses);
|
||||
|
@ -31,21 +29,9 @@ describe('navTabsHosts', () => {
|
|||
expect(tabs).toHaveProperty(HostsTableType.events);
|
||||
});
|
||||
|
||||
test('it should skip risk tab if without hostRisk', () => {
|
||||
test('it should display risk tab', () => {
|
||||
const tabs = navTabsHosts({
|
||||
hasMlUserPermissions: false,
|
||||
isRiskyHostsEnabled: false,
|
||||
});
|
||||
expect(tabs).toHaveProperty(HostsTableType.hosts);
|
||||
expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses);
|
||||
expect(tabs).not.toHaveProperty(HostsTableType.risk);
|
||||
expect(tabs).toHaveProperty(HostsTableType.events);
|
||||
});
|
||||
|
||||
test('it should display risk tab if with hostRisk', () => {
|
||||
const tabs = navTabsHosts({
|
||||
hasMlUserPermissions: false,
|
||||
isRiskyHostsEnabled: true,
|
||||
});
|
||||
expect(tabs).toHaveProperty(HostsTableType.hosts);
|
||||
expect(tabs).toHaveProperty(HostsTableType.uncommonProcesses);
|
||||
|
|
|
@ -15,11 +15,9 @@ const getTabsOnHostsUrl = (tabName: HostsTableType) => `${HOSTS_PATH}/${tabName}
|
|||
|
||||
export const navTabsHosts = ({
|
||||
hasMlUserPermissions,
|
||||
isRiskyHostsEnabled,
|
||||
isEnterprise,
|
||||
}: {
|
||||
hasMlUserPermissions: boolean;
|
||||
isRiskyHostsEnabled: boolean;
|
||||
isEnterprise?: boolean;
|
||||
}): HostsNavTab => {
|
||||
const hiddenTabs = [];
|
||||
|
@ -67,10 +65,6 @@ export const navTabsHosts = ({
|
|||
hiddenTabs.push(HostsTableType.anomalies);
|
||||
}
|
||||
|
||||
if (!isRiskyHostsEnabled) {
|
||||
hiddenTabs.push(HostsTableType.risk);
|
||||
}
|
||||
|
||||
if (!isEnterprise) {
|
||||
hiddenTabs.push(HostsTableType.sessions);
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
import { noop } from 'lodash/fp';
|
||||
import { useUpsellingComponent } from '../../../../common/hooks/use_upselling';
|
||||
import { RiskEnginePrivilegesCallOut } from '../../../../entity_analytics/components/risk_engine_privileges_callout';
|
||||
import { useMissingRiskEnginePrivileges } from '../../../../entity_analytics/hooks/use_missing_risk_engine_privileges';
|
||||
import { HostRiskScoreQueryId } from '../../../../entity_analytics/common/utils';
|
||||
|
@ -96,6 +97,12 @@ export const HostRiskScoreQueryTabBody = ({
|
|||
isDeprecated: isDeprecated && !loading,
|
||||
};
|
||||
|
||||
const RiskScoreUpsell = useUpsellingComponent('entity_analytics_panel');
|
||||
|
||||
if (RiskScoreUpsell) {
|
||||
return <RiskScoreUpsell />;
|
||||
}
|
||||
|
||||
if (!privileges.isLoading && !privileges.hasAllRequiredPrivileges) {
|
||||
return (
|
||||
<EuiPanel hasBorder>
|
||||
|
|
|
@ -76,7 +76,6 @@ 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 { EmptyPrompt } from '../../../../common/components/empty_prompt';
|
||||
import { useHasSecurityCapability } from '../../../../helper_hooks';
|
||||
import { useRefetchOverviewPageRiskScore } from '../../../../entity_analytics/api/hooks/use_refetch_overview_page_risk_score';
|
||||
|
||||
const QUERY_ID = 'UsersDetailsQueryId';
|
||||
|
@ -87,7 +86,6 @@ const UsersDetailsComponent: React.FC<UsersDetailsProps> = ({
|
|||
usersDetailsPagePath,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics');
|
||||
const getTable = useMemo(() => dataTableSelectors.getTableByIdSelector(), []);
|
||||
const graphEventId = useShallowEqualSelector(
|
||||
(state) => (getTable(state, TableId.hostsPageEvents) ?? timelineDefaults).graphEventId
|
||||
|
@ -285,11 +283,7 @@ const UsersDetailsComponent: React.FC<UsersDetailsProps> = ({
|
|||
)}
|
||||
|
||||
<TabNavigation
|
||||
navTabs={navTabsUsersDetails(
|
||||
detailName,
|
||||
hasMlUserPermissions(capabilities),
|
||||
hasEntityAnalyticsCapability
|
||||
)}
|
||||
navTabs={navTabsUsersDetails(detailName, hasMlUserPermissions(capabilities))}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<UsersDetailsTabs
|
||||
|
|
|
@ -9,16 +9,9 @@ import { navTabsUsersDetails } from './nav_tabs';
|
|||
|
||||
describe('navTabsUsersDetails', () => {
|
||||
test('it should not display anomalies tab if user has no ml permission', () => {
|
||||
const tabs = navTabsUsersDetails('username', false, true);
|
||||
const tabs = navTabsUsersDetails('username', false);
|
||||
|
||||
expect(tabs).not.toHaveProperty(UsersTableType.anomalies);
|
||||
expect(tabs).toHaveProperty(UsersTableType.risk);
|
||||
});
|
||||
|
||||
test('it should not display risk tab if isRiskyUserEnabled disabled', () => {
|
||||
const tabs = navTabsUsersDetails('username', true, false);
|
||||
// expect(tabs).toHaveProperty(UsersTableType.allUsers);
|
||||
expect(tabs).toHaveProperty(UsersTableType.anomalies);
|
||||
expect(tabs).not.toHaveProperty(UsersTableType.risk);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,8 +16,7 @@ const getTabsOnUsersDetailsUrl = (userName: string, tabName: UsersTableType) =>
|
|||
|
||||
export const navTabsUsersDetails = (
|
||||
userName: string,
|
||||
hasMlUserPermissions: boolean,
|
||||
isRiskyUserEnabled: boolean
|
||||
hasMlUserPermissions: boolean
|
||||
): UsersDetailsNavTab => {
|
||||
const hiddenTabs = [];
|
||||
|
||||
|
@ -52,9 +51,5 @@ export const navTabsUsersDetails = (
|
|||
hiddenTabs.push(UsersTableType.anomalies);
|
||||
}
|
||||
|
||||
if (!isRiskyUserEnabled) {
|
||||
hiddenTabs.push(UsersTableType.risk);
|
||||
}
|
||||
|
||||
return omit(hiddenTabs, userDetailsNavTabs);
|
||||
};
|
||||
|
|
|
@ -10,23 +10,16 @@ import { navTabsUsers } from './nav_tabs';
|
|||
|
||||
describe('navTabsUsers', () => {
|
||||
test('it should display all tabs', () => {
|
||||
const tabs = navTabsUsers(true, true);
|
||||
const tabs = navTabsUsers(true);
|
||||
expect(tabs).toHaveProperty(UsersTableType.allUsers);
|
||||
expect(tabs).toHaveProperty(UsersTableType.anomalies);
|
||||
expect(tabs).toHaveProperty(UsersTableType.risk);
|
||||
});
|
||||
|
||||
test('it should not display anomalies tab if user has no ml permission', () => {
|
||||
const tabs = navTabsUsers(false, true);
|
||||
const tabs = navTabsUsers(false);
|
||||
expect(tabs).toHaveProperty(UsersTableType.allUsers);
|
||||
expect(tabs).not.toHaveProperty(UsersTableType.anomalies);
|
||||
expect(tabs).toHaveProperty(UsersTableType.risk);
|
||||
});
|
||||
|
||||
test('it should not display risk tab if isRiskyUserEnabled disabled', () => {
|
||||
const tabs = navTabsUsers(true, false);
|
||||
expect(tabs).toHaveProperty(UsersTableType.allUsers);
|
||||
expect(tabs).toHaveProperty(UsersTableType.anomalies);
|
||||
expect(tabs).not.toHaveProperty(UsersTableType.risk);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,10 +13,7 @@ import { USERS_PATH } from '../../../../common/constants';
|
|||
|
||||
const getTabsOnUsersUrl = (tabName: UsersTableType) => `${USERS_PATH}/${tabName}`;
|
||||
|
||||
export const navTabsUsers = (
|
||||
hasMlUserPermissions: boolean,
|
||||
isRiskyUserEnabled: boolean
|
||||
): UsersNavTab => {
|
||||
export const navTabsUsers = (hasMlUserPermissions: boolean): UsersNavTab => {
|
||||
const hiddenTabs = [];
|
||||
|
||||
const userNavTabs = {
|
||||
|
@ -56,9 +53,5 @@ export const navTabsUsers = (
|
|||
hiddenTabs.push(UsersTableType.anomalies);
|
||||
}
|
||||
|
||||
if (!isRiskyUserEnabled) {
|
||||
hiddenTabs.push(UsersTableType.risk);
|
||||
}
|
||||
|
||||
return omit(hiddenTabs, userNavTabs);
|
||||
};
|
||||
|
|
|
@ -49,7 +49,6 @@ import { hasMlUserPermissions } from '../../../../common/machine_learning/has_ml
|
|||
import { useMlCapabilities } from '../../../common/components/ml/hooks/use_ml_capabilities';
|
||||
import { EmptyPrompt } from '../../../common/components/empty_prompt';
|
||||
import { userNameExistsFilter } from './details/helpers';
|
||||
import { useHasSecurityCapability } from '../../../helper_hooks';
|
||||
|
||||
const ID = 'UsersQueryId';
|
||||
|
||||
|
@ -156,11 +155,7 @@ const UsersComponent = () => {
|
|||
);
|
||||
|
||||
const capabilities = useMlCapabilities();
|
||||
const hasEntityAnalyticsCapability = useHasSecurityCapability('entity-analytics');
|
||||
const navTabs = useMemo(
|
||||
() => navTabsUsers(hasMlUserPermissions(capabilities), hasEntityAnalyticsCapability),
|
||||
[capabilities, hasEntityAnalyticsCapability]
|
||||
);
|
||||
const navTabs = useMemo(() => navTabsUsers(hasMlUserPermissions(capabilities)), [capabilities]);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -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 { lazy } from 'react';
|
||||
|
||||
import { withSuspenseUpsell } from '@kbn/security-solution-upselling/helpers';
|
||||
|
||||
export const EntityAnalyticsUpsellingSectionLazy = withSuspenseUpsell(
|
||||
lazy(() =>
|
||||
import('./sections/entity_analytics_upselling').then(
|
||||
({ EntityAnalyticsUpsellingSectionESS }) => ({
|
||||
default: EntityAnalyticsUpsellingSectionESS,
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
export const EntityAnalyticsUpsellingPageLazy = lazy(() =>
|
||||
import('./pages/entity_analytics_upselling').then(({ EntityAnalyticsUpsellingPageESS }) => ({
|
||||
default: EntityAnalyticsUpsellingPageESS,
|
||||
}))
|
||||
);
|
|
@ -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 React from 'react';
|
||||
import { EntityAnalyticsUpsellingPage } from '@kbn/security-solution-upselling/pages/entity_analytics';
|
||||
import { useKibana } from '../../common/services';
|
||||
import * as i18n from '../translations';
|
||||
|
||||
export const EntityAnalyticsUpsellingPageESS = () => {
|
||||
const { services } = useKibana();
|
||||
const requiredLicense = 'Platinum';
|
||||
|
||||
return (
|
||||
<EntityAnalyticsUpsellingPage
|
||||
upgradeMessage={i18n.UPGRADE_LICENSE_MESSAGE(requiredLicense ?? '')}
|
||||
upgradeHref={services.application.getUrlForApp('management', {
|
||||
path: 'stack/license_management',
|
||||
})}
|
||||
upgradeToLabel={requiredLicense}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -5,7 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ILicense, LicenseType } from '@kbn/licensing-plugin/public';
|
||||
import { SecurityPageName } from '@kbn/security-solution-plugin/common';
|
||||
import {
|
||||
ALERT_SUPPRESSION_RULE_DETAILS,
|
||||
ALERT_SUPPRESSION_RULE_FORM,
|
||||
UPGRADE_ALERT_ASSIGNMENTS,
|
||||
UPGRADE_INVESTIGATION_GUIDE,
|
||||
} from '@kbn/security-solution-upselling/messages';
|
||||
import type {
|
||||
MessageUpsellings,
|
||||
PageUpsellings,
|
||||
|
@ -14,19 +21,13 @@ import type {
|
|||
UpsellingSectionId,
|
||||
UpsellingService,
|
||||
} from '@kbn/security-solution-upselling/service';
|
||||
import type { ILicense, LicenseType } from '@kbn/licensing-plugin/public';
|
||||
import React, { lazy } from 'react';
|
||||
import {
|
||||
UPGRADE_ALERT_ASSIGNMENTS,
|
||||
UPGRADE_INVESTIGATION_GUIDE,
|
||||
ALERT_SUPPRESSION_RULE_FORM,
|
||||
ALERT_SUPPRESSION_RULE_DETAILS,
|
||||
} from '@kbn/security-solution-upselling/messages';
|
||||
import type React from 'react';
|
||||
import type { Services } from '../common/services';
|
||||
import { withServicesProvider } from '../common/services';
|
||||
const EntityAnalyticsUpsellingLazy = lazy(
|
||||
() => import('@kbn/security-solution-upselling/pages/entity_analytics')
|
||||
);
|
||||
import {
|
||||
EntityAnalyticsUpsellingPageLazy,
|
||||
EntityAnalyticsUpsellingSectionLazy,
|
||||
} from './lazy_upselling';
|
||||
|
||||
interface UpsellingsConfig {
|
||||
minimumLicenseRequired: LicenseType;
|
||||
|
@ -48,7 +49,7 @@ export const registerUpsellings = (
|
|||
license: ILicense,
|
||||
services: Services
|
||||
) => {
|
||||
const upsellingPagesToRegister = upsellingPages(services).reduce<PageUpsellings>(
|
||||
const upsellingPagesToRegister = upsellingPages.reduce<PageUpsellings>(
|
||||
(pageUpsellings, { pageName, minimumLicenseRequired, component }) => {
|
||||
if (!license.hasAtLeast(minimumLicenseRequired)) {
|
||||
pageUpsellings[pageName] = withServicesProvider(component, services);
|
||||
|
@ -61,7 +62,7 @@ export const registerUpsellings = (
|
|||
const upsellingSectionsToRegister = upsellingSections.reduce<SectionUpsellings>(
|
||||
(sectionUpsellings, { id, minimumLicenseRequired, component }) => {
|
||||
if (!license.hasAtLeast(minimumLicenseRequired)) {
|
||||
sectionUpsellings[id] = component;
|
||||
sectionUpsellings[id] = withServicesProvider(component, services);
|
||||
}
|
||||
return sectionUpsellings;
|
||||
},
|
||||
|
@ -84,25 +85,23 @@ export const registerUpsellings = (
|
|||
};
|
||||
|
||||
// Upsellings for entire pages, linked to a SecurityPageName
|
||||
export const upsellingPages: (services: Services) => UpsellingPages = (services) => [
|
||||
export const upsellingPages: UpsellingPages = [
|
||||
// It is highly advisable to make use of lazy loaded components to minimize bundle size.
|
||||
{
|
||||
pageName: SecurityPageName.entityAnalytics,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
component: () => (
|
||||
<EntityAnalyticsUpsellingLazy
|
||||
requiredLicense="Platinum"
|
||||
subscriptionUrl={services.application.getUrlForApp('management', {
|
||||
path: 'stack/license_management',
|
||||
})}
|
||||
/>
|
||||
),
|
||||
component: EntityAnalyticsUpsellingPageLazy,
|
||||
},
|
||||
];
|
||||
|
||||
// Upsellings for sections, linked by arbitrary ids
|
||||
export const upsellingSections: UpsellingSections = [
|
||||
// It is highly advisable to make use of lazy loaded components to minimize bundle size.
|
||||
{
|
||||
id: 'entity_analytics_panel',
|
||||
minimumLicenseRequired: 'platinum',
|
||||
component: EntityAnalyticsUpsellingSectionLazy,
|
||||
},
|
||||
];
|
||||
|
||||
// Upsellings for sections, linked by arbitrary ids
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 { EntityAnalyticsUpsellingSection } from '@kbn/security-solution-upselling/sections/entity_analytics';
|
||||
import { useKibana } from '../../common/services';
|
||||
import * as i18n from '../translations';
|
||||
|
||||
export const EntityAnalyticsUpsellingSectionESS = () => {
|
||||
const { services } = useKibana();
|
||||
const requiredLicense = 'Platinum';
|
||||
return (
|
||||
<EntityAnalyticsUpsellingSection
|
||||
upgradeMessage={i18n.UPGRADE_LICENSE_MESSAGE(requiredLicense ?? '')}
|
||||
upgradeHref={services.application.getUrlForApp('management', {
|
||||
path: 'stack/license_management',
|
||||
})}
|
||||
upgradeToLabel={requiredLicense}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* 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('xpack.securitySolutionEss.upselling.upgradeLicenseMessage', {
|
||||
defaultMessage: 'This feature is available with {requiredLicense} or higher subscription',
|
||||
values: {
|
||||
requiredLicense,
|
||||
},
|
||||
});
|
|
@ -5,19 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
import { EuiLoadingSpinner } from '@elastic/eui';
|
||||
import { lazy } from 'react';
|
||||
|
||||
const withSuspenseUpsell = <T extends object = {}>(
|
||||
Component: React.ComponentType<T>
|
||||
): React.FC<T> =>
|
||||
function WithSuspenseUpsell(props) {
|
||||
return (
|
||||
<Suspense fallback={<EuiLoadingSpinner size="s" />}>
|
||||
<Component {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
import { withSuspenseUpsell } from '@kbn/security-solution-upselling/helpers';
|
||||
|
||||
export const ThreatIntelligencePaywallLazy = withSuspenseUpsell(
|
||||
lazy(() => import('./pages/threat_intelligence_paywall'))
|
||||
|
@ -31,6 +21,22 @@ export const EndpointExceptionsDetailsUpsellingLazy = withSuspenseUpsell(
|
|||
lazy(() => import('./pages/endpoint_management/endpoint_exceptions_details'))
|
||||
);
|
||||
|
||||
export const EntityAnalyticsUpsellingLazy = withSuspenseUpsell(
|
||||
lazy(() => import('@kbn/security-solution-upselling/pages/entity_analytics'))
|
||||
export const EntityAnalyticsUpsellingPageLazy = withSuspenseUpsell(
|
||||
lazy(() =>
|
||||
import('@kbn/security-solution-upselling/pages/entity_analytics').then(
|
||||
({ EntityAnalyticsUpsellingPage }) => ({
|
||||
default: EntityAnalyticsUpsellingPage,
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
export const EntityAnalyticsUpsellingSectionLazy = withSuspenseUpsell(
|
||||
lazy(() =>
|
||||
import('@kbn/security-solution-upselling/sections/entity_analytics').then(
|
||||
({ EntityAnalyticsUpsellingSection }) => ({
|
||||
default: EntityAnalyticsUpsellingSection,
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
|
|
@ -4,7 +4,14 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
|
||||
import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
|
||||
import { SecurityPageName } from '@kbn/security-solution-plugin/common';
|
||||
import {
|
||||
UPGRADE_INVESTIGATION_GUIDE,
|
||||
UPGRADE_INVESTIGATION_GUIDE_INTERACTIONS,
|
||||
} from '@kbn/security-solution-upselling/messages';
|
||||
import type { UpsellingService } from '@kbn/security-solution-upselling/service';
|
||||
import type {
|
||||
MessageUpsellings,
|
||||
PageUpsellings,
|
||||
|
@ -12,31 +19,26 @@ import type {
|
|||
UpsellingMessageId,
|
||||
UpsellingSectionId,
|
||||
} from '@kbn/security-solution-upselling/service/types';
|
||||
import type { UpsellingService } from '@kbn/security-solution-upselling/service';
|
||||
import React from 'react';
|
||||
import type { SecurityProductTypes } from '../../common/config';
|
||||
import { getProductProductFeatures } from '../../common/pli/pli_features';
|
||||
import type { Services } from '../common/services';
|
||||
import { withServicesProvider } from '../common/services';
|
||||
import { getProductTypeByPLI } from './hooks/use_product_type_by_pli';
|
||||
import {
|
||||
UPGRADE_INVESTIGATION_GUIDE,
|
||||
UPGRADE_INVESTIGATION_GUIDE_INTERACTIONS,
|
||||
} from '@kbn/security-solution-upselling/messages';
|
||||
import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
|
||||
import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
|
||||
EndpointExceptionsDetailsUpsellingLazy,
|
||||
EntityAnalyticsUpsellingPageLazy,
|
||||
EntityAnalyticsUpsellingSectionLazy,
|
||||
OsqueryResponseActionsUpsellingSectionLazy,
|
||||
ThreatIntelligencePaywallLazy,
|
||||
} from './lazy_upselling';
|
||||
import {
|
||||
EndpointAgentTamperProtectionLazy,
|
||||
EndpointPolicyProtectionsLazy,
|
||||
EndpointProtectionUpdatesLazy,
|
||||
RuleDetailsEndpointExceptionsLazy,
|
||||
} from './sections/endpoint_management';
|
||||
import type { SecurityProductTypes } from '../../common/config';
|
||||
import { getProductProductFeatures } from '../../common/pli/pli_features';
|
||||
import {
|
||||
EndpointExceptionsDetailsUpsellingLazy,
|
||||
EntityAnalyticsUpsellingLazy,
|
||||
OsqueryResponseActionsUpsellingSectionLazy,
|
||||
ThreatIntelligencePaywallLazy,
|
||||
} from './lazy_upselling';
|
||||
import { getProductTypeByPLI } from './hooks/use_product_type_by_pli';
|
||||
import type { Services } from '../common/services';
|
||||
import { withServicesProvider } from '../common/services';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface UpsellingsConfig {
|
||||
pli: ProductFeatureKeyType;
|
||||
|
@ -73,7 +75,7 @@ export const registerUpsellings = (
|
|||
const upsellingSectionsToRegister = upsellingSections.reduce<SectionUpsellings>(
|
||||
(sectionUpsellings, { id, pli, component }) => {
|
||||
if (!enabledPLIsSet.has(pli)) {
|
||||
sectionUpsellings[id] = component;
|
||||
sectionUpsellings[id] = withServicesProvider(component, services);
|
||||
}
|
||||
return sectionUpsellings;
|
||||
},
|
||||
|
@ -102,8 +104,9 @@ export const upsellingPages: UpsellingPages = [
|
|||
pageName: SecurityPageName.entityAnalytics,
|
||||
pli: ProductFeatureKey.advancedInsights,
|
||||
component: () => (
|
||||
<EntityAnalyticsUpsellingLazy
|
||||
requiredProduct={getProductTypeByPLI(ProductFeatureKey.advancedInsights) ?? undefined}
|
||||
<EntityAnalyticsUpsellingPageLazy
|
||||
upgradeToLabel={entityAnalyticsProductType}
|
||||
upgradeMessage={i18n.UPGRADE_PRODUCT_MESSAGE(entityAnalyticsProductType)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
@ -123,6 +126,8 @@ export const upsellingPages: UpsellingPages = [
|
|||
},
|
||||
];
|
||||
|
||||
const entityAnalyticsProductType = getProductTypeByPLI(ProductFeatureKey.advancedInsights) ?? '';
|
||||
|
||||
// Upselling for sections, linked by arbitrary ids
|
||||
export const upsellingSections: UpsellingSections = [
|
||||
// It is highly advisable to make use of lazy loaded components to minimize bundle size.
|
||||
|
@ -155,6 +160,16 @@ export const upsellingSections: UpsellingSections = [
|
|||
pli: ProductFeatureKey.endpointProtectionUpdates,
|
||||
component: EndpointProtectionUpdatesLazy,
|
||||
},
|
||||
{
|
||||
id: 'entity_analytics_panel',
|
||||
pli: ProductFeatureKey.advancedInsights,
|
||||
component: () => (
|
||||
<EntityAnalyticsUpsellingSectionLazy
|
||||
upgradeToLabel={entityAnalyticsProductType}
|
||||
upgradeMessage={i18n.UPGRADE_PRODUCT_MESSAGE(entityAnalyticsProductType)}
|
||||
/>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// Upselling for sections, linked by arbitrary ids
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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_PRODUCT_MESSAGE = (requiredProduct: string) =>
|
||||
i18n.translate(
|
||||
'xpack.securitySolutionServerless.upselling.entityAnalytics.upgradeProductMessage',
|
||||
{
|
||||
defaultMessage:
|
||||
'Entity risk scoring capability is available in our {requiredProduct} license tier',
|
||||
values: {
|
||||
requiredProduct,
|
||||
},
|
||||
}
|
||||
);
|
|
@ -5879,8 +5879,6 @@
|
|||
"securitySolutionPackages.ecsDataQualityDashboard.unmanagedPatternTooltip": "{indices} {indices, plural, =1 {L'index correspondant} other {Les index correspondants}} au modèle {pattern} {indices, plural, =1 {n'est pas géré} other {ne sont pas gérés}} par la gestion du cycle de vie des index (ILM)",
|
||||
"securitySolutionPackages.ecsDataQualityDashboard.warmPatternTooltip": "{indices} {indices, plural, =1 {L'index correspondant} other {Les index correspondants}} au modèle {pattern} {indices, plural, =1 {est} other {sont}} \"warm\". Les index \"warm\" ne sont plus mis à jour, mais ils sont toujours interrogés.",
|
||||
"securitySolutionPackages.entityAnalytics.paywall.upgradeButton": "Passer à {requiredLicenseOrProduct}",
|
||||
"securitySolutionPackages.entityAnalytics.paywall.upgradeLicenseMessage": "Cette fonctionnalité est disponible avec l'abonnement {requiredLicense} ou supérieur",
|
||||
"securitySolutionPackages.entityAnalytics.paywall.upgradeProductMessage": "La capacité d'évaluation du risque des entités est disponible dans notre niveau de licence {requiredProduct}",
|
||||
"securitySolutionPackages.markdown.insight.upsell": "Passez au niveau {requiredLicense} pour pouvoir utiliser les informations des guides d'investigation",
|
||||
"securitySolutionPackages.alertSuppressionRuleDetails.upsell": "La suppression d'alertes est configurée mais elle ne sera pas appliquée en raison d'une licence insuffisante",
|
||||
"securitySolutionPackages.beta.label": "Bêta",
|
||||
|
|
|
@ -5870,8 +5870,6 @@
|
|||
"securitySolutionPackages.ecsDataQualityDashboard.unmanagedPatternTooltip": "{pattern}パターンと一致する{indices} {indices, plural, other {インデックス}}{indices, plural, other {は}}インデックスライフサイクル管理(ILM)によって管理されていません",
|
||||
"securitySolutionPackages.ecsDataQualityDashboard.warmPatternTooltip": "{pattern}パターンと一致する{indices} {indices, plural, other {インデックス}}{indices, plural, other {は}}ウォームです。ウォームインデックスは更新されませんが、まだ照会されています。",
|
||||
"securitySolutionPackages.entityAnalytics.paywall.upgradeButton": "{requiredLicenseOrProduct}にアップグレード",
|
||||
"securitySolutionPackages.entityAnalytics.paywall.upgradeLicenseMessage": "この機能は、{requiredLicense}以上のサブスクリプションでご利用いただけます",
|
||||
"securitySolutionPackages.entityAnalytics.paywall.upgradeProductMessage": "エンティティリスクスコアリング機能は、{requiredProduct}ライセンスティアで利用可能です",
|
||||
"securitySolutionPackages.alertSuppressionRuleDetails.upsell": "アラート非表示が構成されていますが、ライセンス不足のため適用されません",
|
||||
"securitySolutionPackages.beta.label": "ベータ",
|
||||
"securitySolutionPackages.dataTable.ariaLabel": "アラート",
|
||||
|
|
|
@ -5883,7 +5883,6 @@
|
|||
"securitySolutionPackages.ecsDataQualityDashboard.unmanagedPatternTooltip": "与 {pattern} 模式匹配的 {indices} 个{indices, plural, other {索引}}{indices, plural, other {}}不通过索引生命周期管理 (ILM) 进行管理",
|
||||
"securitySolutionPackages.ecsDataQualityDashboard.warmPatternTooltip": "{indices} 个匹配 {pattern} 模式的{indices, plural, other {索引}}{indices, plural, other {为}}温索引。不再更新但仍会查询温索引。",
|
||||
"securitySolutionPackages.entityAnalytics.paywall.upgradeButton": "升级到 {requiredLicenseOrProduct}",
|
||||
"securitySolutionPackages.entityAnalytics.paywall.upgradeLicenseMessage": "{requiredLicense}或更高级订阅可以使用此功能",
|
||||
"securitySolutionPackages.markdown.insight.upsell": "升级到{requiredLicense}以利用调查指南中的洞见",
|
||||
"securitySolutionPackages.alertSuppressionRuleDetails.upsell": "已配置告警阻止,但由于许可不足而无法应用",
|
||||
"securitySolutionPackages.beta.label": "公测版",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue