Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ import {
PREVALENCE_DETAILS_TABLE_DOC_COUNT_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_FIELD_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_HOST_PREVALENCE_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_TEST_ID,
PREVALENCE_DETAILS_TABLE_PREVIEW_LINK_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_TEST_ID,
PREVALENCE_DETAILS_UPSELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_UPSELL_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_PREVIEW_LINK_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_UPSELL_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_TEST_ID,
PREVALENCE_DETAILS_UPSELL_TEST_ID,
} from './test_ids';
import { usePrevalence } from '../../shared/hooks/use_prevalence';
import { TestProviders } from '../../../../common/mock';
Expand All @@ -38,11 +38,13 @@ jest.mock('@kbn/expandable-flyout');
jest.mock('../../../../common/components/user_privileges');

const mockedTelemetry = createTelemetryServiceMock();
const mockStorage = jest.fn();
jest.mock('../../../../common/lib/kibana', () => {
return {
useKibana: () => ({
services: {
telemetry: mockedTelemetry,
storage: { get: mockStorage },
},
}),
};
Expand Down Expand Up @@ -403,4 +405,26 @@ describe('PrevalenceDetails', () => {
const { getByText } = renderPrevalenceDetails();
expect(getByText(NO_DATA_MESSAGE)).toBeInTheDocument();
});

it('should use default interval values to fetch prevalence data', () => {
renderPrevalenceDetails();

expect(usePrevalence).toHaveBeenCalledWith(
expect.objectContaining({
interval: { from: 'now-30d', to: 'now' },
})
);
});

it('should use values from local storage to fetch prevalence data', () => {
mockStorage.mockReturnValue({ start: 'now-7d', end: 'now-3d' });

renderPrevalenceDetails();

expect(usePrevalence).toHaveBeenCalledWith(
expect.objectContaining({
interval: { from: 'now-7d', to: 'now-3d' },
})
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -22,25 +22,27 @@ import {
useEuiTheme,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useKibana } from '../../../../common/lib/kibana';
import { FLYOUT_STORAGE_KEYS } from '../../shared/constants/local_storage';
import { FormattedCount } from '../../../../common/components/formatted_number';
import { useLicense } from '../../../../common/hooks/use_license';
import { InvestigateInTimelineButton } from '../../../../common/components/event_details/investigate_in_timeline_button';
import type { PrevalenceData } from '../../shared/hooks/use_prevalence';
import { usePrevalence } from '../../shared/hooks/use_prevalence';
import {
PREVALENCE_DETAILS_DATE_PICKER_TEST_ID,
PREVALENCE_DETAILS_TABLE_ALERT_COUNT_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_COUNT_TEXT_BUTTON_TEST_ID,
PREVALENCE_DETAILS_TABLE_DOC_COUNT_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_HOST_PREVALENCE_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_FIELD_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_HOST_PREVALENCE_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_TEST_ID,
PREVALENCE_DETAILS_TABLE_PREVIEW_LINK_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID,
PREVALENCE_DETAILS_DATE_PICKER_TEST_ID,
PREVALENCE_DETAILS_TABLE_TEST_ID,
PREVALENCE_DETAILS_UPSELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_UPSELL_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_INVESTIGATE_IN_TIMELINE_BUTTON_TEST_ID,
PREVALENCE_DETAILS_TABLE_COUNT_TEXT_BUTTON_TEST_ID,
PREVALENCE_DETAILS_TABLE_USER_PREVALENCE_CELL_TEST_ID,
PREVALENCE_DETAILS_TABLE_VALUE_CELL_TEST_ID,
PREVALENCE_DETAILS_UPSELL_TEST_ID,
} from './test_ids';
import { useDocumentDetailsContext } from '../../shared/context';
import {
Expand Down Expand Up @@ -353,6 +355,8 @@ const columns: Array<EuiBasicTableColumn<PrevalenceDetailsRow>> = [
* Prevalence table displayed in the document details expandable flyout left section under the Insights tab
*/
export const PrevalenceDetails: React.FC = () => {
const { storage } = useKibana().services;

const { dataFormattedForFieldBrowser, investigationFields, scopeId } =
useDocumentDetailsContext();

Expand All @@ -362,16 +366,18 @@ export const PrevalenceDetails: React.FC = () => {

const isPlatinumPlus = useLicense().isPlatinumPlus();

const timeSavedInLocalStorage = storage.get(FLYOUT_STORAGE_KEYS.PREVALENCE_TIME_RANGE);

// these two are used by the usePrevalence hook to fetch the data
const [start, setStart] = useState(DEFAULT_FROM);
const [end, setEnd] = useState(DEFAULT_TO);
const [start, setStart] = useState(timeSavedInLocalStorage?.start || DEFAULT_FROM);
const [end, setEnd] = useState(timeSavedInLocalStorage?.end || DEFAULT_TO);

// these two are used to pass to timeline
const [absoluteStart, setAbsoluteStart] = useState(
(dateMath.parse(DEFAULT_FROM) || new Date()).toISOString()
(dateMath.parse(timeSavedInLocalStorage?.start || DEFAULT_FROM) || new Date()).toISOString()
);
const [absoluteEnd, setAbsoluteEnd] = useState(
(dateMath.parse(DEFAULT_TO) || new Date()).toISOString()
(dateMath.parse(timeSavedInLocalStorage?.end || DEFAULT_TO) || new Date()).toISOString()
);

// TODO update the logic to use a single set of start/end dates
Expand All @@ -380,6 +386,8 @@ export const PrevalenceDetails: React.FC = () => {
const onTimeChange = ({ start: s, end: e, isInvalid }: OnTimeChangeProps) => {
if (isInvalid) return;

storage.set(FLYOUT_STORAGE_KEYS.PREVALENCE_TIME_RANGE, { start: s, end: e });

setStart(s);
setEnd(e);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import React from 'react';
import { PrevalenceOverview } from './prevalence_overview';
import {
EXPANDABLE_PANEL_HEADER_RIGHT_SECTION_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID,
EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID,
Expand All @@ -25,8 +26,10 @@ import {
import { usePrevalence } from '../../shared/hooks/use_prevalence';
import { mockContextValue } from '../../shared/mocks/mock_context';
import { useNavigateToLeftPanel } from '../../shared/hooks/use_navigate_to_left_panel';
import { useKibana } from '../../../../common/lib/kibana';

jest.mock('../../shared/hooks/use_prevalence');
jest.mock('../../../../common/lib/kibana');

const mockNavigateToLeftPanel = jest.fn();
jest.mock('../../shared/hooks/use_navigate_to_left_panel');
Expand All @@ -35,6 +38,8 @@ const TOGGLE_ICON_TEST_ID = EXPANDABLE_PANEL_TOGGLE_ICON_TEST_ID(PREVALENCE_TEST
const TITLE_LINK_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_LINK_TEST_ID(PREVALENCE_TEST_ID);
const TITLE_ICON_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_ICON_TEST_ID(PREVALENCE_TEST_ID);
const TITLE_TEXT_TEST_ID = EXPANDABLE_PANEL_HEADER_TITLE_TEXT_TEST_ID(PREVALENCE_TEST_ID);
const RIGHT_SECTION_TEXT_TEST_ID =
EXPANDABLE_PANEL_HEADER_RIGHT_SECTION_TEST_ID(PREVALENCE_TEST_ID);

const NO_DATA_MESSAGE = 'No prevalence data available.';

Expand All @@ -49,6 +54,7 @@ const renderPrevalenceOverview = (contextValue: DocumentDetailsContext = mockCon

describe('<PrevalenceOverview />', () => {
beforeEach(() => {
jest.clearAllMocks();
(usePrevalence as jest.Mock).mockReturnValue({
loading: false,
error: false,
Expand All @@ -58,6 +64,13 @@ describe('<PrevalenceOverview />', () => {
navigateToLeftPanel: mockNavigateToLeftPanel,
isEnabled: true,
});
(useKibana as jest.Mock).mockReturnValue({
services: {
storage: {
get: () => undefined,
},
},
});
});

it('should render wrapper component', () => {
Expand All @@ -69,6 +82,26 @@ describe('<PrevalenceOverview />', () => {
expect(queryByTestId(TITLE_TEXT_TEST_ID)).not.toBeInTheDocument();
});

it('should show default time range badge', () => {
const { getByTestId } = renderPrevalenceOverview();

expect(getByTestId(RIGHT_SECTION_TEXT_TEST_ID)).toHaveTextContent('Time range applied');
});

it('should show custom time range badge', () => {
(useKibana as jest.Mock).mockReturnValue({
services: {
storage: {
get: () => ({ from: 'now-7d', to: 'now-3d' }),
},
},
});

const { getByTestId } = renderPrevalenceOverview();

expect(getByTestId(RIGHT_SECTION_TEXT_TEST_ID)).toHaveTextContent('Custom time range applied');
});

it('should render link without icon if isPreviewMode is true', () => {
const { getByTestId, queryByTestId } = renderPrevalenceOverview({
...mockContextValue,
Expand Down Expand Up @@ -191,4 +224,32 @@ describe('<PrevalenceOverview />', () => {
getByTestId(TITLE_LINK_TEST_ID).click();
expect(mockNavigateToLeftPanel).toHaveBeenCalled();
});

it('should use default interval values to fetch prevalence data', () => {
renderPrevalenceOverview();

expect(usePrevalence).toHaveBeenCalledWith(
expect.objectContaining({
interval: { from: 'now-30d', to: 'now' },
})
);
});

it('should use values from local storage to fetch prevalence data', () => {
(useKibana as jest.Mock).mockReturnValue({
services: {
storage: {
get: () => ({ start: 'now-7d', end: 'now-3d' }),
},
},
});

renderPrevalenceOverview();

expect(usePrevalence).toHaveBeenCalledWith(
expect.objectContaining({
interval: { from: 'now-7d', to: 'now-3d' },
})
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@

import type { FC } from 'react';
import React, { useMemo } from 'react';
import { EuiBadge, EuiFlexGroup } from '@elastic/eui';
import { EuiBadge, EuiFlexGroup, EuiToolTip } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { useKibana } from '../../../../common/lib/kibana';
import { ExpandablePanel } from '../../../shared/components/expandable_panel';
import { usePrevalence } from '../../shared/hooks/use_prevalence';
import { PREVALENCE_TEST_ID } from './test_ids';
Expand All @@ -17,13 +18,38 @@ import { LeftPanelInsightsTab } from '../../left';
import { PREVALENCE_TAB_ID } from '../../left/components/prevalence_details';
import { InsightsSummaryRow } from './insights_summary_row';
import { useNavigateToLeftPanel } from '../../shared/hooks/use_navigate_to_left_panel';
import { FLYOUT_STORAGE_KEYS } from '../../shared/constants/local_storage';

const UNCOMMON = (
<FormattedMessage
id="xpack.securitySolution.flyout.right.insights.prevalence.uncommonLabel"
defaultMessage="Uncommon"
/>
);
const DEFAULT_TIME_RANGE_LABEL = (
<FormattedMessage
id="xpack.securitySolution.flyout.right.insights.threatIntelligence.defaultTimeRangeApplied.badgeLabel"
defaultMessage="Time range applied"
/>
);
const CUSTOM_TIME_RANGE_LABEL = (
<FormattedMessage
id="xpack.securitySolution.flyout.right.insights.threatIntelligence.customTimeRangeApplied.badgeLabel"
defaultMessage="Custom time range applied"
/>
);
const DEFAULT_TIME_RANGE_TOOLTIP = (
<FormattedMessage
id="xpack.securitySolution.flyout.right.insights.threatIntelligence.custom-time-range-applied-tooltip"
defaultMessage="Prevalence measures how frequently data from this alert is observed across hosts or users in your environment over the last 30 days. To choose a custom time range, click the section title, then use the date time picker in the left panel."
/>
);
const CUSTOM_TIME_RANGE_TOOLTIP = (
<FormattedMessage
id="xpack.securitySolution.flyout.right.insights.threatIntelligence.custom-time-range-applied-tooltip"
defaultMessage="Prevalence measures how frequently data from this alert is observed across hosts or users in your environment over the time range that you chose. To choose a different custom time range, click the section title, then use the date time picker in the left panel."
/>
);

const PERCENTAGE_THRESHOLD = 0.1; // we show the prevalence if its value is below 10%
const DEFAULT_FROM = 'now-30d';
Expand All @@ -34,6 +60,9 @@ const DEFAULT_TO = 'now';
* The component fetches the necessary data at once. The loading and error states are handled by the ExpandablePanel component.
*/
export const PrevalenceOverview: FC = () => {
const { storage } = useKibana().services;
const timeSavedInLocalStorage = storage.get(FLYOUT_STORAGE_KEYS.PREVALENCE_TIME_RANGE);

const { dataFormattedForFieldBrowser, investigationFields, isPreviewMode } =
useDocumentDetailsContext();

Expand All @@ -47,8 +76,8 @@ export const PrevalenceOverview: FC = () => {
dataFormattedForFieldBrowser,
investigationFields,
interval: {
from: DEFAULT_FROM,
to: DEFAULT_TO,
from: timeSavedInLocalStorage?.start || DEFAULT_FROM,
to: timeSavedInLocalStorage?.end || DEFAULT_TO,
},
});

Expand All @@ -63,6 +92,7 @@ export const PrevalenceOverview: FC = () => {
),
[data]
);

const link = useMemo(
() =>
isLinkEnabled
Expand Down Expand Up @@ -90,6 +120,17 @@ export const PrevalenceOverview: FC = () => {
),
link,
iconType: !isPreviewMode ? 'arrowStart' : undefined,
headerContent: (
<EuiToolTip
content={
timeSavedInLocalStorage ? CUSTOM_TIME_RANGE_TOOLTIP : DEFAULT_TIME_RANGE_TOOLTIP
}
>
<EuiBadge color="hollow" iconSide="left" iconType="clock" tabIndex={0}>
{timeSavedInLocalStorage ? CUSTOM_TIME_RANGE_LABEL : DEFAULT_TIME_RANGE_LABEL}
</EuiBadge>
</EuiToolTip>
),
}}
content={{ loading, error }}
data-test-subj={PREVALENCE_TEST_ID}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export const FLYOUT_STORAGE_KEYS = {
OVERVIEW_TAB_EXPANDED_SECTIONS:
'securitySolution.documentDetailsFlyout.overviewSectionExpanded.v8.14',
RIGHT_PANEL_SELECTED_TABS: 'securitySolution.documentDetailsFlyout.rightPanel.selectedTabs.v8.14',
PREVALENCE_TIME_RANGE: 'securitySolution.documentDetailsFlyout.prevalenceTimeRange',
};