def _query(self, project, retention_window_start, group_queryset, tags, environment, sort_by, limit, cursor, count_hits, paginator_options, **parameters): from sentry.models import (Group, Environment, Event, GroupEnvironment, Release) if environment is not None: if 'environment' in tags: environment_name = tags.pop('environment') assert environment_name is ANY or Environment.objects.get( projects=project, name=environment_name, ).id == environment.id event_queryset_builder = QuerySetBuilder({ 'date_from': ScalarCondition('date_added', 'gt'), 'date_to': ScalarCondition('date_added', 'lt'), }) if any(key in parameters for key in event_queryset_builder.conditions.keys()): event_queryset = event_queryset_builder.build( tagstore.get_event_tag_qs( project_id=project.id, environment_id=environment.id, key='environment', value=environment.name, ), parameters, ) if retention_window_start is not None: event_queryset = event_queryset.filter( date_added__gte=retention_window_start) group_queryset = group_queryset.filter( id__in=list(event_queryset.distinct().values_list( 'group_id', flat=True)[:1000])) group_queryset = QuerySetBuilder({ 'first_release': CallbackCondition( lambda queryset, version: queryset.extra( where=[ '{} = {}'.format( get_sql_column(GroupEnvironment, 'first_release_id'), get_sql_column(Release, 'id'), ), '{} = %s'.format( get_sql_column(Release, 'organization'), ), '{} = %s'.format( get_sql_column(Release, 'version'), ), ], params=[project.organization_id, version], tables=[Release._meta.db_table], ), ), 'times_seen': CallbackCondition( # This condition represents the exact number of times that # an issue has been seen in an environment. Since an issue # can't be seen in an environment more times than the issue # was seen overall, we can safely exclude any groups that # don't have at least that many events. lambda queryset, times_seen: queryset.exclude( times_seen__lt=times_seen, ), ), 'times_seen_lower': CallbackCondition( # This condition represents the lower threshold for the # number of times an issue has been seen in an environment. # Since an issue can't be seen in an environment more times # than the issue was seen overall, we can safely exclude # any groups that haven't met that threshold. lambda queryset, times_seen: queryset.exclude( times_seen__lt=times_seen, ), ), # The following conditions make a few assertions that are are # correct in an abstract sense but may not accurately reflect # the existing implementation (see GH-5289). These assumptions # are that 1. The first seen time for a Group is the minimum # value of the first seen time for all of it's GroupEnvironment # relations; 2. The last seen time for a Group is the maximum # value of the last seen time for all of it's GroupEnvironment # relations; 3. The first seen time is always less than or # equal to the last seen time. 'age_from': CallbackCondition( # This condition represents the lower threshold for "first # seen" time for an environment. Due to assertions #1 and # #3, we can exclude any groups where the "last seen" time # is prior to this timestamp. lambda queryset, first_seen: queryset.exclude( last_seen__lt=first_seen, ), ), 'age_to': CallbackCondition( # This condition represents the upper threshold for "first # seen" time for an environment. Due to assertions #1, we # can exclude any values where the group first seen is # greater than that threshold. lambda queryset, first_seen: queryset.exclude( first_seen__gt=first_seen, ), ), 'last_seen_from': CallbackCondition( # This condition represents the lower threshold for "last # seen" time for an environment. Due to assertion #2, we # can exclude any values where the group last seen value is # less than that threshold. lambda queryset, last_seen: queryset.exclude(last_seen__lt= last_seen, ), ), 'last_seen_to': CallbackCondition( # This condition represents the upper threshold for "last # seen" time for an environment. Due to assertions #2 and # #3, we can exclude any values where the group first seen # value is greater than that threshold. lambda queryset, last_seen: queryset.exclude(first_seen__gt =last_seen, ), ), }).build( group_queryset.extra( where=[ '{} = {}'.format( get_sql_column(Group, 'id'), get_sql_column(GroupEnvironment, 'group_id'), ), '{} = %s'.format( get_sql_column(GroupEnvironment, 'environment_id'), ), ], params=[environment.id], tables=[GroupEnvironment._meta.db_table], ), parameters, ) get_sort_expression, sort_value_to_cursor_value = environment_sort_strategies[ sort_by] group_tag_value_queryset = tagstore.get_group_tag_value_qs( project_id=project.id, group_id=set( group_queryset.values_list('id', flat=True)[:10000]), environment_id=environment.id, key='environment', value=environment.name, ) if retention_window_start is not None: group_tag_value_queryset = group_tag_value_queryset.filter( last_seen__gte=retention_window_start) candidates = dict( QuerySetBuilder({ 'age_from': ScalarCondition('first_seen', 'gt'), 'age_to': ScalarCondition('first_seen', 'lt'), 'last_seen_from': ScalarCondition('last_seen', 'gt'), 'last_seen_to': ScalarCondition('last_seen', 'lt'), 'times_seen': CallbackCondition( lambda queryset, times_seen: queryset.filter( times_seen=times_seen), ), 'times_seen_lower': ScalarCondition('times_seen', 'gt'), 'times_seen_upper': ScalarCondition('times_seen', 'lt'), }).build( group_tag_value_queryset, parameters, ).extra(select={ 'sort_value': get_sort_expression(group_tag_value_queryset.model), }, ).values_list('group_id', 'sort_value')) if tags: # TODO: `get_group_ids_for_search_filter` should be able to # utilize the retention window start parameter for additional # optimizations. matches = tagstore.get_group_ids_for_search_filter( project_id=project.id, environment_id=environment.id, tags=tags, candidates=candidates.keys(), limit=len(candidates), ) for key in set(candidates) - set(matches or []): del candidates[key] result = SequencePaginator([(sort_value_to_cursor_value(score), id) for (id, score) in candidates.items()], reverse=True, **paginator_options).get_result( limit, cursor, count_hits=count_hits) groups = Group.objects.in_bulk(result.results) result.results = [groups[k] for k in result.results if k in groups] return result else: event_queryset_builder = QuerySetBuilder({ 'date_from': ScalarCondition('datetime', 'gt'), 'date_to': ScalarCondition('datetime', 'lt'), }) if any(key in parameters for key in event_queryset_builder.conditions.keys()): group_queryset = group_queryset.filter(id__in=list( event_queryset_builder.build( Event.objects.filter(project_id=project.id), parameters, ).distinct().values_list('group_id', flat=True)[:1000], )) group_queryset = QuerySetBuilder({ 'first_release': CallbackCondition( lambda queryset, version: queryset.filter( first_release__organization_id=project.organization_id, first_release__version=version, ), ), 'age_from': ScalarCondition('first_seen', 'gt'), 'age_to': ScalarCondition('first_seen', 'lt'), 'last_seen_from': ScalarCondition('last_seen', 'gt'), 'last_seen_to': ScalarCondition('last_seen', 'lt'), 'times_seen': CallbackCondition( lambda queryset, times_seen: queryset.filter(times_seen= times_seen), ), 'times_seen_lower': ScalarCondition('times_seen', 'gt'), 'times_seen_upper': ScalarCondition('times_seen', 'lt'), }).build( group_queryset, parameters, ).extra(select={ 'sort_value': get_sort_clause(sort_by), }, ) if tags: group_ids = tagstore.get_group_ids_for_search_filter( project_id=project.id, environment_id=None, tags=tags, candidates=None, ) if group_ids: group_queryset = group_queryset.filter(id__in=group_ids) else: group_queryset = group_queryset.none() paginator_cls, sort_clause = sort_strategies[sort_by] group_queryset = group_queryset.order_by(sort_clause) paginator = paginator_cls(group_queryset, sort_clause, **paginator_options) return paginator.get_result(limit, cursor, count_hits=count_hits)
def _query(self, projects, retention_window_start, group_queryset, tags, environments, sort_by, limit, cursor, count_hits, paginator_options, **parameters): from sentry.models import (Group, Environment, Event, GroupEnvironment, Release) # this backend only supports search within one project/environment if len(projects) != 1 or (environments is not None and len(environments) > 1): raise NotImplementedError project = projects[0] environment = environments[0] if environments is not None else environments if environment is not None: if 'environment' in tags: environment_name = tags.pop('environment') assert environment_name is ANY or Environment.objects.get( projects=project, name=environment_name, ).id == environment.id event_queryset_builder = QuerySetBuilder({ 'date_from': ScalarCondition('date_added', 'gt'), 'date_to': ScalarCondition('date_added', 'lt'), }) if any(key in parameters for key in event_queryset_builder.conditions.keys()): event_queryset = event_queryset_builder.build( tagstore.get_event_tag_qs( project_id=project.id, environment_id=environment.id, key='environment', value=environment.name, ), parameters, ) if retention_window_start is not None: event_queryset = event_queryset.filter(date_added__gte=retention_window_start) group_queryset = group_queryset.filter( id__in=list(event_queryset.distinct().values_list('group_id', flat=True)[:1000]) ) _, group_queryset_sort_clause = sort_strategies[sort_by] group_queryset = QuerySetBuilder({ 'first_release': CallbackCondition( lambda queryset, version: queryset.extra( where=[ '{} = {}'.format( get_sql_column(GroupEnvironment, 'first_release_id'), get_sql_column(Release, 'id'), ), '{} = %s'.format( get_sql_column(Release, 'organization'), ), '{} = %s'.format( get_sql_column(Release, 'version'), ), ], params=[project.organization_id, version], tables=[Release._meta.db_table], ), ), 'times_seen': CallbackCondition( # This condition represents the exact number of times that # an issue has been seen in an environment. Since an issue # can't be seen in an environment more times than the issue # was seen overall, we can safely exclude any groups that # don't have at least that many events. lambda queryset, times_seen: queryset.exclude( times_seen__lt=times_seen, ), ), 'times_seen_lower': CallbackCondition( # This condition represents the lower threshold for the # number of times an issue has been seen in an environment. # Since an issue can't be seen in an environment more times # than the issue was seen overall, we can safely exclude # any groups that haven't met that threshold. lambda queryset, times_seen: queryset.exclude( times_seen__lt=times_seen, ), ), # The following conditions make a few assertions that are are # correct in an abstract sense but may not accurately reflect # the existing implementation (see GH-5289). These assumptions # are that 1. The first seen time for a Group is the minimum # value of the first seen time for all of it's GroupEnvironment # relations; 2. The last seen time for a Group is the maximum # value of the last seen time for all of it's GroupEnvironment # relations; 3. The first seen time is always less than or # equal to the last seen time. 'age_from': CallbackCondition( # This condition represents the lower threshold for "first # seen" time for an environment. Due to assertions #1 and # #3, we can exclude any groups where the "last seen" time # is prior to this timestamp. lambda queryset, first_seen: queryset.exclude( last_seen__lt=first_seen, ), ), 'age_to': CallbackCondition( # This condition represents the upper threshold for "first # seen" time for an environment. Due to assertions #1, we # can exclude any values where the group first seen is # greater than that threshold. lambda queryset, first_seen: queryset.exclude( first_seen__gt=first_seen, ), ), 'last_seen_from': CallbackCondition( # This condition represents the lower threshold for "last # seen" time for an environment. Due to assertion #2, we # can exclude any values where the group last seen value is # less than that threshold. lambda queryset, last_seen: queryset.exclude( last_seen__lt=last_seen, ), ), 'last_seen_to': CallbackCondition( # This condition represents the upper threshold for "last # seen" time for an environment. Due to assertions #2 and # #3, we can exclude any values where the group first seen # value is greater than that threshold. lambda queryset, last_seen: queryset.exclude( first_seen__gt=last_seen, ), ), }).build( group_queryset.extra( where=[ '{} = {}'.format( get_sql_column(Group, 'id'), get_sql_column(GroupEnvironment, 'group_id'), ), '{} = %s'.format( get_sql_column(GroupEnvironment, 'environment_id'), ), ], params=[environment.id], tables=[GroupEnvironment._meta.db_table], ), parameters, ).order_by(group_queryset_sort_clause) get_sort_expression, sort_value_to_cursor_value = environment_sort_strategies[sort_by] group_tag_value_queryset = tagstore.get_group_tag_value_qs( project_id=project.id, group_id=set(group_queryset.values_list('id', flat=True)[:10000]), environment_id=environment.id, key='environment', value=environment.name, ) if retention_window_start is not None: group_tag_value_queryset = group_tag_value_queryset.filter( last_seen__gte=retention_window_start ) candidates = dict( QuerySetBuilder({ 'age_from': ScalarCondition('first_seen', 'gt'), 'age_to': ScalarCondition('first_seen', 'lt'), 'last_seen_from': ScalarCondition('last_seen', 'gt'), 'last_seen_to': ScalarCondition('last_seen', 'lt'), 'times_seen': CallbackCondition( lambda queryset, times_seen: queryset.filter(times_seen=times_seen), ), 'times_seen_lower': ScalarCondition('times_seen', 'gt'), 'times_seen_upper': ScalarCondition('times_seen', 'lt'), }).build( group_tag_value_queryset, parameters, ).extra( select={ 'sort_value': get_sort_expression(group_tag_value_queryset.model), }, ).values_list('group_id', 'sort_value') ) if tags: # TODO: `get_group_ids_for_search_filter` should be able to # utilize the retention window start parameter for additional # optimizations. matches = tagstore.get_group_ids_for_search_filter( project_id=project.id, environment_id=environment.id, tags=tags, candidates=candidates.keys(), limit=len(candidates), ) for key in set(candidates) - set(matches or []): del candidates[key] result = SequencePaginator( [(sort_value_to_cursor_value(score), id) for (id, score) in candidates.items()], reverse=True, **paginator_options ).get_result(limit, cursor, count_hits=count_hits) groups = Group.objects.in_bulk(result.results) result.results = [groups[k] for k in result.results if k in groups] return result else: event_queryset_builder = QuerySetBuilder({ 'date_from': ScalarCondition('datetime', 'gt'), 'date_to': ScalarCondition('datetime', 'lt'), }) if any(key in parameters for key in event_queryset_builder.conditions.keys()): group_queryset = group_queryset.filter( id__in=list( event_queryset_builder.build( Event.objects.filter(project_id=project.id), parameters, ).distinct().values_list('group_id', flat=True)[:1000], ) ) group_queryset = QuerySetBuilder({ 'first_release': CallbackCondition( lambda queryset, version: queryset.filter( first_release__organization_id=project.organization_id, first_release__version=version, ), ), 'age_from': ScalarCondition('first_seen', 'gt'), 'age_to': ScalarCondition('first_seen', 'lt'), 'last_seen_from': ScalarCondition('last_seen', 'gt'), 'last_seen_to': ScalarCondition('last_seen', 'lt'), 'times_seen': CallbackCondition( lambda queryset, times_seen: queryset.filter(times_seen=times_seen), ), 'times_seen_lower': ScalarCondition('times_seen', 'gt'), 'times_seen_upper': ScalarCondition('times_seen', 'lt'), }).build( group_queryset, parameters, ).extra( select={ 'sort_value': get_sort_clause(sort_by), }, ) if tags: group_ids = tagstore.get_group_ids_for_search_filter( project_id=project.id, environment_id=None, tags=tags, candidates=None, ) if group_ids: group_queryset = group_queryset.filter(id__in=group_ids) else: group_queryset = group_queryset.none() paginator_cls, sort_clause = sort_strategies[sort_by] group_queryset = group_queryset.order_by(sort_clause) paginator = paginator_cls(group_queryset, sort_clause, **paginator_options) return paginator.get_result(limit, cursor, count_hits=count_hits)
def query(self, project, tags=None, environment=None, sort_by='date', limit=100, cursor=None, count_hits=False, paginator_options=None, **parameters): from sentry.models import (Environment, Event, Group, GroupEnvironment, GroupStatus, GroupSubscription, Release) if paginator_options is None: paginator_options = {} if tags is None: tags = {} try: if tags.get('sentry:release') == 'latest': tags['sentry:release'] = get_latest_release( project, environment) if parameters.get('first_release') == 'latest': parameters['first_release'] = get_latest_release( project, environment) except Release.DoesNotExist: # no matches could possibly be found from this point on return Paginator(Group.objects.none()).get_result() group_queryset = QuerySetBuilder({ 'query': CallbackCondition( lambda queryset, query: queryset.filter( Q(message__icontains=query) | Q(culprit__icontains=query), ) if query else queryset, ), 'status': CallbackCondition( lambda queryset, status: queryset.filter(status=status), ), 'bookmarked_by': CallbackCondition( lambda queryset, user: queryset.filter( bookmark_set__project=project, bookmark_set__user=user, ), ), 'assigned_to': CallbackCondition( functools.partial(assigned_to_filter, project=project), ), 'unassigned': CallbackCondition( lambda queryset, unassigned: queryset.filter( assignee_set__isnull=unassigned, ), ), 'subscribed_by': CallbackCondition( lambda queryset, user: queryset.filter( id__in=GroupSubscription.objects.filter( project=project, user=user, is_active=True, ).values_list('group'), ), ), 'active_at_from': ScalarCondition('active_at', 'gt'), 'active_at_to': ScalarCondition('active_at', 'lt'), }).build( Group.objects.filter(project=project).exclude(status__in=[ GroupStatus.PENDING_DELETION, GroupStatus.DELETION_IN_PROGRESS, GroupStatus.PENDING_MERGE, ]), parameters, ) # filter out groups which are beyond the retention period retention = quotas.get_event_retention( organization=project.organization) if retention: retention_window_start = timezone.now() - timedelta(days=retention) # TODO: This could be optimized when building querysets to identify # criteria that are logically impossible (e.g. if the upper bound # for last seen is before the retention window starts, no results # exist.) group_queryset = group_queryset.filter( last_seen__gte=retention_window_start) else: retention_window_start = None if environment is not None: if 'environment' in tags: # TODO: This should probably just overwrite the existing tag, # rather than asserting on it, but...? assert Environment.objects.get( projects=project, name=tags.pop('environment'), ).id == environment.id event_queryset_builder = QuerySetBuilder({ 'date_from': ScalarCondition('date_added', 'gt'), 'date_to': ScalarCondition('date_added', 'lt'), }) if any(key in parameters for key in event_queryset_builder.conditions.keys()): event_queryset = event_queryset_builder.build( tagstore.get_event_tag_qs( project.id, environment.id, 'environment', environment.name, ), parameters, ) if retention_window_start is not None: event_queryset = event_queryset.filter( date_added__gte=retention_window_start) group_queryset = group_queryset.filter( id__in=list(event_queryset.distinct().values_list( 'group_id', flat=True)[:1000])) group_queryset = QuerySetBuilder({ 'first_release': CallbackCondition( lambda queryset, version: queryset.extra( where=[ '{} = {}'.format( get_sql_column(GroupEnvironment, 'first_release_id'), get_sql_column(Release, 'id'), ), '{} = %s'.format( get_sql_column(Release, 'organization'), ), '{} = %s'.format( get_sql_column(Release, 'version'), ), ], params=[project.organization_id, version], tables=[Release._meta.db_table], ), ), 'times_seen': CallbackCondition( # This condition represents the exact number of times that # an issue has been seen in an environment. Since an issue # can't be seen in an environment more times than the issue # was seen overall, we can safely exclude any groups that # don't have at least that many events. lambda queryset, times_seen: queryset.exclude( times_seen__lt=times_seen, ), ), 'times_seen_lower': CallbackCondition( # This condition represents the lower threshold for the # number of times an issue has been seen in an environment. # Since an issue can't be seen in an environment more times # than the issue was seen overall, we can safely exclude # any groups that haven't met that threshold. lambda queryset, times_seen: queryset.exclude( times_seen__lt=times_seen, ), ), # The following conditions make a few assertions that are are # correct in an abstract sense but may not accurately reflect # the existing implementation (see GH-5289). These assumptions # are that 1. The first seen time for a Group is the minimum # value of the first seen time for all of it's GroupEnvironment # relations; 2. The last seen time for a Group is the maximum # value of the last seen time for all of it's GroupEnvironment # relations; 3. The first seen time is always less than or # equal to the last seen time. 'age_from': CallbackCondition( # This condition represents the lower threshold for "first # seen" time for an environment. Due to assertions #1 and # #3, we can exclude any groups where the "last seen" time # is prior to this timestamp. lambda queryset, first_seen: queryset.exclude( last_seen__lt=first_seen, ), ), 'age_to': CallbackCondition( # This condition represents the upper threshold for "first # seen" time for an environment. Due to assertions #1, we # can exclude any values where the group first seen is # greater than that threshold. lambda queryset, first_seen: queryset.exclude( first_seen__gt=first_seen, ), ), 'last_seen_from': CallbackCondition( # This condition represents the lower threshold for "last # seen" time for an environment. Due to assertion #2, we # can exclude any values where the group last seen value is # less than that threshold. lambda queryset, last_seen: queryset.exclude(last_seen__lt= last_seen, ), ), 'last_seen_to': CallbackCondition( # This condition represents the upper threshold for "last # seen" time for an environment. Due to assertions #2 and # #3, we can exclude any values where the group first seen # value is greater than that threshold. lambda queryset, last_seen: queryset.exclude(first_seen__gt =last_seen, ), ), }).build( group_queryset.extra( where=[ '{} = {}'.format( get_sql_column(Group, 'id'), get_sql_column(GroupEnvironment, 'group_id'), ), '{} = %s'.format( get_sql_column(GroupEnvironment, 'environment_id'), ), ], params=[environment.id], tables=[GroupEnvironment._meta.db_table], ), parameters, ) get_sort_expression, sort_value_to_cursor_value = environment_sort_strategies[ sort_by] group_tag_value_queryset = tagstore.get_group_tag_value_qs( project.id, set(group_queryset.values_list('id', flat=True)), # TODO: Limit?, environment.id, 'environment', environment.name, ) if retention_window_start is not None: group_tag_value_queryset = group_tag_value_queryset.filter( last_seen__gte=retention_window_start) candidates = dict( QuerySetBuilder({ 'age_from': ScalarCondition('first_seen', 'gt'), 'age_to': ScalarCondition('first_seen', 'lt'), 'last_seen_from': ScalarCondition('last_seen', 'gt'), 'last_seen_to': ScalarCondition('last_seen', 'lt'), 'times_seen': CallbackCondition( lambda queryset, times_seen: queryset.filter( times_seen=times_seen), ), 'times_seen_lower': ScalarCondition('times_seen', 'gt'), 'times_seen_upper': ScalarCondition('times_seen', 'lt'), }).build( group_tag_value_queryset, parameters, ).extra(select={ 'sort_value': get_sort_expression(group_tag_value_queryset.model), }, ).values_list('group_id', 'sort_value')) if tags: # TODO: `get_group_ids_for_search_filter` should be able to # utilize the retention window start parameter for additional # optimizations. matches = tagstore.get_group_ids_for_search_filter( project.id, environment.id, tags, candidates.keys(), limit=len(candidates), ) for key in set(candidates) - set(matches or []): del candidates[key] result = SequencePaginator([(sort_value_to_cursor_value(score), id) for (id, score) in candidates.items()], reverse=True, **paginator_options).get_result( limit, cursor, count_hits=count_hits) groups = Group.objects.in_bulk(result.results) result.results = [groups[k] for k in result.results if k in groups] return result else: event_queryset_builder = QuerySetBuilder({ 'date_from': ScalarCondition('datetime', 'gt'), 'date_to': ScalarCondition('datetime', 'lt'), }) if any(key in parameters for key in event_queryset_builder.conditions.keys()): group_queryset = group_queryset.filter(id__in=list( event_queryset_builder.build( Event.objects.filter(project_id=project.id), parameters, ).distinct().values_list('group_id', flat=True)[:1000], )) group_queryset = QuerySetBuilder({ 'first_release': CallbackCondition( lambda queryset, version: queryset.filter( first_release__organization_id=project.organization_id, first_release__version=version, ), ), 'age_from': ScalarCondition('first_seen', 'gt'), 'age_to': ScalarCondition('first_seen', 'lt'), 'last_seen_from': ScalarCondition('last_seen', 'gt'), 'last_seen_to': ScalarCondition('last_seen', 'lt'), 'times_seen': CallbackCondition( lambda queryset, times_seen: queryset.filter(times_seen= times_seen), ), 'times_seen_lower': ScalarCondition('times_seen', 'gt'), 'times_seen_upper': ScalarCondition('times_seen', 'lt'), }).build( group_queryset, parameters, ).extra(select={ 'sort_value': get_sort_clause(sort_by), }, ) if tags: matches = tagstore.get_group_ids_for_search_filter( project.id, None, tags) if matches: group_queryset = group_queryset.filter(id__in=matches) else: group_queryset = group_queryset.none() paginator_cls, sort_clause = sort_strategies[sort_by] group_queryset = group_queryset.order_by(sort_clause) paginator = paginator_cls(group_queryset, sort_clause, **paginator_options) return paginator.get_result(limit, cursor, count_hits=count_hits)
def _build_queryset( self, project, query=None, status=None, tags=None, bookmarked_by=None, assigned_to=None, first_release=None, sort_by='date', unassigned=None, subscribed_by=None, age_from=None, age_from_inclusive=True, age_to=None, age_to_inclusive=True, last_seen_from=None, last_seen_from_inclusive=True, last_seen_to=None, last_seen_to_inclusive=True, date_from=None, date_from_inclusive=True, date_to=None, date_to_inclusive=True, active_at_from=None, active_at_from_inclusive=True, active_at_to=None, active_at_to_inclusive=True, times_seen=None, times_seen_lower=None, times_seen_lower_inclusive=True, times_seen_upper=None, times_seen_upper_inclusive=True, cursor=None, limit=None, environment=None, ): from sentry.models import Event, Group, GroupSubscription, GroupStatus, OrganizationMember if tags is None: tags = {} engine = get_db_engine('default') queryset = Group.objects.filter(project=project) if query: # TODO(dcramer): if we want to continue to support search on SQL # we should at least optimize this in Postgres so that it does # the query filter **after** the index filters, and restricts the # result set queryset = queryset.filter( Q(message__icontains=query) | Q(culprit__icontains=query)) if status is None: status_in = ( GroupStatus.PENDING_DELETION, GroupStatus.DELETION_IN_PROGRESS, GroupStatus.PENDING_MERGE, ) queryset = queryset.exclude(status__in=status_in) else: queryset = queryset.filter(status=status) if bookmarked_by: queryset = queryset.filter( bookmark_set__project=project, bookmark_set__user=bookmarked_by, ) if assigned_to: teams = [] try: member = OrganizationMember.objects.get( user=assigned_to, organization_id=project.organization_id, ) except OrganizationMember.DoesNotExist: pass else: teams = member.get_teams() queryset = queryset.filter( Q(assignee_set__user=assigned_to, assignee_set__project=project) | Q(assignee_set__team__in=teams) ) elif unassigned in (True, False): queryset = queryset.filter( assignee_set__isnull=unassigned, ) if subscribed_by is not None: queryset = queryset.filter( id__in=GroupSubscription.objects.filter( project=project, user=subscribed_by, is_active=True, ).values_list('group'), ) if first_release: if first_release is EMPTY: return queryset.none() queryset = queryset.filter( first_release__organization_id=project.organization_id, first_release__version=first_release, ) if environment is not None: # XXX: This overwrites the ``environment`` tag, if present, to # ensure that the result set is limited to groups that have been # seen in this environment (there is no way to search for groups # that match multiple values of a single tag without changes to the # tagstore API.) tags['environment'] = environment.name if tags: matches = tagstore.get_group_ids_for_search_filter( project.id, environment.id if environment is not None else None, tags, ) if not matches: return queryset.none() queryset = queryset.filter( id__in=matches, ) if age_from or age_to: params = {} if age_from: if age_from_inclusive: params['first_seen__gte'] = age_from else: params['first_seen__gt'] = age_from if age_to: if age_to_inclusive: params['first_seen__lte'] = age_to else: params['first_seen__lt'] = age_to queryset = queryset.filter(**params) if last_seen_from or last_seen_to: params = {} if last_seen_from: if last_seen_from_inclusive: params['last_seen__gte'] = last_seen_from else: params['last_seen__gt'] = last_seen_from if last_seen_to: if last_seen_to_inclusive: params['last_seen__lte'] = last_seen_to else: params['last_seen__lt'] = last_seen_to queryset = queryset.filter(**params) if active_at_from or active_at_to: params = {} if active_at_from: if active_at_from_inclusive: params['active_at__gte'] = active_at_from else: params['active_at__gt'] = active_at_from if active_at_to: if active_at_to_inclusive: params['active_at__lte'] = active_at_to else: params['active_at__lt'] = active_at_to queryset = queryset.filter(**params) if times_seen is not None: queryset = queryset.filter(times_seen=times_seen) if times_seen_lower is not None or times_seen_upper is not None: params = {} if times_seen_lower is not None: if times_seen_lower_inclusive: params['times_seen__gte'] = times_seen_lower else: params['times_seen__gt'] = times_seen_lower if times_seen_upper is not None: if times_seen_upper_inclusive: params['times_seen__lte'] = times_seen_upper else: params['times_seen__lt'] = times_seen_upper queryset = queryset.filter(**params) if date_from or date_to: params = { 'project_id': project.id, } if date_from: if date_from_inclusive: params['datetime__gte'] = date_from else: params['datetime__gt'] = date_from if date_to: if date_to_inclusive: params['datetime__lte'] = date_to else: params['datetime__lt'] = date_to event_queryset = Event.objects.filter(**params) if query: event_queryset = event_queryset.filter( message__icontains=query) # limit to the first 1000 results group_ids = event_queryset.distinct().values_list( 'group_id', flat=True)[:1000] # if Event is not on the primary database remove Django's # implicit subquery by coercing to a list base = router.db_for_read(Group) using = router.db_for_read(Event) # MySQL also cannot do a LIMIT inside of a subquery if base != using or engine.startswith('mysql'): group_ids = list(group_ids) queryset = queryset.filter( id__in=group_ids, ) if engine.startswith('sqlite'): score_clause = SQLITE_SORT_CLAUSES[sort_by] elif engine.startswith('mysql'): score_clause = MYSQL_SORT_CLAUSES[sort_by] elif engine.startswith('oracle'): score_clause = ORACLE_SORT_CLAUSES[sort_by] elif engine in MSSQL_ENGINES: score_clause = MSSQL_SORT_CLAUSES[sort_by] else: score_clause = SORT_CLAUSES[sort_by] queryset = queryset.extra( select={'sort_value': score_clause}, ) return queryset
def _build_queryset( self, project, query=None, status=None, tags=None, bookmarked_by=None, assigned_to=None, first_release=None, sort_by='date', unassigned=None, subscribed_by=None, age_from=None, age_from_inclusive=True, age_to=None, age_to_inclusive=True, last_seen_from=None, last_seen_from_inclusive=True, last_seen_to=None, last_seen_to_inclusive=True, date_from=None, date_from_inclusive=True, date_to=None, date_to_inclusive=True, active_at_from=None, active_at_from_inclusive=True, active_at_to=None, active_at_to_inclusive=True, times_seen=None, times_seen_lower=None, times_seen_lower_inclusive=True, times_seen_upper=None, times_seen_upper_inclusive=True, cursor=None, limit=None, environment_id=None, ): from sentry.models import Event, Group, GroupSubscription, GroupStatus engine = get_db_engine('default') queryset = Group.objects.filter(project=project) if query: # TODO(dcramer): if we want to continue to support search on SQL # we should at least optimize this in Postgres so that it does # the query filter **after** the index filters, and restricts the # result set queryset = queryset.filter( Q(message__icontains=query) | Q(culprit__icontains=query)) if status is None: status_in = ( GroupStatus.PENDING_DELETION, GroupStatus.DELETION_IN_PROGRESS, GroupStatus.PENDING_MERGE, ) queryset = queryset.exclude(status__in=status_in) else: queryset = queryset.filter(status=status) if bookmarked_by: queryset = queryset.filter( bookmark_set__project=project, bookmark_set__user=bookmarked_by, ) if assigned_to: queryset = queryset.filter( assignee_set__project=project, assignee_set__user=assigned_to, ) elif unassigned in (True, False): queryset = queryset.filter( assignee_set__isnull=unassigned, ) if subscribed_by is not None: queryset = queryset.filter( id__in=GroupSubscription.objects.filter( project=project, user=subscribed_by, is_active=True, ).values_list('group'), ) if first_release: if first_release is EMPTY: return queryset.none() queryset = queryset.filter( first_release__organization_id=project.organization_id, first_release__version=first_release, ) if tags: matches = tagstore.get_group_ids_for_search_filter(project.id, environment_id, tags) if not matches: return queryset.none() queryset = queryset.filter( id__in=matches, ) if age_from or age_to: params = {} if age_from: if age_from_inclusive: params['first_seen__gte'] = age_from else: params['first_seen__gt'] = age_from if age_to: if age_to_inclusive: params['first_seen__lte'] = age_to else: params['first_seen__lt'] = age_to queryset = queryset.filter(**params) if last_seen_from or last_seen_to: params = {} if last_seen_from: if last_seen_from_inclusive: params['last_seen__gte'] = last_seen_from else: params['last_seen__gt'] = last_seen_from if last_seen_to: if last_seen_to_inclusive: params['last_seen__lte'] = last_seen_to else: params['last_seen__lt'] = last_seen_to queryset = queryset.filter(**params) if active_at_from or active_at_to: params = {} if active_at_from: if active_at_from_inclusive: params['active_at__gte'] = active_at_from else: params['active_at__gt'] = active_at_from if active_at_to: if active_at_to_inclusive: params['active_at__lte'] = active_at_to else: params['active_at__lt'] = active_at_to queryset = queryset.filter(**params) if times_seen is not None: queryset = queryset.filter(times_seen=times_seen) if times_seen_lower is not None or times_seen_upper is not None: params = {} if times_seen_lower is not None: if times_seen_lower_inclusive: params['times_seen__gte'] = times_seen_lower else: params['times_seen__gt'] = times_seen_lower if times_seen_upper is not None: if times_seen_upper_inclusive: params['times_seen__lte'] = times_seen_upper else: params['times_seen__lt'] = times_seen_upper queryset = queryset.filter(**params) if date_from or date_to: params = { 'project_id': project.id, } if date_from: if date_from_inclusive: params['datetime__gte'] = date_from else: params['datetime__gt'] = date_from if date_to: if date_to_inclusive: params['datetime__lte'] = date_to else: params['datetime__lt'] = date_to event_queryset = Event.objects.filter(**params) if query: event_queryset = event_queryset.filter( message__icontains=query) # limit to the first 1000 results group_ids = event_queryset.distinct().values_list( 'group_id', flat=True)[:1000] # if Event is not on the primary database remove Django's # implicit subquery by coercing to a list base = router.db_for_read(Group) using = router.db_for_read(Event) # MySQL also cannot do a LIMIT inside of a subquery if base != using or engine.startswith('mysql'): group_ids = list(group_ids) queryset = queryset.filter( id__in=group_ids, ) if engine.startswith('sqlite'): score_clause = SQLITE_SORT_CLAUSES[sort_by] elif engine.startswith('mysql'): score_clause = MYSQL_SORT_CLAUSES[sort_by] elif engine.startswith('oracle'): score_clause = ORACLE_SORT_CLAUSES[sort_by] elif engine in MSSQL_ENGINES: score_clause = MSSQL_SORT_CLAUSES[sort_by] else: score_clause = SORT_CLAUSES[sort_by] queryset = queryset.extra( select={'sort_value': score_clause}, ) return queryset