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)
Example #5
0
    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)
Example #6
0
 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)
Example #7
0
    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],
            ),
        )
Example #9
0
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")
Example #10
0
    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(),
            ))
Example #11
0
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")
Example #12
0
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
Example #13
0
    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 [],
        })
Example #14
0
    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)
Example #15
0
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
Example #16
0
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)
Example #17
0
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)
Example #18
0
    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()
        }
Example #19
0
    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]),
        )
Example #20
0
    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
Example #21
0
    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])
Example #23
0
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
Example #24
0
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
Example #25
0
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)
Example #26
0
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
Example #27
0
    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 [],
        })
Example #28
0
    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 [],
        })
Example #29
0
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
Example #30
0
    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 [],
        })
Example #31
0
    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,
        })
Example #32
0
    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
Example #33
0
    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 test_get_owners_default(self):
     assert ProjectOwnership.get_owners(self.project.id,
                                        {}) == (ProjectOwnership.Everyone,
                                                None)
    def tearDown(self):
        cache.delete(ProjectOwnership.get_cache_key(self.project.id))

        super(ProjectOwnershipTestCase, self).tearDown()
Example #38
0
    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