Skip to content

Commit

Permalink
feat: poll user satisfaction and calls to action (#229)
Browse files Browse the repository at this point in the history
  • Loading branch information
billangli committed Oct 8, 2020
1 parent 105afdc commit f9e0bbf
Show file tree
Hide file tree
Showing 5 changed files with 172 additions and 10 deletions.
12 changes: 6 additions & 6 deletions jest.config.js
Expand Up @@ -10,13 +10,13 @@ module.exports = {
testEnvironment: 'jsdom',
transform: {
'^.+\\.js$': 'babel-jest',
'^.+\\.vue$': 'vue-jest'
'^.+\\.vue$': 'vue-jest',
},
testMatch: ['**/test/**/*.test.js?(x)'],
moduleNameMapper: {
'^~/(.+)$': '<rootDir>/src/$1',
},
modulePathIgnorePatterns: ['test/e2e/screenshot.test.js'] // Don't run this file in npm test
modulePathIgnorePatterns: ['test/e2e/screenshot.test.js'], // Don't run this file in npm test
},
{
displayName: 'node',
Expand All @@ -25,7 +25,7 @@ module.exports = {
testMatch: ['**/test/**/*.test.node.js?(x)'],
moduleNameMapper: {
'^~/(.+)$': '<rootDir>/src/$1',
}
}
]
};
},
},
],
};
1 change: 1 addition & 0 deletions src/App.vue
Expand Up @@ -4,6 +4,7 @@ div#wrapper

div.container.aw-container.my-3.py-3
error-boundary
user-satisfaction-poll
new-release-notification(v-if="isNewReleaseCheckEnabled")
router-view

Expand Down
160 changes: 160 additions & 0 deletions src/components/UserSatisfactionPoll.vue
@@ -0,0 +1,160 @@
<template lang="pug">
div
b-alert(v-if="isPollVisible", variant="info", show)
button(type="button", class="close", @click="isPollVisible=false") &times;
form
p
| Hey there! You've been using ActivityWatch for a while. How likely are you to recommend it to a friend/colleague (10 being most likely)?
div(class="radio-options")
div(v-for="i in options", class="option-group")
input(type="radio", :id="'option' + i", name="rating", :value="i", v-model="rating")
br
label(:for="'option' + i", style="display: block")
| {{ i }}
div(style="display: flex; justify-content: space-between")
a(@click="dontShowAgain" href="#")
| Don't show again
input(type="submit" value="Submit" @click="submit")

b-alert(v-if="isPosFollowUpVisible", variant="info" show)
button(type="button", class="close", @click="isPosFollowUpVisible=false") &times;
| We're really happy to hear you are enjoying ActivityWatch, but we think we can do even better! To help us help you, here are a few things you can do:
ul
li
| Support us on #[a(href="https://www.patreon.com/erikbjare") Patreon] or #[a(href="https://opencollective.com/activitywatch") Open Collective]
li
| If you're using ActivityWatch in the workplace, consider asking your employer to support us!
li
| Tell your friends and coworkers! Post about it on social media, we are on #[a(href="https://twitter.com/ActivityWatchIt") Twitter] and #[a(href="https://www.facebook.com/ActivityWatch") Facebook]
li
| Sign up for the newsletter
li
| Vote for new features on the #[a(href="https://forum.activitywatch.net/c/features") forum]
li
| Fill out the #[a(href="https://forms.gle/q2N9K5RoERBV8kqPA") feedback form]
li
| Rate us on #[a(href="https://alternativeto.net/software/activitywatch/") AlternativeTo]
li
| Star us on #[a(href="https://github.com/ActivityWatch/activitywatch") GitHub]

b-alert(v-if="isNegFollowUpVisible", variant="info" show)
button(type="button", class="close", @click="isNegFollowUpVisible=false") &times;
| We are sorry to hear that you did not enjoy using ActivityWatch, but we want to improve! We would be vary thankful if you help us by:
ul
li
| Fill out the #[a(href="https://forms.gle/q2N9K5RoERBV8kqPA") feedback form]
li
| Vote for new features on the #[a(href="https://forum.activitywatch.net/c/features") forum]
</template>

<style scoped>
.radio-options {
display: flex;
justify-content: space-around;
}
.option-group {
text-align: center;
}
ul {
margin: 0;
}
</style>

<script>
import moment from 'moment';
const NUM_OPTIONS = 10;
// INITIAL_WAIT_PERIOD is how long to wait from initialTimestamp to the first time that the poll shows up
const INITIAL_WAIT_PERIOD = 30 * 24 * 60 * 60;
// BACKOFF_PERIOD is how many seconds to wait to show the poll again if the user closed it
const BACKOFF_PERIOD = 24 * 60 * 60;
// The following may be used for testing
// const INITIAL_WAIT_PERIOD = 1;
// const BACKOFF_PERIOD = 1;
export default {
name: 'user-satisfaction-poll',
data() {
return {
isPollVisible: false,
isPosFollowUpVisible: false,
isNegFollowUpVisible: false,
// options is an array of [1, ..., NUM_OPTIONS]
options: Array.from({ length: NUM_OPTIONS }, (_, i) => i + 1),
rating: null,
data: null,
};
},
mounted() {
// Check if initialTimestamp (first time that the user runs the web app) exists
let initialTimestamp = moment();
if (localStorage.initialTimestamp) {
initialTimestamp = moment(localStorage.initialTimestamp);
} else {
localStorage.initialTimestamp = initialTimestamp;
}
// Get the rest of the data
this.retrieveData();
if (!this.data) {
this.data = {
isEnabled: true,
nextPollTime: initialTimestamp.add(INITIAL_WAIT_PERIOD, 'seconds'),
timesPollIsShown: 0,
};
this.saveData();
}
if (!this.data.isEnabled) {
return;
}
// Show poll if enough time has passed
if (moment() >= moment(this.data.nextPollTime)) {
this.data.timesPollIsShown = this.data.timesPollIsShown + 1;
this.isPollVisible = true;
this.data.nextPollTime = moment().add(BACKOFF_PERIOD, 'seconds');
}
// Show the poll a maximum of 3 times
if (this.data.timesPollIsShown > 2) {
this.data.isEnabled = false;
}
this.saveData();
},
methods: {
retrieveData() {
if (localStorage.getItem('userSatisfactionPollData')) {
try {
this.data = JSON.parse(localStorage.getItem('userSatisfactionPollData'));
} catch (err) {
console.error('userSatisfactionPollData not found in localStorage: ', err);
localStorage.removeItem('userSatisfactionPollData');
}
}
},
saveData() {
const parsed = JSON.stringify(this.data);
localStorage.setItem('userSatisfactionPollData', parsed);
},
submit() {
this.isPollVisible = false;
this.data.isEnabled = false;
this.saveData();
if (parseInt(this.rating) >= 6) {
this.isPosFollowUpVisible = true;
} else {
this.isNegFollowUpVisible = true;
}
},
dontShowAgain() {
this.isPollVisible = false;
this.data.isEnabled = false;
this.saveData();
},
},
};
</script>
1 change: 1 addition & 0 deletions src/main.js
Expand Up @@ -43,6 +43,7 @@ Vue.component('aw-header', () => import('./components/Header.vue'));
Vue.component('aw-devonly', () => import('./components/DevOnly.vue'));
Vue.component('aw-selectable-vis', () => import('./components/SelectableVisualization.vue'));
Vue.component('new-release-notification', () => import('./components/NewReleaseNotification.vue'));
Vue.component('user-satisfaction-poll', () => import('./components/UserSatisfactionPoll.vue'));

// Visualization components
Vue.component('aw-summary', () => import('./visualizations/Summary.vue'));
Expand Down
8 changes: 4 additions & 4 deletions vue.config.js
Expand Up @@ -12,10 +12,10 @@ module.exports = {
},
chainWebpack: config => {
config.plugin('define').tap(options => {
options[0]['process.env'].VUE_APP_ON_ANDROID = argv.os == 'android';
return options;
})
},
options[0]['process.env'].VUE_APP_ON_ANDROID = argv.os == 'android';
return options;
});
},
configureWebpack: {
resolve: {
alias: {
Expand Down

0 comments on commit f9e0bbf

Please sign in to comment.