Skip to content

Commit 5308189

Browse files
committed
feat: continued work on Calendar visualization
1 parent 7de395b commit 5308189

File tree

2 files changed

+104
-26
lines changed

2 files changed

+104
-26
lines changed

src/queries.ts

+1
Original file line numberDiff line numberDiff line change
@@ -460,4 +460,5 @@ export default {
460460
activityQueryAndroid,
461461
categoryQuery,
462462
editorActivityQuery,
463+
canonicalEvents,
463464
};

src/visualizations/Calendar.vue

+103-26
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
<template lang="pug">
22
div.mx-3
33
b-form
4-
b-form-group(label="Bucket:")
5-
select(v-model="selectedBucket")
6-
option(v-for="bucket in buckets", :value="bucket.id") {{ bucket.id }}
4+
b-form-group(label="Host:")
5+
select(v-model="selectedHost")
6+
option(v-for="host in hostnames" :value="host") {{ host }}
77
b-form-group(label="Show:")
88
select(v-model="view")
99
option(value="timeGridDay") Day
@@ -15,26 +15,67 @@ div.mx-3
1515
</template>
1616

1717
<script>
18-
import { getTitleAttr, getColorFromString } from '../util/color';
1918
import moment from 'moment';
2019
import _ from 'lodash';
2120
import FullCalendar from '@fullcalendar/vue';
2221
import timeGridPlugin from '@fullcalendar/timegrid';
2322
23+
import queries from '~/queries';
24+
import { useCategoryStore } from '~/stores/categories';
25+
26+
function mergeAdjacent(events) {
27+
// process events, merging adjacent events with same category
28+
const mergedEvents = [];
29+
let lastEvent = null;
30+
for (let i = 0; i < events.length; i++) {
31+
const event = events[i];
32+
if (lastEvent == null) {
33+
lastEvent = event;
34+
continue;
35+
}
36+
// if adjacent with less than 10 seconds between, merge
37+
const isAdjacent =
38+
moment(event.timestamp).diff(
39+
moment(lastEvent.timestamp).add(lastEvent.duration, 'seconds'),
40+
'seconds'
41+
) < 10;
42+
if (
43+
isAdjacent &&
44+
event.data['$category'].join(' > ') == lastEvent.data['$category'].join(' > ')
45+
) {
46+
lastEvent.duration += event.duration;
47+
} else {
48+
mergedEvents.push(lastEvent);
49+
lastEvent = event;
50+
}
51+
}
52+
if (lastEvent != null) mergedEvents.push(lastEvent);
53+
return mergedEvents;
54+
}
55+
2456
// TODO: Use canonical timeline query, with flooding and categorization
2557
// TODO: Checkbox for toggling category-view, where adjacent events with same category are merged and the events are labeled by category
2658
// TODO: Use the recommended way of dynamically getting events: https://fullcalendar.io/docs/events-function
2759
export default {
2860
components: {
29-
FullCalendar, // make the <FullCalendar> tag available
61+
FullCalendar,
3062
},
3163
props: {
3264
buckets: { type: Array },
3365
},
3466
data() {
35-
return { fitToActive: false, selectedBucket: null, view: 'timeGridDay' };
67+
return {
68+
events: [],
69+
fitToActive: false,
70+
selectedHost: 'erb-m2.localdomain',
71+
view: 'timeGridWeek',
72+
};
3673
},
3774
computed: {
75+
hostnames: function () {
76+
if (this.buckets == null) return [];
77+
return _.uniq(this.buckets.map(b => b.hostname).filter(h => h != null));
78+
},
3879
calendarOptions: function () {
3980
const events = this.events;
4081
const first = _.minBy(events, e => e.start);
@@ -67,37 +108,73 @@ export default {
67108
},
68109
};
69110
},
70-
events: function () {
71-
// NOTE: This returns FullCalendar events, not ActivityWatch events.
72-
if (this.buckets == null) return [];
73-
74-
const bucket = _.find(this.buckets, b => b.id == this.selectedBucket);
75-
if (bucket == null) {
76-
return;
77-
}
78-
let events = bucket.events;
79-
events = _.filter(events, e => e.duration > 10);
80-
events = _.map(events, e => {
81-
return {
82-
title: getTitleAttr(bucket, e),
83-
start: moment(e.timestamp).format(),
84-
end: moment(e.timestamp).add(e.duration, 'seconds').format(),
85-
backgroundColor: getColorFromString(getTitleAttr(bucket, e)),
86-
};
87-
});
88-
return events;
111+
queryOptions: function () {
112+
return {
113+
hostname: this.selectedHost,
114+
filter_afk: true,
115+
start: moment().startOf('week').format(),
116+
stop: moment().endOf('week').format(),
117+
};
89118
},
90119
},
91120
watch: {
92121
view: function (to) {
93122
const calendar = this.$refs.fullCalendar.getApi();
94123
calendar.changeView(to);
95124
},
125+
selectedHost: async function () {
126+
console.log('selectedHost changed');
127+
this.events = await this.loadEventsCanonical();
128+
},
129+
},
130+
mounted: async function () {
131+
this.events = await this.loadEventsCanonical();
96132
},
97133
methods: {
98134
onEventClick: function (arg) {
99135
// TODO: Open event inspector/editor here
100-
alert('event click! ' + JSON.stringify(arg.event));
136+
alert('event click!\n' + JSON.stringify(arg.event, null, 2));
137+
},
138+
loadEventsCanonical: async function () {
139+
console.log('loadEventsCanonical');
140+
console.log(this.queryOptions.hostname);
141+
if (this.queryOptions.hostname == null) return [];
142+
143+
const categoryStore = useCategoryStore();
144+
categoryStore.load();
145+
const categories = categoryStore.classes_for_query;
146+
147+
let query = queries.canonicalEvents({
148+
bid_window: 'aw-watcher-window_' + this.queryOptions.hostname,
149+
bid_afk: 'aw-watcher-afk_' + this.queryOptions.hostname,
150+
filter_afk: this.queryOptions.filter_afk,
151+
categories,
152+
});
153+
query += 'RETURN = events;';
154+
console.log(query);
155+
query = query.split(';').map(s => s.trim() + ';');
156+
157+
const timeperiods = [
158+
moment(this.queryOptions.start).format() + '/' + moment(this.queryOptions.stop).format(),
159+
];
160+
console.log('Querying');
161+
const data = await this.$aw.query(timeperiods, query);
162+
console.log(data);
163+
let events = _.orderBy(data[0], ['timestamp'], ['desc']);
164+
165+
events = mergeAdjacent(events);
166+
console.log('mergedEvents', events);
167+
168+
events = _.filter(events, e => e.duration > 60);
169+
events = _.map(events, e => {
170+
return {
171+
title: e.data['$category'].join(' > '),
172+
start: moment(e.timestamp).format(),
173+
end: moment(e.timestamp).add(e.duration, 'seconds').format(),
174+
backgroundColor: categoryStore.get_category_color(e.data['$category']),
175+
};
176+
});
177+
return events;
101178
},
102179
},
103180
};

0 commit comments

Comments
 (0)