Skip to content

Commit 86b8eab

Browse files
committed
feat: added category time to the timeline barchart
1 parent f03e399 commit 86b8eab

7 files changed

Lines changed: 216 additions & 102 deletions

File tree

‎src/components/SelectableVisualization.vue‎

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ import { split_by_hour_into_data } from '~/util/transforms';
9797
9898
// TODO: Move this somewhere else
9999
import { build_category_hierarchy } from '~/util/classes';
100+
import { getColorFromCategory } from '~/util/color';
100101
101102
function pick_subname_as_name(c) {
102103
c.name = c.subname;
@@ -229,15 +230,51 @@ export default {
229230
}
230231
},
231232
datasets: function () {
232-
// TODO: Move elsewhere
233-
const data = split_by_hour_into_data(this.$store.state.activity.active.events);
234-
return [
235-
{
236-
label: 'Total time',
237-
backgroundColor: '#6699ff',
238-
data,
239-
},
240-
];
233+
const METHOD_CATEGORY = 'category';
234+
const METHOD_ACTIVITY = 'activity';
235+
const method = METHOD_CATEGORY;
236+
if (method == METHOD_CATEGORY) {
237+
const SEP = '>>>';
238+
const data = this.$store.state.activity.category.by_hour;
239+
if (data) {
240+
const categories = new Set(
241+
Object.values(data)
242+
.map(result => {
243+
return result.cat_events.map(e => e.data['$category'].join(SEP));
244+
})
245+
.flat()
246+
);
247+
const ds = [...categories].map(c_ => {
248+
const c = this.$store.getters['categories/get_category'](c_.split(SEP));
249+
if (c) {
250+
return {
251+
label: c.name.join(' > '),
252+
backgroundColor: getColorFromCategory(c, this.$store.state.categories.classes),
253+
data: Object.values(data).map(results => {
254+
const cat = results.cat_events.find(e => _.isEqual(e.data['$category'], c.name));
255+
if (cat) return Math.round((cat.duration / (60 * 60)) * 1000) / 1000;
256+
else return null;
257+
}),
258+
};
259+
} else {
260+
console.log('missing c');
261+
}
262+
});
263+
return ds;
264+
} else {
265+
return [];
266+
}
267+
} else if (method == METHOD_ACTIVITY) {
268+
const data = split_by_hour_into_data(this.$store.state.activity.active.events);
269+
return [
270+
{
271+
label: 'Total time',
272+
backgroundColor: '#6699ff',
273+
data,
274+
},
275+
];
276+
}
277+
return [];
241278
},
242279
date: function () {
243280
let date = this.$store.state.activity.query_options.date;

‎src/queries.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,15 @@ export function editorActivityQuery(editorbuckets: string[]): string[] {
256256
return q;
257257
}
258258

259+
export function hourlyCategoryQuery(params: DesktopQueryParams) {
260+
const q = `
261+
${canonicalEvents(params)}
262+
cat_events = sort_by_duration(merge_events_by_keys(events, ["$category"]));
263+
RETURN = { "cat_events": cat_events };
264+
`;
265+
return querystr_to_array(q);
266+
}
267+
259268
export function dailyActivityQuery(afkbucket: string): string[] {
260269
afkbucket = escape_doublequote(afkbucket);
261270
return [
@@ -276,6 +285,7 @@ export default {
276285
fullDesktopQuery,
277286
appQuery,
278287
dailyActivityQuery,
288+
hourlyCategoryQuery,
279289
dailyActivityQueryAndroid,
280290
editorActivityQuery,
281291
};

‎src/store/modules/activity.ts‎

Lines changed: 52 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { unitOfTime } from 'moment';
33
import * as _ from 'lodash';
44
import { map, filter, values, groupBy, sortBy, flow, reverse } from 'lodash/fp';
55

6+
import { window_events } from '~/util/fakedata';
67
import queries from '~/queries';
78
import { getColorFromCategory } from '~/util/color';
89
import { loadClassesForQuery } from '~/util/classes';
@@ -13,8 +14,8 @@ interface TimePeriod {
1314
length: [number, string];
1415
}
1516

16-
function dateToTimeperiod(date: string): TimePeriod {
17-
return { start: get_day_start_with_offset(date), length: [1, 'day'] };
17+
function dateToTimeperiod(date: string, duration?: [number, string]): TimePeriod {
18+
return { start: get_day_start_with_offset(date), length: duration || [1, 'day'] };
1819
}
1920

2021
function timeperiodToStr(tp: TimePeriod): string {
@@ -62,6 +63,7 @@ const _state = {
6263

6364
category: {
6465
available: false,
66+
by_hour: [],
6567
top: [],
6668
},
6769

@@ -104,6 +106,23 @@ function timeperiodsAroundTimeperiod(timeperiod: TimePeriod): TimePeriod[] {
104106
return periods;
105107
}
106108

109+
function timeperiodsHoursOfDay(timeperiod: TimePeriod): TimePeriod[] {
110+
const periods = [];
111+
const _length: [number, string] = [1, 'hour'];
112+
for (let i = 0; i < 24; i++) {
113+
const start = moment(timeperiod.start)
114+
.add(i * _length[0], _length[1] as moment.unitOfTime.DurationConstructor)
115+
.format();
116+
periods.push({ start, length: _length });
117+
}
118+
// const periods = _.range(24).map(i => [TimePeriod(moment(i * 1 + dayOffset), [1, 'hour'])]);
119+
return periods;
120+
}
121+
122+
function timeperiodsStrsHoursOfDay(timeperiod: TimePeriod): string[] {
123+
return timeperiodsHoursOfDay(timeperiod).map(timeperiodToStr);
124+
}
125+
107126
function timeperiodStrsAroundTimeperiod(timeperiod: TimePeriod): string[] {
108127
return timeperiodsAroundTimeperiod(timeperiod).map(timeperiodToStr);
109128
}
@@ -142,6 +161,7 @@ const actions = {
142161

143162
if (state.window.available) {
144163
await dispatch('query_desktop_full', query_options);
164+
await dispatch('query_category_time_by_hour', query_options);
145165
} else if (state.android.available) {
146166
await dispatch('query_android', query_options);
147167
} else {
@@ -263,6 +283,32 @@ const actions = {
263283
commit('query_active_history_completed', { active_history });
264284
},
265285

286+
async query_category_time_by_hour(
287+
{ commit, state },
288+
{ timeperiod, filterCategories, filterAFK }: QueryOptions
289+
) {
290+
// TODO: Only works for the 1 day timeperiod
291+
// TODO: Needs to be adapted for Android
292+
const periods = timeperiodsStrsHoursOfDay(timeperiod);
293+
const classes = loadClassesForQuery();
294+
const data = await this._vm.$aw.query(
295+
periods,
296+
// TODO: Clean up call, pass QueryParams in fullDesktopQuery as well
297+
// TODO: Unify QueryOptions and QueryParams
298+
queries.hourlyCategoryQuery({
299+
bid_afk: state.buckets.afk[0],
300+
bid_window: state.buckets.window[0],
301+
bid_browsers: state.buckets.browser,
302+
// bid_android: state.buckets.android,
303+
classes: classes,
304+
filter_afk: filterAFK,
305+
filter_classes: filterCategories,
306+
})
307+
);
308+
const category_time_by_hour = _.zipObject(periods, data);
309+
commit('query_category_time_by_hour_completed', { category_time_by_hour });
310+
},
311+
266312
async query_active_history_android({ commit, state }, { timeperiod }: QueryOptions) {
267313
const periods = timeperiodStrsAroundTimeperiod(timeperiod).filter(tp_str => {
268314
return !_.includes(state.active.history, tp_str);
@@ -317,95 +363,6 @@ const actions = {
317363
// A function to load some demo data (for screenshots and stuff)
318364
commit('start_loading', {});
319365

320-
const window_events = [
321-
{
322-
duration: 32.1 * 60,
323-
data: {
324-
app: 'Firefox',
325-
title: 'ActivityWatch/activitywatch: Track how you spend your time - Mozilla Firefox',
326-
url: 'https://github.com/ActivityWatch/activitywatch',
327-
$category: ['Work', 'Programming', 'ActivityWatch'],
328-
},
329-
},
330-
{
331-
duration: 14.6 * 60,
332-
data: {
333-
app: 'Firefox',
334-
title: 'Inbox - Gmail - Mozilla Firefox',
335-
url: 'https://mail.google.com',
336-
$category: ['Comms', 'Email'],
337-
},
338-
},
339-
{
340-
duration: 0.2 * 60 * 60,
341-
data: {
342-
app: 'Firefox',
343-
title: 'reddit: the front page of the internet - Mozilla Firefox',
344-
url: 'https://reddit.com',
345-
$category: ['Media', 'Social Media'],
346-
},
347-
},
348-
{
349-
duration: 0.2 * 60 * 60,
350-
data: {
351-
app: 'Firefox',
352-
title: 'YouTube - Mozilla Firefox',
353-
url: 'https://youtube.com',
354-
$category: ['Media', 'Video'],
355-
},
356-
},
357-
{
358-
duration: 0.15 * 60 * 60,
359-
data: {
360-
app: 'Firefox',
361-
title: 'Home / Twitter - Mozilla Firefox',
362-
url: 'https://twitter.com',
363-
$category: ['Media', 'Social Media'],
364-
},
365-
},
366-
{
367-
duration: 0.15 * 60 * 60,
368-
data: {
369-
app: 'Firefox',
370-
title: 'Stack Overflow',
371-
url: 'https://stackoverflow.com',
372-
$category: ['Work', 'Programming'],
373-
},
374-
},
375-
{
376-
duration: 48.2 * 60,
377-
data: {
378-
app: 'Terminal',
379-
title: 'vim ~/code/activitywatch/aw-server/aw-webui/src',
380-
$category: ['Work', 'Programming', 'ActivityWatch'],
381-
},
382-
},
383-
{
384-
duration: 12.6 * 60,
385-
data: {
386-
app: 'Terminal',
387-
title: 'bash ~/code/activitywatch',
388-
$category: ['Work', 'Programming', 'ActivityWatch'],
389-
},
390-
},
391-
{
392-
duration: 58.1 * 60,
393-
data: {
394-
app: 'zoom',
395-
title: 'Zoom Meeting',
396-
$category: ['Comms', 'Video Conferencing'],
397-
},
398-
},
399-
{
400-
duration: 0.4 * 60 * 60,
401-
data: { app: 'Minecraft', title: 'Minecraft', $category: ['Media', 'Games'] },
402-
},
403-
{
404-
duration: 3.15 * 60,
405-
data: { app: 'Spotify', title: 'Spotify', $category: ['Media', 'Music'] },
406-
},
407-
];
408-
409366
function groupSumEventsBy(events, key, f) {
410367
return flow(
411368
filter(f),
@@ -544,6 +501,10 @@ const mutations = {
544501
};
545502
},
546503

504+
query_category_time_by_hour_completed(state, { category_time_by_hour }) {
505+
state.category.by_hour = category_time_by_hour;
506+
},
507+
547508
buckets(state, data) {
548509
state.buckets = data;
549510
state.buckets.loaded = true;

‎src/util/fakedata.js‎

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
export const window_events = [
2+
{
3+
duration: 32.1 * 60,
4+
data: {
5+
app: 'Firefox',
6+
title: 'ActivityWatch/activitywatch: Track how you spend your time - Mozilla Firefox',
7+
url: 'https://github.com/ActivityWatch/activitywatch',
8+
$category: ['Work', 'Programming', 'ActivityWatch'],
9+
},
10+
},
11+
{
12+
duration: 14.6 * 60,
13+
data: {
14+
app: 'Firefox',
15+
title: 'Inbox - Gmail - Mozilla Firefox',
16+
url: 'https://mail.google.com',
17+
$category: ['Comms', 'Email'],
18+
},
19+
},
20+
{
21+
duration: 0.2 * 60 * 60,
22+
data: {
23+
app: 'Firefox',
24+
title: 'reddit: the front page of the internet - Mozilla Firefox',
25+
url: 'https://reddit.com',
26+
$category: ['Media', 'Social Media'],
27+
},
28+
},
29+
{
30+
duration: 0.2 * 60 * 60,
31+
data: {
32+
app: 'Firefox',
33+
title: 'YouTube - Mozilla Firefox',
34+
url: 'https://youtube.com',
35+
$category: ['Media', 'Video'],
36+
},
37+
},
38+
{
39+
duration: 0.15 * 60 * 60,
40+
data: {
41+
app: 'Firefox',
42+
title: 'Home / Twitter - Mozilla Firefox',
43+
url: 'https://twitter.com',
44+
$category: ['Media', 'Social Media'],
45+
},
46+
},
47+
{
48+
duration: 0.15 * 60 * 60,
49+
data: {
50+
app: 'Firefox',
51+
title: 'Stack Overflow',
52+
url: 'https://stackoverflow.com',
53+
$category: ['Work', 'Programming'],
54+
},
55+
},
56+
{
57+
duration: 48.2 * 60,
58+
data: {
59+
app: 'Terminal',
60+
title: 'vim ~/code/activitywatch/aw-server/aw-webui/src',
61+
$category: ['Work', 'Programming', 'ActivityWatch'],
62+
},
63+
},
64+
{
65+
duration: 12.6 * 60,
66+
data: {
67+
app: 'Terminal',
68+
title: 'bash ~/code/activitywatch',
69+
$category: ['Work', 'Programming', 'ActivityWatch'],
70+
},
71+
},
72+
{
73+
duration: 58.1 * 60,
74+
data: {
75+
app: 'zoom',
76+
title: 'Zoom Meeting',
77+
$category: ['Comms', 'Video Conferencing'],
78+
},
79+
},
80+
{
81+
duration: 0.4 * 60 * 60,
82+
data: { app: 'Minecraft', title: 'Minecraft', $category: ['Media', 'Games'] },
83+
},
84+
{
85+
duration: 3.15 * 60,
86+
data: { app: 'Spotify', title: 'Spotify', $category: ['Media', 'Music'] },
87+
},
88+
];

‎src/util/time.js‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ export function get_day_start_with_offset(dateParam) {
3232
return dateMoment.hour(start_of_day_hours).minute(start_of_day_minutes).format();
3333
}
3434

35+
// Return the startOfDay offset as a number of hours
36+
export function get_hour_offset() {
37+
const start_of_day = localStorage.startOfDay;
38+
const start_of_day_hours = parseInt(start_of_day.split(':')[0]);
39+
const start_of_day_minutes = parseInt(start_of_day.split(':')[1]);
40+
return start_of_day_hours + start_of_day_minutes / 60;
41+
}
42+
3543
export function get_day_end_with_offset(date) {
3644
return moment(get_day_start_with_offset(date)).add(1, 'days').format();
3745
}

0 commit comments

Comments
 (0)