diff --git a/public/app/features/alerting/unified/Analytics.ts b/public/app/features/alerting/unified/Analytics.ts
index 586617926a8..d25dd758577 100644
--- a/public/app/features/alerting/unified/Analytics.ts
+++ b/public/app/features/alerting/unified/Analytics.ts
@@ -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',
};
diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx
index 1e0cdc8ffce..66fd221e9cf 100644
--- a/public/app/features/alerting/unified/RuleList.test.tsx
+++ b/public/app/features/alerting/unified/RuleList.test.tsx
@@ -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);
+ });
+ });
});
diff --git a/public/app/features/alerting/unified/RuleList.tsx b/public/app/features/alerting/unified/RuleList.tsx
index fc3ee1c17e4..9a62f75af33 100644
--- a/public/app/features/alerting/unified/RuleList.tsx
+++ b/public/app/features/alerting/unified/RuleList.tsx
@@ -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(
logInfo(LogMessages.alertRuleFromScratch)}
>
New alert rule
diff --git a/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.test.tsx b/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.test.tsx
new file mode 100644
index 00000000000..9950ba64363
--- /dev/null
+++ b/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.test.tsx
@@ -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();
+
+ 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);
+ });
+});
diff --git a/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx b/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx
index 20d394bbb65..51ace2c47d6 100644
--- a/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx
+++ b/public/app/features/alerting/unified/components/panel-alerts-tab/NewRuleFromPanelButton.tsx
@@ -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 = ({ dashboard, panel, className
});
return (
-
+ logInfo(LogMessages.alertRuleFromPanel)}
+ href={ruleFormUrl}
+ className={className}
+ data-testid="create-alert-rule-button"
+ >
Create alert rule from this panel
);
diff --git a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx
index 7c8a0d91469..99959162b39 100644
--- a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx
@@ -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 = ({ existing }) => {