Skip to content

Commit 13777cb

Browse files
committed
feat: ported new Activity view to Android
1 parent 353634b commit 13777cb

File tree

6 files changed

+114
-50
lines changed

6 files changed

+114
-50
lines changed

src/components/Header.vue

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,17 +77,11 @@ import 'vue-awesome/icons/desktop';
7777
7878
import _ from 'lodash';
7979
80-
// Set this to true to test Android behavior when on a desktop
81-
const testingAndroid = false;
82-
8380
export default {
8481
name: 'Header',
8582
data() {
8683
return {
8784
activityViews: [],
88-
isAndroidApp:
89-
testingAndroid ||
90-
(navigator.userAgent.includes('Android') && navigator.userAgent.includes('wv')), // Checks for Android and WebView
9185
};
9286
},
9387
mounted: async function() {
@@ -98,7 +92,7 @@ export default {
9892
// The '&& true;' is just to typecoerce into booleans
9993
types_by_host[v.hostname].afk |= v.type == 'afkstatus';
10094
types_by_host[v.hostname].window |= v.type == 'currentwindow';
101-
types_by_host[v.hostname].android |= v.type == 'currentwindow' && this.isAndroidApp; // Use other bucket type ID in the future
95+
types_by_host[v.hostname].android |= v.type == 'currentwindow' && v.id.includes('android'); // Use other bucket type ID in the future
10296
});
10397
//console.log(types_by_host);
10498
@@ -112,14 +106,21 @@ export default {
112106
icon: 'desktop',
113107
});
114108
}
115-
if (testingAndroid || types.android) {
109+
if (types.android) {
116110
this.activityViews.push({
117111
name: `${hostname} (Android)`,
118112
hostname: hostname,
119113
type: 'android',
120114
pathUrl: '/activity/android',
121115
icon: 'mobile',
122116
});
117+
this.activityViews.push({
118+
name: `${hostname} (Android, new)`,
119+
hostname: hostname,
120+
type: 'android',
121+
pathUrl: `/activity/${hostname}`,
122+
icon: 'mobile',
123+
});
123124
}
124125
});
125126
},

src/main.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@ import 'typeface-varela-round';
2323
// Loads all the filters
2424
import './util/filters.js';
2525

26+
// Create an instance of AWClient as this.$aw
27+
import awclient from './util/awclient.js';
28+
console.log(awclient);
29+
Vue.prototype.$aw = awclient;
30+
2631
// Sets up the routing and the base app (using vue-router)
2732
import router from './route.js';
2833

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

57-
// Create an instance of AWClient as this.$aw
58-
import awclient from './util/awclient.js';
59-
Vue.prototype.$aw = awclient;
60-
6162
// Set the PRODUCTION constant
6263
Vue.prototype.PRODUCTION = PRODUCTION;
6364

src/queries.ts

Lines changed: 45 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,26 @@ function querystr_to_array(querystr: string): string[] {
99
.map(l => l + ';');
1010
}
1111

12-
interface QueryParams {
13-
bid_window: string;
14-
bid_afk: string;
15-
bid_browsers?: string[];
12+
interface BaseQueryParams {
1613
filter_afk: boolean;
1714
include_audible?: boolean;
1815
classes: Record<string, any>;
1916
}
2017

18+
interface DesktopQueryParams extends BaseQueryParams {
19+
bid_window: string;
20+
bid_afk: string;
21+
bid_browsers?: string[];
22+
}
23+
24+
interface AndroidQueryParams extends BaseQueryParams {
25+
bid_android: string;
26+
bid_browsers?: string[];
27+
}
28+
2129
// Constructs a query that returns a fully-detailed list of events from the merging of several sources (window, afk, web).
2230
// Puts it's results in `not_afk` and `events`.
23-
function canonicalEvents(params: QueryParams): string {
31+
function canonicalEvents(params: DesktopQueryParams): string {
2432
return `
2533
events = flood(query_bucket("${params.bid_window}"));
2634
not_afk = flood(query_bucket("${params.bid_afk}"));
@@ -29,18 +37,18 @@ function canonicalEvents(params: QueryParams): string {
2937
`;
3038
}
3139

40+
const default_limit = 100; // Hardcoded limit per group
41+
3242
export function windowQuery(
3343
windowbucket,
3444
afkbucket,
35-
appcount,
36-
titlecount,
3745
filterAFK,
3846
classes,
3947
filterCategories: string[][]
4048
): string[] {
4149
windowbucket = windowbucket.replace('"', '\\"');
4250
afkbucket = afkbucket.replace('"', '\\"');
43-
const params: QueryParams = {
51+
const params: DesktopQueryParams = {
4452
bid_window: windowbucket,
4553
bid_afk: afkbucket,
4654
classes: classes,
@@ -60,22 +68,34 @@ export function windowQuery(
6068
cat_events = sort_by_duration(merge_events_by_keys(events, ["$category"]));
6169
6270
events = sort_by_timestamp(events);
63-
app_events = limit_events(app_events, ${appcount});
64-
title_events = limit_events(title_events, ${titlecount});
71+
app_events = limit_events(app_events, ${default_limit});
72+
title_events = limit_events(title_events, ${default_limit});
6573
duration = sum_durations(events);
6674
RETURN = {"app_events": app_events, "title_events": title_events, "cat_events": cat_events, "duration": duration, "active_events": not_afk};`;
6775
return querystr_to_array(code);
6876
}
6977

70-
export function appQuery(appbucket: string, limit = 5): string[] {
78+
export function appQuery(appbucket: string, classes, filterAFK): string[] {
7179
appbucket = appbucket.replace('"', '\\"');
80+
const params: AndroidQueryParams = {
81+
bid_android: appbucket,
82+
classes: classes,
83+
filter_afk: filterAFK,
84+
};
7285
const code = `
73-
events = query_bucket("${appbucket}");
74-
events = merge_events_by_keys(events, ["app"]);
75-
events = sort_by_duration(events);
76-
events = limit_events(events, ${limit});
77-
total_duration = sum_durations(events);
78-
RETURN = {"events": events, "total_duration": total_duration};
86+
events = query_bucket("${params.bid_android}");
87+
events = merge_events_by_keys(events, ["app"]);
88+
events = categorize(events, ${JSON.stringify(params.classes)});
89+
90+
title_events = sort_by_duration(merge_events_by_keys(events, ["app", "classname"]));
91+
app_events = sort_by_duration(merge_events_by_keys(title_events, ["app"]));
92+
cat_events = sort_by_duration(merge_events_by_keys(events, ["$category"]));
93+
94+
events = sort_by_timestamp(events);
95+
app_events = limit_events(app_events, ${default_limit});
96+
title_events = limit_events(title_events, ${default_limit});
97+
duration = sum_durations(events);
98+
RETURN = {"app_events": app_events, "title_events": title_events, "cat_events": cat_events, "duration": duration, "active_events": app_events};
7999
`;
80100
return querystr_to_array(code);
81101
}
@@ -122,7 +142,7 @@ function browsersWithBuckets(browserbuckets: string[]): [string, string][] {
122142
}
123143

124144
// Returns a list of active browser events (where the browser was the active window) from all browser buckets
125-
function browserEvents(params: QueryParams): string {
145+
function browserEvents(params: DesktopQueryParams): string {
126146
// If multiple browser buckets were found
127147
// AFK filtered later in the process
128148
let code = `
@@ -151,7 +171,6 @@ export function browserSummaryQuery(
151171
browserbuckets: string[],
152172
windowbucket: string,
153173
afkbucket: string,
154-
limit = 5,
155174
filterAFK = true
156175
): string[] {
157176
// Escape `"`
@@ -160,7 +179,7 @@ export function browserSummaryQuery(
160179
afkbucket = afkbucket.replace('"', '\\"');
161180

162181
// TODO: Get classes
163-
const params: QueryParams = {
182+
const params: DesktopQueryParams = {
164183
bid_window: windowbucket,
165184
bid_afk: afkbucket,
166185
bid_browsers: browserbuckets,
@@ -172,29 +191,29 @@ export function browserSummaryQuery(
172191
`${browserEvents(params)}
173192
urls = merge_events_by_keys(events, ["url"]);
174193
urls = sort_by_duration(urls);
175-
urls = limit_events(urls, ${limit});
194+
urls = limit_events(urls, ${default_limit});
176195
domains = split_url_events(events);
177196
domains = merge_events_by_keys(domains, ["$domain"]);
178197
domains = sort_by_duration(domains);
179-
domains = limit_events(domains, ${limit});
198+
domains = limit_events(domains, ${default_limit});
180199
duration = sum_durations(events);
181200
RETURN = {"domains": domains, "urls": urls, "duration": duration};`
182201
);
183202
}
184203

185-
export function editorActivityQuery(editorbuckets: string[], limit): string[] {
204+
export function editorActivityQuery(editorbuckets: string[]): string[] {
186205
let q = ['events = [];'];
187206
for (let editorbucket of editorbuckets) {
188207
editorbucket = editorbucket.replace('"', '\\"');
189208
q.push('events = concat(events, flood(query_bucket("' + editorbucket + '")));');
190209
}
191210
q = q.concat([
192211
'files = sort_by_duration(merge_events_by_keys(events, ["file", "language"]));',
193-
'files = limit_events(files, ' + limit + ');',
212+
`files = limit_events(files, ${default_limit});`,
194213
'languages = sort_by_duration(merge_events_by_keys(events, ["language"]));',
195-
'languages = limit_events(languages, ' + limit + ');',
214+
`languages = limit_events(languages, ${default_limit});`,
196215
'projects = sort_by_duration(merge_events_by_keys(events, ["project"]));',
197-
'projects = limit_events(projects, ' + limit + ');',
216+
`projects = limit_events(projects, ${default_limit});`,
198217
'duration = sum_durations(events);',
199218
'RETURN = {"files": files, "languages": languages, "projects": projects, "duration": duration};',
200219
]);

src/store/modules/activity.ts

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import queries from '~/queries';
66
import { loadClassesForQuery } from '~/util/classes';
77
import { get_day_start_with_offset } from '~/util/time';
88

9-
const default_limit = 100;
10-
119
interface TimePeriod {
1210
start: string;
1311
length: [number, string];
@@ -69,10 +67,15 @@ const _state = {
6967
history: {},
7068
},
7169

70+
android: {
71+
available: false,
72+
},
73+
7274
query_options: {
7375
browser_buckets: 'all',
7476
editor_buckets: 'all',
7577
},
78+
7679
buckets: {
7780
afk_buckets: [],
7881
window_buckets: [],
@@ -130,8 +133,12 @@ const actions = {
130133

131134
if (state.window.available) {
132135
await dispatch('query_window', query_options);
136+
} else if (state.android.available) {
137+
await dispatch('query_android', query_options);
133138
} else {
134-
console.log('Cannot call query_window as we are missing either an afk or window bucket');
139+
console.log(
140+
'Cannot query windows as we are missing either an afk/window bucket pair or an android bucket'
141+
);
135142
await dispatch('query_window_empty', query_options);
136143
}
137144

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

147154
if (state.active.available) {
148155
await dispatch('query_active_history', query_options);
156+
} else if (state.android.available) {
157+
await dispatch('query_active_history_android', query_options);
149158
} else {
150159
console.log('Cannot call query_active_history as we do not have an afk bucket');
151160
await dispatch('query_active_history_empty', query_options);
@@ -166,18 +175,26 @@ const actions = {
166175

167176
async query_window({ state, commit }, { timeperiod, filterAFK, filterCategories }: QueryOptions) {
168177
const periods = [timeperiodToStr(timeperiod)];
178+
const start = moment();
169179
const classes = loadClassesForQuery();
170180
const q = queries.windowQuery(
171181
state.buckets.window_buckets[0],
172182
state.buckets.afk_buckets[0],
173-
default_limit, // this.top_apps_count,
174-
default_limit, // this.top_windowtitles_count,
175183
filterAFK,
176184
classes,
177185
filterCategories
178186
);
179187
const data = await this._vm.$aw.query(periods, q);
180188
commit('query_window_completed', data[0]);
189+
console.info(`Completed window query in ${moment().diff(start)}ms`);
190+
},
191+
192+
async query_android({ state, commit }, { timeperiod, filterCategories }: QueryOptions) {
193+
const periods = [timeperiodToStr(timeperiod)];
194+
const classes = loadClassesForQuery();
195+
const q = queries.appQuery(state.buckets.window_buckets[0], classes, filterCategories);
196+
const data = await this._vm.$aw.query(periods, q).catch(this.errorHandler);
197+
commit('query_window_completed', data[0]);
181198
},
182199

183200
async query_window_empty({ commit }) {
@@ -197,7 +214,6 @@ const actions = {
197214
state.buckets.browser_buckets,
198215
state.buckets.window_buckets[0],
199216
state.buckets.afk_buckets[0],
200-
default_limit, // this.top_web_count
201217
filterAFK
202218
);
203219
const data = await this._vm.$aw.query(periods, q);
@@ -215,7 +231,7 @@ const actions = {
215231

216232
async query_editor({ state, commit }, { timeperiod }) {
217233
const periods = [timeperiodToStr(timeperiod)];
218-
const q = queries.editorActivityQuery(state.buckets.editor_buckets, 100);
234+
const q = queries.editorActivityQuery(state.buckets.editor_buckets);
219235
const data = await this._vm.$aw.query(periods, q);
220236
commit('query_editor_completed', data[0]);
221237
},
@@ -233,12 +249,30 @@ const actions = {
233249
const periods = timeperiodStrsAroundTimeperiod(timeperiod).filter(tp_str => {
234250
return !_.includes(state.active.history, tp_str);
235251
});
236-
const bucket_id_afk = state.buckets.afk_buckets[0];
237-
const data = await this._vm.$aw.query(periods, queries.dailyActivityQuery(bucket_id_afk));
252+
const data = await this._vm.$aw.query(
253+
periods,
254+
queries.dailyActivityQuery(state.buckets.afk_buckets[0])
255+
);
238256
const active_history = _.zipObject(
239257
periods,
240258
_.map(data, pair => _.filter(pair, e => e.data.status == 'not-afk'))
241259
);
260+
console.log(active_history);
261+
commit('query_active_history_completed', { active_history });
262+
},
263+
264+
async query_active_history_android({ commit, state }, { timeperiod }: QueryOptions) {
265+
const periods = timeperiodStrsAroundTimeperiod(timeperiod).filter(tp_str => {
266+
return !_.includes(state.active.history, tp_str);
267+
});
268+
const data = await this._vm.$aw.query(
269+
periods,
270+
queries.dailyActivityQueryAndroid(state.buckets.window_buckets[0])
271+
);
272+
let active_history = _.zipObject(periods, data);
273+
active_history = _.mapValues(active_history, (duration, key) => {
274+
return [{ timestamp: key.split('/')[0], duration, data: { status: 'not-afk' } }];
275+
});
242276
commit('query_active_history_completed', { active_history });
243277
},
244278

@@ -256,11 +290,13 @@ const actions = {
256290
state.buckets.browser_buckets.length > 0;
257291
const active_available = state.buckets.afk_buckets.length > 0;
258292
const editor_available = state.buckets.editor_buckets.length > 0;
293+
const android_available = state.buckets.window_buckets[0].includes('android');
259294
commit('set_available', {
260295
window_available: window_available,
261296
browser_available: browser_available,
262297
active_available: active_available,
263298
editor_available: editor_available,
299+
android_available: android_available,
264300
});
265301
},
266302

@@ -467,6 +503,7 @@ const mutations = {
467503
state.active.available = data['active_available'];
468504
state.editor.available = data['editor_available'];
469505
state.category.available = data['window_available'];
506+
state.android.available = data['android_available'];
470507
},
471508

472509
query_window_completed(state, data) {

0 commit comments

Comments
 (0)