mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #20371 from overleaf/mf-us-gov-banner
[web] Add US gov banner based on inclusion and exclusion criteria GitOrigin-RevId: c45ed280c8ef2dbdf9f3b84488e767c06fcc1ae1
This commit is contained in:
parent
f3cb79c12f
commit
16ba4b0ddf
12 changed files with 211 additions and 11 deletions
|
@ -26,6 +26,7 @@ const GeoIpLookup = require('../../infrastructure/GeoIpLookup')
|
||||||
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
|
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
|
||||||
const SplitTestSessionHandler = require('../SplitTests/SplitTestSessionHandler')
|
const SplitTestSessionHandler = require('../SplitTests/SplitTestSessionHandler')
|
||||||
const SubscriptionLocator = require('../Subscription/SubscriptionLocator')
|
const SubscriptionLocator = require('../Subscription/SubscriptionLocator')
|
||||||
|
const TutorialHandler = require('../Tutorial/TutorialHandler')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @import { GetProjectsRequest, GetProjectsResponse, AllUsersProjects, MongoProject } from "./types"
|
* @import { GetProjectsRequest, GetProjectsResponse, AllUsersProjects, MongoProject } from "./types"
|
||||||
|
@ -114,7 +115,7 @@ async function projectListPage(req, res, next) {
|
||||||
const user = await User.findById(
|
const user = await User.findById(
|
||||||
userId,
|
userId,
|
||||||
`email emails features alphaProgram betaProgram lastPrimaryEmailCheck labsProgram signUpDate${
|
`email emails features alphaProgram betaProgram lastPrimaryEmailCheck labsProgram signUpDate${
|
||||||
isSaas ? ' enrollment writefull' : ''
|
isSaas ? ' enrollment writefull completedTutorials' : ''
|
||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -351,8 +352,26 @@ async function projectListPage(req, res, next) {
|
||||||
affiliation => affiliation.licence && affiliation.licence !== 'free'
|
affiliation => affiliation.licence && affiliation.licence !== 'free'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const inactiveTutorials = TutorialHandler.getInactiveTutorials(user)
|
||||||
|
|
||||||
|
const usGovBannerHooksResponse = await Modules.promises.hooks.fire(
|
||||||
|
'getUSGovBanner',
|
||||||
|
userEmails,
|
||||||
|
hasPaidAffiliation,
|
||||||
|
inactiveTutorials.includes('us-gov-banner')
|
||||||
|
)
|
||||||
|
|
||||||
|
const usGovBanner = (usGovBannerHooksResponse &&
|
||||||
|
usGovBannerHooksResponse[0]) || {
|
||||||
|
showUSGovBanner: false,
|
||||||
|
usGovBannerVariant: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { showUSGovBanner, usGovBannerVariant } = usGovBanner
|
||||||
|
|
||||||
const showGroupsAndEnterpriseBanner =
|
const showGroupsAndEnterpriseBanner =
|
||||||
Features.hasFeature('saas') &&
|
Features.hasFeature('saas') &&
|
||||||
|
!showUSGovBanner &&
|
||||||
!userIsMemberOfGroupSubscription &&
|
!userIsMemberOfGroupSubscription &&
|
||||||
!hasPaidAffiliation
|
!hasPaidAffiliation
|
||||||
|
|
||||||
|
@ -455,6 +474,8 @@ async function projectListPage(req, res, next) {
|
||||||
prefetchedProjectsBlob,
|
prefetchedProjectsBlob,
|
||||||
showGroupsAndEnterpriseBanner,
|
showGroupsAndEnterpriseBanner,
|
||||||
groupsAndEnterpriseBannerVariant,
|
groupsAndEnterpriseBannerVariant,
|
||||||
|
showUSGovBanner,
|
||||||
|
usGovBannerVariant,
|
||||||
showWritefullPromoBanner,
|
showWritefullPromoBanner,
|
||||||
showLATAMBanner,
|
showLATAMBanner,
|
||||||
recommendedCurrency,
|
recommendedCurrency,
|
||||||
|
|
|
@ -10,6 +10,7 @@ const VALID_KEYS = [
|
||||||
'ai-error-assistant-consent',
|
'ai-error-assistant-consent',
|
||||||
'code-editor-mode-prompt',
|
'code-editor-mode-prompt',
|
||||||
'history-restore-promo',
|
'history-restore-promo',
|
||||||
|
'us-gov-banner',
|
||||||
]
|
]
|
||||||
|
|
||||||
async function completeTutorial(req, res, next) {
|
async function completeTutorial(req, res, next) {
|
||||||
|
@ -27,12 +28,21 @@ async function completeTutorial(req, res, next) {
|
||||||
async function postponeTutorial(req, res, next) {
|
async function postponeTutorial(req, res, next) {
|
||||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||||
const tutorialKey = req.params.tutorialKey
|
const tutorialKey = req.params.tutorialKey
|
||||||
|
let postponedUntil
|
||||||
|
if (req.body.postponedUntil) {
|
||||||
|
postponedUntil = new Date(req.body.postponedUntil)
|
||||||
|
}
|
||||||
|
|
||||||
if (!VALID_KEYS.includes(tutorialKey)) {
|
if (!VALID_KEYS.includes(tutorialKey)) {
|
||||||
return res.sendStatus(404)
|
return res.sendStatus(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
await TutorialHandler.setTutorialState(userId, tutorialKey, 'postponed')
|
await TutorialHandler.setTutorialState(
|
||||||
|
userId,
|
||||||
|
tutorialKey,
|
||||||
|
'postponed',
|
||||||
|
postponedUntil
|
||||||
|
)
|
||||||
res.sendStatus(204)
|
res.sendStatus(204)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,26 @@ const POSTPONE_DURATION_MS = 24 * 60 * 60 * 1000 // 1 day
|
||||||
* @param {string} userId
|
* @param {string} userId
|
||||||
* @param {string} tutorialKey
|
* @param {string} tutorialKey
|
||||||
* @param {'completed' | 'postponed'} state
|
* @param {'completed' | 'postponed'} state
|
||||||
|
* @param {Date} [postponedUntil] - The date until which the tutorial is postponed
|
||||||
*/
|
*/
|
||||||
async function setTutorialState(userId, tutorialKey, state) {
|
async function setTutorialState(
|
||||||
|
userId,
|
||||||
|
tutorialKey,
|
||||||
|
state,
|
||||||
|
postponedUntil = null
|
||||||
|
) {
|
||||||
|
const updateData = {
|
||||||
|
state,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state === 'postponed' && postponedUntil) {
|
||||||
|
updateData.postponedUntil = postponedUntil
|
||||||
|
}
|
||||||
|
|
||||||
await UserUpdater.promises.updateUser(userId, {
|
await UserUpdater.promises.updateUser(userId, {
|
||||||
$set: {
|
$set: {
|
||||||
[`completedTutorials.${tutorialKey}`]: { state, updatedAt: new Date() },
|
[`completedTutorials.${tutorialKey}`]: updateData,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -29,9 +44,11 @@ function getInactiveTutorials(user, tutorialKey) {
|
||||||
// Legacy format: single date means the tutorial was completed
|
// Legacy format: single date means the tutorial was completed
|
||||||
inactiveTutorials.push(key)
|
inactiveTutorials.push(key)
|
||||||
} else if (record.state === 'postponed') {
|
} else if (record.state === 'postponed') {
|
||||||
const postponedUntil = new Date(
|
const defaultPostponedUntil = new Date(
|
||||||
record.updatedAt.getTime() + POSTPONE_DURATION_MS
|
record.updatedAt.getTime() + POSTPONE_DURATION_MS
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const postponedUntil = record.postponedUntil ?? defaultPostponedUntil
|
||||||
if (new Date() < postponedUntil) {
|
if (new Date() < postponedUntil) {
|
||||||
inactiveTutorials.push(key)
|
inactiveTutorials.push(key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,8 @@ block append meta
|
||||||
meta(name="ol-groupSubscriptionsPendingEnrollment" data-type="json" content=groupSubscriptionsPendingEnrollment)
|
meta(name="ol-groupSubscriptionsPendingEnrollment" data-type="json" content=groupSubscriptionsPendingEnrollment)
|
||||||
meta(name="ol-hasIndividualRecurlySubscription" data-type="boolean" content=hasIndividualRecurlySubscription)
|
meta(name="ol-hasIndividualRecurlySubscription" data-type="boolean" content=hasIndividualRecurlySubscription)
|
||||||
meta(name="ol-groupSsoSetupSuccess" data-type="boolean" content=groupSsoSetupSuccess)
|
meta(name="ol-groupSsoSetupSuccess" data-type="boolean" content=groupSsoSetupSuccess)
|
||||||
|
meta(name="ol-showUSGovBanner" data-type="boolean" content=showUSGovBanner)
|
||||||
|
meta(name="ol-usGovBannerVariant" data-type="string" content=usGovBannerVariant)
|
||||||
|
|
||||||
block content
|
block content
|
||||||
main.content.content-alt.project-list-react#main-content
|
main.content.content-alt.project-list-react#main-content
|
||||||
|
|
|
@ -959,6 +959,7 @@ module.exports = {
|
||||||
ssoCertificateInfo: [],
|
ssoCertificateInfo: [],
|
||||||
v1ImportDataScreen: [],
|
v1ImportDataScreen: [],
|
||||||
snapshotUtils: [],
|
snapshotUtils: [],
|
||||||
|
usGovBanner: [],
|
||||||
offlineModeToolbarButtons: [],
|
offlineModeToolbarButtons: [],
|
||||||
settingsEntries: [],
|
settingsEntries: [],
|
||||||
},
|
},
|
||||||
|
|
|
@ -1687,6 +1687,8 @@
|
||||||
"upload_project": "",
|
"upload_project": "",
|
||||||
"upload_zipped_project": "",
|
"upload_zipped_project": "",
|
||||||
"url_to_fetch_the_file_from": "",
|
"url_to_fetch_the_file_from": "",
|
||||||
|
"us_gov_banner_government_purchasing": "",
|
||||||
|
"us_gov_banner_small_business_reseller": "",
|
||||||
"use_a_different_password": "",
|
"use_a_different_password": "",
|
||||||
"use_saml_metadata_to_configure_sso_with_idp": "",
|
"use_saml_metadata_to_configure_sso_with_idp": "",
|
||||||
"use_your_own_machine": "",
|
"use_your_own_machine": "",
|
||||||
|
|
|
@ -17,6 +17,8 @@ const [enrollmentNotificationModule] = importOverleafModules(
|
||||||
'managedGroupSubscriptionEnrollmentNotification'
|
'managedGroupSubscriptionEnrollmentNotification'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [usGovBannerModule] = importOverleafModules('usGovBanner')
|
||||||
|
|
||||||
const moduleNotifications = importOverleafModules('userNotifications') as {
|
const moduleNotifications = importOverleafModules('userNotifications') as {
|
||||||
import: { default: ElementType }
|
import: { default: ElementType }
|
||||||
path: string
|
path: string
|
||||||
|
@ -27,6 +29,9 @@ const EnrollmentNotification: JSXElementConstructor<{
|
||||||
groupName: string
|
groupName: string
|
||||||
}> = enrollmentNotificationModule?.import.default
|
}> = enrollmentNotificationModule?.import.default
|
||||||
|
|
||||||
|
const USGovBanner: JSXElementConstructor<Record<string, never>> =
|
||||||
|
usGovBannerModule?.import.default
|
||||||
|
|
||||||
function UserNotifications() {
|
function UserNotifications() {
|
||||||
const groupSubscriptionsPendingEnrollment =
|
const groupSubscriptionsPendingEnrollment =
|
||||||
getMeta('ol-groupSubscriptionsPendingEnrollment') || []
|
getMeta('ol-groupSubscriptionsPendingEnrollment') || []
|
||||||
|
@ -75,6 +80,7 @@ function UserNotifications() {
|
||||||
<ReconfirmationInfo />
|
<ReconfirmationInfo />
|
||||||
<GeoBanners />
|
<GeoBanners />
|
||||||
{!showWritefull && !dismissedWritefull && <GroupsAndEnterpriseBanner />}
|
{!showWritefull && !dismissedWritefull && <GroupsAndEnterpriseBanner />}
|
||||||
|
<USGovBanner />
|
||||||
|
|
||||||
<AccessibilitySurveyBanner />
|
<AccessibilitySurveyBanner />
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import {
|
||||||
Institution as InstitutionType,
|
Institution as InstitutionType,
|
||||||
Notification as NotificationType,
|
Notification as NotificationType,
|
||||||
PendingGroupSubscriptionEnrollment,
|
PendingGroupSubscriptionEnrollment,
|
||||||
|
USGovBannerVariant,
|
||||||
} from '../../../types/project/dashboard/notification'
|
} from '../../../types/project/dashboard/notification'
|
||||||
import { Survey } from '../../../types/project/dashboard/survey'
|
import { Survey } from '../../../types/project/dashboard/survey'
|
||||||
import { GetProjectsResponseBody } from '../../../types/project/dashboard/api'
|
import { GetProjectsResponseBody } from '../../../types/project/dashboard/api'
|
||||||
|
@ -180,6 +181,7 @@ export interface Meta {
|
||||||
'ol-showSupport': boolean
|
'ol-showSupport': boolean
|
||||||
'ol-showSymbolPalette': boolean
|
'ol-showSymbolPalette': boolean
|
||||||
'ol-showTemplatesServerPro': boolean
|
'ol-showTemplatesServerPro': boolean
|
||||||
|
'ol-showUSGovBanner': boolean
|
||||||
'ol-showUpgradePrompt': boolean
|
'ol-showUpgradePrompt': boolean
|
||||||
'ol-skipUrl': string
|
'ol-skipUrl': string
|
||||||
'ol-splitTestInfo': { [name: string]: SplitTestInfo }
|
'ol-splitTestInfo': { [name: string]: SplitTestInfo }
|
||||||
|
@ -198,6 +200,7 @@ export interface Meta {
|
||||||
'ol-translationLoadErrorMessage': string
|
'ol-translationLoadErrorMessage': string
|
||||||
'ol-translationMaintenance': string
|
'ol-translationMaintenance': string
|
||||||
'ol-translationUnableToJoin': string
|
'ol-translationUnableToJoin': string
|
||||||
|
'ol-usGovBannerVariant': USGovBannerVariant
|
||||||
'ol-useShareJsHash': boolean
|
'ol-useShareJsHash': boolean
|
||||||
'ol-usedLatex': 'never' | 'occasionally' | 'often' | undefined
|
'ol-usedLatex': 'never' | 'occasionally' | 'often' | undefined
|
||||||
'ol-user': User
|
'ol-user': User
|
||||||
|
|
|
@ -2300,6 +2300,8 @@
|
||||||
"upload_project": "Upload Project",
|
"upload_project": "Upload Project",
|
||||||
"upload_zipped_project": "Upload Zipped Project",
|
"upload_zipped_project": "Upload Zipped Project",
|
||||||
"url_to_fetch_the_file_from": "URL to fetch the file from",
|
"url_to_fetch_the_file_from": "URL to fetch the file from",
|
||||||
|
"us_gov_banner_government_purchasing": "<0>Get __appName__ for US federal government. </0>Move faster through procurement with our tailored purchasing options. Talk to our government team.",
|
||||||
|
"us_gov_banner_small_business_reseller": "<0>Easy procurement for US federal government. </0>We partner with small business resellers to help you buy Overleaf organizational plans. Talk to our government team.",
|
||||||
"usage_metrics": "Usage metrics",
|
"usage_metrics": "Usage metrics",
|
||||||
"usage_metrics_info": "Metrics that show how many users are accessing the licence, how many projects are being created and worked on, and how much collaboration is happening in Overleaf.",
|
"usage_metrics_info": "Metrics that show how many users are accessing the licence, how many projects are being created and worked on, and how much collaboration is happening in Overleaf.",
|
||||||
"use_a_different_password": "Please use a different password",
|
"use_a_different_password": "Please use a different password",
|
||||||
|
|
|
@ -138,6 +138,17 @@ describe('ProjectListController', function () {
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
this.TutorialHandler = {
|
||||||
|
getInactiveTutorials: sinon.stub().returns([]),
|
||||||
|
}
|
||||||
|
|
||||||
|
this.Modules = {
|
||||||
|
promises: {
|
||||||
|
hooks: {
|
||||||
|
fire: sinon.stub().resolves([]),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
this.ProjectListController = SandboxedModule.require(MODULE_PATH, {
|
this.ProjectListController = SandboxedModule.require(MODULE_PATH, {
|
||||||
requires: {
|
requires: {
|
||||||
|
@ -158,17 +169,14 @@ describe('ProjectListController', function () {
|
||||||
'../User/UserGetter': this.UserGetter,
|
'../User/UserGetter': this.UserGetter,
|
||||||
'../Subscription/SubscriptionViewModelBuilder':
|
'../Subscription/SubscriptionViewModelBuilder':
|
||||||
this.SubscriptionViewModelBuilder,
|
this.SubscriptionViewModelBuilder,
|
||||||
'../../infrastructure/Modules': {
|
'../../infrastructure/Modules': this.Modules,
|
||||||
promises: {
|
|
||||||
hooks: { fire: sinon.stub().resolves([]) },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
'../Survey/SurveyHandler': this.SurveyHandler,
|
'../Survey/SurveyHandler': this.SurveyHandler,
|
||||||
'../User/UserPrimaryEmailCheckHandler':
|
'../User/UserPrimaryEmailCheckHandler':
|
||||||
this.UserPrimaryEmailCheckHandler,
|
this.UserPrimaryEmailCheckHandler,
|
||||||
'../Notifications/NotificationsBuilder': this.NotificationBuilder,
|
'../Notifications/NotificationsBuilder': this.NotificationBuilder,
|
||||||
'../Subscription/SubscriptionLocator': this.SubscriptionLocator,
|
'../Subscription/SubscriptionLocator': this.SubscriptionLocator,
|
||||||
'../../infrastructure/GeoIpLookup': this.GeoIpLookup,
|
'../../infrastructure/GeoIpLookup': this.GeoIpLookup,
|
||||||
|
'../Tutorial/TutorialHandler': this.TutorialHandler,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -594,6 +602,101 @@ describe('ProjectListController', function () {
|
||||||
this.ProjectListController.projectListPage(this.req, this.res)
|
this.ProjectListController.projectListPage(this.req, this.res)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('enterprise banner', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
this.Features.hasFeature.withArgs('saas').returns(true)
|
||||||
|
this.LimitationsManager.promises.userIsMemberOfGroupSubscription.resolves(
|
||||||
|
{ isMember: false }
|
||||||
|
)
|
||||||
|
this.UserGetter.promises.getUserFullEmails.resolves([
|
||||||
|
{
|
||||||
|
email: 'test@test-domain.com',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('normal enterprise banner', function () {
|
||||||
|
it('shows banner', function () {
|
||||||
|
this.res.render = (pageName, opts) => {
|
||||||
|
expect(opts.showGroupsAndEnterpriseBanner).to.be.true
|
||||||
|
}
|
||||||
|
this.ProjectListController.projectListPage(this.req, this.res)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not show banner if user is part of any affiliation', function () {
|
||||||
|
this.UserGetter.promises.getUserFullEmails.resolves([
|
||||||
|
{
|
||||||
|
email: 'test@overleaf.com',
|
||||||
|
affiliation: {
|
||||||
|
licence: 'pro_plus',
|
||||||
|
institution: {
|
||||||
|
id: 1,
|
||||||
|
confirmed: true,
|
||||||
|
name: 'Overleaf',
|
||||||
|
ssoBeta: false,
|
||||||
|
ssoEnabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
this.res.render = (pageName, opts) => {
|
||||||
|
expect(opts.showGroupsAndEnterpriseBanner).to.be.false
|
||||||
|
}
|
||||||
|
this.ProjectListController.projectListPage(this.req, this.res)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not show banner if user is part of any group subscription', function () {
|
||||||
|
this.LimitationsManager.promises.userIsMemberOfGroupSubscription.resolves(
|
||||||
|
{ isMember: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
this.res.render = (pageName, opts) => {
|
||||||
|
expect(opts.showGroupsAndEnterpriseBanner).to.be.false
|
||||||
|
}
|
||||||
|
this.ProjectListController.projectListPage(this.req, this.res)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('have a banner variant of "FOMO" or "on-premise"', function () {
|
||||||
|
this.res.render = (pageName, opts) => {
|
||||||
|
expect(opts.groupsAndEnterpriseBannerVariant).to.be.oneOf([
|
||||||
|
'FOMO',
|
||||||
|
'on-premise',
|
||||||
|
])
|
||||||
|
}
|
||||||
|
this.ProjectListController.projectListPage(this.req, this.res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('US government enterprise banner', function () {
|
||||||
|
it('does not show enterprise banner if US government enterprise banner is shown', function () {
|
||||||
|
const emails = [
|
||||||
|
{
|
||||||
|
email: 'test@test.mil',
|
||||||
|
confirmedAt: new Date('2024-01-01'),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
this.UserGetter.promises.getUserFullEmails.resolves(emails)
|
||||||
|
this.Modules.promises.hooks.fire
|
||||||
|
.withArgs('getUSGovBanner', emails, false, false)
|
||||||
|
.resolves([
|
||||||
|
{
|
||||||
|
showUSGovBanner: true,
|
||||||
|
usGovBannerVariant: 'variant',
|
||||||
|
},
|
||||||
|
])
|
||||||
|
this.res.render = (pageName, opts) => {
|
||||||
|
expect(opts.showGroupsAndEnterpriseBanner).to.be.false
|
||||||
|
expect(opts.showUSGovBanner).to.be.true
|
||||||
|
}
|
||||||
|
this.ProjectListController.projectListPage(this.req, this.res)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('projectListReactPage with duplicate projects', function () {
|
describe('projectListReactPage with duplicate projects', function () {
|
||||||
|
|
|
@ -9,6 +9,10 @@ describe('TutorialHandler', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.clock = sinon.useFakeTimers()
|
this.clock = sinon.useFakeTimers()
|
||||||
|
|
||||||
|
const THIRTY_DAYS_AGO = Date.now() - 30 * 24 * 60 * 60 * 1000
|
||||||
|
const TOMORROW = Date.now() + 24 * 60 * 60 * 1000
|
||||||
|
const YESTERDAY = Date.now() - 24 * 60 * 60 * 1000
|
||||||
|
|
||||||
this.user = {
|
this.user = {
|
||||||
_id: new ObjectId(),
|
_id: new ObjectId(),
|
||||||
completedTutorials: {
|
completedTutorials: {
|
||||||
|
@ -23,7 +27,17 @@ describe('TutorialHandler', function () {
|
||||||
},
|
},
|
||||||
'postponed-long-ago': {
|
'postponed-long-ago': {
|
||||||
state: 'postponed',
|
state: 'postponed',
|
||||||
updatedAt: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000),
|
updatedAt: new Date(THIRTY_DAYS_AGO),
|
||||||
|
},
|
||||||
|
'postponed-until-tomorrow': {
|
||||||
|
state: 'postponed',
|
||||||
|
updatedAt: new Date(THIRTY_DAYS_AGO),
|
||||||
|
postponedUntil: new Date(TOMORROW),
|
||||||
|
},
|
||||||
|
'postponed-until-yesterday': {
|
||||||
|
state: 'postponed',
|
||||||
|
updatedAt: new Date(THIRTY_DAYS_AGO),
|
||||||
|
postponedUntil: new Date(YESTERDAY),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -54,7 +68,20 @@ describe('TutorialHandler', function () {
|
||||||
'legacy-format',
|
'legacy-format',
|
||||||
'completed',
|
'completed',
|
||||||
'postponed-recently',
|
'postponed-recently',
|
||||||
|
'postponed-until-tomorrow',
|
||||||
])
|
])
|
||||||
|
|
||||||
|
expect(hiddenTutorials).to.have.lengthOf(4)
|
||||||
|
|
||||||
|
const shownTutorials = Object.keys(this.user.completedTutorials).filter(
|
||||||
|
key => !hiddenTutorials.includes(key)
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(shownTutorials).to.have.members([
|
||||||
|
'postponed-long-ago',
|
||||||
|
'postponed-until-yesterday',
|
||||||
|
])
|
||||||
|
expect(shownTutorials).to.have.lengthOf(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -105,3 +105,9 @@ export type PendingGroupSubscriptionEnrollment = {
|
||||||
export const GroupsAndEnterpriseBannerVariants = ['on-premise', 'FOMO'] as const
|
export const GroupsAndEnterpriseBannerVariants = ['on-premise', 'FOMO'] as const
|
||||||
export type GroupsAndEnterpriseBannerVariant =
|
export type GroupsAndEnterpriseBannerVariant =
|
||||||
(typeof GroupsAndEnterpriseBannerVariants)[number]
|
(typeof GroupsAndEnterpriseBannerVariants)[number]
|
||||||
|
|
||||||
|
export const USGovBannerVariants = [
|
||||||
|
'government-purchasing',
|
||||||
|
'small-business-reseller',
|
||||||
|
] as const
|
||||||
|
export type USGovBannerVariant = (typeof USGovBannerVariants)[number]
|
||||||
|
|
Loading…
Reference in a new issue