def is_real_column(col): """ Return true if col corresponds to an actual column to be fetched (not an aggregate function or field alias) """ if is_function(col): return False if col in FIELD_ALIASES: return False return True
def get(self, request, organization): if not self.has_feature(request, organization): return Response(status=404) try: params = self.get_snuba_params(request, organization) except NoProjects: return Response([]) maybe_aggregate = request.GET.get("field") if not maybe_aggregate: raise ParseError(detail="No column selected") if not is_function(maybe_aggregate): raise ParseError(detail="Functions may only be given") referrer = request.GET.get("referrer") referrer = (referrer if referrer in ALLOWED_EVENTS_GEO_REFERRERS else "api.organization-events-geo") def data_fn(offset, limit): return discover.query( selected_columns=["geo.country_code", maybe_aggregate], query=f"{request.GET.get('query', '')} has:geo.country_code", params=params, offset=offset, limit=limit, referrer=referrer, use_aggregate_conditions=True, ) with self.handle_query_errors(): # We don't need pagination, so we don't include the cursor headers return Response( self.handle_results_with_meta( request, organization, params["project_id"], # Expect Discover query output to be at most 251 rows, which corresponds # to the number of possible two-letter country codes as defined in ISO 3166-1 alpha-2. # # There are 250 country codes from sentry/src/sentry/static/sentry/app/data/countryCodesMap.tsx # plus events with no assigned country code. data_fn( 0, self.get_per_page(request, default_per_page=251, max_per_page=251)), ))
def find_histogram_buckets(field, params, conditions): match = is_function(field) if not match: raise InvalidSearchQuery( u"received {}, expected histogram function".format(field)) columns = [ c.strip() for c in match.group("columns").split(",") if len(c.strip()) > 0 ] if len(columns) != 2: raise InvalidSearchQuery( u"histogram(...) expects 2 column arguments, received {:g} arguments" .format(len(columns))) column = columns[0] # TODO evanh: This can be expanded to more fields at a later date, for now keep this limited. if column != "transaction.duration": raise InvalidSearchQuery( "histogram(...) can only be used with the transaction.duration column" ) try: num_buckets = int(columns[1]) if num_buckets < 1 or num_buckets > 500: raise Exception() except Exception: raise InvalidSearchQuery( u"histogram(...) requires a bucket value between 1 and 500, not {}" .format(columns[1])) max_alias = u"max_{}".format(column) min_alias = u"min_{}".format(column) conditions = deepcopy(conditions) if conditions else [] found = False for cond in conditions: if (cond[0], cond[1], cond[2]) == ("event.type", "=", "transaction"): found = True if not found: conditions.append(["event.type", "=", "transaction"]) snuba_filter = eventstore.Filter(conditions=conditions) translated_args, _ = resolve_discover_aliases(snuba_filter) results = raw_query( filter_keys={"project_id": params.get("project_id")}, start=params.get("start"), end=params.get("end"), dataset=Dataset.Discover, conditions=translated_args.conditions, aggregations=[["max", "duration", max_alias], ["min", "duration", min_alias]], ) if len(results["data"]) != 1: # If there are no transactions, so no max duration, return one empty bucket return "histogram({}, 1, 1, 0)".format(column) bucket_min = results["data"][0][min_alias] bucket_max = results["data"][0][max_alias] if bucket_max == 0: raise InvalidSearchQuery( u"Cannot calculate histogram for {}".format(field)) bucket_size = ceil((bucket_max - bucket_min) / float(num_buckets)) if bucket_size == 0.0: bucket_size = 1.0 # Determine the first bucket that will show up in our results so that we can # zerofill correctly. offset = floor(bucket_min / bucket_size) * bucket_size return "histogram({}, {:g}, {:g}, {:g})".format(column, num_buckets, bucket_size, offset)