Skip to content

Commit a6e1770

Browse files
terichadbournefsdiogo
authored andcommitted
feat: track user events in Countly (#282)
1 parent 33320ff commit a6e1770

File tree

7 files changed

+79
-12
lines changed

7 files changed

+79
-12
lines changed

cypress/integration/tutorials.spec.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ function viewSolutionsAndSubmitAll ({ tutorialName, lessonCount, hasResources =
1414
cy.get(`[href="#/${tutorialName}/01"]`).click()
1515
})
1616
for (let i = 1; i <= lessonCount; i++) {
17-
it(`should view the solution and pass test ${i}`, function () {
18-
cy.url().should('include', `#/${tutorialName}/0${i}`)
17+
let lessonNr = i.toString().padStart(2, 0)
18+
it(`should view the solution and pass test ${lessonNr}`, function () {
19+
cy.url().should('include', `#/${tutorialName}/${lessonNr}`)
1920
cy.get('[data-cy=code-editor-ready]').should('be.visible') // wait for editor to be updated
2021
cy.get('[data-cy=view-solution]').click()
2122
cy.get('[data-cy=solution-editor-ready]').should('be.visible') // wait for editor to be updated

public/index.html

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,26 @@
6060
const COUNTLY_URL = 'https://countly.proto.school';
6161

6262
(function(){
63-
if (!_dntEnabled()) {
63+
if (!window.Cypress && !_dntEnabled()) {
6464
// Provide countly initialization parameters
6565
Countly.app_key = location.hostname === 'proto.school' ? COUNTLY_KEY_PROTOSCHOOL : COUNTLY_KEY_PROTOSCHOOL_TEST;
6666
Countly.url = COUNTLY_URL;
6767
// Choose what to track
6868
Countly.q.push(['track_sessions']);
6969
Countly.q.push(['track_pageview']);
7070
Countly.q.push(['track_clicks']);
71+
Countly.q.push(['track_scrolls']);
72+
Countly.q.push(['track_links']);
7173
// Load countly script asynchronously
7274
var cly = document.createElement('script'); cly.type = 'text/javascript';
7375
cly.async = true;
7476
cly.src = 'https://countly.proto.school/sdk/web/countly.min.js';
75-
cly.onload = function(){Countly.init()};
77+
cly.onload = function(){
78+
Countly.init()
79+
Countly.getViewUrl = function () {
80+
return location.pathname + location.search + location.hash;
81+
}
82+
};
7683
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(cly, s);
7784
}
7885
})();

src/components/Lesson.vue

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ import Info from './Info.vue'
109109
import Validator from './Validator.vue'
110110
import CID from 'cids'
111111
import marked from 'marked'
112+
import { EVENTS } from '../static/countly'
112113
113114
const hljs = require('highlight.js/lib/highlight.js')
114115
hljs.registerLanguage('js', require('highlight.js/lib/languages/javascript'))
@@ -204,6 +205,7 @@ export default {
204205
parsedConcepts: marked(self.$attrs.concepts || ''),
205206
cacheKey: 'cached' + self.$route.path,
206207
cachedStateMsg: '',
208+
tutorialPath: self.$route.path.split('/')[1],
207209
lessonKey: 'passed' + self.$route.path,
208210
lessonPassed: !!localStorage['passed' + self.$route.path],
209211
lessonTitle: self.$attrs.lessonTitle,
@@ -300,6 +302,9 @@ export default {
300302
this.lessonPassed = !!localStorage[this.lessonKey]
301303
this.isSubmitting = false
302304
this.clearPassed()
305+
if (auto !== true) {
306+
this.trackEvent(EVENTS.CODE_SUBMIT_WRONG)
307+
}
303308
return
304309
}
305310
// Hide the solution
@@ -315,10 +320,17 @@ export default {
315320
}
316321
if (output.test.success) {
317322
localStorage[this.lessonKey] = 'passed'
323+
if (auto !== true) {
324+
// track lesson passed if it has an exercise (incl file ones)
325+
this.trackEvent(EVENTS.LESSON_PASSED)
326+
this.isTutorialPassed()
327+
}
318328
} else {
319329
this.clearPassed()
330+
if (auto !== true) {
331+
this.trackEvent(EVENTS.CODE_SUBMIT_WRONG)
332+
}
320333
}
321-
322334
this.lessonPassed = !!localStorage[this.lessonKey]
323335
this.isSubmitting = false
324336
},
@@ -350,6 +362,7 @@ export default {
350362
this.clearPassed()
351363
delete this.output.test
352364
this.showUploadInfo = false
365+
this.trackEvent(EVENTS.CODE_RESET)
353366
},
354367
resetFileUpload: function () {
355368
this.uploadedFiles = false
@@ -359,11 +372,35 @@ export default {
359372
clearPassed: function () {
360373
delete localStorage[this.lessonKey]
361374
this.lessonPassed = !!localStorage[this.lessonKey]
375+
delete localStorage[`passed/${this.tutorialPath}`]
362376
},
363377
loadCodeFromCache: function () {
364378
this.code = localStorage[this.cacheKey]
365379
this.editor.setValue(this.code)
366380
},
381+
isTutorialPassed: function () {
382+
for (let i = 1; i <= this.lessonsInTutorial; i++) {
383+
let lessonNr = i.toString().padStart(2, 0)
384+
const lsKey = `passed/${this.tutorialPath}/${lessonNr}`
385+
if (localStorage[lsKey] !== 'passed') {
386+
return false
387+
}
388+
}
389+
localStorage[`passed/${this.tutorialPath}`] = 'passed'
390+
this.trackEvent(EVENTS.TUTORIAL_PASSED)
391+
return true
392+
},
393+
trackEvent: function (event, opts = {}) {
394+
window.Countly.q.push(['add_event', {
395+
'key': event,
396+
'segmentation': {
397+
'tutorial': this.tutorialShortname,
398+
'lessonNumber': this.lessonNumber,
399+
'path': this.$route.path,
400+
...opts
401+
}
402+
}])
403+
},
367404
onMounted: function (editor) {
368405
// runs on page load, NOT on every keystroke in editor
369406
this.editor = editor
@@ -405,8 +442,16 @@ export default {
405442
if (this.output.test.success) {
406443
localStorage[this.lessonKey] = 'passed'
407444
this.lessonPassed = !!localStorage[this.lessonKey]
445+
if (result.auto !== true) {
446+
// track multiple choice lesson passed if not on page load
447+
this.trackEvent(EVENTS.LESSON_PASSED)
448+
this.isTutorialPassed()
449+
}
408450
} else {
409451
this.clearPassed()
452+
if (result.auto !== true) {
453+
this.trackEvent(EVENTS.CHOICE_SUBMIT_WRONG, { wrongChoice: result.selected })
454+
}
410455
}
411456
},
412457
next: function () {
@@ -415,6 +460,11 @@ export default {
415460
} else {
416461
localStorage[this.lessonKey] = 'passed'
417462
this.lessonPassed = !!localStorage[this.lessonKey]
463+
// track passed lesson if text only
464+
if (!this.isMultipleChoiceLesson) {
465+
this.trackEvent(EVENTS.LESSON_PASSED)
466+
this.isTutorialPassed()
467+
}
418468
}
419469
let current = this.lessonNumber
420470

src/components/Quiz.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default {
1919
}
2020
},
2121
mounted: function () {
22-
this.handleRadioClick()
22+
this.handleRadioClick(true)
2323
},
2424
computed: {
2525
correctChoice: function () {
@@ -36,13 +36,13 @@ export default {
3636
}
3737
},
3838
methods: {
39-
handleRadioClick () {
39+
handleRadioClick (auto = false) {
4040
let result = null
4141
if (this.selected !== '') {
4242
if (parseInt(this.selected) === this.correctChoice) {
43-
result = { success: this.choices[this.selected].feedback, selected: this.selected }
43+
result = { success: this.choices[this.selected].feedback, selected: this.selected, auto: auto }
4444
} else {
45-
result = { fail: this.choices[this.selected].feedback, selected: this.selected }
45+
result = { fail: this.choices[this.selected].feedback, selected: this.selected, auto: auto }
4646
}
4747
this.$emit('handleChoice', result)
4848
}

src/components/Tutorial.vue

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<template v-else>
1717
{{tutorial.title}}
1818
</template>
19+
<span v-if="isTutorialPassed" class="ml1">🏆</span>
1920
</h2>
2021
<p class="f5 fw5 ma0 pt2 lh-copy charcoal-muted">{{tutorial.description}}</p>
2122
<ul class="mv4 pa0 f5" style="list-style-type: none; background: rgba(11, 58, 82, 5%)">
@@ -45,10 +46,11 @@ export default {
4546
components: {
4647
LessonLink
4748
},
48-
data: () => {
49+
data: self => {
4950
return {
5051
ipfsLogo: ipfsLogo,
51-
libp2pLogo: libp2pLogo
52+
libp2pLogo: libp2pLogo,
53+
isTutorialPassed: !!localStorage[`passed/${self.tutorial.lessons[0].to.split('/')[1]}`]
5254
}
5355
},
5456
computed: {

src/main.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ const router = new VueRouter({
108108
// track page view via Countly when route changes
109109
router.afterEach((to) => {
110110
if (!window.Countly) return
111-
window.Countly.q.push(['track_pageview', to.path])
111+
window.Countly.q.push(['track_pageview', '/#' + to.path])
112112
})
113113

114114
Vue.config.productionTip = false

src/static/countly.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export const EVENTS = {
2+
CODE_RESET: 'resetCode',
3+
CODE_SUBMIT_WRONG: 'submitWrongCode',
4+
CHOICE_SUBMIT_WRONG: 'submitWrongChoice',
5+
LESSON_PASSED: 'lessonPassed',
6+
TUTORIAL_PASSED: 'tutorialPassed'
7+
}

0 commit comments

Comments
 (0)