Add telemetry for gaps UI (#211929)

## Add telemetry for gaps UI


Add several events for:

- Fill gap
- Fill remaining gap
- Filter gaps table
- Show all rules with gaps


### How to test:

1. Pull pr locally
2. enable telemetry
```telemetry.optIn: true```
3. check that events appear [here](6e9919c0-d22e-11ee-8356-8b8a68fd8ef2?_g=())

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Khristinin Nikita 2025-02-28 10:53:37 +01:00 committed by GitHub
parent 91b1ac0305
commit 300e35012d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 115 additions and 8 deletions

View file

@ -78,8 +78,64 @@ export const manualRuleRunCancelJobEvent: ManualRuleRunTelemetryEvent = {
},
};
export const fillGapEvent: ManualRuleRunTelemetryEvent = {
eventType: ManualRuleRunEventTypes.FillGap,
schema: {
rangeInMs: {
type: 'integer',
_meta: {
description: 'The time range (expressed in milliseconds) of the gap',
optional: false,
},
},
},
};
export const fillRemainingGapEvent: ManualRuleRunTelemetryEvent = {
eventType: ManualRuleRunEventTypes.FillRemainingGap,
schema: {
rangeInMs: {
type: 'integer',
_meta: {
description: 'The time range (expressed in milliseconds) of the gap',
optional: false,
},
},
},
};
export const filterGapsEvent: ManualRuleRunTelemetryEvent = {
eventType: ManualRuleRunEventTypes.FilterGaps,
schema: {
status: {
type: 'keyword',
_meta: {
description: 'The statuses of the gaps for filtering, separated by commas',
optional: false,
},
},
},
};
export const showOnlyRulesWithGapsEvent: ManualRuleRunTelemetryEvent = {
eventType: ManualRuleRunEventTypes.ShowOnlyRulesWithGaps,
schema: {
dateRange: {
type: 'keyword',
_meta: {
description: 'The date range of the gaps to show all rules with gaps',
optional: false,
},
},
},
};
export const manualRuleRunTelemetryEvents = [
manualRuleRunCancelJobEvent,
manualRuleRunExecuteEvent,
manualRuleRunOpenModalEvent,
fillGapEvent,
fillRemainingGapEvent,
filterGapsEvent,
showOnlyRulesWithGapsEvent,
];

View file

@ -10,6 +10,10 @@ export enum ManualRuleRunEventTypes {
ManualRuleRunOpenModal = 'Manual Rule Run Open Modal',
ManualRuleRunExecute = 'Manual Rule Run Execute',
ManualRuleRunCancelJob = 'Manual Rule Run Cancel Job',
FillGap = 'Fill Gap',
FillRemainingGap = 'Fill Remaining Gap',
FilterGaps = 'Filter Gaps',
ShowOnlyRulesWithGaps = 'Show Only Rules With Gaps',
}
interface ReportManualRuleRunOpenModalParams {
type: 'single' | 'bulk';
@ -27,10 +31,30 @@ interface ReportManualRuleRunCancelJobParams {
errorTasks: number;
}
interface ReportFillGapParams {
rangeInMs: number;
}
interface ReportFillRemainingGapParams {
rangeInMs: number;
}
interface ReportFilterGapsParams {
status: string;
}
interface ReportShowOnlyRulesWithGapsParams {
dateRange: string;
}
export interface ManualRuleRunTelemetryEventsMap {
[ManualRuleRunEventTypes.ManualRuleRunOpenModal]: ReportManualRuleRunOpenModalParams;
[ManualRuleRunEventTypes.ManualRuleRunExecute]: ReportManualRuleRunExecuteParams;
[ManualRuleRunEventTypes.ManualRuleRunCancelJob]: ReportManualRuleRunCancelJobParams;
[ManualRuleRunEventTypes.FillGap]: ReportFillGapParams;
[ManualRuleRunEventTypes.FillRemainingGap]: ReportFillRemainingGapParams;
[ManualRuleRunEventTypes.FilterGaps]: ReportFilterGapsParams;
[ManualRuleRunEventTypes.ShowOnlyRulesWithGaps]: ReportShowOnlyRulesWithGapsParams;
}
export interface ManualRuleRunTelemetryEvent {

View file

@ -11,7 +11,8 @@ import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import { useFillGapMutation } from '../../api/hooks/use_fill_gap';
import * as i18n from './translations';
import type { Gap } from '../../types';
import { ManualRuleRunEventTypes } from '../../../../common/lib/telemetry';
import { useKibana } from '../../../../common/lib/kibana';
export const FillGap = ({
isRuleEnabled,
ruleId,
@ -22,8 +23,21 @@ export const FillGap = ({
gap: Gap;
}) => {
const { addSuccess, addError } = useAppToasts();
const { telemetry } = useKibana().services;
const isGapFillAvailable = gap.status !== gapStatus.FILLED && gap.unfilled_intervals.length !== 0;
const hasRemainingGaps =
isGapFillAvailable && (gap.in_progress_intervals.length > 0 || gap.filled_intervals.length > 0);
const fillGapMutation = useFillGapMutation({
onSuccess: () => {
telemetry.reportEvent(
hasRemainingGaps
? ManualRuleRunEventTypes.FillRemainingGap
: ManualRuleRunEventTypes.FillGap,
{
rangeInMs: gap.total_gap_duration_ms,
}
);
addSuccess(i18n.GAP_FILL_REQUEST_SUCCESS_MESSAGE, {
toastMessage: i18n.GAP_FILL_REQUEST_SUCCESS_MESSAGE_TOOLTIP,
});
@ -36,14 +50,13 @@ export const FillGap = ({
},
});
if (gap.status === gapStatus.FILLED || gap.unfilled_intervals.length === 0) {
if (!isGapFillAvailable) {
return null;
}
const title =
gap.in_progress_intervals.length > 0 || gap.filled_intervals.length > 0
? i18n.GAPS_TABLE_FILL_REMAINING_GAP_BUTTON_LABEL
: i18n.GAPS_TABLE_FILL_GAP_BUTTON_LABEL;
const title = hasRemainingGaps
? i18n.GAPS_TABLE_FILL_REMAINING_GAP_BUTTON_LABEL
: i18n.GAPS_TABLE_FILL_GAP_BUTTON_LABEL;
return (
<>

View file

@ -12,7 +12,8 @@ import { MultiselectFilter } from '../../../../common/components/multiselect_fil
import * as i18n from './translations';
import type { GapStatus } from '../../types';
import { getStatusLabel } from './utils';
import { ManualRuleRunEventTypes } from '../../../../common/lib/telemetry';
import { useKibana } from '../../../../common/lib/kibana';
interface GapStatusFilterComponent {
selectedItems: GapStatus[];
onChange: (selectedItems: GapStatus[]) => void;
@ -25,11 +26,16 @@ export const GapStatusFilter = ({ selectedItems, onChange }: GapStatusFilterComp
return getStatusLabel(status);
}, []);
const { telemetry } = useKibana().services;
const handleSelectionChange = useCallback(
(statuses: GapStatus[]) => {
telemetry.reportEvent(ManualRuleRunEventTypes.FilterGaps, {
status: statuses?.join(','),
});
onChange(statuses);
},
[onChange]
[onChange, telemetry]
);
return (

View file

@ -25,6 +25,8 @@ import { useRulesTableContext } from '../../../rule_management_ui/components/rul
import * as i18n from './translations';
import { useGetRuleIdsWithGaps } from '../../api/hooks/use_get_rule_ids_with_gaps';
import { defaultRangeValue, GapRangeValue } from '../../constants';
import { ManualRuleRunEventTypes } from '../../../../common/lib/telemetry/events/manual_rule_run/types';
import { useKibana } from '../../../../common/lib/kibana';
export const RulesWithGapsOverviewPanel = () => {
const {
@ -38,6 +40,7 @@ export const RulesWithGapsOverviewPanel = () => {
statuses: [gapStatus.UNFILLED, gapStatus.PARTIALLY_FILLED],
});
const [isPopoverOpen, setPopover] = useState(false);
const telemetry = useKibana().services.telemetry;
useEffect(() => {
return () => {
@ -75,6 +78,11 @@ export const RulesWithGapsOverviewPanel = () => {
);
const handleShowRulesWithGapsFilterButtonClick = () => {
if (!showRulesWithGaps) {
telemetry.reportEvent(ManualRuleRunEventTypes.ShowOnlyRulesWithGaps, {
dateRange: gapSearchRange ?? defaultRangeValue,
});
}
setFilterOptions({
showRulesWithGaps: !showRulesWithGaps,
});