Skip to content

Commit

Permalink
Merge pull request #203 from ActivityWatch/dev/merge-window-and-brows…
Browse files Browse the repository at this point in the history
…er-queries
  • Loading branch information
ErikBjare committed Jun 10, 2020
2 parents 7821419 + 2acb3cb commit 09df952
Show file tree
Hide file tree
Showing 2 changed files with 78 additions and 100 deletions.
126 changes: 60 additions & 66 deletions src/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,42 +39,6 @@ function canonicalEvents(params: DesktopQueryParams): string {

const default_limit = 100; // Hardcoded limit per group

export function windowQuery(
windowbucket,
afkbucket,
filterAFK,
classes,
filterCategories: string[][]
): string[] {
windowbucket = windowbucket.replace('"', '\\"');
afkbucket = afkbucket.replace('"', '\\"');
const params: DesktopQueryParams = {
bid_window: windowbucket,
bid_afk: afkbucket,
classes: classes,
filter_afk: filterAFK,
};
const code =
`
${canonicalEvents(params)}
events = categorize(events, ${JSON.stringify(params.classes)});
` +
(filterCategories
? `events = filter_keyvals(events, "$category", ${JSON.stringify(filterCategories)});`
: '') +
`
title_events = sort_by_duration(merge_events_by_keys(events, ["app", "title"]));
app_events = sort_by_duration(merge_events_by_keys(title_events, ["app"]));
cat_events = sort_by_duration(merge_events_by_keys(events, ["$category"]));
events = sort_by_timestamp(events);
app_events = limit_events(app_events, ${default_limit});
title_events = limit_events(title_events, ${default_limit});
duration = sum_durations(events);
RETURN = {"app_events": app_events, "title_events": title_events, "cat_events": cat_events, "duration": duration, "active_events": not_afk};`;
return querystr_to_array(code);
}

export function appQuery(
appbucket: string,
classes,
Expand All @@ -87,11 +51,15 @@ export function appQuery(
classes: classes,
filter_afk: filterAFK,
};

// Needs escaping for regex patterns like '\w' to work (JSON.stringify adds extra unecessary escaping)
const classes_str = JSON.stringify(params.classes).replace('\\\\', '\\');

const code =
`
events = query_bucket("${params.bid_android}");
events = merge_events_by_keys(events, ["app"]);
events = categorize(events, ${JSON.stringify(params.classes)});` +
events = categorize(events, ${classes_str});` +
(filterCategories
? `events = filter_keyvals(events, "$category", ${JSON.stringify(filterCategories)});`
: '') +
Expand All @@ -109,7 +77,7 @@ export function appQuery(
return querystr_to_array(code);
}

const appnames = {
const browser_appnames = {
chrome: [
'Google-chrome',
'chrome.exe',
Expand Down Expand Up @@ -141,7 +109,7 @@ const appnames = {
// Returns a list of (browserName, bucketId) pairs for found browser buckets
function browsersWithBuckets(browserbuckets: string[]): [string, string][] {
const browsername_to_bucketid: [string, string | undefined][] = _.map(
Object.keys(appnames),
Object.keys(browser_appnames),
browserName => {
const bucketId = _.find(browserbuckets, bucket_id => _.includes(bucket_id, browserName));
return [browserName, bucketId];
Expand All @@ -153,35 +121,28 @@ function browsersWithBuckets(browserbuckets: string[]): [string, string][] {

// Returns a list of active browser events (where the browser was the active window) from all browser buckets
function browserEvents(params: DesktopQueryParams): string {
// If multiple browser buckets were found
// AFK filtered later in the process
let code = `
${canonicalEvents({ ...params, filter_afk: false })}
window = events;
events = [];
browser_events = [];
`;

_.each(browsersWithBuckets(params.bid_browsers), ([browserName, bucketId]) => {
const appnames_str = JSON.stringify(appnames[browserName]);
const browser_appnames_str = JSON.stringify(browser_appnames[browserName]);
code += `events_${browserName} = flood(query_bucket("${bucketId}"));
window_${browserName} = filter_keyvals(window, "app", ${appnames_str});
${
params.filter_afk
? `window_${browserName} = filter_period_intersect(window_${browserName}, not_afk);`
: ''
}
window_${browserName} = filter_keyvals(events, "app", ${browser_appnames_str});
events_${browserName} = filter_period_intersect(events_${browserName}, window_${browserName});
events_${browserName} = split_url_events(events_${browserName});
events = sort_by_timestamp(concat(events, events_${browserName}));`;
browser_events = sort_by_timestamp(concat(browser_events, events_${browserName}));`;
});
return code;
}

export function browserSummaryQuery(
export function fullDesktopQuery(
browserbuckets: string[],
windowbucket: string,
afkbucket: string,
filterAFK = true
filterAFK = true,
classes,
filterCategories: string[][]
): string[] {
// Escape `"`
browserbuckets = _.map(browserbuckets, b => b.replace('"', '\\"'));
Expand All @@ -193,21 +154,54 @@ export function browserSummaryQuery(
bid_window: windowbucket,
bid_afk: afkbucket,
bid_browsers: browserbuckets,
classes: {},
classes: classes,
filter_afk: filterAFK,
};

// Needs escaping for regex patterns like '\w' to work (JSON.stringify adds extra unecessary escaping)
const classes_str = JSON.stringify(params.classes).replace('\\\\', '\\');

return querystr_to_array(
`${browserEvents(params)}
urls = merge_events_by_keys(events, ["url"]);
urls = sort_by_duration(urls);
urls = limit_events(urls, ${default_limit});
domains = split_url_events(events);
domains = merge_events_by_keys(domains, ["$domain"]);
domains = sort_by_duration(domains);
domains = limit_events(domains, ${default_limit});
`
${canonicalEvents(params)}
events = categorize(events, ${classes_str});
` +
(filterCategories
? `events = filter_keyvals(events, "$category", ${JSON.stringify(filterCategories)});`
: '') +
`
title_events = sort_by_duration(merge_events_by_keys(events, ["app", "title"]));
app_events = sort_by_duration(merge_events_by_keys(title_events, ["app"]));
cat_events = sort_by_duration(merge_events_by_keys(events, ["$category"]));
app_events = limit_events(app_events, ${default_limit});
title_events = limit_events(title_events, ${default_limit});
duration = sum_durations(events);
RETURN = {"domains": domains, "urls": urls, "duration": duration};`
${browserEvents(params)}
browser_events = split_url_events(browser_events);
browser_urls = merge_events_by_keys(browser_events, ["url"]);
browser_urls = sort_by_duration(browser_urls);
browser_urls = limit_events(browser_urls, ${default_limit});
browser_domains = merge_events_by_keys(browser_events, ["$domain"]);
browser_domains = sort_by_duration(browser_domains);
browser_domains = limit_events(browser_domains, ${default_limit});
browser_duration = sum_durations(browser_events);
RETURN = {
"window": {
"app_events": app_events,
"title_events": title_events,
"cat_events": cat_events,
"active_events": not_afk,
"duration": duration
},
"browser": {
"domains": browser_domains,
"urls": browser_urls,
"duration": browser_duration
}
};`
);
}

Expand Down Expand Up @@ -235,6 +229,7 @@ export function dailyActivityQuery(afkbucket: string): string[] {
return [
'afkbucket = "' + afkbucket + '";',
'not_afk = flood(query_bucket(afkbucket));',
'not_afk = filter_keyvals(not_afk, "status", ["not-afk"]);',
'not_afk = merge_events_by_keys(not_afk, ["status"]);',
'RETURN = not_afk;',
];
Expand All @@ -246,8 +241,7 @@ export function dailyActivityQueryAndroid(androidbucket: string): string[] {
}

export default {
windowQuery,
browserSummaryQuery,
fullDesktopQuery,
appQuery,
dailyActivityQuery,
dailyActivityQueryAndroid,
Expand Down
52 changes: 18 additions & 34 deletions src/store/modules/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ const _state = {
active: {
available: false,
duration: 0,
// non-afk events (no detail data) for the current period
events: [],
// Aggregated events for current and past periods
history: {},
},

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

if (state.window.available) {
await dispatch('query_window', query_options);
await dispatch('query_desktop_full', query_options);
} else if (state.android.available) {
await dispatch('query_android', query_options);
} else {
console.log(
'Cannot query windows as we are missing either an afk/window bucket pair or an android bucket'
);
await dispatch('query_window_empty', query_options);
}

if (state.browser.available) {
await dispatch('query_browser', query_options);
} else {
console.log(
'Cannot call query_browser as we are missing either an afk, window or browser bucket'
);
await dispatch('query_browser_empty', query_options);
}

Expand All @@ -174,22 +168,6 @@ const actions = {
}
},

async query_window({ state, commit }, { timeperiod, filterAFK, filterCategories }: QueryOptions) {
const periods = [timeperiodToStr(timeperiod)];
const start = moment();
const classes = loadClassesForQuery();
const q = queries.windowQuery(
state.buckets.window[0],
state.buckets.afk[0],
filterAFK,
classes,
filterCategories
);
const data = await this._vm.$aw.query(periods, q);
commit('query_window_completed', data[0]);
console.info(`Completed window query in ${moment().diff(start)}ms`);
},

async query_android({ state, commit }, { timeperiod, filterCategories }: QueryOptions) {
const periods = [timeperiodToStr(timeperiod)];
const classes = loadClassesForQuery();
Expand All @@ -203,22 +181,29 @@ const actions = {
app_events: [],
title_events: [],
cat_events: [],
duration: 0,
active_events: [],
duration: 0,
};
commit('query_window_completed', data);
},

async query_browser({ state, commit }, { timeperiod, filterAFK }: QueryOptions) {
async query_desktop_full(
{ state, commit },
{ timeperiod, filterCategories, filterAFK }: QueryOptions
) {
const periods = [timeperiodToStr(timeperiod)];
const q = queries.browserSummaryQuery(
const classes = loadClassesForQuery();
const q = queries.fullDesktopQuery(
state.buckets.browser,
state.buckets.window[0],
state.buckets.afk[0],
filterAFK
filterAFK,
classes,
filterCategories
);
const data = await this._vm.$aw.query(periods, q);
commit('query_browser_completed', data[0]);
commit('query_browser_completed', data[0].browser);
commit('query_window_completed', data[0].window);
},

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

state.active.duration = null;
state.active.events = null;

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

query_browser_completed(state, data) {
state.browser.top_domains = data['domains'];
state.browser.top_urls = data['urls'];
state.browser.duration = data['duration'];
state.browser.top_domains = data.domains;
state.browser.top_urls = data.urls;
state.browser.duration = data.duration;

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

0 comments on commit 09df952

Please sign in to comment.