diff --git a/services/web/Gruntfile.coffee b/services/web/Gruntfile.coffee
index d3fa598617..9fe53b0115 100644
--- a/services/web/Gruntfile.coffee
+++ b/services/web/Gruntfile.coffee
@@ -11,7 +11,8 @@ module.exports = (grunt) ->
grunt.loadNpmTasks 'grunt-bunyan'
grunt.loadNpmTasks 'grunt-sed'
grunt.loadNpmTasks 'grunt-git-rev-parse'
-
+ grunt.loadNpmTasks 'grunt-file-append'
+
config =
execute:
app:
@@ -129,6 +130,14 @@ module.exports = (grunt) ->
options:
prop: 'commit'
+
+ file_append:
+ default_options: files: [ {
+ append: '\n//ide.js is complete - used for automated testing'
+ input: 'public/minjs/ide.js'
+ output: 'public/minjs/ide.js'
+ }]
+
sed:
version:
path: "app/views/sentry.jade"
@@ -272,7 +281,7 @@ module.exports = (grunt) ->
grunt.registerTask 'compile:server', 'Compile the server side coffee script', ['clean:app', 'coffee:app', 'coffee:app_dir', 'compile:modules:server']
grunt.registerTask 'compile:client', 'Compile the client side coffee script', ['coffee:client', 'coffee:sharejs', 'wrap_sharejs', "compile:modules:client", 'compile:modules:inject_clientside_includes']
grunt.registerTask 'compile:css', 'Compile the less files to css', ['less']
- grunt.registerTask 'compile:minify', 'Concat and minify the client side js', ['requirejs']
+ grunt.registerTask 'compile:minify', 'Concat and minify the client side js', ['requirejs', "file_append"]
grunt.registerTask 'compile:unit_tests', 'Compile the unit tests', ['clean:unit_tests', 'coffee:unit_tests']
grunt.registerTask 'compile:smoke_tests', 'Compile the smoke tests', ['coffee:smoke_tests']
grunt.registerTask 'compile:tests', 'Compile all the tests', ['compile:smoke_tests', 'compile:unit_tests']
diff --git a/services/web/app/coffee/Features/Referal/ReferalAllocator.coffee b/services/web/app/coffee/Features/Referal/ReferalAllocator.coffee
index 43349b9b1e..0127e5681e 100644
--- a/services/web/app/coffee/Features/Referal/ReferalAllocator.coffee
+++ b/services/web/app/coffee/Features/Referal/ReferalAllocator.coffee
@@ -32,46 +32,35 @@ module.exports = ReferalAllocator =
assignBonus: (user_id, callback = (error) ->) ->
- SubscriptionLocator.getUsersSubscription user_id, (error, subscription) ->
- return callback(error) if error?
- logger.log
- subscription: subscription,
- user_id: user_id,
- "checking user doesn't have a subsciption before assigning bonus"
- if !subscription? or !subscription.planCode?
- query = _id: user_id
- User.findOne query, (error, user) ->
- return callback(error) if error
- return callback(new Error("user not found")) if !user?
- logger.log
- user_id: user_id,
- refered_user_count: user.refered_user_count,
- "assigning bonus"
- if user.refered_user_count? and user.refered_user_count > 0
- newFeatures = ReferalAllocator._calculateBonuses(user)
- User.update query, { $set: features: newFeatures }, callback
-
- else
- callback()
+ query = _id: user_id
+ User.findOne query, (error, user) ->
+ return callback(error) if error
+ return callback(new Error("user not found")) if !user?
+ logger.log user_id: user_id, refered_user_count: user.refered_user_count, "assigning bonus"
+ if user.refered_user_count? and user.refered_user_count > 0
+ newFeatures = ReferalAllocator._calculateFeatures(user)
+ if _.isEqual newFeatures, user.features
+ return callback()
+ User.update query, { $set: features: newFeatures }, callback
else
callback()
-
- _calculateBonuses : (user)->
- bonusLevel = ReferalAllocator._getBonusLevel(user)
- newFeatures = {}
+ _calculateFeatures : (user)->
+ bonusLevel = ReferalAllocator._getBonusLevel(user)
+ currentFeatures = _.clone(user.features) #need to clone because we exend with underscore later
+ betterBonusFeatures = {}
_.each Settings.bonus_features["#{bonusLevel}"], (bonusLevel, key)->
currentLevel = user?.features?[key]
if _.isBoolean(currentLevel) and currentLevel == false
- newFeatures[key] = bonusLevel
+ betterBonusFeatures[key] = bonusLevel
- if _.isNumber(currentLevel)
+ if _.isNumber(currentLevel)
if currentLevel == -1
return
bonusIsGreaterThanCurrent = currentLevel < bonusLevel
if bonusIsGreaterThanCurrent or bonusLevel == -1
- newFeatures[key] = bonusLevel
-
+ betterBonusFeatures[key] = bonusLevel
+ newFeatures = _.extend(currentFeatures, betterBonusFeatures)
return newFeatures
diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade
index 690325374a..589c33f241 100644
--- a/services/web/app/views/layout.jade
+++ b/services/web/app/views/layout.jade
@@ -6,6 +6,7 @@ html(itemscope, itemtype='http://schema.org/Product')
script(type="text/javascript").
// Stop superfish from loading
window.similarproducts = true
+ style [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {display: none !important; display: none; }
-if (typeof(gaExperiments) != "undefined")
|!{gaExperiments}
diff --git a/services/web/app/views/project/editor.jade b/services/web/app/views/project/editor.jade
index 492c3795e8..2bf5fb9793 100644
--- a/services/web/app/views/project/editor.jade
+++ b/services/web/app/views/project/editor.jade
@@ -13,7 +13,9 @@ block content
h3 #{translate("loading")}...
.progress
.progress-bar(style="width: 20%", ng-style="{'width': state.load_progress + '%'}")
- p.text-center.text-danger(ng-if="state.error").ng-cloak {{ state.error }}
+ p.text-center.text-danger(ng-if="state.error").ng-cloak
+ span(ng-bind-html="state.error")
+
.global-alerts(ng-cloak)
.alert.alert-danger.small(ng-if="connection.forced_disconnect")
@@ -28,8 +30,11 @@ block content
.alert.alert-warning.small(ng-if="connection.reconnecting")
strong #{translate("reconnecting")}...
+ .alert.alert-warning.small(ng-if="connection.inactive_disconnect")
+ strong #{translate("editor_disconected_click_to_reconnect")}
+
.div(ng-controller="SavingNotificationController")
- .alert.alert-warning.small( ng-repeat="(doc_id, state) in docSavingStatus" ng-if="state.unsavedSeconds > 8") #{translate("saving_notification_with_seconds", {docname:"{{ state.doc.name }}", seconds:"{{ state.unsavedSeconds }}"})}
+ .alert.alert-warning.small(ng-repeat="(doc_id, state) in docSavingStatus" ng-if="state.unsavedSeconds > 8") #{translate("saving_notification_with_seconds", {docname:"{{ state.doc.name }}", seconds:"{{ state.unsavedSeconds }}"})}
include ./editor/left-menu
diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade
index 5c0ac374f1..5923c9b888 100644
--- a/services/web/app/views/project/editor/pdf.jade
+++ b/services/web/app/views/project/editor/pdf.jade
@@ -100,7 +100,7 @@ div.full-size.pdf(ng-controller="PdfController")
p
a.btn.btn-info(
href
- ng-click="hello('compile-timeout')"
+ ng-click="startFreeTrial('compile-timeout')"
) #{translate("start_free_trial")}
.pdf-errors(ng-show="pdf.projectTooLarge")
diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee
index 2b367871f0..e6b7d30da2 100644
--- a/services/web/config/settings.defaults.coffee
+++ b/services/web/config/settings.defaults.coffee
@@ -284,7 +284,7 @@ module.exports =
title: "ShareLaTeX Community Edition"
left_footer: [{
- text: "Powered by ShareLaTeX © 2014"
+ text: "Powered by ShareLaTeX © 2015"
}]
right_footer: [{
diff --git a/services/web/package.json b/services/web/package.json
index d1c139500f..5e2544cf7b 100644
--- a/services/web/package.json
+++ b/services/web/package.json
@@ -48,29 +48,30 @@
"session.socket.io": "0.1.4",
"settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0",
"socket.io": "0.9.16",
- "translations-sharelatex": "git+https://github.com/sharelatex/translations-sharelatex.git#v0.2.0",
+ "translations-sharelatex": "git+https://github.com/sharelatex/translations-sharelatex.git#master",
"underscore": "1.6.0",
"underscore.string": "^3.0.2",
"v8-profiler": "^5.2.3",
"xml2js": "0.2.0"
},
"devDependencies": {
+ "bunyan": "0.22.1",
"chai": "",
"chai-spies": "",
- "sandboxed-module": "0.2.0",
- "timekeeper": "",
- "sinon": "",
+ "grunt-available-tasks": "0.4.1",
+ "grunt-bunyan": "0.5.0",
"grunt-concurrent": "0.4.3",
"grunt-contrib-clean": "0.5.0",
"grunt-contrib-coffee": "0.10.0",
"grunt-contrib-less": "0.9.0",
- "grunt-mocha-test": "0.9.0",
- "grunt-available-tasks": "0.4.1",
"grunt-contrib-requirejs": "0.4.1",
"grunt-execute": "0.1.5",
+ "grunt-file-append": "0.0.6",
"grunt-git-rev-parse": "^0.1.4",
+ "grunt-mocha-test": "0.9.0",
"grunt-sed": "^0.1.1",
- "bunyan": "0.22.1",
- "grunt-bunyan": "0.5.0"
+ "sandboxed-module": "0.2.0",
+ "sinon": "",
+ "timekeeper": ""
}
}
diff --git a/services/web/public/coffee/ide/connection/ConnectionManager.coffee b/services/web/public/coffee/ide/connection/ConnectionManager.coffee
index e6c9597f68..b33566d208 100644
--- a/services/web/public/coffee/ide/connection/ConnectionManager.coffee
+++ b/services/web/public/coffee/ide/connection/ConnectionManager.coffee
@@ -1,5 +1,12 @@
define [], () ->
+ ONEHOUR = 1000 * 60 * 60
class ConnectionManager
+
+
+ disconnectAfterMs: ONEHOUR * 24
+
+ lastUserAction : new Date()
+
constructor: (@ide, @$scope) ->
if !io?
console.error "Socket.io javascript not loaded. Please check that the real-time service is running and accessible."
@@ -8,18 +15,29 @@ define [], () ->
$scope.$apply () =>
@$scope.state.error = "Could not connect to websocket server :("
return
-
- @connected = false
+ setInterval(() =>
+ @disconnectIfInactive()
+ , ONEHOUR)
+
+ @userIsLeavingPage = false
+ window.addEventListener 'beforeunload', =>
+ @userIsLeavingPage = true
+
+ @connected = false
+ @userIsInactive = false
+
@$scope.connection =
reconnecting: false
# If we need to force everyone to reload the editor
forced_disconnect: false
+ inactive_disconnect: false
@$scope.tryReconnectNow = () =>
@tryReconnect()
@$scope.$on 'cursor:editor:update', () =>
+ @lastUserAction = new Date()
if !@connected
@tryReconnect()
@@ -29,6 +47,7 @@ define [], () ->
@ide.socket = io.connect null,
reconnect: false
+ 'connect timeout': 30 * 1000
"force new connection": true
@ide.socket.on "connect", () =>
@@ -37,6 +56,7 @@ define [], () ->
@$scope.$apply () =>
@$scope.connection.reconnecting = false
+ @$scope.connection.inactive_disconnect = false
if @$scope.state.loading
@$scope.state.load_progress = 70
@@ -44,6 +64,13 @@ define [], () ->
@joinProject()
, 100)
+ @ide.socket.on "connect_failed", () =>
+ @connected = false
+ $scope.$apply () =>
+ @$scope.state.error = "Unable to connect, please view the connection problems guide to fix the issue."
+
+
+
@ide.socket.on 'disconnect', () =>
@connected = false
@ide.pushEvent("disconnected")
@@ -55,7 +82,7 @@ define [], () ->
ga('send', 'event', 'editor-interaction', 'disconnect')
, 2000)
- if !$scope.connection.forced_disconnect
+ if !$scope.connection.forced_disconnect and !@userIsInactive
@startAutoReconnectCountdown()
@ide.socket.on 'forceDisconnect', (message) =>
@@ -102,6 +129,9 @@ define [], () ->
else
countdown = 3 + Math.floor(Math.random() * 7)
+ if @userIsLeavingPage #user will have pressed refresh or back etc
+ return
+
@$scope.$apply () =>
@$scope.connection.reconnecting = false
@$scope.connection.reconnection_countdown = countdown
@@ -133,3 +163,10 @@ define [], () ->
@ide.socket.socket.reconnect()
setTimeout (=> @startAutoReconnectCountdown() if !@connected), 2000
+ disconnectIfInactive: ()->
+ @userIsInactive = (new Date() - @lastUserAction) > @disconnectAfterMs
+ if @userIsInactive and @connected
+ @disconnect()
+ @$scope.$apply () =>
+ @$scope.connection.inactive_disconnect = true
+
diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
index 1bd92dc111..4a98b949fc 100644
--- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
+++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee
@@ -196,6 +196,11 @@ define [
else
$scope.switchToSideBySideLayout()
+ $scope.startFreeTrial = (source) ->
+ ga?('send', 'event', 'subscription-funnel', 'compile-timeout', source)
+ window.open("/user/subscription/new?planCode=student_free_trial_7_days")
+ $scope.startedFreeTrial = true
+
App.factory "synctex", ["ide", "$http", "$q", (ide, $http, $q) ->
synctex =
syncToPdf: (cursorPosition) ->
diff --git a/services/web/public/stylesheets/app/base.less b/services/web/public/stylesheets/app/base.less
index 3ed79956cf..a78e1a7cfa 100644
--- a/services/web/public/stylesheets/app/base.less
+++ b/services/web/public/stylesheets/app/base.less
@@ -1,7 +1,3 @@
-[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], .ng-cloak, .x-ng-cloak {
- display: none !important;
-}
-
.system-message {
padding: (@line-height-computed / 4) (@line-height-computed / 2);
background-color: @state-warning-bg;
diff --git a/services/web/test/UnitTests/coffee/Referal/ReferalAllocatorTests.coffee b/services/web/test/UnitTests/coffee/Referal/ReferalAllocatorTests.coffee
index 28592f70bf..3fe925f91c 100644
--- a/services/web/test/UnitTests/coffee/Referal/ReferalAllocatorTests.coffee
+++ b/services/web/test/UnitTests/coffee/Referal/ReferalAllocatorTests.coffee
@@ -4,7 +4,7 @@ require('chai').should()
sinon = require('sinon')
modulePath = require('path').join __dirname, '../../../../app/js/Features/Referal/ReferalAllocator.js'
-describe 'Referal allocator', ->
+describe 'Referalallocator', ->
beforeEach ->
@ReferalAllocator = SandboxedModule.require modulePath, requires:
@@ -75,7 +75,6 @@ describe 'Referal allocator', ->
@callback.called.should.equal true
describe "assignBonus", ->
- describe "when user does not have a subscription", ->
beforeEach ->
@refered_user_count = 3
@Settings.bonus_features =
@@ -88,16 +87,10 @@ describe 'Referal allocator', ->
features:{collaborators:1, dropbox:false, versioning:false}
}
- @User.findOne = sinon.stub().callsArgWith 1, null,stubbedUser
+ @User.findOne = sinon.stub().callsArgWith 1, null, stubbedUser
@User.update = sinon.stub().callsArgWith 2, null
- @SubscriptionLocator.getUsersSubscription = sinon.stub().callsArgWith 1, null, null
@ReferalAllocator.assignBonus @user_id, @callback
- it "should get the users subscription", ->
- @SubscriptionLocator.getUsersSubscription
- .calledWith(@user_id)
- .should.equal true
-
it "should get the users number of refered user", ->
@User.findOne
.calledWith(_id: @user_id)
@@ -116,52 +109,26 @@ describe 'Referal allocator', ->
it "should call the callback", ->
@callback.called.should.equal true
-
- describe "when user does not have a recurlySubscription_id", ->
+
+ describe "when there is nothing to assign", ->
+
beforeEach ->
- @refered_user_count = 4
+ @ReferalAllocator._calculateBonuses = sinon.stub().returns({})
+ @stubbedUser =
+ refered_user_count:4
+ features:{collaborators:3, versioning:true, dropbox:false}
@Settings.bonus_features =
- "2":
- collaborators: 2
- dropbox: false
- versioning: false
- "5":
- collaborators: 5
- dropbox: true
- versioning: false
- "3":
- collaborators: 3
- dropbox: false
- versioning: false
- stubbedUser = { refered_user_count: @refered_user_count, features:{collaborators:1, dropbox:false, versioning:false} }
- @User.findOne = sinon.stub().callsArgWith 1, null, stubbedUser
+ "4":
+ collaborators:3
+ versioning:true
+ dropbox:false
+ @User.findOne = sinon.stub().callsArgWith 1, null, @stubbedUser
@User.update = sinon.stub().callsArgWith 2, null
- @SubscriptionLocator.getUsersSubscription = sinon.stub().callsArgWith 1, null, {}
- @ReferalAllocator.assignBonus @user_id, @callback
- it "should get the users subscription", ->
- @SubscriptionLocator.getUsersSubscription
- .calledWith(@user_id)
- .should.equal true
-
- it "should get the users number of refered user", ->
- @User.findOne
- .calledWith(_id: @user_id)
- .should.equal true
-
- it "should update the user to bonus features with the closest level", ->
- @User.update
- .calledWith({
- _id: @user_id
- }, {
- $set:
- features:
- @Settings.bonus_features["3"]
- })
- .should.equal true
-
- it "should call the callback", ->
- @callback.called.should.equal true
+ it "should not call update if there are no bonuses to apply", (done)->
+ @ReferalAllocator.assignBonus @user_id, (err)=>
+ @User.update.called.should.equal false
+ done()
describe "when the user has better features already", ->
@@ -181,7 +148,6 @@ describe 'Referal allocator', ->
@User.findOne = sinon.stub().callsArgWith 1, null, @stubbedUser
@User.update = sinon.stub().callsArgWith 2, null
- @SubscriptionLocator.getUsersSubscription = sinon.stub().callsArgWith 1, null,null
it "should not set in in mongo when the feature is better", (done)->
@ReferalAllocator.assignBonus @user_id, =>
@@ -191,7 +157,7 @@ describe 'Referal allocator', ->
it "should not overright if the user has -1 users", (done)->
@stubbedUser.features.collaborators = -1
@ReferalAllocator.assignBonus @user_id, =>
- @User.update.calledWith({_id: @user_id }, {$set: features:{dropbox:true, versioning:false} }).should.equal true
+ @User.update.calledWith({_id: @user_id }, {$set: features:{dropbox:true, versioning:false, collaborators:-1} }).should.equal true
done()
describe "when the user is not at a bonus level", ->
@@ -204,14 +170,8 @@ describe 'Referal allocator', ->
versioning: false
@User.findOne = sinon.stub().callsArgWith 1, null, { refered_user_count: @refered_user_count }
@User.update = sinon.stub().callsArgWith 2, null
- @SubscriptionLocator.getUsersSubscription = sinon.stub().callsArgWith 1, null, {}
@ReferalAllocator.assignBonus @user_id, @callback
- it "should get the users subscription", ->
- @SubscriptionLocator.getUsersSubscription
- .calledWith(@user_id)
- .should.equal true
-
it "should get the users number of refered user", ->
@User.findOne
.calledWith(_id: @user_id)
@@ -223,29 +183,4 @@ describe 'Referal allocator', ->
it "should call the callback", ->
@callback.called.should.equal true
- describe "when user has a subscription", ->
- beforeEach ->
- @refered_user_count = 3
- @Settings.bonus_features =
- "3":
- collaborators: 3
- dropbox: false
- versioning: false
- @User.findOne = sinon.stub().callsArgWith 1, null, { refered_user_count: @refered_user_count }
- @User.update = sinon.stub().callsArgWith 2, null
- @SubscriptionLocator.getUsersSubscription = sinon.stub().callsArgWith 1, null, { planCode: "collaborator" }
- @ReferalAllocator.assignBonus @user_id, @callback
- it "should get the users subscription", ->
- @SubscriptionLocator.getUsersSubscription
- .calledWith(@user_id)
- .should.equal true
-
- it "should not get the users number of refered user", ->
- @User.findOne.called.should.equal false
-
- it "should not update the user to bonus features", ->
- @User.update.called.should.equal false
-
- it "should call the callback", ->
- @callback.called.should.equal true