From b8e31dfc7158d656487921fce404026d266be76a Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 1 Jun 2016 16:47:55 +0100 Subject: [PATCH 1/9] fix filename of downloaded pdf files --- services/web/app/views/project/editor/left-menu.jade | 2 +- services/web/app/views/project/editor/pdf.jade | 2 +- .../web/public/coffee/ide/pdf/controllers/PdfController.coffee | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/services/web/app/views/project/editor/left-menu.jade b/services/web/app/views/project/editor/left-menu.jade index 52ea62d505..05fa19542f 100644 --- a/services/web/app/views/project/editor/left-menu.jade +++ b/services/web/app/views/project/editor/left-menu.jade @@ -15,7 +15,7 @@ aside#left-menu.full-size( | #{translate("source")} li a( - ng-href="{{pdf.url}}" + ng-href="{{pdf.downloadUrl || pdf.url}}" target="_blank" ng-if="pdf.url" ) diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.jade index df97ff889e..df47be0e0e 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.jade @@ -46,7 +46,7 @@ div.full-size.pdf(ng-controller="PdfController") ) {{ pdf.logEntries.errors.length + pdf.logEntries.warnings.length }} a( - ng-href="{{pdf.url}}" + ng-href="{{pdf.downloadUrl || pdf.url}}" target="_blank" ng-if="pdf.url" tooltip="#{translate('download_pdf')}" diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index 05d99124c7..8da2c55aa7 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -100,6 +100,7 @@ define [ qs_args = ("#{k}=#{v}" for k, v of qs) $scope.pdf.qs = if qs_args.length then "?" + qs_args.join("&") else "" $scope.pdf.url += $scope.pdf.qs + $scope.pdf.downloadUrl = "/Project/#{$scope.project_id}/output/output.pdf" + $scope.pdf.qs fetchLogs(fileByPath['output.log'], fileByPath['output.blg']) From d8f1e8ec93289eabe6f63b39fc17d823851e95da Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Tue, 7 Jun 2016 11:15:56 +0100 Subject: [PATCH 2/9] Add basic `BetaProgram` feature. --- .../BetaProgram/BetaProgramController.coffee | 29 +++++ .../BetaProgram/BetaProgramHandler.coffee | 18 ++++ services/web/app/coffee/router.coffee | 3 + .../web/app/views/beta_program/opt_in.jade | 30 ++++++ .../public/stylesheets/app/beta-program.less | 16 +++ services/web/public/stylesheets/style.less | 1 + .../BetaProgramControllerTests.coffee | 101 ++++++++++++++++++ .../BetaProgramHandlerTests.coffee | 65 +++++++++++ 8 files changed, 263 insertions(+) create mode 100644 services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee create mode 100644 services/web/app/coffee/Features/BetaProgram/BetaProgramHandler.coffee create mode 100644 services/web/app/views/beta_program/opt_in.jade create mode 100644 services/web/public/stylesheets/app/beta-program.less create mode 100644 services/web/test/UnitTests/coffee/BetaProgram/BetaProgramControllerTests.coffee create mode 100644 services/web/test/UnitTests/coffee/BetaProgram/BetaProgramHandlerTests.coffee diff --git a/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee b/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee new file mode 100644 index 0000000000..5224d8f131 --- /dev/null +++ b/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee @@ -0,0 +1,29 @@ +BetaProgramHandler = require './BetaProgramHandler' +UserLocator = require "../User/UserLocator" +Settings = require "settings-sharelatex" +logger = require 'logger-sharelatex' + + +module.exports = BetaProgramController = + + optIn: (req, res, next) -> + user_id = req?.session?.user?._id + logger.log {user_id}, "user opting in to beta program" + if !user_id + return next(new Error("no user id in session")) + BetaProgramHandler.optIn user_id, (err) -> + if err + return next(err) + return res.redirect "/" + + optInPage: (req, res, next)-> + user_id = req.session?.user?._id + logger.log {user_id}, "showing beta opt-in page for user" + UserLocator.findById user_id, (err, user)-> + if err + logger.err {err, user_id}, "error fetching user" + return next(err) + res.render 'beta_program/opt_in', + title:'beta_program_opt_in' + user: user, + languages: Settings.languages, diff --git a/services/web/app/coffee/Features/BetaProgram/BetaProgramHandler.coffee b/services/web/app/coffee/Features/BetaProgram/BetaProgramHandler.coffee new file mode 100644 index 0000000000..2851ff3fe8 --- /dev/null +++ b/services/web/app/coffee/Features/BetaProgram/BetaProgramHandler.coffee @@ -0,0 +1,18 @@ +User = require("../../models/User").User +logger = require 'logger-sharelatex' +metrics = require("../../infrastructure/Metrics") + +module.exports = BetaProgramHandler = + + optIn: (user_id, callback=(err)->) -> + User.findById user_id, (err, user) -> + if err + logger.err {err, user_id}, "problem adding user to beta" + return callback(err) + metrics.inc "beta-program.opt-in" + user.betaProgram = true + user.save (err) -> + if err + logger.err {err, user_id}, "problem adding user to beta" + return callback(err) + return callback(null) diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 80c52c5a24..50cce6b92c 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -197,6 +197,9 @@ module.exports = class Router webRouter.post "/project/:Project_id/references/index", AuthorizationMiddlewear.ensureUserCanReadProject, ReferencesController.index webRouter.post "/project/:Project_id/references/indexAll", AuthorizationMiddlewear.ensureUserCanReadProject, ReferencesController.indexAll + webRouter.get "/beta/opt-in", AuthenticationController.requireLogin(), BetaProgramController.optInPage + webRouter.post "/beta/opt-in", AuthenticationController.requireLogin(), BetaProgramController.optIn + #Admin Stuff webRouter.get '/admin', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.index webRouter.get '/admin/user', AuthorizationMiddlewear.ensureUserIsSiteAdmin, (req, res)-> res.redirect("/admin/register") #this gets removed by admin-panel addon diff --git a/services/web/app/views/beta_program/opt_in.jade b/services/web/app/views/beta_program/opt_in.jade new file mode 100644 index 0000000000..53e5e770f3 --- /dev/null +++ b/services/web/app/views/beta_program/opt_in.jade @@ -0,0 +1,30 @@ +extends ../layout + +block content + .content.content-alt + .container.beta-opt-in-wrapper + .row + .col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2 + .card + .page-header.text-centered + h1 + | #{translate("sharelatex_beta_program")} + .beta-opt-in + .container-fluid + .row + .col-md-12 + p.text-centered #{translate("beta_program_benefits")} + .row.text-centered + .col-md-12 + if user.betaProgram + p #{translate("beta_program_already_participating")} + else + form(method="post", action="/beta/opt-in", novalidate) + .form-group + input(type="hidden", name="_csrf", value=csrfToken) + button.btn.btn-lg.btn-primary( + type="submit" + ) + span #{translate("beta_program_opt_in_action")} + span.beta-feature-badge(tooltip="Beta Feature" tooltip-placement="right") β + diff --git a/services/web/public/stylesheets/app/beta-program.less b/services/web/public/stylesheets/app/beta-program.less new file mode 100644 index 0000000000..c0a88b76e6 --- /dev/null +++ b/services/web/public/stylesheets/app/beta-program.less @@ -0,0 +1,16 @@ +.beta-opt-in-wrapper { + min-height: 400px; +} + +.beta-opt-in { + .form-group { + margin-top: 15px; + } +} + +.beta-feature-badge { + &:extend(.label); + &:extend(.label-warning); + padding-bottom: 2px; + margin-left: 12px; +} \ No newline at end of file diff --git a/services/web/public/stylesheets/style.less b/services/web/public/stylesheets/style.less index 4e9823631c..b516adff3e 100755 --- a/services/web/public/stylesheets/style.less +++ b/services/web/public/stylesheets/style.less @@ -58,6 +58,7 @@ // ShareLaTeX app classes @import "app/base.less"; @import "app/account-settings.less"; +@import "app/beta-program.less"; @import "app/about-page.less"; @import "app/project-list.less"; @import "app/editor.less"; diff --git a/services/web/test/UnitTests/coffee/BetaProgram/BetaProgramControllerTests.coffee b/services/web/test/UnitTests/coffee/BetaProgram/BetaProgramControllerTests.coffee new file mode 100644 index 0000000000..d764db4865 --- /dev/null +++ b/services/web/test/UnitTests/coffee/BetaProgram/BetaProgramControllerTests.coffee @@ -0,0 +1,101 @@ +should = require('chai').should() +SandboxedModule = require('sandboxed-module') +assert = require('assert') +path = require('path') +sinon = require('sinon') +modulePath = path.join __dirname, "../../../../app/js/Features/BetaProgram/BetaProgramController" +expect = require("chai").expect + +describe "BetaProgramController", -> + + beforeEach -> + @user = + _id: @user_id = "a_simple_id" + email: "user@example.com" + features: {} + betaProgram: false + @req = + query: {} + session: + user: @user + @BetaProgramController = SandboxedModule.require modulePath, requires: + "./BetaProgramHandler": @BetaProgramHandler = { + optIn: sinon.stub() + }, + "../User/UserLocator": @UserLocator = { + findById: sinon.stub() + }, + "settings-sharelatex": @settings = { + languages: {} + } + "logger-sharelatex": @logger = { + log: sinon.stub() + err: sinon.stub() + error: sinon.stub() + } + @res = + send: sinon.stub() + redirect: sinon.stub() + render: sinon.stub() + @next = sinon.stub() + + describe "optIn", -> + + beforeEach -> + @BetaProgramHandler.optIn.callsArgWith(1, null) + + it "should redirect to '/'", () -> + @BetaProgramController.optIn @req, @res, @next + @res.redirect.callCount.should.equal 1 + + it "should not call next with an error", () -> + @BetaProgramController.optIn @req, @res, @next + @next.callCount.should.equal 0 + + it "should not call next with an error", () -> + @BetaProgramController.optIn @req, @res, @next + @next.callCount.should.equal 0 + + it "should call BetaProgramHandler.optIn", () -> + @BetaProgramController.optIn @req, @res, @next + @BetaProgramHandler.optIn.callCount.should.equal 1 + + describe "when BetaProgramHandler.opIn produces an error", -> + + beforeEach -> + @BetaProgramHandler.optIn.callsArgWith(1, new Error('woops')) + + it "should not redirect to '/'", () -> + @BetaProgramController.optIn @req, @res, @next + @res.redirect.callCount.should.equal 0 + + it "should produce an error", () -> + @BetaProgramController.optIn @req, @res, @next + @next.callCount.should.equal 1 + @next.firstCall.args[0].should.be.instanceof Error + + describe "optInPage", -> + + beforeEach -> + @UserLocator.findById.callsArgWith(1, null, @user) + + it "should render the opt-in page", () -> + @BetaProgramController.optInPage @req, @res, @next + @res.render.callCount.should.equal 1 + args = @res.render.firstCall.args + args[0].should.equal 'beta_program/opt_in' + + + describe "when UserLocator.findById produces an error", -> + + beforeEach -> + @UserLocator.findById.callsArgWith(1, new Error('woops')) + + it "should not render the opt-in page", () -> + @BetaProgramController.optInPage @req, @res, @next + @res.render.callCount.should.equal 0 + + it "should produce an error", () -> + @BetaProgramController.optInPage @req, @res, @next + @next.callCount.should.equal 1 + @next.firstCall.args[0].should.be.instanceof Error diff --git a/services/web/test/UnitTests/coffee/BetaProgram/BetaProgramHandlerTests.coffee b/services/web/test/UnitTests/coffee/BetaProgram/BetaProgramHandlerTests.coffee new file mode 100644 index 0000000000..e8b74a9f06 --- /dev/null +++ b/services/web/test/UnitTests/coffee/BetaProgram/BetaProgramHandlerTests.coffee @@ -0,0 +1,65 @@ +should = require('chai').should() +SandboxedModule = require('sandboxed-module') +assert = require('assert') +path = require('path') +modulePath = path.join __dirname, '../../../../app/js/Features/BetaProgram/BetaProgramHandler' +sinon = require("sinon") +expect = require("chai").expect + + +describe 'BetaProgramHandler', -> + + beforeEach -> + @user_id = "some_id" + @user = + _id: @user_id + email: 'user@example.com' + features: {} + betaProgram: false + save: sinon.stub().callsArgWith(0, null) + @handler = SandboxedModule.require modulePath, requires: + "../../models/User": { + User: + findById: sinon.stub().callsArgWith(1, null, @user) + }, + "logger-sharelatex": @logger = { + log: sinon.stub() + err: sinon.stub() + }, + "../../infrastructure/Metrics": @logger = { + inc: sinon.stub() + } + + + describe "optIn", -> + + beforeEach -> + @call = (callback) => + @handler.optIn @user_id, callback + + it "should set betaProgram = true on user object", (done) -> + @call (err) => + @user.betaProgram.should.equal true + done() + + it "should call user.save", (done) -> + @call (err) => + @user.save.callCount.should.equal 1 + done() + + it "should not produce an error", (done) -> + @call (err) => + expect(err).to.equal null + expect(err).to.not.be.instanceof Error + done() + + describe "when user.save produces an error", -> + + beforeEach -> + @user.save.callsArgWith(0, new Error('woops')) + + it "should produce an error", (done) -> + @call (err) => + expect(err).to.not.equal null + expect(err).to.be.instanceof Error + done() From 618d3ee269206574a9de274af8ead8179a56c770 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Tue, 7 Jun 2016 13:41:50 +0100 Subject: [PATCH 3/9] fix missing require --- services/web/app/coffee/router.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 50cce6b92c..38f501cafa 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -38,6 +38,7 @@ InactiveProjectController = require("./Features/InactiveData/InactiveProjectCont ContactRouter = require("./Features/Contacts/ContactRouter") ReferencesController = require('./Features/References/ReferencesController') AuthorizationMiddlewear = require('./Features/Authorization/AuthorizationMiddlewear') +BetaProgramController = require('./Features/BetaProgram/BetaProgramController') logger = require("logger-sharelatex") _ = require("underscore") From 2598661c4c3e8d96a91f6993ef8c46ece39b0873 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Tue, 7 Jun 2016 13:42:06 +0100 Subject: [PATCH 4/9] Use correct title for Beta Opt-in page --- .../coffee/Features/BetaProgram/BetaProgramController.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee b/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee index 5224d8f131..4877d6e945 100644 --- a/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee +++ b/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee @@ -24,6 +24,6 @@ module.exports = BetaProgramController = logger.err {err, user_id}, "error fetching user" return next(err) res.render 'beta_program/opt_in', - title:'beta_program_opt_in' + title:'sharelatex_beta_program' user: user, languages: Settings.languages, From 8c9d15a3e4c4deb3dff9301b3e60b6cedbc0ffb7 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Tue, 7 Jun 2016 13:42:27 +0100 Subject: [PATCH 5/9] Tweak style of the beta-feature-badge class. --- services/web/public/stylesheets/app/beta-program.less | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/web/public/stylesheets/app/beta-program.less b/services/web/public/stylesheets/app/beta-program.less index c0a88b76e6..8c9667d917 100644 --- a/services/web/public/stylesheets/app/beta-program.less +++ b/services/web/public/stylesheets/app/beta-program.less @@ -11,6 +11,8 @@ .beta-feature-badge { &:extend(.label); &:extend(.label-warning); - padding-bottom: 2px; + vertical-align: 11%; + padding-bottom: 4px; + padding-top: 2px; margin-left: 12px; } \ No newline at end of file From 0dfd80d3071ea85828a564b502be81a403c95020 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Tue, 7 Jun 2016 14:04:02 +0100 Subject: [PATCH 6/9] Use css to add the beta symbol to the beta-label --- services/web/app/views/beta_program/opt_in.jade | 2 +- services/web/public/stylesheets/app/beta-program.less | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/services/web/app/views/beta_program/opt_in.jade b/services/web/app/views/beta_program/opt_in.jade index 53e5e770f3..ecf9d21e74 100644 --- a/services/web/app/views/beta_program/opt_in.jade +++ b/services/web/app/views/beta_program/opt_in.jade @@ -26,5 +26,5 @@ block content type="submit" ) span #{translate("beta_program_opt_in_action")} - span.beta-feature-badge(tooltip="Beta Feature" tooltip-placement="right") β + span.beta-feature-badge() diff --git a/services/web/public/stylesheets/app/beta-program.less b/services/web/public/stylesheets/app/beta-program.less index 8c9667d917..f2141d711d 100644 --- a/services/web/public/stylesheets/app/beta-program.less +++ b/services/web/public/stylesheets/app/beta-program.less @@ -15,4 +15,7 @@ padding-bottom: 4px; padding-top: 2px; margin-left: 12px; + &:before { + content: "β"; + } } \ No newline at end of file From 009fa79589e00cbcbf44c147d512aa887f0fc632 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Wed, 8 Jun 2016 10:33:21 +0100 Subject: [PATCH 7/9] remove trailing parens. --- services/web/app/views/beta_program/opt_in.jade | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/views/beta_program/opt_in.jade b/services/web/app/views/beta_program/opt_in.jade index ecf9d21e74..fc8089c255 100644 --- a/services/web/app/views/beta_program/opt_in.jade +++ b/services/web/app/views/beta_program/opt_in.jade @@ -26,5 +26,5 @@ block content type="submit" ) span #{translate("beta_program_opt_in_action")} - span.beta-feature-badge() + span.beta-feature-badge From 90dac348ffb21b4f674e289033b3b7bd814043a3 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Wed, 8 Jun 2016 11:04:44 +0100 Subject: [PATCH 8/9] refine beta opt-in workflow. --- .../coffee/Features/BetaProgram/BetaProgramController.coffee | 2 +- services/web/app/views/beta_program/opt_in.jade | 4 +++- .../coffee/BetaProgram/BetaProgramControllerTests.coffee | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee b/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee index 4877d6e945..1fc7ee4d12 100644 --- a/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee +++ b/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee @@ -14,7 +14,7 @@ module.exports = BetaProgramController = BetaProgramHandler.optIn user_id, (err) -> if err return next(err) - return res.redirect "/" + return res.redirect "/beta/opt-in" optInPage: (req, res, next)-> user_id = req.session?.user?._id diff --git a/services/web/app/views/beta_program/opt_in.jade b/services/web/app/views/beta_program/opt_in.jade index fc8089c255..f48f4c241d 100644 --- a/services/web/app/views/beta_program/opt_in.jade +++ b/services/web/app/views/beta_program/opt_in.jade @@ -18,11 +18,13 @@ block content .col-md-12 if user.betaProgram p #{translate("beta_program_already_participating")} + .form-group + a(href="/project").btn.btn-info #{translate("back_to_your_projects")} else form(method="post", action="/beta/opt-in", novalidate) .form-group input(type="hidden", name="_csrf", value=csrfToken) - button.btn.btn-lg.btn-primary( + button.btn.btn-primary( type="submit" ) span #{translate("beta_program_opt_in_action")} diff --git a/services/web/test/UnitTests/coffee/BetaProgram/BetaProgramControllerTests.coffee b/services/web/test/UnitTests/coffee/BetaProgram/BetaProgramControllerTests.coffee index d764db4865..86a6ad3801 100644 --- a/services/web/test/UnitTests/coffee/BetaProgram/BetaProgramControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/BetaProgram/BetaProgramControllerTests.coffee @@ -44,9 +44,10 @@ describe "BetaProgramController", -> beforeEach -> @BetaProgramHandler.optIn.callsArgWith(1, null) - it "should redirect to '/'", () -> + it "should redirect to '/beta/opt-in'", () -> @BetaProgramController.optIn @req, @res, @next @res.redirect.callCount.should.equal 1 + @res.redirect.firstCall.args[0].should.equal "/beta/opt-in" it "should not call next with an error", () -> @BetaProgramController.optIn @req, @res, @next From 58d7d7bf7436f9a7acb6bc81a079ee25644de818 Mon Sep 17 00:00:00 2001 From: Shane Kilkelly Date: Wed, 8 Jun 2016 11:38:14 +0100 Subject: [PATCH 9/9] Re-work wording of beta opt-in page. --- services/web/app/views/beta_program/opt_in.jade | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/services/web/app/views/beta_program/opt_in.jade b/services/web/app/views/beta_program/opt_in.jade index f48f4c241d..5d1f7705f8 100644 --- a/services/web/app/views/beta_program/opt_in.jade +++ b/services/web/app/views/beta_program/opt_in.jade @@ -14,6 +14,15 @@ block content .row .col-md-12 p.text-centered #{translate("beta_program_benefits")} + p.text-centered + | #{translate("beta_program_badge_description")} + span.beta-feature-badge + p.text-centered + | #{translate("beta_program_current_beta_features_description")} + ul.list-unstyled.text-center + li + i.fa.fa-fw.fa-book + |  #{translate("mendeley_integration")} .row.text-centered .col-md-12 if user.betaProgram @@ -28,5 +37,4 @@ block content type="submit" ) span #{translate("beta_program_opt_in_action")} - span.beta-feature-badge