def test_get_autoassign_owners_only_issueowners_exists(self): rule_a = Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]) rule_b = Rule(Matcher("path", "src/*"), [Owner("user", self.user.email)]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([rule_a, rule_b]), ) # No data matches assert ProjectOwnership.get_autoassign_owners(self.project.id, {}) == (False, [], False) # No autoassignment on match assert ProjectOwnership.get_autoassign_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "foo.py" }] }}) == (False, [self.team], False) # autoassignment is True owner = ProjectOwnership.objects.get(project_id=self.project.id) owner.auto_assignment = True owner.save() assert ProjectOwnership.get_autoassign_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "foo.py" }] }}) == (True, [self.team], False)
def test_get_owners_no_record(self): assert ProjectOwnership.get_owners(self.project.id, {}) == (ProjectOwnership.Everyone, None) assert ProjectOwnership.get_owners(self.project.id, {}) == (ProjectOwnership.Everyone, None)
def test_get_autoassign_owners_only_codeowners_exists(self): # This case will never exist bc we create a ProjectOwnership record if none exists when creating a ProjectCodeOwner record. # We have this testcase for potential corrupt data. self.team = self.create_team(organization=self.organization, slug="tiger-team", members=[self.user]) self.code_mapping = self.create_code_mapping(project=self.project) rule_a = Rule(Matcher("path", "*.js"), [Owner("team", self.team.slug)]) self.create_codeowners( self.project, self.code_mapping, raw="*.js @tiger-team", schema=dump_schema([rule_a]), ) # No data matches assert ProjectOwnership.get_autoassign_owners(self.project.id, {}) == (False, [], False) # No autoassignment on match assert ProjectOwnership.get_autoassign_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "foo.js" }] }}) == (False, [self.team], True)
def test_get_autoassign_owners_when_codeowners_and_issueowners_exists( self): self.team = self.create_team(organization=self.organization, slug="tiger-team", members=[self.user]) self.team2 = self.create_team(organization=self.organization, slug="dolphin-team", members=[self.user]) self.project = self.create_project(organization=self.organization, teams=[self.team, self.team2]) self.code_mapping = self.create_code_mapping(project=self.project) rule_a = Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]) rule_b = Rule(Matcher("path", "src/*"), [Owner("user", self.user.email)]) rule_c = Rule(Matcher("path", "*.py"), [Owner("team", self.team2.slug)]) ProjectOwnership.objects.create(project_id=self.project.id, schema=dump_schema([rule_a, rule_b]), fallthrough=True) self.create_codeowners(self.project, self.code_mapping, raw="*.py @tiger-team", schema=dump_schema([rule_c])) # No autoassignment on match assert ProjectOwnership.get_autoassign_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "api/foo.py" }] }}) == (False, [self.team, self.team2], False) # autoassignment is True owner = ProjectOwnership.objects.get(project_id=self.project.id) owner.auto_assignment = True owner.save() assert ProjectOwnership.get_autoassign_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "api/foo.py" }] }}) == (True, [self.team, self.team2], False) # # more than 2 matches assert ProjectOwnership.get_autoassign_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "src/foo.py" }] }}) == (True, [self.user, self.team], False)
def test_get_owners_basic(self): matcher = Matcher('path', '*.py') ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([ Rule(matcher, [ Owner('user', self.user.email), Owner('team', self.team.slug), ]), ]), fallthrough=True, ) # No data matches assert ProjectOwnership.get_owners(self.project.id, {}) == (ProjectOwnership.Everyone, None) assert ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'foo.py', }] } } ) == ([Actor(self.user.id, User), Actor(self.team.id, Team)], matcher) assert ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'xxxx', }] } } ) == (ProjectOwnership.Everyone, None) # When fallthrough = False, we don't implicitly assign to Everyone ProjectOwnership.objects.filter( project_id=self.project.id, ).update(fallthrough=False) assert ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'xxxx', }] } } ) == ([], None)
def get_ownership(self, project): try: return ProjectOwnership.objects.get(project=project) except ProjectOwnership.DoesNotExist: return ProjectOwnership(project=project, date_created=None, last_updated=None)
def test_get_owners_when_codeowners_and_issueowners_exists(self): self.team = self.create_team( organization=self.organization, slug="tiger-team", members=[self.user] ) self.team2 = self.create_team( organization=self.organization, slug="dolphin-team", members=[self.user] ) self.project = self.create_project( organization=self.organization, teams=[self.team, self.team2] ) self.code_mapping = self.create_code_mapping(project=self.project) rule_a = Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]) rule_b = Rule(Matcher("path", "src/*"), [Owner("user", self.user.email)]) rule_c = Rule(Matcher("path", "*.py"), [Owner("team", self.team2.slug)]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([rule_a, rule_b]), fallthrough=True ) self.create_codeowners( self.project, self.code_mapping, raw="*.py @tiger-team", schema=dump_schema([rule_c]) ) self.assert_ownership_equals( ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "api/foo.py"}]}} ), ( [ActorTuple(self.team.id, Team), ActorTuple(self.team2.id, Team)], [rule_a, rule_c], ), )
def test_get_owners_when_codeowners_exists_and_no_issueowners(self): # This case will never exist bc we create a ProjectOwnership record if none exists when creating a ProjectCodeOwner record. # We have this testcase for potential corrupt data. self.team = self.create_team(organization=self.organization, slug="tiger-team", members=[self.user]) self.code_mapping = self.create_code_mapping(project=self.project) rule_a = Rule(Matcher("path", "*.js"), [Owner("team", self.team.slug)]) self.create_codeowners( self.project, self.code_mapping, raw="*.js @tiger-team", schema=dump_schema([rule_a]), ) self.assert_ownership_equals( ProjectOwnership.get_owners( self.project.id, {"stacktrace": { "frames": [{ "filename": "src/foo.js" }] }}), ( [ActorTuple(self.team.id, Team)], [rule_a], ), )
def handle_owner_assignment(project, group, event): from sentry.models import GroupAssignee, ProjectOwnership with metrics.timer("post_process.handle_owner_assignment"): # Is the issue already assigned to a team or user? key = "assignee_exists:1:%s" % group.id owners_exists = cache.get(key) if owners_exists is None: owners_exists = group.assignee_set.exists() or group.groupowner_set.exists() # Cache for an hour if it's assigned. We don't need to move that fast. cache.set(key, owners_exists, 3600 if owners_exists else 60) if owners_exists: return auto_assignment, owners, assigned_by_codeowners = ProjectOwnership.get_autoassign_owners( group.project_id, event.data ) if auto_assignment and owners: GroupAssignee.objects.assign(group, owners[0]) if assigned_by_codeowners: analytics.record( "codeowners.assignment", organization_id=project.organization_id, project_id=project.id, group_id=group.id, ) if owners: try: handle_group_owners(project, group, owners) except Exception: logger.exception("Failed to store group owners")
def get(self, request, project, event_id): """ Retrieve suggested owners information for an event `````````````````````````````````````````````````` :pparam string project_slug: the slug of the project the event belongs to. :pparam string event_id: the id of the event. :auth: required """ try: event = Event.objects.get( id=event_id, project_id=project.id, ) except Event.DoesNotExist: return Response({'detail': 'Event not found'}, status=404) # populate event data Event.objects.bind_nodes([event], 'data') owners = ProjectOwnership.get_owners(project.id, event.data) # For sake of the API, we don't differentiate between # the implicit "everyone" and no owners if owners == ProjectOwnership.Everyone: owners = [] return Response( serialize( Actor.resolve_many(owners), request.user, ActorSerializer(), ))
def handle_owner_assignment(project, group, event): from sentry.models import GroupAssignee, ProjectOwnership with metrics.timer("post_process.handle_owner_assignment"): owners_ingestion = features.has("projects:workflow-owners-ingestion", event.project) # Is the issue already assigned to a team or user? key = "assignee_exists:1:%s" % group.id owners_exists = cache.get(key) if owners_exists is None: owners_exists = group.assignee_set.exists() and ( not owners_ingestion or group.groupowner_set.exists()) # Cache for an hour if it's assigned. We don't need to move that fast. cache.set(key, owners_exists, 3600 if owners_exists else 60) if owners_exists: return auto_assignment, owners = ProjectOwnership.get_autoassign_owners( group.project_id, event.data) if auto_assignment and owners: GroupAssignee.objects.assign(group, owners[0]) if owners and owners_ingestion: try: handle_group_owners(project, group, owners) except Exception: logger.exception("Failed to store group owners")
def get_owners( project: Project, event: Optional["Event"] = None ) -> Iterable[Union["Team", "User"]]: """Given a project and an event, decide which users and teams are the owners.""" if event: owners, _ = ProjectOwnership.get_owners(project.id, event.data) else: owners = ProjectOwnership.Everyone if not owners: outcome = "empty" recipients = set() elif owners == ProjectOwnership.Everyone: outcome = "everyone" recipients = User.objects.filter(id__in=project.member_set.values_list("user", flat=True)) else: outcome = "match" recipients = ActorTuple.resolve_many(owners) metrics.incr( "features.owners.send_to", tags={"organization": project.organization_id, "outcome": outcome}, skip_internal=True, ) return recipients
def get(self, request, project, event_id): """ Retrieve suggested owners information for an event `````````````````````````````````````````````````` :pparam string project_slug: the slug of the project the event belongs to. :pparam string event_id: the id of the event. :auth: required """ event = eventstore.get_event_by_id(project.id, event_id) if event is None: return Response({"detail": "Event not found"}, status=404) # populate event data Event.objects.bind_nodes([event], "data") owners, rules = ProjectOwnership.get_owners(project.id, event.data) # For sake of the API, we don't differentiate between # the implicit "everyone" and no owners if owners == ProjectOwnership.Everyone: owners = [] return Response({ "owners": serialize(Actor.resolve_many(owners), request.user, ActorSerializer()), # TODO(mattrobenolt): We need to change the API here to return # all rules, just keeping this way currently for API compat "rule": rules[0].matcher if rules else None, "rules": rules or [], })
def test_get_owners_basic(self): matcher = Matcher('path', '*.py') ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([ Rule(matcher, [ Owner('user', self.user.email), Owner('team', self.team.slug), ]), ]), fallthrough=True, ) # No data matches assert ProjectOwnership.get_owners(self.project.id, {}) == (ProjectOwnership.Everyone, None) assert ProjectOwnership.get_owners(self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'foo.py', }] } }) == ([Actor(self.user.id, User), Actor(self.team.id, Team)], matcher) assert ProjectOwnership.get_owners(self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'xxxx', }] } }) == (ProjectOwnership.Everyone, None) # When fallthrough = False, we don't implicitly assign to Everyone ProjectOwnership.objects.filter( project_id=self.project.id, ).update(fallthrough=False) assert ProjectOwnership.get_owners(self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'xxxx', }] } }) == ([], None)
def get_send_to_owners( event: Any, project: Project) -> Mapping[ExternalProviders, Set[User]]: owners, _ = ProjectOwnership.get_owners(project.id, event.data) if owners == ProjectOwnership.Everyone: metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "everyone" }, skip_internal=True, ) return get_send_to_all_in_project(project) if not owners: metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "empty" }, skip_internal=True, ) return {} metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "match" }, skip_internal=True, ) user_ids_to_resolve = set() team_ids_to_resolve = set() for owner in owners: if owner.type == User: user_ids_to_resolve.add(owner.id) else: team_ids_to_resolve.add(owner.id) all_possible_users = set() if user_ids_to_resolve: all_possible_users |= set( User.objects.filter(id__in=user_ids_to_resolve)) # Get all users in teams. if team_ids_to_resolve: all_possible_users |= get_users_for_teams_to_resolve( team_ids_to_resolve) mapping: Mapping[ ExternalProviders, Set[User]] = NotificationSetting.objects.filter_to_subscribed_users( project, all_possible_users) return mapping
def handle_owner_assignment(project, group, event): from sentry.models import GroupAssignee, ProjectOwnership # Is the issue already assigned to a team or user? if group.assignee_set.exists(): return owner = ProjectOwnership.get_autoassign_owner(group.project_id, event.data) if owner is not None: GroupAssignee.objects.assign(group, owner)
def get_send_to_owners(self, event, project): owners, _ = ProjectOwnership.get_owners(project.id, event.data) if owners == ProjectOwnership.Everyone: metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "everyone" }, skip_internal=True, ) return self.get_send_to_all_in_project(project) if not owners: metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "empty" }, skip_internal=True, ) return {} metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "match" }, skip_internal=True, ) all_possible_user_ids = set() teams_to_resolve = set() for owner in owners: if owner.type == User: all_possible_user_ids.add(owner.id) else: teams_to_resolve.add(owner.id) # get all users in teams if teams_to_resolve: all_possible_user_ids |= self.get_user_ids_for_teams_to_resolve( teams_to_resolve) users = User.objects.filter(id__in=all_possible_user_ids) owners_by_provider = NotificationSetting.objects.filter_to_subscribed_users( project, users) return { provider: {user.id for user in users} for provider, users in owners_by_provider.items() }
def test_get_owners_basic(self): rule_a = Rule(Matcher("path", "*.py"), [Owner("team", self.team.slug)]) rule_b = Rule(Matcher("path", "src/*"), [Owner("user", self.user.email)]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([rule_a, rule_b]), fallthrough=True ) # No data matches assert ProjectOwnership.get_owners(self.project.id, {}) == (ProjectOwnership.Everyone, None) # Match only rule_a self.assert_ownership_equals( ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "foo.py"}]}} ), ([Actor(self.team.id, Team)], [rule_a]), ) # Match only rule_b self.assert_ownership_equals( ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "src/thing.txt"}]}} ), ([Actor(self.user.id, User)], [rule_b]), ) # Matches both rule_a and rule_b self.assert_ownership_equals( ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "src/foo.py"}]}} ), ([Actor(self.team.id, Team), Actor(self.user.id, User)], [rule_a, rule_b]), ) assert ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "xxxx"}]}} ) == (ProjectOwnership.Everyone, None) # When fallthrough = False, we don't implicitly assign to Everyone owner = ProjectOwnership.objects.get(project_id=self.project.id) owner.fallthrough = False owner.save() assert ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "xxxx"}]}} ) == ([], None) self.assert_ownership_equals( ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "src/foo.py"}]}} ), ([Actor(self.team.id, Team), Actor(self.user.id, User)], [rule_a, rule_b]), )
def get_send_to_owners(self, event, project): owners, _ = ProjectOwnership.get_owners(project.id, event.data) if owners == ProjectOwnership.Everyone: metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "everyone" }, skip_internal=True, ) return self.get_send_to_all_in_project(project) if not owners: metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "empty" }, skip_internal=True, ) return {} metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "match" }, skip_internal=True, ) all_possible_user_ids = set() teams_to_resolve = set() for owner in owners: if owner.type == User: all_possible_user_ids.add(owner.id) else: teams_to_resolve.add(owner.id) # get all users in teams if teams_to_resolve: all_possible_user_ids |= self.get_user_ids_for_teams_to_resolve( teams_to_resolve) output = defaultdict(set) disabled_users = self.disabled_users_from_project(project) for provider in EXTERNAL_PROVIDERS.keys(): output[provider] = all_possible_user_ids - disabled_users[provider] return output
def get_send_to_owners(self, event, project): owners, _ = ProjectOwnership.get_owners(project.id, event.data) if owners != ProjectOwnership.Everyone: if not owners: metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "empty" }, skip_internal=True, ) return set() metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "match" }, skip_internal=True, ) send_to = set() teams_to_resolve = set() for owner in owners: if owner.type == User: send_to.add(owner.id) else: teams_to_resolve.add(owner.id) # get all users in teams if teams_to_resolve: send_to |= set( User.objects.filter( is_active=True, sentry_orgmember_set__organizationmemberteam__team__id__in =teams_to_resolve, ).values_list("id", flat=True)) return send_to - self.disabled_users_from_project(project) else: metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "everyone" }, skip_internal=True, ) return self.get_send_to_all_in_project(project)
def test_abs_path_when_filename_present(self): frame = { "filename": "computer.cpp", "abs_path": "C:\\My\\Path\\computer.cpp", } rule = Rule(Matcher("path", "*My\\Path*"), [Owner("team", self.team.slug)]) ProjectOwnership.objects.create(project_id=self.project.id, schema=dump_schema([rule]), fallthrough=True) assert ProjectOwnership.get_owners( self.project.id, {"stacktrace": { "frames": [frame] }}) == ([ActorTuple(self.team.id, Team)], [rule])
def build_events_by_actor(project_id, events, user_ids): """ build_events_by_actor(project_id: Int, events: Set(Events), user_ids: Set[Int]) -> Map[Actor, Set(Events)] """ events_by_actor = defaultdict(set) for event in events: # TODO(LB): I Know this is inefficient. # ProjectOwnership.get_owners is O(n) queries and I'm doing that O(len(events)) times # I will create a follow-up PR to address this method's efficiency problem # Just wanted to make as few changes as possible for now. actors, __ = ProjectOwnership.get_owners(project_id, event.data) if actors == ProjectOwnership.Everyone: actors = [Actor(user_id, User) for user_id in user_ids] for actor in actors: events_by_actor[actor].add(event) return events_by_actor
def build_events_by_actor(project_id, events, user_ids): """ build_events_by_actor(project_id: Int, events: Set(Events), user_ids: Set[Int]) -> Map[Actor, Set(Events)] """ events_by_actor = defaultdict(set) for event in events: # TODO(LB): I Know this is inefficent. # ProjectOwnership.get_owners is O(n) queries and I'm doing that O(len(events)) times # I will create a follow-up PR to address this method's efficency problem # Just wanted to make as few changes as possible for now. actors, __ = ProjectOwnership.get_owners(project_id, event.data) if actors == ProjectOwnership.Everyone: actors = [Actor(user_id, User) for user_id in user_ids] for actor in actors: events_by_actor[actor].add(event) return events_by_actor
def handle_owner_assignment(project, group, event): from sentry.models import GroupAssignee, ProjectOwnership # Is the issue already assigned to a team or user? key = "assignee_exists:1:%s" % (group.id) assignee_exists = cache.get(key) if assignee_exists is None: assignee_exists = group.assignee_set.exists() # Cache for an hour if it's assigned. We don't need to move that fast. cache.set(key, assignee_exists, 3600 if assignee_exists else 60) if assignee_exists: return owner = ProjectOwnership.get_autoassign_owner(group.project_id, event.data) if owner is not None: GroupAssignee.objects.assign(group, owner)
def build_events_by_actor( project_id: int, events: Iterable[Event], user_ids: Iterable[int]) -> Mapping[ActorTuple, Iterable[Event]]: """ TODO(mgaeta): I know this is inefficient. ProjectOwnership.get_owners is O(n) queries and I'm doing that O(len(events)) times. I "will" create a follow-up PR to address this method's efficiency problem. Just wanted to make as few changes as possible for now. """ events_by_actor: MutableMapping[ActorTuple, Set[Event]] = defaultdict(set) for event in events: actors, __ = ProjectOwnership.get_owners(project_id, event.data) if actors == ProjectOwnership.Everyone: actors = [ActorTuple(user_id, User) for user_id in user_ids] for actor in actors: events_by_actor[actor].add(event) return events_by_actor
def get(self, request, project, event_id): """ Retrieve suggested owners information for an event `````````````````````````````````````````````````` :pparam string project_slug: the slug of the project the event belongs to. :pparam string event_id: the id of the event. :auth: required """ use_snuba = options.get('snuba.events-queries.enabled') event_cls = SnubaEvent if use_snuba else Event event = event_cls.objects.from_event_id(event_id, project.id) if event is None: return Response({'detail': 'Event not found'}, status=404) # populate event data Event.objects.bind_nodes([event], 'data') owners, rules = ProjectOwnership.get_owners(project.id, event.data) # For sake of the API, we don't differentiate between # the implicit "everyone" and no owners if owners == ProjectOwnership.Everyone: owners = [] return Response({ 'owners': serialize( Actor.resolve_many(owners), request.user, ActorSerializer(), ), # TODO(mattrobenolt): We need to change the API here to return # all rules, just keeping this way currently for API compat 'rule': rules[0].matcher if rules else None, 'rules': rules or [], })
def get(self, request, project, event_id): """ Retrieve suggested owners information for an event `````````````````````````````````````````````````` :pparam string project_slug: the slug of the project the event belongs to. :pparam string event_id: the id of the event. :auth: required """ event = eventstore.get_event_by_id(project.id, event_id) if event is None: return Response({"detail": "Event not found"}, status=404) # populate event data if not options.get("eventstore.use-nodestore"): event.bind_node_data() owners, rules = ProjectOwnership.get_owners(project.id, event.data) # For sake of the API, we don't differentiate between # the implicit "everyone" and no owners if owners == ProjectOwnership.Everyone: owners = [] serialized_owners = serialize(Actor.resolve_many(owners), request.user, ActorSerializer()) # Make sure the serialized owners are in the correct order ordered_owners = [] owner_by_id = {(o["id"], o["type"]): o for o in serialized_owners} for o in owners: key = (six.text_type(o.id), "team" if o.type == Team else "user") if owner_by_id.get(key): ordered_owners.append(owner_by_id[key]) return Response({ "owners": ordered_owners, # TODO(mattrobenolt): We need to change the API here to return # all rules, just keeping this way currently for API compat "rule": rules[0].matcher if rules else None, "rules": rules or [], })
def get_owners(project: Project, event: Event | None = None) -> Sequence[Team | User]: """ Given a project and an event, decide which users and teams are the owners. If when checking owners, there is a rule match we only notify the last owner (would-be auto-assignee) unless the organization passes the feature-flag """ if event: owners, _ = ProjectOwnership.get_owners(project.id, event.data) else: owners = ProjectOwnership.Everyone if not owners: outcome = "empty" recipients = list() elif owners == ProjectOwnership.Everyone: outcome = "everyone" recipients = User.objects.filter( id__in=project.member_set.values_list("user", flat=True)) else: outcome = "match" recipients = ActorTuple.resolve_many(owners) # Used to suppress extra notifications to all matched owners, only notify the would-be auto-assignee if not features.has("organizations:notification-all-recipients", project.organization): recipients = recipients[-1:] metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": outcome }, skip_internal=True, ) return recipients
def get(self, request, project, event_id): """ Retrieve suggested owners information for an event `````````````````````````````````````````````````` :pparam string project_slug: the slug of the project the event belongs to. :pparam string event_id: the id of the event. :auth: required """ try: event = Event.objects.get( id=event_id, project_id=project.id, ) except Event.DoesNotExist: return Response({'detail': 'Event not found'}, status=404) # populate event data Event.objects.bind_nodes([event], 'data') owners, matcher = ProjectOwnership.get_owners(project.id, event.data) # For sake of the API, we don't differentiate between # the implicit "everyone" and no owners if owners == ProjectOwnership.Everyone: owners = [] return Response({ 'owners': serialize( Actor.resolve_many(owners), request.user, ActorSerializer(), ), 'rule': matcher, })
def get_send_to(self, project, event=None): """ Returns a list of user IDs for the users that should receive notifications for the provided project. This result may come from cached data. """ if not (project and project.teams.exists()): logger.debug('Tried to send notification to invalid project: %r', project) return [] if event: owners, _ = ProjectOwnership.get_owners(project.id, event.data) if owners != ProjectOwnership.Everyone: if not owners: metrics.incr( 'features.owners.send_to', tags={ 'organization': project.organization_id, 'outcome': 'empty', }, skip_internal=True, ) return [] metrics.incr( 'features.owners.send_to', tags={ 'organization': project.organization_id, 'outcome': 'match', }, skip_internal=True, ) send_to_list = [] teams_to_resolve = [] for owner in owners: if owner.type == User: send_to_list.append(owner.id) else: teams_to_resolve.append(owner.id) # get all users in teams if teams_to_resolve: send_to_list += User.objects.filter( is_active=True, sentry_orgmember_set__organizationmemberteam__team__id__in =teams_to_resolve, ).values_list('id', flat=True) return send_to_list else: metrics.incr( 'features.owners.send_to', tags={ 'organization': project.organization_id, 'outcome': 'everyone', }, skip_internal=True, ) cache_key = '%s:send_to:%s' % (self.get_conf_key(), project.pk) send_to_list = cache.get(cache_key) if send_to_list is None: send_to_list = [s for s in self.get_sendable_users(project) if s] cache.set(cache_key, send_to_list, 60) # 1 minute cache return send_to_list
def get_send_to(self, project, event=None): """ Returns a list of user IDs for the users that should receive notifications for the provided project. This result may come from cached data. """ if not (project and project.teams.exists()): logger.debug('Tried to send notification to invalid project: %r', project) return [] if event: owners, _ = ProjectOwnership.get_owners(project.id, event.data) if owners != ProjectOwnership.Everyone: if not owners: metrics.incr( 'features.owners.send_to', tags={ 'organization': project.organization_id, 'outcome': 'empty', }, skip_internal=True, ) return [] metrics.incr( 'features.owners.send_to', tags={ 'organization': project.organization_id, 'outcome': 'match', }, skip_internal=True, ) send_to_list = [] teams_to_resolve = [] for owner in owners: if owner.type == User: send_to_list.append(owner.id) else: teams_to_resolve.append(owner.id) # get all users in teams if teams_to_resolve: send_to_list += User.objects.filter( is_active=True, sentry_orgmember_set__organizationmemberteam__team__id__in=teams_to_resolve, ).values_list('id', flat=True) return send_to_list else: metrics.incr( 'features.owners.send_to', tags={ 'organization': project.organization_id, 'outcome': 'everyone', }, skip_internal=True, ) cache_key = '%s:send_to:%s' % (self.get_conf_key(), project.pk) send_to_list = cache.get(cache_key) if send_to_list is None: send_to_list = [s for s in self.get_sendable_users(project) if s] cache.set(cache_key, send_to_list, 60) # 1 minute cache return send_to_list
def test_get_owners_default(self): assert ProjectOwnership.get_owners(self.project.id, {}) == (ProjectOwnership.Everyone, None)
def test_get_owners_basic(self): rule_a = Rule( Matcher('path', '*.py'), [ Owner('team', self.team.slug), ]) rule_b = Rule( Matcher('path', 'src/*'), [ Owner('user', self.user.email), ]) ProjectOwnership.objects.create( project_id=self.project.id, schema=dump_schema([rule_a, rule_b]), fallthrough=True, ) # No data matches assert ProjectOwnership.get_owners(self.project.id, {}) == (ProjectOwnership.Everyone, None) # Match only rule_a self.assert_ownership_equals(ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'foo.py', }] } } ), ([Actor(self.team.id, Team)], [rule_a])) # Match only rule_b self.assert_ownership_equals(ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'src/thing.txt', }] } } ), ([Actor(self.user.id, User)], [rule_b])) # Matches both rule_a and rule_b self.assert_ownership_equals(ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'src/foo.py', }] } } ), ([Actor(self.user.id, User), Actor(self.team.id, Team)], [rule_a, rule_b])) assert ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'xxxx', }] } } ) == (ProjectOwnership.Everyone, None) # When fallthrough = False, we don't implicitly assign to Everyone ProjectOwnership.objects.filter( project_id=self.project.id, ).update(fallthrough=False) assert ProjectOwnership.get_owners( self.project.id, { 'sentry.interfaces.Stacktrace': { 'frames': [{ 'filename': 'xxxx', }] } } ) == ([], None)
def tearDown(self): cache.delete(ProjectOwnership.get_cache_key(self.project.id)) super(ProjectOwnershipTestCase, self).tearDown()
def get_send_to(self, project, event=None): """ Returns a list of user IDs for the users that should receive notifications for the provided project. This result may come from cached data. """ if not (project and project.teams.exists()): logger.debug("Tried to send notification to invalid project: %r", project) return [] if event: owners, _ = ProjectOwnership.get_owners(project.id, event.data) if owners != ProjectOwnership.Everyone: if not owners: metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "empty" }, skip_internal=True, ) return [] metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "match" }, skip_internal=True, ) send_to_list = set() teams_to_resolve = set() for owner in owners: if owner.type == User: send_to_list.add(owner.id) else: teams_to_resolve.add(owner.id) # get all users in teams if teams_to_resolve: send_to_list |= set( User.objects.filter( is_active=True, sentry_orgmember_set__organizationmemberteam__team__id__in =teams_to_resolve, ).values_list("id", flat=True)) alert_settings = project.get_member_alert_settings( self.alert_option_key) disabled_users = set( user for user, setting in alert_settings.items() if setting == 0) return send_to_list - disabled_users else: metrics.incr( "features.owners.send_to", tags={ "organization": project.organization_id, "outcome": "everyone" }, skip_internal=True, ) cache_key = "%s:send_to:%s" % (self.get_conf_key(), project.pk) send_to_list = cache.get(cache_key) if send_to_list is None: send_to_list = [s for s in self.get_sendable_users(project) if s] cache.set(cache_key, send_to_list, 60) # 1 minute cache return send_to_list