Permalink
Browse files

Added 'delete bucket' and moved sunburst logic to its own vue component

  • Loading branch information...
johan-bjareholt committed Nov 25, 2017
1 parent 7be5ca3 commit 6410170d3192c9e73bb7957ea7e7b9ce606e25ac
Showing with 193 additions and 188 deletions.
  1. +4 −180 src/views/Activity.vue
  2. +27 −1 src/views/Buckets.vue
  3. +161 −6 src/visualizations/Sunburst.vue
  4. +1 −1 src/visualizations/coloring.js
@@ -74,7 +74,7 @@ div
| #[b Note:] This is an early version. It has known issues that will be resolved in a future update.
| See #[a(href="https://github.com/ActivityWatch/aw-webui/issues/36") issue #36] for details.

aw-sunburst(:hierarchy="hierarchy")
aw-sunburst(:date="date", :afkBucketId="afkBucketId", :windowBucketId="windowBucketId")

hr

@@ -107,7 +107,6 @@ div
</style>

<script>
import Resources from '../resources.js';
import moment from 'moment';
import timeline from '../visualizations/timeline.js';
import summary from '../visualizations/summary.js';
@@ -121,6 +120,7 @@ import 'vue-awesome/icons/refresh'
import Sunburst from '../visualizations/Sunburst.vue';
import Resources from '../resources.js';
let $Query = Resources.$Query;
let $Info = Resources.$Info;
let $Bucket = Resources.$Bucket;
@@ -132,7 +132,7 @@ export default {
name: "Activity",
data: () => {
return {
today: moment().format("YYYY-MM-DD"),
today: moment().startOf('day').format("YYYY-MM-DD"),
filterAFK: true,
timelineShowAFK: true,
@@ -143,9 +143,6 @@ export default {
numberOfWindowApps: 5,
numberOfWindowTitles: 5,
numberOfBrowserDomains: 5,
// Sunburst stuff
hierarchy: Object,
}
},
@@ -175,7 +172,7 @@ export default {
computed: {
readableDuration: function() { return time.seconds_to_duration(this.duration) },
host: function() { return this.$route.params.host },
date: function() { return this.$route.params.date || moment().format() },
date: function() { return this.$route.params.date || moment().startOf('day').format() },
dateStart: function() { return moment(this.date).startOf('day').format() },
dateShort: function() { return moment(this.date).format("YYYY-MM-DD") },
windowBucketId: function() { return "aw-watcher-window_" + this.host },
@@ -198,7 +195,6 @@ export default {
refresh: function() {
this.query();
this.visualize();
this.duration = "";
this.numberOfWindowApps = 5;
this.numberOfWindowTitles = 5;
@@ -241,45 +237,6 @@ export default {
}, this.errorHandler);
},
todaysEvents: function(bucket_id) {
let today = moment(this.dateStart);
let endOfToday = moment(today).add(1, "days");
return $Event.get({id: bucket_id, limit: -1,
start: today.format(), end: endOfToday.format()})
.then((response) => response.json());
},
windowEventsFilteredByAFK: function() {
return this.todaysEvents(this.windowBucketId)
.then((events) => {
return this.todaysEvents(this.afkBucketId).then((afkevents) => {
if(this.filterAFK) {
events = event_parsing.filterAFKTime(events, afkevents);
}
return events;
})
}, this.errorHandler);
},
groupAndSumEvents: function(events, groupingFunc) {
let groups = _.groupBy(events, groupingFunc);
let groupsList = _.values(groups);
let summedEvents = _.map(groupsList,
(v, i) => _.reduce(_.drop(v, 1),
(acc, e) => {
acc.duration += e.duration;
delete acc.id;
delete acc.timestamp;
delete acc.range;
return acc;
}, v[0]))
// Sort objects by duration
summedEvents = _.sortBy(summedEvents, (e) => e.duration).reverse();
return summedEvents;
},
queryWindowTitles: function() {
let starttime = moment(this.dateStart).format();
let endtime = moment(this.dateStart).add(1, 'days').format();
@@ -339,7 +296,6 @@ export default {
this.errorHandler(response);
} else {
var summedEvents = response.json();
console.log(summedEvents);
summary.updateSummedEvents(container, summedEvents, (e) => e.data.domain, (e) => e.data.domain);
}
}, this.errorHandler
@@ -401,138 +357,6 @@ export default {
'RETURN=events;',
]};
},
// Everything below is related to the sunburst visualization
getBucketInfo: function(bucket_id) {
return $Bucket.get({"id": bucket_id}).then((response) => {
return response.json();
});
},
visualize: function() {
function buildHierarchy(parents, children) {
parents = _.sortBy(parents, "timestamp", "desc");
children = _.sortBy(children, "timestamp", "desc");
var i_child = 0;
for(var i_parent = 0; i_parent < parents.length; i_parent++) {
let p = parents[i_parent];
let p_start = moment(p.timestamp);
let p_end = p_start.clone().add(p.duration, "seconds");
p.children = [];
while(i_child < children.length) {
var e = children[i_child];
var e_start = moment(e.timestamp);
var e_end = e_start.clone().add(e.duration, "seconds");
let too_far = e_start.isAfter(p_end);
let before_parent = e_end.isBefore(p_start);
let within_parent = e_start.isAfter(p_start) && e_end.isBefore(p_end);
let after_parent = e_start.isAfter(p_end);
// TODO: This isn't correct, yet
if(before_parent) {
// Child is behind parent
//console.log("Too far behind: " + i_child);
i_child++;
} else if(within_parent) {
//console.log("Added relation: " + i_child);
p.children = _.concat(p.children, e);
i_child++;
} else if(after_parent) {
// Child is ahead of parent
//console.log("Too far ahead: " + i_child);
break;
} else {
// TODO: Split events when this happens
console.warn("Between parents");
p.children = _.concat(p.children, e);
i_child++;
}
}
}
// Build the root node
console.log(parents);
let m_start = moment(_.first(parents).timestamp)
let m_end = moment(_.tail(parents).timestamp)
let duration = (m_end - m_start) / 1000;
return {
"timestamp": _.first(parents).timestamp,
// TODO: If we want a 12/24h clock, this has to change
"duration": duration,
"data": {"title": "ROOT"},
"children": parents
}
}
function chunkHierarchy(events, key) {
// TODO: Merge window events with same app and assign the title events as children
let new_events = [events[0]];
let p_i = 0;
_.each(events, (e, i) => {
if(e.data[key] === new_events[p_i].data[key]) {
//console.log("merge");
let e_moment = moment(e.timestamp);
let ne_moment = moment(new_events[p_i].timestamp);
new_events[p_i].duration = -e_moment.diff(ne_moment, "seconds", true) + e.duration;
console.log(new_events[p_i].duration);
} else {
//console.log("skip");
//console.log(new_events[p_i].duration);
p_i++;
new_events[p_i] = e;
}
});
_.each(new_events, (e, i) => {
// Get rid of other keys
e.data = _.pickBy(e.data, (v, k) => k === key);
})
return new_events;
}
function chunkHierarchy2(events, key) {
events = _.sortBy(events, (e) => e.timestamp);
events = _.reverse(events);
events = _.reduce(events,
function(acc, e) {
let last = _.last(acc);
if(last.data[key] === e.data[key]) {
last.duration = moment(e.timestamp).diff(last.timestamp, "seconds", true) + e.duration;
} else {
acc.push(e);
}
return acc;
},
[events[0]]);
return events;
}
this.todaysEvents(this.afkBucketId).then((events_afk) => {
this.todaysEvents(this.windowBucketId).then((events_window) => {
//events_afk = _.filter(events_afk, (e) => e.data.status == "not-afk");
//events_window = _.filter(events_window, (e) => e.duration > 10);
//events_afk = chunkHierarchy(events_afk, "status");
//events_window = chunkHierarchy(events_window, "app");
if(events_afk.length > 0 && events_window.length > 0) {
this.hierarchy = buildHierarchy(events_afk, events_window);
} else {
// FIXME: This should do the equivalent of "No data" when such is the case, but it doesn't.
console.log("HELLO");
this.hierarchy = {
"timestamp": "",
// TODO: If we want a 12/24h clock, this has to change
"duration": 0,
"data": {"title": "ROOT"},
"children": []
};
}
});
});
},
},
}
</script>
@@ -2,6 +2,19 @@
div
h2 Buckets

b-alert(variant="danger" :show="bucket_to_delete.length > 0")
| Are you sure you want to delete bucket {{bucket_to_delete}}? (This is permanent and cannot be undone)
b-button-toolbar
b-button-group(size="sm", class="mx-1")
b-button(v-on:click="deleteBucket(bucket_to_delete); bucket_to_delete = ''"
title="Export all events from this bucket to JSON",
variant="danger")
| Confirm
b-button(v-on:click="bucket_to_delete = ''"
title="Export all events from this bucket to JSON",
variant="success")
| Abort

b-alert(show)
| Are you looking to collect more data? Check out #[a(href="https://activitywatch.readthedocs.io/en/latest/watchers.html") the docs] for more watchers.
br
@@ -11,7 +24,7 @@ div
b-card.bucket-card(v-for="bucket in buckets", :key="bucket.id", :header="bucket.id")
b-button-toolbar
b-button-group(size="sm", class="mx-1")
b-button(variant="outline-primary", :to="'/buckets/' + bucket.id")
b-button(variant="primary", :to="'/buckets/' + bucket.id")
| Open bucket
b-button-group(size="sm", class="mx-1")
// TODO: This currently does not export bucket metadata, which makes importing difficult
@@ -22,6 +35,11 @@ div
title="Export all events from this bucket to JSON",
variant="outline-secondary")
| Export as JSON
b-button-group(size="sm", class="mx-1")
b-button(v-on:click="bucket_to_delete = bucket.id"
title="Export all events from this bucket to JSON",
variant="outline-danger")
| Delete bucket
small.bucket-last-updated(v-if="bucket.last_updated", slot="footer")
span
| Last updated:
@@ -66,6 +84,7 @@ export default {
data: () => {
return {
buckets: [],
bucket_to_delete: "",
}
},
methods: {
@@ -81,6 +100,13 @@ export default {
$Bucket.get({"id": bucket_id}).then((response) => {
this.buckets[bucket_id] = response.json();
});
},
deleteBucket: function(bucket_id) {
console.log("Deleting bucket " + bucket_id);
$Bucket.delete({"id": bucket_id}).then(() => {
this.getBuckets();
});
}
}
}
Oops, something went wrong.

2 comments on commit 6410170

@ErikBjare

This comment has been minimized.

Copy link
Member

ErikBjare replied Nov 26, 2017

Really nice that you refactored the Sunburst stuff. I thought of doing that myself but was pressed for time.

@johan-bjareholt

This comment has been minimized.

Copy link
Member

johan-bjareholt replied Nov 26, 2017

Well it's not really refactored, I only moved it. The Activity.vue file was starting to get annoyingly long which was the reason. There's a bit of refactoring needed around in the aw-webui code though.

Please sign in to comment.