mirror of
https://github.com/elastic/kibana.git
synced 2025-04-25 02:09:32 -04:00
[Security Serverless] - no net new capability to initiate/create investigate guides in timelines for 'essential tier' (#8700) (#181562)
## Summary Addresses https://github.com/elastic/security-team/issues/8700 With these changes we disable Interactive Investigation guides interactions buttons (timelines + OSquery interactive actions) for 'Essential tier' in Serverless. For Investigation guides in the Detection rules: **Create rule page -> Advanced settings - > Investigation guide** osquery and timeline buttons should be inactive and have an upgrade callout <img width="1033" alt="Screenshot 2024-04-24 at 15 08 32" src="91f5b5e0
-9efe-4ee3-b0c8-e3e75c4782b7"> **Rule details page -> Investigations guide** buttons in the markdown should be inactive and have an upgrade callout <img width="736" alt="Screenshot 2024-04-24 at 15 09 07" src="a1eca15a
-ea0a-4346-b193-d452a38849f1"> ### Checklist Delete any items that are not applicable to this PR. - [x] Any text added follows [EUI's writing guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses sentence case text and includes [i18n support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md) - [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 - [ ] [Flaky Test Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was used on any tests changed - [ ] [Tests](https://buildkite.com/elastic/kibana-flaky-test-suite-runner/builds/5754) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
bc87224b41
commit
13a968a447
16 changed files with 152 additions and 73 deletions
|
@ -12,6 +12,10 @@ export enum ProductFeatureSecurityKey {
|
||||||
* Enables Investigation guide in Timeline
|
* Enables Investigation guide in Timeline
|
||||||
*/
|
*/
|
||||||
investigationGuide = 'investigation_guide',
|
investigationGuide = 'investigation_guide',
|
||||||
|
/**
|
||||||
|
* Enables Investigation guide interactions (e.g., osquery, timelines, etc.)
|
||||||
|
*/
|
||||||
|
investigationGuideInteractions = 'investigation_guide_interactions',
|
||||||
/**
|
/**
|
||||||
* Enables access to the Endpoint List and associated views that allows management of hosts
|
* Enables access to the Endpoint List and associated views that allows management of hosts
|
||||||
* running endpoint security
|
* running endpoint security
|
||||||
|
|
|
@ -42,6 +42,16 @@ export const securityDefaultProductFeaturesConfig: DefaultSecurityProductFeature
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
[ProductFeatureSecurityKey.investigationGuideInteractions]: {
|
||||||
|
privileges: {
|
||||||
|
all: {
|
||||||
|
ui: ['investigation-guide-interactions'],
|
||||||
|
},
|
||||||
|
read: {
|
||||||
|
ui: ['investigation-guide-interactions'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
[ProductFeatureSecurityKey.threatIntelligence]: {
|
[ProductFeatureSecurityKey.threatIntelligence]: {
|
||||||
privileges: {
|
privileges: {
|
||||||
|
|
|
@ -15,6 +15,14 @@ export const UPGRADE_INVESTIGATION_GUIDE = (requiredLicense: string) =>
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const UPGRADE_INVESTIGATION_GUIDE_INTERACTIONS = (requiredLicense: string) =>
|
||||||
|
i18n.translate('securitySolutionPackages.markdown.investigationGuideInteractions.upsell', {
|
||||||
|
defaultMessage: 'Upgrade to {requiredLicense} to make use of investigation guide interactions',
|
||||||
|
values: {
|
||||||
|
requiredLicense,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
export const UPGRADE_ALERT_ASSIGNMENTS = (requiredLicense: string) =>
|
export const UPGRADE_ALERT_ASSIGNMENTS = (requiredLicense: string) =>
|
||||||
i18n.translate('securitySolutionPackages.alertAssignments.upsell', {
|
i18n.translate('securitySolutionPackages.alertAssignments.upsell', {
|
||||||
defaultMessage: 'Upgrade to {requiredLicense} to make use of alert assignments',
|
defaultMessage: 'Upgrade to {requiredLicense} to make use of alert assignments',
|
||||||
|
|
|
@ -21,6 +21,7 @@ export type UpsellingSectionId =
|
||||||
|
|
||||||
export type UpsellingMessageId =
|
export type UpsellingMessageId =
|
||||||
| 'investigation_guide'
|
| 'investigation_guide'
|
||||||
|
| 'investigation_guide_interactions'
|
||||||
| 'alert_assignments'
|
| 'alert_assignments'
|
||||||
| 'alert_suppression_rule_form'
|
| 'alert_suppression_rule_form'
|
||||||
| 'alert_suppression_rule_details';
|
| 'alert_suppression_rule_details';
|
||||||
|
|
|
@ -39,6 +39,7 @@ import { useInsertTimeline } from '../components/use_insert_timeline';
|
||||||
import * as timelineMarkdownPlugin from '../../common/components/markdown_editor/plugins/timeline';
|
import * as timelineMarkdownPlugin from '../../common/components/markdown_editor/plugins/timeline';
|
||||||
import { DetailsPanel } from '../../timelines/components/side_panel';
|
import { DetailsPanel } from '../../timelines/components/side_panel';
|
||||||
import { useFetchAlertData } from './use_fetch_alert_data';
|
import { useFetchAlertData } from './use_fetch_alert_data';
|
||||||
|
import { useUpsellingMessage } from '../../common/hooks/use_upselling';
|
||||||
|
|
||||||
const TimelineDetailsPanel = () => {
|
const TimelineDetailsPanel = () => {
|
||||||
const { browserFields, runtimeMappings } = useSourcererDataView(SourcererScopeName.detections);
|
const { browserFields, runtimeMappings } = useSourcererDataView(SourcererScopeName.detections);
|
||||||
|
@ -69,6 +70,8 @@ const CaseContainerComponent: React.FC = () => {
|
||||||
[detectionsFormatUrl, detectionsUrlSearch]
|
[detectionsFormatUrl, detectionsUrlSearch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const interactionsUpsellingMessage = useUpsellingMessage('investigation_guide_interactions');
|
||||||
|
|
||||||
const showAlertDetails = useCallback(
|
const showAlertDetails = useCallback(
|
||||||
(alertId: string, index: string) => {
|
(alertId: string, index: string) => {
|
||||||
if (isSecurityFlyoutEnabled) {
|
if (isSecurityFlyoutEnabled) {
|
||||||
|
@ -187,7 +190,7 @@ const CaseContainerComponent: React.FC = () => {
|
||||||
editor_plugins: {
|
editor_plugins: {
|
||||||
parsingPlugin: timelineMarkdownPlugin.parser,
|
parsingPlugin: timelineMarkdownPlugin.parser,
|
||||||
processingPluginRenderer: timelineMarkdownPlugin.renderer,
|
processingPluginRenderer: timelineMarkdownPlugin.renderer,
|
||||||
uiPlugin: timelineMarkdownPlugin.plugin,
|
uiPlugin: timelineMarkdownPlugin.plugin({ interactionsUpsellingMessage }),
|
||||||
},
|
},
|
||||||
hooks: {
|
hooks: {
|
||||||
useInsertTimeline,
|
useInsertTimeline,
|
||||||
|
|
|
@ -74,9 +74,15 @@ const MarkdownEditorComponent = forwardRef<MarkdownEditorRef, MarkdownEditorProp
|
||||||
}, [autoFocusDisabled]);
|
}, [autoFocusDisabled]);
|
||||||
|
|
||||||
const insightsUpsellingMessage = useUpsellingMessage('investigation_guide');
|
const insightsUpsellingMessage = useUpsellingMessage('investigation_guide');
|
||||||
|
const interactionsUpsellingMessage = useUpsellingMessage('investigation_guide_interactions');
|
||||||
const uiPluginsWithState = useMemo(() => {
|
const uiPluginsWithState = useMemo(() => {
|
||||||
return includePlugins ? uiPlugins({ insightsUpsellingMessage }) : undefined;
|
return includePlugins
|
||||||
}, [insightsUpsellingMessage, includePlugins]);
|
? uiPlugins({
|
||||||
|
insightsUpsellingMessage,
|
||||||
|
interactionsUpsellingMessage,
|
||||||
|
})
|
||||||
|
: undefined;
|
||||||
|
}, [includePlugins, insightsUpsellingMessage, interactionsUpsellingMessage]);
|
||||||
|
|
||||||
// @ts-expect-error update types
|
// @ts-expect-error update types
|
||||||
useImperativeHandle(ref, () => {
|
useImperativeHandle(ref, () => {
|
||||||
|
|
|
@ -22,16 +22,24 @@ export const platinumOnlyPluginTokens = [insightMarkdownPlugin.insightPrefix];
|
||||||
|
|
||||||
export const uiPlugins = ({
|
export const uiPlugins = ({
|
||||||
insightsUpsellingMessage,
|
insightsUpsellingMessage,
|
||||||
|
interactionsUpsellingMessage,
|
||||||
}: {
|
}: {
|
||||||
insightsUpsellingMessage: string | null;
|
insightsUpsellingMessage: string | null;
|
||||||
|
interactionsUpsellingMessage: string | null;
|
||||||
}) => {
|
}) => {
|
||||||
const currentPlugins = nonStatefulUiPlugins.map((plugin) => plugin.name);
|
const currentPlugins = nonStatefulUiPlugins.map((plugin) => plugin.name);
|
||||||
const insightPluginWithLicense = insightMarkdownPlugin.plugin({
|
const insightPluginWithLicense = insightMarkdownPlugin.plugin({
|
||||||
insightsUpsellingMessage,
|
insightsUpsellingMessage,
|
||||||
});
|
});
|
||||||
|
const timelinePluginWithLicense = timelineMarkdownPlugin.plugin({
|
||||||
|
interactionsUpsellingMessage,
|
||||||
|
});
|
||||||
|
const osqueryPluginWithLicense = osqueryMarkdownPlugin.plugin({
|
||||||
|
interactionsUpsellingMessage,
|
||||||
|
});
|
||||||
if (currentPlugins.includes(insightPluginWithLicense.name) === false) {
|
if (currentPlugins.includes(insightPluginWithLicense.name) === false) {
|
||||||
nonStatefulUiPlugins.push(timelineMarkdownPlugin.plugin);
|
nonStatefulUiPlugins.push(timelinePluginWithLicense);
|
||||||
nonStatefulUiPlugins.push(osqueryMarkdownPlugin.plugin);
|
nonStatefulUiPlugins.push(osqueryPluginWithLicense);
|
||||||
nonStatefulUiPlugins.push(insightPluginWithLicense);
|
nonStatefulUiPlugins.push(insightPluginWithLicense);
|
||||||
} else {
|
} else {
|
||||||
// When called for the second time we need to update insightMarkdownPlugin
|
// When called for the second time we need to update insightMarkdownPlugin
|
||||||
|
|
|
@ -14,10 +14,10 @@ import {
|
||||||
DEFAULT_TO,
|
DEFAULT_TO,
|
||||||
} from '../../../../../../common/constants';
|
} from '../../../../../../common/constants';
|
||||||
import { KibanaServices } from '../../../../lib/kibana';
|
import { KibanaServices } from '../../../../lib/kibana';
|
||||||
import { licenseService } from '../../../../hooks/use_license';
|
|
||||||
import type { DefaultTimeRangeSetting } from '../../../../utils/default_date_settings';
|
import type { DefaultTimeRangeSetting } from '../../../../utils/default_date_settings';
|
||||||
import { plugin, renderer as Renderer } from '.';
|
import { plugin, renderer as Renderer } from '.';
|
||||||
import type { InvestigateInTimelineButtonProps } from '../../../event_details/table/investigate_in_timeline_button';
|
import type { InvestigateInTimelineButtonProps } from '../../../event_details/table/investigate_in_timeline_button';
|
||||||
|
import { useUpsellingMessage } from '../../../../hooks/use_upselling';
|
||||||
|
|
||||||
jest.mock('../../../../lib/kibana');
|
jest.mock('../../../../lib/kibana');
|
||||||
const mockGetServices = KibanaServices.get as jest.Mock;
|
const mockGetServices = KibanaServices.get as jest.Mock;
|
||||||
|
@ -59,24 +59,12 @@ const mockTimeRange = (
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
jest.mock('../../../../hooks/use_license', () => {
|
jest.mock('../../../../hooks/use_upselling');
|
||||||
const licenseServiceInstance = {
|
|
||||||
isPlatinumPlus: jest.fn(),
|
|
||||||
isEnterprise: jest.fn(() => true),
|
|
||||||
};
|
|
||||||
return {
|
|
||||||
licenseService: licenseServiceInstance,
|
|
||||||
useLicense: () => {
|
|
||||||
return licenseServiceInstance;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
const licenseServiceMock = licenseService as jest.Mocked<typeof licenseService>;
|
|
||||||
|
|
||||||
describe('insight component renderer', () => {
|
describe('insight component renderer', () => {
|
||||||
describe('when license is at least platinum plus', () => {
|
describe('when there is no upselling message', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
licenseServiceMock.isPlatinumPlus.mockReturnValue(true);
|
(useUpsellingMessage as jest.Mock).mockReturnValue(null);
|
||||||
mockTimeRange(null);
|
mockTimeRange(null);
|
||||||
});
|
});
|
||||||
it('renders correctly with valid date strings with no timestamp from results', () => {
|
it('renders correctly with valid date strings with no timestamp from results', () => {
|
||||||
|
@ -106,9 +94,9 @@ describe('insight component renderer', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('when license is not at least platinum plus', () => {
|
describe('when there is an upselling message', () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
licenseServiceMock.isPlatinumPlus.mockReturnValue(false);
|
(useUpsellingMessage as jest.Mock).mockReturnValue('Go for Platinum!');
|
||||||
mockTimeRange(null);
|
mockTimeRange(null);
|
||||||
});
|
});
|
||||||
it('renders a disabled eui button with label', () => {
|
it('renders a disabled eui button with label', () => {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
EuiSelect,
|
EuiSelect,
|
||||||
EuiFlexGroup,
|
EuiFlexGroup,
|
||||||
EuiFlexItem,
|
EuiFlexItem,
|
||||||
|
EuiToolTip,
|
||||||
} from '@elastic/eui';
|
} from '@elastic/eui';
|
||||||
import numeral from '@elastic/numeral';
|
import numeral from '@elastic/numeral';
|
||||||
import { css } from '@emotion/react';
|
import { css } from '@emotion/react';
|
||||||
|
@ -36,6 +37,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
|
||||||
import type { Filter } from '@kbn/es-query';
|
import type { Filter } from '@kbn/es-query';
|
||||||
import { FilterStateStore } from '@kbn/es-query';
|
import { FilterStateStore } from '@kbn/es-query';
|
||||||
import { useForm, FormProvider, useController } from 'react-hook-form';
|
import { useForm, FormProvider, useController } from 'react-hook-form';
|
||||||
|
import { useUpsellingMessage } from '../../../../hooks/use_upselling';
|
||||||
import { useAppToasts } from '../../../../hooks/use_app_toasts';
|
import { useAppToasts } from '../../../../hooks/use_app_toasts';
|
||||||
import { useKibana } from '../../../../lib/kibana';
|
import { useKibana } from '../../../../lib/kibana';
|
||||||
import { useInsightQuery } from './use_insight_query';
|
import { useInsightQuery } from './use_insight_query';
|
||||||
|
@ -240,19 +242,21 @@ const InsightComponent = ({
|
||||||
relativeFrom,
|
relativeFrom,
|
||||||
relativeTo,
|
relativeTo,
|
||||||
}: InsightComponentProps) => {
|
}: InsightComponentProps) => {
|
||||||
const isPlatinum = useLicense().isPlatinumPlus();
|
const insightsUpsellingMessage = useUpsellingMessage('investigation_guide');
|
||||||
|
|
||||||
if (isPlatinum === false) {
|
if (insightsUpsellingMessage) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<EuiButton
|
<EuiToolTip content={insightsUpsellingMessage}>
|
||||||
isDisabled={true}
|
<EuiButton
|
||||||
iconSide={'left'}
|
isDisabled={true}
|
||||||
iconType={'timeline'}
|
iconSide={'left'}
|
||||||
data-test-subj="insight-investigate-in-timeline-button"
|
iconType={'timeline'}
|
||||||
>
|
data-test-subj="insight-investigate-in-timeline-button"
|
||||||
{`${label}`}
|
>
|
||||||
</EuiButton>
|
{`${label}`}
|
||||||
|
</EuiButton>
|
||||||
|
</EuiToolTip>
|
||||||
<div>{description}</div>
|
<div>{description}</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -156,19 +156,26 @@ const OsqueryEditorComponent = ({
|
||||||
|
|
||||||
const OsqueryEditor = React.memo(OsqueryEditorComponent);
|
const OsqueryEditor = React.memo(OsqueryEditorComponent);
|
||||||
|
|
||||||
export const plugin = {
|
export const plugin = ({
|
||||||
name: 'osquery',
|
interactionsUpsellingMessage,
|
||||||
button: {
|
}: {
|
||||||
label: 'Osquery',
|
interactionsUpsellingMessage: string | null;
|
||||||
iconType: 'logoOsquery',
|
}) => {
|
||||||
},
|
return {
|
||||||
helpText: (
|
name: 'osquery',
|
||||||
<div>
|
button: {
|
||||||
<EuiCodeBlock language="md" fontSize="l" paddingSize="s" isCopyable>
|
label: interactionsUpsellingMessage ?? 'Osquery',
|
||||||
{'!{osquery{options}}'}
|
iconType: 'logoOsquery',
|
||||||
</EuiCodeBlock>
|
isDisabled: !!interactionsUpsellingMessage,
|
||||||
<EuiSpacer size="s" />
|
},
|
||||||
</div>
|
helpText: (
|
||||||
),
|
<div>
|
||||||
editor: OsqueryEditor,
|
<EuiCodeBlock language="md" fontSize="l" paddingSize="s" isCopyable>
|
||||||
|
{'!{osquery{options}}'}
|
||||||
|
</EuiCodeBlock>
|
||||||
|
<EuiSpacer size="s" />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
editor: OsqueryEditor,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,8 +10,9 @@ import React, { useCallback, useContext, useMemo, useState } from 'react';
|
||||||
import { reduce } from 'lodash';
|
import { reduce } from 'lodash';
|
||||||
import { i18n } from '@kbn/i18n';
|
import { i18n } from '@kbn/i18n';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { EuiButton } from '@elastic/eui';
|
import { EuiButton, EuiToolTip } from '@elastic/eui';
|
||||||
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
|
import type { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs';
|
||||||
|
import { useUpsellingMessage } from '../../../../hooks/use_upselling';
|
||||||
import { BasicAlertDataContext } from '../../../event_details/investigation_guide_view';
|
import { BasicAlertDataContext } from '../../../event_details/investigation_guide_view';
|
||||||
import { expandDottedObject } from '../../../../../../common/utils/expand_dotted';
|
import { expandDottedObject } from '../../../../../../common/utils/expand_dotted';
|
||||||
import OsqueryLogo from './osquery_icon/osquery.svg';
|
import OsqueryLogo from './osquery_icon/osquery.svg';
|
||||||
|
@ -40,6 +41,8 @@ export const OsqueryRenderer = ({
|
||||||
|
|
||||||
const handleClose = useCallback(() => setShowFlyout(false), [setShowFlyout]);
|
const handleClose = useCallback(() => setShowFlyout(false), [setShowFlyout]);
|
||||||
|
|
||||||
|
const interactionsUpsellingMessage = useUpsellingMessage('investigation_guide_interactions');
|
||||||
|
|
||||||
const ecsData = useMemo(() => {
|
const ecsData = useMemo(() => {
|
||||||
const fieldsMap: Record<string, string> = reduce(
|
const fieldsMap: Record<string, string> = reduce(
|
||||||
data,
|
data,
|
||||||
|
@ -54,12 +57,18 @@ export const OsqueryRenderer = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<StyledEuiButton iconType={OsqueryLogo} onClick={handleOpen}>
|
<EuiToolTip content={interactionsUpsellingMessage}>
|
||||||
{configuration.label ??
|
<StyledEuiButton
|
||||||
i18n.translate('xpack.securitySolution.markdown.osquery.runOsqueryButtonLabel', {
|
iconType={OsqueryLogo}
|
||||||
defaultMessage: 'Run Osquery',
|
onClick={handleOpen}
|
||||||
})}
|
disabled={!!interactionsUpsellingMessage}
|
||||||
</StyledEuiButton>
|
>
|
||||||
|
{configuration.label ??
|
||||||
|
i18n.translate('xpack.securitySolution.markdown.osquery.runOsqueryButtonLabel', {
|
||||||
|
defaultMessage: 'Run Osquery',
|
||||||
|
})}
|
||||||
|
</StyledEuiButton>
|
||||||
|
</EuiToolTip>
|
||||||
{showFlyout && (
|
{showFlyout && (
|
||||||
<OsqueryFlyout
|
<OsqueryFlyout
|
||||||
defaultValues={{
|
defaultValues={{
|
||||||
|
|
|
@ -75,18 +75,25 @@ const TimelineEditorComponent: React.FC<TimelineEditorProps> = ({ onClosePopover
|
||||||
|
|
||||||
const TimelineEditor = memo(TimelineEditorComponent);
|
const TimelineEditor = memo(TimelineEditorComponent);
|
||||||
|
|
||||||
export const plugin: EuiMarkdownEditorUiPlugin = {
|
export const plugin = ({
|
||||||
name: ID,
|
interactionsUpsellingMessage,
|
||||||
button: {
|
}: {
|
||||||
label: i18n.INSERT_TIMELINE,
|
interactionsUpsellingMessage: string | null;
|
||||||
iconType: 'timeline',
|
}): EuiMarkdownEditorUiPlugin => {
|
||||||
},
|
return {
|
||||||
helpText: (
|
name: ID,
|
||||||
<EuiCodeBlock language="md" paddingSize="s" fontSize="l">
|
button: {
|
||||||
{'[title](url)'}
|
label: interactionsUpsellingMessage ?? i18n.INSERT_TIMELINE,
|
||||||
</EuiCodeBlock>
|
iconType: 'timeline',
|
||||||
),
|
isDisabled: !!interactionsUpsellingMessage,
|
||||||
editor: function editor({ node, onSave, onCancel }) {
|
},
|
||||||
return <TimelineEditor onClosePopover={onCancel} onInsert={onSave} />;
|
helpText: (
|
||||||
},
|
<EuiCodeBlock language="md" paddingSize="s" fontSize="l">
|
||||||
|
{'[title](url)'}
|
||||||
|
</EuiCodeBlock>
|
||||||
|
),
|
||||||
|
editor: function editor({ node, onSave, onCancel }) {
|
||||||
|
return <TimelineEditor onClosePopover={onCancel} onInsert={onSave} />;
|
||||||
|
},
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import React, { useCallback, memo } from 'react';
|
import React, { useCallback, memo } from 'react';
|
||||||
import { EuiToolTip, EuiLink } from '@elastic/eui';
|
import { EuiToolTip, EuiLink } from '@elastic/eui';
|
||||||
|
|
||||||
|
import { useUpsellingMessage } from '../../../../hooks/use_upselling';
|
||||||
import { useTimelineClick } from '../../../../utils/timeline/use_timeline_click';
|
import { useTimelineClick } from '../../../../utils/timeline/use_timeline_click';
|
||||||
import type { TimelineProps } from './types';
|
import type { TimelineProps } from './types';
|
||||||
import * as i18n from './translations';
|
import * as i18n from './translations';
|
||||||
|
@ -20,6 +21,8 @@ export const TimelineMarkDownRendererComponent: React.FC<TimelineProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const { addError } = useAppToasts();
|
const { addError } = useAppToasts();
|
||||||
|
|
||||||
|
const interactionsUpsellingMessage = useUpsellingMessage('investigation_guide_interactions');
|
||||||
|
|
||||||
const handleTimelineClick = useTimelineClick();
|
const handleTimelineClick = useTimelineClick();
|
||||||
|
|
||||||
const onError = useCallback(
|
const onError = useCallback(
|
||||||
|
@ -37,8 +40,12 @@ export const TimelineMarkDownRendererComponent: React.FC<TimelineProps> = ({
|
||||||
[id, graphEventId, handleTimelineClick, onError]
|
[id, graphEventId, handleTimelineClick, onError]
|
||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<EuiToolTip content={i18n.TIMELINE_ID(id ?? '')}>
|
<EuiToolTip content={interactionsUpsellingMessage ?? i18n.TIMELINE_ID(id ?? '')}>
|
||||||
<EuiLink onClick={onClickTimeline} data-test-subj={`markdown-timeline-link-${id}`}>
|
<EuiLink
|
||||||
|
onClick={onClickTimeline}
|
||||||
|
disabled={!!interactionsUpsellingMessage}
|
||||||
|
data-test-subj={`markdown-timeline-link-${id}`}
|
||||||
|
>
|
||||||
{title}
|
{title}
|
||||||
</EuiLink>
|
</EuiLink>
|
||||||
</EuiToolTip>
|
</EuiToolTip>
|
||||||
|
|
|
@ -11,6 +11,8 @@ import { render } from '@testing-library/react';
|
||||||
import { removeExternalLinkText } from '@kbn/securitysolution-io-ts-utils';
|
import { removeExternalLinkText } from '@kbn/securitysolution-io-ts-utils';
|
||||||
import { TestProviders } from '../../mock';
|
import { TestProviders } from '../../mock';
|
||||||
import { MarkdownRenderer } from './renderer';
|
import { MarkdownRenderer } from './renderer';
|
||||||
|
import { UpsellingService } from '@kbn/security-solution-upselling/service';
|
||||||
|
import { UpsellingProvider } from '../upselling_provider';
|
||||||
|
|
||||||
jest.mock('../../utils/default_date_settings', () => {
|
jest.mock('../../utils/default_date_settings', () => {
|
||||||
const original = jest.requireActual('../../utils/default_date_settings');
|
const original = jest.requireActual('../../utils/default_date_settings');
|
||||||
|
@ -59,6 +61,8 @@ jest.mock('../../hooks/use_app_toasts', () => ({
|
||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const mockUpselling = new UpsellingService();
|
||||||
|
|
||||||
describe('Markdown', () => {
|
describe('Markdown', () => {
|
||||||
describe('markdown links', () => {
|
describe('markdown links', () => {
|
||||||
const markdownWithLink = 'A link to an external site [External Site](https://google.com)';
|
const markdownWithLink = 'A link to an external site [External Site](https://google.com)';
|
||||||
|
@ -114,7 +118,9 @@ describe('Markdown', () => {
|
||||||
test('displays an upgrade message with a premium markdown plugin', () => {
|
test('displays an upgrade message with a premium markdown plugin', () => {
|
||||||
const { queryByText, getByText } = render(
|
const { queryByText, getByText } = render(
|
||||||
<TestProviders>
|
<TestProviders>
|
||||||
<MarkdownRenderer>{`!{investigate{"label": "", "providers": [[{"field": "event.id", "value": "{{kibana.alert.original_event.id}}", "queryType": "phrase", "excluded": "false"}]]}}`}</MarkdownRenderer>
|
<UpsellingProvider upsellingService={mockUpselling}>
|
||||||
|
<MarkdownRenderer>{`!{investigate{"label": "", "providers": [[{"field": "event.id", "value": "{{kibana.alert.original_event.id}}", "queryType": "phrase", "excluded": "false"}]]}}`}</MarkdownRenderer>
|
||||||
|
</UpsellingProvider>
|
||||||
</TestProviders>
|
</TestProviders>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ export const PLI_PRODUCT_FEATURES: PliProductFeatures = {
|
||||||
ProductFeatureKey.advancedInsights,
|
ProductFeatureKey.advancedInsights,
|
||||||
ProductFeatureKey.assistant,
|
ProductFeatureKey.assistant,
|
||||||
ProductFeatureKey.investigationGuide,
|
ProductFeatureKey.investigationGuide,
|
||||||
|
ProductFeatureKey.investigationGuideInteractions,
|
||||||
ProductFeatureKey.threatIntelligence,
|
ProductFeatureKey.threatIntelligence,
|
||||||
ProductFeatureKey.casesConnectors,
|
ProductFeatureKey.casesConnectors,
|
||||||
ProductFeatureKey.externalRuleActions,
|
ProductFeatureKey.externalRuleActions,
|
||||||
|
|
|
@ -14,7 +14,10 @@ import type {
|
||||||
} from '@kbn/security-solution-upselling/service/types';
|
} from '@kbn/security-solution-upselling/service/types';
|
||||||
import type { UpsellingService } from '@kbn/security-solution-upselling/service';
|
import type { UpsellingService } from '@kbn/security-solution-upselling/service';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { UPGRADE_INVESTIGATION_GUIDE } from '@kbn/security-solution-upselling/messages';
|
import {
|
||||||
|
UPGRADE_INVESTIGATION_GUIDE,
|
||||||
|
UPGRADE_INVESTIGATION_GUIDE_INTERACTIONS,
|
||||||
|
} from '@kbn/security-solution-upselling/messages';
|
||||||
import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
|
import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
|
||||||
import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
|
import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
|
||||||
import {
|
import {
|
||||||
|
@ -163,4 +166,11 @@ export const upsellingMessages: UpsellingMessages = [
|
||||||
getProductTypeByPLI(ProductFeatureKey.investigationGuide) ?? ''
|
getProductTypeByPLI(ProductFeatureKey.investigationGuide) ?? ''
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'investigation_guide_interactions',
|
||||||
|
pli: ProductFeatureKey.investigationGuideInteractions,
|
||||||
|
message: UPGRADE_INVESTIGATION_GUIDE_INTERACTIONS(
|
||||||
|
getProductTypeByPLI(ProductFeatureKey.investigationGuideInteractions) ?? ''
|
||||||
|
),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue