@@ -7,7 +7,7 @@ use crate::{
7
7
web:: { error:: Nope , match_version, page:: WebPage , redirect_base} ,
8
8
BuildQueue , Config ,
9
9
} ;
10
- use chrono:: { DateTime , Utc } ;
10
+ use chrono:: { DateTime , NaiveDate , Utc } ;
11
11
use iron:: {
12
12
headers:: { ContentType , Expires , HttpDate } ,
13
13
mime:: { Mime , SubLevel , TopLevel } ,
@@ -17,7 +17,6 @@ use iron::{
17
17
use postgres:: Client ;
18
18
use router:: Router ;
19
19
use serde:: Serialize ;
20
- use serde_json:: Value ;
21
20
22
21
/// Number of release in home page
23
22
const RELEASES_IN_HOME : i64 = 15 ;
@@ -659,7 +658,9 @@ pub fn search_handler(req: &mut Request) -> IronResult<Response> {
659
658
#[ derive( Debug , Clone , PartialEq , Serialize ) ]
660
659
struct ReleaseActivity {
661
660
description : & ' static str ,
662
- activity_data : Value ,
661
+ dates : Vec < String > ,
662
+ counts : Vec < i64 > ,
663
+ failures : Vec < i64 > ,
663
664
}
664
665
665
666
impl_webpage ! {
@@ -668,20 +669,58 @@ impl_webpage! {
668
669
669
670
pub fn activity_handler ( req : & mut Request ) -> IronResult < Response > {
670
671
let mut conn = extension ! ( req, Pool ) . get ( ) ?;
671
- let activity_data: Value = ctry ! (
672
+
673
+ let data: Vec < ( NaiveDate , i64 , i64 ) > = ctry ! (
672
674
req,
673
675
conn. query(
674
- "SELECT value FROM config WHERE name = 'release_activity'" ,
675
- & [ ]
676
- ) ,
676
+ "
677
+ WITH dates AS (
678
+ -- we need this series so that days in the statistic that don't have any releases are included
679
+ SELECT generate_series(
680
+ CURRENT_DATE - INTERVAL '30 days',
681
+ CURRENT_DATE - INTERVAL '1 day',
682
+ '1 day'::interval
683
+ )::date AS date_
684
+ ),
685
+ release_stats AS (
686
+ SELECT
687
+ release_time::date AS date_,
688
+ COUNT(*) AS counts,
689
+ SUM(CAST((is_library = TRUE AND build_status = FALSE) AS INT)) AS failures
690
+ FROM
691
+ releases
692
+ WHERE
693
+ release_time >= CURRENT_DATE - INTERVAL '30 days' AND
694
+ release_time < CURRENT_DATE
695
+ GROUP BY
696
+ release_time::date
697
+ )
698
+ SELECT
699
+ dates.date_ AS date,
700
+ COALESCE(rs.counts, 0) AS counts,
701
+ COALESCE(rs.failures, 0) AS failures
702
+ FROM
703
+ dates
704
+ LEFT OUTER JOIN Release_stats AS rs ON dates.date_ = rs.date_
705
+
706
+ ORDER BY
707
+ dates.date_
708
+ " ,
709
+ & [ ] ,
710
+ )
677
711
)
678
- . iter ( )
679
- . next ( )
680
- . map_or ( Value :: Null , |row| row . get ( "value" ) ) ;
712
+ . into_iter ( )
713
+ . map ( |row| ( row . get ( 0 ) , row . get ( 1 ) , row . get ( 2 ) ) )
714
+ . collect ( ) ;
681
715
682
716
ReleaseActivity {
683
717
description : "Monthly release activity" ,
684
- activity_data,
718
+ dates : data
719
+ . iter ( )
720
+ . map ( |& d| d. 0 . format ( "%d %b" ) . to_string ( ) )
721
+ . collect ( ) ,
722
+ counts : data. iter ( ) . map ( |& d| d. 1 ) . collect ( ) ,
723
+ failures : data. iter ( ) . map ( |& d| d. 2 ) . collect ( ) ,
685
724
}
686
725
. into_response ( req)
687
726
}
@@ -716,7 +755,7 @@ pub fn build_queue_handler(req: &mut Request) -> IronResult<Response> {
716
755
mod tests {
717
756
use super :: * ;
718
757
use crate :: test:: { assert_redirect, assert_success, wrapper, TestFrontend } ;
719
- use chrono:: TimeZone ;
758
+ use chrono:: { Duration , TimeZone } ;
720
759
use failure:: Error ;
721
760
use kuchiki:: traits:: TendrilSink ;
722
761
use std:: collections:: HashSet ;
@@ -1305,7 +1344,44 @@ mod tests {
1305
1344
fn release_activity ( ) {
1306
1345
wrapper ( |env| {
1307
1346
let web = env. frontend ( ) ;
1308
- assert_success ( "/releases/activity" , web) ?;
1347
+
1348
+ let empty_data = format ! ( "data: [{}]" , vec![ "0" ; 30 ] . join( "," ) ) ;
1349
+
1350
+ // no data / only zeros without releases
1351
+ let response = web. get ( "/releases/activity/" ) . send ( ) ?;
1352
+ assert ! ( response. status( ) . is_success( ) ) ;
1353
+ assert_eq ! ( response. text( ) . unwrap( ) . matches( & empty_data) . count( ) , 2 ) ;
1354
+
1355
+ env. fake_release ( ) . name ( "some_random_crate" ) . create ( ) ?;
1356
+ env. fake_release ( )
1357
+ . name ( "some_random_crate_that_failed" )
1358
+ . build_result_failed ( )
1359
+ . create ( ) ?;
1360
+
1361
+ // same when the release is on the current day, since we ignore today.
1362
+ let response = web. get ( "/releases/activity/" ) . send ( ) ?;
1363
+ assert ! ( response. status( ) . is_success( ) ) ;
1364
+ assert_eq ! ( response. text( ) . unwrap( ) . matches( & empty_data) . count( ) , 2 ) ;
1365
+
1366
+ env. fake_release ( )
1367
+ . name ( "some_random_crate_yesterday" )
1368
+ . release_time ( Utc :: now ( ) - Duration :: days ( 1 ) )
1369
+ . create ( ) ?;
1370
+ env. fake_release ( )
1371
+ . name ( "some_random_crate_that_failed_yesterday" )
1372
+ . build_result_failed ( )
1373
+ . release_time ( Utc :: now ( ) - Duration :: days ( 1 ) )
1374
+ . create ( ) ?;
1375
+
1376
+ // with releases yesterday we get the data we want
1377
+ let response = web. get ( "/releases/activity/" ) . send ( ) ?;
1378
+ assert ! ( response. status( ) . is_success( ) ) ;
1379
+ let text = response. text ( ) . unwrap ( ) ;
1380
+ // counts contain both releases
1381
+ assert ! ( text. contains( & format!( "data: [{},2]" , vec![ "0" ; 29 ] . join( "," ) ) ) ) ;
1382
+ // failures only one
1383
+ assert ! ( text. contains( & format!( "data: [{},1]" , vec![ "0" ; 29 ] . join( "," ) ) ) ) ;
1384
+
1309
1385
Ok ( ( ) )
1310
1386
} )
1311
1387
}
0 commit comments