def update_alert_rule( alert_rule, projects=None, name=None, query=None, aggregation=None, time_window=None, environment=None, threshold_period=None, include_all_projects=None, excluded_projects=None, ): """ Updates an alert rule. :param alert_rule: The alert rule to update :param excluded_projects: List of projects to subscribe to the rule. Ignored if `include_all_projects` is True :param name: Name for the alert rule. This will be used as part of the incident name, and must be unique per project. :param query: An event search query to subscribe to and monitor for alerts :param aggregation: An AlertRuleAggregation that we want to fetch for this alert rule :param time_window: Time period to aggregate over, in minutes. :param environment: List of environments that this rule applies to :param threshold_period: How many update periods the value of the subscription needs to exceed the threshold before triggering :param include_all_projects: Whether to include all current and future projects from this organization :param excluded_projects: List of projects to exclude if we're using `include_all_projects`. Ignored otherwise. :return: The updated `AlertRule` """ if (name and alert_rule.name != name and AlertRule.objects.filter(organization=alert_rule.organization, name=name).exists()): raise AlertRuleNameAlreadyUsedError() updated_fields = {} if name: updated_fields["name"] = name if query is not None: validate_alert_rule_query(query) updated_fields["query"] = query if aggregation is not None: updated_fields["aggregation"] = aggregation.value if time_window: updated_fields["time_window"] = time_window if threshold_period: updated_fields["threshold_period"] = threshold_period if include_all_projects is not None: updated_fields["include_all_projects"] = include_all_projects with transaction.atomic(): incidents = Incident.objects.filter(alert_rule=alert_rule).exists() if incidents: snapshot_alert_rule(alert_rule) alert_rule.update(**updated_fields) existing_subs = [] if (query is not None or aggregation is not None or time_window is not None or projects is not None or include_all_projects is not None or excluded_projects is not None): existing_subs = alert_rule.query_subscriptions.all( ).select_related("project") new_projects = [] deleted_subs = [] if not alert_rule.include_all_projects: # We don't want to have any exclusion rows present if we're not in # `include_all_projects` mode get_excluded_projects_for_alert_rule(alert_rule).delete() if alert_rule.include_all_projects: if include_all_projects or excluded_projects is not None: # If we're in `include_all_projects` mode, we want to just fetch # projects that aren't already subscribed, and haven't been excluded so # we can add them. excluded_project_ids = ({p.id for p in excluded_projects} if excluded_projects else set()) project_exclusions = get_excluded_projects_for_alert_rule( alert_rule) project_exclusions.exclude( project_id__in=excluded_project_ids).delete() existing_excluded_project_ids = { pe.project_id for pe in project_exclusions } new_exclusions = [ AlertRuleExcludedProjects(alert_rule=alert_rule, project_id=project_id) for project_id in excluded_project_ids if project_id not in existing_excluded_project_ids ] AlertRuleExcludedProjects.objects.bulk_create(new_exclusions) new_projects = Project.objects.filter( organization=alert_rule.organization).exclude( id__in=set([sub.project_id for sub in existing_subs]) | excluded_project_ids) # If we're subscribed to any of the excluded projects then we want to # remove those subscriptions deleted_subs = [ sub for sub in existing_subs if sub.project_id in excluded_project_ids ] elif projects is not None: existing_project_slugs = { sub.project.slug for sub in existing_subs } # Determine whether we've added any new projects as part of this update new_projects = [ project for project in projects if project.slug not in existing_project_slugs ] updated_project_slugs = {project.slug for project in projects} # Find any subscriptions that were removed as part of this update deleted_subs = [ sub for sub in existing_subs if sub.project.slug not in updated_project_slugs ] if new_projects: subscribe_projects_to_alert_rule(alert_rule, new_projects) if deleted_subs: bulk_delete_snuba_subscriptions(deleted_subs) # Remove any deleted subscriptions from `existing_subscriptions`, so that # if we need to update any subscriptions we don't end up doing it twice. We # don't add new subscriptions here since they'll already have the updated # values existing_subs = [sub for sub in existing_subs if sub.id] if environment: # Delete rows we don't have present in the updated data. AlertRuleEnvironment.objects.filter(alert_rule=alert_rule).exclude( environment__in=environment).delete() for e in environment: AlertRuleEnvironment.objects.get_or_create( alert_rule=alert_rule, environment=e) else: AlertRuleEnvironment.objects.filter(alert_rule=alert_rule).delete() if existing_subs and (query is not None or aggregation is not None or time_window is not None): # If updating any subscription details, update related Snuba subscriptions # too bulk_update_snuba_subscriptions( existing_subs, alert_rule.query, QueryAggregations(alert_rule.aggregation), timedelta(minutes=alert_rule.time_window), timedelta(minutes=DEFAULT_ALERT_RULE_RESOLUTION), list(alert_rule.environment.all()), ) return alert_rule
def update_alert_rule( alert_rule, projects=None, name=None, threshold_type=None, query=None, aggregation=None, time_window=None, alert_threshold=None, resolve_threshold=None, threshold_period=None, ): """ Updates an alert rule. :param alert_rule: The alert rule to update :param name: Name for the alert rule. This will be used as part of the incident name, and must be unique per project. :param threshold_type: An AlertRuleThresholdType :param query: An event search query to subscribe to and monitor for alerts :param aggregation: An AlertRuleAggregation that we want to fetch for this alert rule :param time_window: Time period to aggregate over, in minutes. :param alert_threshold: Value that the subscription needs to reach to trigger the alert :param resolve_threshold: Value that the subscription needs to reach to resolve the alert :param threshold_period: How many update periods the value of the subscription needs to exceed the threshold before triggering :return: The updated `AlertRule` """ if (name and alert_rule.name != name and AlertRule.objects.filter(organization=alert_rule.organization, name=name).exists()): raise AlertRuleNameAlreadyUsedError() updated_fields = {} if name: updated_fields["name"] = name if threshold_type: updated_fields["threshold_type"] = threshold_type.value if query is not None: validate_alert_rule_query(query) updated_fields["query"] = query if aggregation is not None: updated_fields["aggregation"] = aggregation.value if time_window: updated_fields["time_window"] = time_window if alert_threshold is not None: updated_fields["alert_threshold"] = alert_threshold if resolve_threshold is not None: updated_fields["resolve_threshold"] = resolve_threshold if threshold_period: updated_fields["threshold_period"] = threshold_period with transaction.atomic(): alert_rule.update(**updated_fields) existing_subs = [] if (query is not None or aggregation is not None or time_window is not None or projects is not None): existing_subs = alert_rule.query_subscriptions.all( ).select_related("project") if projects is not None: existing_project_slugs = { sub.project.slug for sub in existing_subs } # Determine whether we've added any new projects as part of this update new_projects = [ project for project in projects if project.slug not in existing_project_slugs ] updated_project_slugs = {project.slug for project in projects} # Find any subscriptions that were removed as part of this update deleted_subs = [ sub for sub in existing_subs if sub.project.slug not in updated_project_slugs ] if new_projects: new_subscriptions = bulk_create_snuba_subscriptions( new_projects, tasks.INCIDENTS_SNUBA_SUBSCRIPTION_TYPE, QueryDatasets(alert_rule.dataset), alert_rule.query, QueryAggregations(alert_rule.aggregation), alert_rule.time_window, DEFAULT_ALERT_RULE_RESOLUTION, ) subscription_links = [ AlertRuleQuerySubscription(query_subscription=subscription, alert_rule=alert_rule) for subscription in new_subscriptions ] AlertRuleQuerySubscription.objects.bulk_create( subscription_links) if deleted_subs: bulk_delete_snuba_subscriptions(deleted_subs) # Remove any deleted subscriptions from `existing_subscriptions`, so that # if we need to update any subscriptions we don't end up doing it twice. We # don't add new subscriptions here since they'll already have the updated # values existing_subs = [sub for sub in existing_subs if sub.id] if existing_subs and (query is not None or aggregation is not None or time_window is not None): # If updating any subscription details, update related Snuba subscriptions # too bulk_update_snuba_subscriptions( existing_subs, alert_rule.query, QueryAggregations(alert_rule.aggregation), alert_rule.time_window, DEFAULT_ALERT_RULE_RESOLUTION, ) return alert_rule