Example #1
0
class DashboardWidget(Model):
    """
    A dashboard widget.
    """

    __include_in_export__ = True

    dashboard = FlexibleForeignKey("sentry.Dashboard")
    order = BoundedPositiveIntegerField()
    title = models.CharField(max_length=255)
    interval = models.CharField(max_length=10, null=True)
    display_type = BoundedPositiveIntegerField(
        choices=DashboardWidgetDisplayTypes.as_choices())
    date_added = models.DateTimeField(default=timezone.now)
    widget_type = BoundedPositiveIntegerField(
        choices=DashboardWidgetTypes.as_choices(), null=True)
    limit = models.IntegerField(null=True)
    detail = JSONField(null=True)

    class Meta:
        app_label = "sentry"
        db_table = "sentry_dashboardwidget"
        unique_together = (("dashboard", "order"), )

    __repr__ = sane_repr("dashboard", "title")
Example #2
0
class DiscoverSavedQuery(Model):
    """
    A saved Discover query
    """

    __include_in_export__ = False

    projects = models.ManyToManyField("sentry.Project",
                                      through=DiscoverSavedQueryProject)
    organization = FlexibleForeignKey("sentry.Organization")
    created_by = FlexibleForeignKey("sentry.User",
                                    null=True,
                                    on_delete=models.SET_NULL)
    name = models.CharField(max_length=255)
    query = JSONField()
    version = models.IntegerField(null=True)
    date_created = models.DateTimeField(auto_now_add=True)
    date_updated = models.DateTimeField(auto_now=True)

    class Meta:
        app_label = "sentry"
        db_table = "sentry_discoversavedquery"

    __repr__ = sane_repr("organization_id", "created_by", "name")

    def set_projects(self, project_ids):
        with transaction.atomic():
            DiscoverSavedQueryProject.objects.filter(
                discover_saved_query=self).exclude(
                    project__in=project_ids).delete()

            existing_project_ids = DiscoverSavedQueryProject.objects.filter(
                discover_saved_query=self).values_list("project", flat=True)

            new_project_ids = list(
                set(project_ids) - set(existing_project_ids))

            DiscoverSavedQueryProject.objects.bulk_create([
                DiscoverSavedQueryProject(project_id=project_id,
                                          discover_saved_query=self)
                for project_id in new_project_ids
            ])
Example #3
0
class ProjectOwnership(Model):
    __core__ = True

    project = FlexibleForeignKey("sentry.Project", unique=True)
    raw = models.TextField(null=True)
    schema = JSONField(null=True)
    fallthrough = models.BooleanField(default=True)
    auto_assignment = models.BooleanField(default=False)
    date_created = models.DateTimeField(default=timezone.now)
    last_updated = models.DateTimeField(default=timezone.now)
    is_active = models.BooleanField(default=True)

    # An object to indicate ownership is implicitly everyone
    Everyone = object()

    class Meta:
        app_label = "sentry"
        db_table = "sentry_projectownership"

    __repr__ = sane_repr("project_id", "is_active")

    @classmethod
    def get_cache_key(self, project_id):
        return "projectownership_project_id:1:{}".format(project_id)

    @classmethod
    def get_ownership_cached(cls, project_id):
        """
        Cached read access to projectownership.

        This method implements a negative cache which saves us
        a pile of read queries in post_processing as most projects
        don't have ownership rules.

        See the post_save and post_delete signals below for additional
        cache updates.
        """
        cache_key = cls.get_cache_key(project_id)
        ownership = cache.get(cache_key)
        if ownership is None:
            try:
                ownership = cls.objects.get(project_id=project_id)
            except cls.DoesNotExist:
                ownership = False
            cache.set(cache_key, ownership, READ_CACHE_DURATION)
        return ownership or None

    @classmethod
    def get_owners(cls, project_id, data):
        """
        For a given project_id, and event data blob.

        If Everyone is returned, this means we implicitly are
        falling through our rules and everyone is responsible.

        If an empty list is returned, this means there are explicitly
        no owners.
        """
        ownership = cls.get_ownership_cached(project_id)
        if not ownership:
            ownership = cls(project_id=project_id)

        rules = cls._matching_ownership_rules(ownership, project_id, data)
        if not rules:
            return cls.Everyone if ownership.fallthrough else [], None

        owners = {o for rule in rules for o in rule.owners}
        owners_to_actors = resolve_actors(owners, project_id)
        ordered_actors = []
        for rule in rules:
            for o in rule.owners:
                if o in owners and owners_to_actors.get(o) is not None:
                    ordered_actors.append(owners_to_actors[o])
                    owners.remove(o)

        return ordered_actors, rules

    @classmethod
    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, []

            owners = []
            # Automatic assignment prefers the owner with the longest
            # matching pattern as the match is more specific.
            for rule in rules:
                candidate = len(rule.matcher.pattern)
                for i, owner in enumerate(rule.owners):
                    owners.append((candidate, -i, owner))

            owners.sort(reverse=True)
            actors = {
                key: val
                for key, val in resolve_actors(
                    set([owner[2] for owner in owners]), project_id).items()
                if val
            }
            actors = [
                actors[owner[2]] for owner in owners if owner[2] 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, []

            return ownership.auto_assignment, actors[0].resolve_many(actors)

    @classmethod
    def _matching_ownership_rules(cls, ownership, project_id, data):
        rules = []
        if ownership.schema is not None:
            for rule in load_schema(ownership.schema):
                if rule.test(data):
                    rules.append(rule)

        return rules
Example #4
0
class ProjectOwnership(Model):
    __core__ = True

    project = FlexibleForeignKey('sentry.Project', unique=True)
    raw = models.TextField(null=True)
    schema = JSONField(null=True)
    fallthrough = models.BooleanField(default=True)
    auto_assignment = models.BooleanField(default=False)
    date_created = models.DateTimeField(default=timezone.now)
    last_updated = models.DateTimeField(default=timezone.now)
    is_active = models.BooleanField(default=True)

    # An object to indicate ownership is implicitly everyone
    Everyone = object()

    class Meta:
        app_label = 'sentry'
        db_table = 'sentry_projectownership'

    __repr__ = sane_repr('project_id', 'is_active')

    @classmethod
    def get_owners(cls, project_id, data):
        """
        For a given project_id, and event data blob.

        If Everyone is returned, this means we implicitly are
        falling through our rules and everyone is responsible.

        If an empty list is returned, this means there are explicitly
        no owners.
        """
        try:
            ownership = cls.objects.get(project_id=project_id)
        except cls.DoesNotExist:
            ownership = cls(
                project_id=project_id,
            )

        rules = cls._matching_ownership_rules(ownership, project_id, data)
        if not rules:
            return cls.Everyone if ownership.fallthrough else [], None

        owners = {o for rule in rules for o in rule.owners}
        return filter(None, resolve_actors(owners, project_id).values()), rules

    @classmethod
    def get_autoassign_owner(cls, project_id, data):
        """
        Get the auto-assign owner for a project if there are any.

        Will return None if there are no owners, or a list of owners.
        """
        try:
            ownership = cls.objects.get(project_id=project_id)
        except cls.DoesNotExist:
            return None
        if not ownership.auto_assignment:
            return None

        rules = cls._matching_ownership_rules(ownership, project_id, data)
        if not rules:
            return None

        score = 0
        owners = None
        # Automatic assignment prefers the owner with the longest
        # matching pattern as the match is more specific.
        for rule in rules:
            candidate = len(rule.matcher.pattern)
            if candidate > score:
                score = candidate
                owners = rule.owners
        actors = filter(None, resolve_actors(owners, project_id).values())

        # 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 None
        return actors[0].resolve()

    @classmethod
    def _matching_ownership_rules(cls, ownership, project_id, data):
        rules = []
        if ownership.schema is not None:
            for rule in load_schema(ownership.schema):
                if rule.test(data):
                    rules.append(rule)

        return rules
Example #5
0
class ProjectOwnership(Model):
    __core__ = True

    project = FlexibleForeignKey("sentry.Project", unique=True)
    raw = models.TextField(null=True)
    schema = JSONField(null=True)
    fallthrough = models.BooleanField(default=True)
    auto_assignment = models.BooleanField(default=False)
    date_created = models.DateTimeField(default=timezone.now)
    last_updated = models.DateTimeField(default=timezone.now)
    is_active = models.BooleanField(default=True)

    # An object to indicate ownership is implicitly everyone
    Everyone = object()

    class Meta:
        app_label = "sentry"
        db_table = "sentry_projectownership"

    __repr__ = sane_repr("project_id", "is_active")

    @classmethod
    def get_cache_key(self, project_id):
        return f"projectownership_project_id:1:{project_id}"

    @classmethod
    def get_combined_schema(self, ownership, codeowners):
        if codeowners and codeowners.schema:
            ownership.schema = (codeowners.schema
                                if not ownership.schema else {
                                    **ownership.schema,
                                    "rules": [
                                        *codeowners.schema["rules"],
                                        *ownership.schema["rules"],
                                    ],
                                })
        return ownership.schema

    @classmethod
    def get_ownership_cached(cls, project_id):
        """
        Cached read access to projectownership.

        This method implements a negative cache which saves us
        a pile of read queries in post_processing as most projects
        don't have ownership rules.

        See the post_save and post_delete signals below for additional
        cache updates.
        """
        cache_key = cls.get_cache_key(project_id)
        ownership = cache.get(cache_key)
        if ownership is None:
            try:
                ownership = cls.objects.get(project_id=project_id)
            except cls.DoesNotExist:
                ownership = False
            cache.set(cache_key, ownership, READ_CACHE_DURATION)
        return ownership or None

    @classmethod
    def get_owners(cls, project_id, data):
        """
        For a given project_id, and event data blob.
        We combine the schemas from IssueOwners and CodeOwners.

        If Everyone is returned, this means we implicitly are
        falling through our rules and everyone is responsible.

        If an empty list is returned, this means there are explicitly
        no owners.
        """
        from sentry.models import ProjectCodeOwners

        ownership = cls.get_ownership_cached(project_id)
        if not ownership:
            ownership = cls(project_id=project_id)

        codeowners = ProjectCodeOwners.get_codeowners_cached(project_id)
        ownership.schema = cls.get_combined_schema(ownership, codeowners)

        rules = cls._matching_ownership_rules(ownership, project_id, data)

        if not rules:
            return cls.Everyone if ownership.fallthrough else [], None

        owners = {o for rule in rules for o in rule.owners}
        owners_to_actors = resolve_actors(owners, project_id)
        ordered_actors = []
        for rule in rules:
            for o in rule.owners:
                if o in owners and owners_to_actors.get(o) is not None:
                    ordered_actors.append(owners_to_actors[o])
                    owners.remove(o)

        return ordered_actors, rules

    @classmethod
    def _find_actors(cls, project_id, rules, limit):
        """
        Get 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]
        return actors

    @classmethod
    def get_autoassign_owners(cls, project_id, data, limit=2):
        """
        Get the auto-assign owner for a project if there are any.

        We combine the schemas from IssueOwners and CodeOwners.

        Returns a tuple of (auto_assignment_enabled, list_of_owners, assigned_by_codeowners: boolean).
        """
        from sentry.models import ProjectCodeOwners

        with metrics.timer("projectownership.get_autoassign_owners"):
            ownership = cls.get_ownership_cached(project_id)
            codeowners = ProjectCodeOwners.get_codeowners_cached(project_id)
            assigned_by_codeowners = False
            if not (ownership or codeowners):
                return False, [], assigned_by_codeowners

            if not ownership:
                ownership = cls(project_id=project_id)

            ownership_rules = cls._matching_ownership_rules(
                ownership, project_id, data)
            codeowners_rules = (cls._matching_ownership_rules(
                codeowners, project_id, data) if codeowners else [])

            if not (codeowners_rules or ownership_rules):
                return ownership.auto_assignment, [], assigned_by_codeowners

            ownership_actors = cls._find_actors(project_id, ownership_rules,
                                                limit)
            codeowners_actors = cls._find_actors(project_id, codeowners_rules,
                                                 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 (ownership_actors or codeowners_actors):
                return ownership.auto_assignment, [], assigned_by_codeowners

            # Ownership rules take precedence over codeowner rules.
            actors = [*ownership_actors, *codeowners_actors][:limit]

            # Only the first item in the list is used for assignment, the rest are just used to suggest suspect owners.
            # So if ownership_actors is empty, it will be assigned by codeowners_actors
            if len(ownership_actors) == 0:
                assigned_by_codeowners = True

            from sentry.models import ActorTuple

            return (
                ownership.auto_assignment,
                ActorTuple.resolve_many(actors),
                assigned_by_codeowners,
            )

    @classmethod
    def _matching_ownership_rules(cls, ownership, project_id, data):
        rules = []
        if ownership.schema is not None:
            for rule in load_schema(ownership.schema):
                if rule.test(data):
                    rules.append(rule)

        return rules
Example #6
0
class ProjectOwnership(Model):
    __core__ = True

    project = FlexibleForeignKey("sentry.Project", unique=True)
    raw = models.TextField(null=True)
    schema = JSONField(null=True)
    fallthrough = models.BooleanField(default=True)
    auto_assignment = models.BooleanField(default=False)
    date_created = models.DateTimeField(default=timezone.now)
    last_updated = models.DateTimeField(default=timezone.now)
    is_active = models.BooleanField(default=True)

    # An object to indicate ownership is implicitly everyone
    Everyone = object()

    class Meta:
        app_label = "sentry"
        db_table = "sentry_projectownership"

    __repr__ = sane_repr("project_id", "is_active")

    @classmethod
    def get_cache_key(self, project_id):
        return u"projectownership_project_id:1:{}".format(project_id)

    @classmethod
    def get_ownership_cached(cls, project_id):
        """
        Cached read access to projectownership.

        This method implements a negative cache which saves us
        a pile of read queries in post_processing as most projects
        don't have ownership rules.

        See the post_save and post_delete signals below for additional
        cache updates.
        """
        cache_key = cls.get_cache_key(project_id)
        ownership = cache.get(cache_key)
        if ownership is None:
            try:
                ownership = cls.objects.get(project_id=project_id)
            except cls.DoesNotExist:
                ownership = False
            cache.set(cache_key, ownership, READ_CACHE_DURATION)
        return ownership or None

    @classmethod
    def get_owners(cls, project_id, data):
        """
        For a given project_id, and event data blob.

        If Everyone is returned, this means we implicitly are
        falling through our rules and everyone is responsible.

        If an empty list is returned, this means there are explicitly
        no owners.
        """
        ownership = cls.get_ownership_cached(project_id)
        if not ownership:
            ownership = cls(project_id=project_id)

        rules = cls._matching_ownership_rules(ownership, project_id, data)
        if not rules:
            return cls.Everyone if ownership.fallthrough else [], None

        owners = {o for rule in rules for o in rule.owners}
        return filter(None, resolve_actors(owners, project_id).values()), rules

    @classmethod
    def get_autoassign_owner(cls, project_id, data):
        """
        Get the auto-assign owner for a project if there are any.

        Will return None if there are no owners, or a list of owners.
        """
        ownership = cls.get_ownership_cached(project_id)
        if not ownership or not ownership.auto_assignment:
            return None

        rules = cls._matching_ownership_rules(ownership, project_id, data)
        if not rules:
            return None

        score = 0
        owners = None
        # Automatic assignment prefers the owner with the longest
        # matching pattern as the match is more specific.
        for rule in rules:
            candidate = len(rule.matcher.pattern)
            if candidate > score:
                score = candidate
                owners = rule.owners
        actors = filter(None, resolve_actors(owners, project_id).values())

        # 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 None
        return actors[0].resolve()

    @classmethod
    def _matching_ownership_rules(cls, ownership, project_id, data):
        rules = []
        if ownership.schema is not None:
            for rule in load_schema(ownership.schema):
                if rule.test(data):
                    rules.append(rule)

        return rules