mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[SecuritySolution] Add UI tracking for grouping options (#151708)
## Summary Relevant issues: Overall telemetry: https://github.com/elastic/kibana/issues/144945 Alerts telemetry: https://github.com/elastic/kibana/issues/150656 Preview dashboard:40755fc0
-b454-11ed-a6e6-d32d2209b7b7?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-7d%2Fd,to:now)) **UI counter added** - overall counts 1. alerts_table_group_by_{tableId}_{groupByField} - `alerts_table_group_by_alerts-page_host.name ` triggered on grouping option changed. 2. alerts_table_toggled_{on|off}_{tableId}_group-{groupNumber} - `alerts_table_toggled_off_alerts-page_group-0` sent when grouped alerts toggled 3. alerts_table_{tableId}_group-{groupNumber}_mark-{status} - `alerts_table_alerts-page_group-0_mark-open` sent when group actions taken **Event based telemetry added** - extra info from `properties` can be aggregated / visualised 1. Alerts grouping take action - sent when group actions taken 2. Alerts grouping toggled - sent when grouped alerts toggled 3. Alerts grouping changed - triggered on grouping option changed [Example events](9b0f2080
-bcd1-11ed-a6e6-d32d2209b7b7?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-7d%2Fd,to:now))&_a=(columns:!(context.applicationId,properties,properties.groupingId,properties.groupNumber,properties.status,event_type,properties.tableId),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:c5dc7cd0-2950-4e51-b428-d0451b1b8d9d,key:context.applicationId,negate:!f,params:(query:securitySolutionUI),type:phrase),query:(match_phrase:(context.applicationId:securitySolutionUI)))),grid:(),hideChart:!f,index:c5dc7cd0-2950-4e51-b428-d0451b1b8d9d,interval:auto,query:(language:kuery,query:'event_type%20:%20Alerts%20Grouping*'),sort:!(!(timestamp,desc)))) **Steps to verify:** 1. add telemetry.optIn: true to kibana.dev.yml 2. Visit alerts page or rule details page, change the grouping , toggle each group, and take actions to grouped alerts 3. Usually the event would be sent every hour to [staging](9b0f2080
-bcd1-11ed-a6e6-d32d2209b7b7?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time:(from:now-7d%2Fd,to:now))&_a=(columns:!(context.applicationId,properties,properties.groupingId,properties.groupNumber,properties.status,event_type,properties.tableId),filters:!(('$state':(store:appState),meta:(alias:!n,disabled:!f,index:c5dc7cd0-2950-4e51-b428-d0451b1b8d9d,key:context.applicationId,negate:!f,params:(query:securitySolutionUI),type:phrase),query:(match_phrase:(context.applicationId:securitySolutionUI)))),grid:(),hideChart:!f,index:c5dc7cd0-2950-4e51-b428-d0451b1b8d9d,interval:auto,query:(language:kuery,query:'event_type%20:%20Alerts%20Grouping*'),sort:!(!(timestamp,desc)))), if not, visit staging again on the next day. ### 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 --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Pablo Neves Machado <pablo.nevesmachado@elastic.co>
This commit is contained in:
parent
6e3a34fdda
commit
a564ca5fe3
24 changed files with 719 additions and 24 deletions
|
@ -12,6 +12,7 @@ import React from 'react';
|
|||
|
||||
const onGroupChange = jest.fn();
|
||||
const testProps = {
|
||||
groupingId: 'test-grouping-id',
|
||||
fields: [
|
||||
{
|
||||
name: 'kibana.alert.rule.name',
|
||||
|
|
|
@ -20,6 +20,7 @@ import { StyledContextMenu, StyledEuiButtonEmpty } from '../styles';
|
|||
export interface GroupSelectorProps {
|
||||
'data-test-subj'?: string;
|
||||
fields: FieldSpec[];
|
||||
groupingId: string;
|
||||
groupSelected: string;
|
||||
onGroupChange: (groupSelection: string) => void;
|
||||
options: Array<{ key: string; label: string }>;
|
||||
|
|
|
@ -11,9 +11,12 @@ import React from 'react';
|
|||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { Grouping } from './grouping';
|
||||
import { createGroupFilter } from './accordion_panel/helpers';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
import { getTelemetryEvent } from '../telemetry/const';
|
||||
|
||||
const renderChildComponent = jest.fn();
|
||||
const takeActionItems = jest.fn();
|
||||
const mockTracker = jest.fn();
|
||||
const rule1Name = 'Rule 1 name';
|
||||
const rule1Desc = 'Rule 1 description';
|
||||
const rule2Name = 'Rule 2 name';
|
||||
|
@ -98,6 +101,7 @@ const testProps = {
|
|||
value: 2,
|
||||
},
|
||||
},
|
||||
groupingId: 'test-grouping-id',
|
||||
isLoading: false,
|
||||
pagination: {
|
||||
pageIndex: 0,
|
||||
|
@ -109,6 +113,7 @@ const testProps = {
|
|||
renderChildComponent,
|
||||
selectedGroup: 'kibana.alert.rule.name',
|
||||
takeActionItems,
|
||||
tracker: mockTracker,
|
||||
};
|
||||
|
||||
describe('grouping container', () => {
|
||||
|
@ -171,4 +176,33 @@ describe('grouping container', () => {
|
|||
createGroupFilter(testProps.selectedGroup, rule2Name)
|
||||
);
|
||||
});
|
||||
|
||||
it('Send Telemetry when each group is clicked', () => {
|
||||
const { getAllByTestId } = render(
|
||||
<I18nProvider>
|
||||
<Grouping {...testProps} />
|
||||
</I18nProvider>
|
||||
);
|
||||
const group1 = within(getAllByTestId('grouping-accordion')[0]).getAllByRole('button')[0];
|
||||
fireEvent.click(group1);
|
||||
expect(mockTracker).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
METRIC_TYPE.CLICK,
|
||||
getTelemetryEvent.groupToggled({
|
||||
isOpen: true,
|
||||
groupingId: testProps.groupingId,
|
||||
groupNumber: 0,
|
||||
})
|
||||
);
|
||||
fireEvent.click(group1);
|
||||
expect(mockTracker).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
METRIC_TYPE.CLICK,
|
||||
getTelemetryEvent.groupToggled({
|
||||
isOpen: false,
|
||||
groupingId: testProps.groupingId,
|
||||
groupNumber: 0,
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
} from '@elastic/eui';
|
||||
import type { Filter } from '@kbn/es-query';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics';
|
||||
import { defaultUnit, firstNonNullValue } from '../helpers';
|
||||
import { createGroupFilter } from './accordion_panel/helpers';
|
||||
import type { BadgeMetric, CustomMetric } from './accordion_panel';
|
||||
|
@ -24,15 +25,23 @@ import { EmptyGroupingComponent } from './empty_results_panel';
|
|||
import { groupingContainerCss, countCss } from './styles';
|
||||
import { GROUPS_UNIT } from './translations';
|
||||
import type { GroupingAggregation, GroupingFieldTotalAggregation, RawBucket } from './types';
|
||||
import { getTelemetryEvent } from '../telemetry/const';
|
||||
|
||||
export interface GroupingProps<T> {
|
||||
badgeMetricStats?: (fieldBucket: RawBucket<T>) => BadgeMetric[];
|
||||
customMetricStats?: (fieldBucket: RawBucket<T>) => CustomMetric[];
|
||||
data?: GroupingAggregation<T> & GroupingFieldTotalAggregation;
|
||||
groupingId: string;
|
||||
groupPanelRenderer?: (fieldBucket: RawBucket<T>) => JSX.Element | undefined;
|
||||
groupSelector?: JSX.Element;
|
||||
inspectButton?: JSX.Element;
|
||||
isLoading: boolean;
|
||||
onToggleCallback?: (params: {
|
||||
isOpen: boolean;
|
||||
groupName?: string | undefined;
|
||||
groupNumber: number;
|
||||
groupingId: string;
|
||||
}) => void;
|
||||
pagination: {
|
||||
pageIndex: number;
|
||||
pageSize: number;
|
||||
|
@ -42,7 +51,12 @@ export interface GroupingProps<T> {
|
|||
};
|
||||
renderChildComponent: (groupFilter: Filter[]) => React.ReactNode;
|
||||
selectedGroup: string;
|
||||
takeActionItems: (groupFilters: Filter[]) => JSX.Element[];
|
||||
takeActionItems: (groupFilters: Filter[], groupNumber: number) => JSX.Element[];
|
||||
tracker?: (
|
||||
type: UiCounterMetricType,
|
||||
event: string | string[],
|
||||
count?: number | undefined
|
||||
) => void;
|
||||
unit?: (n: number) => string;
|
||||
}
|
||||
|
||||
|
@ -50,14 +64,17 @@ const GroupingComponent = <T,>({
|
|||
badgeMetricStats,
|
||||
customMetricStats,
|
||||
data,
|
||||
groupingId,
|
||||
groupPanelRenderer,
|
||||
groupSelector,
|
||||
inspectButton,
|
||||
isLoading,
|
||||
onToggleCallback,
|
||||
pagination,
|
||||
renderChildComponent,
|
||||
selectedGroup,
|
||||
takeActionItems,
|
||||
tracker,
|
||||
unit = defaultUnit,
|
||||
}: GroupingProps<T>) => {
|
||||
const [trigger, setTrigger] = useState<
|
||||
|
@ -77,9 +94,9 @@ const GroupingComponent = <T,>({
|
|||
|
||||
const groupPanels = useMemo(
|
||||
() =>
|
||||
data?.stackByMultipleFields0?.buckets?.map((groupBucket) => {
|
||||
data?.stackByMultipleFields0?.buckets?.map((groupBucket, groupNumber) => {
|
||||
const group = firstNonNullValue(groupBucket.key);
|
||||
const groupKey = `group0-${group}`;
|
||||
const groupKey = `group-${groupNumber}-${group}`;
|
||||
|
||||
return (
|
||||
<span key={groupKey}>
|
||||
|
@ -87,7 +104,10 @@ const GroupingComponent = <T,>({
|
|||
extraAction={
|
||||
<GroupStats
|
||||
bucket={groupBucket}
|
||||
takeActionItems={takeActionItems(createGroupFilter(selectedGroup, group))}
|
||||
takeActionItems={takeActionItems(
|
||||
createGroupFilter(selectedGroup, group),
|
||||
groupNumber
|
||||
)}
|
||||
badgeMetricStats={badgeMetricStats && badgeMetricStats(groupBucket)}
|
||||
customMetricStats={customMetricStats && customMetricStats(groupBucket)}
|
||||
/>
|
||||
|
@ -97,6 +117,11 @@ const GroupingComponent = <T,>({
|
|||
groupPanelRenderer={groupPanelRenderer && groupPanelRenderer(groupBucket)}
|
||||
isLoading={isLoading}
|
||||
onToggleGroup={(isOpen) => {
|
||||
// built-in telemetry: UI-counter
|
||||
tracker?.(
|
||||
METRIC_TYPE.CLICK,
|
||||
getTelemetryEvent.groupToggled({ isOpen, groupingId, groupNumber })
|
||||
);
|
||||
setTrigger({
|
||||
// ...trigger, -> this change will keep only one group at a time expanded and one table displayed
|
||||
[groupKey]: {
|
||||
|
@ -104,6 +129,7 @@ const GroupingComponent = <T,>({
|
|||
selectedBucket: groupBucket,
|
||||
},
|
||||
});
|
||||
onToggleCallback?.({ isOpen, groupName: group, groupNumber, groupingId });
|
||||
}}
|
||||
renderChildComponent={
|
||||
trigger[groupKey] && trigger[groupKey].state === 'open'
|
||||
|
@ -121,10 +147,13 @@ const GroupingComponent = <T,>({
|
|||
customMetricStats,
|
||||
data?.stackByMultipleFields0?.buckets,
|
||||
groupPanelRenderer,
|
||||
groupingId,
|
||||
isLoading,
|
||||
onToggleCallback,
|
||||
renderChildComponent,
|
||||
selectedGroup,
|
||||
takeActionItems,
|
||||
tracker,
|
||||
trigger,
|
||||
]
|
||||
);
|
||||
|
|
|
@ -10,6 +10,7 @@ import { act, renderHook } from '@testing-library/react-hooks';
|
|||
import { useGetGroupSelector } from './use_get_group_selector';
|
||||
import { initialState } from './state';
|
||||
import { ActionType, defaultGroup } from '..';
|
||||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
|
||||
const defaultGroupingOptions = [
|
||||
{ label: 'ruleName', key: 'kibana.alert.rule.name' },
|
||||
|
@ -25,6 +26,8 @@ const defaultArgs = {
|
|||
fields: [],
|
||||
groupingId,
|
||||
groupingState: initialState,
|
||||
tracker: jest.fn(),
|
||||
onGroupChangeCallback: jest.fn(),
|
||||
};
|
||||
const customField = 'custom.field';
|
||||
describe('useGetGroupSelector', () => {
|
||||
|
@ -123,6 +126,54 @@ describe('useGetGroupSelector', () => {
|
|||
expect(dispatch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('On group change, sends telemetry', () => {
|
||||
const testGroup = {
|
||||
[groupingId]: {
|
||||
...defaultGroup,
|
||||
options: defaultGroupingOptions,
|
||||
activeGroup: 'host.name',
|
||||
},
|
||||
};
|
||||
const { result } = renderHook((props) => useGetGroupSelector(props), {
|
||||
initialProps: {
|
||||
...defaultArgs,
|
||||
groupingState: {
|
||||
groupById: testGroup,
|
||||
},
|
||||
},
|
||||
});
|
||||
act(() => result.current.props.onGroupChange(customField));
|
||||
expect(defaultArgs.tracker).toHaveBeenCalledTimes(1);
|
||||
expect(defaultArgs.tracker).toHaveBeenCalledWith(
|
||||
METRIC_TYPE.CLICK,
|
||||
`alerts_table_group_by_test-table_${customField}`
|
||||
);
|
||||
});
|
||||
|
||||
it('On group change, executes callback', () => {
|
||||
const testGroup = {
|
||||
[groupingId]: {
|
||||
...defaultGroup,
|
||||
options: defaultGroupingOptions,
|
||||
activeGroup: 'host.name',
|
||||
},
|
||||
};
|
||||
const { result } = renderHook((props) => useGetGroupSelector(props), {
|
||||
initialProps: {
|
||||
...defaultArgs,
|
||||
groupingState: {
|
||||
groupById: testGroup,
|
||||
},
|
||||
},
|
||||
});
|
||||
act(() => result.current.props.onGroupChange(customField));
|
||||
expect(defaultArgs.onGroupChangeCallback).toHaveBeenCalledTimes(1);
|
||||
expect(defaultArgs.onGroupChangeCallback).toHaveBeenCalledWith({
|
||||
tableId: groupingId,
|
||||
groupByField: customField,
|
||||
});
|
||||
});
|
||||
|
||||
it('On group change to custom field, updates options', () => {
|
||||
const testGroup = {
|
||||
[groupingId]: {
|
||||
|
|
|
@ -9,10 +9,12 @@
|
|||
import type { FieldSpec } from '@kbn/data-views-plugin/common';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
|
||||
import { METRIC_TYPE, UiCounterMetricType } from '@kbn/analytics';
|
||||
import { getGroupSelector, isNoneGroup } from '../..';
|
||||
import { groupActions, groupByIdSelector } from './state';
|
||||
import type { GroupOption } from './types';
|
||||
import { Action, defaultGroup, GroupMap } from './types';
|
||||
import { getTelemetryEvent } from '../telemetry/const';
|
||||
|
||||
export interface UseGetGroupSelectorArgs {
|
||||
defaultGroupingOptions: GroupOption[];
|
||||
|
@ -20,6 +22,12 @@ export interface UseGetGroupSelectorArgs {
|
|||
fields: FieldSpec[];
|
||||
groupingId: string;
|
||||
groupingState: GroupMap;
|
||||
onGroupChangeCallback?: (param: { groupByField: string; tableId: string }) => void;
|
||||
tracker: (
|
||||
type: UiCounterMetricType,
|
||||
event: string | string[],
|
||||
count?: number | undefined
|
||||
) => void;
|
||||
}
|
||||
|
||||
export const useGetGroupSelector = ({
|
||||
|
@ -28,6 +36,8 @@ export const useGetGroupSelector = ({
|
|||
fields,
|
||||
groupingId,
|
||||
groupingState,
|
||||
onGroupChangeCallback,
|
||||
tracker,
|
||||
}: UseGetGroupSelectorArgs) => {
|
||||
const { activeGroup: selectedGroup, options } =
|
||||
groupByIdSelector({ groups: groupingState }, groupingId) ?? defaultGroup;
|
||||
|
@ -61,6 +71,14 @@ export const useGetGroupSelector = ({
|
|||
setGroupsActivePage(0);
|
||||
setSelectedGroup(groupSelection);
|
||||
|
||||
// built-in telemetry: UI-counter
|
||||
tracker?.(
|
||||
METRIC_TYPE.CLICK,
|
||||
getTelemetryEvent.groupChanged({ groupingId, selected: groupSelection })
|
||||
);
|
||||
|
||||
onGroupChangeCallback?.({ tableId: groupingId, groupByField: groupSelection });
|
||||
|
||||
// only update options if the new selection is a custom field
|
||||
if (
|
||||
!isNoneGroup(groupSelection) &&
|
||||
|
@ -77,11 +95,14 @@ export const useGetGroupSelector = ({
|
|||
},
|
||||
[
|
||||
defaultGroupingOptions,
|
||||
groupingId,
|
||||
onGroupChangeCallback,
|
||||
options,
|
||||
selectedGroup,
|
||||
setGroupsActivePage,
|
||||
setOptions,
|
||||
setSelectedGroup,
|
||||
tracker,
|
||||
]
|
||||
);
|
||||
|
||||
|
@ -106,6 +127,7 @@ export const useGetGroupSelector = ({
|
|||
}, [defaultGroupingOptions, options.length, selectedGroup, setOptions]);
|
||||
|
||||
return getGroupSelector({
|
||||
groupingId,
|
||||
groupSelected: selectedGroup,
|
||||
'data-test-subj': 'alerts-table-group-selector',
|
||||
onGroupChange,
|
||||
|
|
|
@ -21,6 +21,7 @@ const defaultArgs = {
|
|||
defaultGroupingOptions,
|
||||
fields: [],
|
||||
groupingId,
|
||||
tracker: jest.fn(),
|
||||
};
|
||||
|
||||
const groupingArgs = {
|
||||
|
@ -36,7 +37,7 @@ const groupingArgs = {
|
|||
renderChildComponent: jest.fn(),
|
||||
runtimeMappings: {},
|
||||
signalIndexName: 'test',
|
||||
tableId: groupingId,
|
||||
groupingId,
|
||||
takeActionItems: jest.fn(),
|
||||
to: '2020-07-08T08:20:18.966Z',
|
||||
};
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { FieldSpec } from '@kbn/data-views-plugin/common';
|
||||
import React, { useCallback, useMemo, useReducer } from 'react';
|
||||
import { UiCounterMetricType } from '@kbn/analytics';
|
||||
import { groupsReducerWithStorage, initialState } from './state/reducer';
|
||||
import { GroupingProps, GroupSelectorProps } from '..';
|
||||
import { useGroupingPagination } from './use_grouping_pagination';
|
||||
|
@ -31,14 +32,21 @@ interface Grouping<T> {
|
|||
|
||||
interface GroupingArgs {
|
||||
defaultGroupingOptions: GroupOption[];
|
||||
|
||||
fields: FieldSpec[];
|
||||
groupingId: string;
|
||||
onGroupChangeCallback?: (param: { groupByField: string; tableId: string }) => void;
|
||||
tracker: (
|
||||
type: UiCounterMetricType,
|
||||
event: string | string[],
|
||||
count?: number | undefined
|
||||
) => void;
|
||||
}
|
||||
export const useGrouping = <T,>({
|
||||
defaultGroupingOptions,
|
||||
fields,
|
||||
groupingId,
|
||||
onGroupChangeCallback,
|
||||
tracker,
|
||||
}: GroupingArgs): Grouping<T> => {
|
||||
const [groupingState, dispatch] = useReducer(groupsReducerWithStorage, initialState);
|
||||
|
||||
|
@ -53,6 +61,8 @@ export const useGrouping = <T,>({
|
|||
fields,
|
||||
groupingId,
|
||||
groupingState,
|
||||
onGroupChangeCallback,
|
||||
tracker,
|
||||
});
|
||||
|
||||
const pagination = useGroupingPagination({ groupingId, groupingState, dispatch });
|
||||
|
|
|
@ -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 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
enum TELEMETRY_EVENT {
|
||||
GROUP_TOGGLED = 'alerts_table_toggled_',
|
||||
GROUPED_ALERTS = 'alerts_table_group_by_',
|
||||
}
|
||||
|
||||
export const getTelemetryEvent = {
|
||||
groupToggled: ({
|
||||
isOpen,
|
||||
groupingId,
|
||||
groupNumber,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
groupingId: string;
|
||||
groupNumber: number;
|
||||
}) =>
|
||||
`${TELEMETRY_EVENT.GROUP_TOGGLED}${isOpen ? 'on' : 'off'}_${groupingId}_group-${groupNumber}`,
|
||||
groupChanged: ({ groupingId, selected }: { groupingId: string; selected: string }) =>
|
||||
`${TELEMETRY_EVENT.GROUPED_ALERTS}${groupingId}_${selected}`,
|
||||
};
|
|
@ -24,5 +24,6 @@
|
|||
"@kbn/kibana-react-plugin",
|
||||
"@kbn/shared-svg",
|
||||
"@kbn/ui-theme",
|
||||
"@kbn/analytics"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -9,9 +9,13 @@ import type { UiCounterMetricType } from '@kbn/analytics';
|
|||
import { METRIC_TYPE } from '@kbn/analytics';
|
||||
|
||||
import type { SetupPlugins } from '../../../types';
|
||||
import type { AlertWorkflowStatus } from '../../types';
|
||||
export { telemetryMiddleware } from './middleware';
|
||||
|
||||
export { METRIC_TYPE };
|
||||
export * from './telemetry_client';
|
||||
export * from './telemetry_service';
|
||||
export * from './types';
|
||||
|
||||
type TrackFn = (type: UiCounterMetricType, event: string | string[], count?: number) => void;
|
||||
|
||||
|
@ -40,7 +44,6 @@ export enum TELEMETRY_EVENT {
|
|||
SIEM_RULE_DISABLED = 'siem_rule_disabled',
|
||||
CUSTOM_RULE_ENABLED = 'custom_rule_enabled',
|
||||
CUSTOM_RULE_DISABLED = 'custom_rule_disabled',
|
||||
|
||||
// ML
|
||||
SIEM_JOB_ENABLED = 'siem_job_enabled',
|
||||
SIEM_JOB_DISABLED = 'siem_job_disabled',
|
||||
|
@ -67,3 +70,15 @@ export enum TELEMETRY_EVENT {
|
|||
BREADCRUMB = 'breadcrumb_',
|
||||
LEGACY_NAVIGATION = 'legacy_navigation_',
|
||||
}
|
||||
|
||||
export const getTelemetryEvent = {
|
||||
groupedAlertsTakeAction: ({
|
||||
tableId,
|
||||
groupNumber,
|
||||
status,
|
||||
}: {
|
||||
tableId: string;
|
||||
groupNumber: number;
|
||||
status: AlertWorkflowStatus;
|
||||
}) => `alerts_table_${tableId}_group-${groupNumber}_mark-${status}`,
|
||||
};
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { TelemetryClientStart } from './types';
|
||||
|
||||
export const createTelemetryClientMock = (): jest.Mocked<TelemetryClientStart> => ({
|
||||
reportAlertsGroupingChanged: jest.fn(),
|
||||
reportAlertsGroupingToggled: jest.fn(),
|
||||
reportAlertsGroupingTakeAction: jest.fn(),
|
||||
});
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { AnalyticsServiceSetup } from '@kbn/core-analytics-server';
|
||||
import type {
|
||||
TelemetryClientStart,
|
||||
ReportAlertsGroupingChangedParams,
|
||||
ReportAlertsGroupingToggledParams,
|
||||
ReportAlertsTakeActionParams,
|
||||
} from './types';
|
||||
import { TelemetryEventTypes } from './types';
|
||||
|
||||
/**
|
||||
* Client which aggregate all the available telemetry tracking functions
|
||||
* for the plugin
|
||||
*/
|
||||
export class TelemetryClient implements TelemetryClientStart {
|
||||
constructor(private analytics: AnalyticsServiceSetup) {}
|
||||
|
||||
public reportAlertsGroupingChanged = ({
|
||||
tableId,
|
||||
groupByField,
|
||||
}: ReportAlertsGroupingChangedParams) => {
|
||||
this.analytics.reportEvent(TelemetryEventTypes.AlertsGroupingChanged, {
|
||||
tableId,
|
||||
groupByField,
|
||||
});
|
||||
};
|
||||
|
||||
public reportAlertsGroupingToggled = ({
|
||||
isOpen,
|
||||
tableId,
|
||||
groupNumber,
|
||||
groupName,
|
||||
}: ReportAlertsGroupingToggledParams) => {
|
||||
this.analytics.reportEvent(TelemetryEventTypes.AlertsGroupingToggled, {
|
||||
isOpen,
|
||||
tableId,
|
||||
groupNumber,
|
||||
groupName,
|
||||
});
|
||||
};
|
||||
|
||||
public reportAlertsGroupingTakeAction = ({
|
||||
tableId,
|
||||
groupNumber,
|
||||
status,
|
||||
groupByField,
|
||||
}: ReportAlertsTakeActionParams) => {
|
||||
this.analytics.reportEvent(TelemetryEventTypes.AlertsGroupingTakeAction, {
|
||||
tableId,
|
||||
groupNumber,
|
||||
status,
|
||||
groupByField,
|
||||
});
|
||||
};
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { TelemetryEvent } from './types';
|
||||
import { TelemetryEventTypes } from './types';
|
||||
|
||||
const alertsGroupingToggledEvent: TelemetryEvent = {
|
||||
eventType: TelemetryEventTypes.AlertsGroupingToggled,
|
||||
schema: {
|
||||
isOpen: {
|
||||
type: 'boolean',
|
||||
_meta: {
|
||||
description: 'on or off',
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
tableId: {
|
||||
type: 'text',
|
||||
_meta: {
|
||||
description: 'Table ID',
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
groupNumber: {
|
||||
type: 'integer',
|
||||
_meta: {
|
||||
description: 'Group number',
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
groupName: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: 'Group value',
|
||||
optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const alertsGroupingChangedEvent: TelemetryEvent = {
|
||||
eventType: TelemetryEventTypes.AlertsGroupingChanged,
|
||||
schema: {
|
||||
tableId: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: 'Table ID',
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
groupByField: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: 'Selected field',
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const alertsGroupingTakeActionEvent: TelemetryEvent = {
|
||||
eventType: TelemetryEventTypes.AlertsGroupingTakeAction,
|
||||
schema: {
|
||||
tableId: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: 'Table ID',
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
groupNumber: {
|
||||
type: 'integer',
|
||||
_meta: {
|
||||
description: 'Group number',
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
status: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: 'Alert status',
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
groupByField: {
|
||||
type: 'keyword',
|
||||
_meta: {
|
||||
description: 'Selected field',
|
||||
optional: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const telemetryEvents = [
|
||||
alertsGroupingToggledEvent,
|
||||
alertsGroupingChangedEvent,
|
||||
alertsGroupingTakeActionEvent,
|
||||
];
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 { createTelemetryClientMock } from './telemetry_client.mock';
|
||||
|
||||
export const createTelemetryServiceMock = () => createTelemetryClientMock();
|
|
@ -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 { coreMock } from '@kbn/core/server/mocks';
|
||||
import { telemetryEvents } from './telemetry_events';
|
||||
|
||||
import { TelemetryService } from './telemetry_service';
|
||||
import { TelemetryEventTypes } from './types';
|
||||
|
||||
describe('TelemetryService', () => {
|
||||
let service: TelemetryService;
|
||||
|
||||
beforeEach(() => {
|
||||
service = new TelemetryService();
|
||||
});
|
||||
|
||||
const getSetupParams = () => {
|
||||
const mockCoreStart = coreMock.createSetup();
|
||||
return {
|
||||
analytics: mockCoreStart.analytics,
|
||||
};
|
||||
};
|
||||
|
||||
describe('#setup()', () => {
|
||||
it('should register all the custom events', () => {
|
||||
const setupParams = getSetupParams();
|
||||
service.setup(setupParams);
|
||||
|
||||
expect(setupParams.analytics.registerEventType).toHaveBeenCalledTimes(telemetryEvents.length);
|
||||
|
||||
telemetryEvents.forEach((eventConfig, pos) => {
|
||||
expect(setupParams.analytics.registerEventType).toHaveBeenNthCalledWith(
|
||||
pos + 1,
|
||||
eventConfig
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#start()', () => {
|
||||
it('should return all the available tracking methods', () => {
|
||||
const setupParams = getSetupParams();
|
||||
service.setup(setupParams);
|
||||
const telemetry = service.start();
|
||||
|
||||
expect(telemetry).toHaveProperty('reportAlertsGroupingChanged');
|
||||
expect(telemetry).toHaveProperty('reportAlertsGroupingToggled');
|
||||
expect(telemetry).toHaveProperty('reportAlertsGroupingTakeAction');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#reportAlertsGroupingTakeAction', () => {
|
||||
it('should report hosts entry click with properties', async () => {
|
||||
const setupParams = getSetupParams();
|
||||
service.setup(setupParams);
|
||||
const telemetry = service.start();
|
||||
|
||||
telemetry.reportAlertsGroupingTakeAction({
|
||||
tableId: 'test-groupingId',
|
||||
groupNumber: 0,
|
||||
status: 'closed',
|
||||
groupByField: 'host.name',
|
||||
});
|
||||
|
||||
expect(setupParams.analytics.reportEvent).toHaveBeenCalledTimes(1);
|
||||
expect(setupParams.analytics.reportEvent).toHaveBeenCalledWith(
|
||||
TelemetryEventTypes.AlertsGroupingTakeAction,
|
||||
{
|
||||
tableId: 'test-groupingId',
|
||||
groupNumber: 0,
|
||||
status: 'closed',
|
||||
groupByField: 'host.name',
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { AnalyticsServiceSetup } from '@kbn/core-analytics-server';
|
||||
import { of } from 'rxjs';
|
||||
|
||||
import type {
|
||||
TelemetryServiceSetupParams,
|
||||
TelemetryClientStart,
|
||||
TelemetryEventParams,
|
||||
} from './types';
|
||||
import { telemetryEvents } from './telemetry_events';
|
||||
import { TelemetryClient } from './telemetry_client';
|
||||
|
||||
/**
|
||||
* Service that interacts with the Core's analytics module
|
||||
* to trigger custom event for the Infra plugin features
|
||||
*/
|
||||
export class TelemetryService {
|
||||
constructor(private analytics: AnalyticsServiceSetup | null = null) {}
|
||||
|
||||
public setup({ analytics }: TelemetryServiceSetupParams, context?: Record<string, unknown>) {
|
||||
this.analytics = analytics;
|
||||
if (context) {
|
||||
const context$ = of(context);
|
||||
|
||||
analytics.registerContextProvider({
|
||||
name: 'detection_response',
|
||||
// RxJS Observable that emits every time the context changes.
|
||||
context$,
|
||||
// Similar to the `reportEvent` API, schema defining the structure of the expected output of the context$ observable.
|
||||
schema: {
|
||||
prebuiltRulesPackageVersion: {
|
||||
type: 'keyword',
|
||||
_meta: { description: 'The version of prebuilt rules', optional: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
telemetryEvents.forEach((eventConfig) =>
|
||||
analytics.registerEventType<TelemetryEventParams>(eventConfig)
|
||||
);
|
||||
}
|
||||
|
||||
public start(): TelemetryClientStart {
|
||||
if (!this.analytics) {
|
||||
throw new Error(
|
||||
'The TelemetryService.setup() method has not been invoked, be sure to call it during the plugin setup.'
|
||||
);
|
||||
}
|
||||
|
||||
return new TelemetryClient(this.analytics);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RootSchema } from '@kbn/analytics-client';
|
||||
import type { AnalyticsServiceSetup } from '@kbn/core/public';
|
||||
|
||||
export interface TelemetryServiceSetupParams {
|
||||
analytics: AnalyticsServiceSetup;
|
||||
}
|
||||
|
||||
export enum TelemetryEventTypes {
|
||||
AlertsGroupingChanged = 'Alerts Grouping Changed',
|
||||
AlertsGroupingToggled = 'Alerts Grouping Toggled',
|
||||
AlertsGroupingTakeAction = 'Alerts Grouping Take Action',
|
||||
}
|
||||
|
||||
export interface ReportAlertsGroupingChangedParams {
|
||||
tableId: string;
|
||||
groupByField: string;
|
||||
}
|
||||
|
||||
export interface ReportAlertsGroupingToggledParams {
|
||||
isOpen: boolean;
|
||||
tableId: string;
|
||||
groupNumber: number;
|
||||
groupName?: string | undefined;
|
||||
}
|
||||
|
||||
export interface ReportAlertsTakeActionParams {
|
||||
tableId: string;
|
||||
groupNumber: number;
|
||||
status: 'open' | 'closed' | 'acknowledged';
|
||||
groupByField: string;
|
||||
}
|
||||
|
||||
export type TelemetryEventParams =
|
||||
| ReportAlertsGroupingChangedParams
|
||||
| ReportAlertsGroupingToggledParams
|
||||
| ReportAlertsTakeActionParams;
|
||||
|
||||
export interface TelemetryClientStart {
|
||||
reportAlertsGroupingChanged(params: ReportAlertsGroupingChangedParams): void;
|
||||
reportAlertsGroupingToggled(params: ReportAlertsGroupingToggledParams): void;
|
||||
reportAlertsGroupingTakeAction(params: ReportAlertsTakeActionParams): void;
|
||||
}
|
||||
|
||||
export type TelemetryEvent =
|
||||
| {
|
||||
eventType: TelemetryEventTypes.AlertsGroupingToggled;
|
||||
schema: RootSchema<ReportAlertsGroupingToggledParams>;
|
||||
}
|
||||
| {
|
||||
eventType: TelemetryEventTypes.AlertsGroupingChanged;
|
||||
schema: RootSchema<ReportAlertsGroupingChangedParams>;
|
||||
}
|
||||
| {
|
||||
eventType: TelemetryEventTypes.AlertsGroupingTakeAction;
|
||||
schema: RootSchema<ReportAlertsTakeActionParams>;
|
||||
};
|
|
@ -45,6 +45,7 @@ import {
|
|||
useGroupTakeActionsItems,
|
||||
} from './grouping_settings';
|
||||
import { updateGroupSelector, updateSelectedGroup } from '../../../common/store/grouping/actions';
|
||||
import { track } from '../../../common/lib/telemetry';
|
||||
|
||||
const ALERTS_GROUPING_ID = 'alerts-grouping';
|
||||
|
||||
|
@ -82,16 +83,19 @@ export const GroupedAlertsTableComponent: React.FC<AlertsTableComponentProps> =
|
|||
renderChildComponent,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { browserFields, indexPattern, selectedPatterns } = useSourcererDataView(
|
||||
SourcererScopeName.detections
|
||||
);
|
||||
const kibana = useKibana();
|
||||
const {
|
||||
services: { uiSettings, telemetry },
|
||||
} = useKibana();
|
||||
|
||||
const getGlobalQuery = useCallback(
|
||||
(customFilters: Filter[]) => {
|
||||
if (browserFields != null && indexPattern != null) {
|
||||
return combineQueries({
|
||||
config: getEsQueryConfig(kibana.services.uiSettings),
|
||||
config: getEsQueryConfig(uiSettings),
|
||||
dataProviders: [],
|
||||
indexPattern,
|
||||
browserFields,
|
||||
|
@ -107,13 +111,22 @@ export const GroupedAlertsTableComponent: React.FC<AlertsTableComponentProps> =
|
|||
}
|
||||
return null;
|
||||
},
|
||||
[browserFields, defaultFilters, globalFilters, globalQuery, indexPattern, kibana, to, from]
|
||||
[browserFields, indexPattern, uiSettings, defaultFilters, globalFilters, from, to, globalQuery]
|
||||
);
|
||||
|
||||
const onGroupChangeCallback = useCallback(
|
||||
(param) => {
|
||||
telemetry.reportAlertsGroupingChanged(param);
|
||||
},
|
||||
[telemetry]
|
||||
);
|
||||
|
||||
const { groupSelector, getGrouping, selectedGroup, pagination } = useGrouping({
|
||||
defaultGroupingOptions: getDefaultGroupingOptions(tableId),
|
||||
groupingId: tableId,
|
||||
fields: indexPattern.fields,
|
||||
onGroupChangeCallback,
|
||||
tracker: track,
|
||||
});
|
||||
const resetPagination = pagination.reset;
|
||||
|
||||
|
@ -221,9 +234,14 @@ export const GroupedAlertsTableComponent: React.FC<AlertsTableComponentProps> =
|
|||
});
|
||||
|
||||
const getTakeActionItems = useCallback(
|
||||
(groupFilters: Filter[]) =>
|
||||
takeActionItems(getGlobalQuery([...(defaultFilters ?? []), ...groupFilters])?.filterQuery),
|
||||
[defaultFilters, getGlobalQuery, takeActionItems]
|
||||
(groupFilters: Filter[], groupNumber: number) =>
|
||||
takeActionItems({
|
||||
query: getGlobalQuery([...(defaultFilters ?? []), ...groupFilters])?.filterQuery,
|
||||
tableId,
|
||||
groupNumber,
|
||||
selectedGroup,
|
||||
}),
|
||||
[defaultFilters, getGlobalQuery, selectedGroup, tableId, takeActionItems]
|
||||
);
|
||||
|
||||
const groupedAlerts = useMemo(
|
||||
|
@ -236,12 +254,17 @@ export const GroupedAlertsTableComponent: React.FC<AlertsTableComponentProps> =
|
|||
customMetricStats: (fieldBucket: RawBucket<AlertsGroupingAggregation>) =>
|
||||
getSelectedGroupCustomMetrics(selectedGroup, fieldBucket),
|
||||
data: alertsGroupsData?.aggregations,
|
||||
groupingId: tableId,
|
||||
groupPanelRenderer: (fieldBucket: RawBucket<AlertsGroupingAggregation>) =>
|
||||
getSelectedGroupButtonContent(selectedGroup, fieldBucket),
|
||||
inspectButton: inspect,
|
||||
isLoading: loading || isLoadingGroups,
|
||||
onToggleCallback: (param) => {
|
||||
telemetry.reportAlertsGroupingToggled({ ...param, tableId: param.groupingId });
|
||||
},
|
||||
renderChildComponent,
|
||||
takeActionItems: getTakeActionItems,
|
||||
tracker: track,
|
||||
unit: defaultUnit,
|
||||
}),
|
||||
[
|
||||
|
@ -253,6 +276,8 @@ export const GroupedAlertsTableComponent: React.FC<AlertsTableComponentProps> =
|
|||
loading,
|
||||
renderChildComponent,
|
||||
selectedGroup,
|
||||
tableId,
|
||||
telemetry,
|
||||
]
|
||||
);
|
||||
|
||||
|
|
|
@ -25,6 +25,11 @@ describe('useGroupTakeActionsItems', () => {
|
|||
const wrapperContainer: React.FC<{ children?: React.ReactNode }> = ({ children }) => (
|
||||
<TestProviders>{children}</TestProviders>
|
||||
);
|
||||
const getActionItemsParams = {
|
||||
tableId: 'mock-id',
|
||||
groupNumber: 0,
|
||||
selectedGroup: 'test',
|
||||
};
|
||||
it('returns array take actions items available for alerts table if showAlertStatusActions is true', async () => {
|
||||
await act(async () => {
|
||||
const { result, waitForNextUpdate } = renderHook(
|
||||
|
@ -38,7 +43,7 @@ describe('useGroupTakeActionsItems', () => {
|
|||
}
|
||||
);
|
||||
await waitForNextUpdate();
|
||||
expect(result.current().length).toEqual(3);
|
||||
expect(result.current(getActionItemsParams).length).toEqual(3);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -55,7 +60,7 @@ describe('useGroupTakeActionsItems', () => {
|
|||
}
|
||||
);
|
||||
await waitForNextUpdate();
|
||||
expect(result.current().length).toEqual(0);
|
||||
expect(result.current(getActionItemsParams).length).toEqual(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { EuiContextMenuItem } from '@elastic/eui';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import type { Status } from '../../../../../common/detection_engine/schemas/common';
|
||||
import type { inputsModel } from '../../../../common/store';
|
||||
import { inputsSelectors } from '../../../../common/store';
|
||||
|
@ -27,7 +28,8 @@ import {
|
|||
import { FILTER_ACKNOWLEDGED, FILTER_CLOSED, FILTER_OPEN } from '../../../../../common/types';
|
||||
import { useDeepEqualSelector } from '../../../../common/hooks/use_selector';
|
||||
import * as i18n from '../translations';
|
||||
|
||||
import { getTelemetryEvent, METRIC_TYPE, track } from '../../../../common/lib/telemetry';
|
||||
import type { StartServices } from '../../../../types';
|
||||
export interface TakeActionsProps {
|
||||
currentStatus?: Status;
|
||||
indexName: string;
|
||||
|
@ -47,6 +49,21 @@ export const useGroupTakeActionsItems = ({
|
|||
const refetchQuery = useCallback(() => {
|
||||
globalQueries.forEach((q) => q.refetch && (q.refetch as inputsModel.Refetch)());
|
||||
}, [globalQueries]);
|
||||
const {
|
||||
services: { telemetry },
|
||||
} = useKibana<StartServices>();
|
||||
|
||||
const reportAlertsGroupingTakeActionClick = useCallback(
|
||||
(params: {
|
||||
tableId: string;
|
||||
groupNumber: number;
|
||||
status: 'open' | 'closed' | 'acknowledged';
|
||||
groupByField: string;
|
||||
}) => {
|
||||
telemetry.reportAlertsGroupingTakeAction(params);
|
||||
},
|
||||
[telemetry]
|
||||
);
|
||||
|
||||
const onUpdateSuccess = useCallback(
|
||||
(updated: number, conflicts: number, newStatus: AlertWorkflowStatus) => {
|
||||
|
@ -113,13 +130,36 @@ export const useGroupTakeActionsItems = ({
|
|||
);
|
||||
|
||||
const onClickUpdate = useCallback(
|
||||
async (status: AlertWorkflowStatus, query?: string) => {
|
||||
async ({
|
||||
groupNumber,
|
||||
query,
|
||||
status,
|
||||
tableId,
|
||||
selectedGroup,
|
||||
}: {
|
||||
groupNumber: number;
|
||||
query?: string;
|
||||
status: AlertWorkflowStatus;
|
||||
tableId: string;
|
||||
selectedGroup: string;
|
||||
}) => {
|
||||
if (query) {
|
||||
startTransaction({ name: APM_USER_INTERACTIONS.BULK_QUERY_STATUS_UPDATE });
|
||||
} else {
|
||||
startTransaction({ name: APM_USER_INTERACTIONS.STATUS_UPDATE });
|
||||
}
|
||||
|
||||
track(
|
||||
METRIC_TYPE.CLICK,
|
||||
getTelemetryEvent.groupedAlertsTakeAction({ tableId, groupNumber, status })
|
||||
);
|
||||
reportAlertsGroupingTakeActionClick({
|
||||
tableId,
|
||||
groupNumber,
|
||||
status,
|
||||
groupByField: selectedGroup,
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await updateAlertStatus({
|
||||
index: indexName,
|
||||
|
@ -133,16 +173,27 @@ export const useGroupTakeActionsItems = ({
|
|||
}
|
||||
},
|
||||
[
|
||||
startTransaction,
|
||||
reportAlertsGroupingTakeActionClick,
|
||||
updateAlertStatus,
|
||||
indexName,
|
||||
onAlertStatusUpdateSuccess,
|
||||
onAlertStatusUpdateFailure,
|
||||
startTransaction,
|
||||
]
|
||||
);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const getActionItems = (query?: string) => {
|
||||
const getActionItems = ({
|
||||
query,
|
||||
tableId,
|
||||
groupNumber,
|
||||
selectedGroup,
|
||||
}: {
|
||||
query?: string;
|
||||
tableId: string;
|
||||
groupNumber: number;
|
||||
selectedGroup: string;
|
||||
}) => {
|
||||
const actionItems: JSX.Element[] = [];
|
||||
if (showAlertStatusActions) {
|
||||
if (currentStatus !== FILTER_OPEN) {
|
||||
|
@ -150,7 +201,15 @@ export const useGroupTakeActionsItems = ({
|
|||
<EuiContextMenuItem
|
||||
key="open"
|
||||
data-test-subj="open-alert-status"
|
||||
onClick={() => onClickUpdate(FILTER_OPEN as AlertWorkflowStatus, query)}
|
||||
onClick={() =>
|
||||
onClickUpdate({
|
||||
groupNumber,
|
||||
query,
|
||||
selectedGroup,
|
||||
status: FILTER_OPEN as AlertWorkflowStatus,
|
||||
tableId,
|
||||
})
|
||||
}
|
||||
>
|
||||
{BULK_ACTION_OPEN_SELECTED}
|
||||
</EuiContextMenuItem>
|
||||
|
@ -161,7 +220,15 @@ export const useGroupTakeActionsItems = ({
|
|||
<EuiContextMenuItem
|
||||
key="acknowledge"
|
||||
data-test-subj="acknowledged-alert-status"
|
||||
onClick={() => onClickUpdate(FILTER_ACKNOWLEDGED as AlertWorkflowStatus, query)}
|
||||
onClick={() =>
|
||||
onClickUpdate({
|
||||
groupNumber,
|
||||
query,
|
||||
selectedGroup,
|
||||
status: FILTER_ACKNOWLEDGED as AlertWorkflowStatus,
|
||||
tableId,
|
||||
})
|
||||
}
|
||||
>
|
||||
{BULK_ACTION_ACKNOWLEDGED_SELECTED}
|
||||
</EuiContextMenuItem>
|
||||
|
@ -172,7 +239,15 @@ export const useGroupTakeActionsItems = ({
|
|||
<EuiContextMenuItem
|
||||
key="close"
|
||||
data-test-subj="close-alert-status"
|
||||
onClick={() => onClickUpdate(FILTER_CLOSED as AlertWorkflowStatus, query)}
|
||||
onClick={() =>
|
||||
onClickUpdate({
|
||||
groupNumber,
|
||||
query,
|
||||
selectedGroup,
|
||||
status: FILTER_CLOSED as AlertWorkflowStatus,
|
||||
tableId,
|
||||
})
|
||||
}
|
||||
>
|
||||
{BULK_ACTION_CLOSE_SELECTED}
|
||||
</EuiContextMenuItem>
|
||||
|
|
|
@ -30,7 +30,7 @@ import type {
|
|||
StartedSubPlugins,
|
||||
StartPluginsDependencies,
|
||||
} from './types';
|
||||
import { initTelemetry } from './common/lib/telemetry';
|
||||
import { initTelemetry, TelemetryService } from './common/lib/telemetry';
|
||||
import { KibanaServices } from './common/lib/kibana/services';
|
||||
import { SOLUTION_NAME } from './common/translations';
|
||||
|
||||
|
@ -83,6 +83,8 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
*/
|
||||
readonly prebuiltRulesPackageVersion?: string;
|
||||
private config: SecuritySolutionUiConfigType;
|
||||
private telemetry: TelemetryService;
|
||||
|
||||
readonly experimentalFeatures: ExperimentalFeatures;
|
||||
|
||||
constructor(private readonly initializerContext: PluginInitializerContext) {
|
||||
|
@ -91,6 +93,8 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
this.kibanaVersion = initializerContext.env.packageInfo.version;
|
||||
this.kibanaBranch = initializerContext.env.packageInfo.branch;
|
||||
this.prebuiltRulesPackageVersion = this.config.prebuiltRulesPackageVersion;
|
||||
|
||||
this.telemetry = new TelemetryService();
|
||||
}
|
||||
private appUpdater$ = new Subject<AppUpdater>();
|
||||
|
||||
|
@ -120,6 +124,10 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
},
|
||||
APP_UI_ID
|
||||
);
|
||||
const telemetryContext = {
|
||||
prebuiltRulesPackageVersion: this.prebuiltRulesPackageVersion,
|
||||
};
|
||||
this.telemetry.setup({ analytics: core.analytics }, telemetryContext);
|
||||
|
||||
if (plugins.home) {
|
||||
plugins.home.featureCatalogue.registerSolution({
|
||||
|
@ -159,6 +167,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
securityLayout: {
|
||||
getPluginWrapper: () => SecuritySolutionTemplateWrapper,
|
||||
},
|
||||
telemetry: this.telemetry.start(),
|
||||
};
|
||||
return services;
|
||||
};
|
||||
|
|
|
@ -60,7 +60,7 @@ import type { CloudDefend } from './cloud_defend';
|
|||
import type { ThreatIntelligence } from './threat_intelligence';
|
||||
import type { SecuritySolutionTemplateWrapper } from './app/home/template_wrapper';
|
||||
import type { Explore } from './explore';
|
||||
|
||||
import type { TelemetryClientStart } from './common/lib/telemetry';
|
||||
export interface SetupPlugins {
|
||||
home?: HomePublicPluginSetup;
|
||||
licensing: LicensingPluginSetup;
|
||||
|
@ -119,6 +119,7 @@ export type StartServices = CoreStart &
|
|||
securityLayout: {
|
||||
getPluginWrapper: () => typeof SecuritySolutionTemplateWrapper;
|
||||
};
|
||||
telemetry: TelemetryClientStart;
|
||||
};
|
||||
|
||||
export interface PluginSetup {
|
||||
|
|
|
@ -147,6 +147,8 @@
|
|||
"@kbn/alerts-as-data-utils",
|
||||
"@kbn/expandable-flyout",
|
||||
"@kbn/securitysolution-grouping",
|
||||
"@kbn/core-analytics-server",
|
||||
"@kbn/analytics-client",
|
||||
"@kbn/security-solution-side-nav",
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue