Просмотр исходного кода

feat(api): Convert `SubscriptionProcessor` to handle multiple triggers per rule (SEN-984)

This converts `SubscriptionProcessor` to use the `AlertRuleTriggers` model instead of the
thresholds that exist on the alert level. One major change is that we need to keep a set of
alert/resolve thresholds per trigger rather than just one set per rule.

We also add in `IncidentTrigger`, which keeps track of which triggers have been fired for a
specific incident.

I'll remove the alert level fields in a separate PR, since this one is complicated enough. I did
test removing them to make sure that we're not accidentally relying on them anywhere in
`SubscriptionProcessor`, and looks like we're good to go there.

Other future work would be to write activity to the incident when it's created, and when each
subsequent trigger is fired (and maybe resolved).
Dan Fuller 5 лет назад
Родитель
Сommit
59b12592f4

+ 23 - 0
src/sentry/incidents/models.py

@@ -118,6 +118,7 @@ class Incident(Model):
         app_label = "sentry"
         db_table = "sentry_incident"
         unique_together = (("organization", "identifier"),)
+        index_together = (("alert_rule", "type", "status"),)
 
     @property
     def current_end_date(self):
@@ -318,6 +319,25 @@ class AlertRule(Model):
         unique_together = (("organization", "name"),)
 
 
+class TriggerStatus(Enum):
+    ACTIVE = 0
+    RESOLVED = 1
+
+
+class IncidentTrigger(Model):
+    __core__ = True
+
+    incident = FlexibleForeignKey("sentry.Incident", db_index=False)
+    alert_rule_trigger = FlexibleForeignKey("sentry.AlertRuleTrigger")
+    status = models.SmallIntegerField()
+    date_added = models.DateTimeField(default=timezone.now)
+
+    class Meta:
+        app_label = "sentry"
+        db_table = "sentry_incidenttrigger"
+        unique_together = (("incident", "alert_rule_trigger"),)
+
+
 class AlertRuleTrigger(Model):
     __core__ = True
 
@@ -326,6 +346,9 @@ class AlertRuleTrigger(Model):
     threshold_type = models.SmallIntegerField()
     alert_threshold = models.IntegerField()
     resolve_threshold = models.IntegerField(null=True)
+    triggered_incidents = models.ManyToManyField(
+        "sentry.Incident", related_name="triggers", through=IncidentTrigger
+    )
     date_added = models.DateTimeField(default=timezone.now)
 
     class Meta:

+ 214 - 87
src/sentry/incidents/subscription_processor.py

@@ -2,7 +2,9 @@ from __future__ import absolute_import
 
 import logging
 import operator
+from copy import deepcopy
 from datetime import timedelta
+from itertools import izip
 
 from django.conf import settings
 from django.db import transaction
@@ -14,7 +16,9 @@ from sentry.incidents.models import (
     AlertRuleThresholdType,
     Incident,
     IncidentStatus,
+    IncidentTrigger,
     IncidentType,
+    TriggerStatus,
 )
 from sentry.snuba.models import QueryAggregations
 from sentry.utils import metrics, redis
@@ -23,8 +27,11 @@ from sentry.utils.dates import to_datetime
 
 logger = logging.getLogger(__name__)
 REDIS_TTL = int(timedelta(days=7).total_seconds())
-ALERT_RULE_BASE_STAT_KEY = "{alert_rule:%s:project:%s}:%%s"
-ALERT_RULE_STAT_KEYS = ("last_update", "alert_triggered", "resolve_triggered")
+ALERT_RULE_BASE_KEY = "{alert_rule:%s:project:%s}"
+ALERT_RULE_BASE_STAT_KEY = "%s:%s"
+ALERT_RULE_STAT_KEYS = ("last_update",)
+ALERT_RULE_BASE_TRIGGER_STAT_KEY = "%s:trigger:%s:%s"
+ALERT_RULE_TRIGGER_STAT_KEYS = ("alert_triggered", "resolve_triggered")
 
 
 class SubscriptionProcessor(object):
@@ -48,20 +55,19 @@ class SubscriptionProcessor(object):
         except AlertRule.DoesNotExist:
             return
 
-        self.last_update, self.alert_triggers, self.resolve_triggers = get_alert_rule_stats(
-            self.alert_rule, self.subscription
+        self.triggers = list(self.alert_rule.alertruletrigger_set.all().order_by("alert_threshold"))
+
+        self.last_update, self.trigger_alert_counts, self.trigger_resolve_counts = get_alert_rule_stats(
+            self.alert_rule, self.subscription, self.triggers
         )
-        self.orig_alert_triggers = self.alert_triggers
-        self.orig_resolve_triggers = self.resolve_triggers
+        self.orig_trigger_alert_counts = deepcopy(self.trigger_alert_counts)
+        self.orig_trigger_resolve_counts = deepcopy(self.trigger_resolve_counts)
 
     @property
     def active_incident(self):
         if not hasattr(self, "_active_incident"):
             try:
                 # Fetch the active incident if one exists for this alert rule.
-                # TODO: Probably worth adding an index to optimize this, since could
-                # potentially be called frequently and could be slow if we have
-                # a lot of incidents created by a rule.
                 self._active_incident = Incident.objects.filter(
                     type=IncidentType.ALERT_TRIGGERED.value,
                     status=IncidentStatus.OPEN.value,
@@ -76,6 +82,30 @@ class SubscriptionProcessor(object):
     def active_incident(self, active_incident):
         self._active_incident = active_incident
 
+    @property
+    def incident_triggers(self):
+        if not hasattr(self, "_incident_triggers"):
+            incident = self.active_incident
+            incident_triggers = {}
+            if incident:
+                # Fetch any existing triggers for the rule
+                triggers = IncidentTrigger.objects.filter(incident=incident).select_related(
+                    "alert_rule_trigger"
+                )
+                incident_triggers = {trigger.alert_rule_trigger_id: trigger for trigger in triggers}
+            self._incident_triggers = incident_triggers
+        return self._incident_triggers
+
+    def check_trigger_status(self, trigger, status):
+        """
+        Determines whether a trigger is currently at the specified status
+        :param trigger: An `AlertRuleTrigger`
+        :param status: A `TriggerStatus`
+        :return: True if at the specified status, otherwise False
+        """
+        incident_trigger = self.incident_triggers.get(trigger.id)
+        return incident_trigger is not None and incident_trigger.status == status.value
+
     def process_update(self, subscription_update):
         if not hasattr(self, "alert_rule"):
             # If the alert rule has been removed then just skip
@@ -96,28 +126,26 @@ class SubscriptionProcessor(object):
         aggregation_name = query_aggregation_to_snuba[aggregation][2]
         aggregation_value = subscription_update["values"][aggregation_name]
 
-        alert_operator, resolve_operator = self.THRESHOLD_TYPE_OPERATORS[
-            AlertRuleThresholdType(self.alert_rule.threshold_type)
-        ]
-
-        if (
-            alert_operator(aggregation_value, self.alert_rule.alert_threshold)
-            and not self.active_incident
-        ):
-            with transaction.atomic():
-                self.trigger_alert_threshold()
-        elif (
-            # TODO: Need to make `resolve_threshold` nullable so that it can be
-            # optional
-            self.alert_rule.resolve_threshold is not None
-            and resolve_operator(aggregation_value, self.alert_rule.resolve_threshold)
-            and self.active_incident
-        ):
-            with transaction.atomic():
-                self.trigger_resolve_threshold()
-        else:
-            self.alert_triggers = 0
-            self.resolve_triggers = 0
+        for trigger in self.triggers:
+            alert_operator, resolve_operator = self.THRESHOLD_TYPE_OPERATORS[
+                AlertRuleThresholdType(trigger.threshold_type)
+            ]
+
+            if alert_operator(
+                aggregation_value, trigger.alert_threshold
+            ) and not self.check_trigger_status(trigger, TriggerStatus.ACTIVE):
+                with transaction.atomic():
+                    self.trigger_alert_threshold(trigger)
+            elif (
+                trigger.resolve_threshold is not None
+                and resolve_operator(aggregation_value, trigger.resolve_threshold)
+                and self.check_trigger_status(trigger, TriggerStatus.ACTIVE)
+            ):
+                with transaction.atomic():
+                    self.trigger_resolve_threshold(trigger)
+            else:
+                self.trigger_alert_counts[trigger.id] = 0
+                self.trigger_resolve_counts[trigger.id] = 0
 
         # We update the rule stats here after we commit the transaction. This guarantees
         # that we'll never miss an update, since we'll never roll back if the process
@@ -126,98 +154,197 @@ class SubscriptionProcessor(object):
         # before the next one then we might alert twice.
         self.update_alert_rule_stats()
 
-    def trigger_alert_threshold(self):
+    def trigger_alert_threshold(self, trigger):
         """
         Called when a subscription update exceeds the value defined in the
-        `alert_rule.alert_threshold`, and there is not already an active incident. Increments the
-        count of how many times we've consecutively exceeded the threshold, and if
-        above the `threshold_period` defined in the alert rule then create an incident.
+        `trigger.alert_threshold`, and the trigger hasn't already been activated.
+        Increments the count of how many times we've consecutively exceeded the threshold, and if
+        above the `threshold_period` defined in the alert rule then mark the trigger as
+        activated, and create an incident if there isn't already one.
         :return:
         """
-        self.alert_triggers += 1
-        if self.alert_triggers >= self.alert_rule.threshold_period:
-            detected_at = to_datetime(self.last_update)
-            self.active_incident = create_incident(
-                self.alert_rule.organization,
-                IncidentType.ALERT_TRIGGERED,
-                # TODO: Include more info in name?
-                self.alert_rule.name,
-                alert_rule=self.alert_rule,
-                # TODO: Incidents need to keep track of which metric to display
-                query=self.subscription.query,
-                date_started=detected_at,
-                date_detected=detected_at,
-                projects=[self.subscription.project],
-            )
+        self.trigger_alert_counts[trigger.id] += 1
+        if self.trigger_alert_counts[trigger.id] >= self.alert_rule.threshold_period:
+            # Only create a new incident if we don't already have an active one
+            if not self.active_incident:
+                detected_at = to_datetime(self.last_update)
+                self.active_incident = create_incident(
+                    self.alert_rule.organization,
+                    IncidentType.ALERT_TRIGGERED,
+                    # TODO: Include more info in name?
+                    self.alert_rule.name,
+                    alert_rule=self.alert_rule,
+                    # TODO: Incidents need to keep track of which metric to display
+                    query=self.subscription.query,
+                    date_started=detected_at,
+                    date_detected=detected_at,
+                    projects=[self.subscription.project],
+                )
+            # Now create (or update if it already exists) the incident trigger so that
+            # we have a record of this trigger firing for this incident
+            incident_trigger = self.incident_triggers.get(trigger.id)
+            if incident_trigger:
+                incident_trigger.status = TriggerStatus.ACTIVE.value
+                incident_trigger.save()
+            else:
+                incident_trigger = IncidentTrigger.objects.create(
+                    incident=self.active_incident,
+                    alert_rule_trigger=trigger,
+                    status=TriggerStatus.ACTIVE.value,
+                )
+            self.incident_triggers[trigger.id] = incident_trigger
+
             # TODO: We should create an audit log, and maybe something that keeps
             # all of the details available for showing on the incident. Might be a json
-            # blob or w/e? Or might be able to use the audit log.
+            # blob or w/e? Or might be able to use the audit log
 
             # We now set this threshold to 0. We don't need to count it anymore
             # once we've triggered an incident.
-            self.alert_triggers = 0
+            self.trigger_alert_counts[trigger.id] = 0
 
-    def trigger_resolve_threshold(self):
+    def check_triggers_resolved(self):
+        """
+        Determines whether all triggers associated with the active incident are
+        resolved. A trigger is considered resolved if it either has no resolve
+        threshold, or is in the `TriggerStatus.Resolved` state.
+        :return:
+        """
+        for incident_trigger in self.incident_triggers.values():
+            if (
+                incident_trigger.alert_rule_trigger.resolve_threshold is not None
+                and incident_trigger.status != TriggerStatus.RESOLVED.value
+            ):
+                return False
+        return True
+
+    def trigger_resolve_threshold(self, trigger):
         """
         Called when a subscription update exceeds the value defined in
-        `alert_rule.resolve_threshold` and there's a current active incident.
+        `trigger.resolve_threshold` and the trigger is currently ACTIVE.
         :return:
         """
-        self.resolve_triggers += 1
-        if self.resolve_triggers >= self.alert_rule.threshold_period:
-            update_incident_status(self.active_incident, IncidentStatus.CLOSED)
-            self.resolve_triggers = 0
-            self.active_incident = None
+        self.trigger_resolve_counts[trigger.id] += 1
+        if self.trigger_resolve_counts[trigger.id] >= self.alert_rule.threshold_period:
+            self.incident_triggers[trigger.id].status = TriggerStatus.RESOLVED.value
+            self.incident_triggers[trigger.id].save()
+            if self.check_triggers_resolved():
+                update_incident_status(self.active_incident, IncidentStatus.CLOSED)
+                self.active_incident = None
+                self.incident_triggers.clear()
+            self.trigger_resolve_counts[trigger.id] = 0
 
     def update_alert_rule_stats(self):
         """
         Updates stats about the alert rule, if they're changed.
         :return:
         """
-        kwargs = {}
-        if self.alert_triggers != self.orig_alert_triggers:
-            self.orig_alert_triggers = kwargs["alert_triggers"] = self.alert_triggers
-        if self.resolve_triggers != self.orig_resolve_triggers:
-            self.orig_resolve_trigger = kwargs["resolve_triggers"] = self.resolve_triggers
-
-        update_alert_rule_stats(self.alert_rule, self.subscription, self.last_update, **kwargs)
+        updated_trigger_alert_counts = {
+            trigger_id: alert_count
+            for trigger_id, alert_count in self.trigger_alert_counts.items()
+            if alert_count != self.orig_trigger_alert_counts[trigger_id]
+        }
+        updated_trigger_resolve_counts = {
+            trigger_id: resolve_count
+            for trigger_id, resolve_count in self.trigger_resolve_counts.items()
+            if resolve_count != self.orig_trigger_resolve_counts[trigger_id]
+        }
+
+        update_alert_rule_stats(
+            self.alert_rule,
+            self.subscription,
+            self.last_update,
+            updated_trigger_alert_counts,
+            updated_trigger_resolve_counts,
+        )
 
 
 def build_alert_rule_stat_keys(alert_rule, subscription):
-    key_base = ALERT_RULE_BASE_STAT_KEY % (alert_rule.id, subscription.project_id)
-    return [key_base % stat_key for stat_key in ALERT_RULE_STAT_KEYS]
+    """
+    Builds keys for fetching stats about alert rules
+    :return: A list containing the alert rule stat keys
+    """
+    key_base = ALERT_RULE_BASE_KEY % (alert_rule.id, subscription.project_id)
+    return [ALERT_RULE_BASE_STAT_KEY % (key_base, stat_key) for stat_key in ALERT_RULE_STAT_KEYS]
 
 
-def get_alert_rule_stats(alert_rule, subscription):
+def build_trigger_stat_keys(alert_rule, subscription, triggers):
+    """
+    Builds keys for fetching stats about triggers
+    :return: A list containing the alert rule trigger stat keys
+    """
+    return [
+        build_alert_rule_trigger_stat_key(
+            alert_rule.id, subscription.project_id, trigger.id, stat_key
+        )
+        for trigger in triggers
+        for stat_key in ALERT_RULE_TRIGGER_STAT_KEYS
+    ]
+
+
+def build_alert_rule_trigger_stat_key(alert_rule_id, project_id, trigger_id, stat_key):
+    key_base = ALERT_RULE_BASE_KEY % (alert_rule_id, project_id)
+    return ALERT_RULE_BASE_TRIGGER_STAT_KEY % (key_base, trigger_id, stat_key)
+
+
+def partition(iterable, n):
+    """
+    Partitions an iterable into tuples of size n. Expects the iterable length to be a
+    multiple of n.
+    partition('ABCDEF', 3) --> [('ABC', 'DEF')]
+    """
+    assert len(iterable) % n == 0
+    args = [iter(iterable)] * n
+    return izip(*args)
+
+
+def get_alert_rule_stats(alert_rule, subscription, triggers):
     """
     Fetches stats about the alert rule, specific to the current subscription
     :return: A tuple containing the stats about the alert rule and subscription.
      - last_update: Int representing the timestamp it was last updated
-     - alert_triggered: Int representing how many consecutive times the we have
-       triggered the alert threshold
-     - resolve_triggered: Int representing how many consecutive times we have triggered
-       the resolve threshold
+     - trigger_alert_counts: A dict of trigger alert counts, where the key is the
+       trigger id, and the value is an int representing how many consecutive times we
+       have triggered the alert threshold
+     - trigger_resolve_counts: A dict of trigger resolve counts, where the key is the
+       trigger id, and the value is an int representing how many consecutive times we
+       have triggered the resolve threshold
     """
-    results = get_redis_client().mget(build_alert_rule_stat_keys(alert_rule, subscription))
-    return tuple(0 if result is None else int(result) for result in results)
 
+    alert_rule_keys = build_alert_rule_stat_keys(alert_rule, subscription)
+    trigger_keys = build_trigger_stat_keys(alert_rule, subscription, triggers)
+    results = get_redis_client().mget(alert_rule_keys + trigger_keys)
+    results = tuple(0 if result is None else int(result) for result in results)
+    last_update = results[0]
+    trigger_results = results[1:]
+    trigger_alert_counts = {}
+    trigger_resolve_counts = {}
+    for trigger, trigger_result in zip(
+        triggers, partition(trigger_results, len(ALERT_RULE_TRIGGER_STAT_KEYS))
+    ):
+        trigger_alert_counts[trigger.id] = trigger_result[0]
+        trigger_resolve_counts[trigger.id] = trigger_result[1]
+
+    return last_update, trigger_alert_counts, trigger_resolve_counts
 
-def update_alert_rule_stats(
-    alert_rule, subscription, last_update, alert_triggers=None, resolve_triggers=None
-):
+
+def update_alert_rule_stats(alert_rule, subscription, last_update, alert_counts, resolve_counts):
     """
-    Updates stats about the alert rule and subscription, if they're changed.
-    :return:
+    Updates stats about the alert rule, subscription and triggers if they've changed.
     """
     pipeline = get_redis_client().pipeline()
-    last_update_key, alert_trigger_key, resolve_trigger_key = build_alert_rule_stat_keys(
-        alert_rule, subscription
-    )
-    if alert_triggers is not None:
-        pipeline.set(alert_trigger_key, alert_triggers, ex=REDIS_TTL)
-    if resolve_triggers is not None:
-        pipeline.set(resolve_trigger_key, resolve_triggers, ex=REDIS_TTL)
 
+    counts_with_stat_keys = zip(ALERT_RULE_TRIGGER_STAT_KEYS, (alert_counts, resolve_counts))
+    for stat_key, trigger_counts in counts_with_stat_keys:
+        for trigger_id, alert_count in trigger_counts.items():
+            pipeline.set(
+                build_alert_rule_trigger_stat_key(
+                    alert_rule.id, subscription.project_id, trigger_id, stat_key
+                ),
+                alert_count,
+                ex=REDIS_TTL,
+            )
+
+    last_update_key = build_alert_rule_stat_keys(alert_rule, subscription)[0]
     pipeline.set(last_update_key, last_update, ex=REDIS_TTL)
     pipeline.execute()
 

+ 1529 - 0
src/sentry/south_migrations/0512_alert_rule_incident_triggers.py

@@ -0,0 +1,1529 @@
+# -*- coding: utf-8 -*-
+from south.utils import datetime_utils as datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+
+class Migration(SchemaMigration):
+
+    # Flag to indicate if this migration is too risky
+    # to run online and needs to be coordinated for offline
+    is_dangerous = False
+
+    def forwards(self, orm):
+        # Adding model 'IncidentTrigger'
+        db.create_table('sentry_incidenttrigger', (
+            ('id', self.gf('sentry.db.models.fields.bounded.BoundedBigAutoField')(primary_key=True)),
+            ('incident', self.gf('sentry.db.models.fields.foreignkey.FlexibleForeignKey')(to=orm['sentry.Incident'], db_index=False)),
+            ('alert_rule_trigger', self.gf('sentry.db.models.fields.foreignkey.FlexibleForeignKey')(to=orm['sentry.AlertRuleTrigger'])),
+            ('status', self.gf('django.db.models.fields.SmallIntegerField')()),
+            ('date_added', self.gf('django.db.models.fields.DateTimeField')(default=datetime.datetime.now)),
+        ))
+        db.send_create_signal('sentry', ['IncidentTrigger'])
+
+        # Adding unique constraint on 'IncidentTrigger', fields ['incident', 'alert_rule_trigger']
+        db.create_unique('sentry_incidenttrigger', [u'incident_id', u'alert_rule_trigger_id'])
+
+        # Adding index on 'Incident', fields ['alert_rule', 'type', 'status']
+        db.create_index('sentry_incident', [u'alert_rule_id', 'type', 'status'])
+
+
+    def backwards(self, orm):
+        # Removing index on 'Incident', fields ['alert_rule', 'type', 'status']
+        db.delete_index('sentry_incident', [u'alert_rule_id', 'type', 'status'])
+
+        # Removing unique constraint on 'IncidentTrigger', fields ['incident', 'alert_rule_trigger']
+        db.delete_unique('sentry_incidenttrigger', [u'incident_id', u'alert_rule_trigger_id'])
+
+        # Deleting model 'IncidentTrigger'
+        db.delete_table('sentry_incidenttrigger')
+
+
+    models = {
+        'sentry.activity': {
+            'Meta': {'unique_together': '()', 'object_name': 'Activity', 'index_together': '()'},
+            'data': ('sentry.db.models.fields.gzippeddict.GzippedDictField', [], {'null': 'True'}),
+            'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Group']", 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ident': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'type': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']", 'null': 'True'})
+        },
+        'sentry.alertrule': {
+            'Meta': {'unique_together': "(('organization', 'name'),)", 'object_name': 'AlertRule', 'index_together': '()'},
+            'aggregation': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'alert_threshold': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'dataset': ('django.db.models.fields.TextField', [], {}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_modified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'excluded_projects': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'alert_rule_exclusions'", 'symmetrical': 'False', 'through': "orm['sentry.AlertRuleExcludedProjects']", 'to': "orm['sentry.Project']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'include_all_projects': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.TextField', [], {}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']", 'null': 'True', 'db_index': 'False'}),
+            'query': ('django.db.models.fields.TextField', [], {}),
+            'query_subscriptions': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'alert_rules'", 'symmetrical': 'False', 'through': "orm['sentry.AlertRuleQuerySubscription']", 'to': "orm['sentry.QuerySubscription']"}),
+            'resolution': ('django.db.models.fields.IntegerField', [], {}),
+            'resolve_threshold': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'status': ('django.db.models.fields.SmallIntegerField', [], {'default': '0'}),
+            'threshold_period': ('django.db.models.fields.IntegerField', [], {}),
+            'threshold_type': ('django.db.models.fields.SmallIntegerField', [], {'null': 'True'}),
+            'time_window': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'sentry.alertruleexcludedprojects': {
+            'Meta': {'unique_together': "(('alert_rule', 'project'),)", 'object_name': 'AlertRuleExcludedProjects', 'index_together': '()'},
+            'alert_rule': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.AlertRule']", 'db_index': 'False'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"})
+        },
+        'sentry.alertrulequerysubscription': {
+            'Meta': {'unique_together': '()', 'object_name': 'AlertRuleQuerySubscription', 'index_together': '()'},
+            'alert_rule': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.AlertRule']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'query_subscription': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.QuerySubscription']", 'unique': 'True'})
+        },
+        'sentry.alertruletrigger': {
+            'Meta': {'unique_together': "(('alert_rule', 'label'),)", 'object_name': 'AlertRuleTrigger', 'index_together': '()'},
+            'alert_rule': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.AlertRule']"}),
+            'alert_threshold': ('django.db.models.fields.IntegerField', [], {}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'label': ('django.db.models.fields.TextField', [], {}),
+            'resolve_threshold': ('django.db.models.fields.IntegerField', [], {'null': 'True'}),
+            'threshold_type': ('django.db.models.fields.SmallIntegerField', [], {}),
+            'triggered_incidents': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'triggers'", 'symmetrical': 'False', 'through': "orm['sentry.IncidentTrigger']", 'to': "orm['sentry.Incident']"})
+        },
+        'sentry.alertruletriggerexclusion': {
+            'Meta': {'unique_together': "(('alert_rule_trigger', 'query_subscription'),)", 'object_name': 'AlertRuleTriggerExclusion', 'index_together': '()'},
+            'alert_rule_trigger': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'exclusions'", 'to': "orm['sentry.AlertRuleTrigger']"}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'query_subscription': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.QuerySubscription']"})
+        },
+        'sentry.apiapplication': {
+            'Meta': {'unique_together': '()', 'object_name': 'ApiApplication', 'index_together': '()'},
+            'allowed_origins': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'client_id': ('django.db.models.fields.CharField', [], {'default': "'ad7e6411695b442c93ba60f09931aee79ce5092cf5064feba37a224271b836e7'", 'unique': 'True', 'max_length': '64'}),
+            'client_secret': ('sentry.db.models.fields.encrypted.EncryptedTextField', [], {'default': "'047930f2eed348bb969937a26449ab343e3ddd519fce418cb45697dc75c7e2b7'"}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'homepage_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'default': "'Next Polecat'", 'max_length': '64', 'blank': 'True'}),
+            'owner': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"}),
+            'privacy_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+            'redirect_uris': ('django.db.models.fields.TextField', [], {}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'terms_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'})
+        },
+        'sentry.apiauthorization': {
+            'Meta': {'unique_together': "(('user', 'application'),)", 'object_name': 'ApiAuthorization', 'index_together': '()'},
+            'application': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.ApiApplication']", 'null': 'True'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'scope_list': ('sentry.db.models.fields.array.ArrayField', [], {'of': (u'django.db.models.fields.TextField', [], {})}),
+            'scopes': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.apigrant': {
+            'Meta': {'unique_together': '()', 'object_name': 'ApiGrant', 'index_together': '()'},
+            'application': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.ApiApplication']"}),
+            'code': ('django.db.models.fields.CharField', [], {'default': "'64bf5143d54e4441a5d02c8d695ff0ba'", 'max_length': '64', 'db_index': 'True'}),
+            'expires_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2019, 9, 25, 0, 0)', 'db_index': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'redirect_uri': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'scope_list': ('sentry.db.models.fields.array.ArrayField', [], {'of': (u'django.db.models.fields.TextField', [], {})}),
+            'scopes': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.apikey': {
+            'Meta': {'unique_together': '()', 'object_name': 'ApiKey', 'index_together': '()'},
+            'allowed_origins': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32'}),
+            'label': ('django.db.models.fields.CharField', [], {'default': "'Default'", 'max_length': '64', 'blank': 'True'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'key_set'", 'to': "orm['sentry.Organization']"}),
+            'scope_list': ('sentry.db.models.fields.array.ArrayField', [], {'of': (u'django.db.models.fields.TextField', [], {})}),
+            'scopes': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'db_index': 'True'})
+        },
+        'sentry.apitoken': {
+            'Meta': {'unique_together': '()', 'object_name': 'ApiToken', 'index_together': '()'},
+            'application': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.ApiApplication']", 'null': 'True'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'expires_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2019, 10, 25, 0, 0)', 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'refresh_token': ('django.db.models.fields.CharField', [], {'default': "'f82b73b1d44b47fabc0f8d0c23906eba696e5af7681a4615bd47b509f40ec0b0'", 'max_length': '64', 'unique': 'True', 'null': 'True'}),
+            'scope_list': ('sentry.db.models.fields.array.ArrayField', [], {'of': (u'django.db.models.fields.TextField', [], {})}),
+            'scopes': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}),
+            'token': ('django.db.models.fields.CharField', [], {'default': "'df6ac73452c140e592a540838efe32f6a91761a7e9164217af5849711b428089'", 'unique': 'True', 'max_length': '64'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.assistantactivity': {
+            'Meta': {'unique_together': "(('user', 'guide_id'),)", 'object_name': 'AssistantActivity', 'db_table': "'sentry_assistant_activity'", 'index_together': '()'},
+            'dismissed_ts': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'guide_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'useful': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"}),
+            'viewed_ts': ('django.db.models.fields.DateTimeField', [], {'null': 'True'})
+        },
+        'sentry.auditlogentry': {
+            'Meta': {'unique_together': '()', 'object_name': 'AuditLogEntry', 'index_together': '()'},
+            'actor': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'blank': 'True', 'related_name': "u'audit_actors'", 'null': 'True', 'to': "orm['sentry.User']"}),
+            'actor_key': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.ApiKey']", 'null': 'True', 'blank': 'True'}),
+            'actor_label': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
+            'data': ('sentry.db.models.fields.gzippeddict.GzippedDictField', [], {}),
+            'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'event': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'target_object': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'target_user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'blank': 'True', 'related_name': "u'audit_targets'", 'null': 'True', 'to': "orm['sentry.User']"})
+        },
+        'sentry.authenticator': {
+            'Meta': {'unique_together': "(('user', 'type'),)", 'object_name': 'Authenticator', 'db_table': "'auth_authenticator'", 'index_together': '()'},
+            'config': ('sentry.db.models.fields.encrypted.EncryptedPickledObjectField', [], {}),
+            'created_at': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedAutoField', [], {'primary_key': 'True'}),
+            'last_used_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'type': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.authidentity': {
+            'Meta': {'unique_together': "(('auth_provider', 'ident'), ('auth_provider', 'user'))", 'object_name': 'AuthIdentity', 'index_together': '()'},
+            'auth_provider': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.AuthProvider']"}),
+            'data': ('sentry.db.models.fields.encrypted.EncryptedJsonField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ident': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'last_synced': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_verified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.authprovider': {
+            'Meta': {'unique_together': '()', 'object_name': 'AuthProvider', 'index_together': '()'},
+            'config': ('sentry.db.models.fields.encrypted.EncryptedJsonField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'default_global_access': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'default_role': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '50'}),
+            'default_teams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sentry.Team']", 'symmetrical': 'False', 'blank': 'True'}),
+            'flags': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'last_sync': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']", 'unique': 'True'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'sync_time': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'})
+        },
+        'sentry.broadcast': {
+            'Meta': {'unique_together': '()', 'object_name': 'Broadcast', 'index_together': '()'},
+            'cta': ('django.db.models.fields.CharField', [], {'max_length': '256', 'null': 'True', 'blank': 'True'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_expires': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2019, 10, 2, 0, 0)', 'null': 'True', 'blank': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'db_index': 'True'}),
+            'link': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+            'message': ('django.db.models.fields.CharField', [], {'max_length': '256'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'upstream_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'})
+        },
+        'sentry.broadcastseen': {
+            'Meta': {'unique_together': "(('broadcast', 'user'),)", 'object_name': 'BroadcastSeen', 'index_together': '()'},
+            'broadcast': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Broadcast']"}),
+            'date_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.commit': {
+            'Meta': {'unique_together': "(('repository_id', 'key'),)", 'object_name': 'Commit', 'index_together': "(('repository_id', 'date_added'),)"},
+            'author': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.CommitAuthor']", 'null': 'True'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'message': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'repository_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {})
+        },
+        'sentry.commitauthor': {
+            'Meta': {'unique_together': "(('organization_id', 'email'), ('organization_id', 'external_id'))", 'object_name': 'CommitAuthor', 'index_together': '()'},
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'external_id': ('django.db.models.fields.CharField', [], {'max_length': '164', 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'})
+        },
+        'sentry.commitfilechange': {
+            'Meta': {'unique_together': "(('commit', 'filename'),)", 'object_name': 'CommitFileChange', 'index_together': '()'},
+            'commit': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Commit']"}),
+            'filename': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '1'})
+        },
+        'sentry.counter': {
+            'Meta': {'unique_together': '()', 'object_name': 'Counter', 'db_table': "'sentry_projectcounter'", 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']", 'unique': 'True'}),
+            'value': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {})
+        },
+        'sentry.dashboard': {
+            'Meta': {'unique_together': "(('organization', 'title'),)", 'object_name': 'Dashboard', 'index_together': '()'},
+            'created_by': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'sentry.deletedorganization': {
+            'Meta': {'unique_together': '()', 'object_name': 'DeletedOrganization', 'index_together': '()'},
+            'actor_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'}),
+            'actor_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'actor_label': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'date_deleted': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'})
+        },
+        'sentry.deletedproject': {
+            'Meta': {'unique_together': '()', 'object_name': 'DeletedProject', 'index_together': '()'},
+            'actor_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'}),
+            'actor_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'actor_label': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'date_deleted': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'}),
+            'organization_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'organization_slug': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
+            'platform': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'})
+        },
+        'sentry.deletedteam': {
+            'Meta': {'unique_together': '()', 'object_name': 'DeletedTeam', 'index_together': '()'},
+            'actor_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'}),
+            'actor_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'actor_label': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'date_deleted': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'}),
+            'organization_name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'organization_slug': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'}),
+            'reason': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
+            'slug': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True'})
+        },
+        'sentry.deploy': {
+            'Meta': {'unique_together': '()', 'object_name': 'Deploy', 'index_together': '()'},
+            'date_finished': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_started': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'environment_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
+            'notified': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'release': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Release']"}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'})
+        },
+        'sentry.discoversavedquery': {
+            'Meta': {'unique_together': '()', 'object_name': 'DiscoverSavedQuery', 'index_together': '()'},
+            'created_by': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'date_updated': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'blank': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'projects': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sentry.Project']", 'through': "orm['sentry.DiscoverSavedQueryProject']", 'symmetrical': 'False'}),
+            'query': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'})
+        },
+        'sentry.discoversavedqueryproject': {
+            'Meta': {'unique_together': "(('project', 'discover_saved_query'),)", 'object_name': 'DiscoverSavedQueryProject', 'index_together': '()'},
+            'discover_saved_query': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.DiscoverSavedQuery']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"})
+        },
+        'sentry.distribution': {
+            'Meta': {'unique_together': "(('release', 'name'),)", 'object_name': 'Distribution', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'release': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Release']"})
+        },
+        'sentry.email': {
+            'Meta': {'unique_together': '()', 'object_name': 'Email', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('sentry.db.models.fields.citext.CIEmailField', [], {'unique': 'True', 'max_length': '75'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'})
+        },
+        'sentry.environment': {
+            'Meta': {'unique_together': "(('organization_id', 'name'),)", 'object_name': 'Environment', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'projects': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sentry.Project']", 'through': "orm['sentry.EnvironmentProject']", 'symmetrical': 'False'})
+        },
+        'sentry.environmentproject': {
+            'Meta': {'unique_together': "(('project', 'environment'),)", 'object_name': 'EnvironmentProject', 'index_together': '()'},
+            'environment': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Environment']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'is_hidden': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"})
+        },
+        'sentry.event': {
+            'Meta': {'unique_together': "(('project_id', 'event_id'),)", 'object_name': 'Event', 'db_table': "'sentry_message'", 'index_together': "(('group_id', 'datetime'),)"},
+            'data': ('sentry.db.models.fields.node.NodeField', [], {'null': 'True', 'blank': 'True'}),
+            'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'event_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'db_column': "'message_id'"}),
+            'group_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'message': ('django.db.models.fields.TextField', [], {}),
+            'platform': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'time_spent': ('sentry.db.models.fields.bounded.BoundedIntegerField', [], {'null': 'True'})
+        },
+        'sentry.eventattachment': {
+            'Meta': {'unique_together': "(('project_id', 'event_id', 'file'),)", 'object_name': 'EventAttachment', 'index_together': "(('project_id', 'date_added'),)"},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'event_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'db_index': 'True'}),
+            'file': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.File']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.TextField', [], {}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {})
+        },
+        'sentry.eventprocessingissue': {
+            'Meta': {'unique_together': "(('raw_event', 'processing_issue'),)", 'object_name': 'EventProcessingIssue', 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'processing_issue': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.ProcessingIssue']"}),
+            'raw_event': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.RawEvent']"})
+        },
+        'sentry.eventtag': {
+            'Meta': {'unique_together': "(('event_id', 'key_id', 'value_id'),)", 'object_name': 'EventTag', 'index_together': "(('group_id', 'key_id', 'value_id'),)"},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'event_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {}),
+            'group_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {}),
+            'value_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {})
+        },
+        'sentry.eventuser': {
+            'Meta': {'unique_together': "(('project_id', 'ident'), ('project_id', 'hash'))", 'object_name': 'EventUser', 'index_together': "(('project_id', 'email'), ('project_id', 'username'), ('project_id', 'ip_address'))"},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True'}),
+            'hash': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ident': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+            'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39', 'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True'})
+        },
+        'sentry.externalissue': {
+            'Meta': {'unique_together': "(('organization_id', 'integration_id', 'key'),)", 'object_name': 'ExternalIssue', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'description': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'integration_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'metadata': ('sentry.db.models.fields.jsonfield.JSONField', [], {'null': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'title': ('django.db.models.fields.TextField', [], {'null': 'True'})
+        },
+        'sentry.featureadoption': {
+            'Meta': {'unique_together': "(('organization', 'feature_id'),)", 'object_name': 'FeatureAdoption', 'index_together': '()'},
+            'applicable': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'complete': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'data': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'}),
+            'date_completed': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'feature_id': ('django.db.models.fields.PositiveIntegerField', [], {}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"})
+        },
+        'sentry.file': {
+            'Meta': {'unique_together': '()', 'object_name': 'File', 'index_together': '()'},
+            'blob': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'legacy_blob'", 'null': 'True', 'to': "orm['sentry.FileBlob']"}),
+            'blobs': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sentry.FileBlob']", 'through': "orm['sentry.FileBlobIndex']", 'symmetrical': 'False'}),
+            'checksum': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'db_index': 'True'}),
+            'headers': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.TextField', [], {}),
+            'path': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'size': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '64'})
+        },
+        'sentry.fileblob': {
+            'Meta': {'unique_together': '()', 'object_name': 'FileBlob', 'index_together': '()'},
+            'checksum': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'path': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'size': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'timestamp': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'})
+        },
+        'sentry.fileblobindex': {
+            'Meta': {'unique_together': "(('file', 'blob', 'offset'),)", 'object_name': 'FileBlobIndex', 'index_together': '()'},
+            'blob': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.FileBlob']"}),
+            'file': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.File']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'offset': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {})
+        },
+        'sentry.fileblobowner': {
+            'Meta': {'unique_together': "(('blob', 'organization'),)", 'object_name': 'FileBlobOwner', 'index_together': '()'},
+            'blob': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.FileBlob']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"})
+        },
+        'sentry.group': {
+            'Meta': {'unique_together': "(('project', 'short_id'),)", 'object_name': 'Group', 'db_table': "'sentry_groupedmessage'", 'index_together': "(('project', 'first_release'), ('project', 'id'))"},
+            'active_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
+            'culprit': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'db_column': "'view'", 'blank': 'True'}),
+            'data': ('sentry.db.models.fields.gzippeddict.GzippedDictField', [], {'null': 'True', 'blank': 'True'}),
+            'first_release': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Release']", 'null': 'True', 'on_delete': 'models.PROTECT'}),
+            'first_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'is_public': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'blank': 'True'}),
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'level': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '40', 'db_index': 'True', 'blank': 'True'}),
+            'logger': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64', 'db_index': 'True', 'blank': 'True'}),
+            'message': ('django.db.models.fields.TextField', [], {}),
+            'num_comments': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'null': 'True'}),
+            'platform': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'resolved_at': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'db_index': 'True'}),
+            'score': ('sentry.db.models.fields.bounded.BoundedIntegerField', [], {'default': '0'}),
+            'short_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'time_spent_count': ('sentry.db.models.fields.bounded.BoundedIntegerField', [], {'default': '0'}),
+            'time_spent_total': ('sentry.db.models.fields.bounded.BoundedIntegerField', [], {'default': '0'}),
+            'times_seen': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '1', 'db_index': 'True'})
+        },
+        'sentry.groupassignee': {
+            'Meta': {'unique_together': "(('project', 'group'),)", 'object_name': 'GroupAssignee', 'db_table': "'sentry_groupasignee'", 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'assignee_set'", 'unique': 'True', 'to': "orm['sentry.Group']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'assignee_set'", 'to': "orm['sentry.Project']"}),
+            'team': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'sentry_assignee_set'", 'null': 'True', 'to': "orm['sentry.Team']"}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'sentry_assignee_set'", 'null': 'True', 'to': "orm['sentry.User']"})
+        },
+        'sentry.groupbookmark': {
+            'Meta': {'unique_together': "(('project', 'user', 'group'),)", 'object_name': 'GroupBookmark', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True'}),
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'bookmark_set'", 'to': "orm['sentry.Group']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'bookmark_set'", 'to': "orm['sentry.Project']"}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'sentry_bookmark_set'", 'to': "orm['sentry.User']"})
+        },
+        'sentry.groupcommitresolution': {
+            'Meta': {'unique_together': "(('group_id', 'commit_id'),)", 'object_name': 'GroupCommitResolution', 'index_together': '()'},
+            'commit_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'group_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'})
+        },
+        'sentry.groupemailthread': {
+            'Meta': {'unique_together': "(('email', 'group'), ('email', 'msgid'))", 'object_name': 'GroupEmailThread', 'index_together': '()'},
+            'date': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'groupemail_set'", 'to': "orm['sentry.Group']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'msgid': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'groupemail_set'", 'to': "orm['sentry.Project']"})
+        },
+        'sentry.groupenvironment': {
+            'Meta': {'unique_together': "(('group', 'environment'),)", 'object_name': 'GroupEnvironment', 'index_together': "(('environment', 'first_release'),)"},
+            'environment': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Environment']"}),
+            'first_release': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Release']", 'null': 'True', 'on_delete': 'models.DO_NOTHING'}),
+            'first_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'db_index': 'True'}),
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Group']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'})
+        },
+        'sentry.grouphash': {
+            'Meta': {'unique_together': "(('project', 'hash'),)", 'object_name': 'GroupHash', 'index_together': '()'},
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Group']", 'null': 'True'}),
+            'group_tombstone_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True', 'db_index': 'True'}),
+            'hash': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']", 'null': 'True'}),
+            'state': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'})
+        },
+        'sentry.grouplink': {
+            'Meta': {'unique_together': "(('group_id', 'linked_type', 'linked_id'),)", 'object_name': 'GroupLink', 'index_together': '()'},
+            'data': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'}),
+            'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'group_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'linked_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {}),
+            'linked_type': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '1'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'db_index': 'True'}),
+            'relationship': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '2'})
+        },
+        'sentry.groupmeta': {
+            'Meta': {'unique_together': "(('group', 'key'),)", 'object_name': 'GroupMeta', 'index_together': '()'},
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Group']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'value': ('django.db.models.fields.TextField', [], {})
+        },
+        'sentry.groupredirect': {
+            'Meta': {'unique_together': "(('organization_id', 'previous_short_id', 'previous_project_slug'),)", 'object_name': 'GroupRedirect', 'index_together': '()'},
+            'group_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'db_index': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'}),
+            'previous_group_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'unique': 'True'}),
+            'previous_project_slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True'}),
+            'previous_short_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'})
+        },
+        'sentry.grouprelease': {
+            'Meta': {'unique_together': "(('group_id', 'release_id', 'environment'),)", 'object_name': 'GroupRelease', 'index_together': '()'},
+            'environment': ('django.db.models.fields.CharField', [], {'default': "''", 'max_length': '64'}),
+            'first_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'group_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'release_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'})
+        },
+        'sentry.groupresolution': {
+            'Meta': {'unique_together': '()', 'object_name': 'GroupResolution', 'index_together': '()'},
+            'actor_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Group']", 'unique': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'release': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Release']"}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'type': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'})
+        },
+        'sentry.grouprulestatus': {
+            'Meta': {'unique_together': "(('rule', 'group'),)", 'object_name': 'GroupRuleStatus', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Group']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'last_active': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'rule': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Rule']"}),
+            'status': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'})
+        },
+        'sentry.groupseen': {
+            'Meta': {'unique_together': "(('user', 'group'),)", 'object_name': 'GroupSeen', 'index_together': '()'},
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Group']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']", 'db_index': 'False'})
+        },
+        'sentry.groupshare': {
+            'Meta': {'unique_together': '()', 'object_name': 'GroupShare', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Group']", 'unique': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']", 'null': 'True'}),
+            'uuid': ('django.db.models.fields.CharField', [], {'default': "'d9f46e4ab272454593db8efa077ac82d'", 'unique': 'True', 'max_length': '32'})
+        },
+        'sentry.groupsnooze': {
+            'Meta': {'unique_together': '()', 'object_name': 'GroupSnooze', 'index_together': '()'},
+            'actor_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'count': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Group']", 'unique': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'state': ('sentry.db.models.fields.jsonfield.JSONField', [], {'null': 'True'}),
+            'until': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'user_count': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'user_window': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'window': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'})
+        },
+        'sentry.groupsubscription': {
+            'Meta': {'unique_together': "(('group', 'user'),)", 'object_name': 'GroupSubscription', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True'}),
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'subscription_set'", 'to': "orm['sentry.Group']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'subscription_set'", 'to': "orm['sentry.Project']"}),
+            'reason': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.grouptagkey': {
+            'Meta': {'unique_together': "(('project_id', 'group_id', 'key'),)", 'object_name': 'GroupTagKey', 'index_together': '()'},
+            'group_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True', 'db_index': 'True'}),
+            'values_seen': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'})
+        },
+        'sentry.grouptagvalue': {
+            'Meta': {'unique_together': "(('group_id', 'key', 'value'),)", 'object_name': 'GroupTagValue', 'db_table': "'sentry_messagefiltervalue'", 'index_together': "(('project_id', 'key', 'value', 'last_seen'),)"},
+            'first_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'db_index': 'True'}),
+            'group_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'db_index': 'True'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True', 'db_index': 'True'}),
+            'times_seen': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+        },
+        'sentry.grouptombstone': {
+            'Meta': {'unique_together': '()', 'object_name': 'GroupTombstone', 'index_together': '()'},
+            'actor_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'culprit': ('django.db.models.fields.CharField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+            'data': ('sentry.db.models.fields.gzippeddict.GzippedDictField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'level': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '40', 'blank': 'True'}),
+            'message': ('django.db.models.fields.TextField', [], {}),
+            'previous_group_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'unique': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"})
+        },
+        'sentry.identity': {
+            'Meta': {'unique_together': "(('idp', 'external_id'), ('idp', 'user'))", 'object_name': 'Identity', 'index_together': '()'},
+            'data': ('sentry.db.models.fields.encrypted.EncryptedJsonField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_verified': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'external_id': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'idp': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.IdentityProvider']"}),
+            'scopes': ('sentry.db.models.fields.array.ArrayField', [], {'of': (u'django.db.models.fields.TextField', [], {})}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.identityprovider': {
+            'Meta': {'unique_together': "(('type', 'external_id'),)", 'object_name': 'IdentityProvider', 'index_together': '()'},
+            'config': ('sentry.db.models.fields.encrypted.EncryptedJsonField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True'}),
+            'external_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '64'})
+        },
+        'sentry.incident': {
+            'Meta': {'unique_together': "(('organization', 'identifier'),)", 'object_name': 'Incident', 'index_together': "(('alert_rule', 'type', 'status'),)"},
+            'alert_rule': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.AlertRule']", 'null': 'True', 'on_delete': 'models.SET_NULL'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_closed': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'date_detected': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_started': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'detection_uuid': ('sentry.db.models.fields.uuid.UUIDField', [], {'max_length': '32', 'null': 'True', 'db_index': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'incidents'", 'symmetrical': 'False', 'through': "orm['sentry.IncidentGroup']", 'to': "orm['sentry.Group']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'identifier': ('django.db.models.fields.IntegerField', [], {}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'projects': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'incidents'", 'symmetrical': 'False', 'through': "orm['sentry.IncidentProject']", 'to': "orm['sentry.Project']"}),
+            'query': ('django.db.models.fields.TextField', [], {}),
+            'status': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'}),
+            'title': ('django.db.models.fields.TextField', [], {}),
+            'type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '1'})
+        },
+        'sentry.incidentactivity': {
+            'Meta': {'unique_together': '()', 'object_name': 'IncidentActivity', 'index_together': '()'},
+            'comment': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'event_stats_snapshot': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.TimeSeriesSnapshot']", 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'incident': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Incident']"}),
+            'previous_value': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'type': ('django.db.models.fields.IntegerField', [], {}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']", 'null': 'True'}),
+            'value': ('django.db.models.fields.TextField', [], {'null': 'True'})
+        },
+        'sentry.incidentgroup': {
+            'Meta': {'unique_together': "(('group', 'incident'),)", 'object_name': 'IncidentGroup', 'index_together': '()'},
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Group']", 'db_index': 'False'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'incident': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Incident']"})
+        },
+        'sentry.incidentproject': {
+            'Meta': {'unique_together': "(('project', 'incident'),)", 'object_name': 'IncidentProject', 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'incident': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Incident']"}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']", 'db_index': 'False'})
+        },
+        'sentry.incidentseen': {
+            'Meta': {'unique_together': "(('user', 'incident'),)", 'object_name': 'IncidentSeen', 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'incident': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Incident']"}),
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']", 'db_index': 'False'})
+        },
+        'sentry.incidentsnapshot': {
+            'Meta': {'unique_together': '()', 'object_name': 'IncidentSnapshot', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'event_stats_snapshot': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.TimeSeriesSnapshot']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'incident': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['sentry.Incident']", 'unique': 'True'}),
+            'total_events': ('django.db.models.fields.IntegerField', [], {}),
+            'unique_users': ('django.db.models.fields.IntegerField', [], {})
+        },
+        'sentry.incidentsubscription': {
+            'Meta': {'unique_together': "(('incident', 'user'),)", 'object_name': 'IncidentSubscription', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'incident': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Incident']", 'db_index': 'False'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.incidentsuspectcommit': {
+            'Meta': {'unique_together': "(('incident', 'commit'),)", 'object_name': 'IncidentSuspectCommit', 'index_together': '()'},
+            'commit': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Commit']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'incident': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Incident']", 'db_index': 'False'}),
+            'order': ('django.db.models.fields.SmallIntegerField', [], {})
+        },
+        'sentry.incidenttrigger': {
+            'Meta': {'unique_together': "(('incident', 'alert_rule_trigger'),)", 'object_name': 'IncidentTrigger', 'index_together': '()'},
+            'alert_rule_trigger': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.AlertRuleTrigger']"}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'incident': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Incident']", 'db_index': 'False'}),
+            'status': ('django.db.models.fields.SmallIntegerField', [], {})
+        },
+        'sentry.integration': {
+            'Meta': {'unique_together': "(('provider', 'external_id'),)", 'object_name': 'Integration', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True'}),
+            'external_id': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'metadata': ('sentry.db.models.fields.encrypted.EncryptedJsonField', [], {'default': '{}'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'organizations': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'integrations'", 'symmetrical': 'False', 'through': "orm['sentry.OrganizationIntegration']", 'to': "orm['sentry.Organization']"}),
+            'projects': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'integrations'", 'symmetrical': 'False', 'through': "orm['sentry.ProjectIntegration']", 'to': "orm['sentry.Project']"}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'null': 'True'})
+        },
+        'sentry.integrationexternalproject': {
+            'Meta': {'unique_together': "(('organization_integration_id', 'external_id'),)", 'object_name': 'IntegrationExternalProject', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'external_id': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'organization_integration_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'resolved_status': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'unresolved_status': ('django.db.models.fields.CharField', [], {'max_length': '64'})
+        },
+        'sentry.integrationfeature': {
+            'Meta': {'unique_together': "(('sentry_app', 'feature'),)", 'object_name': 'IntegrationFeature', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'feature': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'sentry_app': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.SentryApp']"}),
+            'user_description': ('django.db.models.fields.TextField', [], {'null': 'True'})
+        },
+        'sentry.latestrelease': {
+            'Meta': {'unique_together': "(('repository_id', 'environment_id'),)", 'object_name': 'LatestRelease', 'index_together': '()'},
+            'commit_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'}),
+            'deploy_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'}),
+            'environment_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'release_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {}),
+            'repository_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {})
+        },
+        'sentry.lostpasswordhash': {
+            'Meta': {'unique_together': '()', 'object_name': 'LostPasswordHash', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'hash': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']", 'unique': 'True'})
+        },
+        'sentry.monitor': {
+            'Meta': {'unique_together': '()', 'object_name': 'Monitor', 'index_together': "(('type', 'next_checkin'),)"},
+            'config': ('sentry.db.models.fields.encrypted.EncryptedJsonField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'guid': ('sentry.db.models.fields.uuid.UUIDField', [], {'auto_add': "'uuid:uuid4'", 'unique': 'True', 'max_length': '32'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'last_checkin': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'next_checkin': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'type': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'})
+        },
+        'sentry.monitorcheckin': {
+            'Meta': {'unique_together': '()', 'object_name': 'MonitorCheckIn', 'index_together': '()'},
+            'config': ('sentry.db.models.fields.encrypted.EncryptedJsonField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'duration': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'guid': ('sentry.db.models.fields.uuid.UUIDField', [], {'auto_add': "'uuid:uuid4'", 'unique': 'True', 'max_length': '32'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'location': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.MonitorLocation']", 'null': 'True'}),
+            'monitor': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Monitor']"}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'})
+        },
+        'sentry.monitorlocation': {
+            'Meta': {'unique_together': '()', 'object_name': 'MonitorLocation', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'guid': ('sentry.db.models.fields.uuid.UUIDField', [], {'auto_add': "'uuid:uuid4'", 'unique': 'True', 'max_length': '32'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'})
+        },
+        'sentry.option': {
+            'Meta': {'unique_together': '()', 'object_name': 'Option', 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+            'last_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'value': ('sentry.db.models.fields.encrypted.EncryptedPickledObjectField', [], {})
+        },
+        'sentry.organization': {
+            'Meta': {'unique_together': '()', 'object_name': 'Organization', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'default_role': ('django.db.models.fields.CharField', [], {'default': "'member'", 'max_length': '32'}),
+            'flags': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'members': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'org_memberships'", 'symmetrical': 'False', 'through': "orm['sentry.OrganizationMember']", 'to': "orm['sentry.User']"}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '50'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'})
+        },
+        'sentry.organizationaccessrequest': {
+            'Meta': {'unique_together': "(('team', 'member'),)", 'object_name': 'OrganizationAccessRequest', 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'member': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.OrganizationMember']"}),
+            'team': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Team']"})
+        },
+        'sentry.organizationavatar': {
+            'Meta': {'unique_together': '()', 'object_name': 'OrganizationAvatar', 'index_together': '()'},
+            'avatar_type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
+            'file': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.File']", 'unique': 'True', 'null': 'True', 'on_delete': 'models.SET_NULL'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ident': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'avatar'", 'unique': 'True', 'to': "orm['sentry.Organization']"})
+        },
+        'sentry.organizationintegration': {
+            'Meta': {'unique_together': "(('organization', 'integration'),)", 'object_name': 'OrganizationIntegration', 'index_together': '()'},
+            'config': ('sentry.db.models.fields.encrypted.EncryptedJsonField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True'}),
+            'default_auth_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True', 'db_index': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'integration': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Integration']"}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'})
+        },
+        'sentry.organizationmember': {
+            'Meta': {'unique_together': "(('organization', 'user'), ('organization', 'email'))", 'object_name': 'OrganizationMember', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'null': 'True', 'blank': 'True'}),
+            'flags': ('django.db.models.fields.BigIntegerField', [], {'default': '0'}),
+            'has_global_access': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'invite_status': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'null': 'True'}),
+            'inviter': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'blank': 'True', 'related_name': "u'sentry_inviter_set'", 'null': 'True', 'to': "orm['sentry.User']"}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'member_set'", 'to': "orm['sentry.Organization']"}),
+            'role': ('django.db.models.fields.CharField', [], {'default': "'member'", 'max_length': '32'}),
+            'teams': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['sentry.Team']", 'symmetrical': 'False', 'through': "orm['sentry.OrganizationMemberTeam']", 'blank': 'True'}),
+            'token': ('django.db.models.fields.CharField', [], {'max_length': '64', 'unique': 'True', 'null': 'True', 'blank': 'True'}),
+            'token_expires_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}),
+            'type': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '50', 'blank': 'True'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'blank': 'True', 'related_name': "u'sentry_orgmember_set'", 'null': 'True', 'to': "orm['sentry.User']"})
+        },
+        'sentry.organizationmemberteam': {
+            'Meta': {'unique_together': "(('team', 'organizationmember'),)", 'object_name': 'OrganizationMemberTeam', 'db_table': "'sentry_organizationmember_teams'", 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedAutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'organizationmember': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.OrganizationMember']"}),
+            'team': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Team']"})
+        },
+        'sentry.organizationonboardingtask': {
+            'Meta': {'unique_together': "(('organization', 'task'),)", 'object_name': 'OrganizationOnboardingTask', 'index_together': '()'},
+            'data': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'}),
+            'date_completed': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'task': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']", 'null': 'True'})
+        },
+        'sentry.organizationoption': {
+            'Meta': {'unique_together': "(('organization', 'key'),)", 'object_name': 'OrganizationOption', 'db_table': "'sentry_organizationoptions'", 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'value': ('sentry.db.models.fields.encrypted.EncryptedPickledObjectField', [], {})
+        },
+        'sentry.pagerdutyserviceproject': {
+            'Meta': {'unique_together': "(('project', 'organization_integration'),)", 'object_name': 'PagerDutyServiceProject', 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'integration_key': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'organization_integration': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.OrganizationIntegration']"}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']", 'db_index': 'False'}),
+            'service_id': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'service_name': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'sentry.platformexternalissue': {
+            'Meta': {'unique_together': "(('group_id', 'service_type'),)", 'object_name': 'PlatformExternalIssue', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'display_name': ('django.db.models.fields.TextField', [], {}),
+            'group_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'service_type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'web_url': ('django.db.models.fields.URLField', [], {'max_length': '200'})
+        },
+        'sentry.processingissue': {
+            'Meta': {'unique_together': "(('project', 'checksum', 'type'),)", 'object_name': 'ProcessingIssue', 'index_together': '()'},
+            'checksum': ('django.db.models.fields.CharField', [], {'max_length': '40', 'db_index': 'True'}),
+            'data': ('sentry.db.models.fields.gzippeddict.GzippedDictField', [], {}),
+            'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '30'})
+        },
+        'sentry.project': {
+            'Meta': {'unique_together': "(('organization', 'slug'),)", 'object_name': 'Project', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'first_event': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'flags': ('django.db.models.fields.BigIntegerField', [], {'default': '0', 'null': 'True'}),
+            'forced_color': ('django.db.models.fields.CharField', [], {'max_length': '6', 'null': 'True', 'blank': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'platform': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50', 'null': 'True'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'teams': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'teams'", 'symmetrical': 'False', 'through': "orm['sentry.ProjectTeam']", 'to': "orm['sentry.Team']"})
+        },
+        'sentry.projectavatar': {
+            'Meta': {'unique_together': '()', 'object_name': 'ProjectAvatar', 'index_together': '()'},
+            'avatar_type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
+            'file': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.File']", 'unique': 'True', 'null': 'True', 'on_delete': 'models.SET_NULL'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ident': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'avatar'", 'unique': 'True', 'to': "orm['sentry.Project']"})
+        },
+        'sentry.projectbookmark': {
+            'Meta': {'unique_together': "(('project', 'user'),)", 'object_name': 'ProjectBookmark', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']", 'null': 'True', 'blank': 'True'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.projectdebugfile': {
+            'Meta': {'unique_together': '()', 'object_name': 'ProjectDebugFile', 'db_table': "'sentry_projectdsymfile'", 'index_together': "(('project', 'debug_id'), ('project', 'code_id'))"},
+            'code_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'cpu_name': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'data': ('sentry.db.models.fields.jsonfield.JSONField', [], {'null': 'True'}),
+            'debug_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'db_column': "'uuid'"}),
+            'file': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.File']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'object_name': ('django.db.models.fields.TextField', [], {}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']", 'null': 'True'})
+        },
+        'sentry.projectintegration': {
+            'Meta': {'unique_together': "(('project', 'integration'),)", 'object_name': 'ProjectIntegration', 'index_together': '()'},
+            'config': ('sentry.db.models.fields.encrypted.EncryptedJsonField', [], {'default': '{}'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'integration': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Integration']"}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"})
+        },
+        'sentry.projectkey': {
+            'Meta': {'unique_together': '()', 'object_name': 'ProjectKey', 'index_together': '()'},
+            'data': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'label': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'key_set'", 'to': "orm['sentry.Project']"}),
+            'public_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'unique': 'True', 'null': 'True'}),
+            'rate_limit_count': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'rate_limit_window': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'roles': ('django.db.models.fields.BigIntegerField', [], {'default': '1'}),
+            'secret_key': ('django.db.models.fields.CharField', [], {'max_length': '32', 'unique': 'True', 'null': 'True'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'db_index': 'True'})
+        },
+        'sentry.projectoption': {
+            'Meta': {'unique_together': "(('project', 'key'),)", 'object_name': 'ProjectOption', 'db_table': "'sentry_projectoptions'", 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'value': ('sentry.db.models.fields.encrypted.EncryptedPickledObjectField', [], {})
+        },
+        'sentry.projectownership': {
+            'Meta': {'unique_together': '()', 'object_name': 'ProjectOwnership', 'index_together': '()'},
+            'auto_assignment': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'date_created': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'fallthrough': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'last_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']", 'unique': 'True'}),
+            'raw': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'schema': ('sentry.db.models.fields.jsonfield.JSONField', [], {'null': 'True'})
+        },
+        'sentry.projectplatform': {
+            'Meta': {'unique_together': "(('project_id', 'platform'),)", 'object_name': 'ProjectPlatform', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'platform': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {})
+        },
+        'sentry.projectredirect': {
+            'Meta': {'unique_together': "(('organization', 'redirect_slug'),)", 'object_name': 'ProjectRedirect', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'redirect_slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'})
+        },
+        'sentry.projectteam': {
+            'Meta': {'unique_together': "(('project', 'team'),)", 'object_name': 'ProjectTeam', 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'team': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Team']"})
+        },
+        'sentry.promptsactivity': {
+            'Meta': {'unique_together': "(('user', 'feature', 'organization_id', 'project_id'),)", 'object_name': 'PromptsActivity', 'index_together': '()'},
+            'data': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'feature': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.pullrequest': {
+            'Meta': {'unique_together': "(('repository_id', 'key'),)", 'object_name': 'PullRequest', 'db_table': "'sentry_pull_request'", 'index_together': "(('repository_id', 'date_added'), ('organization_id', 'merge_commit_sha'))"},
+            'author': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.CommitAuthor']", 'null': 'True'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'merge_commit_sha': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'message': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'repository_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'title': ('django.db.models.fields.TextField', [], {'null': 'True'})
+        },
+        'sentry.pullrequestcommit': {
+            'Meta': {'unique_together': "(('pull_request', 'commit'),)", 'object_name': 'PullRequestCommit', 'db_table': "'sentry_pullrequest_commit'", 'index_together': '()'},
+            'commit': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Commit']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'pull_request': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.PullRequest']"})
+        },
+        'sentry.querysubscription': {
+            'Meta': {'unique_together': '()', 'object_name': 'QuerySubscription', 'index_together': '()'},
+            'aggregation': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
+            'dataset': ('django.db.models.fields.TextField', [], {}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'query': ('django.db.models.fields.TextField', [], {}),
+            'resolution': ('django.db.models.fields.IntegerField', [], {}),
+            'subscription_id': ('django.db.models.fields.TextField', [], {'unique': 'True'}),
+            'time_window': ('django.db.models.fields.IntegerField', [], {}),
+            'type': ('django.db.models.fields.TextField', [], {})
+        },
+        'sentry.rawevent': {
+            'Meta': {'unique_together': "(('project', 'event_id'),)", 'object_name': 'RawEvent', 'index_together': '()'},
+            'data': ('sentry.db.models.fields.node.NodeField', [], {'null': 'True', 'blank': 'True'}),
+            'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'event_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"})
+        },
+        'sentry.recentsearch': {
+            'Meta': {'unique_together': "(('user', 'organization', 'type', 'query_hash'),)", 'object_name': 'RecentSearch', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'query': ('django.db.models.fields.TextField', [], {}),
+            'query_hash': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'type': ('django.db.models.fields.PositiveSmallIntegerField', [], {}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']", 'db_index': 'False'})
+        },
+        'sentry.relay': {
+            'Meta': {'unique_together': '()', 'object_name': 'Relay', 'index_together': '()'},
+            'first_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'is_internal': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'public_key': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'relay_id': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'})
+        },
+        'sentry.release': {
+            'Meta': {'unique_together': "(('organization', 'version'),)", 'object_name': 'Release', 'index_together': '()'},
+            'authors': ('sentry.db.models.fields.array.ArrayField', [], {'of': (u'django.db.models.fields.TextField', [], {})}),
+            'commit_count': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'null': 'True'}),
+            'data': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_released': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'date_started': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'last_commit_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'last_deploy_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'new_groups': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'owner': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']", 'null': 'True', 'on_delete': 'models.SET_NULL', 'blank': 'True'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'projects': ('django.db.models.fields.related.ManyToManyField', [], {'related_name': "u'releases'", 'symmetrical': 'False', 'through': "orm['sentry.ReleaseProject']", 'to': "orm['sentry.Project']"}),
+            'ref': ('django.db.models.fields.CharField', [], {'max_length': '250', 'null': 'True', 'blank': 'True'}),
+            'total_deploys': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'null': 'True'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True', 'blank': 'True'}),
+            'version': ('django.db.models.fields.CharField', [], {'max_length': '250'})
+        },
+        'sentry.releasecommit': {
+            'Meta': {'unique_together': "(('release', 'commit'), ('release', 'order'))", 'object_name': 'ReleaseCommit', 'index_together': '()'},
+            'commit': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Commit']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'order': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'release': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Release']"})
+        },
+        'sentry.releaseenvironment': {
+            'Meta': {'unique_together': "(('organization', 'release', 'environment'),)", 'object_name': 'ReleaseEnvironment', 'db_table': "'sentry_environmentrelease'", 'index_together': '()'},
+            'environment': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Environment']"}),
+            'first_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'release': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Release']"})
+        },
+        'sentry.releasefile': {
+            'Meta': {'unique_together': "(('release', 'ident'),)", 'object_name': 'ReleaseFile', 'index_together': "(('release', 'name'),)"},
+            'dist': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Distribution']", 'null': 'True'}),
+            'file': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.File']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ident': ('django.db.models.fields.CharField', [], {'max_length': '40'}),
+            'name': ('django.db.models.fields.TextField', [], {}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'release': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Release']"})
+        },
+        'sentry.releaseheadcommit': {
+            'Meta': {'unique_together': "(('repository_id', 'release'),)", 'object_name': 'ReleaseHeadCommit', 'index_together': '()'},
+            'commit': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Commit']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'release': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Release']"}),
+            'repository_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {})
+        },
+        'sentry.releaseproject': {
+            'Meta': {'unique_together': "(('project', 'release'),)", 'object_name': 'ReleaseProject', 'db_table': "'sentry_release_project'", 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'new_groups': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'null': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'release': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Release']"})
+        },
+        'sentry.releaseprojectenvironment': {
+            'Meta': {'unique_together': "(('project', 'release', 'environment'),)", 'object_name': 'ReleaseProjectEnvironment', 'index_together': '()'},
+            'environment': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Environment']"}),
+            'first_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'last_deploy_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True', 'db_index': 'True'}),
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'new_issues_count': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'release': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Release']"})
+        },
+        'sentry.repository': {
+            'Meta': {'unique_together': "(('organization_id', 'name'), ('organization_id', 'provider', 'external_id'))", 'object_name': 'Repository', 'index_together': '()'},
+            'config': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'external_id': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'integration_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True', 'db_index': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '200'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'provider': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'})
+        },
+        'sentry.reprocessingreport': {
+            'Meta': {'unique_together': "(('project', 'event_id'),)", 'object_name': 'ReprocessingReport', 'index_together': '()'},
+            'datetime': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'event_id': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"})
+        },
+        'sentry.rule': {
+            'Meta': {'unique_together': '()', 'object_name': 'Rule', 'index_together': '()'},
+            'data': ('sentry.db.models.fields.gzippeddict.GzippedDictField', [], {}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'environment_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'label': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'db_index': 'True'})
+        },
+        'sentry.savedsearch': {
+            'Meta': {'unique_together': "(('project', 'name'), ('organization', 'owner', 'type'))", 'object_name': 'SavedSearch', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'is_default': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_global': ('django.db.models.fields.NullBooleanField', [], {'default': 'False', 'null': 'True', 'db_index': 'True', 'blank': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']", 'null': 'True'}),
+            'owner': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']", 'null': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']", 'null': 'True'}),
+            'query': ('django.db.models.fields.TextField', [], {}),
+            'type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0', 'null': 'True'})
+        },
+        'sentry.savedsearchuserdefault': {
+            'Meta': {'unique_together': "(('project', 'user'),)", 'object_name': 'SavedSearchUserDefault', 'db_table': "'sentry_savedsearch_userdefault'", 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"}),
+            'savedsearch': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.SavedSearch']"}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.scheduleddeletion': {
+            'Meta': {'unique_together': "(('app_label', 'model_name', 'object_id'),)", 'object_name': 'ScheduledDeletion', 'index_together': '()'},
+            'aborted': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'actor_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'}),
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'data': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_scheduled': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime(2019, 10, 25, 0, 0)'}),
+            'guid': ('django.db.models.fields.CharField', [], {'default': "'37bd293e52bb42f78b2c3872e89178f5'", 'unique': 'True', 'max_length': '32'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'in_progress': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'model_name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'object_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {})
+        },
+        'sentry.scheduledjob': {
+            'Meta': {'unique_together': '()', 'object_name': 'ScheduledJob', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_scheduled': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'payload': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'})
+        },
+        'sentry.sentryapp': {
+            'Meta': {'unique_together': '()', 'object_name': 'SentryApp', 'index_together': '()'},
+            'application': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "u'sentry_app'", 'unique': 'True', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['sentry.ApiApplication']"}),
+            'author': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_deleted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'date_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'events': ('sentry.db.models.fields.array.ArrayField', [], {'of': (u'django.db.models.fields.TextField', [], {})}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'is_alertable': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.TextField', [], {}),
+            'overview': ('django.db.models.fields.TextField', [], {'null': 'True'}),
+            'owner': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'owned_sentry_apps'", 'to': "orm['sentry.Organization']"}),
+            'proxy_user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "u'sentry_app'", 'unique': 'True', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['sentry.User']"}),
+            'redirect_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'}),
+            'schema': ('sentry.db.models.fields.encrypted.EncryptedJsonField', [], {'default': '{}'}),
+            'scope_list': ('sentry.db.models.fields.array.ArrayField', [], {'of': (u'django.db.models.fields.TextField', [], {})}),
+            'scopes': ('django.db.models.fields.BigIntegerField', [], {'default': 'None'}),
+            'slug': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '64'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'uuid': ('django.db.models.fields.CharField', [], {'default': "'2f9d6775-401c-4b22-a5fd-9664dd4fe68c'", 'max_length': '64'}),
+            'verify_install': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'webhook_url': ('django.db.models.fields.URLField', [], {'max_length': '200', 'null': 'True'})
+        },
+        'sentry.sentryappavatar': {
+            'Meta': {'unique_together': '()', 'object_name': 'SentryAppAvatar', 'index_together': '()'},
+            'avatar_type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
+            'file': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.File']", 'unique': 'True', 'null': 'True', 'on_delete': 'models.SET_NULL'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ident': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
+            'sentry_app': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'avatar'", 'unique': 'True', 'to': "orm['sentry.SentryApp']"})
+        },
+        'sentry.sentryappcomponent': {
+            'Meta': {'unique_together': '()', 'object_name': 'SentryAppComponent', 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'schema': ('sentry.db.models.fields.encrypted.EncryptedJsonField', [], {'default': '{}'}),
+            'sentry_app': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'components'", 'to': "orm['sentry.SentryApp']"}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'uuid': ('sentry.db.models.fields.uuid.UUIDField', [], {'auto_add': "'uuid:uuid4'", 'unique': 'True', 'max_length': '32'})
+        },
+        'sentry.sentryappinstallation': {
+            'Meta': {'unique_together': '()', 'object_name': 'SentryAppInstallation', 'index_together': '()'},
+            'api_grant': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "u'sentry_app_installation'", 'unique': 'True', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['sentry.ApiGrant']"}),
+            'api_token': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "u'sentry_app_installation'", 'unique': 'True', 'null': 'True', 'on_delete': 'models.SET_NULL', 'to': "orm['sentry.ApiToken']"}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'date_deleted': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'date_updated': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'sentry_app_installations'", 'to': "orm['sentry.Organization']"}),
+            'sentry_app': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'installations'", 'to': "orm['sentry.SentryApp']"}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'uuid': ('django.db.models.fields.CharField', [], {'default': "'8671ac52-bd98-4cf1-b846-de5e2fdb4e7e'", 'max_length': '64'})
+        },
+        'sentry.sentryappinstallationtoken': {
+            'Meta': {'unique_together': "(('sentry_app_installation', 'api_token'),)", 'object_name': 'SentryAppInstallationToken', 'index_together': '()'},
+            'api_token': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.ApiToken']"}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'sentry_app_installation': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.SentryAppInstallation']"})
+        },
+        'sentry.servicehook': {
+            'Meta': {'unique_together': '()', 'object_name': 'ServiceHook', 'index_together': '()'},
+            'actor_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'application': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.ApiApplication']", 'null': 'True'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'events': ('sentry.db.models.fields.array.ArrayField', [], {'of': (u'django.db.models.fields.TextField', [], {})}),
+            'guid': ('django.db.models.fields.CharField', [], {'max_length': '32', 'unique': 'True', 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'organization_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True', 'db_index': 'True'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'secret': ('sentry.db.models.fields.encrypted.EncryptedTextField', [], {'default': "'87e1e739f34a4597a3074e440e42250ffb81b392a6fa4ec78c8ab6e9d8d6b0b2'"}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0', 'db_index': 'True'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '512'}),
+            'version': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'})
+        },
+        'sentry.servicehookproject': {
+            'Meta': {'unique_together': "(('service_hook', 'project_id'),)", 'object_name': 'ServiceHookProject', 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'service_hook': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.ServiceHook']"})
+        },
+        'sentry.tagkey': {
+            'Meta': {'unique_together': "(('project_id', 'key'),)", 'object_name': 'TagKey', 'db_table': "'sentry_filterkey'", 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'label': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'db_index': 'True'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'values_seen': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'})
+        },
+        'sentry.tagvalue': {
+            'Meta': {'unique_together': "(('project_id', 'key', 'value'),)", 'object_name': 'TagValue', 'db_table': "'sentry_filtervalue'", 'index_together': "(('project_id', 'key', 'last_seen'),)"},
+            'data': ('sentry.db.models.fields.gzippeddict.GzippedDictField', [], {'null': 'True', 'blank': 'True'}),
+            'first_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'db_index': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True', 'db_index': 'True'}),
+            'project_id': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'null': 'True', 'db_index': 'True'}),
+            'times_seen': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'value': ('django.db.models.fields.CharField', [], {'max_length': '200'})
+        },
+        'sentry.team': {
+            'Meta': {'unique_together': "(('organization', 'slug'),)", 'object_name': 'Team', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']"}),
+            'slug': ('django.db.models.fields.SlugField', [], {'max_length': '50'}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'})
+        },
+        'sentry.teamavatar': {
+            'Meta': {'unique_together': '()', 'object_name': 'TeamAvatar', 'index_together': '()'},
+            'avatar_type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
+            'file': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.File']", 'unique': 'True', 'null': 'True', 'on_delete': 'models.SET_NULL'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ident': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
+            'team': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'avatar'", 'unique': 'True', 'to': "orm['sentry.Team']"})
+        },
+        'sentry.timeseriessnapshot': {
+            'Meta': {'unique_together': '()', 'object_name': 'TimeSeriesSnapshot', 'index_together': '()'},
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'end': ('django.db.models.fields.DateTimeField', [], {}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'period': ('django.db.models.fields.IntegerField', [], {}),
+            'start': ('django.db.models.fields.DateTimeField', [], {}),
+            'values': ('sentry.db.models.fields.array.ArrayField', [], {'of': (u'sentry.db.models.fields.array.ArrayField', [], {'null': 'True'})})
+        },
+        'sentry.user': {
+            'Meta': {'unique_together': '()', 'object_name': 'User', 'db_table': "'auth_user'", 'index_together': '()'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'flags': ('django.db.models.fields.BigIntegerField', [], {'default': '0', 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedAutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_managed': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_password_expired': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_sentry_app': ('django.db.models.fields.NullBooleanField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_active': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'null': 'True'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}),
+            'last_password_change': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '200', 'db_column': "'first_name'", 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'session_nonce': ('django.db.models.fields.CharField', [], {'max_length': '12', 'null': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '128'})
+        },
+        'sentry.useravatar': {
+            'Meta': {'unique_together': '()', 'object_name': 'UserAvatar', 'index_together': '()'},
+            'avatar_type': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '0'}),
+            'file': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.File']", 'unique': 'True', 'null': 'True', 'on_delete': 'models.SET_NULL'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ident': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '32', 'db_index': 'True'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'avatar'", 'unique': 'True', 'to': "orm['sentry.User']"})
+        },
+        'sentry.useremail': {
+            'Meta': {'unique_together': "(('user', 'email'),)", 'object_name': 'UserEmail', 'index_together': '()'},
+            'date_hash_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'is_verified': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'related_name': "u'emails'", 'to': "orm['sentry.User']"}),
+            'validation_hash': ('django.db.models.fields.CharField', [], {'default': "u'DVoq7jnsHnfRNK4o69k2AKaitBIS6WCS'", 'max_length': '32'})
+        },
+        'sentry.userip': {
+            'Meta': {'unique_together': "(('user', 'ip_address'),)", 'object_name': 'UserIP', 'index_together': '()'},
+            'country_code': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True'}),
+            'first_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'ip_address': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
+            'last_seen': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'region_code': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.useroption': {
+            'Meta': {'unique_together': "(('user', 'project', 'key'), ('user', 'organization', 'key'))", 'object_name': 'UserOption', 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'key': ('django.db.models.fields.CharField', [], {'max_length': '64'}),
+            'organization': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Organization']", 'null': 'True'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']", 'null': 'True'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"}),
+            'value': ('sentry.db.models.fields.encrypted.EncryptedPickledObjectField', [], {})
+        },
+        'sentry.userpermission': {
+            'Meta': {'unique_together': "(('user', 'permission'),)", 'object_name': 'UserPermission', 'index_together': '()'},
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'permission': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'user': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.User']"})
+        },
+        'sentry.userreport': {
+            'Meta': {'unique_together': "(('project', 'event_id'),)", 'object_name': 'UserReport', 'index_together': "(('project', 'event_id'), ('project', 'date_added'))"},
+            'comments': ('django.db.models.fields.TextField', [], {}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now', 'db_index': 'True'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75'}),
+            'environment': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Environment']", 'null': 'True'}),
+            'event_id': ('django.db.models.fields.CharField', [], {'max_length': '32'}),
+            'event_user_id': ('sentry.db.models.fields.bounded.BoundedBigIntegerField', [], {'null': 'True'}),
+            'group': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Group']", 'null': 'True'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'project': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Project']"})
+        },
+        'sentry.widget': {
+            'Meta': {'unique_together': "(('dashboard', 'order'), ('dashboard', 'title'))", 'object_name': 'Widget', 'index_together': '()'},
+            'dashboard': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Dashboard']"}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'display_options': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'}),
+            'display_type': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'order': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'title': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'sentry.widgetdatasource': {
+            'Meta': {'unique_together': "(('widget', 'name'), ('widget', 'order'))", 'object_name': 'WidgetDataSource', 'index_together': '()'},
+            'data': ('sentry.db.models.fields.jsonfield.JSONField', [], {'default': '{}'}),
+            'date_added': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'id': ('sentry.db.models.fields.bounded.BoundedBigAutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'order': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'status': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {'default': '0'}),
+            'type': ('sentry.db.models.fields.bounded.BoundedPositiveIntegerField', [], {}),
+            'widget': ('sentry.db.models.fields.foreignkey.FlexibleForeignKey', [], {'to': "orm['sentry.Widget']"})
+        }
+    }
+
+    complete_apps = ['sentry']

+ 414 - 105
tests/sentry/incidents/test_subscription_processor.py

@@ -1,5 +1,6 @@
 from __future__ import absolute_import
 
+import unittest
 from datetime import timedelta
 from time import time
 from random import randint
@@ -9,11 +10,29 @@ from django.utils import timezone
 from exam import fixture, patcher
 from freezegun import freeze_time
 
-from sentry.incidents.logic import create_alert_rule
+from sentry.incidents.logic import create_alert_rule, create_alert_rule_trigger
 from sentry.snuba.subscriptions import query_aggregation_to_snuba
-from sentry.incidents.models import AlertRuleThresholdType, Incident, IncidentStatus, IncidentType
-from sentry.incidents.subscription_processor import get_alert_rule_stats, SubscriptionProcessor
-from sentry.snuba.models import QueryAggregations
+from sentry.incidents.models import (
+    AlertRule,
+    AlertRuleThresholdType,
+    AlertRuleTrigger,
+    Incident,
+    IncidentStatus,
+    IncidentTrigger,
+    IncidentType,
+    TriggerStatus,
+)
+from sentry.incidents.subscription_processor import (
+    build_alert_rule_stat_keys,
+    build_alert_rule_trigger_stat_key,
+    build_trigger_stat_keys,
+    get_alert_rule_stats,
+    get_redis_client,
+    partition,
+    SubscriptionProcessor,
+    update_alert_rule_stats,
+)
+from sentry.snuba.models import QueryAggregations, QuerySubscription
 from sentry.testutils import TestCase
 from sentry.utils.dates import to_timestamp
 
@@ -48,8 +67,16 @@ class ProcessUpdateTest(TestCase):
             resolve_threshold=10,
             threshold_period=1,
         )
+        # Make sure the trigger exists
+        create_alert_rule_trigger(
+            rule, "hi", AlertRuleThresholdType.ABOVE, 100, resolve_threshold=10
+        )
         return rule
 
+    @fixture
+    def trigger(self):
+        return self.rule.alertruletrigger_set.get()
+
     def build_subscription_update(self, subscription, time_delta=None, value=None):
         if time_delta is not None:
             timestamp = int(to_timestamp(timezone.now() + time_delta))
@@ -83,30 +110,54 @@ class ProcessUpdateTest(TestCase):
         processor.process_update(message)
         return processor
 
+    def assert_trigger_exists_with_status(self, incident, trigger, status):
+        assert IncidentTrigger.objects.filter(
+            incident=incident, alert_rule_trigger=trigger, status=status.value
+        ).exists()
+
+    def assert_trigger_does_not_exist_for_incident(self, incident, trigger):
+        assert not IncidentTrigger.objects.filter(
+            incident=incident, alert_rule_trigger=trigger
+        ).exists()
+
+    def assert_trigger_does_not_exist(self, trigger, incidents_to_exclude=None):
+        if incidents_to_exclude is None:
+            incidents_to_exclude = []
+        assert (
+            not IncidentTrigger.objects.filter(alert_rule_trigger=trigger)
+            .exclude(incident__in=incidents_to_exclude)
+            .exists()
+        )
+
     def assert_no_active_incident(self, rule, subscription=None):
         assert not self.active_incident_exists(rule, subscription=subscription)
 
     def assert_active_incident(self, rule, subscription=None):
-        assert self.active_incident_exists(rule, subscription=subscription)
+        incidents = self.active_incident_exists(rule, subscription=subscription)
+        assert incidents
+        return incidents[0]
 
     def active_incident_exists(self, rule, subscription=None):
         if subscription is None:
             subscription = self.sub
-        return Incident.objects.filter(
-            type=IncidentType.ALERT_TRIGGERED.value,
-            status=IncidentStatus.OPEN.value,
-            alert_rule=rule,
-            projects=subscription.project,
-        ).exists()
-
-    def assert_trigger_counts(self, processor, alert_triggers=0, resolve_triggers=0):
-        assert processor.alert_triggers == alert_triggers
-        assert processor.resolve_triggers == resolve_triggers
-        assert get_alert_rule_stats(processor.alert_rule, processor.subscription)[1:] == (
-            alert_triggers,
-            resolve_triggers,
+        return list(
+            Incident.objects.filter(
+                type=IncidentType.ALERT_TRIGGERED.value,
+                status=IncidentStatus.OPEN.value,
+                alert_rule=rule,
+                projects=subscription.project,
+            )
         )
 
+    def assert_trigger_counts(self, processor, trigger, alert_triggers=0, resolve_triggers=0):
+        assert processor.trigger_alert_counts[trigger.id] == alert_triggers
+        assert processor.trigger_resolve_counts[trigger.id] == resolve_triggers
+        alert_stats, resolve_stats = get_alert_rule_stats(
+            processor.alert_rule, processor.subscription, [trigger]
+        )[1:]
+        assert alert_stats[trigger.id] == alert_triggers
+        assert resolve_stats[trigger.id] == resolve_triggers
+
     def test_removed_alert_rule(self):
         message = self.build_subscription_update(self.sub)
         self.rule.delete()
@@ -117,47 +168,54 @@ class ProcessUpdateTest(TestCase):
         # TODO: Check subscription is deleted once we start doing that
 
     def test_skip_already_processed_update(self):
-        self.send_update(self.rule, self.rule.alert_threshold)
+        self.send_update(self.rule, self.trigger.alert_threshold)
         self.metrics.incr.reset_mock()
-        self.send_update(self.rule, self.rule.alert_threshold)
+        self.send_update(self.rule, self.trigger.alert_threshold)
         self.metrics.incr.assert_called_once_with(
             "incidents.alert_rules.skipping_already_processed_update"
         )
         self.metrics.incr.reset_mock()
-        self.send_update(self.rule, self.rule.alert_threshold, timedelta(hours=-1))
+        self.send_update(self.rule, self.trigger.alert_threshold, timedelta(hours=-1))
         self.metrics.incr.assert_called_once_with(
             "incidents.alert_rules.skipping_already_processed_update"
         )
         self.metrics.incr.reset_mock()
-        self.send_update(self.rule, self.rule.alert_threshold, timedelta(hours=1))
+        self.send_update(self.rule, self.trigger.alert_threshold, timedelta(hours=1))
         self.metrics.incr.assert_not_called()  # NOQA
 
     def test_no_alert(self):
         rule = self.rule
-        processor = self.send_update(rule, rule.alert_threshold)
-        self.assert_trigger_counts(processor, 0, 0)
+        trigger = self.trigger
+        processor = self.send_update(rule, trigger.alert_threshold)
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
         self.assert_no_active_incident(self.rule)
+        self.assert_trigger_does_not_exist(self.trigger)
 
     def test_alert(self):
         # Verify that an alert rule that only expects a single update to be over the
         # alert threshold triggers correctly
         rule = self.rule
-        processor = self.send_update(rule, rule.alert_threshold + 1)
-        self.assert_trigger_counts(processor, 0, 0)
-        self.assert_active_incident(rule)
+        trigger = self.trigger
+        processor = self.send_update(rule, trigger.alert_threshold + 1)
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
+        incident = self.assert_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
 
-    def test_alert_multiple_triggers(self):
+    def test_alert_multiple_threshold_periods(self):
         # Verify that a rule that expects two consecutive updates to be over the
         # alert threshold triggers correctly
         rule = self.rule
+        trigger = self.trigger
         rule.update(threshold_period=2)
-        processor = self.send_update(rule, rule.alert_threshold + 1, timedelta(minutes=-2))
-        self.assert_trigger_counts(processor, 1, 0)
+        processor = self.send_update(rule, trigger.alert_threshold + 1, timedelta(minutes=-2))
+        self.assert_trigger_counts(processor, self.trigger, 1, 0)
         self.assert_no_active_incident(rule)
+        self.assert_trigger_does_not_exist(self.trigger)
 
-        processor = self.send_update(rule, rule.alert_threshold + 1, timedelta(minutes=-1))
-        self.assert_trigger_counts(processor, 0, 0)
-        self.assert_active_incident(rule)
+        processor = self.send_update(rule, trigger.alert_threshold + 1, timedelta(minutes=-1))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
+        incident = self.assert_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
 
     def test_alert_multiple_triggers_non_consecutive(self):
         # Verify that a rule that expects two consecutive updates to be over the
@@ -165,177 +223,428 @@ class ProcessUpdateTest(TestCase):
         # an update that is below the threshold in the middle
         rule = self.rule
         rule.update(threshold_period=2)
-        processor = self.send_update(rule, rule.alert_threshold + 1, timedelta(minutes=-3))
-        self.assert_trigger_counts(processor, 1, 0)
+        trigger = self.trigger
+        processor = self.send_update(rule, trigger.alert_threshold + 1, timedelta(minutes=-3))
+        self.assert_trigger_counts(processor, self.trigger, 1, 0)
         self.assert_no_active_incident(rule)
+        self.assert_trigger_does_not_exist(self.trigger)
 
-        processor = self.send_update(rule, rule.alert_threshold, timedelta(minutes=-2))
-        self.assert_trigger_counts(processor, 0, 0)
+        processor = self.send_update(rule, trigger.alert_threshold, timedelta(minutes=-2))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
         self.assert_no_active_incident(rule)
+        self.assert_trigger_does_not_exist(self.trigger)
 
-        processor = self.send_update(rule, rule.alert_threshold + 1, timedelta(minutes=-1))
-        self.assert_trigger_counts(processor, 1, 0)
+        processor = self.send_update(rule, trigger.alert_threshold + 1, timedelta(minutes=-1))
+        self.assert_trigger_counts(processor, self.trigger, 1, 0)
         self.assert_no_active_incident(rule)
+        self.assert_trigger_does_not_exist(self.trigger)
 
     def test_no_active_incident_resolve(self):
         # Test that we don't track stats for resolving if there are no active incidents
         # related to the alert rule.
         rule = self.rule
-        processor = self.send_update(rule, rule.resolve_threshold - 1)
-        self.assert_trigger_counts(processor, 0, 0)
+        trigger = self.trigger
+        processor = self.send_update(rule, trigger.resolve_threshold - 1)
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
         self.assert_no_active_incident(rule)
+        self.assert_trigger_does_not_exist(self.trigger)
 
     def test_resolve(self):
         # Verify that an alert rule that only expects a single update to be under the
         # resolve threshold triggers correctly
         rule = self.rule
-        processor = self.send_update(rule, rule.alert_threshold + 1, timedelta(minutes=-2))
-        self.assert_trigger_counts(processor, 0, 0)
-        self.assert_active_incident(rule)
-
-        processor = self.send_update(rule, rule.resolve_threshold - 1, timedelta(minutes=-1))
-        self.assert_trigger_counts(processor, 0, 0)
+        trigger = self.trigger
+        processor = self.send_update(rule, trigger.alert_threshold + 1, timedelta(minutes=-2))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
+        incident = self.assert_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
+
+        processor = self.send_update(rule, trigger.resolve_threshold - 1, timedelta(minutes=-1))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
         self.assert_no_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.RESOLVED)
 
-    def test_resolve_multiple_triggers(self):
+    def test_resolve_multiple_threshold_periods(self):
         # Verify that a rule that expects two consecutive updates to be under the
         # resolve threshold triggers correctly
         rule = self.rule
+        trigger = self.trigger
 
-        processor = self.send_update(rule, rule.alert_threshold + 1, timedelta(minutes=-3))
-        self.assert_trigger_counts(processor, 0, 0)
-        self.assert_active_incident(rule)
+        processor = self.send_update(rule, trigger.alert_threshold + 1, timedelta(minutes=-3))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
+        incident = self.assert_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
 
         rule.update(threshold_period=2)
-        processor = self.send_update(rule, rule.resolve_threshold - 1, timedelta(minutes=-2))
-        self.assert_trigger_counts(processor, 0, 1)
-        self.assert_active_incident(rule)
+        processor = self.send_update(rule, trigger.resolve_threshold - 1, timedelta(minutes=-2))
+        self.assert_trigger_counts(processor, self.trigger, 0, 1)
+        incident = self.assert_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
 
-        processor = self.send_update(rule, rule.resolve_threshold - 1, timedelta(minutes=-1))
-        self.assert_trigger_counts(processor, 0, 0)
+        processor = self.send_update(rule, trigger.resolve_threshold - 1, timedelta(minutes=-1))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
         self.assert_no_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.RESOLVED)
 
-    def test_resolve_multiple_triggers_non_consecutive(self):
+    def test_resolve_multiple_threshold_periods_non_consecutive(self):
         # Verify that a rule that expects two consecutive updates to be under the
         # resolve threshold doesn't trigger if there's two updates that are below with
         # an update that is above the threshold in the middle
         rule = self.rule
+        trigger = self.trigger
 
-        processor = self.send_update(rule, rule.alert_threshold + 1, timedelta(minutes=-4))
-        self.assert_trigger_counts(processor, 0, 0)
-        self.assert_active_incident(rule)
+        processor = self.send_update(rule, trigger.alert_threshold + 1, timedelta(minutes=-4))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
+        incident = self.assert_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
 
         rule.update(threshold_period=2)
-        processor = self.send_update(rule, rule.resolve_threshold - 1, timedelta(minutes=-3))
-        self.assert_trigger_counts(processor, 0, 1)
+        processor = self.send_update(rule, trigger.resolve_threshold - 1, timedelta(minutes=-3))
+        self.assert_trigger_counts(processor, self.trigger, 0, 1)
         self.assert_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
 
-        processor = self.send_update(rule, rule.resolve_threshold, timedelta(minutes=-2))
-        self.assert_trigger_counts(processor, 0, 0)
+        processor = self.send_update(rule, trigger.resolve_threshold, timedelta(minutes=-2))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
         self.assert_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
 
-        processor = self.send_update(rule, rule.resolve_threshold - 1, timedelta(minutes=-1))
-        self.assert_trigger_counts(processor, 0, 1)
+        processor = self.send_update(rule, trigger.resolve_threshold - 1, timedelta(minutes=-1))
+        self.assert_trigger_counts(processor, self.trigger, 0, 1)
         self.assert_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
 
     def test_reversed_threshold_alert(self):
         # Test that inverting thresholds correctly alerts
         rule = self.rule
-        rule.update(threshold_type=AlertRuleThresholdType.BELOW.value)
-        processor = self.send_update(rule, rule.alert_threshold + 1, timedelta(minutes=-2))
-        self.assert_trigger_counts(processor, 0, 0)
+        trigger = self.trigger
+        trigger.update(threshold_type=AlertRuleThresholdType.BELOW.value)
+        processor = self.send_update(rule, trigger.alert_threshold + 1, timedelta(minutes=-2))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
         self.assert_no_active_incident(rule)
+        self.assert_trigger_does_not_exist(self.trigger)
 
-        processor = self.send_update(rule, rule.alert_threshold - 1, timedelta(minutes=-1))
-        self.assert_trigger_counts(processor, 0, 0)
-        self.assert_active_incident(rule)
+        processor = self.send_update(rule, trigger.alert_threshold - 1, timedelta(minutes=-1))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
+        incident = self.assert_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
 
     def test_reversed_threshold_resolve(self):
         # Test that inverting thresholds correctly resolves
         rule = self.rule
-        rule.update(threshold_type=AlertRuleThresholdType.BELOW.value)
+        trigger = self.trigger
+        trigger.update(threshold_type=AlertRuleThresholdType.BELOW.value)
 
-        processor = self.send_update(rule, rule.alert_threshold - 1, timedelta(minutes=-3))
-        self.assert_trigger_counts(processor, 0, 0)
-        self.assert_active_incident(rule)
+        processor = self.send_update(rule, trigger.alert_threshold - 1, timedelta(minutes=-3))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
+        incident = self.assert_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
 
-        processor = self.send_update(rule, rule.resolve_threshold - 1, timedelta(minutes=-2))
-        self.assert_trigger_counts(processor, 0, 0)
-        self.assert_active_incident(rule)
+        processor = self.send_update(rule, trigger.resolve_threshold - 1, timedelta(minutes=-2))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
+        incident = self.assert_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
 
-        processor = self.send_update(rule, rule.resolve_threshold + 1, timedelta(minutes=-1))
-        self.assert_trigger_counts(processor, 0, 0)
+        processor = self.send_update(rule, trigger.resolve_threshold + 1, timedelta(minutes=-1))
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
         self.assert_no_active_incident(rule)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.RESOLVED)
 
     def test_multiple_subscriptions_do_not_conflict(self):
         # Verify that multiple subscriptions associated with a rule don't conflict with
         # each other
         rule = self.rule
         rule.update(threshold_period=2)
+        trigger = self.trigger
 
         # Send an update through for the first subscription. This shouldn't trigger an
         # incident, since we need two consecutive updates that are over the threshold.
         processor = self.send_update(
-            rule, rule.alert_threshold + 1, timedelta(minutes=-10), subscription=self.sub
+            rule, trigger.alert_threshold + 1, timedelta(minutes=-10), subscription=self.sub
         )
-        self.assert_trigger_counts(processor, 1, 0)
+        self.assert_trigger_counts(processor, self.trigger, 1, 0)
         self.assert_no_active_incident(rule, self.sub)
+        self.assert_trigger_does_not_exist(self.trigger)
 
         # Have an update come through for the other sub. This shouldn't influence the original
         processor = self.send_update(
-            rule, rule.alert_threshold + 1, timedelta(minutes=-9), subscription=self.other_sub
+            rule, trigger.alert_threshold + 1, timedelta(minutes=-9), subscription=self.other_sub
         )
-        self.assert_trigger_counts(processor, 1, 0)
+        self.assert_trigger_counts(processor, self.trigger, 1, 0)
         self.assert_no_active_incident(rule, self.sub)
+        self.assert_trigger_does_not_exist(self.trigger)
         self.assert_no_active_incident(rule, self.other_sub)
+        self.assert_trigger_does_not_exist(self.trigger)
 
         # Send another update through for the first subscription. This should trigger an
         # incident for just this subscription.
         processor = self.send_update(
-            rule, rule.alert_threshold + 1, timedelta(minutes=-9), subscription=self.sub
+            rule, trigger.alert_threshold + 1, timedelta(minutes=-9), subscription=self.sub
         )
-        self.assert_trigger_counts(processor, 0, 0)
-        self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
+        incident = self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
         self.assert_no_active_incident(rule, self.other_sub)
+        self.assert_trigger_does_not_exist(self.trigger, [incident])
 
         # Send another update through for the second subscription. This should trigger an
         # incident for just this subscription.
         processor = self.send_update(
-            rule, rule.alert_threshold + 1, timedelta(minutes=-8), subscription=self.other_sub
+            rule, trigger.alert_threshold + 1, timedelta(minutes=-8), subscription=self.other_sub
         )
-        self.assert_trigger_counts(processor, 0, 0)
-        self.assert_active_incident(rule, self.sub)
-        self.assert_active_incident(rule, self.other_sub)
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
+        incident = self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
+        other_incident = self.assert_active_incident(rule, self.other_sub)
+        self.assert_trigger_exists_with_status(other_incident, self.trigger, TriggerStatus.ACTIVE)
 
         # Now we want to test that resolving is isolated. Send another update through
         # for the first subscription.
         processor = self.send_update(
-            rule, rule.resolve_threshold - 1, timedelta(minutes=-7), subscription=self.sub
+            rule, trigger.resolve_threshold - 1, timedelta(minutes=-7), subscription=self.sub
         )
-        self.assert_trigger_counts(processor, 0, 1)
-        self.assert_active_incident(rule, self.sub)
-        self.assert_active_incident(rule, self.other_sub)
+        self.assert_trigger_counts(processor, self.trigger, 0, 1)
+        incident = self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
+        other_incident = self.assert_active_incident(rule, self.other_sub)
+        self.assert_trigger_exists_with_status(other_incident, self.trigger, TriggerStatus.ACTIVE)
 
         processor = self.send_update(
-            rule, rule.resolve_threshold - 1, timedelta(minutes=-7), subscription=self.other_sub
+            rule, trigger.resolve_threshold - 1, timedelta(minutes=-7), subscription=self.other_sub
         )
-        self.assert_trigger_counts(processor, 0, 1)
-        self.assert_active_incident(rule, self.sub)
-        self.assert_active_incident(rule, self.other_sub)
+        self.assert_trigger_counts(processor, self.trigger, 0, 1)
+        incident = self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
+        other_incident = self.assert_active_incident(rule, self.other_sub)
+        self.assert_trigger_exists_with_status(other_incident, self.trigger, TriggerStatus.ACTIVE)
 
         # This second update for the second subscription should resolve its incident,
         # but not the incident from the first subscription.
         processor = self.send_update(
-            rule, rule.resolve_threshold - 1, timedelta(minutes=-6), subscription=self.other_sub
+            rule, trigger.resolve_threshold - 1, timedelta(minutes=-6), subscription=self.other_sub
         )
-        self.assert_trigger_counts(processor, 0, 0)
-        self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
+        incident = self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.ACTIVE)
         self.assert_no_active_incident(rule, self.other_sub)
+        self.assert_trigger_exists_with_status(other_incident, self.trigger, TriggerStatus.RESOLVED)
 
         # This second update for the first subscription should resolve its incident now.
         processor = self.send_update(
-            rule, rule.resolve_threshold - 1, timedelta(minutes=-6), subscription=self.sub
+            rule, trigger.resolve_threshold - 1, timedelta(minutes=-6), subscription=self.sub
         )
-        self.assert_trigger_counts(processor, 0, 0)
+        self.assert_trigger_counts(processor, self.trigger, 0, 0)
         self.assert_no_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, self.trigger, TriggerStatus.RESOLVED)
         self.assert_no_active_incident(rule, self.other_sub)
+        self.assert_trigger_exists_with_status(other_incident, self.trigger, TriggerStatus.RESOLVED)
+
+    def test_multiple_triggers(self):
+        rule = self.rule
+        rule.update(threshold_period=2)
+        trigger = self.trigger
+        other_trigger = create_alert_rule_trigger(
+            self.rule, "hello", AlertRuleThresholdType.ABOVE, 200, resolve_threshold=50
+        )
+        processor = self.send_update(
+            rule, trigger.alert_threshold + 1, timedelta(minutes=-10), subscription=self.sub
+        )
+        self.assert_trigger_counts(processor, trigger, 1, 0)
+        self.assert_trigger_counts(processor, other_trigger, 0, 0)
+        self.assert_no_active_incident(rule, self.sub)
+        self.assert_trigger_does_not_exist(trigger)
+        self.assert_trigger_does_not_exist(other_trigger)
+
+        # This should cause both to increment, although only `trigger` should fire.
+        processor = self.send_update(
+            rule, other_trigger.alert_threshold + 1, timedelta(minutes=-9), subscription=self.sub
+        )
+        self.assert_trigger_counts(processor, trigger, 0, 0)
+        self.assert_trigger_counts(processor, other_trigger, 1, 0)
+        incident = self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, trigger, TriggerStatus.ACTIVE)
+        self.assert_trigger_does_not_exist(other_trigger)
+
+        # Now only `other_trigger` should increment and fire.
+        processor = self.send_update(
+            rule, other_trigger.alert_threshold + 1, timedelta(minutes=-8), subscription=self.sub
+        )
+        self.assert_trigger_counts(processor, trigger, 0, 0)
+        self.assert_trigger_counts(processor, other_trigger, 0, 0)
+        incident = self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, trigger, TriggerStatus.ACTIVE)
+        self.assert_trigger_exists_with_status(incident, other_trigger, TriggerStatus.ACTIVE)
+
+        # Now send through two updates where we're below threshold for `other_trigger`.
+        # The trigger should end up resolved, but the incident should still be active
+        processor = self.send_update(
+            rule, other_trigger.resolve_threshold - 1, timedelta(minutes=-7), subscription=self.sub
+        )
+        self.assert_trigger_counts(processor, trigger, 0, 0)
+        self.assert_trigger_counts(processor, other_trigger, 0, 1)
+        incident = self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, trigger, TriggerStatus.ACTIVE)
+        self.assert_trigger_exists_with_status(incident, other_trigger, TriggerStatus.ACTIVE)
+
+        processor = self.send_update(
+            rule, other_trigger.resolve_threshold - 1, timedelta(minutes=-6), subscription=self.sub
+        )
+        self.assert_trigger_counts(processor, trigger, 0, 0)
+        self.assert_trigger_counts(processor, other_trigger, 0, 0)
+        incident = self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, trigger, TriggerStatus.ACTIVE)
+        self.assert_trigger_exists_with_status(incident, other_trigger, TriggerStatus.RESOLVED)
+
+        # Now we push the other trigger below the resolve threshold twice. This should
+        # close the incident.
+        processor = self.send_update(
+            rule, trigger.resolve_threshold - 1, timedelta(minutes=-5), subscription=self.sub
+        )
+        self.assert_trigger_counts(processor, trigger, 0, 1)
+        self.assert_trigger_counts(processor, other_trigger, 0, 0)
+        incident = self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, trigger, TriggerStatus.ACTIVE)
+        self.assert_trigger_exists_with_status(incident, other_trigger, TriggerStatus.RESOLVED)
+
+        processor = self.send_update(
+            rule, trigger.resolve_threshold - 1, timedelta(minutes=-4), subscription=self.sub
+        )
+        self.assert_trigger_counts(processor, trigger, 0, 0)
+        self.assert_trigger_counts(processor, other_trigger, 0, 0)
+        self.assert_no_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, trigger, TriggerStatus.RESOLVED)
+        self.assert_trigger_exists_with_status(incident, other_trigger, TriggerStatus.RESOLVED)
+
+    def test_multiple_triggers_at_same_time(self):
+        # Check that both triggers fire if an update comes through that exceeds both of
+        # their thresholds
+        rule = self.rule
+        trigger = self.trigger
+        other_trigger = create_alert_rule_trigger(
+            self.rule, "hello", AlertRuleThresholdType.ABOVE, 200, resolve_threshold=50
+        )
+
+        processor = self.send_update(
+            rule, other_trigger.alert_threshold + 1, timedelta(minutes=-10), subscription=self.sub
+        )
+        self.assert_trigger_counts(processor, trigger, 0, 0)
+        self.assert_trigger_counts(processor, other_trigger, 0, 0)
+        incident = self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, trigger, TriggerStatus.ACTIVE)
+        self.assert_trigger_exists_with_status(incident, other_trigger, TriggerStatus.ACTIVE)
+
+        processor = self.send_update(
+            rule, trigger.resolve_threshold - 1, timedelta(minutes=-9), subscription=self.sub
+        )
+        self.assert_trigger_counts(processor, trigger, 0, 0)
+        self.assert_trigger_counts(processor, other_trigger, 0, 0)
+        self.assert_no_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, trigger, TriggerStatus.RESOLVED)
+        self.assert_trigger_exists_with_status(incident, other_trigger, TriggerStatus.RESOLVED)
+
+    def test_multiple_triggers_one_with_no_resolve(self):
+        # Check that both triggers fire if an update comes through that exceeds both of
+        # their thresholds
+        rule = self.rule
+        trigger = self.trigger
+        other_trigger = create_alert_rule_trigger(
+            self.rule, "hello", AlertRuleThresholdType.ABOVE, 200
+        )
+
+        processor = self.send_update(
+            rule, other_trigger.alert_threshold + 1, timedelta(minutes=-10), subscription=self.sub
+        )
+        self.assert_trigger_counts(processor, trigger, 0, 0)
+        self.assert_trigger_counts(processor, other_trigger, 0, 0)
+        incident = self.assert_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, trigger, TriggerStatus.ACTIVE)
+        self.assert_trigger_exists_with_status(incident, other_trigger, TriggerStatus.ACTIVE)
+
+        processor = self.send_update(
+            rule, trigger.resolve_threshold - 1, timedelta(minutes=-9), subscription=self.sub
+        )
+        self.assert_trigger_counts(processor, trigger, 0, 0)
+        self.assert_trigger_counts(processor, other_trigger, 0, 0)
+        self.assert_no_active_incident(rule, self.sub)
+        self.assert_trigger_exists_with_status(incident, trigger, TriggerStatus.RESOLVED)
+        self.assert_trigger_exists_with_status(incident, other_trigger, TriggerStatus.ACTIVE)
+
+
+class TestBuildAlertRuleStatKeys(unittest.TestCase):
+    def test(self):
+        stat_keys = build_alert_rule_stat_keys(AlertRule(id=1), QuerySubscription(project_id=2))
+        assert stat_keys == ["{alert_rule:1:project:2}:last_update"]
+
+
+class TestBuildTriggerStatKeys(unittest.TestCase):
+    def test(self):
+        stat_keys = build_trigger_stat_keys(
+            AlertRule(id=1),
+            QuerySubscription(project_id=2),
+            [AlertRuleTrigger(id=3), AlertRuleTrigger(id=4)],
+        )
+        assert stat_keys == [
+            "{alert_rule:1:project:2}:trigger:3:alert_triggered",
+            "{alert_rule:1:project:2}:trigger:3:resolve_triggered",
+            "{alert_rule:1:project:2}:trigger:4:alert_triggered",
+            "{alert_rule:1:project:2}:trigger:4:resolve_triggered",
+        ]
+
+
+class TestBuildAlertRuleTriggerStatKey(unittest.TestCase):
+    def test(self):
+        stat_key = build_alert_rule_trigger_stat_key(
+            alert_rule_id=1, project_id=2, trigger_id=3, stat_key="hello"
+        )
+        assert stat_key == "{alert_rule:1:project:2}:trigger:3:hello"
+
+
+class TestPartition(unittest.TestCase):
+    def test(self):
+        assert list(partition(range(8), 2)) == [(0, 1), (2, 3), (4, 5), (6, 7)]
+        assert list(partition(range(9), 3)) == [(0, 1, 2), (3, 4, 5), (6, 7, 8)]
+
+
+class TestGetAlertRuleStats(TestCase):
+    def test(self):
+        alert_rule = AlertRule(id=1)
+        sub = QuerySubscription(project_id=2)
+        triggers = [AlertRuleTrigger(id=3), AlertRuleTrigger(id=4)]
+        client = get_redis_client()
+        pipeline = client.pipeline()
+        pipeline.set("{alert_rule:1:project:2}:last_update", 1234)
+        for key, value in [
+            ("{alert_rule:1:project:2}:trigger:3:alert_triggered", 1),
+            ("{alert_rule:1:project:2}:trigger:3:resolve_triggered", 2),
+            ("{alert_rule:1:project:2}:trigger:4:alert_triggered", 3),
+            ("{alert_rule:1:project:2}:trigger:4:resolve_triggered", 4),
+        ]:
+            pipeline.set(key, value)
+        pipeline.execute()
+
+        last_update, alert_counts, resolve_counts = get_alert_rule_stats(alert_rule, sub, triggers)
+        assert last_update == 1234
+        assert alert_counts == {3: 1, 4: 3}
+        assert resolve_counts == {3: 2, 4: 4}
+
+
+class TestUpdateAlertRuleStats(TestCase):
+    def test(self):
+        alert_rule = AlertRule(id=1)
+        sub = QuerySubscription(project_id=2)
+        update_alert_rule_stats(alert_rule, sub, 1234, {3: 20, 4: 3}, {3: 10, 4: 15})
+        client = get_redis_client()
+        results = map(
+            int,
+            client.mget(
+                [
+                    "{alert_rule:1:project:2}:last_update",
+                    "{alert_rule:1:project:2}:trigger:3:alert_triggered",
+                    "{alert_rule:1:project:2}:trigger:3:resolve_triggered",
+                    "{alert_rule:1:project:2}:trigger:4:alert_triggered",
+                    "{alert_rule:1:project:2}:trigger:4:resolve_triggered",
+                ]
+            ),
+        )
+
+        assert results == [1234, 20, 10, 3, 15]

+ 9 - 2
tests/snuba/incidents/test_tasks.py

@@ -9,7 +9,7 @@ from django.conf import settings
 from django.test.utils import override_settings
 from exam import fixture
 
-from sentry.incidents.logic import create_alert_rule
+from sentry.incidents.logic import create_alert_rule, create_alert_rule_trigger
 from sentry.snuba.subscriptions import query_aggregation_to_snuba
 from sentry.incidents.models import AlertRuleThresholdType, Incident, IncidentStatus, IncidentType
 from sentry.incidents.tasks import INCIDENTS_SNUBA_SUBSCRIPTION_TYPE
@@ -52,8 +52,15 @@ class HandleSnubaQueryUpdateTest(TestCase):
             resolve_threshold=10,
             threshold_period=1,
         )
+        create_alert_rule_trigger(
+            rule, "hi", AlertRuleThresholdType.ABOVE, 100, resolve_threshold=10
+        )
         return rule
 
+    @fixture
+    def trigger(self):
+        return self.rule.alertruletrigger_set.get()
+
     @fixture
     def producer(self):
         cluster_name = settings.KAFKA_TOPICS[self.topic]["cluster"]
@@ -88,7 +95,7 @@ class HandleSnubaQueryUpdateTest(TestCase):
             "version": 1,
             "payload": {
                 "subscription_id": self.subscription.subscription_id,
-                "values": {value_name: self.rule.alert_threshold + 1},
+                "values": {value_name: self.trigger.alert_threshold + 1},
                 "timestamp": 1235,
                 "interval": 5,
                 "partition": 50,