Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 95 additions & 94 deletions src/stores/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,107 +432,108 @@ export const useActivityStore = defineStore('activity', {
},

async query_category_time_by_period({
timeperiod,
filter_categories,
filter_afk,
include_stopwatch,
dontQueryInactive,
always_active_pattern,
}: QueryOptions & { dontQueryInactive: boolean }) {
// TODO: Needs to be adapted for Android
let periods: string[];
const count = timeperiod.length[0];
const res = timeperiod.length[1];
if (res.startsWith('day') && count == 1) {
// If timeperiod is a single day, we query the individual hours
periods = timeperiodsStrsHoursOfPeriod(timeperiod);
} else if (
res.startsWith('day') ||
(res.startsWith('week') && count == 1) ||
(res.startsWith('month') && count == 1)
) {
// If timeperiod is several days, or a single week/month, we query the individual days
periods = timeperiodsStrsDaysOfPeriod(timeperiod);
} else if (timeperiod.length[1].startsWith('year') && timeperiod.length[0] == 1) {
// If timeperiod a single year, we query the individual months
periods = timeperiodsStrsMonthsOfPeriod(timeperiod);
} else {
console.error(`Unknown timeperiod length: ${timeperiod.length}`);
timeperiod,
filter_categories,
filter_afk,
include_stopwatch,
dontQueryInactive,
always_active_pattern,
}: QueryOptions & { dontQueryInactive: boolean }) {
let periods: string[];
const count = timeperiod.length[0];
const res = timeperiod.length[1];

// Handle hourly timeperiods
if (res.startsWith('hour')) {
// For hour periods (including custom ranges like 1.5 hours),
// just query the exact timeperiod as a single period
periods = timeperiodsStrsHoursOfPeriod(timeperiod);
} else if (res.startsWith('day') && count == 1) {
// If timeperiod is a single day, we query the individual hours
periods = timeperiodsStrsHoursOfPeriod(timeperiod);
} else if (
res.startsWith('day') ||
(res.startsWith('week') && count == 1) ||
(res.startsWith('month') && count == 1)
) {
// If timeperiod is several days, or a single week/month, we query the individual days
periods = timeperiodsStrsDaysOfPeriod(timeperiod);
} else if (timeperiod.length[1].startsWith('year') && timeperiod.length[0] == 1) {
// If timeperiod a single year, we query the individual months
periods = timeperiodsStrsMonthsOfPeriod(timeperiod);
} else {
console.error(`Unknown timeperiod length: ${timeperiod.length}`);
}

// Filter out periods that start in the future
periods = periods.filter(period => new Date(period.split('/')[0]) < new Date());

const signal = getClient().controller.signal;
let cancelled = false;
signal.onabort = () => {
cancelled = true;
console.debug('Request aborted');
};

// Query one period at a time, to avoid timeout on slow queries
let data = [];
for (const period of periods) {
if (cancelled) {
throw signal['reason'] || 'unknown reason';
}

// Filter out periods that start in the future
periods = periods.filter(period => new Date(period.split('/')[0]) < new Date());

const signal = getClient().controller.signal;
let cancelled = false;
signal.onabort = () => {
cancelled = true;
console.debug('Request aborted');
};
// Only query periods with known data from AFK bucket
if (dontQueryInactive && this.active.events.length > 0) {
const start = new Date(period.split('/')[0]);
const end = new Date(period.split('/')[1]);

// Query one period at a time, to avoid timeout on slow queries
let data = [];
for (const period of periods) {
// Not stable
//signal.throwIfAborted();
if (cancelled) {
throw signal['reason'] || 'unknown reason';
}

// Only query periods with known data from AFK bucket
if (dontQueryInactive && this.active.events.length > 0) {
const start = new Date(period.split('/')[0]);
const end = new Date(period.split('/')[1]);

// Retrieve active time in period
const period_activity = this.active.events.find((e: IEvent) => {
return start < new Date(e.timestamp) && new Date(e.timestamp) < end;
});
// Retrieve active time in period
const period_activity = this.active.events.find((e: IEvent) => {
return start < new Date(e.timestamp) && new Date(e.timestamp) < end;
});

// Check if there was active time
if (!(period_activity && period_activity.duration > 0)) {
data = data.concat([{ cat_events: [] }]);
continue;
}
// Check if there was active time
if (!(period_activity && period_activity.duration > 0)) {
data = data.concat([{ cat_events: [] }]);
continue;
}

const isAndroid = this.buckets.android[0] !== undefined;
const categories = useCategoryStore().classes_for_query;
// TODO: Clean up call, pass QueryParams in fullDesktopQuery as well
// TODO: Unify QueryOptions and QueryParams
const query = queries.categoryQuery({
bid_browsers: this.buckets.browser,
bid_stopwatch:
include_stopwatch && this.buckets.stopwatch.length > 0
? this.buckets.stopwatch[0]
: undefined,
categories,
filter_categories,
filter_afk,
always_active_pattern,
...(isAndroid
? {
bid_android: this.buckets.android[0],
}
: {
bid_afk: this.buckets.afk[0],
bid_window: this.buckets.window[0],
}),
});
const result = await getClient().query([period], query, {
verbose: true,
name: 'categoryQuery',
});
data = data.concat(result);
}

// Zip periods
let by_period = _.zipObject(periods, data);
// Filter out values that are undefined (no longer needed, only used when visualization was progressive (looks buggy))
by_period = _.fromPairs(_.toPairs(by_period).filter(o => o[1]));
const isAndroid = this.buckets.android[0] !== undefined;
const categories = useCategoryStore().classes_for_query;
const query = queries.categoryQuery({
bid_browsers: this.buckets.browser,
bid_stopwatch:
include_stopwatch && this.buckets.stopwatch.length > 0
? this.buckets.stopwatch[0]
: undefined,
categories,
filter_categories,
filter_afk,
always_active_pattern,
...(isAndroid
? {
bid_android: this.buckets.android[0],
}
: {
bid_afk: this.buckets.afk[0],
bid_window: this.buckets.window[0],
}),
});
const result = await getClient().query([period], query, {
verbose: true,
name: 'categoryQuery',
});
data = data.concat(result);
}

this.query_category_time_by_period_completed({ by_period });
},
// Zip periods
let by_period = _.zipObject(periods, data);
// Filter out values that are undefined
by_period = _.fromPairs(_.toPairs(by_period).filter(o => o[1]));

this.query_category_time_by_period_completed({ by_period });
},

async query_active_history_android({ timeperiod }: QueryOptions) {
const periods = timeperiodStrsAroundTimeperiod(timeperiod).filter(tp_str => {
Expand Down Expand Up @@ -750,4 +751,4 @@ export const useActivityStore = defineStore('activity', {
this.category.by_period = by_period;
},
},
});
});
96 changes: 84 additions & 12 deletions src/views/activity/Activity.vue
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ div
div.mx-2(v-if="periodLength === 'day'")
input.form-control.px-2(id="date" type="date" :value="_date" :max="today"
@change="setDate($event.target.value, periodLength)")

div.mx-2(v-if="periodLength === 'hour'")
input.form-control.px-2(id="date" type="date" :value="_date" :max="today"
@change="setDate($event.target.value, periodLength)")
div.mx-2(v-if="periodLength === 'hour'")
b-input-group
input.form-control.px-2(type="time" :value="startTime"
@change="setStartTime($event.target.value)")
b-input-group-text.px-2 to
input.form-control.px-2(type="time" :value="endTime"
@change="setEndTime($event.target.value)")


div.ml-auto
b-button-group
Expand Down Expand Up @@ -178,11 +190,6 @@ export default {
host: String,
date: {
type: String,
// NOTE: This does not work as you'd might expect since the default is set on
// initialization, which would lead to the same date always being returned,
// even if the day has changed.
// Instead, use the computed _date.
//default: get_today(),
},
periodLength: {
type: String,
Expand Down Expand Up @@ -210,6 +217,29 @@ export default {
...mapState(useSettingsStore, ['devmode']),
...mapState(useSettingsStore, ['always_active_pattern']),

// Get start and end times from query params, with defaults
startTime: {
get() {
return this.$route.query.start_time || '00:00';
},
set(value) {
this.$router.push({
query: { ...this.$route.query, start_time: value }
});
}
},

endTime: {
get() {
return this.$route.query.end_time || '23:59';
},
set(value) {
this.$router.push({
query: { ...this.$route.query, end_time: value }
});
}
},

// number of filters currently set (different from defaults)
filters_set() {
return (this.filter_category ? 1 : 0) + (!this.filter_afk ? 1 : 0);
Expand All @@ -233,6 +263,7 @@ export default {
periodLengths: function () {
const settingsStore = useSettingsStore();
let periods: Record<string, string> = {
hour: 'hour',
day: 'day',
week: 'week',
month: 'month',
Expand All @@ -248,7 +279,7 @@ export default {
return periods;
},
periodIsBrowseable: function () {
return ['day', 'week', 'month', 'year'].includes(this.periodLength);
return ['hour', 'day', 'week', 'month', 'year'].includes(this.periodLength);
},
currentView: function () {
return this.views.find(v => v.id == this.$route.params.view_id) || this.views[0];
Expand Down Expand Up @@ -284,6 +315,31 @@ export default {
const settingsStore = useSettingsStore();

if (this.periodIsBrowseable) {
if (this.periodLength === 'hour') {
// Parse start and end times
const [startHour, startMin] = this.startTime.split(':').map(Number);
const [endHour, endMin] = this.endTime.split(':').map(Number);

// Create moment objects for start and end
const startDateTime = moment(this._date)
.startOf('day')
.add(startHour, 'hours')
.add(startMin, 'minutes');

const endDateTime = moment(this._date)
.startOf('day')
.add(endHour, 'hours')
.add(endMin, 'minutes');

// Calculate duration in hours (can be fractional)
const durationHours = endDateTime.diff(startDateTime, 'minutes') / 60;

return {
start: startDateTime.format(),
length: [durationHours, 'hour'], // Use 'hour' (singular) to match TimelineBarChart
};
}

return {
start: get_day_start_with_offset(this._date, settingsStore.startOfDay),
length: [1, this.periodLength],
Expand All @@ -303,13 +359,13 @@ export default {
const periodStart = moment(this.timeperiod.start);
const dateFormatString = 'YYYY-MM-DD';

// it's helpful to render a range for the week as opposed to just the start of the week
// or the number of the week so users can easily determine (a) if we are using monday/sunday as the week
// start and exactly when the week ends. The formatting code ends up being a bit more wonky, but it's
// worth the tradeoff. https://github.com/ActivityWatch/aw-webui/pull/284

let periodLength;
if (this.periodIsBrowseable) {
if (this.periodLength === 'hour') {
// For hour view, show the time range
const endTime = moment(periodStart).add(this.timeperiod.length[0], 'hours');
return `${periodStart.format('YYYY-MM-DD HH:mm')} — ${endTime.format('HH:mm')}`;
}
periodLength = [1, this.periodLength];
} else {
if (this.periodLength === 'last7d') {
Expand Down Expand Up @@ -364,6 +420,10 @@ export default {

methods: {
previousPeriod: function () {
if (this.periodLength === 'hour') {
// For hour view, go back by 1 day
return moment(this._date).subtract(1, 'day').format('YYYY-MM-DD');
}
return moment(this._date)
.subtract(
this.timeperiod.length[0],
Expand All @@ -372,6 +432,10 @@ export default {
.format('YYYY-MM-DD');
},
nextPeriod: function () {
if (this.periodLength === 'hour') {
// For hour view, go forward by 1 day
return moment(this._date).add(1, 'day').format('YYYY-MM-DD');
}
return moment(this._date)
.add(
this.timeperiod.length[0],
Expand Down Expand Up @@ -477,6 +541,14 @@ export default {
name: '',
};
},

setStartTime: function(time) {
this.startTime = time;
},

setEndTime: function(time) {
this.endTime = time;
}
},
};
</script>
</script>
Loading