def validate(self, data): if not data.get("id"): keys = set(data.keys()) if self.required_for_create - keys: raise serializers.ValidationError( { "fields": "fields are required during creation.", "conditions": "conditions are required during creation.", } ) # Validate the query that would be created when run. conditions = self._get_attr(data, "conditions", "") fields = self._get_attr(data, "fields", []).copy() orderby = self._get_attr(data, "orderby", "") equations, fields = categorize_columns(fields) if equations is not None: resolved_equations, _ = resolve_equation_list(equations, fields) else: resolved_equations = [] try: # When using the eps/epm functions, they require an interval argument # or to provide the start/end so that the interval can be computed. # This uses a hard coded start/end to ensure the validation succeeds # since the values themselves don't matter. params = { "start": datetime.now() - timedelta(days=1), "end": datetime.now(), "project_id": [p.id for p in self.context.get("projects")], "organization_id": self.context.get("organization").id, } snuba_filter = get_filter(conditions, params=params) except InvalidSearchQuery as err: raise serializers.ValidationError({"conditions": f"Invalid conditions: {err}"}) if orderby: snuba_filter.orderby = get_function_alias(orderby) try: resolve_field_list(fields, snuba_filter, resolved_equations=resolved_equations) except InvalidSearchQuery as err: raise serializers.ValidationError({"fields": f"Invalid fields: {err}"}) return data
def __init__( self, dataset: Dataset, params: ParamsType, granularity: int, top_events: List[Dict[str, Any]], other: bool = False, query: Optional[str] = None, selected_columns: Optional[List[str]] = None, timeseries_columns: Optional[List[str]] = None, equations: Optional[List[str]] = None, functions_acl: Optional[List[str]] = None, limit: Optional[int] = 10000, ): selected_columns = [] if selected_columns is None else selected_columns timeseries_columns = [] if timeseries_columns is None else timeseries_columns equations = [] if equations is None else equations timeseries_equations, timeseries_functions = categorize_columns( timeseries_columns) super().__init__( dataset, params, granularity=granularity, query=query, selected_columns=list(set(selected_columns + timeseries_functions)), equations=list(set(equations + timeseries_equations)), functions_acl=functions_acl, limit=limit, ) self.fields: List[ str] = selected_columns if selected_columns is not None else [] if (conditions := self.resolve_top_event_conditions(top_events, other)) is not None: self.where.append(conditions)
def validate(self, data): if not data.get("id"): keys = set(data.keys()) if self.required_for_create - keys: raise serializers.ValidationError({ "fields": "fields are required during creation.", "conditions": "conditions are required during creation.", }) # Validate the query that would be created when run. conditions = self._get_attr(data, "conditions", "") fields = self._get_attr(data, "fields", []).copy() orderby = self._get_attr(data, "orderby", "") equations, fields = categorize_columns(fields) is_table = is_table_display_type(self.context.get("displayType")) if equations is not None: try: resolved_equations, _, _ = resolve_equation_list( equations, fields, auto_add=not is_table, aggregates_only=not is_table, ) except (InvalidSearchQuery, ArithmeticError) as err: raise serializers.ValidationError( {"fields": f"Invalid fields: {err}"}) else: resolved_equations = [] try: parse_search_query(conditions) except InvalidSearchQuery as err: # We don't know if the widget that this query belongs to is an # Issue widget or Discover widget. Pass the error back to the # Widget serializer to decide if whether or not to raise this # error based on the Widget's type data["issue_query_error"] = { "conditions": [f"Invalid conditions: {err}"] } try: # When using the eps/epm functions, they require an interval argument # or to provide the start/end so that the interval can be computed. # This uses a hard coded start/end to ensure the validation succeeds # since the values themselves don't matter. params = { "start": datetime.now() - timedelta(days=1), "end": datetime.now(), "project_id": [p.id for p in self.context.get("projects")], "organization_id": self.context.get("organization").id, } snuba_filter = get_filter(conditions, params=params) except InvalidSearchQuery as err: data["discover_query_error"] = { "conditions": [f"Invalid conditions: {err}"] } return data if orderby: snuba_filter.orderby = get_function_alias(orderby) try: resolve_field_list(fields, snuba_filter, resolved_equations=resolved_equations) except InvalidSearchQuery as err: # We don't know if the widget that this query belongs to is an # Issue widget or Discover widget. Pass the error back to the # Widget serializer to decide if whether or not to raise this # error based on the Widget's type data["discover_query_error"] = {"fields": f"Invalid fields: {err}"} return data
def validate(self, data): organization = self.context["organization"] query_info = data["query_info"] # Validate the project field, if provided # A PermissionDenied error will be raised in `get_projects_by_id` if the request is invalid project_query = query_info.get("project") if project_query: get_projects_by_id = self.context["get_projects_by_id"] # Coerce the query into a set if isinstance(project_query, list): projects = get_projects_by_id(set(map(int, project_query))) else: projects = get_projects_by_id({int(project_query)}) query_info["project"] = [project.id for project in projects] # Discover Pre-processing if data["query_type"] == ExportQueryType.DISCOVER_STR: # coerce the fields into a list as needed base_fields = query_info.get("field", []) if not isinstance(base_fields, list): base_fields = [base_fields] equations, fields = categorize_columns(base_fields) if len(base_fields) > MAX_FIELDS: detail = f"You can export up to {MAX_FIELDS} fields at a time. Please delete some and try again." raise serializers.ValidationError(detail) elif len(base_fields) == 0: raise serializers.ValidationError( "at least one field is required to export") if "query" not in query_info: detail = "query is a required to export, please pass an empty string if you don't want to set one" raise serializers.ValidationError(detail) query_info["field"] = fields query_info["equations"] = equations if not query_info.get("project"): projects = self.context["get_projects"]() query_info["project"] = [project.id for project in projects] # make sure to fix the export start/end times to ensure consistent results try: start, end = get_date_range_from_params(query_info) except InvalidParams as e: sentry_sdk.set_tag("query.error_reason", "Invalid date params") raise serializers.ValidationError(str(e)) if "statsPeriod" in query_info: del query_info["statsPeriod"] if "statsPeriodStart" in query_info: del query_info["statsPeriodStart"] if "statsPeriodEnd" in query_info: del query_info["statsPeriodEnd"] query_info["start"] = start.isoformat() query_info["end"] = end.isoformat() query_info["use_snql"] = features.has( "organizations:discover-use-snql", organization) # validate the query string by trying to parse it processor = DiscoverProcessor( discover_query=query_info, organization_id=organization.id, ) try: snuba_filter = get_filter(query_info["query"], processor.params) if len(equations) > 0: resolved_equations, _, _ = resolve_equation_list( equations, fields) else: resolved_equations = [] resolve_field_list( fields.copy(), snuba_filter, auto_fields=True, auto_aggregations=True, resolved_equations=resolved_equations, ) except InvalidSearchQuery as err: raise serializers.ValidationError(str(err)) return data
def timeseries_query( selected_columns: Sequence[str], query: str, params: Dict[str, str], rollup: int, referrer: str, zerofill_results: bool = True, comparison_delta: Optional[timedelta] = None, functions_acl: Optional[List[str]] = None, use_snql: Optional[bool] = False, ) -> SnubaTSResult: """ High-level API for doing arbitrary user timeseries queries against events. this API should match that of sentry.snuba.discover.timeseries_query """ metrics_compatible = False equations, columns = categorize_columns(selected_columns) if comparison_delta is None and not equations: metrics_compatible = True if metrics_compatible: try: metrics_query = TimeseriesMetricQueryBuilder( params, rollup, query=query, selected_columns=columns, functions_acl=functions_acl, ) result = metrics_query.run_query(referrer + ".metrics-enhanced") result = discover.transform_results(result, metrics_query.function_alias_map, {}, None) result["data"] = ( discover.zerofill( result["data"], params["start"], params["end"], rollup, "time", ) if zerofill_results else result["data"] ) return SnubaTSResult( {"data": result["data"], "isMetricsData": True}, params["start"], params["end"], rollup, ) # raise Invalid Queries since the same thing will happen with discover except InvalidSearchQuery as error: raise error # any remaining errors mean we should try again with discover except IncompatibleMetricsQuery: metrics_compatible = False # This isn't a query we can enhance with metrics if not metrics_compatible: return discover.timeseries_query( selected_columns, query, params, rollup, referrer, zerofill_results, comparison_delta, functions_acl, use_snql, ) return SnubaTSResult()