Skip to content

Commit

Permalink
Merge branch 'master' into eu-opt-in-scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
differentmatt committed May 24, 2018
2 parents 956108a + 377fa57 commit fc7f66c
Show file tree
Hide file tree
Showing 39 changed files with 643 additions and 664 deletions.
110 changes: 49 additions & 61 deletions app/core/Tracker.coffee
Expand Up @@ -6,49 +6,63 @@ loadSegmentIo = require('core/services/segment')
api = require('core/api')

debugAnalytics = false
targetInspectJSLevelSlugs = ['cupboards-of-kithgard']

module.exports = class Tracker extends CocoClass
initialized: false
cookies: {required: false, answered: false, consented: false, declined: false}
constructor: ->
super()
if window.tracker
console.error 'Overwrote our Tracker!', window.tracker
window.tracker = @
@supermodel = new SuperModel()
@isProduction = document.location.href.search('codecombat.com') isnt -1
@promptForCookieConsent() # Will call finishInitialization

promptForCookieConsent: ->
return unless $.i18n.lng() # Will initialize once we finish initializing translations
return @finishInitialization() unless me.get('country') and me.inEU()
@cookies.required = true
@cookiePopup?.close()
window.cookieconsent.hasTransition = false
window.cookieconsent.initialise
onPopupOpen: ->
window.tracker.cookiePopup = @
onInitialise: (status) ->
window.tracker.cookiePopup = @
window.tracker.cookies.answered = status in ['allow', 'dismiss', 'deny']
window.tracker.cookies.consented = status in ['allow', 'dismiss']
window.tracker.cookies.declined = status is 'deny'
console.log 'Initial cookie consent status:', status, window.tracker.cookies if debugAnalytics
window.tracker.finishInitialization()
onStatusChange: (status) ->
window.tracker.cookies.answered = status in ['allow', 'dismiss', 'deny']
window.tracker.cookies.consented = status in ['allow', 'dismiss']
window.tracker.cookies.declined = status is 'deny'
console.log 'Cookie consent status change:', status, window.tracker.cookies if debugAnalytics
container: document.getElementById('#page-container')
palette: {popup: {background: "#000"}, button: {background: "#f1d600"}}
hasTransition: false
revokable: true
law: false
location: false
type: 'opt-out'
content:
message: $.i18n.t 'legal.cookies_message'
dismiss: $.i18n.t 'general.accept'
deny: $.i18n.t 'legal.cookies_deny'
link: $.i18n.t 'nav.privacy'
href: '/privacy'

finishInitialization: ->
return if @initialized
@initialized = true
@trackReferrers()
@supermodel = new SuperModel()
@identify() # Needs supermodel to exist first
@updateRole() if me.get('role')
if me.isTeacher(true)
@updateIntercomRegularly()

enableInspectletJS: (levelSlug) ->
# InspectletJS loading is delayed and targeting specific levels for more focused investigations
return @disableInspectletJS() unless levelSlug in targetInspectJSLevelSlugs

scriptLoaded = =>
# Identify and track pageview here, because inspectlet is loaded too late for standard Tracker calls
@identify()
# http://www.inspectlet.com/docs#virtual_pageviews
window.__insp?.push(['virtualPage'])
window.__insp = [['wid', 2102699786]]
insp = document.createElement('script')
insp.type = 'text/javascript'
insp.async = true
insp.id = 'inspsync'
insp.src = (if 'https:' == document.location.protocol then 'https' else 'http') + '://cdn.inspectlet.com/inspectlet.js'
insp.onreadystatechange = -> scriptLoaded() if insp.readyState is 'complete'
insp.onload = scriptLoaded
x = document.getElementsByTagName('script')[0]
@inspectletScriptNode = x.parentNode.insertBefore insp, x

disableInspectletJS: ->
if @inspectletScriptNode
x = document.getElementsByTagName('script')[0]
x.parentNode.removeChild(@inspectletScriptNode)
@inspectletScriptNode = null
delete window.__insp

trackReferrers: ->
elapsed = new Date() - new Date(me.get('dateCreated'))
return unless elapsed < 5 * 60 * 1000
Expand Down Expand Up @@ -80,34 +94,15 @@ module.exports = class Tracker extends CocoClass
@trackEventInternal('Identify', {id: me.id, traits})
return unless @shouldTrackExternalEvents()

# Errorception
# https://errorception.com/docs/meta
_errs?.meta = traits

# Inspectlet
# https://www.inspectlet.com/docs#identifying_users
__insp?.push ['identify', me.id]
__insp?.push ['tagSession', traits]

# Mixpanel
# https://mixpanel.com/help/reference/javascript
# mixpanel?.identify(me.id)
# mixpanel?.register(traits)

if me.isTeacher(true) and @segmentLoaded
traits.createdAt = me.get 'dateCreated' # Intercom, at least, wants this
analytics.identify me.id, traits

trackPageView: (includeIntegrations=[]) ->
includeMixpanel = (name) ->
# mixpanelIncludes = []
# name in mixpanelIncludes or /courses|students|teachers/ig.test(name)
false

name = Backbone.history.getFragment()
url = "/#{name}"

console.log "Would track analytics pageview: #{url} Mixpanel=#{includeMixpanel(name)}" if debugAnalytics
console.log "Would track analytics pageview: #{url}" if debugAnalytics
@trackEventInternal 'Pageview', url: name, href: window.location.href
return unless @shouldTrackExternalEvents()

Expand All @@ -117,9 +112,6 @@ module.exports = class Tracker extends CocoClass
ga?('codeplay.send', 'pageview', url) if features.codePlay
window.snowplow 'trackPageView'

# Mixpanel
# mixpanel?.track('page viewed', 'page name' : name, url : url) if includeMixpanel(name)

if me.isTeacher(true) and @segmentLoaded
options = {}
if includeIntegrations?.length
Expand Down Expand Up @@ -147,14 +139,6 @@ module.exports = class Tracker extends CocoClass
ga? 'send', gaFieldObject
ga? 'codeplay.send', gaFieldObject if features.codePlay

# Inspectlet
# http://www.inspectlet.com/docs#tagging
__insp?.push ['tagSession', action: action, properies: properties]

# Mixpanel
# Only log explicit events for now
# mixpanel?.track(action, properties) if 'Mixpanel' in includeIntegrations

if me.isTeacher(true) and @segmentLoaded
options = {}
if includeIntegrations
Expand Down Expand Up @@ -182,6 +166,10 @@ module.exports = class Tracker extends CocoClass
delete properties.cachedEssentialResources
delete properties.totalEssentialResources

# Remove personally identifiable data
delete properties.name
delete properties.email

# SnowPlow
snowplowAction = event.toLowerCase().replace(/[^a-z0-9]+/ig, '_')
properties.user = me.id
Expand Down Expand Up @@ -263,7 +251,7 @@ module.exports = class Tracker extends CocoClass
@identify(attrs)

shouldBlockAllTracking: ->
return me.isSmokeTestUser() or window.serverSession.amActually or navigator?.doNotTrack or window?.doNotTrack
return me.isSmokeTestUser() or window.serverSession.amActually or navigator?.doNotTrack or window?.doNotTrack or @cookies.declined
# Should we include application.testing in this?

shouldTrackExternalEvents: ->
Expand Down
5 changes: 0 additions & 5 deletions app/core/api/contact.coffee
@@ -1,9 +1,4 @@
fetchJson = require './fetch-json'

module.exports = {
sendAPCSPAccessRequest: ({ name, email, message }, options={}) ->
fetchJson('/contact/send-apcsp-access-request', _.assign({}, options, {
method: 'POST'
json: { name, email, message }
}))
}
36 changes: 10 additions & 26 deletions app/core/application.coffee
Expand Up @@ -49,7 +49,7 @@ Application = {
initialize: ->
# if features.codePlay and me.isAnonymous()
# document.location.href = '//lenovogamestate.com/login/'

Router = require('core/Router')
@isProduction = -> document.location.href.search('https?://localhost') is -1
Vue.config.devtools = not @isProduction()
Expand All @@ -73,30 +73,14 @@ Application = {
@facebookHandler = new FacebookHandler()
@gplusHandler = new GPlusHandler()
@githubHandler = new GitHubHandler()
locale.load(me.get('preferredLanguage', true))
locale.load(me.get('preferredLanguage', true)).then =>
@tracker.promptForCookieConsent()
preferredLanguage = me.get('preferredLanguage') or 'en'
$(document).bind 'keydown', preventBackspace
preload(COMMON_FILES)
moment.relativeTimeThreshold('ss', 1) # do not return 'a few seconds' when calling 'humanize'
moment.relativeTimeThreshold('ss', 1) # do not return 'a few seconds' when calling 'humanize'
CocoModel.pollAchievements()
unless me.get('anonymous')
# TODO: Remove logging later, once this system has proved stable
me.on 'change:earned', (user, newEarned) ->
newEarned ?= {}
oldEarned = user.previous('earned') ? {}
if oldEarned.gems isnt newEarned.gems
console.log 'Gems changed', oldEarned.gems, '->', newEarned.gems
newLevels = _.difference(newEarned.levels, oldEarned.levels)
if newLevels.length
console.log 'Levels added', newLevels
newItems = _.difference(newEarned.items, oldEarned.items)
if newItems.length
console.log 'Items added', newItems
newHeroes = _.difference(newEarned.heroes, oldEarned.heroes)
if newHeroes.length
console.log 'Heroes added', newHeroes
me.on 'change:points', (user, newPoints) ->
console.log 'Points changed', user.previous('points'), '->', newPoints
@checkForNewAchievement()
$.i18n.init {
lng: me.get('preferredLanguage', true)
Expand All @@ -118,30 +102,30 @@ Application = {
onVisible: onIdleChanged false
awayTimeout: 5 * 60 * 1000
@idleTracker.start()

checkForNewAchievement: ->
if me.get('lastAchievementChecked')
startFrom = new Date(me.get('lastAchievementChecked'))
else
startFrom = me.created()

daysSince = moment.duration(new Date() - startFrom).asDays()
if daysSince > 1
me.checkForNewAchievement().then => @checkForNewAchievement()

featureMode: {
useChina: -> api.admin.setFeatureMode('china').then(-> document.location.reload())
useCodePlay: -> api.admin.setFeatureMode('code-play').then(-> document.location.reload())
usePicoCtf: -> api.admin.setFeatureMode('pico-ctf').then(-> document.location.reload())
useBrainPop: -> api.admin.setFeatureMode('brain-pop').then(-> document.location.reload())
clear: -> api.admin.clearFeatureMode().then(-> document.location.reload())
}

loadedStaticPage: window.alreadyLoadedView?

setHocCampaign: (campaignSlug) -> storage.save('hoc-campaign', campaignSlug)
getHocCampaign: -> storage.load('hoc-campaign')

}

module.exports = Application
Expand Down
2 changes: 1 addition & 1 deletion app/core/contact.coffee
Expand Up @@ -34,6 +34,6 @@ module.exports =
sendTeacherGameDevProjectShare: ({teacherEmail, sessionId, codeLanguage, levelName}) ->
jqxhr = $.ajax('/contact/send-teacher-game-dev-project-share', {
method: 'POST'
data: {teacherEmail, sessionId, levelName, codeLanguage: _.string.titleize(codeLanguage)}
data: {teacherEmail, sessionId, levelName, codeLanguage: _.string.titleize(codeLanguage).replace('script', 'Script')}
})
return new Promise(jqxhr.then)
3 changes: 2 additions & 1 deletion app/locale/en.coffee
Expand Up @@ -1130,7 +1130,6 @@
delete_account_tab: "Delete Your Account"
wrong_email: "Wrong Email"
wrong_password: "Wrong Password"
use_gravatar: "Change your profile picture by signing up for Gravatar"
delete_this_account: "Delete this account permanently"
reset_progress_tab: "Reset All Progress"
reset_your_progress: "Clear all your progress and start over"
Expand Down Expand Up @@ -2390,6 +2389,8 @@
canonical: "The English version of this document is the definitive, canonical version. If there are any discrepancies between translations, the English document takes precedence."
third_party_title: "Third Party Services"
third_party_description: "CodeCombat uses the following third party services (among others):"
cookies_message: 'CodeCombat uses a few essential and non-essential cookies.'
cookies_deny: 'Decline non-essential cookies'

ladder_prizes:
title: "Tournament Prizes" # This section was for an old tournament and doesn't need new translations now.
Expand Down
6 changes: 3 additions & 3 deletions app/locale/locale.coffee
Expand Up @@ -75,8 +75,8 @@ Object.defineProperties module.exports,
load:
enumerable: false
value: (langCode) ->
console.log "Loading locale:", langCode
return Promise.resolve() if langCode in ['en', 'en-US']
console.log "Loading locale:", langCode
promises = [
new Promise (accept, reject) ->
require('bundle-loader?lazy&name=[name]!locale/'+langCode)((localeData) -> accept(localeData))
Expand Down Expand Up @@ -143,11 +143,11 @@ Object.defineProperties module.exports,
opts.ns = ns
Vue.util.extend opts, options
i18n.t key, opts

Vue::$dbt = (source, key, options) ->
options ?= {}
utils.i18n(source, key, options.language, options.fallback)

return

Vue.use(VueI18Next)
9 changes: 0 additions & 9 deletions app/models/User.coffee
@@ -1,4 +1,3 @@
GRAVATAR_URL = 'https://www.gravatar.com/'
cache = {}
CocoModel = require './CocoModel'
ThangTypeConstants = require 'lib/ThangTypeConstants'
Expand Down Expand Up @@ -452,14 +451,6 @@ module.exports = class User extends CocoModel
freeOnly: ->
return features.freeOnly and not me.isPremium()

sendParentEmail: (email, options={}) ->
options.data ?= {}
options.data.type = 'subscribe modal parent'
options.data.email = email
options.url = '/db/user/-/send_one_time_email'
options.method = 'POST'
return $.ajax(options)

subscribe: (token, options={}) ->
stripe = _.clone(@get('stripe') ? {})
stripe.planID = 'basic'
Expand Down
2 changes: 1 addition & 1 deletion app/schemas/models/user.coffee
Expand Up @@ -105,7 +105,7 @@ _.extend UserSchema.properties,

oneTimes: c.array {title: 'One-time emails'},
c.object {title: 'One-time email', required: ['type', 'email']},
type: c.shortString() # E.g 'subscribe modal parent'
type: c.shortString() # E.g 'share progress modal parent'
email: c.shortString()
sent: c.date() # Set when sent
unsubscribedFromMarketingEmails: { type: 'boolean' }
Expand Down
15 changes: 8 additions & 7 deletions app/templates/account/account-settings-view.jade
Expand Up @@ -57,13 +57,14 @@ else
// TODO: show better summary states, like active, subscribed, free, and active until.
a(href="/account/subscription", data-i18n="account_settings.manage_subscription")

.panel.panel-default
.panel-heading
.panel-title(data-i18n="account_settings.picture_tab")
#profile-photo-panel-body.panel-body
img.profile-photo(src=me.getPhotoURL(230), draggable="false")
.text-center
a(href="https://gravatar.com/", data-i18n="account_settings.use_gravatar")
// TODO: allow users to pick their avatars from our preset list now that we killed Gravatar and file uploads
//.panel.panel-default
// .panel-heading
// .panel-title(data-i18n="account_settings.picture_tab")
// #profile-photo-panel-body.panel-body
// img.profile-photo(src=me.getPhotoURL(230), draggable="false")
// .text-center
// a(href="https://gravatar.com/", data-i18n="account_settings.use_gravatar")
.panel.panel-default
.panel-heading
Expand Down
2 changes: 2 additions & 0 deletions app/vendor.js
Expand Up @@ -16,6 +16,8 @@ window.TreemaUtils = require('exports-loader?TreemaUtils!bower_components/treema
import 'bower_components/treema/treema.css'
window.moment = require('bower_components/moment/min/moment-with-locales.min.js');
window.$.i18n = window.i18n = require('bower_components/i18next/i18next.js');
require('bower_components/cookieconsent/build/cookieconsent.min.js')// TODO webpack: Try to extract this
import 'bower_components/cookieconsent/build/cookieconsent.min.css'// TODO webpack: Try to extract this
require('vendor/scripts/idle.js').createjs;
window.key = require('../vendor/scripts/keymaster.js');
require('vendor/scripts/jquery.noty.packaged.min.js');
Expand Down
1 change: 1 addition & 0 deletions app/views/AboutView.coffee
Expand Up @@ -41,6 +41,7 @@ module.exports = class AboutView extends RootView
})

afterInsert: ->
super()
# scroll to the current hash, once everything in the browser is set up
f = =>
return if @destroyed
Expand Down
1 change: 1 addition & 0 deletions app/views/core/RootView.coffee
Expand Up @@ -159,6 +159,7 @@ module.exports = class RootView extends CocoView

locale.load(me.get('preferredLanguage', true)).then =>
@onLanguageLoaded()
window.tracker.promptForCookieConsent()

onLanguageLoaded: ->
@render()
Expand Down
2 changes: 1 addition & 1 deletion app/views/play/CampaignView.coffee
Expand Up @@ -373,7 +373,7 @@ module.exports = class CampaignView extends RootView
@openModalView new CodePlayCreateAccountModal()
else if me.get('anonymous') and me.get('lastLevel') is 'shadow-guard' and me.level() < 4 and not features.noAuth
@promptForSignup()
else if me.get('name') and me.get('lastLevel') in ['forgetful-gemsmith', 'signs-and-portents'] and
else if me.get('name') and me.get('lastLevel') in ['forgetful-gemsmith', 'signs-and-portents', 'true-names'] and
me.level() < 5 and not (me.get('ageRange') in ['18-24', '25-34', '35-44', '45-100']) and
not storage.load('sent-parent-email') and not me.isPremium()
@openModalView new ShareProgressModal()
Expand Down

0 comments on commit fc7f66c

Please sign in to comment.