Browse Source

fix(orgmember): Add caching to the feature flag check (#61680)

- This adds in memory caching for the feature flag check so we're not
doing it repeatedly for the same organization
William Mak 1 year ago
parent
commit
05ca087f46

+ 24 - 13
src/sentry/api/serializers/models/project.py

@@ -105,6 +105,7 @@ def get_access_by_project(
     prefetch_related_objects(projects, "organization")
 
     result = {}
+    has_team_roles_cache = {}
     for project in projects:
         parent_teams = [t.id for t in project_team_map.get(project.id, [])]
         member_teams = [m for m in team_memberships if m.team_id in parent_teams]
@@ -119,19 +120,29 @@ def get_access_by_project(
         )
 
         team_scopes = set()
-        if has_access:
-            # Project can be the child of several Teams, and the User can join
-            # several Teams and receive roles at each of them,
-            team_scopes = team_scopes.union(*[m.get_scopes() for m in member_teams])
-
-            # User may have elevated team-roles from their org-role
-            top_org_role = org_roles[0] if org_roles else None
-            if is_superuser:
-                top_org_role = organization_roles.get_top_dog().id
-
-            if top_org_role:
-                minimum_team_role = roles.get_minimum_team_role(top_org_role)
-                team_scopes = team_scopes.union(minimum_team_role.scopes)
+        with sentry_sdk.start_span(op="project.check-team-access", description=project.id) as span:
+            span.set_tag("project.member_count", len(member_teams))
+            if has_access:
+                # Project can be the child of several Teams, and the User can join
+                # several Teams and receive roles at each of them,
+                for member in member_teams:
+                    role_org = member.organizationmember.organization
+                    if role_org.id not in has_team_roles_cache:
+                        has_team_roles_cache[role_org.id] = features.has(
+                            "organizations:team-roles", role_org
+                        )
+                    team_scopes = team_scopes.union(
+                        *[member.get_scopes(has_team_roles_cache[role_org.id])]
+                    )
+
+                # User may have elevated team-roles from their org-role
+                top_org_role = org_roles[0] if org_roles else None
+                if is_superuser:
+                    top_org_role = organization_roles.get_top_dog().id
+
+                if top_org_role:
+                    minimum_team_role = roles.get_minimum_team_role(top_org_role)
+                    team_scopes = team_scopes.union(minimum_team_role.scopes)
 
         result[project] = {
             "is_member": is_member,

+ 7 - 3
src/sentry/models/organizationmemberteam.py

@@ -1,6 +1,6 @@
 from __future__ import annotations
 
-from typing import Any, ClassVar, FrozenSet, Mapping
+from typing import Any, ClassVar, FrozenSet, Mapping, Optional
 
 from django.db import models
 
@@ -94,8 +94,12 @@ class OrganizationMemberTeam(ReplicatedRegionModel):
                 return team_role
         return minimum_role
 
-    def get_scopes(self) -> FrozenSet[str]:
+    def get_scopes(self, has_team_roles: Optional[bool] = None) -> FrozenSet[str]:
         """Get the scopes belonging to this member's team-level role."""
-        if features.has("organizations:team-roles", self.organizationmember.organization):
+        if has_team_roles is None:
+            has_team_roles = features.has(
+                "organizations:team-roles", self.organizationmember.organization
+            )
+        if has_team_roles:
             return self.organizationmember.organization.get_scopes(self.get_team_role())
         return frozenset()