Skip to content

Commit

Permalink
feat: added "count-as-active" setting to always count some apps/title…
Browse files Browse the repository at this point in the history
…s as active (#375)

Co-authored-by: Erik Bjäreholt <erik@bjareho.lt>
  • Loading branch information
ShootingKing-AM and ErikBjare authored Oct 17, 2022
1 parent 3361706 commit 40e6f25
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 2 deletions.
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, '\\')
: 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

0 comments on commit 40e6f25

Please sign in to comment.