3636//! ```
3737
3838use crate :: classes:: { CategoryId , CategorySpec } ;
39+ use crate :: AwClient ;
3940use serde:: { Deserialize , Serialize } ;
4041
4142/// Browser application names mapped by browser type
@@ -78,7 +79,6 @@ pub static BROWSER_APPNAMES: phf::Map<&'static str, &'static [&'static str]> = p
7879 "vivaldi" => & [ "Vivaldi-stable" , "Vivaldi-snapshot" , "vivaldi.exe" ] ,
7980} ;
8081
81- pub const DEFAULT_LIMIT : u32 = 100 ;
8282
8383/// Type alias for categorization classes
8484pub type ClassRule = ( CategoryId , CategorySpec ) ;
@@ -135,33 +135,6 @@ impl QueryParams {
135135 QueryParams :: Android ( params) => build_android_canonical_events ( params) ,
136136 }
137137 }
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- }
165138}
166139
167140/// Helper function to serialize classes in the format expected by the categorize function
@@ -201,7 +174,7 @@ fn serialize_classes(classes: &[ClassRule]) -> String {
201174 format ! ( "[{}]" , parts. join( ", " ) )
202175}
203176
204- fn build_desktop_canonical_events ( params : & DesktopQueryParams ) -> String {
177+ pub fn build_desktop_canonical_events ( params : & DesktopQueryParams ) -> String {
205178 let mut query = Vec :: new ( ) ;
206179
207180 // Fetch window events
@@ -256,7 +229,7 @@ not_afk = period_union(not_afk, audible_events)"
256229 query. join ( ";\n " )
257230}
258231
259- fn build_android_canonical_events ( params : & AndroidQueryParams ) -> String {
232+ pub fn build_android_canonical_events ( params : & AndroidQueryParams ) -> String {
260233 let mut query = Vec :: new ( ) ;
261234
262235 // Fetch app events
@@ -287,7 +260,7 @@ fn build_android_canonical_events(params: &AndroidQueryParams) -> String {
287260 query. join ( ";\n " )
288261}
289262
290- fn build_browser_events ( params : & DesktopQueryParams ) -> String {
263+ pub fn build_browser_events ( params : & DesktopQueryParams ) -> String {
291264 let mut query = String :: from ( "browser_events = [];" ) ;
292265
293266 for browser_bucket in & params. base . bid_browsers {
@@ -311,10 +284,143 @@ browser_events = sort_by_timestamp(browser_events)",
311284 query
312285}
313286
314- /// Build a full desktop query
287+ /// Build a full desktop query using default localhost:5600 configuration
315288pub fn full_desktop_query ( params : & DesktopQueryParams ) -> String {
316289 let mut query = QueryParams :: Desktop ( params. clone ( ) ) . canonical_events_with_classes ( ) ;
317290
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+
318424 // Add basic event aggregations
319425 query. push_str ( & format ! (
320426 "
@@ -490,4 +596,85 @@ mod tests {
490596 assert ! ( query. contains( "events = categorize" ) ) ;
491597 assert ! ( query. contains( "test" ) ) ;
492598 }
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+ }
493680}
0 commit comments