36
36
//! ```
37
37
38
38
use crate :: classes:: { CategoryId , CategorySpec } ;
39
+ use crate :: AwClient ;
39
40
use serde:: { Deserialize , Serialize } ;
40
41
41
42
/// Browser application names mapped by browser type
@@ -78,7 +79,6 @@ pub static BROWSER_APPNAMES: phf::Map<&'static str, &'static [&'static str]> = p
78
79
"vivaldi" => & [ "Vivaldi-stable" , "Vivaldi-snapshot" , "vivaldi.exe" ] ,
79
80
} ;
80
81
81
- pub const DEFAULT_LIMIT : u32 = 100 ;
82
82
83
83
/// Type alias for categorization classes
84
84
pub type ClassRule = ( CategoryId , CategorySpec ) ;
@@ -135,33 +135,6 @@ impl QueryParams {
135
135
QueryParams :: Android ( params) => build_android_canonical_events ( params) ,
136
136
}
137
137
}
138
-
139
- /// Build canonical events query string with automatic class fetching if not provided
140
- pub fn canonical_events_with_classes ( & self ) -> String {
141
- self . canonical_events_with_classes_from_server ( "localhost" , 5600 )
142
- }
143
-
144
- /// Build canonical events query string with automatic class fetching from custom server
145
- pub fn canonical_events_with_classes_from_server ( & self , host : & str , port : u16 ) -> String {
146
- match self {
147
- QueryParams :: Desktop ( params) => {
148
- let mut params_with_classes = params. clone ( ) ;
149
- if params_with_classes. base . classes . is_empty ( ) {
150
- params_with_classes. base . classes =
151
- crate :: classes:: get_classes_from_server ( host, port) ;
152
- }
153
- build_desktop_canonical_events ( & params_with_classes)
154
- }
155
- QueryParams :: Android ( params) => {
156
- let mut params_with_classes = params. clone ( ) ;
157
- if params_with_classes. base . classes . is_empty ( ) {
158
- params_with_classes. base . classes =
159
- crate :: classes:: get_classes_from_server ( host, port) ;
160
- }
161
- build_android_canonical_events ( & params_with_classes)
162
- }
163
- }
164
- }
165
138
}
166
139
167
140
/// Helper function to serialize classes in the format expected by the categorize function
@@ -201,7 +174,7 @@ fn serialize_classes(classes: &[ClassRule]) -> String {
201
174
format ! ( "[{}]" , parts. join( ", " ) )
202
175
}
203
176
204
- fn build_desktop_canonical_events ( params : & DesktopQueryParams ) -> String {
177
+ pub fn build_desktop_canonical_events ( params : & DesktopQueryParams ) -> String {
205
178
let mut query = Vec :: new ( ) ;
206
179
207
180
// Fetch window events
@@ -256,7 +229,7 @@ not_afk = period_union(not_afk, audible_events)"
256
229
query. join ( ";\n " )
257
230
}
258
231
259
- fn build_android_canonical_events ( params : & AndroidQueryParams ) -> String {
232
+ pub fn build_android_canonical_events ( params : & AndroidQueryParams ) -> String {
260
233
let mut query = Vec :: new ( ) ;
261
234
262
235
// Fetch app events
@@ -287,7 +260,7 @@ fn build_android_canonical_events(params: &AndroidQueryParams) -> String {
287
260
query. join ( ";\n " )
288
261
}
289
262
290
- fn build_browser_events ( params : & DesktopQueryParams ) -> String {
263
+ pub fn build_browser_events ( params : & DesktopQueryParams ) -> String {
291
264
let mut query = String :: from ( "browser_events = [];" ) ;
292
265
293
266
for browser_bucket in & params. base . bid_browsers {
@@ -311,10 +284,143 @@ browser_events = sort_by_timestamp(browser_events)",
311
284
query
312
285
}
313
286
314
- /// Build a full desktop query
287
+ /// Build a full desktop query using default localhost:5600 configuration
315
288
pub fn full_desktop_query ( params : & DesktopQueryParams ) -> String {
316
289
let mut query = QueryParams :: Desktop ( params. clone ( ) ) . canonical_events_with_classes ( ) ;
317
290
291
+ // Add basic event aggregations
292
+ query. push_str ( & format ! (
293
+ "
294
+ title_events = sort_by_duration(merge_events_by_keys(events, [\" app\" , \" title\" ]));
295
+ app_events = sort_by_duration(merge_events_by_keys(title_events, [\" app\" ]));
296
+ cat_events = sort_by_duration(merge_events_by_keys(events, [\" $category\" ]));
297
+ duration = sum_durations(events);
298
+ " ,
299
+ ) ) ;
300
+
301
+ // Add browser-specific query parts if browser buckets exist
302
+ if !params. base . bid_browsers . is_empty ( ) {
303
+ query. push_str ( & format ! (
304
+ "
305
+ browser_events = split_url_events(browser_events);
306
+ browser_urls = merge_events_by_keys(browser_events, [\" url\" ]);
307
+ browser_urls = sort_by_duration(browser_urls);
308
+ browser_domains = merge_events_by_keys(browser_events, [\" $domain\" ]);
309
+ browser_domains = sort_by_duration(browser_domains);
310
+ browser_duration = sum_durations(browser_events);
311
+ " ,
312
+ ) ) ;
313
+ } else {
314
+ query. push_str (
315
+ "
316
+ browser_events = [];
317
+ browser_urls = [];
318
+ browser_domains = [];
319
+ browser_duration = 0;
320
+ " ,
321
+ ) ;
322
+ }
323
+
324
+ // Add return statement
325
+ query. push_str (
326
+ "
327
+ RETURN = {
328
+ \" events\" : events,
329
+ \" window\" : {
330
+ \" app_events\" : app_events,
331
+ \" title_events\" : title_events,
332
+ \" cat_events\" : cat_events,
333
+ \" active_events\" : not_afk,
334
+ \" duration\" : duration
335
+ },
336
+ \" browser\" : {
337
+ \" domains\" : browser_domains,
338
+ \" urls\" : browser_urls,
339
+ \" duration\" : browser_duration
340
+ }
341
+ };
342
+ " ,
343
+ ) ;
344
+
345
+ query
346
+ }
347
+
348
+ /// Build a full desktop query using client configuration
349
+ pub fn full_desktop_query_from_client (
350
+ params : & DesktopQueryParams ,
351
+ client : & crate :: AwClient ,
352
+ ) -> String {
353
+ let mut query =
354
+ QueryParams :: Desktop ( params. clone ( ) ) . canonical_events_with_classes_from_client ( client) ;
355
+
356
+ // Add basic event aggregations
357
+ query. push_str ( & format ! (
358
+ "
359
+ title_events = sort_by_duration(merge_events_by_keys(events, [\" app\" , \" title\" ]));
360
+ app_events = sort_by_duration(merge_events_by_keys(title_events, [\" app\" ]));
361
+ cat_events = sort_by_duration(merge_events_by_keys(events, [\" $category\" ]));
362
+ app_events = limit_events(app_events, {});
363
+ title_events = limit_events(title_events, {});
364
+ duration = sum_durations(events);
365
+ " ,
366
+ DEFAULT_LIMIT , DEFAULT_LIMIT
367
+ ) ) ;
368
+
369
+ // Add browser-specific query parts if browser buckets exist
370
+ if !params. base . bid_browsers . is_empty ( ) {
371
+ query. push_str ( & format ! (
372
+ "
373
+ browser_events = split_url_events(browser_events);
374
+ browser_urls = merge_events_by_keys(browser_events, [\" url\" ]);
375
+ browser_urls = sort_by_duration(browser_urls);
376
+ browser_domains = merge_events_by_keys(browser_events, [\" $domain\" ]);
377
+ browser_domains = sort_by_duration(browser_domains);
378
+ browser_duration = sum_durations(browser_events);
379
+ "
380
+ ) ) ;
381
+ } else {
382
+ query. push_str (
383
+ "
384
+ browser_events = [];
385
+ browser_urls = [];
386
+ browser_domains = [];
387
+ browser_duration = 0;
388
+ " ,
389
+ ) ;
390
+ }
391
+
392
+ // Add return statement
393
+ query. push_str (
394
+ "
395
+ RETURN = {
396
+ \" events\" : events,
397
+ \" window\" : {
398
+ \" app_events\" : app_events,
399
+ \" title_events\" : title_events,
400
+ \" cat_events\" : cat_events,
401
+ \" active_events\" : not_afk,
402
+ \" duration\" : duration
403
+ },
404
+ \" browser\" : {
405
+ \" domains\" : browser_domains,
406
+ \" urls\" : browser_urls,
407
+ \" duration\" : browser_duration
408
+ }
409
+ };
410
+ " ,
411
+ ) ;
412
+
413
+ query
414
+ }
415
+
416
+ /// Build a full desktop query using blocking client configuration
417
+ pub fn full_desktop_query_from_blocking_client (
418
+ params : & DesktopQueryParams ,
419
+ client : & crate :: blocking:: AwClient ,
420
+ ) -> String {
421
+ let mut query = QueryParams :: Desktop ( params. clone ( ) )
422
+ . canonical_events_with_classes_from_blocking_client ( client) ;
423
+
318
424
// Add basic event aggregations
319
425
query. push_str ( & format ! (
320
426
"
@@ -490,4 +596,85 @@ mod tests {
490
596
assert ! ( query. contains( "events = categorize" ) ) ;
491
597
assert ! ( query. contains( "test" ) ) ;
492
598
}
599
+
600
+ #[ test]
601
+ fn test_canonical_events_with_client_config ( ) {
602
+ use crate :: AwClient ;
603
+
604
+ let params = DesktopQueryParams {
605
+ base : QueryParamsBase {
606
+ bid_browsers : vec ! [ ] ,
607
+ classes : vec ! [ ] , // Empty - would fetch from server if available
608
+ filter_classes : vec ! [ ] ,
609
+ filter_afk : true ,
610
+ include_audible : true ,
611
+ } ,
612
+ bid_window : "test-window" . to_string ( ) ,
613
+ bid_afk : "test-afk" . to_string ( ) ,
614
+ } ;
615
+
616
+ // Test with custom port client
617
+ if let Ok ( client) = AwClient :: new ( "localhost" , 8080 , "test-client" ) {
618
+ let query_params = QueryParams :: Desktop ( params. clone ( ) ) ;
619
+ let query = query_params. canonical_events_with_classes_from_client ( & client) ;
620
+
621
+ // Should contain basic query structure
622
+ assert ! ( query. contains( "events = flood" ) ) ;
623
+ assert ! ( query. contains( "test-window" ) ) ;
624
+ }
625
+
626
+ // Test with blocking client
627
+ use crate :: blocking:: AwClient as BlockingClient ;
628
+ if let Ok ( blocking_client) = BlockingClient :: new ( "localhost" , 9090 , "test-blocking-client" )
629
+ {
630
+ let query_params = QueryParams :: Desktop ( params) ;
631
+ let query =
632
+ query_params. canonical_events_with_classes_from_blocking_client ( & blocking_client) ;
633
+
634
+ // Should contain basic query structure
635
+ assert ! ( query. contains( "events = flood" ) ) ;
636
+ assert ! ( query. contains( "test-window" ) ) ;
637
+ }
638
+ }
639
+
640
+ #[ test]
641
+ fn test_full_desktop_query_from_client ( ) {
642
+ use crate :: AwClient ;
643
+
644
+ let params = DesktopQueryParams {
645
+ base : QueryParamsBase {
646
+ bid_browsers : vec ! [ "aw-watcher-web-chrome" . to_string( ) ] ,
647
+ classes : vec ! [ ] ,
648
+ filter_classes : vec ! [ ] ,
649
+ filter_afk : true ,
650
+ include_audible : true ,
651
+ } ,
652
+ bid_window : "test-window" . to_string ( ) ,
653
+ bid_afk : "test-afk" . to_string ( ) ,
654
+ } ;
655
+
656
+ // Test the client-aware full desktop query
657
+ if let Ok ( client) = AwClient :: new ( "localhost" , 8080 , "test-client" ) {
658
+ let query = full_desktop_query_from_client ( & params, & client) ;
659
+
660
+ // Should contain all expected parts
661
+ assert ! ( query. contains( "events = flood" ) ) ;
662
+ assert ! ( query. contains( "title_events = sort_by_duration" ) ) ;
663
+ assert ! ( query. contains( "browser_events" ) ) ;
664
+ assert ! ( query. contains( "RETURN" ) ) ;
665
+ }
666
+
667
+ // Test the blocking client version
668
+ use crate :: blocking:: AwClient as BlockingClient ;
669
+ if let Ok ( blocking_client) = BlockingClient :: new ( "localhost" , 9090 , "test-blocking-client" )
670
+ {
671
+ let query = full_desktop_query_from_blocking_client ( & params, & blocking_client) ;
672
+
673
+ // Should contain all expected parts
674
+ assert ! ( query. contains( "events = flood" ) ) ;
675
+ assert ! ( query. contains( "title_events = sort_by_duration" ) ) ;
676
+ assert ! ( query. contains( "browser_events" ) ) ;
677
+ assert ! ( query. contains( "RETURN" ) ) ;
678
+ }
679
+ }
493
680
}
0 commit comments