Skip to content

Commit c17a752

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

File tree

2 files changed

+121
-19
lines changed

2 files changed

+121
-19
lines changed

src/queries.ts

Lines changed: 1 addition & 0 deletions
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

Lines changed: 120 additions & 19 deletions
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,38 @@ 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 { getTitleAttr, getColorFromString } from '../util/color';
25+
import { useCategoryStore } from '~/stores/categories';
26+
2427
// TODO: Use canonical timeline query, with flooding and categorization
2528
// TODO: Checkbox for toggling category-view, where adjacent events with same category are merged and the events are labeled by category
2629
// TODO: Use the recommended way of dynamically getting events: https://fullcalendar.io/docs/events-function
2730
export default {
2831
components: {
29-
FullCalendar, // make the <FullCalendar> tag available
32+
FullCalendar,
3033
},
3134
props: {
3235
buckets: { type: Array },
3336
},
3437
data() {
35-
return { fitToActive: false, selectedBucket: null, view: 'timeGridDay' };
38+
return {
39+
events: [],
40+
fitToActive: false,
41+
selectedHost: 'erb-m2.localdomain',
42+
view: 'timeGridWeek',
43+
};
3644
},
3745
computed: {
46+
hostnames: function () {
47+
if (this.buckets == null) return [];
48+
return _.uniq(this.buckets.map(b => b.hostname).filter(h => h != null));
49+
},
3850
calendarOptions: function () {
3951
const events = this.events;
4052
const first = _.minBy(events, e => e.start);
@@ -67,7 +79,108 @@ export default {
6779
},
6880
};
6981
},
70-
events: function () {
82+
queryOptions: function () {
83+
return {
84+
hostname: this.selectedHost,
85+
filter_afk: true,
86+
start: moment().startOf('week').format(),
87+
stop: moment().endOf('week').format(),
88+
};
89+
},
90+
},
91+
watch: {
92+
view: function (to) {
93+
const calendar = this.$refs.fullCalendar.getApi();
94+
calendar.changeView(to);
95+
},
96+
selectedHost: async function () {
97+
console.log('selectedHost changed');
98+
this.events = await this.loadEventsCanonical();
99+
},
100+
},
101+
mounted: async function () {
102+
this.events = await this.loadEventsCanonical();
103+
},
104+
methods: {
105+
onEventClick: function (arg) {
106+
// TODO: Open event inspector/editor here
107+
alert('event click! ' + JSON.stringify(arg.event));
108+
},
109+
loadEventsCanonical: async function () {
110+
console.log('loadEventsCanonical');
111+
console.log(this.queryOptions.hostname);
112+
if (this.queryOptions.hostname == null) return [];
113+
114+
const categoryStore = useCategoryStore();
115+
categoryStore.load();
116+
const categories = categoryStore.classes_for_query;
117+
118+
let query = queries.canonicalEvents({
119+
bid_window: 'aw-watcher-window_' + this.queryOptions.hostname,
120+
bid_afk: 'aw-watcher-afk_' + this.queryOptions.hostname,
121+
filter_afk: this.queryOptions.filter_afk,
122+
categories,
123+
});
124+
query += 'RETURN = events;';
125+
console.log(query);
126+
query = query.split(';').map(s => s.trim() + ';');
127+
128+
const timeperiods = [
129+
moment(this.queryOptions.start).format() + '/' + moment(this.queryOptions.stop).format(),
130+
];
131+
console.log('Querying');
132+
const data = await this.$aw.query(timeperiods, query);
133+
console.log(data);
134+
let events = _.orderBy(data[0], ['timestamp'], ['desc']);
135+
console.log('events', events);
136+
137+
// process events, merging adjacent events with same category
138+
const mergedEvents = [];
139+
let lastEvent = null;
140+
for (let i = 0; i < events.length; i++) {
141+
const event = events[i];
142+
if (lastEvent == null) {
143+
lastEvent = event;
144+
continue;
145+
}
146+
// if adjacent with less than 10 seconds between, merge
147+
const isAdjacent =
148+
moment(event.timestamp).diff(
149+
moment(lastEvent.timestamp).add(lastEvent.duration, 'seconds'),
150+
'seconds'
151+
) < 10;
152+
if (
153+
isAdjacent &&
154+
event.data['$category'].join(' > ') == lastEvent.data['$category'].join(' > ')
155+
) {
156+
lastEvent.duration += event.duration;
157+
} else {
158+
mergedEvents.push(lastEvent);
159+
lastEvent = event;
160+
}
161+
}
162+
if (lastEvent != null) mergedEvents.push(lastEvent);
163+
164+
console.log(mergedEvents);
165+
166+
events = _.filter(mergedEvents, e => e.duration > 60);
167+
const bucket = {
168+
id: 'Canonical',
169+
hostname: this.queryOptions.hostname,
170+
type: 'currentwindow',
171+
};
172+
events = _.map(events, e => {
173+
return {
174+
title: e.data['$category'].join(' > '),
175+
start: moment(e.timestamp).format(),
176+
end: moment(e.timestamp).add(e.duration, 'seconds').format(),
177+
// TODO: this isn't the correct way to get the category color
178+
backgroundColor: getColorFromString(e.data['$category'].join(' > ')),
179+
};
180+
});
181+
return events;
182+
},
183+
loadEventsBucket: function () {
71184
// NOTE: This returns FullCalendar events, not ActivityWatch events.
72185
if (this.buckets == null) return [];
73186
@@ -88,17 +201,5 @@ export default {
88201
return events;
89202
},
90203
},
91-
watch: {
92-
view: function (to) {
93-
const calendar = this.$refs.fullCalendar.getApi();
94-
calendar.changeView(to);
95-
},
96-
},
97-
methods: {
98-
onEventClick: function (arg) {
99-
// TODO: Open event inspector/editor here
100-
alert('event click! ' + JSON.stringify(arg.event));
101-
},
102-
},
103204
};
104205
</script>

0 commit comments

Comments
 (0)