Browse Source

feat(alerts): Add parameters for duplicate rule analytics (#34526)

* feat(alerts): Add parameters for duplicate rule analytics

* lint

* make duplicate_rule not req param
Taylan Gocmen 2 years ago
parent
commit
79833a00fe

+ 1 - 0
src/sentry/analytics/events/alert_created.py

@@ -13,6 +13,7 @@ class AlertCreatedEvent(analytics.Event):
         analytics.Attribute("is_api_token"),
         # `alert_rule_ui_component` can be `alert-rule-action`
         analytics.Attribute("alert_rule_ui_component", required=False),
+        analytics.Attribute("duplicate_rule", required=False),
     )
 
 

+ 2 - 0
src/sentry/api/endpoints/project_rules.py

@@ -113,6 +113,7 @@ class ProjectRulesEndpoint(ProjectEndpoint):
             RuleActivity.objects.create(
                 rule=rule, user=request.user, type=RuleActivityType.CREATED.value
             )
+            duplicate_rule = request.query_params.get("duplicateRule")
 
             self.create_audit_entry(
                 request=request,
@@ -129,6 +130,7 @@ class ProjectRulesEndpoint(ProjectEndpoint):
                 sender=self,
                 is_api_token=request.auth is not None,
                 alert_rule_ui_component=created_alert_rule_ui_component,
+                duplicate_rule=duplicate_rule,
             )
 
             return Response(serialize(rule, request.user))

+ 2 - 0
src/sentry/incidents/endpoints/project_alert_rule_index.py

@@ -110,6 +110,7 @@ class ProjectAlertRuleIndexEndpoint(ProjectEndpoint):
                 alert_rule = serializer.save()
                 referrer = request.query_params.get("referrer")
                 session_id = request.query_params.get("sessionId")
+                duplicate_rule = request.query_params.get("duplicateRule")
                 alert_rule_created.send_robust(
                     user=request.user,
                     project=project,
@@ -119,6 +120,7 @@ class ProjectAlertRuleIndexEndpoint(ProjectEndpoint):
                     referrer=referrer,
                     session_id=session_id,
                     is_api_token=request.auth is not None,
+                    duplicate_rule=duplicate_rule,
                 )
                 return Response(serialize(alert_rule, request.user), status=status.HTTP_201_CREATED)
 

+ 2 - 0
src/sentry/receivers/features.py

@@ -291,6 +291,7 @@ def record_alert_rule_created(
     referrer=None,
     session_id=None,
     alert_rule_ui_component=None,
+    duplicate_rule=None,
     **kwargs,
 ):
     if rule_type == "issue" and rule.label == DEFAULT_RULE_LABEL and rule.data == DEFAULT_RULE_DATA:
@@ -318,6 +319,7 @@ def record_alert_rule_created(
         session_id=session_id,
         is_api_token=is_api_token,
         alert_rule_ui_component=alert_rule_ui_component,
+        duplicate_rule=duplicate_rule,
     )
 
 

+ 1 - 1
src/sentry/signals.py

@@ -85,7 +85,7 @@ inbound_filter_toggled = BetterSignal(providing_args=["project"])
 sso_enabled = BetterSignal(providing_args=["organization", "user", "provider"])
 data_scrubber_enabled = BetterSignal(providing_args=["organization"])
 alert_rule_created = BetterSignal(
-    providing_args=["project", "rule", "user", "rule_type", "is_api_token"]
+    providing_args=["project", "rule", "user", "rule_type", "is_api_token", "duplicate_rule"]
 )
 alert_rule_edited = BetterSignal(
     providing_args=["project", "rule", "user", "rule_type", "is_api_token"]

+ 1 - 0
static/app/utils/analytics/workflowAnalyticsEvents.tsx

@@ -47,6 +47,7 @@ export type TeamInsightsEventParameters = {
   'issue_details.event_navigation_clicked': {button: string; project_id: number};
   'issue_details.viewed': IssueDetailsWithAlert;
   'new_alert_rule.viewed': RuleViewed & {
+    duplicate_rule: string;
     session_id: string;
   };
   'team_insights.viewed': {};

+ 14 - 11
static/app/views/alerts/create.tsx

@@ -95,28 +95,33 @@ class Create extends Component<Props, State> {
 
   componentDidMount() {
     const {organization, project} = this.props;
+
     trackAdvancedAnalyticsEvent('new_alert_rule.viewed', {
       organization,
       project_id: project.id,
       session_id: this.sessionId,
       alert_type: this.state.alertType,
+      duplicate_rule: this.isDuplicateRule ? 'true' : 'false',
     });
   }
 
   /** Used to track analytics within one visit to the creation page */
   sessionId = uniqueId();
 
+  get isDuplicateRule(): boolean {
+    const {location, organization} = this.props;
+    const createFromDuplicate = location?.query.createFromDuplicate === 'true';
+    const hasDuplicateAlertRules = organization.features.includes('duplicate-alert-rule');
+    return (
+      hasDuplicateAlertRules && createFromDuplicate && location?.query.duplicateRuleId
+    );
+  }
+
   render() {
     const {hasMetricAlerts, organization, project, location, routes} = this.props;
     const {alertType} = this.state;
-    const {
-      aggregate,
-      dataset,
-      eventTypes,
-      createFromWizard,
-      createFromDiscover,
-      createFromDuplicate,
-    } = location?.query ?? {};
+    const {aggregate, dataset, eventTypes, createFromWizard, createFromDiscover} =
+      location?.query ?? {};
     const wizardTemplate: WizardRuleTemplate = {
       aggregate: aggregate ?? DEFAULT_WIZARD_TEMPLATE.aggregate,
       dataset: dataset ?? DEFAULT_WIZARD_TEMPLATE.dataset,
@@ -124,8 +129,6 @@ class Create extends Component<Props, State> {
     };
     const eventView = createFromDiscover ? EventView.fromLocation(location) : undefined;
 
-    const hasDuplicateAlertRules = organization.features.includes('duplicate-alert-rule');
-
     let wizardAlertType: undefined | WizardAlertType;
     if (createFromWizard && alertType === AlertRuleType.METRIC) {
       wizardAlertType = wizardTemplate
@@ -174,7 +177,7 @@ class Create extends Component<Props, State> {
 
                     {hasMetricAlerts &&
                       alertType === AlertRuleType.METRIC &&
-                      (createFromDuplicate && hasDuplicateAlertRules ? (
+                      (this.isDuplicateRule ? (
                         <IncidentRulesDuplicate
                           {...this.props}
                           eventView={eventView}

+ 12 - 52
static/app/views/alerts/incidentRules/duplicate.tsx

@@ -5,9 +5,6 @@ import {Organization, Project} from 'sentry/types';
 import EventView from 'sentry/utils/discover/eventView';
 import {uniqueId} from 'sentry/utils/guid';
 import {
-  createDefaultRule,
-  createRuleFromEventView,
-  createRuleFromWizardTemplate,
   DuplicateActionFields,
   DuplicateMetricFields,
   DuplicateTriggerFields,
@@ -32,7 +29,6 @@ type Props = {
 } & RouteComponentProps<RouteParams, {}>;
 
 type State = {
-  defaultRule: IncidentRule;
   duplicateTargetRule?: IncidentRule;
 } & AsyncView['state'];
 
@@ -41,53 +37,18 @@ type State = {
  */
 
 class IncidentRulesDuplicate extends AsyncView<Props, State> {
-  get isDuplicateRule() {
-    const {
-      location: {query},
-      organization,
-    } = this.props;
-    const hasDuplicateAlertRules = organization.features.includes('duplicate-alert-rule');
-    return hasDuplicateAlertRules && query.createFromDuplicate && query.duplicateRuleId;
-  }
-
-  getDefaultState() {
-    const {project, eventView, wizardTemplate, userTeamIds} = this.props;
-    const defaultRule = eventView
-      ? createRuleFromEventView(eventView)
-      : wizardTemplate
-      ? createRuleFromWizardTemplate(wizardTemplate)
-      : createDefaultRule();
-
-    const projectTeamIds = new Set(project.teams.map(({id}) => id));
-    const defaultOwnerId = userTeamIds.find(id => projectTeamIds.has(id)) ?? null;
-    const owner = defaultOwnerId && `team:${defaultOwnerId}`;
-
-    return {
-      ...super.getDefaultState(),
-      defaultRule: {
-        ...defaultRule,
-        owner,
-        projects: [project.slug],
-      },
-    };
-  }
-
   getEndpoints(): ReturnType<AsyncView['getEndpoints']> {
     const {
       params: {orgId},
       location: {query},
     } = this.props;
 
-    if (this.isDuplicateRule) {
-      return [
-        [
-          'duplicateTargetRule',
-          `/organizations/${orgId}/alert-rules/${query.duplicateRuleId}/`,
-        ],
-      ];
-    }
-
-    return [];
+    return [
+      [
+        'duplicateTargetRule',
+        `/organizations/${orgId}/alert-rules/${query.duplicateRuleId}/`,
+      ],
+    ];
   }
 
   handleSubmitSuccess = (data: any) => {
@@ -112,21 +73,19 @@ class IncidentRulesDuplicate extends AsyncView<Props, State> {
 
   renderBody() {
     const {project, sessionId, userTeamIds, ...otherProps} = this.props;
-    const {defaultRule, duplicateTargetRule} = this.state;
+    const {duplicateTargetRule} = this.state;
 
-    if (this.isDuplicateRule && !duplicateTargetRule) {
+    if (!duplicateTargetRule) {
       return this.renderLoading();
     }
 
-    const rule = duplicateTargetRule ?? defaultRule;
-
     return (
       <RuleForm
         onSubmitSuccess={this.handleSubmitSuccess}
         rule={
           {
-            ...pick(rule, DuplicateMetricFields),
-            triggers: rule.triggers.map(trigger => ({
+            ...pick(duplicateTargetRule, DuplicateMetricFields),
+            triggers: duplicateTargetRule.triggers.map(trigger => ({
               ...pick(trigger, DuplicateTriggerFields),
               actions: trigger.actions.map(action => ({
                 inputChannelId: null,
@@ -138,12 +97,13 @@ class IncidentRulesDuplicate extends AsyncView<Props, State> {
                 ...pick(action, DuplicateActionFields),
               })),
             })),
-            name: rule.name + ' copy',
+            name: duplicateTargetRule.name + ' copy',
           } as IncidentRule
         }
         sessionId={sessionId}
         project={project}
         userTeamIds={userTeamIds}
+        isDuplicateRule
         {...otherProps}
       />
     );

+ 2 - 0
static/app/views/alerts/incidentRules/ruleForm/index.tsx

@@ -75,6 +75,7 @@ type Props = {
   rule: IncidentRule;
   userTeamIds: string[];
   isCustomMetric?: boolean;
+  isDuplicateRule?: boolean;
   ruleId?: string;
   sessionId?: string;
 } & RouteComponentProps<{orgId: string; projectId?: string; ruleId?: string}, {}> & {
@@ -531,6 +532,7 @@ class RuleFormContainer extends AsyncComponent<Props, State> {
           aggregate,
         },
         {
+          duplicateRule: this.props.isDuplicateRule ? 'true' : 'false',
           referrer: location?.query?.referrer,
           sessionId,
         }

+ 12 - 0
static/app/views/alerts/issueRuleEditor/index.tsx

@@ -140,6 +140,15 @@ function isSavedAlertRule(rule: State['rule']): rule is IssueAlertRule {
 class IssueRuleEditor extends AsyncView<Props, State> {
   pollingTimeout: number | undefined = undefined;
 
+  get isDuplicateRule(): boolean {
+    const {location, organization} = this.props;
+    const createFromDuplicate = location?.query.createFromDuplicate === 'true';
+    const hasDuplicateAlertRules = organization.features.includes('duplicate-alert-rule');
+    return (
+      hasDuplicateAlertRules && createFromDuplicate && location?.query.duplicateRuleId
+    );
+  }
+
   componentWillUnmount() {
     window.clearTimeout(this.pollingTimeout);
   }
@@ -347,6 +356,9 @@ class IssueRuleEditor extends AsyncView<Props, State> {
         includeAllArgs: true,
         method: isNew ? 'POST' : 'PUT',
         data: rule,
+        query: {
+          duplicateRule: this.isDuplicateRule ? 'true' : 'false',
+        },
       });
 
       // if we get a 202 back it means that we have an async task