@@ -49,7 +49,14 @@ struct GetRequest {
49
49
struct TimeRange {
50
50
start : DateTime < Utc > ,
51
51
end : DateTime < Utc > ,
52
- resolution_minutes : NonZeroU64 ,
52
+ resolution : TimeRangeResolution ,
53
+ }
54
+
55
+ #[ derive( Debug , Serialize , Deserialize ) ]
56
+ #[ serde( rename_all = "snake_case" ) ]
57
+ pub enum TimeRangeResolution {
58
+ Minutes ( NonZeroU64 ) ,
59
+ Slices ( NonZeroU64 ) ,
53
60
}
54
61
55
62
#[ derive( Debug , Serialize , Deserialize ) ]
@@ -73,6 +80,7 @@ enum ProjectViewsField {
73
80
Domain ,
74
81
SitePath ,
75
82
Monetized ,
83
+ Country ,
76
84
}
77
85
78
86
#[ derive( Debug , Clone , Copy , PartialEq , Eq , Serialize , Deserialize ) ]
@@ -82,6 +90,7 @@ enum ProjectDownloadsField {
82
90
VersionId ,
83
91
Domain ,
84
92
SitePath ,
93
+ Country ,
85
94
}
86
95
87
96
#[ derive( Debug , Clone , Copy , PartialEq , Eq , Serialize , Deserialize ) ]
@@ -125,6 +134,8 @@ enum ProjectMetrics {
125
134
site_path : Option < String > ,
126
135
#[ serde( skip_serializing_if = "Option::is_none" ) ]
127
136
monetized : Option < bool > ,
137
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
138
+ country : Option < String > ,
128
139
views : u64 ,
129
140
} ,
130
141
Downloads {
@@ -134,6 +145,8 @@ enum ProjectMetrics {
134
145
site_path : Option < String > ,
135
146
#[ serde( skip_serializing_if = "Option::is_none" ) ]
136
147
version_id : Option < VersionId > ,
148
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
149
+ country : Option < String > ,
137
150
downloads : u64 ,
138
151
} ,
139
152
Playtime {
@@ -174,6 +187,7 @@ mod query {
174
187
pub domain : String ,
175
188
pub site_path : String ,
176
189
pub monetized : i8 ,
190
+ pub country : String ,
177
191
pub views : u64 ,
178
192
}
179
193
@@ -182,6 +196,7 @@ mod query {
182
196
const USE_DOMAIN : & str = "{use_domain: Bool}" ;
183
197
const USE_SITE_PATH : & str = "{use_site_path: Bool}" ;
184
198
const USE_MONETIZED : & str = "{use_monetized: Bool}" ;
199
+ const USE_COUNTRY : & str = "{use_country: Bool}" ;
185
200
186
201
formatcp ! (
187
202
"SELECT
@@ -190,6 +205,7 @@ mod query {
190
205
if({USE_DOMAIN}, domain, '') AS domain,
191
206
if({USE_SITE_PATH}, site_path, '') AS site_path,
192
207
if({USE_MONETIZED}, CAST(monetized AS Int8), -1) AS monetized,
208
+ if({USE_COUNTRY}, country, '') AS country,
193
209
COUNT(*) AS views
194
210
FROM views
195
211
WHERE
@@ -199,7 +215,7 @@ mod query {
199
215
-- by using `views.project_id` instead of `project_id`
200
216
AND views.project_id IN {PROJECT_IDS}
201
217
GROUP BY
202
- bucket, project_id, domain, site_path, monetized"
218
+ bucket, project_id, domain, site_path, monetized, country "
203
219
)
204
220
} ;
205
221
@@ -210,6 +226,7 @@ mod query {
210
226
pub domain : String ,
211
227
pub site_path : String ,
212
228
pub version_id : DBVersionId ,
229
+ pub country : String ,
213
230
pub downloads : u64 ,
214
231
}
215
232
@@ -218,6 +235,7 @@ mod query {
218
235
const USE_DOMAIN : & str = "{use_domain: Bool}" ;
219
236
const USE_SITE_PATH : & str = "{use_site_path: Bool}" ;
220
237
const USE_VERSION_ID : & str = "{use_version_id: Bool}" ;
238
+ const USE_COUNTRY : & str = "{use_country: Bool}" ;
221
239
222
240
formatcp ! (
223
241
"SELECT
@@ -226,6 +244,7 @@ mod query {
226
244
if({USE_DOMAIN}, domain, '') AS domain,
227
245
if({USE_SITE_PATH}, site_path, '') AS site_path,
228
246
if({USE_VERSION_ID}, version_id, 0) AS version_id,
247
+ if({USE_COUNTRY}, country, '') AS country,
229
248
COUNT(*) AS downloads
230
249
FROM downloads
231
250
WHERE
@@ -235,7 +254,7 @@ mod query {
235
254
-- by using `downloads.project_id` instead of `project_id`
236
255
AND downloads.project_id IN {PROJECT_IDS}
237
256
GROUP BY
238
- bucket, project_id, domain, site_path, version_id"
257
+ bucket, project_id, domain, site_path, version_id, country "
239
258
)
240
259
} ;
241
260
@@ -285,7 +304,7 @@ async fn get(
285
304
clickhouse : web:: Data < clickhouse:: Client > ,
286
305
) -> Result < web:: Json < GetResponse > , ApiError > {
287
306
const MIN_RESOLUTION_MINUTES : u64 = 60 ;
288
- const MAX_TIME_SLICES : usize = 0x10000 ;
307
+ const MAX_TIME_SLICES : usize = 1024 ;
289
308
290
309
let ( scopes, user) = get_user_from_headers (
291
310
& http_req,
@@ -296,23 +315,31 @@ async fn get(
296
315
)
297
316
. await ?;
298
317
299
- let resolution_minutes = req. time_range . resolution_minutes . get ( ) ;
300
- if resolution_minutes < MIN_RESOLUTION_MINUTES {
301
- return Err ( ApiError :: InvalidInput ( format ! (
302
- "resolution must be at least {} minutes" ,
303
- MIN_RESOLUTION_MINUTES
304
- ) ) ) ;
305
- }
318
+ let num_time_slices = match req. time_range . resolution {
319
+ TimeRangeResolution :: Slices ( slices) => slices. get ( ) ,
320
+ TimeRangeResolution :: Minutes ( resolution_minutes) => {
321
+ if resolution_minutes. get ( ) < MIN_RESOLUTION_MINUTES {
322
+ return Err ( ApiError :: InvalidInput ( format ! (
323
+ "resolution must be at least {} minutes" ,
324
+ MIN_RESOLUTION_MINUTES
325
+ ) ) ) ;
326
+ }
306
327
307
- let range_minutes = u64:: try_from (
308
- ( req. time_range . end - req. time_range . start ) . num_minutes ( ) ,
309
- )
310
- . map_err ( |_| {
311
- ApiError :: InvalidInput ( "time range end must be after start" . into ( ) )
312
- } ) ?;
328
+ let range_minutes = u64:: try_from (
329
+ ( req. time_range . end - req. time_range . start ) . num_minutes ( ) ,
330
+ )
331
+ . map_err ( |_| {
332
+ ApiError :: InvalidInput (
333
+ "time range end must be after start" . into ( ) ,
334
+ )
335
+ } ) ?;
313
336
314
- let num_time_slices = usize:: try_from ( range_minutes / resolution_minutes)
337
+ range_minutes / resolution_minutes
338
+ }
339
+ } ;
340
+ let num_time_slices = usize:: try_from ( num_time_slices)
315
341
. expect ( "u64 should fit within a usize" ) ;
342
+
316
343
if num_time_slices > MAX_TIME_SLICES {
317
344
return Err ( ApiError :: InvalidInput ( format ! (
318
345
"resolution is too fine or range is too large - maximum of {MAX_TIME_SLICES} time slices"
@@ -346,9 +373,15 @@ async fn get(
346
373
( "use_domain" , uses ( F :: Domain ) ) ,
347
374
( "use_site_path" , uses ( F :: SitePath ) ) ,
348
375
( "use_monetized" , uses ( F :: Monetized ) ) ,
376
+ ( "use_country" , uses ( F :: Country ) ) ,
349
377
] ,
350
378
|row| row. bucket ,
351
379
|row| {
380
+ let country = if uses ( F :: Country ) {
381
+ Some ( condense_country ( row. country , row. views ) )
382
+ } else {
383
+ None
384
+ } ;
352
385
ProjectAnalytics {
353
386
source_project : row. project_id . into ( ) ,
354
387
metrics : ProjectMetrics :: Views {
@@ -359,6 +392,7 @@ async fn get(
359
392
1 => Some ( true ) ,
360
393
_ => None ,
361
394
} ,
395
+ country,
362
396
views : row. views ,
363
397
} ,
364
398
}
@@ -380,15 +414,22 @@ async fn get(
380
414
( "use_domain" , uses ( F :: Domain ) ) ,
381
415
( "use_site_path" , uses ( F :: SitePath ) ) ,
382
416
( "use_version_id" , uses ( F :: VersionId ) ) ,
417
+ ( "use_country" , uses ( F :: Country ) ) ,
383
418
] ,
384
419
|row| row. bucket ,
385
420
|row| {
421
+ let country = if uses ( F :: Country ) {
422
+ Some ( condense_country ( row. country , row. downloads ) )
423
+ } else {
424
+ None
425
+ } ;
386
426
ProjectAnalytics {
387
427
source_project : row. project_id . into ( ) ,
388
428
metrics : ProjectMetrics :: Downloads {
389
429
domain : none_if_empty ( row. domain ) ,
390
430
site_path : none_if_empty ( row. site_path ) ,
391
431
version_id : none_if_zero_version_id ( row. version_id ) ,
432
+ country,
392
433
downloads : row. downloads ,
393
434
} ,
394
435
}
@@ -466,8 +507,6 @@ async fn get(
466
507
)
467
508
} ) ?;
468
509
469
- tracing:: info!( "bkt = {bucket}" ) ;
470
-
471
510
if let Some ( source_project) =
472
511
row. mod_id . map ( DBProjectId ) . map ( ProjectId :: from)
473
512
&& let Some ( revenue) = row. amount_sum
@@ -496,6 +535,15 @@ fn none_if_zero_version_id(v: DBVersionId) -> Option<VersionId> {
496
535
if v. 0 == 0 { None } else { Some ( v. into ( ) ) }
497
536
}
498
537
538
+ fn condense_country ( country : String , count : u64 ) -> String {
539
+ // Every country under '50' (view or downloads) should be condensed into 'XX'
540
+ if count < 50 {
541
+ "XX" . to_string ( )
542
+ } else {
543
+ country
544
+ }
545
+ }
546
+
499
547
struct QueryClickhouseContext < ' a > {
500
548
clickhouse : & ' a clickhouse:: Client ,
501
549
req : & ' a GetRequest ,
0 commit comments