From 9dbcf27e115269556030ed75f114a4041c99f82e Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Wed, 8 Jun 2016 06:24:59 -0700 Subject: [PATCH] Add classroom Mixpanel logging Closes #3720 --- app/core/Tracker.coffee | 9 +- app/templates/courses/courses-view.jade | 10 +- app/templates/courses/teacher-class-view.jade | 6 +- .../courses/teacher-classes-view.jade | 36 +++---- .../courses/teacher-courses-view.jade | 4 +- app/templates/new-home-view.jade | 28 +++--- app/views/NewHomeView.coffee | 71 +++++++------- .../courses/ClassroomSettingsModal.coffee | 6 +- app/views/courses/ClassroomView.coffee | 2 +- app/views/courses/CourseDetailsView.coffee | 5 + app/views/courses/CoursesView.coffee | 43 ++++++--- app/views/courses/EnrollmentsView.coffee | 22 +++-- .../courses/RestrictedToStudentsView.coffee | 5 +- app/views/courses/TeacherClassView.coffee | 32 ++++--- app/views/courses/TeacherClassesView.coffee | 45 ++++++--- app/views/courses/TeacherCoursesView.coffee | 94 +++---------------- app/views/play/level/ControlBarView.coffee | 6 +- .../level/modal/CourseVictoryModal.coffee | 3 + .../ConvertToTeacherAccountView.coffee | 4 + .../teachers/CreateTeacherAccountView.coffee | 6 +- app/views/teachers/RequestQuoteView.coffee | 12 ++- .../teachers/RestrictedToTeachersView.coffee | 5 +- 22 files changed, 228 insertions(+), 226 deletions(-) diff --git a/app/core/Tracker.coffee b/app/core/Tracker.coffee index e1a4ae7cc..2f2aab758 100644 --- a/app/core/Tracker.coffee +++ b/app/core/Tracker.coffee @@ -95,9 +95,13 @@ module.exports = class Tracker extends CocoClass analytics.identify me.id, traits trackPageView: (includeIntegrations=[]) -> + includeMixpanel = (name) -> + mixpanelIncludes = ['', 'schools', 'play', 'play/level/dungeons-of-kithgard'] + name in mixpanelIncludes or /courses|students|teachers/ig.test(name) + name = Backbone.history.getFragment() url = "/#{name}" - console.log "Would track analytics pageview: #{url}" if debugAnalytics + console.log "Would track analytics pageview: #{url} Mixpanel=#{includeMixpanel(name)}" if debugAnalytics @trackEventInternal 'Pageview', url: name unless me?.isAdmin() and @isProduction return unless @isProduction and not me.isAdmin() @@ -106,8 +110,7 @@ module.exports = class Tracker extends CocoClass ga? 'send', 'pageview', url # Mixpanel - mixpanelIncludes = ['', 'courses', 'courses/purchase', 'courses/teachers', 'courses/students', 'schools', 'teachers', 'teachers/freetrial', 'teachers/quote', 'play', 'play/level/dungeons-of-kithgard'] - mixpanel.track('page viewed', 'page name' : name, url : url) if name in mixpanelIncludes + mixpanel.track('page viewed', 'page name' : name, url : url) if includeMixpanel(name) if me.isTeacher() and @segmentLoaded options = {} diff --git a/app/templates/courses/courses-view.jade b/app/templates/courses/courses-view.jade index 11a03df1e..69e5265b5 100644 --- a/app/templates/courses/courses-view.jade +++ b/app/templates/courses/courses-view.jade @@ -67,7 +67,7 @@ block content h5 span.spr= classroom.get('name') span.spr (#{(classroom.get('aceConfig') || {}).language === 'javascript' ? 'JavaScript' : 'Python'}) - a(href="/courses/"+classroom.id, data-i18n="courses.view_class") + a.view-class-btn(data-classroom-id=classroom.id, data-i18n="courses.view_class") - var courseInstances = view.courseInstances.where({classroomID: classroom.id}); for courseInstance in courseInstances @@ -77,7 +77,7 @@ block content h6 span.spr= course.get('name') small - a(href="/courses/"+courseInstance.get('courseID')+'/'+courseInstance.id, data-i18n="courses.view_levels") + a.view-levels-btn(data-course-id=courseInstance.get('courseID'), data-courseinstance-id=courseInstance.id, data-i18n="courses.view_levels") +course-instance-body(courseInstance, classroom) .clearfix @@ -112,19 +112,19 @@ mixin course-instance-body(courseInstance, classroom) - var arenaLevel = stats.levels.arena; if arenaLevel - var arenaURL = "/play/ladder/"+arenaLevel.get('slug')+"/course/"+courseInstance.id; - a.btn.btn-burgandy.btn-lg.m-b-1(href=arenaURL) + a.play-btn.btn.btn-burgandy.btn-lg.m-b-1(data-href=arenaURL, data-level-slug=arenaLevel.get('slug'), data-event-action="Students Play Arena") span(data-i18n="courses.play_arena") else a.btn.btn-default.btn-lg.m-b-1(disabled=true, data-i18n="courses.course_complete") else if stats.levels.next != stats.levels.first - var next = stats.levels.next; - var levelURL = "/play/level/"+next.get('slug')+"?course="+courseInstance.get('courseID')+"&course-instance="+courseInstance.id; - a.btn.btn-forest.btn-lg.m-b-1(href=levelURL) + a.play-btn.btn.btn-forest.btn-lg.m-b-1(data-href=levelURL, data-level-slug=next.get('slug'), data-event-action="Students Continue Course") span(data-i18n="common.continue") else - var firstLevel = stats.levels.first; - var levelURL = "/play/level/"+firstLevel.get('slug')+"?course="+courseInstance.get('courseID')+"&course-instance="+courseInstance.id; - a.btn.btn-navy.btn-lg.m-b-1(href=levelURL) + a.play-btn.btn.btn-navy.btn-lg.m-b-1(data-href=levelURL, data-level-slug=firstLevel.get('slug'), data-event-action="Students Start Course") span(data-i18n="courses.start") div diff --git a/app/templates/courses/teacher-class-view.jade b/app/templates/courses/teacher-class-view.jade index 3f135c235..7215a08cf 100644 --- a/app/templates/courses/teacher-class-view.jade +++ b/app/templates/courses/teacher-class-view.jade @@ -244,7 +244,7 @@ mixin studentRow(student) div(data-i18n='teacher.remove') mixin enrollStudentButton(student) - a.enroll-student-button.btn.btn-lg.btn-primary(data-classroom-id=view.classroom.id data-user-id=student.id) + a.enroll-student-button.btn.btn-lg.btn-primary(data-classroom-id=view.classroom.id data-user-id=student.id data-event-action="Teachers Class Students Enroll Student") span(data-i18n='teacher.enroll_student') mixin courseProgressTab @@ -294,7 +294,7 @@ mixin courseProgressTab span(data-i18n='TODO') | Assign Course else - .enroll-student-button.btn.btn-md.btn-navy.pull-right(data-user-id=student.id) + .enroll-student-button.btn.btn-md.btn-navy.pull-right(data-user-id=student.id data-event-action="Teachers Class Course Enroll Student") span(data-i18n='TODO') | Enroll Student @@ -426,4 +426,4 @@ mixin enrollmentStatusTab strong(class= status === 'expired' ? 'text-danger' : '')= view.studentStatusString(student) td.enroll-col if status !== 'enrolled' - button.enroll-student-button.btn.btn-navy(data-i18n="teacher.enroll_student", data-user-id=student.id) + button.enroll-student-button.btn.btn-navy(data-i18n="teacher.enroll_student", data-user-id=student.id, data-event-action="Teachers Class Enrollment Enroll Student") diff --git a/app/templates/courses/teacher-classes-view.jade b/app/templates/courses/teacher-classes-view.jade index ae7e134e6..9946d300c 100644 --- a/app/templates/courses/teacher-classes-view.jade +++ b/app/templates/courses/teacher-classes-view.jade @@ -10,15 +10,15 @@ block content p(data-i18n='teacher.teacher_account_required') if me.isAnonymous() .login-button.btn.btn-lg.btn-primary(data-i18n='login.log_in') - a.btn.btn-lg.btn-primary-alt(href="/teachers/signup" data-i18n='teacher.create_teacher_account') + button.btn.btn-lg.btn-primary-alt.create-teacher-btn(data-event-action="Teachers Classes Create Teacher Account", data-i18n='teacher.create_teacher_account') else - a.btn.btn-lg.btn-primary(href="/teachers/update-account" data-i18n="teachers_quote.convert_account_title") + button.btn.btn-lg.btn-primary.update-teacher-btn(data-event-action="Teachers Classes Convert Teacher Account", data-i18n="teachers_quote.convert_account_title") button#logout-button.btn.btn-lg.btn-primary-alt(data-i18n="login.log_out") - + .teacher-account-blurb.text-center.col-xs-6.col-xs-offset-3.m-y-3 h5(data-i18n='teacher.what_is_a_teacher_account') p(data-i18n='teacher.teacher_account_explanation') - + else if !me.isTeacher() .alert.alert-danger.text-center @@ -27,30 +27,30 @@ block content h3 ATTENTION: Please upgrade your account to a Teacher Account. p | We are transitioning to a new improved classroom management system for instructors. - | Please convert your account to ensure you retain access to your classrooms. - a.btn.btn-primary.btn-lg(href="/teachers/update-account") Upgrade to teacher account - + | Please convert your account to ensure you retain access to your classrooms. + button.btn.btn-primary.btn-lg.update-teacher-btn(data-event-action="Teachers Classes Convert Teacher Account Temp") Upgrade to teacher account + .container h3(data-i18n='teacher.current_classes') - + .classes.container // Loop each class each classroom in view.classrooms.models unless classroom.get('archived') +classRow(classroom) - + +createClassButton - + - var archivedClassrooms = view.classrooms.where({archived: true}); if _.size(archivedClassrooms) .container h3(data-i18n='teacher.archived_classes') p(data-i18n='teacher.archived_classes_blurb') - + .classes.container each classroom in archivedClassrooms +archivedClassRow(classroom) - + mixin classRow(classroom) .class.row .col-xs-6 @@ -67,9 +67,9 @@ mixin classRow(classroom) span = classroom.get('members').length .class-links - a.text-h6(data-i18n='teacher.view_class' href=('/teachers/classes/' + classroom.id)) - a.edit-classroom.text-h6(data-i18n='teacher.edit_class_settings' data-classroom-id=classroom.id) - a.archive-classroom.text-h6(data-i18n='teacher.archive_class' data-classroom-id=classroom.id) + a.view-class-btn.text-h6(data-i18n='teacher.view_class' data-classroom-id=classroom.id data-event-action="Teachers Classes View Class Link") + a.edit-classroom.text-h6(data-i18n='teacher.edit_class_settings' data-classroom-id=classroom.id data-event-action="Teachers Classes Edit Class Started") + a.archive-classroom.text-h6(data-i18n='teacher.archive_class' data-classroom-id=classroom.id data-event-action="Teachers Classes Archive Class") .progress-col.col-xs-5 if classroom.get('members').length == 0 @@ -80,7 +80,7 @@ mixin classRow(classroom) if view.courseInstances.findWhere({ classroomID: classroom.id, courseID: course.id }) +progressDot(classroom, course, index) .view-class-arrow.col-xs-1 - a.view-class-arrow-inner.glyphicon.glyphicon-chevron-right(data-classroom-id=classroom.id, href=('/teachers/classes/' + classroom.id)) + a.view-class-arrow-inner.glyphicon.glyphicon-chevron-right.view-class-btn(data-classroom-id=classroom.id data-event-action="Teachers Classes View Class Chevron") mixin addStudentsButton(classroom) @@ -91,7 +91,7 @@ mixin addStudentsButton(classroom) a.add-students-btn.btn.btn-lg.btn-primary(data-classroom-id=classroom.id ) span(data-i18n='teacher.add_students') | Add Students - + mixin createClassButton .create-class .text-center @@ -110,7 +110,7 @@ mixin progressDot(classroom, course, index) - complete = courseInstance.numCompleted - started = courseInstance.started - dotClass = complete === total ? 'forest' : started ? 'gold' : ''; - - var progressDotContext = {total: total, complete: complete}; + - var progressDotContext = {total: total, complete: complete}; .progress-dot(class=dotClass, data-title=view.progressDotTemplate(progressDotContext)) +progressDotLabel(index) diff --git a/app/templates/courses/teacher-courses-view.jade b/app/templates/courses/teacher-courses-view.jade index 25038c973..9707e0170 100644 --- a/app/templates/courses/teacher-courses-view.jade +++ b/app/templates/courses/teacher-courses-view.jade @@ -86,10 +86,10 @@ mixin course-info(course) if view.guideLinks[course.id] //- a.btn.btn-primary(href=view.guideLinks[course.id] class=(me.isTeacher() ? '': 'disabled')) //- span(data-i18n="courses.print_guide") - a.btn.btn-primary(href=view.guideLinks[course.id].python class=(me.isTeacher() ? '': 'disabled')) + a.guide-btn.btn.btn-primary(href=view.guideLinks[course.id].python data-course-id=course.id data-course-name=course.get('name') data-event-action="Classes Guides Guide Python" class=(me.isTeacher() ? '': 'disabled')) span(data-i18n="courses.view_guide_online") | — Python - a.btn.btn-primary(href=view.guideLinks[course.id].javascript class=(me.isTeacher() ? '': 'disabled')) + a.guide-btn.btn.btn-primary(href=view.guideLinks[course.id].javascript data-course-id=course.id data-course-name=course.get('name') data-event-action="Classes Guides Guide JavaScript" class=(me.isTeacher() ? '': 'disabled')) span(data-i18n="courses.view_guide_online") | — JavaScript else diff --git a/app/templates/new-home-view.jade b/app/templates/new-home-view.jade index 59065644a..f4c88d015 100644 --- a/app/templates/new-home-view.jade +++ b/app/templates/new-home-view.jade @@ -5,36 +5,36 @@ mixin box if me.isAnonymous() == true h6#classroom-edition-header(data-i18n="new_home.classroom_edition") div - button.teacher-btn.btn.btn-primary.btn-lg.btn-block(data-i18n="new_home.im_a_teacher") + a.teacher-btn.btn.btn-primary.btn-lg.btn-block(data-event-action="Homepage Click Teacher Button CTA", data-i18n="new_home.im_a_teacher") div - a.btn.btn-forest.btn-lg.btn-block(href="/courses", data-i18n="new_home.im_a_student") + a.student-btn.btn.btn-forest.btn-lg.btn-block(href="/courses", data-event-action="Homepage Click Student Button CTA", data-i18n="new_home.im_a_student") h6#learn-to-code-header(data-i18n="new_home.learn_to_code") - a.btn.btn-gold.btn-lg.btn-block.play-btn(href=view.playURL, data-i18n="new_home.play_now") + a.btn.btn-gold.btn-lg.btn-block.play-btn(href=view.playURL, data-event-action="Homepage Play Now CTA", data-i18n="new_home.play_now") else h6#classroom-edition-header(data-i18n="new_home.logged_in_as") p.small #{me.get("email")} if me.isTeacher() div - button.teacher-btn.btn.btn-forest.btn-lg.btn-block(data-i18n="new_home.goto_classes") + button.teacher-btn.btn.btn-forest.btn-lg.btn-block(data-event-action="Homepage Click My Classes CTA", data-i18n="new_home.goto_classes") div if view.isTeacherWithDemo h6(data-i18n="new_home.check_out_wiki") - a.btn.btn-primary.btn-lg.btn-block(href="https://sites.google.com/a/codecombat.com/teacher-guides/course-guides", data-i18n="nav.educator_wiki") + button.wiki-btn.btn.btn-primary.btn-lg.btn-block(data-event-action="Homepage Click Educator Wiki CTA", data-i18n="nav.educator_wiki") else h6(data-i18n="new_home.want_coco") - a.btn.btn-primary.btn-lg.btn-block(href=view.demoRequestURL, data-i18n="new_home.get_started") + button.btn.btn-primary.btn-lg.request-demo(data-event-action="Homepage Request Demo CTA", data-i18n="new_home.request_demo") else if me.justPlaysCourses() div - a.btn.btn-forest.btn-lg.btn-block(href=view.playURL, data-i18n="courses.continue_playing") + a.btn.btn-forest.btn-lg.btn-block.play-btn(href=view.playURL, data-event-action="Homepage Classroom Continue Playing CTA", data-i18n="courses.continue_playing") div - a.btn.btn-primary.btn-lg.btn-block.play-btn(href=view.playURL, data-i18n="new_home.view_progress") + a.btn.btn-primary.btn-lg.btn-block.play-btn(href=view.playURL, data-event-action="Homepage View Progress CTA", data-i18n="new_home.view_progress") else div - a.btn.btn-forest.btn-lg.btn-block.play-btn(href=view.playURL, data-i18n="courses.continue_playing") + a.btn.btn-forest.btn-lg.btn-block.play-btn(href=view.playURL, data-event-action="Homepage Campaign Continue Playing CTA", data-i18n="courses.continue_playing") div - a.btn.btn-primary.btn-lg.btn-block(href="/user/#{me.getSlugOrID()}", data-i18n="new_home.view_profile") + a.btn.btn-primary.btn-lg.btn-block.profile-btn(href=view.playURL, data-event-action="Homepage View Profile CTA", data-i18n="new_home.view_profile") p.small @@ -213,11 +213,11 @@ block content if view.isTeacherWithDemo h4(data-i18n="new_home.get_started_subtitle") div - a.btn.btn-primary.btn-lg(href="/teachers/classes", data-i18n="new_home.setup_a_class") + button.btn.btn-primary.btn-lg.setup-class-btn(data-event-action="Homepage Setup Class", data-i18n="new_home.setup_a_class") else h4(data-i18n="new_home.request_demo_subtitle") div - a.btn.btn-primary.btn-lg(href=view.demoRequestURL, data-i18n="new_home.request_demo") + button.btn.btn-primary.btn-lg.request-demo(data-event-action="Homepage Request Demo", data-i18n="new_home.request_demo") if me.isAnonymous() .have-an-account span.spr(data-i18n="new_home.have_an_account") @@ -306,10 +306,10 @@ block content h3(data-i18n="new_home.run_class") if view.isTeacherWithDemo div - a.btn.btn-primary.btn-lg(href="/teachers/classes", data-i18n="new_home.setup_a_class") + button.btn.btn-primary.btn-lg.setup-class-btn(data-event-action="Homepage Setup Class Page Bottom", data-i18n="new_home.setup_a_class") else div - a.btn.btn-primary.btn-lg(href=view.demoRequestURL, data-i18n="new_home.request_demo") + button.btn.btn-primary.btn-lg.request-demo(data-event-action="Homepage Request Demo Page Bottom", data-i18n="new_home.request_demo") if me.isAnonymous() .have-an-account span.spr(data-i18n="new_home.have_an_account") diff --git a/app/views/NewHomeView.coffee b/app/views/NewHomeView.coffee index 0651a512b..3ccade336 100644 --- a/app/views/NewHomeView.coffee +++ b/app/views/NewHomeView.coffee @@ -18,14 +18,17 @@ module.exports = class NewHomeView extends RootView events: 'click .play-btn': 'onClickPlayButton' 'change #school-level-dropdown': 'onChangeSchoolLevelDropdown' + 'click .student-btn': 'onClickStudentButton' 'click .teacher-btn': 'onClickTeacherButton' 'click #learn-more-link': 'onClickLearnMoreLink' 'click .screen-thumbnail': 'onClickScreenThumbnail' 'click #carousel-left': 'onLeftPressed' 'click #carousel-right': 'onRightPressed' 'click .request-demo': 'onClickRequestDemo' - 'click .join-class': 'onClickJoinClass' 'click .logout-btn': 'logoutAccount' + 'click .profile-btn': 'onClickViewProfile' + 'click .setup-class-btn': 'onClickSetupClass' + 'click .wiki-btn': 'onClickWikiButton' shortcuts: 'right': 'onRightPressed' @@ -36,7 +39,6 @@ module.exports = class NewHomeView extends RootView @courses = new CocoCollection [], {url: "/db/course", model: Course} @supermodel.loadCollection(@courses, 'courses') - window.tracker?.trackEvent 'Homepage Loaded', category: 'Homepage' if me.isTeacher() @trialRequests = new TrialRequests() @trialRequests.fetchOwn() @@ -51,43 +53,52 @@ module.exports = class NewHomeView extends RootView else if me.justPlaysCourses() # Save players who might be in a classroom from getting into the campaign @playURL = '/courses' - @alternatePlayURL = '/play' - @alternatePlayText = 'home.play_campaign_version' else @playURL = '/play' onLoaded: -> @trialRequest = @trialRequests.first() if @trialRequests?.size() @isTeacherWithDemo = @trialRequest and @trialRequest.get('status') in ['approved', 'submitted'] - @demoRequestURL = if me.isTeacher() then '/teachers/update-account' else '/teachers/demo' super() + onClickLearnMoreLink: -> + window.tracker?.trackEvent 'Homepage Click Learn More', category: 'Homepage', ['Mixpanel'] + @scrollToLink('#classroom-in-box-container') + onClickPlayButton: (e) -> - @playSound 'menu-button-click' - e.preventDefault() - e.stopImmediatePropagation() - window.tracker?.trackEvent 'Homepage Click Play', category: 'Homepage' - application.router.navigate @playURL, trigger: true - #window.open @playURL, '_blank' + window.tracker?.trackEvent $(e.target).data('event-action'), category: 'Homepage', ['Mixpanel'] onClickRequestDemo: (e) -> @playSound 'menu-button-click' e.preventDefault() e.stopImmediatePropagation() - window.tracker?.trackEvent 'Homepage Submit Jumbo Form', category: 'Homepage' - obj = storage.load('request-quote-form') - obj ?= {} - obj.role = @$('#request-form-role').val() - obj.numStudents = @$('#request-form-range').val() - storage.save('request-quote-form', obj) - application.router.navigate "/teachers/demo", trigger: true + window.tracker?.trackEvent $(e.target).data('event-action'), category: 'Homepage', ['Mixpanel'] + if me.isTeacher() + application.router.navigate '/teachers/update-account', trigger: true + else + application.router.navigate '/teachers/demo', trigger: true - onClickJoinClass: (e) -> - @playSound 'menu-button-click' - e.preventDefault() - e.stopImmediatePropagation() - window.tracker?.trackEvent 'Homepage Click Join Class', category: 'Homepage' - application.router.navigate "/courses", trigger: true + onClickSetupClass: (e) -> + window.tracker?.trackEvent $(e.target).data('event-action'), category: 'Homepage', ['Mixpanel'] + application.router.navigate("/teachers/classes", { trigger: true }) + + onClickStudentButton: (e) -> + window.tracker?.trackEvent $(e.target).data('event-action'), category: 'Homepage', ['Mixpanel'] + + onClickTeacherButton: (e) -> + window.tracker?.trackEvent $(e.target).data('event-action'), category: 'Homepage', ['Mixpanel'] + if me.isTeacher() + application.router.navigate('/teachers', { trigger: true }) + else + @scrollToLink('.request-demo-row', 600) + + onClickViewProfile: (e) -> + window.tracker?.trackEvent $(e.target).data('event-action'), category: 'Homepage', ['Mixpanel'] + application.router.navigate("/user/#{me.getSlugOrID()}", { trigger: true }) + + onClickWikiButton: (e) -> + window.tracker?.trackEvent $(e.target).data('event-action'), category: 'Homepage', ['Mixpanel'] + window.location.href = 'https://sites.google.com/a/codecombat.com/teacher-guides/course-guides' afterRender: -> @onChangeSchoolLevelDropdown() @@ -125,18 +136,6 @@ module.exports = class NewHomeView extends RootView isNewPlayer: -> not me.get('stats')?.gamesCompleted and not me.get('heroConfig') - onClickLearnMoreLink: -> - window.tracker?.trackEvent 'Homepage Click Learn More', category: 'Homepage' - @scrollToLink('#classroom-in-box-container') - - onClickTeacherButton: -> - if me.isTeacher() - window.tracker?.trackEvent 'Homepage Click Teacher Button (logged in)', category: 'Homepage' - application.router.navigate('/teachers', { trigger: true }) - else - window.tracker?.trackEvent 'Homepage Click Teacher Button', category: 'Homepage' - @scrollToLink('.request-demo-row', 600) - onRightPressed: (event) -> # Special handling, otherwise after you click the control, keyboard presses move the slide twice return if event.type is 'keydown' and $(document.activeElement).is('.carousel-control') diff --git a/app/views/courses/ClassroomSettingsModal.coffee b/app/views/courses/ClassroomSettingsModal.coffee index d42a20bd6..e228e16af 100644 --- a/app/views/courses/ClassroomSettingsModal.coffee +++ b/app/views/courses/ClassroomSettingsModal.coffee @@ -14,10 +14,6 @@ module.exports = class ClassroomSettingsModal extends ModalView initialize: (options={}) -> @classroom = options.classroom or new Classroom() - if @classroom.isNew() - application.tracker?.trackEvent 'Create new class', category: 'Courses' - else - application.tracker?.trackEvent 'Classroom started edit settings', category: 'Courses', classroomID: @classroom.id afterRender: -> super() @@ -53,3 +49,5 @@ module.exports = class ClassroomSettingsModal extends ModalView button.text(@oldButtonText).attr('disabled', false) errors.showNotyNetworkError(jqxhr) @listenToOnce @classroom, 'sync', @hide + window.tracker?.trackEvent "Teachers Edit Class Saved", category: 'Teachers', classroomID: @classroom.id, ['Mixpanel'] + diff --git a/app/views/courses/ClassroomView.coffee b/app/views/courses/ClassroomView.coffee index 4ba47d3ce..e7f9f1c51 100644 --- a/app/views/courses/ClassroomView.coffee +++ b/app/views/courses/ClassroomView.coffee @@ -57,8 +57,8 @@ module.exports = class ClassroomView extends RootView @levels = new Levels() @levels.fetchForClassroom(classroomID, {data: {project: 'name,slug,original'}}) @levels.on 'add', (model) -> @_byId[model.get('original')] = model # so you can 'get' them - @supermodel.trackCollection(@levels) + window.tracker?.trackEvent 'Students Class Loaded', category: 'Students', classroomID: classroomID, ['Mixpanel'] onCourseInstancesSync: -> @sessions = new CocoCollection([], { model: LevelSession }) diff --git a/app/views/courses/CourseDetailsView.coffee b/app/views/courses/CourseDetailsView.coffee index abb619d2b..61ebbf35c 100644 --- a/app/views/courses/CourseDetailsView.coffee +++ b/app/views/courses/CourseDetailsView.coffee @@ -74,6 +74,10 @@ module.exports = class CourseDetailsView extends RootView )) )) + initialize: (options) -> + window.tracker?.trackEvent 'Students Class Course Loaded', category: 'Students', ['Mixpanel'] + super(options) + buildSessionStats: -> return if @destroyed @@ -125,6 +129,7 @@ module.exports = class CourseDetailsView extends RootView levelSlug = $(e.target).closest('.btn-play-level').data('level-slug') levelID = $(e.target).closest('.btn-play-level').data('level-id') level = @levels.findWhere({original: levelID}) + window.tracker?.trackEvent 'Students Class Course Play Level', category: 'Students', courseID: @courseID, courseInstanceID: @courseInstanceID, levelSlug: levelSlug, ['Mixpanel'] if level.get('type') is 'course-ladder' viewClass = 'views/ladder/LadderView' viewArgs = [{supermodel: @supermodel}, levelSlug] diff --git a/app/views/courses/CoursesView.coffee b/app/views/courses/CoursesView.coffee index 0cf3d2d90..fe70659c4 100644 --- a/app/views/courses/CoursesView.coffee +++ b/app/views/courses/CoursesView.coffee @@ -29,11 +29,14 @@ module.exports = class CoursesView extends RootView 'click .change-hero-btn': 'onClickChangeHeroButton' 'click #join-class-btn': 'onClickJoinClassButton' 'submit #join-class-form': 'onSubmitJoinClassForm' - 'click #change-language-link': 'onClickChangeLanguageLink' + 'click .play-btn': 'onClickPlay' + 'click .view-class-btn': 'onClickViewClass' + 'click .view-levels-btn': 'onClickViewLevels' getTitle: -> return $.i18n.t('teacher.students') initialize: -> + @classCodeQueryVar = utils.getQueryVariable('_cc', false) @courseInstances = new CocoCollection([], { url: "/db/user/#{me.id}/course_instances", model: CourseInstance}) @courseInstances.comparator = (ci) -> return ci.get('classroomID') + ci.get('courseID') @listenToOnce @courseInstances, 'sync', @onCourseInstancesLoaded @@ -55,6 +58,7 @@ module.exports = class CoursesView extends RootView @supermodel.loadModel(@hero, 'hero') @listenTo @hero, 'all', -> @render() + window.tracker?.trackEvent 'Students Loaded', category: 'Students', ['Mixpanel'] onCourseInstancesLoaded: -> map = {} @@ -76,20 +80,22 @@ module.exports = class CoursesView extends RootView onLoaded: -> super() - if utils.getQueryVariable('_cc', false) and not me.isAnonymous() + if @classCodeQueryVar and not me.isAnonymous() + window.tracker?.trackEvent 'Students Join Class Link', category: 'Students', classCode: @classCodeQueryVar, ['Mixpanel'] @joinClass() onClickLogInButton: -> modal = new AuthModal() @openModalView(modal) - application.tracker?.trackEvent 'Started Student Login', category: 'Courses' + window.tracker?.trackEvent 'Students Login Started', category: 'Students', ['Mixpanel'] openSignUpModal: -> + window.tracker?.trackEvent 'Students Signup Started', category: 'Students', ['Mixpanel'] modal = new CreateAccountModal({ initialValues: { classCode: utils.getQueryVariable('_cc', "") } }) @openModalView(modal) - application.tracker?.trackEvent 'Started Student Signup', category: 'Courses' onClickChangeHeroButton: -> + window.tracker?.trackEvent 'Students Change Hero Started', category: 'Students', ['Mixpanel'] modal = new HeroSelectModal({ currentHeroID: @hero.id }) @openModalView(modal) @listenTo modal, 'hero-select:success', (newHero) => @@ -101,16 +107,20 @@ module.exports = class CoursesView extends RootView onSubmitJoinClassForm: (e) -> e.preventDefault() + classCode = @$('#class-code-input').val() or @classCodeQueryVar + window.tracker?.trackEvent 'Students Join Class With Code', category: 'Students', classCode: classCode, ['Mixpanel'] @joinClass() onClickJoinClassButton: (e) -> + classCode = @$('#class-code-input').val() or @classCodeQueryVar + window.tracker?.trackEvent 'Students Join Class With Code', category: 'Students', classCode: classCode, ['Mixpanel'] @joinClass() joinClass: -> return if @state @state = 'enrolling' @errorMessage = null - @classCode = @$('#class-code-input').val() or utils.getQueryVariable('_cc', false) + @classCode = @$('#class-code-input').val() or @classCodeQueryVar if not @classCode @state = null @errorMessage = 'Please enter a code.' @@ -133,7 +143,6 @@ module.exports = class CoursesView extends RootView onJoinClassroomError: (classroom, jqxhr, options) -> @state = null - application.tracker?.trackEvent 'Failed to join classroom with code', category: 'Courses', status: jqxhr.status if jqxhr.status is 422 @errorMessage = 'Please enter a code.' else if jqxhr.status is 404 @@ -161,9 +170,19 @@ module.exports = class CoursesView extends RootView # TODO: Smoother system for joining a classroom and course instances, without requiring page reload, # and showing which class was just joined. document.location.search = '' # Using document.location.reload() causes an infinite loop of reloading - - onClickChangeLanguageLink: -> - application.tracker?.trackEvent 'Student clicked change language', category: 'Courses' - modal = new ChangeCourseLanguageModal() - @openModalView(modal) - modal.once 'hidden', @render, @ + + onClickPlay: (e) -> + levelSlug = $(e.currentTarget).data('level-slug') + window.tracker?.trackEvent $(e.currentTarget).data('event-action'), category: 'Students', levelSlug: levelSlug, ['Mixpanel'] + application.router.navigate($(e.currentTarget).data('href'), { trigger: true }) + + onClickViewClass: (e) -> + classroomID = $(e.target).data('classroom-id') + window.tracker?.trackEvent 'Students View Class', category: 'Students', classroomID: classroomID, ['Mixpanel'] + application.router.navigate("/courses/#{classroomID}", { trigger: true }) + + onClickViewLevels: (e) -> + courseID = $(e.target).data('course-id') + courseInstanceID = $(e.target).data('courseinstance-id') + window.tracker?.trackEvent 'Students View Levels', category: 'Students', courseID: courseID, courseInstanceID: courseInstanceID, ['Mixpanel'] + application.router.navigate("/courses/#{courseID}/#{courseInstanceID}", { trigger: true }) diff --git a/app/views/courses/EnrollmentsView.coffee b/app/views/courses/EnrollmentsView.coffee index db77c870c..bcc3b3596 100644 --- a/app/views/courses/EnrollmentsView.coffee +++ b/app/views/courses/EnrollmentsView.coffee @@ -21,7 +21,7 @@ module.exports = class EnrollmentsView extends RootView getTitle: -> return $.i18n.t('teacher.enrollments') - initialize: -> + initialize: (options) -> @state = new State({ totalEnrolled: 0 totalNotEnrolled: 0 @@ -34,6 +34,8 @@ module.exports = class EnrollmentsView extends RootView 'pending': [] } }) + window.tracker?.trackEvent 'Classes Licenses Loaded', category: 'Teachers', ['Mixpanel'] + super(options) @courses = new Courses() @supermodel.trackRequest @courses.fetch({data: { project: 'free' }}) @@ -58,31 +60,31 @@ module.exports = class EnrollmentsView extends RootView @calculateEnrollmentStats() @state.set('totalCourses', @courses.size()) super() - + updatePrepaidGroups: -> @state.set('prepaidGroups', @prepaids.groupBy((p) -> p.status())) calculateEnrollmentStats: -> @removeDeletedStudents() - + # sort users into enrolled, not enrolled groups = @members.groupBy (m) -> m.isEnrolled() enrolledUsers = new Users(groups.true) @notEnrolledUsers = new Users(groups.false) - map = {} - + map = {} + for classroom in @classrooms.models map[classroom.id] = _.countBy(classroom.get('members'), (userID) -> enrolledUsers.get(userID)?).false - + @state.set({ totalEnrolled: enrolledUsers.size() totalNotEnrolled: @notEnrolledUsers.size() classroomNotEnrolledMap: map }) - + true - + removeDeletedStudents: (e) -> for classroom in @classrooms.models _.remove(classroom.get('members'), (memberID) => @@ -94,8 +96,9 @@ module.exports = class EnrollmentsView extends RootView @openModalView(new HowToEnrollModal()) onClickContactUsButton: -> + window.tracker?.trackEvent 'Classes Licenses Contact Us', category: 'Teachers', enrollmentsNeeded: @state.get('numberOfStudents'), ['Mixpanel'] @openModalView(new TeachersContactModal({ enrollmentsNeeded: @state.get('numberOfStudents') })) - + onInputStudentsInput: -> input = @$('#students-input').val() if input isnt "" and (parseFloat(input) isnt parseInt(input) or _.isNaN parseInt(input)) @@ -106,6 +109,7 @@ module.exports = class EnrollmentsView extends RootView numberOfStudentsIsValid: -> 0 < @get('numberOfStudents') < 100000 onClickEnrollStudentsButton: -> + window.tracker?.trackEvent 'Classes Licenses Enroll Students', category: 'Teachers', ['Mixpanel'] modal = new ActivateLicensesModal({ selectedUsers: @notEnrolledUsers, users: @members }) @openModalView(modal) modal.once 'hidden', => diff --git a/app/views/courses/RestrictedToStudentsView.coffee b/app/views/courses/RestrictedToStudentsView.coffee index d607f538a..276b39d32 100644 --- a/app/views/courses/RestrictedToStudentsView.coffee +++ b/app/views/courses/RestrictedToStudentsView.coffee @@ -2,4 +2,7 @@ RootView = require 'views/core/RootView' module.exports = class RestrictedToStudentsView extends RootView id: 'restricted-to-students-view' - template: require 'templates/courses/restricted-to-students-view' \ No newline at end of file + template: require 'templates/courses/restricted-to-students-view' + + initialize: -> + window.tracker?.trackEvent 'Restricted To Students Loaded', category: 'Students', ['Mixpanel'] diff --git a/app/views/courses/TeacherClassView.coffee b/app/views/courses/TeacherClassView.coffee index 906337f42..f5978af3f 100644 --- a/app/views/courses/TeacherClassView.coffee +++ b/app/views/courses/TeacherClassView.coffee @@ -120,6 +120,7 @@ module.exports = class TeacherClassView extends RootView @supermodel.trackRequest @levels.fetchForClassroom(classroomID, {data: {project: 'original,concepts'}}) @attachMediatorEvents() + window.tracker?.trackEvent 'Teachers Class Loaded', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel'] attachMediatorEvents: () -> # Model/Collection events @@ -211,31 +212,35 @@ module.exports = class TeacherClassView extends RootView window.location.hash = hash onClickCopyCodeButton: -> + window.tracker?.trackEvent 'Teachers Class Copy Class Code', category: 'Teachers', classroomID: @classroom.id, classCode: @state.get('classCode'), ['Mixpanel'] @$('#join-code-input').val(@state.get('classCode')).select() @tryCopy() onClickCopyURLButton: -> + window.tracker?.trackEvent 'Teachers Class Copy Class URL', category: 'Teachers', classroomID: @classroom.id, url: @state.get('joinURL'), ['Mixpanel'] @$('#join-url-input').val(@state.get('joinURL')).select() @tryCopy() tryCopy: -> try document.execCommand('copy') - application.tracker?.trackEvent 'Classroom copy URL', category: 'Courses', classroomID: @classroom.id, url: @state.joinURL catch err message = 'Oops, unable to copy' noty text: message, layout: 'topCenter', type: 'error', killer: false onClickUnarchive: -> + window.tracker?.trackEvent 'Teachers Class Unarchive', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel'] @classroom.save { archived: false } onClickEditClassroom: (e) -> + window.tracker?.trackEvent 'Teachers Class Edit Class Started', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel'] classroom = @classroom modal = new ClassroomSettingsModal({ classroom: classroom }) @openModalView(modal) @listenToOnce modal, 'hide', @render onClickEditStudentLink: (e) -> + window.tracker?.trackEvent 'Teachers Class Students Edit', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel'] user = @students.get($(e.currentTarget).data('student-id')) modal = new EditStudentModal({ user, @classroom }) @openModalView(modal) @@ -252,9 +257,10 @@ module.exports = class TeacherClassView extends RootView onStudentRemoved: (e) -> @students.remove(e.user) - application.tracker?.trackEvent 'Classroom removed student', category: 'Courses', classroomID: @classroom.id, userID: e.user.id + window.tracker?.trackEvent 'Teachers Class Students Removed', category: 'Teachers', classroomID: @classroom.id, userID: e.user.id, ['Mixpanel'] onClickAddStudents: (e) => + window.tracker?.trackEvent 'Teachers Class Add Students', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel'] modal = new InviteToClassroomModal({ classroom: @classroom }) @openModalView(modal) @listenToOnce modal, 'hide', @render @@ -294,14 +300,14 @@ module.exports = class TeacherClassView extends RootView user = @students.get(userID) selectedUsers = new Users([user]) @enrollStudents(selectedUsers) - + window.tracker?.trackEvent $(e.currentTarget).data('event-action'), category: 'Teachers', classroomID: @classroom.id, userID: userID, ['Mixpanel'] + onClickBulkEnroll: -> - courseID = @$('.bulk-course-select').val() - courseInstance = @courseInstances.findWhere({ courseID, classroomID: @classroom.id }) userIDs = @getSelectedStudentIDs().toArray() selectedUsers = new Users(@students.get(userID) for userID in userIDs) @enrollStudents(selectedUsers) - + window.tracker?.trackEvent 'Teachers Class Students Enroll Selected', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel'] + enrollStudents: (selectedUsers) -> modal = new ActivateLicensesModal { @classroom, selectedUsers, users: @students } @openModalView(modal) @@ -311,10 +317,10 @@ module.exports = class TeacherClassView extends RootView if user user.set(newUser.attributes) null - application.tracker?.trackEvent 'Classroom started enroll students', category: 'Courses' onClickExportStudentProgress: -> # TODO: Does not yield .csv download on Safari, and instead opens a new tab with the .csv contents + window.tracker?.trackEvent 'Teachers Class Export CSV', category: 'Teachers', classroomID: @classroom.id, ['Mixpanel'] csvContent = "data:text/csv;charset=utf-8,Username, Email, Playtime, Concepts\n" for student in @students.models concepts = [] @@ -336,15 +342,14 @@ module.exports = class TeacherClassView extends RootView encodedUri = encodeURI(csvContent) window.open(encodedUri) - onClickAssignStudentButton: (e) -> userID = $(e.currentTarget).data('user-id') user = @students.get(userID) members = [userID] courseID = $(e.currentTarget).data('course-id') - @assignCourse courseID, members - + window.tracker?.trackEvent 'Teachers Class Students Assign Selected', category: 'Teachers', classroomID: @classroom.id, courseID: courseID, userID: userID, ['Mixpanel'] + onClickBulkAssign: -> courseID = @$('.bulk-course-select').val() selectedIDs = @getSelectedStudentIDs() @@ -352,16 +357,13 @@ module.exports = class TeacherClassView extends RootView user = @students.get(userID) user.isEnrolled() ).toArray() - assigningToUnenrolled = _.any selectedIDs, (userID) => not @students.get(userID).isEnrolled() - assigningToNobody = selectedIDs.length is 0 - @state.set errors: { assigningToNobody, assigningToUnenrolled } - @assignCourse courseID, members - + window.tracker?.trackEvent 'Teachers Class Students Assign Selected', category: 'Teachers', classroomID: @classroom.id, courseID: courseID, ['Mixpanel'] + # TODO: Move this to the model. Use promises/callbacks? assignCourse: (courseID, members) -> courseInstance = @courseInstances.findWhere({ courseID, classroomID: @classroom.id }) diff --git a/app/views/courses/TeacherClassesView.coffee b/app/views/courses/TeacherClassesView.coffee index 63f196e14..b281a2ce0 100644 --- a/app/views/courses/TeacherClassesView.coffee +++ b/app/views/courses/TeacherClassesView.coffee @@ -17,13 +17,16 @@ helper = require 'lib/coursesHelper' module.exports = class TeacherClassesView extends RootView id: 'teacher-classes-view' template: template - + events: 'click .edit-classroom': 'onClickEditClassroom' 'click .archive-classroom': 'onClickArchiveClassroom' 'click .unarchive-classroom': 'onClickUnarchiveClassroom' 'click .add-students-btn': 'onClickAddStudentsButton' 'click .create-classroom-btn': 'onClickCreateClassroomButton' + 'click .create-teacher-btn': 'onClickCreateTeacherButton' + 'click .update-teacher-btn': 'onClickUpdateTeacherButton' + 'click .view-class-btn': 'onClickViewClassButton' getTitle: -> return $.i18n.t('teacher.my_classes') @@ -38,18 +41,19 @@ module.exports = class TeacherClassesView extends RootView jqxhrs = classroom.sessions.fetchForAllClassroomMembers(classroom) if jqxhrs.length > 0 @supermodel.trackCollection(classroom.sessions) - + window.tracker?.trackEvent 'Teachers Classes Loaded', category: 'Teachers', ['Mixpanel'] + @courses = new Courses() @courses.fetch() @supermodel.trackCollection(@courses) - + @courseInstances = new CourseInstances() @courseInstances.fetchByOwner(me.id) @supermodel.trackCollection(@courseInstances) @progressDotTemplate = require 'templates/teachers/hovers/progress-dot-whole-course' - + # Level Sessions loaded after onLoaded to prevent race condition in calculateDots - + afterRender: -> super() $('.progress-dot').each (i, el) -> @@ -58,52 +62,71 @@ module.exports = class TeacherClassesView extends RootView html: true container: dot }) - + onLoaded: -> helper.calculateDots(@classrooms, @courses, @courseInstances) super() - + onClickEditClassroom: (e) -> classroomID = $(e.target).data('classroom-id') + window.tracker?.trackEvent $(e.target).data('event-action'), category: 'Teachers', classroomID: classroomID, ['Mixpanel'] classroom = @classrooms.get(classroomID) modal = new ClassroomSettingsModal({ classroom: classroom }) @openModalView(modal) @listenToOnce modal, 'hide', @render onClickCreateClassroomButton: (e) -> + window.tracker?.trackEvent 'Teachers Classes Create New Class Started', category: 'Teachers', ['Mixpanel'] classroom = new Classroom({ ownerID: me.id }) modal = new ClassroomSettingsModal({ classroom: classroom }) @openModalView(modal) @listenToOnce modal.classroom, 'sync', -> + window.tracker?.trackEvent 'Teachers Classes Create New Class Finished', category: 'Teachers', ['Mixpanel'] @classrooms.add(modal.classroom) @addFreeCourseInstances() @render() - + + onClickCreateTeacherButton: (e) -> + window.tracker?.trackEvent $(e.target).data('event-action'), category: 'Teachers', ['Mixpanel'] + application.router.navigate("/teachers/signup", { trigger: true }) + + onClickUpdateTeacherButton: (e) -> + window.tracker?.trackEvent $(e.target).data('event-action'), category: 'Teachers', ['Mixpanel'] + application.router.navigate("/teachers/update-account", { trigger: true }) + onClickAddStudentsButton: (e) -> + window.tracker?.trackEvent 'Teachers Classes Add Students Started', category: 'Teachers', ['Mixpanel'] classroomID = $(e.currentTarget).data('classroom-id') classroom = @classrooms.get(classroomID) modal = new InviteToClassroomModal({ classroom: classroom }) @openModalView(modal) @listenToOnce modal, 'hide', @render - + onClickArchiveClassroom: (e) -> classroomID = $(e.currentTarget).data('classroom-id') classroom = @classrooms.get(classroomID) classroom.set('archived', true) classroom.save {}, { success: => + window.tracker?.trackEvent 'Teachers Classes Archived Class', category: 'Teachers', ['Mixpanel'] @render() } - + onClickUnarchiveClassroom: (e) -> classroomID = $(e.currentTarget).data('classroom-id') classroom = @classrooms.get(classroomID) classroom.set('archived', false) classroom.save {}, { success: => + window.tracker?.trackEvent 'Teachers Classes Unarchived Class', category: 'Teachers', ['Mixpanel'] @render() } - + + onClickViewClassButton: (e) -> + classroomID = $(e.target).data('classroom-id') + window.tracker?.trackEvent $(e.target).data('event-action'), category: 'Teachers', classroomID: classroomID, ['Mixpanel'] + application.router.navigate("/teachers/classes/#{classroomID}", { trigger: true }) + addFreeCourseInstances: -> # so that when students join the classroom, they can automatically get free courses # non-free courses are generated when the teacher first adds a student to them diff --git a/app/views/courses/TeacherCoursesView.coffee b/app/views/courses/TeacherCoursesView.coffee index 14ca396fe..018326881 100644 --- a/app/views/courses/TeacherCoursesView.coffee +++ b/app/views/courses/TeacherCoursesView.coffee @@ -1,4 +1,3 @@ -ActivateLicensesModal = require 'views/courses/ActivateLicensesModal' app = require 'core/application' CocoCollection = require 'collections/CocoCollection' CocoModel = require 'models/CocoModel' @@ -6,24 +5,19 @@ Course = require 'models/Course' Campaigns = require 'collections/Campaigns' Classroom = require 'models/Classroom' Classrooms = require 'collections/Classrooms' -InviteToClassroomModal = require 'views/courses/InviteToClassroomModal' User = require 'models/User' CourseInstance = require 'models/CourseInstance' RootView = require 'views/core/RootView' template = require 'templates/courses/teacher-courses-view' -ClassroomSettingsModal = require 'views/courses/ClassroomSettingsModal' module.exports = class TeacherCoursesView extends RootView id: 'teacher-courses-view' template: template events: - 'click #activate-licenses-btn': 'onClickActivateLicensesButton' - 'click .btn-add-students': 'onClickAddStudents' - 'click .create-new-class': 'onClickCreateNewClassButton' - 'click .edit-classroom-small': 'onClickEditClassroomSmall' + 'click .guide-btn': 'onClickGuideButton' 'click .play-level-button': 'onClickPlayLevel' - + guideLinks: { "560f1a9f22961295f9427742": @@ -48,91 +42,25 @@ module.exports = class TeacherCoursesView extends RootView @supermodel.trackCollection(@ownedClassrooms) @courses = new CocoCollection([], { url: "/db/course", model: Course}) @supermodel.loadCollection(@courses, 'courses') - @classrooms = new CocoCollection([], { url: "/db/classroom", model: Classroom }) - @classrooms.comparator = '_id' - @listenToOnce @classrooms, 'sync', @onceClassroomsSync - @supermodel.loadCollection(@classrooms, 'classrooms', {data: {ownerID: me.id}}) @campaigns = new Campaigns() @supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels,levelsUpdated' } }) - @courseInstances = new CocoCollection([], { url: "/db/course_instance", model: CourseInstance }) - @courseInstances.comparator = 'courseID' - @courseInstances.sliceWithMembers = -> return @filter (courseInstance) -> _.size(courseInstance.get('members')) and courseInstance.get('classroomID') - @supermodel.loadCollection(@courseInstances, 'course_instances', {data: {ownerID: me.id}}) - @members = new CocoCollection([], { model: User }) - @listenTo @members, 'sync', @render @ - onceClassroomsSync: -> - for classroom in @classrooms.models - @members.fetch({ - remove: false - url: "/db/classroom/#{classroom.id}/members" - }) + initialize: (options) -> + window.tracker?.trackEvent 'Classes Guides Loaded', category: 'Teachers', ['Mixpanel'] + super(options) - onClickActivateLicensesButton: -> - modal = new ActivateLicensesModal({ - users: @members - }) - @openModalView(modal) - modal.once 'redeem-users', -> document.location.reload() - application.tracker?.trackEvent 'Courses teachers started enroll students', category: 'Courses' + onClickGuideButton: (e) -> + courseID = $(e.currentTarget).data('course-id') + courseName = $(e.currentTarget).data('course-name') + eventAction = $(e.currentTarget).data('event-action') + window.tracker?.trackEvent eventAction, category: 'Teachers', courseID: courseID, courseName: courseName, ['Mixpanel'] - onClickAddStudents: (e) -> - classroomID = $(e.target).data('classroom-id') - classroom = @classrooms.get(classroomID) - unless classroom - console.error 'No classroom ID found.' - return - modal = new InviteToClassroomModal({classroom: classroom}) - @openModalView(modal) - application.tracker?.trackEvent 'Classroom started add students', category: 'Courses', classroomID: classroom.id - - onClickCreateNewClassButton: -> - return application.router.navigate('/teachers/signup', {trigger: true}) if me.get('anonymous') - modal = new ClassroomSettingsModal({}) - @openModalView(modal) - @listenToOnce modal, 'hide', => - # TODO: how to get new classroom from modal? - @classrooms.add(modal.classroom) - # TODO: will this definitely fire after modal saves new classroom? - @listenToOnce modal.classroom, 'sync', -> - @addFreeCourseInstances() - @render() - - onClickEditClassroomSmall: (e) -> - classroomID = $(e.target).data('classroom-id') - classroom = @classrooms.get(classroomID) - modal = new ClassroomSettingsModal({classroom: classroom}) - @openModalView(modal) - @listenToOnce modal, 'hide', @render - onClickPlayLevel: (e) -> form = $(e.currentTarget).closest('.play-level-form') levelSlug = form.find('.level-select').val() courseID = form.data('course-id') language = form.find('.language-select').val() + window.tracker?.trackEvent 'Classes Guides Play Level', category: 'Teachers', courseID: courseID, language: language, levelSlug: levelSlug, ['Mixpanel'] url = "/play/level/#{levelSlug}?course=#{courseID}&codeLanguage=#{language}" application.router.navigate(url, { trigger: true }) - - onLoaded: -> - super() - @addFreeCourseInstances() - - addFreeCourseInstances: -> - # so that when students join the classroom, they can automatically get free courses - # non-free courses are generated when the teacher first adds a student to them - for classroom in @classrooms.models - for course in @courses.models - continue if not course.get('free') - courseInstance = @courseInstances.findWhere({classroomID: classroom.id, courseID: course.id}) - if not courseInstance - courseInstance = new CourseInstance({ - classroomID: classroom.id - courseID: course.id - }) - # TODO: figure out a better way to get around triggering validation errors for properties - # that the server will end up filling in, like an empty members array, ownerID - courseInstance.save(null, {validate: false}) - @courseInstances.add(courseInstance) - @listenToOnce courseInstance, 'sync', @addFreeCourseInstances - return diff --git a/app/views/play/level/ControlBarView.coffee b/app/views/play/level/ControlBarView.coffee index 515a20f2b..32d3b7e2f 100644 --- a/app/views/play/level/ControlBarView.coffee +++ b/app/views/play/level/ControlBarView.coffee @@ -34,7 +34,8 @@ module.exports = class ControlBarView extends CocoView @worldName = options.worldName @session = options.session @level = options.level - @levelID = @level.get('slug') or @level.id + @levelSlug = @level.get('slug') + @levelID = @levelSlug or @level.id @spectateGame = options.spectateGame ? false @observing = options.session.get('creator') isnt me.id super options @@ -121,6 +122,9 @@ module.exports = class ControlBarView extends CocoView @setupManager.open() onClickHome: (e) -> + if @level.get('type', true) in ['course'] + category = if me.isTeacher() then 'Teachers' else 'Students' + window.tracker?.trackEvent 'Play Level Back To Levels', category: category, levelSlug: @levelSlug, ['Mixpanel'] e.preventDefault() e.stopImmediatePropagation() Backbone.Mediator.publish 'router:navigate', route: @homeLink, viewClass: @homeViewClass, viewArgs: @homeViewArgs diff --git a/app/views/play/level/modal/CourseVictoryModal.coffee b/app/views/play/level/modal/CourseVictoryModal.coffee index 6694718f8..47ec3eb84 100644 --- a/app/views/play/level/modal/CourseVictoryModal.coffee +++ b/app/views/play/level/modal/CourseVictoryModal.coffee @@ -56,6 +56,7 @@ module.exports = class CourseVictoryModal extends ModalView @levelSessions = @supermodel.loadCollection(@levelSessions, 'sessions', { data: { project: 'state.complete level.original playtime changed' } }).model + window.tracker?.trackEvent 'Play Level Victory Modal Loaded', category: 'Students', levelSlug: @level.get('slug'), ['Mixpanel'] onResourceLoadFailed: (e) -> if e.resource.jqxhr is @nextLevelRequest @@ -158,6 +159,7 @@ module.exports = class CourseVictoryModal extends ModalView @showView(@views[index+1]) onNextLevel: -> + window.tracker?.trackEvent 'Play Level Victory Modal Next Level', category: 'Students', levelSlug: @level.get('slug'), nextLevelSlug: @nextLevel.get('slug'), ['Mixpanel'] if me.isSessionless() link = "/play/level/#{@nextLevel.get('slug')}?course=#{@courseID}&codeLanguage=#{utils.getQueryVariable('codeLanguage', 'python')}" else @@ -165,6 +167,7 @@ module.exports = class CourseVictoryModal extends ModalView application.router.navigate(link, {trigger: true}) onDone: -> + window.tracker?.trackEvent 'Play Level Victory Modal Done', category: 'Students', levelSlug: @level.get('slug'), ['Mixpanel'] if me.isSessionless() link = "/teachers/courses" else diff --git a/app/views/teachers/ConvertToTeacherAccountView.coffee b/app/views/teachers/ConvertToTeacherAccountView.coffee index 096ac7607..ad3e3b7f5 100644 --- a/app/views/teachers/ConvertToTeacherAccountView.coffee +++ b/app/views/teachers/ConvertToTeacherAccountView.coffee @@ -33,6 +33,7 @@ module.exports = class ConvertToTeacherAccountView extends RootView @trialRequests = new TrialRequests() @trialRequests.fetchOwn() @supermodel.trackCollection(@trialRequests) + window.tracker?.trackEvent 'Teachers Convert Account Loaded', category: 'Teachers', ['Mixpanel'] onLeaveMessage: -> if @formChanged @@ -87,6 +88,8 @@ module.exports = class ConvertToTeacherAccountView extends RootView @onChangeForm() onChangeForm: -> + unless @formChanged + window.tracker?.trackEvent 'Teachers Convert Account Form Started', category: 'Teachers', ['Mixpanel'] @formChanged = true onSubmitForm: (e) -> @@ -141,6 +144,7 @@ module.exports = class ConvertToTeacherAccountView extends RootView errors.showNotyNetworkError(arguments...) onTrialRequestSubmit: -> + window.tracker?.trackEvent 'Teachers Convert Account Submitted', category: 'Teachers', ['Mixpanel'] @formChanged = false me.setRole @trialRequest.get('properties').role.toLowerCase(), true application.router.navigate('/teachers/classes', {trigger: true}) diff --git a/app/views/teachers/CreateTeacherAccountView.coffee b/app/views/teachers/CreateTeacherAccountView.coffee index 59be15e5a..b0107e08c 100644 --- a/app/views/teachers/CreateTeacherAccountView.coffee +++ b/app/views/teachers/CreateTeacherAccountView.coffee @@ -31,6 +31,7 @@ module.exports = class CreateTeacherAccountView extends RootView @trialRequests = new TrialRequests() @trialRequests.fetchOwn() @supermodel.trackCollection(@trialRequests) + window.tracker?.trackEvent 'Teachers Create Account Loaded', category: 'Teachers', ['Mixpanel'] onLeaveMessage: -> if @formChanged @@ -39,8 +40,6 @@ module.exports = class CreateTeacherAccountView extends RootView onLoaded: -> if @trialRequests.size() @trialRequest = @trialRequests.first() - if @trialRequest and @trialRequest.get('status') isnt 'submitted' and @trialRequest.get('status') isnt 'approved' - window.tracker?.trackEvent 'View Trial Request', category: 'Teachers', label: 'View Trial Request', ['Mixpanel'] super() invalidateNCES: -> @@ -90,6 +89,8 @@ module.exports = class CreateTeacherAccountView extends RootView @openModalView(modal) onChangeForm: -> + unless @formChanged + window.tracker?.trackEvent 'Teachers Create Account Form Started', category: 'Teachers', ['Mixpanel'] @formChanged = true onSubmitForm: (e) -> @@ -158,6 +159,7 @@ module.exports = class CreateTeacherAccountView extends RootView @openModalView(modal) onTrialRequestSubmit: -> + window.tracker?.trackEvent 'Teachers Create Account Submitted', category: 'Teachers', ['Mixpanel'] @formChanged = false attrs = _.pick(forms.formToObject(@$('form')), 'name', 'email', 'role') attrs.role = attrs.role.toLowerCase() diff --git a/app/views/teachers/RequestQuoteView.coffee b/app/views/teachers/RequestQuoteView.coffee index bb18ce945..b48a9d355 100644 --- a/app/views/teachers/RequestQuoteView.coffee +++ b/app/views/teachers/RequestQuoteView.coffee @@ -34,6 +34,7 @@ module.exports = class RequestQuoteView extends RootView @trialRequests.fetchOwn() @supermodel.trackCollection(@trialRequests) @formChanged = false + window.tracker?.trackEvent 'Teachers Request Demo Loaded', category: 'Teachers', ['Mixpanel'] onLeaveMessage: -> if @formChanged @@ -42,8 +43,6 @@ module.exports = class RequestQuoteView extends RootView onLoaded: -> if @trialRequests.size() @trialRequest = @trialRequests.first() - if @trialRequest and @trialRequest.get('status') isnt 'submitted' and @trialRequest.get('status') isnt 'approved' - window.tracker?.trackEvent 'View Trial Request', category: 'Teachers', label: 'View Trial Request', ['Mixpanel'] super() invalidateNCES: -> @@ -89,6 +88,8 @@ module.exports = class RequestQuoteView extends RootView @onChangeRequestForm() onChangeRequestForm: -> + unless @formChanged + window.tracker?.trackEvent 'Teachers Request Demo Form Started', category: 'Teachers', ['Mixpanel'] @formChanged = true onSubmitRequestForm: (e) -> @@ -161,6 +162,7 @@ module.exports = class RequestQuoteView extends RootView @openModalView(modal) onTrialRequestSubmit: -> + window.tracker?.trackEvent 'Teachers Request Demo Form Submitted', category: 'Teachers', ['Mixpanel'] @formChanged = false me.setRole @trialRequest.get('properties').role.toLowerCase(), true defaultName = [@trialRequest.get('firstName'), @trialRequest.get('lastName')].join(' ') @@ -168,7 +170,6 @@ module.exports = class RequestQuoteView extends RootView @$('#request-form, #form-submit-success').toggleClass('hide') @scrollToTop(0) $('#flying-focus').css({top: 0, left: 0}) # Hack copied from Router.coffee#187. Ideally we'd swap out the view and have view-swapping logic handle this - window.tracker?.trackEvent 'Submit Trial Request', category: 'Teachers', label: 'Trial Request', ['Mixpanel'] onClickGPlusSignupButton: -> btn = @$('#gplus-signup-btn') @@ -190,6 +191,7 @@ module.exports = class RequestQuoteView extends RootView url: "/db/user?gplusID=#{gplusAttrs.gplusID}&gplusAccessToken=#{application.gplusHandler.token()}" type: 'PUT' success: -> + window.tracker?.trackEvent 'Teachers Request Demo Create Account Google', category: 'Teachers', ['Mixpanel'] application.router.navigate(SIGNUP_REDIRECT) window.location.reload() error: errors.showNotyNetworkError @@ -218,6 +220,7 @@ module.exports = class RequestQuoteView extends RootView url: "/db/user?facebookID=#{facebookAttrs.facebookID}&facebookAccessToken=#{application.facebookHandler.token()}" type: 'PUT' success: -> + window.tracker?.trackEvent 'Teachers Request Demo Create Account Facebook', category: 'Teachers', ['Mixpanel'] application.router.navigate(SIGNUP_REDIRECT) window.location.reload() error: errors.showNotyNetworkError @@ -250,13 +253,12 @@ module.exports = class RequestQuoteView extends RootView }) me.save(null, { success: -> + window.tracker?.trackEvent 'Teachers Request Demo Create Account', category: 'Teachers', ['Mixpanel'] application.router.navigate(SIGNUP_REDIRECT) window.location.reload() error: errors.showNotyNetworkError }) - - requestFormSchemaAnonymous = { type: 'object' required: [ diff --git a/app/views/teachers/RestrictedToTeachersView.coffee b/app/views/teachers/RestrictedToTeachersView.coffee index add4f4796..9b4a6014e 100644 --- a/app/views/teachers/RestrictedToTeachersView.coffee +++ b/app/views/teachers/RestrictedToTeachersView.coffee @@ -2,4 +2,7 @@ RootView = require 'views/core/RootView' module.exports = class RestrictedToTeachersView extends RootView id: 'restricted-to-teachers-view' - template: require 'templates/teachers/restricted-to-teachers-view' \ No newline at end of file + template: require 'templates/teachers/restricted-to-teachers-view' + + initialize: -> + window.tracker?.trackEvent 'Restricted To Teachers Loaded', category: 'Students', ['Mixpanel']