def resolve_actor(owner, project_id): """ Convert an Owner object into an Actor """ from sentry.api.fields.actor import Actor from sentry.models import User, Team if owner.type == 'user': try: user_id = User.objects.filter( email__iexact=owner.identifier, is_active=True, sentry_orgmember_set__organizationmemberteam__team__projectteam__project_id=project_id, ).values_list('id', flat=True)[0] except IndexError: raise UnknownActor return Actor(user_id, User) if owner.type == 'team': try: team_id = Team.objects.filter( projectteam__project_id=project_id, slug=owner.identifier, ).values_list('id', flat=True)[0] except IndexError: return UnknownActor return Actor(team_id, Team) raise TypeError('Unknown actor type: %r' % owner.type)
def test_team_actors_to_user_ids(self): team1 = self.create_team() team2 = self.create_team() team3 = self.create_team() # team with no active members users = [self.create_user() for i in range(0, 8)] self.create_member(user=users[0], organization=self.organization, teams=[team1]) self.create_member(user=users[1], organization=self.organization, teams=[team1]) self.create_member(user=users[2], organization=self.organization, teams=[team1]) self.create_member(user=users[3], organization=self.organization, teams=[team1, team2]) self.create_member(user=users[4], organization=self.organization, teams=[team2, self.team]) self.create_member(user=users[5], organization=self.organization, teams=[team2]) # Inactive member member6 = self.create_member( user=users[6], organization=self.organization, teams=[ team2, team3]) team_member6 = OrganizationMemberTeam.objects.filter(organizationmember_id=member6.id) for team_member in team_member6: team_member.update(is_active=False) # Member without teams self.create_member(user=users[7], organization=self.organization, teams=[]) team_actors = [Actor(team1.id, Team), Actor(team2.id, Team), Actor(team3.id, Team)] user_ids = [user.id for user in users] assert team_actors_to_user_ids(team_actors, user_ids) == { team1.id: set([users[0].id, users[1].id, users[2].id, users[3].id]), team2.id: set([users[3].id, users[4].id, users[5].id]), }
def test_basic(self): owners = [ Owner("user", self.user.email), Owner("team", self.team.slug) ] assert resolve_actors(owners, self.project.id) == { owners[0]: Actor(self.user.id, User), owners[1]: Actor(self.team.id, Team), }
def test_build_events_by_actor(self): events = self.team1_events + self.team2_events + self.user4_events events_by_actor = { Actor(self.team1.id, Team): set(self.team1_events), Actor(self.team2.id, Team): set(self.team2_events), Actor(self.user3.id, User): set(self.team1_events), Actor(self.user4.id, User): set(self.user4_events), } assert build_events_by_actor(self.project.id, events, self.user_ids) == events_by_actor
def test_users(self): actor = Actor(self.user.id, User) result = extract_user_ids_from_mentions(self.organization.id, [actor]) assert result['users'] == set([self.user.id]) assert result['team_users'] == set() other_user = self.create_user() result = extract_user_ids_from_mentions( self.organization.id, [actor, Actor(other_user.id, User)]) assert result['users'] == set([self.user.id, other_user.id]) assert result['team_users'] == set()
def test_convert_actors_to_user_set(self): user1 = self.create_user() user2 = self.create_user() user3 = self.create_user() user4 = self.create_user() team1 = self.create_team() team2 = self.create_team() self.create_member(user=user1, organization=self.organization, teams=[team1]) self.create_member(user=user2, organization=self.organization, teams=[team2]) self.create_member(user=user3, organization=self.organization, teams=[team1, team2]) self.create_member(user=user4, organization=self.organization, teams=[]) team1_events = set([ self.create_event(self.project.id), self.create_event(self.project.id), self.create_event(self.project.id), self.create_event(self.project.id), ]) team2_events = set([ self.create_event(self.project.id), self.create_event(self.project.id), self.create_event(self.project.id), self.create_event(self.project.id), ]) user4_events = set([ self.create_event(self.project.id), self.create_event(self.project.id) ]) events_by_actor = { Actor(team1.id, Team): team1_events, Actor(team2.id, Team): team2_events, Actor(user3.id, User): team1_events.union(team2_events), Actor(user4.id, User): user4_events, } user_by_events = { user1.id: team1_events, user2.id: team2_events, user3.id: team1_events.union(team2_events), user4.id: user4_events, } assert convert_actors_to_users(events_by_actor, user_by_events.keys()) == user_by_events
def resolve_actors(owners, project_id): """ Convert a list of Owner objects into a dictionary of {Owner: Actor} pairs. Actors not identified are returned as None. """ from sentry.api.fields.actor import Actor from sentry.models import User, Team if not owners: return {} users, teams = [], [] owners_lookup = {} for owner in owners: # teams aren't technical case insensitive, but teams also # aren't allowed to have non-lowercase in slugs, so # this kinda works itself out correctly since they won't match owners_lookup[(owner.type, owner.identifier.lower())] = owner if owner.type == 'user': users.append(owner) elif owner.type == 'team': teams.append(owner) actors = {} if users: actors.update({ ('user', email.lower()): Actor(u_id, User) for u_id, email in User.objects.filter( reduce( operator.or_, [Q(emails__email__iexact=o.identifier) for o in users] ), # We don't require verified emails # emails__is_verified=True, is_active=True, sentry_orgmember_set__organizationmemberteam__team__projectteam__project_id=project_id, ).distinct().values_list('id', 'emails__email') }) if teams: actors.update({ ('team', slug): Actor(t_id, Team) for t_id, slug in Team.objects.filter( slug__in=[o.identifier for o in teams], projectteam__project_id=project_id, ).values_list('id', 'slug') }) return { o: actors.get((o.type, o.identifier.lower())) for o in owners }
def build_assigned_text(group, identity, assignee): actor = Actor.from_actor_id(assignee) try: assigned_actor = actor.resolve() except actor.type.DoesNotExist: return if actor.type == Team: assignee_text = u'#{}'.format(assigned_actor.slug) elif actor.type == User: try: assignee_ident = Identity.objects.get( user=assigned_actor, idp__type='slack', idp__external_id=identity.idp.external_id, ) assignee_text = u'<@{}>'.format(assignee_ident.external_id) except Identity.DoesNotExist: assignee_text = assigned_actor.get_display_name() else: raise NotImplementedError return u'*Issue assigned to {assignee_text} by <@{user_id}>*'.format( assignee_text=assignee_text, user_id=identity.external_id, )
def get_autoassign_owners(cls, project_id, data, limit=2): """ Get the auto-assign owner for a project if there are any. Returns a tuple of (auto_assignment_enabled, list_of_owners). """ with metrics.timer("projectownership.get_autoassign_owners"): ownership = cls.get_ownership_cached(project_id) if not ownership: return False, [] rules = cls._matching_ownership_rules(ownership, project_id, data) if not rules: return ownership.auto_assignment, [] # We want the last matching rule to take the most precedence. owners = [owner for rule in rules for owner in rule.owners] owners.reverse() actors = { key: val for key, val in resolve_actors({owner for owner in owners}, project_id).items() if val } actors = [actors[owner] for owner in owners if owner in actors][:limit] # Can happen if the ownership rule references a user/team that no longer # is assigned to the project or has been removed from the org. if not actors: return ownership.auto_assignment, [] from sentry.api.fields.actor import Actor return ownership.auto_assignment, Actor.resolve_many(actors)
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_teams(self): member_user = self.create_user() self.create_member(user=member_user, organization=self.organization, role="member", teams=[self.team]) actor = Actor(self.team.id, Team) result = extract_user_ids_from_mentions(self.organization.id, [actor]) assert result["users"] == set() assert result["team_users"] == set([self.user.id, member_user.id]) # Explicitly mentioned users shouldn't be included in team_users result = extract_user_ids_from_mentions( self.organization.id, [Actor(member_user.id, User), actor]) assert result["users"] == set([member_user.id]) assert result["team_users"] == set([self.user.id])
def extract_user_ids_from_mentions(organization_id, mentions): """ Extracts user ids from a set of mentions. Mentions should be a list of `Actor` instances. Returns a dictionary with 'users' and 'team_users' keys. 'users' is the user ids for all explicitly mentioned users, and 'team_users' is all user ids from explicitly mentioned teams, excluding any already mentioned users. """ actors = Actor.resolve_many(mentions) actor_mentions = seperate_resolved_actors(actors) mentioned_team_users = list( User.objects.get_from_teams( organization_id, actor_mentions['teams'], ).exclude(id__in={u.id for u in actor_mentions['users']}).values_list( 'id', flat=True, ) ) return { 'users': set([user.id for user in actor_mentions['users']]), 'team_users': set(mentioned_team_users), }
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 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 test_team(self): data = { 'actor_field': "team:1", } serializer = DummySerializer(data=data) assert serializer.is_valid() assert serializer.object == {'actor_field': Actor(id=1, type=Team)}
def test_legacy_user_fallback(self): data = { 'actor_field': "1", } serializer = DummySerializer(data=data) assert serializer.is_valid() assert serializer.object == {'actor_field': Actor(id=1, type=User)}
def test_simple(self): data = { 'actor_field': "user:1", } serializer = DummySerializer(data=data) assert serializer.is_valid() assert serializer.object == {'actor_field': Actor(id=1, type=User)}
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.user.id, User), Actor(self.team.id, Team)], [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 ProjectOwnership.objects.filter(project_id=self.project.id).update(fallthrough=False) assert ProjectOwnership.get_owners( self.project.id, {"stacktrace": {"frames": [{"filename": "xxxx"}]}} ) == ([], None)
def resolve_actors(owners, project_id): """ Convert a list of Owner objects into a dictionary of {Owner: Actor} pairs. Actors not identified are returned as None. """ from sentry.api.fields.actor import Actor from sentry.ownership.grammar import Owner from sentry.models import User, Team if not owners: return {} users, teams = [], [] owners_to_actors = {o: None for o in owners} for owner in owners: if owner.type == 'user': users.append(owner) elif owner.type == 'team': teams.append(owner) actors = [] if users: actors.extend([( 'user', email, Actor(u_id, User) ) for u_id, email in User.objects.filter( reduce(operator.or_, [Q(email__iexact=o.identifier) for o in users]), is_active=True, sentry_orgmember_set__organizationmemberteam__team__projectteam__project_id =project_id, ).values_list('id', 'email')]) if teams: actors.extend([('team', slug, Actor(t_id, Team)) for t_id, slug in Team.objects.filter( slug__in=[o.identifier for o in teams], projectteam__project_id=project_id, ).values_list('id', 'slug')]) for type, identifier, actor in actors: owners_to_actors[Owner(type, identifier)] = actor return owners_to_actors
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 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(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 test_teams(self): # Normal team owner1 = Owner('team', self.team.slug) actor1 = Actor(self.team.id, Team) # Team that doesn't exist owner2 = Owner('team', 'nope') actor2 = None # A team that's not ours otherteam = Team.objects.exclude(projectteam__project_id=self.project.id)[0] owner3 = Owner('team', otherteam.slug) actor3 = None assert resolve_actors([owner1, owner2, owner3], self.project.id) == { owner1: actor1, owner2: actor2, owner3: actor3, }
def test_users(self): # Normal user owner1 = Owner("user", self.user.email) actor1 = Actor(self.user.id, User) # An extra secondary email email1 = self.create_useremail(self.user, None, is_verified=True).email owner2 = Owner("user", email1) actor2 = actor1 # They map to the same user since it's just a secondary email # Another secondary email, that isn't verified email2 = self.create_useremail(self.user, None, is_verified=False).email owner3 = Owner("user", email2) # Intentionally allow unverified emails # actor3 = None actor3 = actor1 # An entirely unknown user owner4 = Owner("user", "nope") actor4 = None # A user that doesn't belong with us otheruser = self.create_user() owner5 = Owner("user", otheruser.email) actor5 = None # Case-insensitive for user owner6 = Owner("user", self.user.email.upper()) actor6 = actor1 assert resolve_actors([owner1, owner2, owner3, owner4, owner5, owner6], self.project.id) == { owner1: actor1, owner2: actor2, owner3: actor3, owner4: actor4, owner5: actor5, owner6: actor6, }
def build_assigned_text(group, identity, assignee): actor = Actor.from_actor_identifier(assignee) try: assigned_actor = actor.resolve() except actor.type.DoesNotExist: return if actor.type == Team: assignee_text = f"#{assigned_actor.slug}" elif actor.type == User: try: assignee_ident = Identity.objects.get( user=assigned_actor, idp__type="slack", idp__external_id=identity.idp.external_id) assignee_text = f"<@{assignee_ident.external_id}>" except Identity.DoesNotExist: assignee_text = assigned_actor.get_display_name() else: raise NotImplementedError return f"*Issue assigned to {assignee_text} by <@{identity.external_id}>*"
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_attrs(self, item_list, user): from sentry.plugins.base import plugins from sentry.integrations import IntegrationFeatures from sentry.models import PlatformExternalIssue GroupMeta.objects.populate_cache(item_list) attach_foreignkey(item_list, Group.project) if user.is_authenticated() and item_list: bookmarks = set( GroupBookmark.objects.filter(user=user, group__in=item_list).values_list( "group_id", flat=True ) ) seen_groups = dict( GroupSeen.objects.filter(user=user, group__in=item_list).values_list( "group_id", "last_seen" ) ) subscriptions = self._get_subscriptions(item_list, user) else: bookmarks = set() seen_groups = {} subscriptions = defaultdict(lambda: (False, None)) assignees = { a.group_id: a.assigned_actor() for a in GroupAssignee.objects.filter(group__in=item_list) } resolved_assignees = Actor.resolve_dict(assignees) ignore_items = {g.group_id: g for g in GroupSnooze.objects.filter(group__in=item_list)} resolved_item_list = [i for i in item_list if i.status == GroupStatus.RESOLVED] if resolved_item_list: release_resolutions = { i[0]: i[1:] for i in GroupResolution.objects.filter(group__in=resolved_item_list).values_list( "group", "type", "release__version", "actor_id" ) } # due to our laziness, and django's inability to do a reasonable join here # we end up with two queries commit_results = list( Commit.objects.extra( select={"group_id": "sentry_grouplink.group_id"}, tables=["sentry_grouplink"], where=[ "sentry_grouplink.linked_id = sentry_commit.id", "sentry_grouplink.group_id IN ({})".format( ", ".join(six.text_type(i.id) for i in resolved_item_list) ), "sentry_grouplink.linked_type = %s", "sentry_grouplink.relationship = %s", ], params=[int(GroupLink.LinkedType.commit), int(GroupLink.Relationship.resolves)], ) ) commit_resolutions = { i.group_id: d for i, d in zip(commit_results, serialize(commit_results, user)) } else: release_resolutions = {} commit_resolutions = {} actor_ids = set(r[-1] for r in six.itervalues(release_resolutions)) actor_ids.update(r.actor_id for r in six.itervalues(ignore_items)) if actor_ids: users = list(User.objects.filter(id__in=actor_ids, is_active=True)) actors = {u.id: d for u, d in zip(users, serialize(users, user))} else: actors = {} share_ids = dict( GroupShare.objects.filter(group__in=item_list).values_list("group_id", "uuid") ) result = {} seen_stats = self._get_seen_stats(item_list, user) annotations_by_group_id = defaultdict(list) organization_id_list = list(set(item.project.organization_id for item in item_list)) # if no groups, then we can't proceed but this seems to be a valid use case if not item_list: return {} if len(organization_id_list) > 1: # this should never happen but if it does we should know about it logger.warn( "Found multiple organizations for groups: %s, with orgs: %s" % ([item.id for item in item_list], organization_id_list) ) # should only have 1 org at this point organization_id = organization_id_list[0] organization = Organization.objects.get_from_cache(id=organization_id) has_unhandled_flag = features.has( "organizations:unhandled-issue-flag", organization, actor=user ) # find all the integration installs that have issue tracking for integration in Integration.objects.filter(organizations=organization_id): if not ( integration.has_feature(IntegrationFeatures.ISSUE_BASIC) or integration.has_feature(IntegrationFeatures.ISSUE_SYNC) ): continue install = integration.get_installation(organization_id) local_annotations_by_group_id = ( safe_execute( install.get_annotations_for_group_list, group_list=item_list, _with_transaction=False, ) or {} ) merge_list_dictionaries(annotations_by_group_id, local_annotations_by_group_id) # find the external issues for sentry apps and add them in local_annotations_by_group_id = ( safe_execute( PlatformExternalIssue.get_annotations_for_group_list, group_list=item_list, _with_transaction=False, ) or {} ) merge_list_dictionaries(annotations_by_group_id, local_annotations_by_group_id) snuba_stats = {} if has_unhandled_flag: snuba_stats = self._get_group_snuba_stats(item_list, seen_stats) for item in item_list: active_date = item.active_at or item.first_seen annotations = [] annotations.extend(annotations_by_group_id[item.id]) # add the annotations for plugins # note that the model GroupMeta where all the information is stored is already cached at the top of this function # so these for loops doesn't make a bunch of queries for plugin in plugins.for_project(project=item.project, version=1): safe_execute(plugin.tags, None, item, annotations, _with_transaction=False) for plugin in plugins.for_project(project=item.project, version=2): annotations.extend( safe_execute(plugin.get_annotations, group=item, _with_transaction=False) or () ) resolution_actor = None resolution_type = None resolution = release_resolutions.get(item.id) if resolution: resolution_type = "release" resolution_actor = actors.get(resolution[-1]) if not resolution: resolution = commit_resolutions.get(item.id) if resolution: resolution_type = "commit" ignore_item = ignore_items.get(item.id) if ignore_item: ignore_actor = actors.get(ignore_item.actor_id) else: ignore_actor = None result[item] = { "id": item.id, "assigned_to": resolved_assignees.get(item.id), "is_bookmarked": item.id in bookmarks, "subscription": subscriptions[item.id], "has_seen": seen_groups.get(item.id, active_date) > active_date, "annotations": annotations, "ignore_until": ignore_item, "ignore_actor": ignore_actor, "resolution": resolution, "resolution_type": resolution_type, "resolution_actor": resolution_actor, "share_id": share_ids.get(item.id), } if has_unhandled_flag: result[item]["is_unhandled"] = bool(snuba_stats.get(item.id, {}).get("unhandled")) if seen_stats: result[item].update(seen_stats.get(item, {})) return result
def post(self, request, group): serializer = NoteSerializer(data=request.DATA, context={'group': group}) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) data = dict(serializer.object) mentions = data.pop('mentions', []) if Activity.objects.filter(group=group, type=Activity.NOTE, user=request.user, data=data, datetime__gte=timezone.now() - timedelta(hours=1)).exists(): return Response( '{"detail": "You have already posted that comment."}', status=status.HTTP_400_BAD_REQUEST) GroupSubscription.objects.subscribe( group=group, user=request.user, reason=GroupSubscriptionReason.comment, ) actors = Actor.resolve_many(mentions) actor_mentions = seperate_resolved_actors(actors) for user in actor_mentions.get('users'): GroupSubscription.objects.subscribe( group=group, user=user, reason=GroupSubscriptionReason.mentioned, ) mentioned_teams = actor_mentions.get('teams') mentioned_team_users = list( User.objects.filter( sentry_orgmember_set__organization_id=group.project. organization_id, sentry_orgmember_set__organizationmemberteam__team__in= mentioned_teams, sentry_orgmember_set__organizationmemberteam__is_active=True, is_active=True, ).exclude( id__in={u.id for u in actor_mentions.get('users')}).values_list( 'id', flat=True)) GroupSubscription.objects.bulk_subscribe( group=group, user_ids=mentioned_team_users, reason=GroupSubscriptionReason.team_mentioned, ) activity = Activity.objects.create( group=group, project=group.project, type=Activity.NOTE, user=extract_lazy_object(request.user), data=data, ) activity.send_notification() # sync Sentry comments to external issues if features.has('organizations:internal-catchall', group.organization, actor=request.user): external_issue_ids = GroupLink.objects.filter( project_id=group.project_id, group_id=group.id, linked_type=GroupLink.LinkedType.issue, ).values_list('linked_id', flat=True) for external_issue_id in external_issue_ids: post_comment.apply_async(kwargs={ 'external_issue_id': external_issue_id, 'data': data, }) return Response(serialize(activity, request.user), status=201)
def get_attrs(self, item_list, user): from sentry.plugins.base import plugins GroupMeta.objects.populate_cache(item_list) attach_foreignkey(item_list, Group.project) if user.is_authenticated() and item_list: bookmarks = set( GroupBookmark.objects.filter( user=user, group__in=item_list).values_list("group_id", flat=True)) seen_groups = dict( GroupSeen.objects.filter(user=user, group__in=item_list).values_list( "group_id", "last_seen")) subscriptions = self._get_subscriptions(item_list, user) else: bookmarks = set() seen_groups = {} subscriptions = defaultdict(lambda: (False, None)) assignees = { a.group_id: a.assigned_actor() for a in GroupAssignee.objects.filter(group__in=item_list) } resolved_assignees = Actor.resolve_dict(assignees) ignore_items = { g.group_id: g for g in GroupSnooze.objects.filter(group__in=item_list) } resolved_item_list = [ i for i in item_list if i.status == GroupStatus.RESOLVED ] if resolved_item_list: release_resolutions = { i[0]: i[1:] for i in GroupResolution.objects.filter( group__in=resolved_item_list).values_list( "group", "type", "release__version", "actor_id") } # due to our laziness, and django's inability to do a reasonable join here # we end up with two queries commit_results = list( Commit.objects.extra( select={"group_id": "sentry_grouplink.group_id"}, tables=["sentry_grouplink"], where=[ "sentry_grouplink.linked_id = sentry_commit.id", "sentry_grouplink.group_id IN ({})".format(", ".join( six.text_type(i.id) for i in resolved_item_list)), "sentry_grouplink.linked_type = %s", "sentry_grouplink.relationship = %s", ], params=[ int(GroupLink.LinkedType.commit), int(GroupLink.Relationship.resolves) ], )) commit_resolutions = { i.group_id: d for i, d in zip(commit_results, serialize( commit_results, user)) } else: release_resolutions = {} commit_resolutions = {} actor_ids = set(r[-1] for r in six.itervalues(release_resolutions)) actor_ids.update(r.actor_id for r in six.itervalues(ignore_items)) if actor_ids: users = list(User.objects.filter(id__in=actor_ids, is_active=True)) actors = {u.id: d for u, d in zip(users, serialize(users, user))} else: actors = {} share_ids = dict( GroupShare.objects.filter(group__in=item_list).values_list( "group_id", "uuid")) result = {} seen_stats = self._get_seen_stats(item_list, user) for item in item_list: active_date = item.active_at or item.first_seen annotations = [] for plugin in plugins.for_project(project=item.project, version=1): safe_execute(plugin.tags, None, item, annotations, _with_transaction=False) for plugin in plugins.for_project(project=item.project, version=2): annotations.extend( safe_execute(plugin.get_annotations, group=item, _with_transaction=False) or ()) from sentry.integrations import IntegrationFeatures for integration in Integration.objects.filter( organizations=item.project.organization_id): if not (integration.has_feature( IntegrationFeatures.ISSUE_BASIC) or integration.has_feature( IntegrationFeatures.ISSUE_SYNC)): continue install = integration.get_installation( item.project.organization_id) annotations.extend( safe_execute(install.get_annotations, group=item, _with_transaction=False) or ()) from sentry.models import PlatformExternalIssue annotations.extend( safe_execute(PlatformExternalIssue.get_annotations, group=item, _with_transaction=False) or ()) resolution_actor = None resolution_type = None resolution = release_resolutions.get(item.id) if resolution: resolution_type = "release" resolution_actor = actors.get(resolution[-1]) if not resolution: resolution = commit_resolutions.get(item.id) if resolution: resolution_type = "commit" ignore_item = ignore_items.get(item.id) if ignore_item: ignore_actor = actors.get(ignore_item.actor_id) else: ignore_actor = None result[item] = { "assigned_to": resolved_assignees.get(item.id), "is_bookmarked": item.id in bookmarks, "subscription": subscriptions[item.id], "has_seen": seen_groups.get(item.id, active_date) > active_date, "annotations": annotations, "ignore_until": ignore_item, "ignore_actor": ignore_actor, "resolution": resolution, "resolution_type": resolution_type, "resolution_actor": resolution_actor, "share_id": share_ids.get(item.id), } result[item].update(seen_stats.get(item, {})) return result
def assigned_actor(self): from sentry.api.fields.actor import Actor return Actor.from_actor_id(self.assigned_actor_id())
def owner(self): from sentry.api.fields.actor import Actor return Actor.from_actor_identifier(self.owner_id())
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 get_attrs(self, item_list, user): from sentry.plugins import plugins GroupMeta.objects.populate_cache(item_list) attach_foreignkey(item_list, Group.project) if user.is_authenticated() and item_list: bookmarks = set( GroupBookmark.objects.filter( user=user, group__in=item_list, ).values_list('group_id', flat=True) ) seen_groups = dict( GroupSeen.objects.filter( user=user, group__in=item_list, ).values_list('group_id', 'last_seen') ) subscriptions = self._get_subscriptions(item_list, user) else: bookmarks = set() seen_groups = {} subscriptions = defaultdict(lambda: (False, None)) assignees = { a.group_id: a.assigned_actor() for a in GroupAssignee.objects.filter( group__in=item_list, ) } resolved_assignees = Actor.resolve_dict(assignees) ignore_items = {g.group_id: g for g in GroupSnooze.objects.filter( group__in=item_list, )} resolved_item_list = [i for i in item_list if i.status == GroupStatus.RESOLVED] if resolved_item_list: release_resolutions = { i[0]: i[1:] for i in GroupResolution.objects.filter( group__in=resolved_item_list, ).values_list( 'group', 'type', 'release__version', 'actor_id', ) } # due to our laziness, and django's inability to do a reasonable join here # we end up with two queries commit_results = list(Commit.objects.extra( select={ 'group_id': 'sentry_grouplink.group_id', }, tables=['sentry_grouplink'], where=[ 'sentry_grouplink.linked_id = sentry_commit.id', 'sentry_grouplink.group_id IN ({})'.format( ', '.join(six.text_type(i.id) for i in resolved_item_list)), 'sentry_grouplink.linked_type = %s', 'sentry_grouplink.relationship = %s', ], params=[ int(GroupLink.LinkedType.commit), int(GroupLink.Relationship.resolves), ] )) commit_resolutions = { i.group_id: d for i, d in itertools.izip(commit_results, serialize(commit_results, user)) } else: release_resolutions = {} commit_resolutions = {} actor_ids = set(r[-1] for r in six.itervalues(release_resolutions)) actor_ids.update(r.actor_id for r in six.itervalues(ignore_items)) if actor_ids: users = list(User.objects.filter( id__in=actor_ids, is_active=True, )) actors = {u.id: d for u, d in itertools.izip(users, serialize(users, user))} else: actors = {} share_ids = dict(GroupShare.objects.filter( group__in=item_list, ).values_list('group_id', 'uuid')) result = {} seen_stats = self._get_seen_stats(item_list, user) for item in item_list: active_date = item.active_at or item.first_seen annotations = [] for plugin in plugins.for_project(project=item.project, version=1): safe_execute(plugin.tags, None, item, annotations, _with_transaction=False) for plugin in plugins.for_project(project=item.project, version=2): annotations.extend( safe_execute(plugin.get_annotations, group=item, _with_transaction=False) or () ) from sentry.integrations import IntegrationFeatures for integration in Integration.objects.filter( organizations=item.project.organization_id): if not (integration.has_feature(IntegrationFeatures.ISSUE_BASIC) or integration.has_feature( IntegrationFeatures.ISSUE_SYNC)): continue install = integration.get_installation(item.project.organization_id) annotations.extend( safe_execute(install.get_annotations, group=item, _with_transaction=False) or () ) resolution_actor = None resolution_type = None resolution = release_resolutions.get(item.id) if resolution: resolution_type = 'release' resolution_actor = actors.get(resolution[-1]) if not resolution: resolution = commit_resolutions.get(item.id) if resolution: resolution_type = 'commit' ignore_item = ignore_items.get(item.id) if ignore_item: ignore_actor = actors.get(ignore_item.actor_id) else: ignore_actor = None result[item] = { 'assigned_to': resolved_assignees.get(item.id), 'is_bookmarked': item.id in bookmarks, 'subscription': subscriptions[item.id], 'has_seen': seen_groups.get(item.id, active_date) > active_date, 'annotations': annotations, 'ignore_until': ignore_item, 'ignore_actor': ignore_actor, 'resolution': resolution, 'resolution_type': resolution_type, 'resolution_actor': resolution_actor, 'share_id': share_ids.get(item.id), } result[item].update(seen_stats.get(item, {})) return result
def post(self, request, group): serializer = NoteSerializer(data=request.DATA, context={'group': group}) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) data = dict(serializer.object) mentions = data.pop('mentions', []) if Activity.objects.filter( group=group, type=Activity.NOTE, user=request.user, data=data, datetime__gte=timezone.now() - timedelta(hours=1) ).exists(): return Response( '{"detail": "You have already posted that comment."}', status=status.HTTP_400_BAD_REQUEST ) GroupSubscription.objects.subscribe( group=group, user=request.user, reason=GroupSubscriptionReason.comment, ) actors = Actor.resolve_many(mentions) actor_mentions = seperate_resolved_actors(actors) for user in actor_mentions.get('users'): GroupSubscription.objects.subscribe( group=group, user=user, reason=GroupSubscriptionReason.mentioned, ) mentioned_teams = actor_mentions.get('teams') mentioned_team_users = list( User.objects.filter( sentry_orgmember_set__organization_id=group.project.organization_id, sentry_orgmember_set__organizationmemberteam__team__in=mentioned_teams, sentry_orgmember_set__organizationmemberteam__is_active=True, is_active=True, ).exclude(id__in={u.id for u in actor_mentions.get('users')}) .values_list('id', flat=True) ) GroupSubscription.objects.bulk_subscribe( group=group, user_ids=mentioned_team_users, reason=GroupSubscriptionReason.team_mentioned, ) activity = Activity.objects.create( group=group, project=group.project, type=Activity.NOTE, user=extract_lazy_object(request.user), data=data, ) activity.send_notification() return Response(serialize(activity, request.user), status=201)