Alerting: log alert rule creation and clicking state filters (#55698)

* Add messages for new trackings

* Track clicking on alert state filters

* Track creating alert rule from panel

* Track creating alert rule from scratch

* Track on success and when cancelling a rule creation
This commit is contained in:
Virginia Cepeda 2022-10-03 11:00:19 -03:00 committed by GitHub
parent 09f8e026a1
commit 0d348dc0b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 174 additions and 6 deletions

View File

@ -2,4 +2,9 @@ export const LogMessages = {
filterByLabel: 'filtering alert instances by label',
loadedList: 'loaded Alert Rules list',
leavingRuleGroupEdit: 'leaving rule group edit without saving',
alertRuleFromPanel: 'creating alert rule from panel',
alertRuleFromScratch: 'creating alert rule from scratch',
clickingAlertStateFilters: 'clicking alert state filters',
cancelSavingAlertRule: 'user canceled alert rule creation',
successSavingAlertRule: 'alert rule saved successfully',
};

View File

@ -1,17 +1,18 @@
import { SerializedError } from '@reduxjs/toolkit';
import { render, waitFor } from '@testing-library/react';
import { render, waitFor, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { Provider } from 'react-redux';
import { Router } from 'react-router-dom';
import { byLabelText, byRole, byTestId, byText } from 'testing-library-selector';
import { locationService, setDataSourceSrv } from '@grafana/runtime';
import { locationService, setDataSourceSrv, logInfo } from '@grafana/runtime';
import { contextSrv } from 'app/core/services/context_srv';
import { configureStore } from 'app/store/configureStore';
import { AccessControlAction } from 'app/types';
import { PromAlertingRuleState, PromApplication } from 'app/types/unified-alerting-dto';
import { LogMessages } from './Analytics';
import RuleList from './RuleList';
import { discoverFeatures } from './api/buildInfo';
import { fetchRules } from './api/prometheus';
@ -44,6 +45,13 @@ jest.mock('app/core/core', () => ({
emit: () => {},
},
}));
jest.mock('@grafana/runtime', () => {
const original = jest.requireActual('@grafana/runtime');
return {
...original,
logInfo: jest.fn(),
};
});
jest.spyOn(config, 'getAllDataSources');
@ -745,4 +753,35 @@ describe('RuleList', () => {
});
});
});
describe('Analytics', () => {
it('Sends log info when creating an alert rule from a scratch', async () => {
enableRBAC();
grantUserPermissions([
AccessControlAction.FoldersRead,
AccessControlAction.AlertingRuleCreate,
AccessControlAction.AlertingRuleRead,
]);
mocks.getAllDataSourcesMock.mockReturnValue([]);
setDataSourceSrv(new MockDataSourceSrv({}));
mocks.api.fetchRules.mockResolvedValue([]);
mocks.api.fetchRulerRules.mockResolvedValue({});
renderRuleList();
await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(1));
const button = screen.getByText('New alert rule');
button.addEventListener('click', (event) => event.preventDefault(), false);
expect(button).toBeEnabled();
await userEvent.click(button);
expect(logInfo).toHaveBeenCalledWith(LogMessages.alertRuleFromScratch);
});
});
});

View File

@ -3,10 +3,12 @@ import React, { useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router-dom';
import { GrafanaTheme2, urlUtil } from '@grafana/data';
import { logInfo } from '@grafana/runtime';
import { Button, LinkButton, useStyles2, withErrorBoundary } from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { useDispatch } from 'app/types';
import { LogMessages } from './Analytics';
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
import { NoRulesSplash } from './components/rules/NoRulesCTA';
import { RuleListErrors } from './components/rules/RuleListErrors';
@ -101,6 +103,7 @@ const RuleList = withErrorBoundary(
<LinkButton
href={urlUtil.renderUrl('alerting/new', { returnTo: location.pathname + location.search })}
icon="plus"
onClick={() => logInfo(LogMessages.alertRuleFromScratch)}
>
New alert rule
</LinkButton>

View File

@ -0,0 +1,56 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { logInfo } from '@grafana/runtime';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { LogMessages } from '../../Analytics';
import { NewRuleFromPanelButton } from './NewRuleFromPanelButton';
jest.mock('app/types', () => {
const original = jest.requireActual('app/types');
return {
...original,
useSelector: jest.fn(),
};
});
jest.mock('react-router-dom', () => ({
useLocation: () => ({
pathname: 'localhost:3000/example/path',
}),
}));
jest.mock('@grafana/runtime', () => {
const original = jest.requireActual('@grafana/runtime');
return {
...original,
logInfo: jest.fn(),
};
});
jest.mock('react-use', () => ({
useAsync: () => ({ loading: false, value: {} }),
}));
describe('Analytics', () => {
it('Sends log info when creating an alert rule from a panel', async () => {
const panel = new PanelModel({
id: 123,
});
const dashboard = new DashboardModel({
id: 1,
});
render(<NewRuleFromPanelButton panel={panel} dashboard={dashboard} />);
const button = screen.getByText('Create alert rule from this panel');
button.addEventListener('click', (event) => event.preventDefault(), false);
await userEvent.click(button);
expect(logInfo).toHaveBeenCalledWith(LogMessages.alertRuleFromPanel);
});
});

View File

@ -3,10 +3,12 @@ import { useLocation } from 'react-router-dom';
import { useAsync } from 'react-use';
import { urlUtil } from '@grafana/data';
import { logInfo } from '@grafana/runtime';
import { Alert, Button, LinkButton } from '@grafana/ui';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { useSelector } from 'app/types';
import { LogMessages } from '../../Analytics';
import { panelToRuleFormValues } from '../../utils/rule-form';
interface Props {
@ -46,7 +48,13 @@ export const NewRuleFromPanelButton: FC<Props> = ({ dashboard, panel, className
});
return (
<LinkButton icon="bell" href={ruleFormUrl} className={className} data-testid="create-alert-rule-button">
<LinkButton
icon="bell"
onClick={() => logInfo(LogMessages.alertRuleFromPanel)}
href={ruleFormUrl}
className={className}
data-testid="create-alert-rule-button"
>
Create alert rule from this panel
</LinkButton>
);

View File

@ -4,6 +4,7 @@ import { FormProvider, useForm, UseFormWatch } from 'react-hook-form';
import { Link } from 'react-router-dom';
import { GrafanaTheme2 } from '@grafana/data';
import { logInfo } from '@grafana/runtime';
import { Button, ConfirmModal, CustomScrollbar, PageToolbar, Spinner, useStyles2 } from '@grafana/ui';
import { useAppNotification } from 'app/core/copy/appNotification';
import { useCleanup } from 'app/core/hooks/useCleanup';
@ -11,6 +12,7 @@ import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { useDispatch } from 'app/types';
import { RuleWithLocation } from 'app/types/unified-alerting';
import { LogMessages } from '../../Analytics';
import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector';
import { deleteRuleAction, saveRuleFormAction } from '../../state/actions';
import { RuleFormType, RuleFormValues } from '../../types/rule-form';
@ -110,7 +112,13 @@ export const AlertRuleForm: FC<Props> = ({ existing }) => {
<form onSubmit={(e) => e.preventDefault()} className={styles.form}>
<PageToolbar title={`${existing ? 'Edit' : 'Create'} alert rule`} pageIcon="bell">
<Link to={returnTo}>
<Button variant="secondary" disabled={submitState.loading} type="button" fill="outline">
<Button
variant="secondary"
disabled={submitState.loading}
type="button"
fill="outline"
onClick={() => logInfo(LogMessages.cancelSavingAlertRule)}
>
Cancel
</Button>
</Link>

View File

@ -1,8 +1,10 @@
import React, { FC } from 'react';
import { logInfo } from '@grafana/runtime';
import { CallToActionCard } from '@grafana/ui';
import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA';
import { LogMessages } from '../../Analytics';
import { useRulesAccess } from '../../utils/accessControlHooks';
export const NoRulesSplash: FC = () => {
@ -19,6 +21,7 @@ export const NoRulesSplash: FC = () => {
proTipLink="https://grafana.com/docs/"
proTipLinkTitle="Learn more"
proTipTarget="_blank"
onClick={() => logInfo(LogMessages.alertRuleFromScratch)}
/>
);
}

View File

@ -0,0 +1,40 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { logInfo } from '@grafana/runtime';
import { LogMessages } from '../../Analytics';
import RulesFilter from './RulesFilter';
jest.mock('@grafana/runtime', () => {
const original = jest.requireActual('@grafana/runtime');
return {
...original,
logInfo: jest.fn(),
DataSourcePicker: () => <></>,
};
});
jest.mock('react-router-dom', () => ({
useLocation: () => ({
pathname: 'localhost:3000/example/path',
}),
}));
jest.mock('../../utils/misc', () => ({
getFiltersFromUrlParams: jest.fn(() => ({ dataSource: {}, alertState: {}, queryString: '', ruleType: '' })),
}));
describe('Analytics', () => {
it('Sends log info when clicking alert state filters', async () => {
render(<RulesFilter />);
const button = screen.getByText('Pending');
await userEvent.click(button);
expect(logInfo).toHaveBeenCalledWith(LogMessages.clickingAlertStateFilters);
});
});

View File

@ -3,11 +3,12 @@ import { debounce } from 'lodash';
import React, { FormEvent, useState } from 'react';
import { DataSourceInstanceSettings, GrafanaTheme, SelectableValue } from '@grafana/data';
import { DataSourcePicker } from '@grafana/runtime';
import { DataSourcePicker, logInfo } from '@grafana/runtime';
import { Button, Field, Icon, Input, Label, RadioButtonGroup, Stack, Tooltip, useStyles } from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { PromAlertingRuleState, PromRuleType } from 'app/types/unified-alerting-dto';
import { LogMessages } from '../../Analytics';
import { getFiltersFromUrlParams } from '../../utils/misc';
import { alertStateToReadable } from '../../utils/rules';
@ -69,6 +70,7 @@ const RulesFilter = () => {
}, 600);
const handleAlertStateChange = (value: string) => {
logInfo(LogMessages.clickingAlertStateFilters);
setQueryParams({ alertState: value });
};

View File

@ -1,7 +1,7 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { isEmpty } from 'lodash';
import { locationService } from '@grafana/runtime';
import { locationService, logInfo } from '@grafana/runtime';
import {
AlertmanagerAlert,
AlertManagerCortexConfig,
@ -32,6 +32,7 @@ import {
} from 'app/types/unified-alerting-dto';
import { backendSrv } from '../../../../core/services/backend_srv';
import { LogMessages } from '../Analytics';
import {
addAlertManagers,
createOrUpdateSilence,
@ -422,6 +423,9 @@ export const saveRuleFormAction = createAsyncThunk(
} else {
throw new Error('Unexpected rule form type');
}
logInfo(LogMessages.successSavingAlertRule);
if (redirectOnSave) {
locationService.push(redirectOnSave);
} else {