@@ -17,22 +17,15 @@ pub type PrometheusConnection = (Client, String, PrometheusTimeRange);
1717#[ serde( rename_all = "camelCase" ) ]
1818#[ ts( export) ]
1919pub struct PrometheusQueryResult {
20- series : Vec < PrometheusSeries > ,
20+ /// Columnar data for uPlot: [timestamps, series1_values, series2_values, ...]
21+ /// Timestamps are in seconds (Unix epoch)
22+ data : Vec < Vec < f64 > > ,
23+ /// Series names in order (index i corresponds to data[i+1])
24+ series_names : Vec < String > ,
2125 query_executed : String ,
2226 time_range : PrometheusTimeRange ,
2327}
2428
25- #[ derive( Debug , Clone , Serialize , TS ) ]
26- #[ serde( rename_all = "camelCase" ) ]
27- #[ ts( export) ]
28- pub struct PrometheusSeries {
29- #[ serde( rename = "type" ) ]
30- series_type : String ,
31- show_symbol : bool ,
32- name : String ,
33- data : Vec < ( f64 , f64 ) > ,
34- }
35-
3629#[ derive( Debug , Clone , Serialize , TS ) ]
3730#[ serde( rename_all = "camelCase" ) ]
3831#[ ts( export) ]
@@ -57,7 +50,7 @@ pub struct PrometheusBlockOutput {
5750impl PrometheusBlockOutput {
5851 /// Create a new PrometheusBlockOutput from query results
5952 pub fn new ( results : Vec < PrometheusQueryResult > ) -> Self {
60- let total_series = results. iter ( ) . map ( |r| r. series . len ( ) ) . sum ( ) ;
53+ let total_series = results. iter ( ) . map ( |r| r. series_names . len ( ) ) . sum ( ) ;
6154 Self {
6255 results,
6356 total_series,
@@ -82,9 +75,12 @@ impl BlockExecutionOutput for PrometheusBlockOutput {
8275 "result_count" => Some ( minijinja:: Value :: from ( self . results . len ( ) ) ) ,
8376
8477 // Convenience accessors for first result
85- "series" => self
78+ "series_names" => self
79+ . first_result ( )
80+ . map ( |r| minijinja:: Value :: from_serialize ( & r. series_names ) ) ,
81+ "data" => self
8682 . first_result ( )
87- . map ( |r| minijinja:: Value :: from_serialize ( & r. series ) ) ,
83+ . map ( |r| minijinja:: Value :: from_serialize ( & r. data ) ) ,
8884 "query_executed" => self
8985 . first_result ( )
9086 . map ( |r| minijinja:: Value :: from ( r. query_executed . clone ( ) ) ) ,
@@ -102,7 +98,8 @@ impl BlockExecutionOutput for PrometheusBlockOutput {
10298 "first" ,
10399 "total_series" ,
104100 "result_count" ,
105- "series" ,
101+ "series_names" ,
102+ "data" ,
106103 "query_executed" ,
107104 "time_range" ,
108105 ] )
@@ -418,14 +415,17 @@ impl QueryBlockBehavior for Prometheus {
418415 PrometheusBlockError :: QueryError ( "Missing result field in data" . to_string ( ) )
419416 } ) ?;
420417
421- let mut series = Vec :: new ( ) ;
418+ let mut series_names: Vec < String > = Vec :: new ( ) ;
419+ let mut all_values: Vec < Vec < f64 > > = Vec :: new ( ) ;
420+ let mut timestamps: Vec < f64 > = Vec :: new ( ) ;
422421
423422 if let Some ( result_array) = result. as_array ( ) {
424423 for series_data in result_array {
425424 if let ( Some ( metric) , Some ( values) ) = (
426425 series_data. get ( "metric" ) ,
427426 series_data. get ( "values" ) . and_then ( |v| v. as_array ( ) ) ,
428427 ) {
428+ // Build series name from metric labels
429429 let series_name = if let Some ( metric_obj) = metric. as_object ( ) {
430430 if metric_obj. is_empty ( ) {
431431 query. to_string ( )
@@ -440,32 +440,42 @@ impl QueryBlockBehavior for Prometheus {
440440 query. to_string ( )
441441 } ;
442442
443- let mut data_points = Vec :: new ( ) ;
443+ // Extract timestamps (only from first series - all share same timestamps)
444+ // and values for this series
445+ let mut series_values: Vec < f64 > = Vec :: new ( ) ;
444446 for value_pair in values {
445447 if let Some ( pair) = value_pair. as_array ( ) {
446448 if pair. len ( ) == 2 {
447- let timestamp = pair[ 0 ] . as_f64 ( ) . unwrap_or ( 0.0 ) * 1000.0 ;
449+ // Timestamps in seconds (uPlot native format)
450+ let ts = pair[ 0 ] . as_f64 ( ) . unwrap_or ( 0.0 ) ;
448451 let value = pair[ 1 ]
449452 . as_str ( )
450453 . and_then ( |s| s. parse :: < f64 > ( ) . ok ( ) )
451454 . unwrap_or ( 0.0 ) ;
452- data_points. push ( ( timestamp, value) ) ;
455+
456+ // Only collect timestamps from first series
457+ if series_names. is_empty ( ) {
458+ timestamps. push ( ts) ;
459+ }
460+ series_values. push ( value) ;
453461 }
454462 }
455463 }
456464
457- series. push ( PrometheusSeries {
458- series_type : "line" . to_string ( ) ,
459- show_symbol : false ,
460- name : series_name,
461- data : data_points,
462- } ) ;
465+ series_names. push ( series_name) ;
466+ all_values. push ( series_values) ;
463467 }
464468 }
465469 }
466470
471+ // Build columnar data: [timestamps, series1_values, series2_values, ...]
472+ let mut data: Vec < Vec < f64 > > = Vec :: with_capacity ( 1 + all_values. len ( ) ) ;
473+ data. push ( timestamps) ;
474+ data. extend ( all_values) ;
475+
467476 let result = PrometheusQueryResult {
468- series,
477+ data,
478+ series_names,
469479 query_executed : query. to_string ( ) ,
470480 time_range : time_range. clone ( ) ,
471481 } ;
0 commit comments