Skip to content

Commit 09df952

Browse files
authored
Merge pull request #203 from ActivityWatch/dev/merge-window-and-browser-queries
2 parents 7821419 + 2acb3cb commit 09df952

File tree

2 files changed

+78
-100
lines changed

2 files changed

+78
-100
lines changed

src/queries.ts

Lines changed: 60 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -39,42 +39,6 @@ function canonicalEvents(params: DesktopQueryParams): string {
3939

4040
const default_limit = 100; // Hardcoded limit per group
4141

42-
export function windowQuery(
43-
windowbucket,
44-
afkbucket,
45-
filterAFK,
46-
classes,
47-
filterCategories: string[][]
48-
): string[] {
49-
windowbucket = windowbucket.replace('"', '\\"');
50-
afkbucket = afkbucket.replace('"', '\\"');
51-
const params: DesktopQueryParams = {
52-
bid_window: windowbucket,
53-
bid_afk: afkbucket,
54-
classes: classes,
55-
filter_afk: filterAFK,
56-
};
57-
const code =
58-
`
59-
${canonicalEvents(params)}
60-
events = categorize(events, ${JSON.stringify(params.classes)});
61-
` +
62-
(filterCategories
63-
? `events = filter_keyvals(events, "$category", ${JSON.stringify(filterCategories)});`
64-
: '') +
65-
`
66-
title_events = sort_by_duration(merge_events_by_keys(events, ["app", "title"]));
67-
app_events = sort_by_duration(merge_events_by_keys(title_events, ["app"]));
68-
cat_events = sort_by_duration(merge_events_by_keys(events, ["$category"]));
69-
70-
events = sort_by_timestamp(events);
71-
app_events = limit_events(app_events, ${default_limit});
72-
title_events = limit_events(title_events, ${default_limit});
73-
duration = sum_durations(events);
74-
RETURN = {"app_events": app_events, "title_events": title_events, "cat_events": cat_events, "duration": duration, "active_events": not_afk};`;
75-
return querystr_to_array(code);
76-
}
77-
7842
export function appQuery(
7943
appbucket: string,
8044
classes,
@@ -87,11 +51,15 @@ export function appQuery(
8751
classes: classes,
8852
filter_afk: filterAFK,
8953
};
54+
55+
// Needs escaping for regex patterns like '\w' to work (JSON.stringify adds extra unecessary escaping)
56+
const classes_str = JSON.stringify(params.classes).replace('\\\\', '\\');
57+
9058
const code =
9159
`
9260
events = query_bucket("${params.bid_android}");
9361
events = merge_events_by_keys(events, ["app"]);
94-
events = categorize(events, ${JSON.stringify(params.classes)});` +
62+
events = categorize(events, ${classes_str});` +
9563
(filterCategories
9664
? `events = filter_keyvals(events, "$category", ${JSON.stringify(filterCategories)});`
9765
: '') +
@@ -109,7 +77,7 @@ export function appQuery(
10977
return querystr_to_array(code);
11078
}
11179

112-
const appnames = {
80+
const browser_appnames = {
11381
chrome: [
11482
'Google-chrome',
11583
'chrome.exe',
@@ -141,7 +109,7 @@ const appnames = {
141109
// Returns a list of (browserName, bucketId) pairs for found browser buckets
142110
function browsersWithBuckets(browserbuckets: string[]): [string, string][] {
143111
const browsername_to_bucketid: [string, string | undefined][] = _.map(
144-
Object.keys(appnames),
112+
Object.keys(browser_appnames),
145113
browserName => {
146114
const bucketId = _.find(browserbuckets, bucket_id => _.includes(bucket_id, browserName));
147115
return [browserName, bucketId];
@@ -153,35 +121,28 @@ function browsersWithBuckets(browserbuckets: string[]): [string, string][] {
153121

154122
// Returns a list of active browser events (where the browser was the active window) from all browser buckets
155123
function browserEvents(params: DesktopQueryParams): string {
156-
// If multiple browser buckets were found
157-
// AFK filtered later in the process
158124
let code = `
159-
${canonicalEvents({ ...params, filter_afk: false })}
160-
window = events;
161-
events = [];
125+
browser_events = [];
162126
`;
163127

164128
_.each(browsersWithBuckets(params.bid_browsers), ([browserName, bucketId]) => {
165-
const appnames_str = JSON.stringify(appnames[browserName]);
129+
const browser_appnames_str = JSON.stringify(browser_appnames[browserName]);
166130
code += `events_${browserName} = flood(query_bucket("${bucketId}"));
167-
window_${browserName} = filter_keyvals(window, "app", ${appnames_str});
168-
${
169-
params.filter_afk
170-
? `window_${browserName} = filter_period_intersect(window_${browserName}, not_afk);`
171-
: ''
172-
}
131+
window_${browserName} = filter_keyvals(events, "app", ${browser_appnames_str});
173132
events_${browserName} = filter_period_intersect(events_${browserName}, window_${browserName});
174133
events_${browserName} = split_url_events(events_${browserName});
175-
events = sort_by_timestamp(concat(events, events_${browserName}));`;
134+
browser_events = sort_by_timestamp(concat(browser_events, events_${browserName}));`;
176135
});
177136
return code;
178137
}
179138

180-
export function browserSummaryQuery(
139+
export function fullDesktopQuery(
181140
browserbuckets: string[],
182141
windowbucket: string,
183142
afkbucket: string,
184-
filterAFK = true
143+
filterAFK = true,
144+
classes,
145+
filterCategories: string[][]
185146
): string[] {
186147
// Escape `"`
187148
browserbuckets = _.map(browserbuckets, b => b.replace('"', '\\"'));
@@ -193,21 +154,54 @@ export function browserSummaryQuery(
193154
bid_window: windowbucket,
194155
bid_afk: afkbucket,
195156
bid_browsers: browserbuckets,
196-
classes: {},
157+
classes: classes,
197158
filter_afk: filterAFK,
198159
};
199160

161+
// Needs escaping for regex patterns like '\w' to work (JSON.stringify adds extra unecessary escaping)
162+
const classes_str = JSON.stringify(params.classes).replace('\\\\', '\\');
163+
200164
return querystr_to_array(
201-
`${browserEvents(params)}
202-
urls = merge_events_by_keys(events, ["url"]);
203-
urls = sort_by_duration(urls);
204-
urls = limit_events(urls, ${default_limit});
205-
domains = split_url_events(events);
206-
domains = merge_events_by_keys(domains, ["$domain"]);
207-
domains = sort_by_duration(domains);
208-
domains = limit_events(domains, ${default_limit});
165+
`
166+
${canonicalEvents(params)}
167+
events = categorize(events, ${classes_str});
168+
` +
169+
(filterCategories
170+
? `events = filter_keyvals(events, "$category", ${JSON.stringify(filterCategories)});`
171+
: '') +
172+
`
173+
title_events = sort_by_duration(merge_events_by_keys(events, ["app", "title"]));
174+
app_events = sort_by_duration(merge_events_by_keys(title_events, ["app"]));
175+
cat_events = sort_by_duration(merge_events_by_keys(events, ["$category"]));
176+
177+
app_events = limit_events(app_events, ${default_limit});
178+
title_events = limit_events(title_events, ${default_limit});
209179
duration = sum_durations(events);
210-
RETURN = {"domains": domains, "urls": urls, "duration": duration};`
180+
181+
${browserEvents(params)}
182+
browser_events = split_url_events(browser_events);
183+
browser_urls = merge_events_by_keys(browser_events, ["url"]);
184+
browser_urls = sort_by_duration(browser_urls);
185+
browser_urls = limit_events(browser_urls, ${default_limit});
186+
browser_domains = merge_events_by_keys(browser_events, ["$domain"]);
187+
browser_domains = sort_by_duration(browser_domains);
188+
browser_domains = limit_events(browser_domains, ${default_limit});
189+
browser_duration = sum_durations(browser_events);
190+
191+
RETURN = {
192+
"window": {
193+
"app_events": app_events,
194+
"title_events": title_events,
195+
"cat_events": cat_events,
196+
"active_events": not_afk,
197+
"duration": duration
198+
},
199+
"browser": {
200+
"domains": browser_domains,
201+
"urls": browser_urls,
202+
"duration": browser_duration
203+
}
204+
};`
211205
);
212206
}
213207

@@ -235,6 +229,7 @@ export function dailyActivityQuery(afkbucket: string): string[] {
235229
return [
236230
'afkbucket = "' + afkbucket + '";',
237231
'not_afk = flood(query_bucket(afkbucket));',
232+
'not_afk = filter_keyvals(not_afk, "status", ["not-afk"]);',
238233
'not_afk = merge_events_by_keys(not_afk, ["status"]);',
239234
'RETURN = not_afk;',
240235
];
@@ -246,8 +241,7 @@ export function dailyActivityQueryAndroid(androidbucket: string): string[] {
246241
}
247242

248243
export default {
249-
windowQuery,
250-
browserSummaryQuery,
244+
fullDesktopQuery,
251245
appQuery,
252246
dailyActivityQuery,
253247
dailyActivityQueryAndroid,

src/store/modules/activity.ts

Lines changed: 18 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ const _state = {
6363
active: {
6464
available: false,
6565
duration: 0,
66+
// non-afk events (no detail data) for the current period
6667
events: [],
68+
// Aggregated events for current and past periods
6769
history: {},
6870
},
6971

@@ -133,22 +135,14 @@ const actions = {
133135
await dispatch('set_available', query_options);
134136

135137
if (state.window.available) {
136-
await dispatch('query_window', query_options);
138+
await dispatch('query_desktop_full', query_options);
137139
} else if (state.android.available) {
138140
await dispatch('query_android', query_options);
139141
} else {
140142
console.log(
141143
'Cannot query windows as we are missing either an afk/window bucket pair or an android bucket'
142144
);
143145
await dispatch('query_window_empty', query_options);
144-
}
145-
146-
if (state.browser.available) {
147-
await dispatch('query_browser', query_options);
148-
} else {
149-
console.log(
150-
'Cannot call query_browser as we are missing either an afk, window or browser bucket'
151-
);
152146
await dispatch('query_browser_empty', query_options);
153147
}
154148

@@ -174,22 +168,6 @@ const actions = {
174168
}
175169
},
176170

177-
async query_window({ state, commit }, { timeperiod, filterAFK, filterCategories }: QueryOptions) {
178-
const periods = [timeperiodToStr(timeperiod)];
179-
const start = moment();
180-
const classes = loadClassesForQuery();
181-
const q = queries.windowQuery(
182-
state.buckets.window[0],
183-
state.buckets.afk[0],
184-
filterAFK,
185-
classes,
186-
filterCategories
187-
);
188-
const data = await this._vm.$aw.query(periods, q);
189-
commit('query_window_completed', data[0]);
190-
console.info(`Completed window query in ${moment().diff(start)}ms`);
191-
},
192-
193171
async query_android({ state, commit }, { timeperiod, filterCategories }: QueryOptions) {
194172
const periods = [timeperiodToStr(timeperiod)];
195173
const classes = loadClassesForQuery();
@@ -203,22 +181,29 @@ const actions = {
203181
app_events: [],
204182
title_events: [],
205183
cat_events: [],
206-
duration: 0,
207184
active_events: [],
185+
duration: 0,
208186
};
209187
commit('query_window_completed', data);
210188
},
211189

212-
async query_browser({ state, commit }, { timeperiod, filterAFK }: QueryOptions) {
190+
async query_desktop_full(
191+
{ state, commit },
192+
{ timeperiod, filterCategories, filterAFK }: QueryOptions
193+
) {
213194
const periods = [timeperiodToStr(timeperiod)];
214-
const q = queries.browserSummaryQuery(
195+
const classes = loadClassesForQuery();
196+
const q = queries.fullDesktopQuery(
215197
state.buckets.browser,
216198
state.buckets.window[0],
217199
state.buckets.afk[0],
218-
filterAFK
200+
filterAFK,
201+
classes,
202+
filterCategories
219203
);
220204
const data = await this._vm.$aw.query(periods, q);
221-
commit('query_browser_completed', data[0]);
205+
commit('query_browser_completed', data[0].browser);
206+
commit('query_window_completed', data[0].window);
222207
},
223208

224209
async query_browser_empty({ commit }) {
@@ -492,7 +477,6 @@ const mutations = {
492477
state.category.top = null;
493478

494479
state.active.duration = null;
495-
state.active.events = null;
496480

497481
// Ensures that active history isn't being fully reloaded on every date change
498482
// (see caching done in query_active_history and query_active_history_android)
@@ -520,9 +504,9 @@ const mutations = {
520504
},
521505

522506
query_browser_completed(state, data) {
523-
state.browser.top_domains = data['domains'];
524-
state.browser.top_urls = data['urls'];
525-
state.browser.duration = data['duration'];
507+
state.browser.top_domains = data.domains;
508+
state.browser.top_urls = data.urls;
509+
state.browser.duration = data.duration;
526510

527511
// FIXME: This one might take up a lot of size in the request, move it to a seperate request
528512
// (or remove entirely, since we have the other timeline now)

0 commit comments

Comments
 (0)