Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added "count-as-active" setting to always count some apps/titles as active #375

Merged
Merged
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
13 changes: 12 additions & 1 deletion src/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ interface DesktopQueryParams extends BaseQueryParams {
bid_window: string;
bid_afk: string;
filter_afk: boolean;
always_active_pattern: string;
}

interface AndroidQueryParams extends BaseQueryParams {
Expand All @@ -48,6 +49,7 @@ interface AndroidQueryParams extends BaseQueryParams {
interface MultiQueryParams extends BaseQueryParams {
hosts: string[];
filter_afk: boolean;
always_active_pattern: string;
// This can be used to override params on a per-host basis
host_params: { [host: string]: DesktopQueryParams | AndroidQueryParams };
}
Expand Down Expand Up @@ -102,6 +104,9 @@ function isMultiParams(object: any): object is MultiQueryParams {
export function canonicalEvents(params: DesktopQueryParams | AndroidQueryParams): string {
// Needs escaping for regex patterns like '\w' to work (JSON.stringify adds extra unecessary escaping)
const categories_str = JSON.stringify(params.categories).replace(/\\\\/g, '\\');
const always_active_pattern_str = isDesktopParams(params)
? params.always_active_pattern.replace(/\\\\/g, '\\')
ShootingKing-AM marked this conversation as resolved.
Show resolved Hide resolved
: undefined;
const cat_filter_str = JSON.stringify(params.filter_categories);

// For simplicity, we assume that bid_window and bid_android are exchangeable (note however it needs special treatment)
Expand All @@ -115,7 +120,13 @@ export function canonicalEvents(params: DesktopQueryParams | AndroidQueryParams)
// Fetch not-afk events
isDesktopParams(params)
? `not_afk = flood(query_bucket("${params.bid_afk}"));
not_afk = filter_keyvals(not_afk, "status", ["not-afk"]);`
not_afk = filter_keyvals(not_afk, "status", ["not-afk"]);` +
(always_active_pattern_str
? `not_treat_as_afk = filter_keyvals_regex(events, "app", "${always_active_pattern_str}");
not_afk = period_union(not_afk, not_treat_as_afk);
not_treat_as_afk = filter_keyvals_regex(events, "title", "${always_active_pattern_str}");
not_afk = period_union(not_afk, not_treat_as_afk);`
: '')
: '',
// Fetch browser events
isDesktopParams(params) && params.bid_browsers
Expand Down
8 changes: 7 additions & 1 deletion src/stores/activity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface QueryOptions {
filter_categories?: string[][];
dont_query_inactive?: boolean;
force?: boolean;
always_active_pattern?: string;
}

interface State {
Expand Down Expand Up @@ -301,7 +302,7 @@ export const useActivityStore = defineStore('activity', {
},

async query_multidevice_full(
{ timeperiod, filter_categories, filter_afk }: QueryOptions,
{ timeperiod, filter_categories, filter_afk, always_active_pattern }: QueryOptions,
hosts: string[]
) {
const periods = [timeperiodToStr(timeperiod)];
Expand All @@ -313,6 +314,7 @@ export const useActivityStore = defineStore('activity', {
categories,
filter_categories,
host_params: {},
always_active_pattern,
});
const data = await getClient().query(periods, q);
const data_window = data[0].window;
Expand All @@ -329,6 +331,7 @@ export const useActivityStore = defineStore('activity', {
filter_afk,
include_audible,
include_stopwatch,
always_active_pattern,
}: QueryOptions) {
const periods = [timeperiodToStr(timeperiod)];
const categories = useCategoryStore().classes_for_query;
Expand All @@ -345,6 +348,7 @@ export const useActivityStore = defineStore('activity', {
categories,
filter_categories,
include_audible,
always_active_pattern,
});
const data = await getClient().query(periods, q);
const data_window = data[0].window;
Expand Down Expand Up @@ -382,6 +386,7 @@ export const useActivityStore = defineStore('activity', {
filter_afk,
include_stopwatch,
dontQueryInactive,
always_active_pattern,
}: QueryOptions & { dontQueryInactive: boolean }) {
// TODO: Needs to be adapted for Android
let periods: string[];
Expand Down Expand Up @@ -454,6 +459,7 @@ export const useActivityStore = defineStore('activity', {
categories,
filter_categories,
filter_afk,
always_active_pattern,
})
);
data = data.concat(result);
Expand Down
3 changes: 3 additions & 0 deletions src/stores/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ interface State {

newReleaseCheckData: Record<string, any>;
userSatisfactionPollData: Record<string, any>;
always_active_pattern: string;

// Whether to show certain WIP features
devmode: boolean;
Expand Down Expand Up @@ -49,6 +50,8 @@ export const useSettingsStore = defineStore('settings', {
},
userSatisfactionPollData: {},

always_active_pattern: '',

// Developer settings
// NOTE: PRODUCTION might be undefined (in tests, for example)
devmode: typeof PRODUCTION === 'undefined' ? true : !PRODUCTION,
Expand Down
2 changes: 2 additions & 0 deletions src/views/activity/Activity.vue
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ export default {
computed: {
...mapState(useViewsStore, ['views']),
...mapState(useSettingsStore, ['devmode']),
...mapState(useSettingsStore, ['always_active_pattern']),

// number of filters currently set (different from defaults)
filters_set() {
Expand Down Expand Up @@ -406,6 +407,7 @@ export default {
include_audible: this.include_audible,
include_stopwatch: this.include_stopwatch,
filter_categories: this.filter_categories,
always_active_pattern: this.always_active_pattern,
};
await this.activityStore.ensure_loaded(queryOptions);
},
Expand Down
65 changes: 65 additions & 0 deletions src/views/settings/ActivePatternSettings.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<template lang="pug">
div
div.d-sm-flex.justify-content-between
div
h5.mb-2.mb-sm-0 Always count as active pattern

small
| Apps or titles matching this regular expression will never be counted as AFK.
|
| Can be used to count time as active, despite no input (like meetings, or games with controllers). An empty string disables it.
|
| Example expression:&nbsp;
code(style="background-color: rgba(200, 200, 200, 0.3); padding: 2px; border-radius: 2px;")
| Zoom Meeting|Google Meet|Microsoft Teams
div
b-form-input(size="sm" v-model="always_active_pattern")
small.text-right
div(v-if="enabled" style="color: #0A0") Enabled
div(v-else, style="color: gray") Disabled
div(v-if="enabled && broad_pattern" style="color: #A00") Pattern too broad

</template>

<script>
import { useSettingsStore } from '~/stores/settings';

export default {
name: 'ActivePatternSettings',
data() {
return {
settingsStore: useSettingsStore(),
};
},
computed: {
enabled: function () {
return this.settingsStore.always_active_pattern != '';
},
broad_pattern: function () {
// Check if the pattern matches random strings that we don't expect it to
// like the alphabet
const pattern = this.settingsStore.always_active_pattern;
if (pattern == '') {
return false;
}
const re = new RegExp(pattern);
const alphabet = 'abcdefghijklmnopqrstuvwxyz';
const numbers = '0123456789';
return re.test(
'THIS STRING SHOULD PROBABLY NOT MATCH: ' + alphabet + alphabet.toUpperCase() + numbers
);
},
always_active_pattern: {
get() {
return this.settingsStore.always_active_pattern;
},
set(value) {
if (value.trim().length != 0 || this.settingsStore.always_active_pattern.length != 0) {
console.log('Setting always_active_pattern to ' + value);
this.settingsStore.update({ always_active_pattern: value });
}
},
},
},
};
</script>
6 changes: 6 additions & 0 deletions src/views/settings/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ div

hr

ActivePatternSettings

hr

CategorizationSettings

hr
Expand All @@ -48,6 +52,7 @@ import LandingPageSettings from '~/views/settings/LandingPageSettings.vue';
import DeveloperSettings from '~/views/settings/DeveloperSettings.vue';
import Theme from '~/views/settings/Theme.vue';
import ColorSettings from '~/views/settings/ColorSettings.vue';
import ActivePatternSettings from '~/views/settings/ActivePatternSettings.vue';

export default {
name: 'Settings',
Expand All @@ -60,6 +65,7 @@ export default {
Theme,
ColorSettings,
DeveloperSettings,
ActivePatternSettings,
},
async created() {
await this.init();
Expand Down
4 changes: 4 additions & 0 deletions test/unit/__snapshots__/queries.test.node.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ exports[`generate fullDesktopQuery 1`] = `
"events = flood(query_bucket(\\"\\"));
not_afk = flood(query_bucket(\\"\\"));
not_afk = filter_keyvals(not_afk, \\"status\\", [\\"not-afk\\"]);
not_treat_as_afk = filter_keyvals_regex(events, \\"app\\", \\"meow|nyaan\\");
not_afk = period_union(not_afk, not_treat_as_afk);
not_treat_as_afk = filter_keyvals_regex(events, \\"title\\", \\"meow|nyaan\\");
not_afk = period_union(not_afk, not_treat_as_afk);
browser_events = [];
audible_events = filter_keyvals(browser_events, \\"audible\\", [true]);
not_afk = period_union(not_afk, audible_events);
Expand Down
2 changes: 2 additions & 0 deletions test/unit/queries.test.node.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ test('generate fullDesktopQuery', () => {
const categories = [];
const filter_categories = true;
const include_audible = true;
const always_active_pattern = 'meow|nyaan';
const query_lines = queries.fullDesktopQuery({
bid_window,
bid_afk,
Expand All @@ -16,6 +17,7 @@ test('generate fullDesktopQuery', () => {
categories,
filter_categories,
include_audible,
always_active_pattern,
});

// join query lines into a single string
Expand Down