diff --git a/services/web/app/coffee/Features/Compile/ClsiManager.coffee b/services/web/app/coffee/Features/Compile/ClsiManager.coffee index 59ce3b4724..032d806665 100755 --- a/services/web/app/coffee/Features/Compile/ClsiManager.coffee +++ b/services/web/app/coffee/Features/Compile/ClsiManager.coffee @@ -42,6 +42,8 @@ module.exports = ClsiManager = return callback(error) if error? if 200 <= response.statusCode < 300 callback null, body + else if response.statusCode == 413 + callback null, compile:status:"project-too-large" else error = new Error("CLSI returned non-success code: #{response.statusCode}") logger.error err: error, project_id: project_id, "CLSI returned failure code" diff --git a/services/web/app/coffee/Features/Compile/CompileController.coffee b/services/web/app/coffee/Features/Compile/CompileController.coffee index 387c9eb950..edb42f3e1f 100755 --- a/services/web/app/coffee/Features/Compile/CompileController.coffee +++ b/services/web/app/coffee/Features/Compile/CompileController.coffee @@ -13,21 +13,25 @@ module.exports = CompileController = res.setTimeout(5 * 60 * 1000) project_id = req.params.Project_id isAutoCompile = !!req.query?.auto_compile - settingsOverride = req.body?.settingsOverride ? {}; - logger.log "root doc overriden" if settingsOverride.rootDoc_id? AuthenticationController.getLoggedInUserId req, (error, user_id) -> return next(error) if error? - UserGetter.getUser user_id, {"features.compileGroup":1, "features.compileTimeout":1}, (err, user)-> - settingsOverride.timeout = user?.features?.compileTimeout || Settings.defaultFeatures.compileTimeout - settingsOverride.compiler = user?.features?.compileGroup || Settings.defaultFeatures.compileGroup - req.session.compileGroup = settingsOverride.compiler - CompileManager.compile project_id, user_id, { isAutoCompile, settingsOverride }, (error, status, outputFiles) -> - return next(error) if error? - res.contentType("application/json") - res.send 200, JSON.stringify { - status: status - outputFiles: outputFiles - } + options = { + isAutoCompile: isAutoCompile + } + if req.body?.rootDoc_id? + options.rootDoc_id = req.body.rootDoc_id + else if req.body?.settingsOverride?.rootDoc_id? # Can be removed after deploy + options.rootDoc_id = req.body.settingsOverride.rootDoc_id + if req.body?.compiler + options.compiler = req.body.compiler + logger.log {options, project_id}, "got compile request" + CompileManager.compile project_id, user_id, options, (error, status, outputFiles) -> + return next(error) if error? + res.contentType("application/json") + res.send 200, JSON.stringify { + status: status + outputFiles: outputFiles + } downloadPdf: (req, res, next = (error) ->)-> Metrics.inc "pdf-downloads" @@ -40,7 +44,7 @@ module.exports = CompileController = else logger.log project_id: project_id, "download pdf to embed in browser" res.header('Content-Disposition', "filename=#{project.getSafeProjectName()}.pdf") - CompileController.proxyToClsi("/project/#{project_id}/output/output.pdf", req, res, next) + CompileController.proxyToClsi(project_id, "/project/#{project_id}/output/output.pdf", req, res, next) deleteAuxFiles: (req, res, next) -> project_id = req.params.Project_id @@ -55,25 +59,28 @@ module.exports = CompileController = logger.err err:err, project_id:project_id, "something went wrong compile and downloading pdf" res.send 500 url = "/project/#{project_id}/output/output.pdf" - CompileController.proxyToClsi url, req, res, next + CompileController.proxyToClsi project_id, url, req, res, next getFileFromClsi: (req, res, next = (error) ->) -> - CompileController.proxyToClsi("/project/#{req.params.Project_id}/output/#{req.params.file}", req, res, next) + project_id = req.params.Project_id + CompileController.proxyToClsi(project_id, "/project/#{project_id}/output/#{req.params.file}", req, res, next) proxySync: (req, res, next = (error) ->) -> - CompileController.proxyToClsi(req.url, req, res, next) + CompileController.proxyToClsi(req.params.Project_id, req.url, req, res, next) - proxyToClsi: (url, req, res, next = (error) ->) -> - if req.session.compileGroup == "priority" - compilerUrl = Settings.apis.clsi_priority.url - else - compilerUrl = Settings.apis.clsi.url - url = "#{compilerUrl}#{url}" - logger.log url: url, "proxying to CLSI" - oneMinute = 60 * 1000 - proxy = request(url: url, method: req.method, timeout: oneMinute) - proxy.pipe(res) - proxy.on "error", (error) -> - logger.warn err: error, url: url, "CLSI proxy error" + proxyToClsi: (project_id, url, req, res, next = (error) ->) -> + CompileManager.getProjectCompileLimits project_id, (error, limits) -> + return next(error) if error? + if limits.compileGroup == "priority" + compilerUrl = Settings.apis.clsi_priority.url + else + compilerUrl = Settings.apis.clsi.url + url = "#{compilerUrl}#{url}" + logger.log url: url, "proxying to CLSI" + oneMinute = 60 * 1000 + proxy = request(url: url, method: req.method, timeout: oneMinute) + proxy.pipe(res) + proxy.on "error", (error) -> + logger.warn err: error, url: url, "CLSI proxy error" diff --git a/services/web/app/coffee/Features/Compile/CompileManager.coffee b/services/web/app/coffee/Features/Compile/CompileManager.coffee index 870ad14d6c..1835fa0c0c 100755 --- a/services/web/app/coffee/Features/Compile/CompileManager.coffee +++ b/services/web/app/coffee/Features/Compile/CompileManager.coffee @@ -6,19 +6,20 @@ rclient = redis.createClient(Settings.redis.web) DocumentUpdaterHandler = require "../DocumentUpdater/DocumentUpdaterHandler" Project = require("../../models/Project").Project ProjectRootDocManager = require "../Project/ProjectRootDocManager" +UserGetter = require "../User/UserGetter" ClsiManager = require "./ClsiManager" Metrics = require('../../infrastructure/Metrics') logger = require("logger-sharelatex") rateLimiter = require("../../infrastructure/RateLimiter") module.exports = CompileManager = - compile: (project_id, user_id, opt = {}, _callback = (error) ->) -> + compile: (project_id, user_id, options = {}, _callback = (error) ->) -> timer = new Metrics.Timer("editor.compile") callback = (args...) -> timer.done() _callback(args...) - @_checkIfAutoCompileLimitHasBeenHit opt.isAutoCompile, (err, canCompile)-> + @_checkIfAutoCompileLimitHasBeenHit options.isAutoCompile, (err, canCompile)-> if !canCompile return callback null, "autocompile-backoff", [] logger.log project_id: project_id, user_id: user_id, "compiling project" @@ -31,11 +32,24 @@ module.exports = CompileManager = return callback(error) if error? DocumentUpdaterHandler.flushProjectToMongo project_id, (error) -> return callback(error) if error? - ClsiManager.sendRequest project_id, opt.settingsOverride, (error, status, outputFiles) -> + CompileManager.getProjectCompileLimits project_id, (error, limits) -> return callback(error) if error? - logger.log files: outputFiles, "output files" - callback(null, status, outputFiles) + for key, value of limits + options[key] = value + ClsiManager.sendRequest project_id, options, (error, status, outputFiles, output) -> + return callback(error) if error? + logger.log files: outputFiles, "output files" + callback(null, status, outputFiles, output) + getProjectCompileLimits: (project_id, callback = (error, limits) ->) -> + Project.findById project_id, {owner_ref: 1}, (error, project) -> + return callback(error) if error? + UserGetter.getUser project.owner_ref, {"features":1}, (err, owner)-> + return callback(error) if error? + callback null, { + timeout: owner.features?.compileTimeout || Settings.defaultFeatures.compileTimeout + compileGroup: owner.features?.compileGroup || Settings.defaultFeatures.compileGroup + } getLogLines: (project_id, callback)-> Metrics.inc "editor.raw-logs" diff --git a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee index 45d169e27b..732c0c88f4 100644 --- a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee @@ -195,13 +195,13 @@ module.exports = ProjectEntityHandler = tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:result.path.fileSystem, rev:fileRef.rev, project_name:project.name}, (error) -> callback(error, fileRef, folder_id) - mkdirp: (project_or_id, path, callback = (err, newlyCreatedFolders, lastFolderInPath)->)-> + mkdirp: (project_id, path, callback = (err, newlyCreatedFolders, lastFolderInPath)->)-> self = @ folders = path.split('/') folders = _.select folders, (folder)-> return folder.length != 0 - Project.getProject project_or_id, "", (err, project)=> + ProjectGetter.getProjectWithoutDocLines project_id, (err, project)=> if path == '/' logger.log project_id: project._id, "mkdir is only trying to make path of / so sending back root folder" return callback(null, [], project.rootFolder[0]) @@ -214,10 +214,10 @@ module.exports = ProjectEntityHandler = if parentFolder? parentFolder_id = parentFolder._id builtUpPath = "#{builtUpPath}/#{folderName}" - projectLocator.findElementByPath project_or_id, builtUpPath, (err, foundFolder)=> + projectLocator.findElementByPath project_id, builtUpPath, (err, foundFolder)=> if !foundFolder? logger.log path:path, project_id:project._id, folderName:folderName, "making folder from mkdirp" - @addFolder project_or_id, parentFolder_id, folderName, (err, newFolder, parentFolder_id)-> + @addFolder project_id, parentFolder_id, folderName, (err, newFolder, parentFolder_id)-> newFolder.parentFolder_id = parentFolder_id previousFolders.push newFolder callback null, previousFolders diff --git a/services/web/app/coffee/Features/Project/ProjectRootDocManager.coffee b/services/web/app/coffee/Features/Project/ProjectRootDocManager.coffee index ca9dba02c6..da2743d661 100644 --- a/services/web/app/coffee/Features/Project/ProjectRootDocManager.coffee +++ b/services/web/app/coffee/Features/Project/ProjectRootDocManager.coffee @@ -1,18 +1,29 @@ ProjectEntityHandler = require "./ProjectEntityHandler" Path = require "path" +async = require("async") +_ = require("underscore") module.exports = ProjectRootDocManager = setRootDocAutomatically: (project_id, callback = (error) ->) -> + ProjectEntityHandler.getAllDocs project_id, (error, docs) -> return callback(error) if error? - root_doc_id = null - for path, doc of docs - for line in doc.lines || [] - match = line.match /(.*)\\documentclass/ # no lookbehind in js regexp :( - if Path.extname(path).match(/\.R?tex$/) and match and !match[1].match /%/ - root_doc_id = doc._id - if root_doc_id? - ProjectEntityHandler.setRootDoc project_id, root_doc_id, callback - else - callback() + + + root_doc_id = null + jobs = _.map docs, (doc, path)-> + return (cb)-> + rootDocId = null + for line in doc.lines || [] + match = line.match /(.*)\\documentclass/ # no lookbehind in js regexp :( + isRootDoc = Path.extname(path).match(/\.R?tex$/) and match and !match[1].match /%/ + if isRootDoc + rootDocId = doc?._id + cb(rootDocId) + + async.series jobs, (root_doc_id)-> + if root_doc_id? + ProjectEntityHandler.setRootDoc project_id, root_doc_id, callback + else + callback() diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index 6d59fd0584..1abcb75ae4 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -102,6 +102,14 @@ div.full-size.pdf(ng-controller="PdfController") ng-click="hello('compile-timeout')" ) #{translate("start_free_trial")} + .pdf-errors(ng-show="pdf.projectTooLarge") + .alert.alert-danger + strong #{translate("project_too_large")} + span #{translate("project_too_large_please_reduce")} + + + + .pdf-logs(ng-show="(pdf.view == 'logs' || pdf.failure) && !pdf.error && !pdf.timeout && !pdf.uncompiled") diff --git a/services/web/app/views/user/login.jade b/services/web/app/views/user/login.jade index aa200d5db3..4d8848d8a6 100644 --- a/services/web/app/views/user/login.jade +++ b/services/web/app/views/user/login.jade @@ -8,7 +8,7 @@ block content .card .page-header h1 #{translate("log_in")} - form(async-form="login", name="loginForm", action='/login', ng-cloak) + form(async-form="login", name="loginForm", action='/login', method="POST", ng-cloak) input(name='_csrf', type='hidden', value=csrfToken) input(name='redir', type='hidden', value=redir) form-messages(for="loginForm") diff --git a/services/web/app/views/user/passwordReset.jade b/services/web/app/views/user/passwordReset.jade index 4db644d916..6516e2cc94 100644 --- a/services/web/app/views/user/passwordReset.jade +++ b/services/web/app/views/user/passwordReset.jade @@ -13,6 +13,7 @@ block content async-form="password-reset-request", name="passwordResetForm" action="/user/password/reset", + method="POST", ng-cloak ) input(type="hidden", name="_csrf", value=csrfToken) diff --git a/services/web/app/views/user/register.jade b/services/web/app/views/user/register.jade index b94e22b8d4..7f76787631 100644 --- a/services/web/app/views/user/register.jade +++ b/services/web/app/views/user/register.jade @@ -20,7 +20,7 @@ block content .card .page-header h1 #{translate("register")} - form(async-form="register", name="registerForm", action="/register", ng-cloak) + form(async-form="register", name="registerForm", action="/register", method="POST", ng-cloak) input(name='_csrf', type='hidden', value=csrfToken) input(name='redir', type='hidden', value=redir) form-messages(for="registerForm") diff --git a/services/web/app/views/user/setPassword.jade b/services/web/app/views/user/setPassword.jade index ef015d67c0..dcbdad2db1 100644 --- a/services/web/app/views/user/setPassword.jade +++ b/services/web/app/views/user/setPassword.jade @@ -12,6 +12,7 @@ block content async-form="password-reset", name="passwordResetForm", action="/user/password/set", + method="POST", ng-cloak ) input(type="hidden", name="_csrf", value=csrfToken) @@ -41,4 +42,4 @@ block content button.btn.btn-primary( type='submit', ng-disabled="passwordResetForm.$invalid" - ) #{translate("set_new_password")} \ No newline at end of file + ) #{translate("set_new_password")} diff --git a/services/web/app/views/user/settings.jade b/services/web/app/views/user/settings.jade index fe79a32c29..d40f68c59c 100644 --- a/services/web/app/views/user/settings.jade +++ b/services/web/app/views/user/settings.jade @@ -17,7 +17,7 @@ block content .row .col-md-5 h3 #{translate("update_account_info")} - form(async-form="settings", name="settingsForm", action="/user/settings", novalidate) + form(async-form="settings", name="settingsForm", method="POST", action="/user/settings", novalidate) input(type="hidden", name="_csrf", value=csrfToken) .form-group label(for='email') #{translate("email")} @@ -54,7 +54,7 @@ block content .col-md-5.col-md-offset-1 h3 #{translate("change_password")} - form(async-form="changepassword", name="changePasswordForm", action="/user/password/update", novalidate) + form(async-form="changepassword", name="changePasswordForm", action="/user/password/update", method="POST", novalidate) input(type="hidden", name="_csrf", value=csrfToken) .form-group label(for='currentPassword') #{translate("current_password")} diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee index 8b73ce6df9..bdddb2ab20 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee @@ -17,12 +17,18 @@ define [ onChange = (e) => @runCheckOnChange(e) + + onScroll = () => + @closeContextMenu() @editor.on "changeSession", (e) => @runSpellCheckSoon(200) e.oldSession?.getDocument().off "change", onChange e.session.getDocument().on "change", onChange + + e.oldSession?.off "changeScrollTop", onScroll + e.session.on "changeScrollTop", onScroll @$scope.spellingMenu = {left: '0px', top: '0px'} diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellingMenuView.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellingMenuView.coffee deleted file mode 100644 index 3ba9fd1d96..0000000000 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellingMenuView.coffee +++ /dev/null @@ -1,82 +0,0 @@ -define [ - "libs/backbone" - "libs/mustache" -], () -> - SUGGESTIONS_TO_SHOW = 5 - - SpellingMenuView = Backbone.View.extend - templates: - menu: $("#spellingMenuTemplate").html() - entry: $("#spellingMenuEntryTemplate").html() - - events: - "click a#learnWord": -> - @trigger "click:learn", @_currentHighlight - @hide() - - initialize: (options) -> - @ide = options.ide - @ide.editor.getContainerElement().append @render().el - @ide.editor.on "click", () => @hide() - @ide.editor.on "scroll", () => @hide() - @ide.editor.on "update:doc", () => @hide() - @ide.editor.on "change:doc", () => @hide() - - render: () -> - @setElement($(@templates.menu)) - @$el.css "z-index" : 10000 - @$(".dropdown-toggle").dropdown() - @hide() - return @ - - showForHighlight: (highlight) -> - if @_currentHighlight? and highlight != @_currentHighlight - @_close() - - if !@_currentHighlight? - @_currentHighlight = highlight - @_setSuggestions(highlight) - position = @ide.editor.textToEditorCoordinates( - highlight.row - highlight.column + highlight.word.length - ) - @_position(position.x, position.y) - @_show() - - hideIfAppropriate: (cursorPosition) -> - if @_currentHighlight? - if !@_cursorCloseToHighlight(cursorPosition, @_currentHighlight) and !@_isOpen() - @hide() - - hide: () -> - delete @_currentHighlight - @_close() - @$el.hide() - - _setSuggestions: (highlight) -> - @$(".spelling-suggestion").remove() - divider = @$(".divider") - for suggestion in highlight.suggestions.slice(0, SUGGESTIONS_TO_SHOW) - do (suggestion) => - entry = $(Mustache.to_html(@templates.entry, word: suggestion)) - divider.before(entry) - entry.on "click", () => - @trigger "click:suggestion", suggestion, highlight - - _show: () -> @$el.show() - - _isOpen: () -> - @$(".dropdown-menu").is(":visible") - - _close: () -> - if @_isOpen() - @$el.dropdown("toggle") - - _cursorCloseToHighlight: (position, highlight) -> - position.row == highlight.row and - position.column >= highlight.column and - position.column <= highlight.column + highlight.word.length + 1 - - _position: (x,y) -> @$el.css left: x, top: y - - diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index e5eaf6f352..3ab4687e8a 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -15,8 +15,7 @@ define [ if options.isAutoCompile url += "?auto_compile=true" return $http.post url, { - settingsOverride: - rootDoc_id: options.rootDocOverride_id or null + rootDoc_id: options.rootDocOverride_id or null _csrf: window.csrfToken } @@ -32,6 +31,8 @@ define [ $scope.pdf.timedout = true else if response.status == "autocompile-backoff" $scope.pdf.uncompiled = true + else if response.status == "project-too-large" + $scope.pdf.projectTooLarge = true else if response.status == "failure" $scope.pdf.failure = true fetchLogs() diff --git a/services/web/public/js/ace/mode-latex.js b/services/web/public/js/ace/mode-latex.js index 84d5b8048b..886b5c058e 100644 --- a/services/web/public/js/ace/mode-latex.js +++ b/services/web/public/js/ace/mode-latex.js @@ -15,7 +15,7 @@ var LatexHighlightRules = function() { regex : "(\\\\(?:documentclass|usepackage))(?:(\\[)([^\\]]*)(\\]))?({)([^}]*)(})" }, { token : ["keyword","lparen", "variable.parameter", "rparen"], - regex : "(\\\\(?:label|ref|cite(?:[^{]*)))(?:({)([^}]*)(}))?" + regex : "(\\\\(?:label|v?ref|cite(?:[^{]*)))(?:({)([^}]*)(}))?" }, { token : ["storage.type", "lparen", "variable.parameter", "rparen"], regex : "(\\\\(?:begin|end))({)(\\w*)(})" diff --git a/services/web/test/UnitTests/coffee/Compile/CompileControllerTests.coffee b/services/web/test/UnitTests/coffee/Compile/CompileControllerTests.coffee index 8d6925577b..f9fb7134b7 100644 --- a/services/web/test/UnitTests/coffee/Compile/CompileControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Compile/CompileControllerTests.coffee @@ -21,7 +21,7 @@ describe "CompileController", -> clsi: url: "clsi.example.com" clsi_priority: - url: "clsi.example.com" + url: "clsi-priority.example.com" "request": @request = sinon.stub() "../../models/Project": Project: @Project = {} "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } @@ -47,8 +47,7 @@ describe "CompileController", -> Project_id: @project_id @req.session = {} @AuthenticationController.getLoggedInUserId = sinon.stub().callsArgWith(1, null, @user_id = "mock-user-id") - @CompileManager.compile = sinon.stub().callsArgWith(3, null, @status = "success", @outputFiles = ["mock-output-files"]) - @UserGetter.getUser.callsArgWith(2, null, @user) + @CompileManager.compile = sinon.stub().callsArgWith(3, null, @status = "success", @outputFiles = ["mock-output-files"], @output = "mock-output") @CompileController.compile @req, @res, @next it "should look up the user id", -> @@ -58,7 +57,7 @@ describe "CompileController", -> it "should do the compile without the auto compile flag", -> @CompileManager.compile - .calledWith(@project_id, @user_id, { isAutoCompile: false, settingsOverride:{timeout:@user.features.compileTimeout, compiler:@user.features.compileGroup} }) + .calledWith(@project_id, @user_id, { isAutoCompile: false }) .should.equal true it "should set the content-type of the response to application/json", -> @@ -73,16 +72,6 @@ describe "CompileController", -> outputFiles: @outputFiles }) - it "should get the compile timeout from the users features",-> - @UserGetter.getUser.args[0][0].should.equal @user_id - assert.deepEqual @UserGetter.getUser.args[0][1], {"features.compileGroup":1, "features.compileTimeout":1} - - it "should put the compile group on the req", -> - @req.session.compileGroup.should.equal @user.features.compileGroup - - it "should set the timeout", -> - assert @res.timout > 1000 * 60 * 3 - describe "when an auto compile", -> beforeEach -> @req.params = @@ -91,11 +80,12 @@ describe "CompileController", -> auto_compile: "true" @AuthenticationController.getLoggedInUserId = sinon.stub().callsArgWith(1, null, @user_id = "mock-user-id") @CompileManager.compile = sinon.stub().callsArgWith(3, null, @status = "success", @outputFiles = ["mock-output-files"]) - @UserGetter.getUser.callsArgWith(2, null, @user) @CompileController.compile @req, @res, @next it "should do the compile with the auto compile flag", -> - @CompileManager.compile.calledWith(@project_id, @user_id, { isAutoCompile: true, settingsOverride:{timeout:@user.features.compileTimeout, compiler:@user.features.compileGroup} }).should.equal true + @CompileManager.compile + .calledWith(@project_id, @user_id, { isAutoCompile: true }) + .should.equal true describe "downloadPdf", -> beforeEach -> @@ -134,7 +124,7 @@ describe "CompileController", -> it "should proxy the PDF from the CLSI", -> @CompileController.proxyToClsi - .calledWith("/project/#{@project_id}/output/output.pdf", @req, @res, @next) + .calledWith(@project_id, "/project/#{@project_id}/output/output.pdf", @req, @res, @next) .should.equal true describe "proxyToClsi", -> @@ -152,8 +142,8 @@ describe "CompileController", -> describe "user with standard priority", -> beforeEach -> - @UserGetter.getUser.callsArgWith(2, null, @user) - @CompileController.proxyToClsi(@url = "/test", @req, @res, @next) + @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "standard"}) + @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next) it "should open a request to the CLSI", -> @@ -176,9 +166,8 @@ describe "CompileController", -> describe "user with priority compile", -> beforeEach -> - @req.session.compileGroup = "priority" - @UserGetter.getUser.callsArgWith(2, null, @user) - @CompileController.proxyToClsi(@url = "/test", @req, @res, @next) + @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, {compileGroup: "priority"}) + @CompileController.proxyToClsi(@project_id, @url = "/test", @req, @res, @next) it "should proxy to the priorty url if the user has the feature", ()-> @request @@ -225,5 +214,5 @@ describe "CompileController", -> it "should proxy the res to the clsi with correct url", (done)-> @CompileController.compileAndDownloadPdf @req, @res - @CompileController.proxyToClsi.calledWith("/project/#{@project_id}/output/output.pdf", @req, @res).should.equal true + @CompileController.proxyToClsi.calledWith(@project_id, "/project/#{@project_id}/output/output.pdf", @req, @res).should.equal true done() diff --git a/services/web/test/UnitTests/coffee/Compile/CompileManagerTests.coffee b/services/web/test/UnitTests/coffee/Compile/CompileManagerTests.coffee index 7e3a0b2f91..7e183e8050 100644 --- a/services/web/test/UnitTests/coffee/Compile/CompileManagerTests.coffee +++ b/services/web/test/UnitTests/coffee/Compile/CompileManagerTests.coffee @@ -20,6 +20,7 @@ describe "CompileManager", -> "../DocumentUpdater/DocumentUpdaterHandler": @DocumentUpdaterHandler = {} "../Project/ProjectRootDocManager": @ProjectRootDocManager = {} "../../models/Project": Project: @Project = {} + "../User/UserGetter": @UserGetter = {} "./ClsiManager": @ClsiManager = {} "../../infrastructure/RateLimiter": @ratelimiter "../../infrastructure/Metrics": @Metrics = @@ -30,13 +31,18 @@ describe "CompileManager", -> @project_id = "mock-project-id-123" @user_id = "mock-user-id-123" @callback = sinon.stub() + @limits = { + timeout: 42 + } + describe "compile", -> beforeEach -> @CompileManager._checkIfRecentlyCompiled = sinon.stub().callsArgWith(2, null, false) @CompileManager._ensureRootDocumentIsSet = sinon.stub().callsArgWith(1, null) @DocumentUpdaterHandler.flushProjectToMongo = sinon.stub().callsArgWith(1, null) - @ClsiManager.sendRequest = sinon.stub().callsArgWith(2, null, @status = "mock-status") + @CompileManager.getProjectCompileLimits = sinon.stub().callsArgWith(1, null, @limits) + @ClsiManager.sendRequest = sinon.stub().callsArgWith(2, null, @status = "mock-status", @outputFiles = "mock output files", @output = "mock output") describe "succesfully", -> beforeEach -> @@ -58,14 +64,21 @@ describe "CompileManager", -> .calledWith(@project_id) .should.equal true - it "should run the compile with the new compiler API", -> - @ClsiManager.sendRequest + it "should get the project compile limits", -> + @CompileManager.getProjectCompileLimits .calledWith(@project_id) .should.equal true - it "should call the callback", -> + it "should run the compile with the compile limits", -> + @ClsiManager.sendRequest + .calledWith(@project_id, { + timeout: @limits.timeout + }) + .should.equal true + + it "should call the callback with the output", -> @callback - .calledWith(null, @status) + .calledWith(null, @status, @outputFiles, @output) .should.equal true it "should time the compile", -> @@ -93,6 +106,34 @@ describe "CompileManager", -> @CompileManager.compile @project_id, @user_id, {}, (err, status)-> status.should.equal "autocompile-backoff" done() + + describe "getProjectCompileLimits", -> + beforeEach -> + @features = { + compileTimeout: @timeout = 42 + compileGroup: @group = "priority" + } + @Project.findById = sinon.stub().callsArgWith(2, null, @project = { owner_ref: @owner_id = "owner-id-123" }) + @UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user = { features: @features }) + @CompileManager.getProjectCompileLimits @project_id, @callback + + it "should look up the owner of the project", -> + @Project.findById + .calledWith(@project_id, { owner_ref: 1 }) + .should.equal true + + it "should look up the owner's features", -> + @UserGetter.getUser + .calledWith(@project.owner_ref, { features: 1 }) + .should.equal true + + it "should return the limits", -> + @callback + .calledWith(null, { + timeout: @timeout + compileGroup: @group + }) + .should.equal true describe "getLogLines", -> beforeEach -> diff --git a/services/web/test/UnitTests/coffee/Project/ProjectEntityHandlerTests.coffee b/services/web/test/UnitTests/coffee/Project/ProjectEntityHandlerTests.coffee index 29731de247..c25b126e82 100644 --- a/services/web/test/UnitTests/coffee/Project/ProjectEntityHandlerTests.coffee +++ b/services/web/test/UnitTests/coffee/Project/ProjectEntityHandlerTests.coffee @@ -77,6 +77,7 @@ describe 'ProjectEntityHandler', -> @parentFolder_id = "1jnjknjk" @newFolder = {_id:"newFolder_id_here"} @lastFolder = {_id:"123das", folders:[]} + @ProjectGetter.getProjectWithoutDocLines = sinon.stub().callsArgWith(1, null, @project) @projectLocator.findElementByPath = (project_id, path, cb)=> @parentFolder = {_id:"parentFolder_id_here"} lastFolder = path.substring(path.lastIndexOf("/")) diff --git a/services/web/test/UnitTests/coffee/Project/ProjectRootDocManagerTests.coffee b/services/web/test/UnitTests/coffee/Project/ProjectRootDocManagerTests.coffee index fbd12c6ed4..c4b7d17d11 100644 --- a/services/web/test/UnitTests/coffee/Project/ProjectRootDocManagerTests.coffee +++ b/services/web/test/UnitTests/coffee/Project/ProjectRootDocManagerTests.coffee @@ -14,17 +14,24 @@ describe 'ProjectRootDocManager', -> describe "setRootDocAutomatically", -> describe "when there is a suitable root doc", -> - beforeEach -> + beforeEach (done)-> @docs = "/chapter1.tex": _id: "doc-id-1" - lines: ["\\begin{document}", "Hello world", "\\end{document}"] + lines: ["something else","\\begin{document}", "Hello world", "\\end{document}"] "/main.tex": _id: "doc-id-2" - lines: ["\\documentclass{article}", "\\input{chapter1}"] + lines: ["different line","\\documentclass{article}", "\\input{chapter1}"] + "/nested/chapter1a.tex": + _id: "doc-id-3" + lines: ["Hello world"] + "/nested/chapter1b.tex": + _id: "doc-id-4" + lines: ["Hello world"] + @ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @docs) @ProjectEntityHandler.setRootDoc = sinon.stub().callsArgWith(2) - @ProjectRootDocManager.setRootDocAutomatically @project_id, @callback + @ProjectRootDocManager.setRootDocAutomatically @project_id, done it "should check the docs of the project", -> @ProjectEntityHandler.getAllDocs.calledWith(@project_id) @@ -34,9 +41,6 @@ describe 'ProjectRootDocManager', -> @ProjectEntityHandler.setRootDoc.calledWith(@project_id, "doc-id-2") .should.equal true - it "should call the callback", -> - @callback.called.should.equal true - describe "when the root doc is an Rtex file", -> beforeEach -> @docs = @@ -55,7 +59,7 @@ describe 'ProjectRootDocManager', -> .should.equal true describe "when there is no suitable root doc", -> - beforeEach -> + beforeEach (done)-> @docs = "/chapter1.tex": _id: "doc-id-1" @@ -65,11 +69,8 @@ describe 'ProjectRootDocManager', -> lines: ["%Example: \\documentclass{article}"] @ProjectEntityHandler.getAllDocs = sinon.stub().callsArgWith(1, null, @docs) @ProjectEntityHandler.setRootDoc = sinon.stub().callsArgWith(2) - @ProjectRootDocManager.setRootDocAutomatically @project_id, @callback + @ProjectRootDocManager.setRootDocAutomatically @project_id, done it "should not set the root doc to the doc containing a documentclass", -> @ProjectEntityHandler.setRootDoc.called.should.equal false - it "should call the callback", -> - @callback.called.should.equal true -