Add basic BetaProgram feature.

This commit is contained in:
Shane Kilkelly 2016-06-07 11:15:56 +01:00
parent a297c07bbb
commit d8f1e8ec93
8 changed files with 263 additions and 0 deletions

View file

@ -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,

View file

@ -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)

View file

@ -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/index", AuthorizationMiddlewear.ensureUserCanReadProject, ReferencesController.index
webRouter.post "/project/:Project_id/references/indexAll", AuthorizationMiddlewear.ensureUserCanReadProject, ReferencesController.indexAll 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 #Admin Stuff
webRouter.get '/admin', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.index 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 webRouter.get '/admin/user', AuthorizationMiddlewear.ensureUserIsSiteAdmin, (req, res)-> res.redirect("/admin/register") #this gets removed by admin-panel addon

View file

@ -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") β

View file

@ -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;
}

View file

@ -58,6 +58,7 @@
// ShareLaTeX app classes // ShareLaTeX app classes
@import "app/base.less"; @import "app/base.less";
@import "app/account-settings.less"; @import "app/account-settings.less";
@import "app/beta-program.less";
@import "app/about-page.less"; @import "app/about-page.less";
@import "app/project-list.less"; @import "app/project-list.less";
@import "app/editor.less"; @import "app/editor.less";

View file

@ -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

View file

@ -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()