mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Improve alert rule and search interaction tracking (#83217)
* Fix alert rule interaction tracking * Add search component interaction tracking * Add fine-grained search input analytics
This commit is contained in:
parent
07128cfec1
commit
183a42b7f6
@ -1,3 +1,5 @@
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
import { dateTime } from '@grafana/data';
|
||||
import { createMonitoringLogger, getBackendSrv } from '@grafana/runtime';
|
||||
import { config, reportInteraction } from '@grafana/runtime/src';
|
||||
@ -6,6 +8,9 @@ import { contextSrv } from 'app/core/core';
|
||||
import { RuleNamespace } from '../../../types/unified-alerting';
|
||||
import { RulerRulesConfigDTO } from '../../../types/unified-alerting-dto';
|
||||
|
||||
import { getSearchFilterFromQuery, RulesFilter } from './search/rulesSearchParser';
|
||||
import { RuleFormType } from './types/rule-form';
|
||||
|
||||
export const USER_CREATION_MIN_DAYS = 7;
|
||||
|
||||
export const LogMessages = {
|
||||
@ -150,27 +155,17 @@ export const trackRuleListNavigation = async (
|
||||
reportInteraction('grafana_alerting_navigation', props);
|
||||
};
|
||||
|
||||
export const trackNewAlerRuleFormSaved = async (props: AlertRuleTrackingProps) => {
|
||||
const isNew = await isNewUser();
|
||||
if (isNew) {
|
||||
return;
|
||||
}
|
||||
export const trackAlertRuleFormSaved = (props: { formAction: 'create' | 'update'; ruleType?: RuleFormType }) => {
|
||||
reportInteraction('grafana_alerting_rule_creation', props);
|
||||
};
|
||||
|
||||
export const trackNewAlerRuleFormCancelled = async (props: AlertRuleTrackingProps) => {
|
||||
const isNew = await isNewUser();
|
||||
if (isNew) {
|
||||
return;
|
||||
}
|
||||
export const trackAlertRuleFormCancelled = (props: { formAction: 'create' | 'update' }) => {
|
||||
reportInteraction('grafana_alerting_rule_aborted', props);
|
||||
};
|
||||
|
||||
export const trackNewAlerRuleFormError = async (props: AlertRuleTrackingProps & { error: string }) => {
|
||||
const isNew = await isNewUser();
|
||||
if (isNew) {
|
||||
return;
|
||||
}
|
||||
export const trackAlertRuleFormError = (
|
||||
props: AlertRuleTrackingProps & { error: string; formAction: 'create' | 'update' }
|
||||
) => {
|
||||
reportInteraction('grafana_alerting_rule_form_error', props);
|
||||
};
|
||||
|
||||
@ -183,6 +178,48 @@ export const trackInsightsFeedback = async (props: { useful: boolean; panel: str
|
||||
reportInteraction('grafana_alerting_insights', { ...defaults, ...props });
|
||||
};
|
||||
|
||||
interface RulesSearchInteractionPayload {
|
||||
filter: string;
|
||||
triggeredBy: 'typing' | 'component';
|
||||
}
|
||||
|
||||
function trackRulesSearchInteraction(payload: RulesSearchInteractionPayload) {
|
||||
reportInteraction('grafana_alerting_rules_search', { ...payload });
|
||||
}
|
||||
|
||||
export function trackRulesSearchInputInteraction({ oldQuery, newQuery }: { oldQuery: string; newQuery: string }) {
|
||||
try {
|
||||
const oldFilter = getSearchFilterFromQuery(oldQuery);
|
||||
const newFilter = getSearchFilterFromQuery(newQuery);
|
||||
|
||||
const oldFilterTerms = extractFilterKeys(oldFilter);
|
||||
const newFilterTerms = extractFilterKeys(newFilter);
|
||||
|
||||
const newTerms = newFilterTerms.filter((term) => !oldFilterTerms.includes(term));
|
||||
newTerms.forEach((term) => {
|
||||
trackRulesSearchInteraction({ filter: term, triggeredBy: 'typing' });
|
||||
});
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
logError(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function extractFilterKeys(filter: RulesFilter) {
|
||||
return Object.entries(filter)
|
||||
.filter(([_, value]) => !isEmpty(value))
|
||||
.map(([key]) => key);
|
||||
}
|
||||
|
||||
export function trackRulesSearchComponentInteraction(filter: keyof RulesFilter) {
|
||||
trackRulesSearchInteraction({ filter, triggeredBy: 'component' });
|
||||
}
|
||||
|
||||
export function trackRulesListViewChange(payload: { view: string }) {
|
||||
reportInteraction('grafana_alerting_rules_list_mode', { ...payload });
|
||||
}
|
||||
|
||||
export type AlertRuleTrackingProps = {
|
||||
user_id: number;
|
||||
grafana_version?: string;
|
||||
|
@ -14,7 +14,13 @@ import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||
import { useDispatch } from 'app/types';
|
||||
import { RuleWithLocation } from 'app/types/unified-alerting';
|
||||
|
||||
import { LogMessages, logInfo, trackNewAlerRuleFormError } from '../../../Analytics';
|
||||
import {
|
||||
LogMessages,
|
||||
logInfo,
|
||||
trackAlertRuleFormError,
|
||||
trackAlertRuleFormCancelled,
|
||||
trackAlertRuleFormSaved,
|
||||
} from '../../../Analytics';
|
||||
import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSelector';
|
||||
import { deleteRuleAction, saveRuleFormAction } from '../../../state/actions';
|
||||
import { RuleFormType, RuleFormValues } from '../../../types/rule-form';
|
||||
@ -109,6 +115,9 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
|
||||
notifyApp.error(conditionErrorMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
trackAlertRuleFormSaved({ formAction: existing ? 'update' : 'create', ruleType: values.type });
|
||||
|
||||
// when creating a new rule, we save the manual routing setting in local storage
|
||||
if (!existing) {
|
||||
if (values.manualRouting) {
|
||||
@ -154,20 +163,21 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
|
||||
};
|
||||
|
||||
const onInvalid: SubmitErrorHandler<RuleFormValues> = (errors): void => {
|
||||
if (!existing) {
|
||||
trackNewAlerRuleFormError({
|
||||
grafana_version: config.buildInfo.version,
|
||||
org_id: contextSrv.user.orgId,
|
||||
user_id: contextSrv.user.id,
|
||||
error: Object.keys(errors).toString(),
|
||||
});
|
||||
}
|
||||
trackAlertRuleFormError({
|
||||
grafana_version: config.buildInfo.version,
|
||||
org_id: contextSrv.user.orgId,
|
||||
user_id: contextSrv.user.id,
|
||||
error: Object.keys(errors).toString(),
|
||||
formAction: existing ? 'update' : 'create',
|
||||
});
|
||||
notifyApp.error('There are errors in the form. Please correct them and try again!');
|
||||
};
|
||||
|
||||
const cancelRuleCreation = () => {
|
||||
logInfo(LogMessages.cancelSavingAlertRule);
|
||||
trackAlertRuleFormCancelled({ formAction: existing ? 'update' : 'create' });
|
||||
};
|
||||
|
||||
const evaluateEveryInForm = watch('evaluateEvery');
|
||||
useEffect(() => setEvaluateEvery(evaluateEveryInForm), [evaluateEveryInForm]);
|
||||
|
||||
|
@ -8,7 +8,13 @@ import { DashboardPicker } from 'app/core/components/Select/DashboardPicker';
|
||||
import { useQueryParams } from 'app/core/hooks/useQueryParams';
|
||||
import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { logInfo, LogMessages } from '../../Analytics';
|
||||
import {
|
||||
logInfo,
|
||||
LogMessages,
|
||||
trackRulesListViewChange,
|
||||
trackRulesSearchComponentInteraction,
|
||||
trackRulesSearchInputInteraction,
|
||||
} from '../../Analytics';
|
||||
import { useRulesFilter } from '../../hooks/useFilteredRules';
|
||||
import { RuleHealth } from '../../search/rulesSearchParser';
|
||||
import { alertStateToReadable } from '../../utils/rules';
|
||||
@ -90,10 +96,12 @@ const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) =>
|
||||
});
|
||||
|
||||
setFilterKey((key) => key + 1);
|
||||
trackRulesSearchComponentInteraction('dataSourceNames');
|
||||
};
|
||||
|
||||
const handleDashboardChange = (dashboardUid: string | undefined) => {
|
||||
updateFilters({ ...filterState, dashboardUid });
|
||||
trackRulesSearchComponentInteraction('dashboardUid');
|
||||
};
|
||||
|
||||
const clearDataSource = () => {
|
||||
@ -104,18 +112,17 @@ const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) =>
|
||||
const handleAlertStateChange = (value: PromAlertingRuleState) => {
|
||||
logInfo(LogMessages.clickingAlertStateFilters);
|
||||
updateFilters({ ...filterState, ruleState: value });
|
||||
};
|
||||
|
||||
const handleViewChange = (view: string) => {
|
||||
setQueryParams({ view });
|
||||
trackRulesSearchComponentInteraction('ruleState');
|
||||
};
|
||||
|
||||
const handleRuleTypeChange = (ruleType: PromRuleType) => {
|
||||
updateFilters({ ...filterState, ruleType });
|
||||
trackRulesSearchComponentInteraction('ruleType');
|
||||
};
|
||||
|
||||
const handleRuleHealthChange = (ruleHealth: RuleHealth) => {
|
||||
updateFilters({ ...filterState, ruleHealth });
|
||||
trackRulesSearchComponentInteraction('ruleHealth');
|
||||
};
|
||||
|
||||
const handleClearFiltersClick = () => {
|
||||
@ -125,6 +132,11 @@ const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) =>
|
||||
setTimeout(() => setFilterKey(filterKey + 1), 100);
|
||||
};
|
||||
|
||||
const handleViewChange = (view: string) => {
|
||||
setQueryParams({ view });
|
||||
trackRulesListViewChange({ view });
|
||||
};
|
||||
|
||||
const searchIcon = <Icon name={'search'} />;
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
@ -211,6 +223,7 @@ const RulesFilter = ({ onFilterCleared = () => undefined }: RulesFilerProps) =>
|
||||
onSubmit={handleSubmit((data) => {
|
||||
setSearchQuery(data.searchQuery);
|
||||
searchQueryRef.current?.blur();
|
||||
trackRulesSearchInputInteraction({ oldQuery: searchQuery, newQuery: data.searchQuery });
|
||||
})}
|
||||
>
|
||||
<Field
|
||||
|
Loading…
Reference in New Issue
Block a user