|
|
@@ -39,6 +39,7 @@ const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
|
|
const Modules = require('../../infrastructure/Modules')
|
|
|
const SplitTestV2Handler = require('../SplitTests/SplitTestV2Handler')
|
|
|
const { getNewLogsUIVariantForUser } = require('../Helpers/NewLogsUI')
|
|
|
+const FeaturesUpdater = require('../Subscription/FeaturesUpdater')
|
|
|
|
|
|
const _ssoAvailable = (affiliation, session, linkedInstitutionIds) => {
|
|
|
if (!affiliation.institution) return false
|
|
|
@@ -661,16 +662,21 @@ const ProjectController = {
|
|
|
} else {
|
|
|
User.findById(
|
|
|
userId,
|
|
|
- 'email first_name last_name referal_id signUpDate featureSwitches features refProviders alphaProgram betaProgram isAdmin ace',
|
|
|
+ 'email first_name last_name referal_id signUpDate featureSwitches features featuresEpoch refProviders alphaProgram betaProgram isAdmin ace',
|
|
|
(err, user) => {
|
|
|
// Handle case of deleted user
|
|
|
if (user == null) {
|
|
|
UserController.logout(req, res, next)
|
|
|
return
|
|
|
}
|
|
|
-
|
|
|
+ if (err) {
|
|
|
+ return cb(err)
|
|
|
+ }
|
|
|
logger.log({ projectId, userId }, 'got user')
|
|
|
- cb(err, user)
|
|
|
+ if (FeaturesUpdater.featuresEpochIsCurrent(user)) {
|
|
|
+ return cb(null, user)
|
|
|
+ }
|
|
|
+ ProjectController._refreshFeatures(req, user, cb)
|
|
|
}
|
|
|
)
|
|
|
}
|
|
|
@@ -893,6 +899,59 @@ const ProjectController = {
|
|
|
)
|
|
|
},
|
|
|
|
|
|
+ _refreshFeatures(req, user, callback) {
|
|
|
+ // If the feature refresh has failed in this session, don't retry
|
|
|
+ // it - require the user to log in again.
|
|
|
+ if (req.session.feature_refresh_failed) {
|
|
|
+ metrics.inc('features-refresh', 1, {
|
|
|
+ path: 'load-editor',
|
|
|
+ status: 'skipped',
|
|
|
+ })
|
|
|
+ return callback(null, user)
|
|
|
+ }
|
|
|
+ // If the refresh takes too long then return the current
|
|
|
+ // features. Note that the user.features property may still be
|
|
|
+ // updated in the background after the callback is called.
|
|
|
+ callback = _.once(callback)
|
|
|
+ const refreshTimeoutHandler = setTimeout(() => {
|
|
|
+ req.session.feature_refresh_failed = { reason: 'timeout', at: new Date() }
|
|
|
+ metrics.inc('features-refresh', 1, {
|
|
|
+ path: 'load-editor',
|
|
|
+ status: 'timeout',
|
|
|
+ })
|
|
|
+ callback(null, user)
|
|
|
+ }, 5000)
|
|
|
+ // try to refresh user features now
|
|
|
+ const timer = new metrics.Timer('features-refresh-on-load-editor')
|
|
|
+ FeaturesUpdater.refreshFeatures(
|
|
|
+ user._id,
|
|
|
+ 'load-editor',
|
|
|
+ (err, features) => {
|
|
|
+ clearTimeout(refreshTimeoutHandler)
|
|
|
+ timer.done()
|
|
|
+ if (err) {
|
|
|
+ // keep a record to prevent unneceary retries and leave
|
|
|
+ // the original features unmodified if the refresh failed
|
|
|
+ req.session.feature_refresh_failed = {
|
|
|
+ reason: 'error',
|
|
|
+ at: new Date(),
|
|
|
+ }
|
|
|
+ metrics.inc('features-refresh', 1, {
|
|
|
+ path: 'load-editor',
|
|
|
+ status: 'error',
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ user.features = features
|
|
|
+ metrics.inc('features-refresh', 1, {
|
|
|
+ path: 'load-editor',
|
|
|
+ status: 'success',
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return callback(null, user)
|
|
|
+ }
|
|
|
+ )
|
|
|
+ },
|
|
|
+
|
|
|
_buildProjectList(allProjects, userId) {
|
|
|
let project
|
|
|
const {
|