44from app .schemas import MetricsSchema
55from types import SimpleNamespace
66import re
7+ from marshmallow import ValidationError
78
89PRODUCT_BASE_URL = os .getenv ('PRODUCT_BASE_URL' )
910PROJECT_ENDPOINT = f"{ PRODUCT_BASE_URL } /projects"
@@ -14,17 +15,28 @@ def get_project_data(request):
1415 project_query_data = request .get_json ()
1516
1617 metric_schema = MetricsSchema ()
17- validated_query_data = metric_schema .load (
18- project_query_data )
19-
20- # if errors:
21- # return dict(status='fail', message=errors), 400
18+ try :
19+ validated_query_data = metric_schema .load (project_query_data )
20+ except ValidationError as e :
21+ # Format error messages properly
22+ error_messages = []
23+ for field , messages in e .messages .items ():
24+ for message in messages :
25+ error_messages .append (f"{ field } : { message } " )
26+ return SimpleNamespace (status = 'fail' , message = '; ' .join (error_messages ), status_code = 400 )
2227
2328 current_time = datetime .datetime .now ()
2429 yesterday_time = current_time + datetime .timedelta (days = - 1 )
2530
26- start = validated_query_data .get ('start' , yesterday_time .timestamp ())
27- end = validated_query_data .get ('end' , current_time .timestamp ())
31+ start_raw = validated_query_data .get ('start' ) or yesterday_time .timestamp ()
32+ end_raw = validated_query_data .get ('end' ) or current_time .timestamp ()
33+
34+ # Convert millisecond timestamps to seconds if needed
35+ start_val = float (start_raw )
36+ end_val = float (end_raw )
37+
38+ start = start_val / 1000 if start_val > 1e10 else start_val
39+ end = end_val / 1000 if end_val > 1e10 else end_val
2840 step = validated_query_data .get ('step' , '1h' )
2941 project_id = validated_query_data .get ('project_id' , '' )
3042 project_name = validated_query_data .get ('project_name' , '' )
@@ -67,8 +79,15 @@ def get_app_data(request):
6779 app_query_data = request .get_json ()
6880
6981 metric_schema = MetricsSchema ()
70- validated_query_data = metric_schema .load (
71- app_query_data )
82+ try :
83+ validated_query_data = metric_schema .load (app_query_data )
84+ except ValidationError as e :
85+ # Format error messages properly
86+ error_messages = []
87+ for field , messages in e .messages .items ():
88+ for message in messages :
89+ error_messages .append (f"{ field } : { message } " )
90+ return SimpleNamespace (status = 'fail' , message = '; ' .join (error_messages ), status_code = 400 )
7291
7392 app_name = validated_query_data .get ('app_name' , '' )
7493 app_id = validated_query_data .get ('app_id' , '' )
@@ -122,6 +141,26 @@ def get_app_data(request):
122141}
123142
124143
144+ def suggest_optimal_step (duration_seconds : float ) -> str :
145+ min_step_seconds = duration_seconds / MAX_DATA_POINTS
146+
147+ # Define step options in ascending order
148+ step_options = [
149+ (60 , "1m" ), (300 , "5m" ), (600 , "10m" ), (900 , "15m" ), (1800 , "30m" ),
150+ (3600 , "1h" ), (7200 , "2h" ), (10800 , "3h" ), (14400 , "4h" ), (18000 , "5h" ),
151+ (21600 , "6h" ), (43200 , "12h" ), (86400 , "1d" ), (172800 , "2d" ), (604800 , "1w" )
152+ ]
153+
154+ # Find the smallest step that satisfies the constraint
155+ for step_seconds , step_str in step_options :
156+ if step_seconds >= min_step_seconds :
157+ return step_str
158+
159+ # If even weekly steps aren't enough, calculate a custom step
160+ weeks_needed = int (min_step_seconds / 604800 ) + 1
161+ return f"{ weeks_needed } w"
162+
163+
125164def parse_step_to_seconds (step : str ) -> int :
126165 """
127166 Parses a Prometheus step string like '1m', '2h', '4d' into seconds.
@@ -134,7 +173,7 @@ def parse_step_to_seconds(step: str) -> int:
134173 return int (value ) * STEP_UNITS_IN_SECONDS [unit ]
135174
136175
137- def is_valid_prometheus_query (step : str , start_ts : int , end_ts : int ) -> ( bool , str ) :
176+ def is_valid_prometheus_query (step : str , start_ts : int , end_ts : int ) -> tuple [ bool , str ] :
138177 """
139178 Validates if the number of points in a Prometheus query is within the allowed range.
140179 """
@@ -147,9 +186,10 @@ def is_valid_prometheus_query(step: str, start_ts: int, end_ts: int) -> (bool, s
147186 return False , "Start timestamp must be less than end timestamp."
148187
149188 total_duration = end_ts - start_ts
150- num_points = total_duration // step_seconds
189+ num_points = total_duration / step_seconds
151190
152191 if num_points > MAX_DATA_POINTS :
153- return False , f"Query returns { num_points } points, which exceeds the limit of { MAX_DATA_POINTS } . Increase the step or reduce the time range."
192+ suggested_step = suggest_optimal_step (total_duration )
193+ return False , f"Query returns { num_points } points, which exceeds the limit of { MAX_DATA_POINTS } . Try using step '{ suggested_step } ' or reduce the time range."
154194
155- return True , f"Query valid: { num_points } data points."
195+ return True , f"Query valid: { int ( num_points ) } data points."
0 commit comments