From 1bf3eb54dd706e065d6a62533b1ede4e4b644271 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Fri, 8 Jul 2016 18:24:42 -0700 Subject: [PATCH 1/6] :bug:Fix admin classroom progress export paging --- app/views/admin/MainAdminView.coffee | 47 ++++++++++------------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/app/views/admin/MainAdminView.coffee b/app/views/admin/MainAdminView.coffee index 4a9d96145..b1bd225e7 100644 --- a/app/views/admin/MainAdminView.coffee +++ b/app/views/admin/MainAdminView.coffee @@ -5,6 +5,8 @@ template = require 'templates/admin' AdministerUserModal = require 'views/admin/AdministerUserModal' forms = require 'core/forms' +# TODO: respect classroom versioning + Campaigns = require 'collections/Campaigns' Classroom = require 'models/Classroom' CocoCollection = require 'collections/CocoCollection' @@ -161,38 +163,22 @@ module.exports = class MainAdminView extends RootView $('.classroom-progress-csv').prop('disabled', true) classCode = $('.classroom-progress-class-code').val() + classroom = null + sessions = null + users = null userMap = {} - new Promise((resolve, reject) => - new Classroom().fetchByCode(classCode, { - success: resolve - error: (model, response, options) => reject(response) - }) - ) - .then (classroom) => - new Promise((resolve, reject) => - new Classroom({ _id: classroom.id }).fetch({ - success: resolve - error: (model, response, options) => reject(response) - }) - ) - .then (classroom) => - new Promise((resolve, reject) => - new Users().fetchForClassroom(classroom, { - success: (models, response, options) => - resolve([classroom, models]) if models?.loaded - error: (models, response, options) => reject(response) - }) - ) - .then ([classroom, users]) => + Promise.resolve(new Classroom().fetchByCode(classCode)) + .then (model) => + classroom = new Classroom({ _id: model.data._id }) + Promise.resolve(classroom.fetch()) + .then (model) => + users = new Users() + Promise.resolve($.when(users.fetchForClassroom(classroom)...)) + .then (models) => userMap[user.id] = user for user in users.models - new Promise((resolve, reject) => - new LevelSessions().fetchForAllClassroomMembers(classroom, { - success: (models, response, options) => - resolve(models) if models?.loaded - error: (models, response, options) => reject(response) - }) - ) - .then (sessions) => + sessions = new LevelSessions() + Promise.resolve($.when(sessions.fetchForAllClassroomMembers(classroom)...)) + .then (models) => userLevelPlaytimeMap = {} for session in sessions.models continue unless session.get('state')?.complete @@ -238,3 +224,4 @@ module.exports = class MainAdminView extends RootView .catch (error) -> $('.classroom-progress-csv').prop('disabled', false) console.error error + throw error From f598e4395755d7297d1d15b9f37c3848a47f764f Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Fri, 8 Jul 2016 18:47:06 -0700 Subject: [PATCH 2/6] Admin classroom export respects classroom versioning --- app/views/admin/MainAdminView.coffee | 36 +++++++--------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/app/views/admin/MainAdminView.coffee b/app/views/admin/MainAdminView.coffee index b1bd225e7..97aab328b 100644 --- a/app/views/admin/MainAdminView.coffee +++ b/app/views/admin/MainAdminView.coffee @@ -5,8 +5,6 @@ template = require 'templates/admin' AdministerUserModal = require 'views/admin/AdministerUserModal' forms = require 'core/forms' -# TODO: respect classroom versioning - Campaigns = require 'collections/Campaigns' Classroom = require 'models/Classroom' CocoCollection = require 'collections/CocoCollection' @@ -33,31 +31,10 @@ module.exports = class MainAdminView extends RootView getTitle: -> return $.i18n.t('account_settings.admin') initialize: -> - @campaigns = new Campaigns() - @courses = new CocoCollection([], { url: "/db/course", model: Course}) - if window.amActually @amActually = new User({_id: window.amActually}) @amActually.fetch() @supermodel.trackModel(@amActually) - if me.isAdmin() - @supermodel.trackRequest @campaigns.fetchByType('course', { data: { project: 'levels' } }) - @supermodel.loadCollection(@courses, 'courses') - super() - - onLoaded: -> - campaignCourseIndexMap = {} - for course, index in @courses.models - campaignCourseIndexMap[course.get('campaignID')] = index + 1 - @courseLevels = [] - for campaign in @campaigns.models - continue unless campaignCourseIndexMap[campaign.id] - for levelID, level of campaign.get('levels') - @courseLevels.push({ - levelID - slug: level.slug - courseIndex: campaignCourseIndexMap[campaign.id] - }) super() onClickStopSpyingButton: -> @@ -159,11 +136,10 @@ module.exports = class MainAdminView extends RootView @supermodel.addRequestResource('create_prepaid', options, 0).load() onClickExportProgress: -> - return unless @courseLevels?.length > 0 $('.classroom-progress-csv').prop('disabled', true) - classCode = $('.classroom-progress-class-code').val() classroom = null + courseLevels = [] sessions = null users = null userMap = {} @@ -172,6 +148,12 @@ module.exports = class MainAdminView extends RootView classroom = new Classroom({ _id: model.data._id }) Promise.resolve(classroom.fetch()) .then (model) => + for course, index in classroom.get('courses') + for level in course.levels + courseLevels.push + courseIndex: index + 1 + levelID: level.original + slug: level.slug users = new Users() Promise.resolve($.when(users.fetchForClassroom(classroom)...)) .then (models) => @@ -191,7 +173,7 @@ module.exports = class MainAdminView extends RootView userPlaytimes = [] for userID, user of userMap playtimes = [user.get('name') ? 'Anonymous'] - for level in @courseLevels + for level in courseLevels if userLevelPlaytimeMap[userID]?[level.levelID]? rawSeconds = parseInt(userLevelPlaytimeMap[userID][level.levelID]) hours = Math.floor(rawSeconds / 60 / 60) @@ -208,7 +190,7 @@ module.exports = class MainAdminView extends RootView columnLabels = "Username" currentLevel = 1 lastCourseIndex = 1 - for level in @courseLevels + for level in courseLevels unless level.courseIndex is lastCourseIndex currentLevel = 1 lastCourseIndex = level.courseIndex From 9b68e9140961871a3b50dd82062c612c6157cf21 Mon Sep 17 00:00:00 2001 From: Matt Lott Date: Sun, 10 Jul 2016 17:55:07 -0700 Subject: [PATCH 3/6] :bug:Fix analytics dashboard coursePrepaid use --- app/templates/admin/analytics.jade | 46 ++---- app/views/admin/AnalyticsView.coffee | 84 ++++++----- .../mongodb/insertAnalyticsActiveClasses.js | 140 +++++++++++------- .../mongodb/insertAnalyticsActiveUsers.js | 50 +++++-- 4 files changed, 188 insertions(+), 132 deletions(-) diff --git a/app/templates/admin/analytics.jade b/app/templates/admin/analytics.jade index 51dc0c100..f5f7a1d4e 100644 --- a/app/templates/admin/analytics.jade +++ b/app/templates/admin/analytics.jade @@ -18,7 +18,7 @@ block content .col-md-5.big-stat.classroom-active-users div.description Classroom Monthly Active Users if view.activeUsers.length > 0 - - var classroomBigMAU = 0; + - var classroomBigMAU = 0; each count, event in view.activeUsers[0].events if event.indexOf('MAU classroom') >= 0 - classroomBigMAU += count; @@ -26,24 +26,24 @@ block content .col-md-5.big-stat.campaign-active-users div.description Campaign Monthly Active Users if view.activeUsers.length > 0 - - var campaignBigMAU = 0; + - var campaignBigMAU = 0; each count, event in view.activeUsers[0].events if event.indexOf('MAU campaign') >= 0 - campaignBigMAU += count; div.count= campaignBigMAU ul.nav.nav-tabs - li.active + li.active a(data-target="#tab_kpis", data-toggle="tab") KPIs - li + li a(data-target="#tab_active_classes", data-toggle="tab") Active Classes - li + li a(data-target="#tab_revenue", data-toggle="tab") Revenue - li + li a(data-target="#tab_classroom", data-toggle="tab") Classroom - li + li a(data-target="#tab_campaign", data-toggle="tab") Campaign - li + li a(data-target="#tab_campaign_vs_classroom", data-toggle="tab") Campaign vs Classroom .tab-content @@ -165,7 +165,7 @@ block content .small Paid teacher: at least one paid student in course instance .small Trial teacher: at least one trial student in course instance, and no paid students .small Free teacher: no paid students, no trial students - .small Paid status takes precedent over furthest course, so teacher furthest course is furthest course of highest paid status student + .small Paid status takes precedent over furthest course, so teacher furthest course is furthest course of highest paid status student if view.courseDistributionsRecent table.table.table-striped.table-condensed tr @@ -267,9 +267,9 @@ block content h1 Active Users if view.activeUsers.length > 0 - var eventNames = []; - each count, event in view.activeUsers[0].events + each event in view.activeUserEventNames if event.indexOf('classroom') >= 0 - - eventNames.push(event) + - eventNames.push(event); - eventNames.sort(function (a, b) {return a.localeCompare(b);}); table.table.table-striped.table-condensed tr @@ -293,7 +293,7 @@ block content each day in view.enrollmentDays tr td= day - if view.dayEnrollmentsMap[day] + if view.dayEnrollmentsMap[day] td= view.dayEnrollmentsMap[day].paidIssued || 0 td= view.dayEnrollmentsMap[day].paidRedeemed || 0 td= view.dayEnrollmentsMap[day].trialIssued || 0 @@ -322,9 +322,9 @@ block content h1 Active Users if view.activeUsers.length > 0 - var eventNames = []; - each count, event in view.activeUsers[0].events + each event in view.activeUserEventNames if event.indexOf('campaign') >= 0 - - eventNames.push(event) + - eventNames.push(event); - eventNames.sort(function (a, b) {return a.localeCompare(b);}); table.table.table-striped.table-condensed tr @@ -346,27 +346,13 @@ block content h1 Active Users if view.activeUsers.length > 0 - - var eventNames = []; - each count, event in view.activeUsers[0].events - - eventNames.push(event) - - eventNames.sort(function (a, b) { - - if (a.indexOf('campaign') == b.indexOf('campaign') || a.indexOf('classroom') == b.indexOf('classroom')) { - - return a.localeCompare(b); - - } - - else if (a.indexOf('campaign') > b.indexOf('campaign')) { - - return 1; - - } - - else { - - return -1; - - } - - }); table.table.table-striped.table-condensed tr th(style='min-width:85px;') Day - each eventName in eventNames + each eventName in view.activeUserEventNames th= eventName each activeUser in view.activeUsers tr td= activeUser.day - each eventName in eventNames + each eventName in view.activeUserEventNames td= activeUser.events[eventName] || 0 diff --git a/app/views/admin/AnalyticsView.coffee b/app/views/admin/AnalyticsView.coffee index f23c0e58a..591a174a5 100644 --- a/app/views/admin/AnalyticsView.coffee +++ b/app/views/admin/AnalyticsView.coffee @@ -73,6 +73,7 @@ module.exports = class AnalyticsView extends RootView # Add campaign/classroom DAU 30-day averages and daily totals campaignDauTotals = [] classroomDauTotals = [] + eventMap = {} for entry in @activeUsers day = entry.day campaignDauTotal = 0 @@ -82,18 +83,31 @@ module.exports = class AnalyticsView extends RootView campaignDauTotal += count else if event.indexOf('DAU classroom') >= 0 classroomDauTotal += count + eventMap[event] = true; entry.events['DAU campaign total'] = campaignDauTotal + eventMap['DAU campaign total'] = true; campaignDauTotals.unshift(campaignDauTotal) campaignDauTotals.pop() while campaignDauTotals.length > 30 if campaignDauTotals.length is 30 entry.events['DAU campaign 30-day average'] = Math.round(_.reduce(campaignDauTotals, (a, b) -> a + b) / 30) + eventMap['DAU campaign 30-day average'] = true; entry.events['DAU classroom total'] = classroomDauTotal + eventMap['DAU classroom total'] = true; classroomDauTotals.unshift(classroomDauTotal) classroomDauTotals.pop() while classroomDauTotals.length > 30 if classroomDauTotals.length is 30 entry.events['DAU classroom 30-day average'] = Math.round(_.reduce(classroomDauTotals, (a, b) -> a + b) / 30) + eventMap['DAU classroom 30-day average'] = true; @activeUsers.sort (a, b) -> b.day.localeCompare(a.day) + @activeUserEventNames = Object.keys(eventMap) + @activeUserEventNames.sort (a, b) -> + if a.indexOf('campaign') is b.indexOf('campaign') or a.indexOf('classroom') is b.indexOf('classroom') + a.localeCompare(b) + else if a.indexOf('campaign') > b.indexOf('campaign') + 1 + else + -1 @updateAllKPIChartData() @updateActiveUsersChartData() @@ -134,13 +148,13 @@ module.exports = class AnalyticsView extends RootView return unless @revenue.length > 0 # Add monthly recurring revenue values - + # For each daily group, add up monthly values walking forward through time, and add to revenue groups monthlyDailyGroupMap = {} dailyGroupIndexMap = {} for group, i in @revenueGroups monthlyDailyGroupMap[group.replace('DRR', 'MRR')] = group - dailyGroupIndexMap[group] = i + dailyGroupIndexMap[group] = i for monthlyGroup, dailyGroup of monthlyDailyGroupMap monthlyValues = [] for i in [@revenue.length-1..0] @@ -183,7 +197,7 @@ module.exports = class AnalyticsView extends RootView @supermodel.addRequestResource({ url: '/db/prepaid/-/courses' method: 'POST' - data: {project: {maxRedeemers: 1, properties: 1, redeemers: 1}} + data: {project: {endDate: 1, maxRedeemers: 1, properties: 1, redeemers: 1}} success: (prepaids) => paidDayMaxMap = {} paidDayRedeemedMap = {} @@ -201,14 +215,13 @@ module.exports = class AnalyticsView extends RootView redeemDay = redeemer.date.substring(0, 10) trialDayRedeemedMap[redeemDay] ?= 0 trialDayRedeemedMap[redeemDay]++ - else + else if not prepaid.endDate? or new Date(prepaid.endDate) > new Date() paidDayMaxMap[day] ?= 0 paidDayMaxMap[day] += prepaid.maxRedeemers for redeemer in prepaid.redeemers redeemDay = redeemer.date.substring(0, 10) paidDayRedeemedMap[redeemDay] ?= 0 paidDayRedeemedMap[redeemDay]++ - @dayEnrollmentsMap = {} @paidCourseTotalEnrollments = [] for day, count of paidDayMaxMap @@ -218,7 +231,7 @@ module.exports = class AnalyticsView extends RootView @paidCourseTotalEnrollments.sort (a, b) -> a.day.localeCompare(b.day) @paidCourseRedeemedEnrollments = [] for day, count of paidDayRedeemedMap - @paidCourseRedeemedEnrollments.push({day: day, count: count}) + @paidCourseRedeemedEnrollments.push({day: day, count: count}) @dayEnrollmentsMap[day] ?= {paidIssued: 0, paidRedeemed: 0, trialIssued: 0, trialRedeemed: 0} @dayEnrollmentsMap[day].paidRedeemed += count @paidCourseRedeemedEnrollments.sort (a, b) -> a.day.localeCompare(b.day) @@ -239,7 +252,7 @@ module.exports = class AnalyticsView extends RootView }, 0).load() @courses = new CocoCollection([], { url: "/db/course", model: Course}) - @courses.comparator = "_id" + @courses.comparator = "_id" @listenToOnce @courses, 'sync', @onCoursesSync @supermodel.loadCollection(@courses) @@ -276,7 +289,7 @@ module.exports = class AnalyticsView extends RootView studentFurthestCourseMap = {} studentPaidStatusMap = {} for courseInstance in data.courseInstances - continue if utils.objectIdToDate(courseInstance._id) < startDate + continue if utils.objectIdToDate(courseInstance._id) < startDate courseID = courseInstance.courseID teacherID = courseInstance.ownerID for studentID in courseInstance.members @@ -306,7 +319,7 @@ module.exports = class AnalyticsView extends RootView # Paid teacher: at least one paid student # Trial teacher: at least one trial student in course instance, and no paid students # Free teacher: no paid students, no trial students - # Teacher furthest course is furthest course of highest paid status student + # Teacher furthest course is furthest course of highest paid status student teacherFurthestCourseMap = {} teacherPaidStatusMap = {} for teacher, students of teacherStudentsMap @@ -368,14 +381,17 @@ module.exports = class AnalyticsView extends RootView # Trim points preceding days if points.length and days.length and points[0].day.localeCompare(days[0]) < 0 - for point, i in points - if point.day.localeCompare(days[0]) >= 0 - points.splice(0, i) - break + if points[points.length - 1].day.localeCompare(days[0]) < 0 + points = [] + else + for point, i in points + if point.day.localeCompare(days[0]) >= 0 + points.splice(0, i) + break # Ensure points for each day for day, i in days - if points.length <= i or points[i].day isnt day + if points.length <= i or points[i]?.day isnt day prevY = if i > 0 then points[i - 1].y else 0.0 points.splice i, 0, day: day @@ -534,7 +550,7 @@ module.exports = class AnalyticsView extends RootView day = entry.day for event, count of entry.events eventDataMap[event] ?= [] - eventDataMap[event].push + eventDataMap[event].push day: entry.day value: count @@ -550,7 +566,7 @@ module.exports = class AnalyticsView extends RootView lines.push points: points description: event - lineColor: @lineColors[colorIndex++ % @lineColors.length] + lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 showYScale: showYScale @@ -577,7 +593,7 @@ module.exports = class AnalyticsView extends RootView day = entry.day for event, count of entry.events eventDataMap[event] ?= [] - eventDataMap[event].push + eventDataMap[event].push day: entry.day value: count @@ -591,7 +607,7 @@ module.exports = class AnalyticsView extends RootView @campaignVsClassroomMonthlyActiveUsersRecentChartLines.push points: points description: event - lineColor: @lineColors[colorIndex++ % @lineColors.length] + lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 showYScale: true @@ -601,7 +617,7 @@ module.exports = class AnalyticsView extends RootView @campaignVsClassroomMonthlyActiveUsersRecentChartLines.push points: points description: event - lineColor: @lineColors[colorIndex++ % @lineColors.length] + lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 showYScale: false @@ -619,7 +635,7 @@ module.exports = class AnalyticsView extends RootView @campaignVsClassroomMonthlyActiveUsersChartLines.push points: points description: event - lineColor: @lineColors[colorIndex++ % @lineColors.length] + lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 showYScale: true @@ -629,7 +645,7 @@ module.exports = class AnalyticsView extends RootView @campaignVsClassroomMonthlyActiveUsersChartLines.push points: points description: event - lineColor: @lineColors[colorIndex++ % @lineColors.length] + lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 showYScale: false @@ -648,16 +664,14 @@ module.exports = class AnalyticsView extends RootView dailyMax = 0 data = [] - total = 0 for entry in @paidCourseTotalEnrollments - total += entry.count data.push day: entry.day - value: total + value: entry.count points = @createLineChartPoints(days, data) @enrollmentsChartLines.push points: points - description: 'Total paid enrollments issued' + description: 'Paid enrollments issued' lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 @@ -666,16 +680,14 @@ module.exports = class AnalyticsView extends RootView dailyMax = _.max([dailyMax, _.max(points, 'y').y]) data = [] - total = 0 for entry in @paidCourseRedeemedEnrollments - total += entry.count data.push day: entry.day - value: total + value: entry.count points = @createLineChartPoints(days, data) @enrollmentsChartLines.push points: points - description: 'Total paid enrollments redeemed' + description: 'Paid enrollments redeemed' lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 @@ -684,16 +696,14 @@ module.exports = class AnalyticsView extends RootView dailyMax = _.max([dailyMax, _.max(points, 'y').y]) data = [] - total = 0 for entry in @trialCourseTotalEnrollments - total += entry.count data.push day: entry.day - value: total - points = @createLineChartPoints(days, data) + value: entry.count + points = @createLineChartPoints(days, data, true) @enrollmentsChartLines.push points: points - description: 'Total trial enrollments issued' + description: 'Trial enrollments issued' lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 @@ -702,16 +712,14 @@ module.exports = class AnalyticsView extends RootView dailyMax = _.max([dailyMax, _.max(points, 'y').y]) data = [] - total = 0 for entry in @trialCourseRedeemedEnrollments - total += entry.count data.push day: entry.day - value: total + value: entry.count points = @createLineChartPoints(days, data) @enrollmentsChartLines.push points: points - description: 'Total trial enrollments redeemed' + description: 'Trial enrollments redeemed' lineColor: @lineColors[colorIndex++ % @lineColors.length] strokeWidth: 1 min: 0 diff --git a/scripts/analytics/mongodb/insertAnalyticsActiveClasses.js b/scripts/analytics/mongodb/insertAnalyticsActiveClasses.js index b26e89af1..9599ffd94 100644 --- a/scripts/analytics/mongodb/insertAnalyticsActiveClasses.js +++ b/scripts/analytics/mongodb/insertAnalyticsActiveClasses.js @@ -6,51 +6,49 @@ // Usage: // mongo
:/