Logs UI variant without popup (#3794)

* Add logs UI without pop-up variant

* Implement frontend for logs UI without pop-up

* Add logs UI variants to admin panel and front user info

* Fix existing UI subvariant window global

GitOrigin-RevId: 075db56032354d782e489b5235925f55b1a67e0b
This commit is contained in:
Paulo Jorge Reis 2021-03-23 10:18:28 +00:00 committed by Copybot
parent b19bd1ef61
commit 3f0e897e32
8 changed files with 179 additions and 41 deletions

View file

@ -1,29 +1,71 @@
const { ObjectId } = require('mongodb')
const Settings = require('settings-sharelatex')
function shouldUserSeeNewLogsUI(user) {
const EXISTING_UI = { newLogsUI: false, subvariant: null }
const NEW_UI_WITH_POPUP = {
newLogsUI: true,
subvariant: 'new-logs-ui-with-popup'
}
const NEW_UI_WITHOUT_POPUP = {
newLogsUI: true,
subvariant: 'new-logs-ui-without-popup'
}
function _getVariantForPercentile(
percentile,
newLogsUIWithPopupPercentage,
newLogsUIWithoutPopupPercentage
) {
// The thresholds below are upper thresholds
const newLogsUIThreshold = newLogsUIWithPopupPercentage
const newLogsUIWithoutPopupThreshold =
newLogsUIWithPopupPercentage + newLogsUIWithoutPopupPercentage
// The partitions for each of the variants (range is 0 to 99) are defined as:
// * New UI with pop-up: 0 to newLogsUIThreshold (exc)
// * New UI without pop-up: newLogsUIThreshold (inc) to newLogsUIWithoutPopupThreshold (exc)
// * Existing UI: newLogsUIWithoutPopupThreshold (inc) to 99
if (percentile < newLogsUIThreshold) {
return NEW_UI_WITH_POPUP
} else if (
percentile >= newLogsUIThreshold &&
percentile < newLogsUIWithoutPopupThreshold
) {
return NEW_UI_WITHOUT_POPUP
} else {
return EXISTING_UI
}
}
function getNewLogsUIVariantForUser(user) {
const {
_id: userId,
alphaProgram: isAlphaUser,
betaProgram: isBetaUser
} = user
if (!userId) {
return false
return EXISTING_UI
}
const userIdAsPercentile = (ObjectId(userId).getTimestamp() / 1000) % 100
if (isAlphaUser) {
return true
} else if (isBetaUser && userIdAsPercentile < Settings.logsUIPercentageBeta) {
return true
} else if (userIdAsPercentile < Settings.logsUIPercentage) {
return true
return NEW_UI_WITH_POPUP
} else if (isBetaUser) {
return _getVariantForPercentile(
userIdAsPercentile,
Settings.logsUIPercentageBeta,
Settings.logsUIPercentageWithoutPopupBeta
)
} else {
return false
return _getVariantForPercentile(
userIdAsPercentile,
Settings.logsUIPercentage,
Settings.logsUIPercentageWithoutPopup
)
}
}
module.exports = {
shouldUserSeeNewLogsUI
getNewLogsUIVariantForUser
}

View file

@ -37,7 +37,7 @@ const BrandVariationsHandler = require('../BrandVariations/BrandVariationsHandle
const UserController = require('../User/UserController')
const AnalyticsManager = require('../Analytics/AnalyticsManager')
const Modules = require('../../infrastructure/Modules')
const { shouldUserSeeNewLogsUI } = require('../Helpers/NewLogsUI')
const { getNewLogsUIVariantForUser } = require('../Helpers/NewLogsUI')
const _ssoAvailable = (affiliation, session, linkedInstitutionIds) => {
if (!affiliation.institution) return false
@ -800,7 +800,8 @@ const ProjectController = {
})
}
const userShouldSeeNewLogsUI = shouldUserSeeNewLogsUI(user)
const logsUIVariant = getNewLogsUIVariantForUser(user)
const userShouldSeeNewLogsUI = logsUIVariant.newLogsUI
const wantsOldLogsUI =
req.query && req.query.new_logs_ui === 'false'
@ -860,6 +861,7 @@ const ProjectController = {
wsUrl,
showSupport: Features.hasFeature('support'),
showNewLogsUI: userShouldSeeNewLogsUI && !wantsOldLogsUI,
logsUISubvariant: logsUIVariant.subvariant,
showNewNavigationUI:
req.query && req.query.new_navigation_ui === 'true',
showReactFileTree: !wantsOldFileTreeUI,

View file

@ -30,6 +30,7 @@ div.full-size.pdf(ng-controller="PdfController")
on-set-split-layout="setPdfSplitLayout"
on-set-full-layout="setPdfFullLayout"
on-stop-compilation="stop"
variant-with-first-error-popup="logsUISubvariant === 'new-logs-ui-with-popup'"
show-logs="shouldShowLogs"
on-log-entry-location-click="openInEditor"
)
@ -415,3 +416,4 @@ script(type='text/ng-template', id='clearCacheModalTemplate')
script(type="text/javascript").
window.showNewLogsUI = #{showNewLogsUI || false}
window.logsUISubvariant = !{logsUISubvariant ? '"' + logsUISubvariant + '"' : 'null'}

View file

@ -227,7 +227,10 @@ module.exports = settings =
# Compile UI rollout percentages
logsUIPercentageBeta: parseInt(process.env['LOGS_UI_PERCENTAGE_BETA'] || '0', 10)
logsUIPercentageWithoutPopupBeta: parseInt(process.env['LOGS_UI_WITHOUT_POPUP_PERCENTAGE_BETA'] || '0', 10)
logsUIPercentage: parseInt(process.env['LOGS_UI_PERCENTAGE'] || '0', 10)
logsUIPercentageWithoutPopup: parseInt(process.env['LOGS_UI_WITHOUT_POPUP_PERCENTAGE'] || '0', 10)
# cookie domain
# use full domain for cookies to only be accessible from that domain,

View file

@ -22,6 +22,7 @@ function PreviewPane({
pdfDownloadUrl,
onLogEntryLocationClick,
showLogs,
variantWithFirstErrorPopup = true,
splitLayout
}) {
const { t } = useTranslation()
@ -67,6 +68,7 @@ function PreviewPane({
!compilerState.isCompiling
const showFirstErrorPopUp =
variantWithFirstErrorPopup &&
nErrors > 0 &&
!seenLogsForCurrentCompile &&
!dismissedFirstErrorPopUp &&
@ -168,6 +170,7 @@ PreviewPane.propTypes = {
outputFiles: PropTypes.array,
pdfDownloadUrl: PropTypes.string,
showLogs: PropTypes.bool.isRequired,
variantWithFirstErrorPopup: PropTypes.bool,
splitLayout: PropTypes.bool.isRequired
}

View file

@ -32,6 +32,8 @@ App.controller('PdfController', function(
$scope.pdf.clearingCache = false
$scope.shouldShowLogs = false
$scope.logsUISubvariant = window.logsUISubvariant
// view logic to check whether the files dropdown should "drop up" or "drop down"
$scope.shouldDropUp = false
@ -708,7 +710,8 @@ App.controller('PdfController', function(
errors: $scope.pdf.logEntries.errors.length,
warnings: $scope.pdf.logEntries.warnings.length,
typesetting: $scope.pdf.logEntries.typesetting.length,
newLogsUI: window.showNewLogsUI
newLogsUI: window.showNewLogsUI,
subvariant: window.logsUISubvariant
}
eventTracking.sendMBSampled(
'compile-result',

View file

@ -13,6 +13,20 @@ describe('NewLogsUI helper', function() {
return ObjectId.createFromTime(time).toString()
}
function isExistingUI(variant) {
return !variant.newLogsUI && !variant.subvariant
}
function isNewUIWithPopup(variant) {
return variant.newLogsUI && variant.subvariant === 'new-logs-ui-with-popup'
}
function isNewUIWithoutPopup(variant) {
return (
variant.newLogsUI && variant.subvariant === 'new-logs-ui-without-popup'
)
}
beforeEach(function() {
this.user = {
alphaProgram: false,
@ -21,7 +35,9 @@ describe('NewLogsUI helper', function() {
}
this.settings = {
logsUIPercentageBeta: 0,
logsUIPercentage: 0
logsUIPercentageWithoutPopupBeta: 0,
logsUIPercentage: 0,
logsUIPercentageWithoutPopup: 0
}
NewLogsUI = SandboxedModule.require(MODULE_PATH, {
requires: {
@ -31,52 +47,117 @@ describe('NewLogsUI helper', function() {
})
})
it('should show the new logs ui for alpha users', function() {
it('should always show the new UI with popup for alpha users', function() {
this.user.alphaProgram = true
expect(NewLogsUI.shouldUserSeeNewLogsUI(this.user)).to.be.true
for (const percentile of [0, 20, 40, 60, 80]) {
this.user._id = userIdFromTime(percentile)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isNewUIWithPopup(variant)).to.be.true
}
})
describe('for beta users', function() {
beforeEach(function() {
this.user.betaProgram = true
})
it('should not show the new logs ui with a beta rollout percentage of 0', function() {
this.settings.logsUIPercentageBeta = 0
expect(NewLogsUI.shouldUserSeeNewLogsUI(this.user)).to.be.false
describe('with a 0% rollout', function() {
it('should always show the existing UI', function() {
for (const percentile of [0, 20, 40, 60, 80]) {
this.user._id = userIdFromTime(percentile)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isExistingUI(variant)).to.be.true
}
})
})
describe('with a beta rollout percentage > 0', function() {
const percentileThresold = 50
describe('with a new UI rollout', function() {
const newUIWithPopupPercentage = 33
const newUIWithoutPopupPercentage = 33
const newUIWithPopupThreshold = newUIWithPopupPercentage
const newUIWithoutPopupThreshold =
newUIWithPopupPercentage + newUIWithoutPopupPercentage
beforeEach(function() {
this.settings.logsUIPercentageBeta = percentileThresold
this.settings.logsUIPercentageBeta = newUIWithPopupPercentage
this.settings.logsUIPercentageWithoutPopupBeta = newUIWithoutPopupPercentage
})
it('should not show the new logs ui when the user id is higher than the percent threshold', function() {
this.user._id = userIdFromTime(percentileThresold + 1)
expect(NewLogsUI.shouldUserSeeNewLogsUI(this.user)).to.be.false
it('should show the new UI with popup when the id is below the new UI with popup upper threshold (exc)', function() {
this.user._id = userIdFromTime(newUIWithPopupThreshold - 1)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isNewUIWithPopup(variant)).to.be.true
})
it('should show the new logs ui when the user id is lower than the percent threshold', function() {
this.user._id = userIdFromTime(percentileThresold - 1)
expect(NewLogsUI.shouldUserSeeNewLogsUI(this.user)).to.be.true
it('should show the new UI without popup when the id is at the new UI with popup upper threshold (exc)', function() {
this.user._id = userIdFromTime(newUIWithPopupThreshold)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isNewUIWithoutPopup(variant)).to.be.true
})
it('should show the new UI without popup when the id is above the new UI with popup upper threshold (inc) and below the new UI without popup upper threshold (exc)', function() {
this.user._id = userIdFromTime(newUIWithoutPopupThreshold - 1)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isNewUIWithoutPopup(variant)).to.be.true
})
it('should show the existing UI when the id is at the new UI without popup upper threshold (exc)', function() {
this.user._id = userIdFromTime(newUIWithoutPopupThreshold)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isExistingUI(variant)).to.be.true
})
it('should show the existing UI when the id is above the new UI without popup upper threshold (exc)', function() {
this.user._id = userIdFromTime(newUIWithoutPopupThreshold + 1)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isExistingUI(variant)).to.be.true
})
})
})
describe('for normal users', function() {
it('should not show the new logs ui rollout percentage of 0', function() {
this.settings.logsUIPercentage = 0
expect(NewLogsUI.shouldUserSeeNewLogsUI(this.user)).to.be.false
describe('for regular users', function() {
describe('with a 0% rollout', function() {
it('should always show the existing UI', function() {
for (const percentile of [0, 20, 40, 60, 80]) {
this.user._id = userIdFromTime(percentile)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isExistingUI(variant)).to.be.true
}
})
})
describe('with a rollout percentage > 0', function() {
const percentileThresold = 50
describe('with a new UI rollout', function() {
const newUIWithPopupPercentage = 33
const newUIWithoutPopupPercentage = 33
const newUIWithPopupThreshold = newUIWithPopupPercentage
const newUIWithoutPopupThreshold =
newUIWithPopupPercentage + newUIWithoutPopupPercentage
beforeEach(function() {
this.settings.logsUIPercentage = percentileThresold
this.settings.logsUIPercentage = newUIWithPopupPercentage
this.settings.logsUIPercentageWithoutPopup = newUIWithoutPopupPercentage
})
it('should not show the new logs ui when the user id is higher than the percent threshold', function() {
this.user._id = userIdFromTime(percentileThresold + 1)
expect(NewLogsUI.shouldUserSeeNewLogsUI(this.user)).to.be.false
it('should show the new UI with popup when the id is below the new UI with popup upper threshold (exc)', function() {
this.user._id = userIdFromTime(newUIWithPopupThreshold - 1)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isNewUIWithPopup(variant)).to.be.true
})
it('should show the new logs ui when the user id is lower than the percent threshold', function() {
this.user._id = userIdFromTime(percentileThresold - 1)
expect(NewLogsUI.shouldUserSeeNewLogsUI(this.user)).to.be.true
it('should show the new UI without popup when the id is at the new UI with popup upper threshold (exc)', function() {
this.user._id = userIdFromTime(newUIWithPopupThreshold)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isNewUIWithoutPopup(variant)).to.be.true
})
it('should show the new UI without popup when the id is above the new UI with popup upper threshold (inc) and below the new UI without popup upper threshold (exc)', function() {
this.user._id = userIdFromTime(newUIWithoutPopupThreshold - 1)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isNewUIWithoutPopup(variant)).to.be.true
})
it('should show the existing UI when the id is at the new UI without popup upper threshold (exc)', function() {
this.user._id = userIdFromTime(newUIWithoutPopupThreshold)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isExistingUI(variant)).to.be.true
})
it('should show the existing UI when the id is above the new UI without popup upper threshold (exc)', function() {
this.user._id = userIdFromTime(newUIWithoutPopupThreshold + 1)
const variant = NewLogsUI.getNewLogsUIVariantForUser(this.user)
expect(isExistingUI(variant)).to.be.true
})
})
})

View file

@ -121,7 +121,9 @@ describe('ProjectController', function() {
inc: sinon.stub()
}
this.NewLogsUIHelper = {
shouldUserSeeNewLogsUI: sinon.stub().returns(false)
getNewLogsUIVariantForUser: sinon
.stub()
.returns({ newLogsUI: false, subvariant: null })
}
this.ProjectController = SandboxedModule.require(MODULE_PATH, {