Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat: ported new Activity view to Android
  • Loading branch information
ErikBjare committed Apr 27, 2020
1 parent 353634b commit 13777cb
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 50 deletions.
17 changes: 9 additions & 8 deletions src/components/Header.vue
Expand Up @@ -77,17 +77,11 @@ import 'vue-awesome/icons/desktop';
import _ from 'lodash';
// Set this to true to test Android behavior when on a desktop
const testingAndroid = false;
export default {
name: 'Header',
data() {
return {
activityViews: [],
isAndroidApp:
testingAndroid ||
(navigator.userAgent.includes('Android') && navigator.userAgent.includes('wv')), // Checks for Android and WebView
};
},
mounted: async function() {
Expand All @@ -98,7 +92,7 @@ export default {
// The '&& true;' is just to typecoerce into booleans
types_by_host[v.hostname].afk |= v.type == 'afkstatus';
types_by_host[v.hostname].window |= v.type == 'currentwindow';
types_by_host[v.hostname].android |= v.type == 'currentwindow' && this.isAndroidApp; // Use other bucket type ID in the future
types_by_host[v.hostname].android |= v.type == 'currentwindow' && v.id.includes('android'); // Use other bucket type ID in the future
});
//console.log(types_by_host);
Expand All @@ -112,14 +106,21 @@ export default {
icon: 'desktop',
});
}
if (testingAndroid || types.android) {
if (types.android) {
this.activityViews.push({
name: `${hostname} (Android)`,
hostname: hostname,
type: 'android',
pathUrl: '/activity/android',
icon: 'mobile',
});
this.activityViews.push({
name: `${hostname} (Android, new)`,
hostname: hostname,
type: 'android',
pathUrl: `/activity/${hostname}`,
icon: 'mobile',
});
}
});
},
Expand Down
9 changes: 5 additions & 4 deletions src/main.js
Expand Up @@ -23,6 +23,11 @@ import 'typeface-varela-round';
// Loads all the filters
import './util/filters.js';

// Create an instance of AWClient as this.$aw
import awclient from './util/awclient.js';
console.log(awclient);
Vue.prototype.$aw = awclient;

// Sets up the routing and the base app (using vue-router)
import router from './route.js';

Expand Down Expand Up @@ -54,10 +59,6 @@ Vue.component('aw-timeline-barchart', () => import('./visualizations/TimelineBar
// A mixin to make async method errors propagate
Vue.mixin(require('~/mixins/asyncErrorCaptured.js'));

// Create an instance of AWClient as this.$aw
import awclient from './util/awclient.js';
Vue.prototype.$aw = awclient;

// Set the PRODUCTION constant
Vue.prototype.PRODUCTION = PRODUCTION;

Expand Down
71 changes: 45 additions & 26 deletions src/queries.ts
Expand Up @@ -9,18 +9,26 @@ function querystr_to_array(querystr: string): string[] {
.map(l => l + ';');
}

interface QueryParams {
bid_window: string;
bid_afk: string;
bid_browsers?: string[];
interface BaseQueryParams {
filter_afk: boolean;
include_audible?: boolean;
classes: Record<string, any>;
}

interface DesktopQueryParams extends BaseQueryParams {
bid_window: string;
bid_afk: string;
bid_browsers?: string[];
}

interface AndroidQueryParams extends BaseQueryParams {
bid_android: string;
bid_browsers?: string[];
}

// Constructs a query that returns a fully-detailed list of events from the merging of several sources (window, afk, web).
// Puts it's results in `not_afk` and `events`.
function canonicalEvents(params: QueryParams): string {
function canonicalEvents(params: DesktopQueryParams): string {
return `
events = flood(query_bucket("${params.bid_window}"));
not_afk = flood(query_bucket("${params.bid_afk}"));
Expand All @@ -29,18 +37,18 @@ function canonicalEvents(params: QueryParams): string {
`;
}

const default_limit = 100; // Hardcoded limit per group

export function windowQuery(
windowbucket,
afkbucket,
appcount,
titlecount,
filterAFK,
classes,
filterCategories: string[][]
): string[] {
windowbucket = windowbucket.replace('"', '\\"');
afkbucket = afkbucket.replace('"', '\\"');
const params: QueryParams = {
const params: DesktopQueryParams = {
bid_window: windowbucket,
bid_afk: afkbucket,
classes: classes,
Expand All @@ -60,22 +68,34 @@ export function windowQuery(
cat_events = sort_by_duration(merge_events_by_keys(events, ["$category"]));
events = sort_by_timestamp(events);
app_events = limit_events(app_events, ${appcount});
title_events = limit_events(title_events, ${titlecount});
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, limit = 5): string[] {
export function appQuery(appbucket: string, classes, filterAFK): string[] {
appbucket = appbucket.replace('"', '\\"');
const params: AndroidQueryParams = {
bid_android: appbucket,
classes: classes,
filter_afk: filterAFK,
};
const code = `
events = query_bucket("${appbucket}");
events = merge_events_by_keys(events, ["app"]);
events = sort_by_duration(events);
events = limit_events(events, ${limit});
total_duration = sum_durations(events);
RETURN = {"events": events, "total_duration": total_duration};
events = query_bucket("${params.bid_android}");
events = merge_events_by_keys(events, ["app"]);
events = categorize(events, ${JSON.stringify(params.classes)});
title_events = sort_by_duration(merge_events_by_keys(events, ["app", "classname"]));
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": app_events};
`;
return querystr_to_array(code);
}
Expand Down Expand Up @@ -122,7 +142,7 @@ 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: QueryParams): string {
function browserEvents(params: DesktopQueryParams): string {
// If multiple browser buckets were found
// AFK filtered later in the process
let code = `
Expand Down Expand Up @@ -151,7 +171,6 @@ export function browserSummaryQuery(
browserbuckets: string[],
windowbucket: string,
afkbucket: string,
limit = 5,
filterAFK = true
): string[] {
// Escape `"`
Expand All @@ -160,7 +179,7 @@ export function browserSummaryQuery(
afkbucket = afkbucket.replace('"', '\\"');

// TODO: Get classes
const params: QueryParams = {
const params: DesktopQueryParams = {
bid_window: windowbucket,
bid_afk: afkbucket,
bid_browsers: browserbuckets,
Expand All @@ -172,29 +191,29 @@ export function browserSummaryQuery(
`${browserEvents(params)}
urls = merge_events_by_keys(events, ["url"]);
urls = sort_by_duration(urls);
urls = limit_events(urls, ${limit});
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, ${limit});
domains = limit_events(domains, ${default_limit});
duration = sum_durations(events);
RETURN = {"domains": domains, "urls": urls, "duration": duration};`
);
}

export function editorActivityQuery(editorbuckets: string[], limit): string[] {
export function editorActivityQuery(editorbuckets: string[]): string[] {
let q = ['events = [];'];
for (let editorbucket of editorbuckets) {
editorbucket = editorbucket.replace('"', '\\"');
q.push('events = concat(events, flood(query_bucket("' + editorbucket + '")));');
}
q = q.concat([
'files = sort_by_duration(merge_events_by_keys(events, ["file", "language"]));',
'files = limit_events(files, ' + limit + ');',
`files = limit_events(files, ${default_limit});`,
'languages = sort_by_duration(merge_events_by_keys(events, ["language"]));',
'languages = limit_events(languages, ' + limit + ');',
`languages = limit_events(languages, ${default_limit});`,
'projects = sort_by_duration(merge_events_by_keys(events, ["project"]));',
'projects = limit_events(projects, ' + limit + ');',
`projects = limit_events(projects, ${default_limit});`,
'duration = sum_durations(events);',
'RETURN = {"files": files, "languages": languages, "projects": projects, "duration": duration};',
]);
Expand Down
55 changes: 46 additions & 9 deletions src/store/modules/activity.ts
Expand Up @@ -6,8 +6,6 @@ import queries from '~/queries';
import { loadClassesForQuery } from '~/util/classes';
import { get_day_start_with_offset } from '~/util/time';

const default_limit = 100;

interface TimePeriod {
start: string;
length: [number, string];
Expand Down Expand Up @@ -69,10 +67,15 @@ const _state = {
history: {},
},

android: {
available: false,
},

query_options: {
browser_buckets: 'all',
editor_buckets: 'all',
},

buckets: {
afk_buckets: [],
window_buckets: [],
Expand Down Expand Up @@ -130,8 +133,12 @@ const actions = {

if (state.window.available) {
await dispatch('query_window', query_options);
} else if (state.android.available) {
await dispatch('query_android', query_options);
} else {
console.log('Cannot call query_window as we are missing either an afk or window bucket');
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);
}

Expand All @@ -146,6 +153,8 @@ const actions = {

if (state.active.available) {
await dispatch('query_active_history', query_options);
} else if (state.android.available) {
await dispatch('query_active_history_android', query_options);
} else {
console.log('Cannot call query_active_history as we do not have an afk bucket');
await dispatch('query_active_history_empty', query_options);
Expand All @@ -166,18 +175,26 @@ 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_buckets[0],
state.buckets.afk_buckets[0],
default_limit, // this.top_apps_count,
default_limit, // this.top_windowtitles_count,
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();
const q = queries.appQuery(state.buckets.window_buckets[0], classes, filterCategories);
const data = await this._vm.$aw.query(periods, q).catch(this.errorHandler);
commit('query_window_completed', data[0]);
},

async query_window_empty({ commit }) {
Expand All @@ -197,7 +214,6 @@ const actions = {
state.buckets.browser_buckets,
state.buckets.window_buckets[0],
state.buckets.afk_buckets[0],
default_limit, // this.top_web_count
filterAFK
);
const data = await this._vm.$aw.query(periods, q);
Expand All @@ -215,7 +231,7 @@ const actions = {

async query_editor({ state, commit }, { timeperiod }) {
const periods = [timeperiodToStr(timeperiod)];
const q = queries.editorActivityQuery(state.buckets.editor_buckets, 100);
const q = queries.editorActivityQuery(state.buckets.editor_buckets);
const data = await this._vm.$aw.query(periods, q);
commit('query_editor_completed', data[0]);
},
Expand All @@ -233,12 +249,30 @@ const actions = {
const periods = timeperiodStrsAroundTimeperiod(timeperiod).filter(tp_str => {
return !_.includes(state.active.history, tp_str);
});
const bucket_id_afk = state.buckets.afk_buckets[0];
const data = await this._vm.$aw.query(periods, queries.dailyActivityQuery(bucket_id_afk));
const data = await this._vm.$aw.query(
periods,
queries.dailyActivityQuery(state.buckets.afk_buckets[0])
);
const active_history = _.zipObject(
periods,
_.map(data, pair => _.filter(pair, e => e.data.status == 'not-afk'))
);
console.log(active_history);
commit('query_active_history_completed', { active_history });
},

async query_active_history_android({ commit, state }, { timeperiod }: QueryOptions) {
const periods = timeperiodStrsAroundTimeperiod(timeperiod).filter(tp_str => {
return !_.includes(state.active.history, tp_str);
});
const data = await this._vm.$aw.query(
periods,
queries.dailyActivityQueryAndroid(state.buckets.window_buckets[0])
);
let active_history = _.zipObject(periods, data);
active_history = _.mapValues(active_history, (duration, key) => {
return [{ timestamp: key.split('/')[0], duration, data: { status: 'not-afk' } }];
});
commit('query_active_history_completed', { active_history });
},

Expand All @@ -256,11 +290,13 @@ const actions = {
state.buckets.browser_buckets.length > 0;
const active_available = state.buckets.afk_buckets.length > 0;
const editor_available = state.buckets.editor_buckets.length > 0;
const android_available = state.buckets.window_buckets[0].includes('android');
commit('set_available', {
window_available: window_available,
browser_available: browser_available,
active_available: active_available,
editor_available: editor_available,
android_available: android_available,
});
},

Expand Down Expand Up @@ -467,6 +503,7 @@ const mutations = {
state.active.available = data['active_available'];
state.editor.available = data['editor_available'];
state.category.available = data['window_available'];
state.android.available = data['android_available'];
},

query_window_completed(state, data) {
Expand Down

0 comments on commit 13777cb

Please sign in to comment.