We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
IntegrityError: IntegrityError('duplicate key value violates unique constraint "sentry_featureadoption_organizati...
https://sentry.io/sentry/sentry/issues/299179840/

```
IntegrityError: IntegrityError('duplicate key value violates unique constraint "sentry_featureadoption_organization_id_78451b8747a9e638_uniq"\nDETAIL:  Key (organization_id, feature_id)=(****, 0) already exists.\n',)
SQL: INSERT INTO "sentry_featureadoption" ("organization_id", "feature_id", "date_completed", "complete", "applicable", "data") VALUES (%s, %s, %s, %s, %s, %s), (%s, %s, %s, %s, %s, %s)
(12 additional frame(s) were not displayed)
...
  File "sentry/db/postgres/decorators.py", line 74, in inner
    return func(self, *args, **kwargs)
  File "sentry/db/postgres/decorators.py", line 21, in inner
    return func(self, *args, **kwargs)
  File "sentry/db/postgres/decorators.py", line 96, in inner
    six.reraise(exc_info[0], exc_info[0](msg), exc_info[2])
  File "sentry/db/postgres/decorators.py", line 89, in inner
    return func(self, sql, *args, **kwargs)
  File "sentry/db/postgres/base.py", line 39, in execute
    return self.cursor.execute(sql, params)

IntegrityError('duplicate key value violates unique constraint "sentry_featureadoption_organization_id_78451b8747a9e638_uniq"\nDETAIL:  Key (organization_id, feature_id)=(44991, 0) already exists.\n',)
SQL: INSERT INTO "sentry_featureadoption" ("organization_id", "feature_id", "date_completed", "complete", "applicable", "data") VALUES (%s, %s, %s, %s, %s, %s), (%s, %s, %s, %s, %s, %s)
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/sentry/models/featureadoption.py`
Content:
```
1 from __future__ import absolute_import
2 
3 import logging
4 
5 from django.db import models, IntegrityError, transaction
6 from django.utils import timezone
7 from jsonfield import JSONField
8 
9 from sentry.adoption import manager
10 from sentry.adoption.manager import UnknownFeature
11 from sentry.db.models import (
12     BaseManager,
13     FlexibleForeignKey,
14     Model,
15     sane_repr
16 )
17 from sentry.utils import redis
18 
19 logger = logging.getLogger(__name__)
20 
21 FEATURE_ADOPTION_REDIS_KEY = 'organization-feature-adoption:{}'
22 
23 # Languages
24 manager.add(0, "python", "Python", "language")
25 manager.add(1, "javascript", "JavaScript", "language")
26 manager.add(2, "node", "Node.js", "language")
27 manager.add(3, "ruby", "Ruby", "language")
28 manager.add(4, "java", "Java", "language")
29 manager.add(5, "cocoa", "Cocoa", "language")
30 manager.add(6, "objc", "Objective-C", "language")
31 manager.add(7, "php", "PHP", "language")
32 manager.add(8, "go", "Go", "language")
33 manager.add(9, "csharp", "C#", "language")
34 manager.add(10, "perl", "Perl", "language")
35 manager.add(11, "elixir", "Elixir", "language")
36 manager.add(12, "cfml", "CFML", "language")
37 manager.add(13, "groovy", "Groovy", "language")
38 manager.add(14, "csp", "CSP Reports", "language")
39 
40 # Frameworks
41 manager.add(20, "flask", "Flask", "integration", prerequisite=["python"])
42 manager.add(21, "django", "Django", "integration", prerequisite=["python"])
43 manager.add(22, "celery", "Celery", "integration", prerequisite=["python"])
44 manager.add(23, "bottle", "Bottle", "integration", prerequisite=["python"])
45 manager.add(24, "pylons", "Pylons", "integration", prerequisite=["python"])
46 manager.add(25, "tornado", "Tornado", "integration", prerequisite=["python"])
47 manager.add(26, "webpy", "web.py", "integration", prerequisite=["python"])
48 manager.add(27, "zope", "Zope", "integration", prerequisite=["python"])
49 
50 # Configuration
51 manager.add(40, "first_event", "First Event", "code", prerequisite=["first_project"])
52 manager.add(41, "release_tracking", "Release Tracking", "code", prerequisite=["first_event"])
53 manager.add(
54     42,
55     "environment_tracking",
56     "Environment Tracking",
57     "code",
58     prerequisite=["first_event"])
59 manager.add(43, "user_tracking", "User Tracking", "code", prerequisite=["first_event"])
60 manager.add(44, "custom_tags", "Custom Tags", "code", prerequisite=["first_event"])
61 manager.add(45, "source_maps", "Source Maps", "code", prerequisite=["first_event", "javascript"])
62 manager.add(46, "user_feedback", "User Feedback", "code", prerequisite=["user_tracking"])
63 # manager.add(47, "api", "API", "code", prerequisite=["first_event"])  #
64 # Challenging to determine what organization (i.e. api/0/organizations/)
65 manager.add(48, "breadcrumbs", "Breadcrumbs", "code", prerequisite=[
66             "first_event", ("python", "javascript", "node", "php")])
67 # TODO(ehfeng) manager.add("resolve_in_commit", "Resolve in Commit",
68 # "code", prerequisite=["first_event", "releases"])
69 
70 # Web UI
71 manager.add(60, "first_project", "First Project", "web")
72 manager.add(61, "invite_team", "Invite Team", "web", prerequisite=["first_project"])
73 manager.add(62, "assignment", "Assign Issue", "web", prerequisite=["invite_team", "first_event"])
74 manager.add(
75     63,
76     "resolved_in_release",
77     "Resolve in Next Release",
78     "web",
79     prerequisite=["release_tracking"])
80 manager.add(64, "advanced_search", "Advanced Search", "web", prerequisite=["first_event"])
81 manager.add(65, "saved_search", "Saved Search", "web", prerequisite=["advanced_search"])
82 manager.add(66, "inbound_filters", "Inbound Filters", "web", prerequisite=["first_event"])
83 manager.add(67, "alert_rules", "Alert Rules", "web", prerequisite=["first_event"])
84 manager.add(
85     68,
86     "issue_tracker_integration",
87     "Issue Tracker Integration",
88     "web",
89     prerequisite=["first_project"])
90 manager.add(
91     69,
92     "notification_integration",
93     "Notification Integration",
94     "web",
95     prerequisite=["first_project"])
96 # TODO(ehfeng) manager.add("snooze", "Snooze Issue", "web", prerequisite=["first_event"])
97 # TODO(ehfeng) manager.add("merge", "Merge Issues", "web", prerequisite=["first_event"])
98 # TODO(ehfeng) manager.add("releases", "Releases", "web", prerequisite=["first_project"])
99 
100 # Admin UI
101 manager.add(80, "sso", "SSO", "admin", prerequisite=["invite_team"])
102 manager.add(81, "data_scrubbers", "Data Scrubbers", "admin", prerequisite=["first_event"])
103 
104 
105 class FeatureAdoptionManager(BaseManager):
106     def in_cache(self, organization_id, feature_id):
107         org_key = FEATURE_ADOPTION_REDIS_KEY.format(organization_id)
108         feature_matches = []
109         with redis.clusters.get('default').map() as client:
110             feature_matches.append(client.sismember(org_key, feature_id))
111 
112         return any([p.value for p in feature_matches])
113 
114     def set_cache(self, organization_id, feature_id):
115         org_key = FEATURE_ADOPTION_REDIS_KEY.format(organization_id)
116         with redis.clusters.get('default').map() as client:
117             client.sadd(org_key, feature_id)
118         return True
119 
120     def get_all_cache(self, organization_id):
121         org_key = FEATURE_ADOPTION_REDIS_KEY.format(organization_id)
122         result = []
123         with redis.clusters.get('default').map() as client:
124             result.append(client.smembers(org_key))
125 
126         return {int(x) for x in set.union(*[p.value for p in result])}
127 
128     def bulk_set_cache(self, organization_id, *args):
129         if not args:
130             return False
131 
132         org_key = FEATURE_ADOPTION_REDIS_KEY.format(organization_id)
133         with redis.clusters.get('default').map() as client:
134             client.sadd(org_key, *args)
135         return True
136 
137     def record(self, organization_id, feature_slug, **kwargs):
138         try:
139             feature_id = manager.get_by_slug(feature_slug).id
140         except UnknownFeature as e:
141             logger.exception(e)
142             return False
143 
144         if not self.in_cache(organization_id, feature_id):
145             row, created = self.create_or_update(
146                 organization_id=organization_id,
147                 feature_id=feature_id,
148                 complete=True)
149             self.set_cache(organization_id, feature_id)
150             return created
151 
152         return False
153 
154     def bulk_record(self, organization_id, feature_slugs, **kwargs):
155         features = []
156 
157         try:
158             feature_ids = set([manager.get_by_slug(slug).id for slug in feature_slugs])
159         except UnknownFeature as e:
160             logger.exception(e)
161             return False
162 
163         incomplete_feature_ids = feature_ids - self.get_all_cache(organization_id)
164 
165         if not incomplete_feature_ids:
166             return False
167 
168         for feature_id in incomplete_feature_ids:
169             features.append(FeatureAdoption(
170                 organization_id=organization_id,
171                 feature_id=feature_id,
172                 complete=True))
173         try:
174             with transaction.atomic():
175                 self.bulk_create(features)
176                 return True
177 
178         except IntegrityError as e:
179             # This can occur if redis somehow loses the set of complete features and
180             # we attempt to insert duplicate (org_id, feature_id) rows
181             logger.exception(e)
182             return False
183         finally:
184             return self.bulk_set_cache(organization_id, *incomplete_feature_ids)
185 
186     def get_by_slug(self, organization, slug):
187         return self.filter(organization=organization,
188                            feature_id=manager.get_by_slug(slug).id).first()
189 
190 
191 class FeatureAdoption(Model):
192     __core__ = False
193 
194     organization = FlexibleForeignKey('sentry.Organization')
195     feature_id = models.PositiveIntegerField(choices=[(f.id, f.name) for f in manager.all()])
196     date_completed = models.DateTimeField(default=timezone.now)
197     complete = models.BooleanField(default=False)
198     applicable = models.BooleanField(default=True)  # Is this feature applicable to this team?
199     data = JSONField()
200 
201     objects = FeatureAdoptionManager()
202 
203     __repr__ = sane_repr('organization_id', 'feature_id', 'complete', 'applicable')
204 
205     class Meta:
206         app_label = 'sentry'
207         db_table = 'sentry_featureadoption'
208         unique_together = (('organization', 'feature_id'),)
209
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/sentry/models/featureadoption.py b/src/sentry/models/featureadoption.py
--- a/src/sentry/models/featureadoption.py
+++ b/src/sentry/models/featureadoption.py
@@ -175,10 +175,12 @@
                 self.bulk_create(features)
                 return True
 
-        except IntegrityError as e:
+        except IntegrityError:
             # This can occur if redis somehow loses the set of complete features and
             # we attempt to insert duplicate (org_id, feature_id) rows
-            logger.exception(e)
+            # This also will happen if we get parallel processes running `bulk_record` and
+            # `get_all_cache` returns in the second process before the first process
+            # can `bulk_set_cache`.
             return False
         finally:
             return self.bulk_set_cache(organization_id, *incomplete_feature_ids)

{"golden_diff": "diff --git a/src/sentry/models/featureadoption.py b/src/sentry/models/featureadoption.py\n--- a/src/sentry/models/featureadoption.py\n+++ b/src/sentry/models/featureadoption.py\n@@ -175,10 +175,12 @@\n                 self.bulk_create(features)\n                 return True\n \n-        except IntegrityError as e:\n+        except IntegrityError:\n             # This can occur if redis somehow loses the set of complete features and\n             # we attempt to insert duplicate (org_id, feature_id) rows\n-            logger.exception(e)\n+            # This also will happen if we get parallel processes running `bulk_record` and\n+            # `get_all_cache` returns in the second process before the first process\n+            # can `bulk_set_cache`.\n             return False\n         finally:\n             return self.bulk_set_cache(organization_id, *incomplete_feature_ids)\n", "issue": "IntegrityError: IntegrityError('duplicate key value violates unique constraint \"sentry_featureadoption_organizati...\nhttps://sentry.io/sentry/sentry/issues/299179840/\r\n\r\n```\r\nIntegrityError: IntegrityError('duplicate key value violates unique constraint \"sentry_featureadoption_organization_id_78451b8747a9e638_uniq\"\\nDETAIL:  Key (organization_id, feature_id)=(****, 0) already exists.\\n',)\r\nSQL: INSERT INTO \"sentry_featureadoption\" (\"organization_id\", \"feature_id\", \"date_completed\", \"complete\", \"applicable\", \"data\") VALUES (%s, %s, %s, %s, %s, %s), (%s, %s, %s, %s, %s, %s)\r\n(12 additional frame(s) were not displayed)\r\n...\r\n  File \"sentry/db/postgres/decorators.py\", line 74, in inner\r\n    return func(self, *args, **kwargs)\r\n  File \"sentry/db/postgres/decorators.py\", line 21, in inner\r\n    return func(self, *args, **kwargs)\r\n  File \"sentry/db/postgres/decorators.py\", line 96, in inner\r\n    six.reraise(exc_info[0], exc_info[0](msg), exc_info[2])\r\n  File \"sentry/db/postgres/decorators.py\", line 89, in inner\r\n    return func(self, sql, *args, **kwargs)\r\n  File \"sentry/db/postgres/base.py\", line 39, in execute\r\n    return self.cursor.execute(sql, params)\r\n\r\nIntegrityError('duplicate key value violates unique constraint \"sentry_featureadoption_organization_id_78451b8747a9e638_uniq\"\\nDETAIL:  Key (organization_id, feature_id)=(44991, 0) already exists.\\n',)\r\nSQL: INSERT INTO \"sentry_featureadoption\" (\"organization_id\", \"feature_id\", \"date_completed\", \"complete\", \"applicable\", \"data\") VALUES (%s, %s, %s, %s, %s, %s), (%s, %s, %s, %s, %s, %s)\r\n```\n", "before_files": [{"content": "from __future__ import absolute_import\n\nimport logging\n\nfrom django.db import models, IntegrityError, transaction\nfrom django.utils import timezone\nfrom jsonfield import JSONField\n\nfrom sentry.adoption import manager\nfrom sentry.adoption.manager import UnknownFeature\nfrom sentry.db.models import (\n    BaseManager,\n    FlexibleForeignKey,\n    Model,\n    sane_repr\n)\nfrom sentry.utils import redis\n\nlogger = logging.getLogger(__name__)\n\nFEATURE_ADOPTION_REDIS_KEY = 'organization-feature-adoption:{}'\n\n# Languages\nmanager.add(0, \"python\", \"Python\", \"language\")\nmanager.add(1, \"javascript\", \"JavaScript\", \"language\")\nmanager.add(2, \"node\", \"Node.js\", \"language\")\nmanager.add(3, \"ruby\", \"Ruby\", \"language\")\nmanager.add(4, \"java\", \"Java\", \"language\")\nmanager.add(5, \"cocoa\", \"Cocoa\", \"language\")\nmanager.add(6, \"objc\", \"Objective-C\", \"language\")\nmanager.add(7, \"php\", \"PHP\", \"language\")\nmanager.add(8, \"go\", \"Go\", \"language\")\nmanager.add(9, \"csharp\", \"C#\", \"language\")\nmanager.add(10, \"perl\", \"Perl\", \"language\")\nmanager.add(11, \"elixir\", \"Elixir\", \"language\")\nmanager.add(12, \"cfml\", \"CFML\", \"language\")\nmanager.add(13, \"groovy\", \"Groovy\", \"language\")\nmanager.add(14, \"csp\", \"CSP Reports\", \"language\")\n\n# Frameworks\nmanager.add(20, \"flask\", \"Flask\", \"integration\", prerequisite=[\"python\"])\nmanager.add(21, \"django\", \"Django\", \"integration\", prerequisite=[\"python\"])\nmanager.add(22, \"celery\", \"Celery\", \"integration\", prerequisite=[\"python\"])\nmanager.add(23, \"bottle\", \"Bottle\", \"integration\", prerequisite=[\"python\"])\nmanager.add(24, \"pylons\", \"Pylons\", \"integration\", prerequisite=[\"python\"])\nmanager.add(25, \"tornado\", \"Tornado\", \"integration\", prerequisite=[\"python\"])\nmanager.add(26, \"webpy\", \"web.py\", \"integration\", prerequisite=[\"python\"])\nmanager.add(27, \"zope\", \"Zope\", \"integration\", prerequisite=[\"python\"])\n\n# Configuration\nmanager.add(40, \"first_event\", \"First Event\", \"code\", prerequisite=[\"first_project\"])\nmanager.add(41, \"release_tracking\", \"Release Tracking\", \"code\", prerequisite=[\"first_event\"])\nmanager.add(\n    42,\n    \"environment_tracking\",\n    \"Environment Tracking\",\n    \"code\",\n    prerequisite=[\"first_event\"])\nmanager.add(43, \"user_tracking\", \"User Tracking\", \"code\", prerequisite=[\"first_event\"])\nmanager.add(44, \"custom_tags\", \"Custom Tags\", \"code\", prerequisite=[\"first_event\"])\nmanager.add(45, \"source_maps\", \"Source Maps\", \"code\", prerequisite=[\"first_event\", \"javascript\"])\nmanager.add(46, \"user_feedback\", \"User Feedback\", \"code\", prerequisite=[\"user_tracking\"])\n# manager.add(47, \"api\", \"API\", \"code\", prerequisite=[\"first_event\"])  #\n# Challenging to determine what organization (i.e. api/0/organizations/)\nmanager.add(48, \"breadcrumbs\", \"Breadcrumbs\", \"code\", prerequisite=[\n            \"first_event\", (\"python\", \"javascript\", \"node\", \"php\")])\n# TODO(ehfeng) manager.add(\"resolve_in_commit\", \"Resolve in Commit\",\n# \"code\", prerequisite=[\"first_event\", \"releases\"])\n\n# Web UI\nmanager.add(60, \"first_project\", \"First Project\", \"web\")\nmanager.add(61, \"invite_team\", \"Invite Team\", \"web\", prerequisite=[\"first_project\"])\nmanager.add(62, \"assignment\", \"Assign Issue\", \"web\", prerequisite=[\"invite_team\", \"first_event\"])\nmanager.add(\n    63,\n    \"resolved_in_release\",\n    \"Resolve in Next Release\",\n    \"web\",\n    prerequisite=[\"release_tracking\"])\nmanager.add(64, \"advanced_search\", \"Advanced Search\", \"web\", prerequisite=[\"first_event\"])\nmanager.add(65, \"saved_search\", \"Saved Search\", \"web\", prerequisite=[\"advanced_search\"])\nmanager.add(66, \"inbound_filters\", \"Inbound Filters\", \"web\", prerequisite=[\"first_event\"])\nmanager.add(67, \"alert_rules\", \"Alert Rules\", \"web\", prerequisite=[\"first_event\"])\nmanager.add(\n    68,\n    \"issue_tracker_integration\",\n    \"Issue Tracker Integration\",\n    \"web\",\n    prerequisite=[\"first_project\"])\nmanager.add(\n    69,\n    \"notification_integration\",\n    \"Notification Integration\",\n    \"web\",\n    prerequisite=[\"first_project\"])\n# TODO(ehfeng) manager.add(\"snooze\", \"Snooze Issue\", \"web\", prerequisite=[\"first_event\"])\n# TODO(ehfeng) manager.add(\"merge\", \"Merge Issues\", \"web\", prerequisite=[\"first_event\"])\n# TODO(ehfeng) manager.add(\"releases\", \"Releases\", \"web\", prerequisite=[\"first_project\"])\n\n# Admin UI\nmanager.add(80, \"sso\", \"SSO\", \"admin\", prerequisite=[\"invite_team\"])\nmanager.add(81, \"data_scrubbers\", \"Data Scrubbers\", \"admin\", prerequisite=[\"first_event\"])\n\n\nclass FeatureAdoptionManager(BaseManager):\n    def in_cache(self, organization_id, feature_id):\n        org_key = FEATURE_ADOPTION_REDIS_KEY.format(organization_id)\n        feature_matches = []\n        with redis.clusters.get('default').map() as client:\n            feature_matches.append(client.sismember(org_key, feature_id))\n\n        return any([p.value for p in feature_matches])\n\n    def set_cache(self, organization_id, feature_id):\n        org_key = FEATURE_ADOPTION_REDIS_KEY.format(organization_id)\n        with redis.clusters.get('default').map() as client:\n            client.sadd(org_key, feature_id)\n        return True\n\n    def get_all_cache(self, organization_id):\n        org_key = FEATURE_ADOPTION_REDIS_KEY.format(organization_id)\n        result = []\n        with redis.clusters.get('default').map() as client:\n            result.append(client.smembers(org_key))\n\n        return {int(x) for x in set.union(*[p.value for p in result])}\n\n    def bulk_set_cache(self, organization_id, *args):\n        if not args:\n            return False\n\n        org_key = FEATURE_ADOPTION_REDIS_KEY.format(organization_id)\n        with redis.clusters.get('default').map() as client:\n            client.sadd(org_key, *args)\n        return True\n\n    def record(self, organization_id, feature_slug, **kwargs):\n        try:\n            feature_id = manager.get_by_slug(feature_slug).id\n        except UnknownFeature as e:\n            logger.exception(e)\n            return False\n\n        if not self.in_cache(organization_id, feature_id):\n            row, created = self.create_or_update(\n                organization_id=organization_id,\n                feature_id=feature_id,\n                complete=True)\n            self.set_cache(organization_id, feature_id)\n            return created\n\n        return False\n\n    def bulk_record(self, organization_id, feature_slugs, **kwargs):\n        features = []\n\n        try:\n            feature_ids = set([manager.get_by_slug(slug).id for slug in feature_slugs])\n        except UnknownFeature as e:\n            logger.exception(e)\n            return False\n\n        incomplete_feature_ids = feature_ids - self.get_all_cache(organization_id)\n\n        if not incomplete_feature_ids:\n            return False\n\n        for feature_id in incomplete_feature_ids:\n            features.append(FeatureAdoption(\n                organization_id=organization_id,\n                feature_id=feature_id,\n                complete=True))\n        try:\n            with transaction.atomic():\n                self.bulk_create(features)\n                return True\n\n        except IntegrityError as e:\n            # This can occur if redis somehow loses the set of complete features and\n            # we attempt to insert duplicate (org_id, feature_id) rows\n            logger.exception(e)\n            return False\n        finally:\n            return self.bulk_set_cache(organization_id, *incomplete_feature_ids)\n\n    def get_by_slug(self, organization, slug):\n        return self.filter(organization=organization,\n                           feature_id=manager.get_by_slug(slug).id).first()\n\n\nclass FeatureAdoption(Model):\n    __core__ = False\n\n    organization = FlexibleForeignKey('sentry.Organization')\n    feature_id = models.PositiveIntegerField(choices=[(f.id, f.name) for f in manager.all()])\n    date_completed = models.DateTimeField(default=timezone.now)\n    complete = models.BooleanField(default=False)\n    applicable = models.BooleanField(default=True)  # Is this feature applicable to this team?\n    data = JSONField()\n\n    objects = FeatureAdoptionManager()\n\n    __repr__ = sane_repr('organization_id', 'feature_id', 'complete', 'applicable')\n\n    class Meta:\n        app_label = 'sentry'\n        db_table = 'sentry_featureadoption'\n        unique_together = (('organization', 'feature_id'),)\n", "path": "src/sentry/models/featureadoption.py"}], "after_files": [{"content": "from __future__ import absolute_import\n\nimport logging\n\nfrom django.db import models, IntegrityError, transaction\nfrom django.utils import timezone\nfrom jsonfield import JSONField\n\nfrom sentry.adoption import manager\nfrom sentry.adoption.manager import UnknownFeature\nfrom sentry.db.models import (\n    BaseManager,\n    FlexibleForeignKey,\n    Model,\n    sane_repr\n)\nfrom sentry.utils import redis\n\nlogger = logging.getLogger(__name__)\n\nFEATURE_ADOPTION_REDIS_KEY = 'organization-feature-adoption:{}'\n\n# Languages\nmanager.add(0, \"python\", \"Python\", \"language\")\nmanager.add(1, \"javascript\", \"JavaScript\", \"language\")\nmanager.add(2, \"node\", \"Node.js\", \"language\")\nmanager.add(3, \"ruby\", \"Ruby\", \"language\")\nmanager.add(4, \"java\", \"Java\", \"language\")\nmanager.add(5, \"cocoa\", \"Cocoa\", \"language\")\nmanager.add(6, \"objc\", \"Objective-C\", \"language\")\nmanager.add(7, \"php\", \"PHP\", \"language\")\nmanager.add(8, \"go\", \"Go\", \"language\")\nmanager.add(9, \"csharp\", \"C#\", \"language\")\nmanager.add(10, \"perl\", \"Perl\", \"language\")\nmanager.add(11, \"elixir\", \"Elixir\", \"language\")\nmanager.add(12, \"cfml\", \"CFML\", \"language\")\nmanager.add(13, \"groovy\", \"Groovy\", \"language\")\nmanager.add(14, \"csp\", \"CSP Reports\", \"language\")\n\n# Frameworks\nmanager.add(20, \"flask\", \"Flask\", \"integration\", prerequisite=[\"python\"])\nmanager.add(21, \"django\", \"Django\", \"integration\", prerequisite=[\"python\"])\nmanager.add(22, \"celery\", \"Celery\", \"integration\", prerequisite=[\"python\"])\nmanager.add(23, \"bottle\", \"Bottle\", \"integration\", prerequisite=[\"python\"])\nmanager.add(24, \"pylons\", \"Pylons\", \"integration\", prerequisite=[\"python\"])\nmanager.add(25, \"tornado\", \"Tornado\", \"integration\", prerequisite=[\"python\"])\nmanager.add(26, \"webpy\", \"web.py\", \"integration\", prerequisite=[\"python\"])\nmanager.add(27, \"zope\", \"Zope\", \"integration\", prerequisite=[\"python\"])\n\n# Configuration\nmanager.add(40, \"first_event\", \"First Event\", \"code\", prerequisite=[\"first_project\"])\nmanager.add(41, \"release_tracking\", \"Release Tracking\", \"code\", prerequisite=[\"first_event\"])\nmanager.add(\n    42,\n    \"environment_tracking\",\n    \"Environment Tracking\",\n    \"code\",\n    prerequisite=[\"first_event\"])\nmanager.add(43, \"user_tracking\", \"User Tracking\", \"code\", prerequisite=[\"first_event\"])\nmanager.add(44, \"custom_tags\", \"Custom Tags\", \"code\", prerequisite=[\"first_event\"])\nmanager.add(45, \"source_maps\", \"Source Maps\", \"code\", prerequisite=[\"first_event\", \"javascript\"])\nmanager.add(46, \"user_feedback\", \"User Feedback\", \"code\", prerequisite=[\"user_tracking\"])\n# manager.add(47, \"api\", \"API\", \"code\", prerequisite=[\"first_event\"])  #\n# Challenging to determine what organization (i.e. api/0/organizations/)\nmanager.add(48, \"breadcrumbs\", \"Breadcrumbs\", \"code\", prerequisite=[\n            \"first_event\", (\"python\", \"javascript\", \"node\", \"php\")])\n# TODO(ehfeng) manager.add(\"resolve_in_commit\", \"Resolve in Commit\",\n# \"code\", prerequisite=[\"first_event\", \"releases\"])\n\n# Web UI\nmanager.add(60, \"first_project\", \"First Project\", \"web\")\nmanager.add(61, \"invite_team\", \"Invite Team\", \"web\", prerequisite=[\"first_project\"])\nmanager.add(62, \"assignment\", \"Assign Issue\", \"web\", prerequisite=[\"invite_team\", \"first_event\"])\nmanager.add(\n    63,\n    \"resolved_in_release\",\n    \"Resolve in Next Release\",\n    \"web\",\n    prerequisite=[\"release_tracking\"])\nmanager.add(64, \"advanced_search\", \"Advanced Search\", \"web\", prerequisite=[\"first_event\"])\nmanager.add(65, \"saved_search\", \"Saved Search\", \"web\", prerequisite=[\"advanced_search\"])\nmanager.add(66, \"inbound_filters\", \"Inbound Filters\", \"web\", prerequisite=[\"first_event\"])\nmanager.add(67, \"alert_rules\", \"Alert Rules\", \"web\", prerequisite=[\"first_event\"])\nmanager.add(\n    68,\n    \"issue_tracker_integration\",\n    \"Issue Tracker Integration\",\n    \"web\",\n    prerequisite=[\"first_project\"])\nmanager.add(\n    69,\n    \"notification_integration\",\n    \"Notification Integration\",\n    \"web\",\n    prerequisite=[\"first_project\"])\n# TODO(ehfeng) manager.add(\"snooze\", \"Snooze Issue\", \"web\", prerequisite=[\"first_event\"])\n# TODO(ehfeng) manager.add(\"merge\", \"Merge Issues\", \"web\", prerequisite=[\"first_event\"])\n# TODO(ehfeng) manager.add(\"releases\", \"Releases\", \"web\", prerequisite=[\"first_project\"])\n\n# Admin UI\nmanager.add(80, \"sso\", \"SSO\", \"admin\", prerequisite=[\"invite_team\"])\nmanager.add(81, \"data_scrubbers\", \"Data Scrubbers\", \"admin\", prerequisite=[\"first_event\"])\n\n\nclass FeatureAdoptionManager(BaseManager):\n    def in_cache(self, organization_id, feature_id):\n        org_key = FEATURE_ADOPTION_REDIS_KEY.format(organization_id)\n        feature_matches = []\n        with redis.clusters.get('default').map() as client:\n            feature_matches.append(client.sismember(org_key, feature_id))\n\n        return any([p.value for p in feature_matches])\n\n    def set_cache(self, organization_id, feature_id):\n        org_key = FEATURE_ADOPTION_REDIS_KEY.format(organization_id)\n        with redis.clusters.get('default').map() as client:\n            client.sadd(org_key, feature_id)\n        return True\n\n    def get_all_cache(self, organization_id):\n        org_key = FEATURE_ADOPTION_REDIS_KEY.format(organization_id)\n        result = []\n        with redis.clusters.get('default').map() as client:\n            result.append(client.smembers(org_key))\n\n        return {int(x) for x in set.union(*[p.value for p in result])}\n\n    def bulk_set_cache(self, organization_id, *args):\n        if not args:\n            return False\n\n        org_key = FEATURE_ADOPTION_REDIS_KEY.format(organization_id)\n        with redis.clusters.get('default').map() as client:\n            client.sadd(org_key, *args)\n        return True\n\n    def record(self, organization_id, feature_slug, **kwargs):\n        try:\n            feature_id = manager.get_by_slug(feature_slug).id\n        except UnknownFeature as e:\n            logger.exception(e)\n            return False\n\n        if not self.in_cache(organization_id, feature_id):\n            row, created = self.create_or_update(\n                organization_id=organization_id,\n                feature_id=feature_id,\n                complete=True)\n            self.set_cache(organization_id, feature_id)\n            return created\n\n        return False\n\n    def bulk_record(self, organization_id, feature_slugs, **kwargs):\n        features = []\n\n        try:\n            feature_ids = set([manager.get_by_slug(slug).id for slug in feature_slugs])\n        except UnknownFeature as e:\n            logger.exception(e)\n            return False\n\n        incomplete_feature_ids = feature_ids - self.get_all_cache(organization_id)\n\n        if not incomplete_feature_ids:\n            return False\n\n        for feature_id in incomplete_feature_ids:\n            features.append(FeatureAdoption(\n                organization_id=organization_id,\n                feature_id=feature_id,\n                complete=True))\n        try:\n            with transaction.atomic():\n                self.bulk_create(features)\n                return True\n\n        except IntegrityError:\n            # This can occur if redis somehow loses the set of complete features and\n            # we attempt to insert duplicate (org_id, feature_id) rows\n            # This also will happen if we get parallel processes running `bulk_record` and\n            # `get_all_cache` returns in the second process before the first process\n            # can `bulk_set_cache`.\n            return False\n        finally:\n            return self.bulk_set_cache(organization_id, *incomplete_feature_ids)\n\n    def get_by_slug(self, organization, slug):\n        return self.filter(organization=organization,\n                           feature_id=manager.get_by_slug(slug).id).first()\n\n\nclass FeatureAdoption(Model):\n    __core__ = False\n\n    organization = FlexibleForeignKey('sentry.Organization')\n    feature_id = models.PositiveIntegerField(choices=[(f.id, f.name) for f in manager.all()])\n    date_completed = models.DateTimeField(default=timezone.now)\n    complete = models.BooleanField(default=False)\n    applicable = models.BooleanField(default=True)  # Is this feature applicable to this team?\n    data = JSONField()\n\n    objects = FeatureAdoptionManager()\n\n    __repr__ = sane_repr('organization_id', 'feature_id', 'complete', 'applicable')\n\n    class Meta:\n        app_label = 'sentry'\n        db_table = 'sentry_featureadoption'\n        unique_together = (('organization', 'feature_id'),)\n", "path": "src/sentry/models/featureadoption.py"}]}