diff --git a/services/web/app/src/Features/Notifications/NotificationsBuilder.js b/services/web/app/src/Features/Notifications/NotificationsBuilder.js
index 8823022e7a..30ef8b97d5 100644
--- a/services/web/app/src/Features/Notifications/NotificationsBuilder.js
+++ b/services/web/app/src/Features/Notifications/NotificationsBuilder.js
@@ -281,6 +281,9 @@ function personalAndGroupSubscriptions(userId) {
}
}
+/**
+ * @param {string} [userId]
+ */
function ieeeCollabratecRetirement(userId) {
return {
key: 'notification-ieee-collabratec-retirement',
@@ -295,8 +298,8 @@ function ieeeCollabratecRetirement(userId) {
callback
)
},
- read(callback) {
- NotificationsHandler.markAsReadByKeyOnly(this.key, callback)
+ deleteAllUnread(callback) {
+ NotificationsHandler.markAsReadByKeyOnlyBulk(this.key, callback)
},
}
}
diff --git a/services/web/frontend/js/features/project-list/components/notifications/groups/common.tsx b/services/web/frontend/js/features/project-list/components/notifications/groups/common.tsx
index 7f46a0fbd6..2d8e5e9748 100644
--- a/services/web/frontend/js/features/project-list/components/notifications/groups/common.tsx
+++ b/services/web/frontend/js/features/project-list/components/notifications/groups/common.tsx
@@ -13,6 +13,7 @@ import {
} from '../../../../../../../types/project/dashboard/notification'
import { User } from '../../../../../../../types/user'
import GroupInvitationNotification from './group-invitation/group-invitation'
+import IEEERetirementBanner from '../ieee-retirement-banner'
import { debugConsole } from '@/utils/debugging'
function Common() {
@@ -305,21 +306,7 @@ function CommonNotification({ notification }: CommonNotificationProps) {
) : templateKey === 'notification_group_invitation' ? (
) : templateKey === 'notification_ieee_collabratec_retirement' ? (
- id && handleDismiss(id)}
- body={
- ,
- // eslint-disable-next-line jsx-a11y/anchor-has-content,react/jsx-key
- ,
- ]}
- />
- }
- />
+
) : templateKey === 'notification_personal_and_group_subscriptions' ? (
{
+ eventTracking.sendMB('promo-dismiss', {
+ name: 'ieee - retirement',
+ })
+ if (id) {
+ handleDismiss(id)
+ }
+ }, [id, handleDismiss])
+
+ const handleClickPlans = useCallback(() => {
+ eventTracking.sendMB('promo-click', {
+ name: 'ieee - retirement',
+ content: 'plans',
+ })
+ }, [])
+
+ const handleClickEmail = useCallback(() => {
+ eventTracking.sendMB('promo-click', {
+ name: 'ieee - retirement',
+ content: 'email',
+ })
+ }, [])
+
+ useEffect(() => {
+ if (!viewEventSent) {
+ eventTracking.sendMB('promo-prompt', {
+ name: 'ieee - retirement',
+ })
+ viewEventSent = true
+ }
+ }, [])
+
+ return (
+ ,
+ // eslint-disable-next-line jsx-a11y/anchor-has-content,react/jsx-key
+ ,
+ ]}
+ />
+ }
+ />
+ )
+}
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index da7555964a..8d1602df20 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -1212,7 +1212,7 @@
"note_experiments_under_development": "<0>Please note0> that experiments in this program are still being tested and actively developed. This means that they might <0>change0>, be <0>removed0> or <0>become part of a paid plan0>",
"note_features_under_development": "<0>Please note0> that features in this program are still being tested and actively developed. This means that they might <0>change0>, be <0>removed0> or <0>become part of a premium plan0>",
"notification_features_upgraded_by_affiliation": "Good news! Your affiliated organization __institutionName__ has an Overleaf subscription, and you now have access to all of Overleaf’s Professional features.",
- "notification_ieee_collabratec_retirement_message": "From January 31, IEEE is no longer providing access to Overleaf premium features for Collabratec users. Please contact <0>authors@ieee.org0> with any questions. Need to upgrade? <1>View our plans1>",
+ "notification_ieee_collabratec_retirement_message": "As of April 2, 2024, free access to Overleaf Professional for Collabratec users or IEEE accounts is no longer provided. Please contact <0>authors@ieee.org0> with any questions. Need to upgrade? <1>View our plans1>",
"notification_personal_and_group_subscriptions": "We’ve spotted that you’ve got <0>more than one active __appName__ subscription0>. To avoid paying more than you need to, <1>review your subscriptions1>.",
"notification_personal_subscription_not_required_due_to_affiliation": " Good news! Your affiliated organization __institutionName__ has an Overleaf subscription, and you now have access to Overleaf’s Professional features through your affiliation. You can cancel your individual subscription without losing access to any features.",
"notification_project_invite": "__userName__ would like you to join __projectName__ Join Project",
diff --git a/services/web/locales/zh-CN.json b/services/web/locales/zh-CN.json
index 198d7d2630..1077e0a67a 100644
--- a/services/web/locales/zh-CN.json
+++ b/services/web/locales/zh-CN.json
@@ -1200,7 +1200,6 @@
"note_experiments_under_development": "<0>请注意0>该计划中的实验仍在测试和快速开发中。 这意味着它们可能<0>发生改变0>、<0>被删除0>或<0>成为付费计划的一部分0>",
"note_features_under_development": "<0>请注意0>此计划中的功能仍在测试和快速开发中。 这意味着它们可能<0>改变0>、<0>被删除0>或<0>成为高级计划的一部分0>",
"notification_features_upgraded_by_affiliation": "好消息!您的组织__institutionName__已有 Overleaf 订阅,并且您现在可以访问 Overleaf 的所有专业功能。",
- "notification_ieee_collabratec_retirement_message": "从 2024年 1 月 31日起, IEEE 不再提供 Overleaf premium 功能给协作者。有问题请联系 <0>authors@ieee.org0>。需要升级吗? <1>查看我们的订阅计划1>",
"notification_personal_and_group_subscriptions": "我们发现您有<0>多个活跃的 __appName__ 订阅0>。 为避免支付超出您需要的费用,请<1>检查您的订阅1>。",
"notification_personal_subscription_not_required_due_to_affiliation": " 好消息!您的组织 __institutionName__ 与 Overleaf 有合作关系。您可以取消您的个人订阅,而不会失去访问您的任何利益。",
"notification_project_invite": "__userName__ 想让您加入 __projectName__ 加入项目",
diff --git a/services/web/scripts/add_notification_ieee_collabratec_users.js b/services/web/scripts/add_notification_ieee_collabratec_users.js
index 183e6217ae..09a23c6c27 100644
--- a/services/web/scripts/add_notification_ieee_collabratec_users.js
+++ b/services/web/scripts/add_notification_ieee_collabratec_users.js
@@ -1,11 +1,78 @@
+const path = require('path')
+const fs = require('fs')
const NotificationsBuilder = require('../app/src/Features/Notifications/NotificationsBuilder')
const { waitForDb } = require('../app/src/infrastructure/mongodb')
const { Subscription } = require('../app/src/models/Subscription')
const minimist = require('minimist')
+const { db } = require('../app/src/infrastructure/mongodb')
+const { promiseMapWithLimit } = require('@overleaf/promise-utils')
+
+/**
+ * This script is used to notify some users in the IEEECollabratec group that
+ * they will lose access to Overleaf.
+ *
+ * Parameters:
+ * --filename: the filename of the JSON file containing emails of users that
+ * should **not** be notified.
+ * --commit: if present, the script will commit the changes to the database.
+ *
+ * Usage:
+ * - dry run:
+ * node add_notification_ieee_collabratec_users.js --filename=emails.json
+ * - commit:
+ * node add_notification_ieee_collabratec_users.js --filename=emails.json --commit
+ */
let COMMIT = false
+let EMAILS_FILENAME
+
+/**
+ * The IEEE have provided us with a list of active users that should not be removed
+ * (and therefore not notified). This method retrives those users.
+ */
+function getActiveUserEmails(filename) {
+ const data = fs.readFileSync(path.join(__dirname, filename), 'utf8')
+ const emailsArray = JSON.parse(data)
+ const emailsSet = new Set(emailsArray)
+ console.log(
+ `Read ${emailsSet.size} (${emailsArray.length} in array) emails from ${filename}`
+ )
+ return emailsSet
+}
+
+async function getIEEEUsers() {
+ return await db.subscriptions
+ .aggregate([
+ { $match: { teamName: 'IEEECollabratec' } },
+ { $unwind: '$member_ids' },
+ {
+ $lookup: {
+ from: 'users',
+ localField: 'member_ids',
+ foreignField: '_id',
+ as: 'member_details',
+ },
+ },
+ {
+ $project: {
+ _id: 1,
+ teamName: 1,
+ 'member_details._id': 1,
+ 'member_details.email': 1,
+ 'member_details.emails.email': 1,
+ },
+ },
+ ])
+ .toArray()
+}
async function main() {
+ const start = performance.now()
+
+ if (!EMAILS_FILENAME) {
+ throw new Error('No email filename provided')
+ }
+
await waitForDb()
const subscription = await Subscription.findOne({
teamName: 'IEEECollabratec',
@@ -16,35 +83,71 @@ async function main() {
return
}
- const userIds = subscription.member_ids
-
- console.log(`Found ${userIds.length} users in IEEECollabratec group`)
-
- if (!COMMIT) {
- console.log('Dry run enabled, quitting here')
- return
+ // First we remove all existing Collabratec retirement notifications
+ if (COMMIT) {
+ await NotificationsBuilder.promises
+ .ieeeCollabratecRetirement()
+ .deleteAllUnread()
}
- if (userIds.length > 0) {
- console.log(`Notifying ${userIds.length} users`)
+ let totalUsers = 0
+ let totalUsersNotified = 0
- for (const id of userIds) {
+ const usersArray = await getIEEEUsers()
+ const activeUsers = getActiveUserEmails(EMAILS_FILENAME)
+
+ const activeUsersFound = new Set()
+
+ // Then go through each collabratec user to see if we need to notify them
+ await promiseMapWithLimit(10, usersArray, async member => {
+ if (totalUsers % 5000 === 0)
+ console.log(
+ `notified: ${totalUsersNotified} - progress: ${totalUsers} / ${usersArray.length}`
+ )
+
+ totalUsers = totalUsers + 1
+
+ const userDetails = member.member_details[0]
+
+ for (const email of userDetails.emails) {
+ if (activeUsers.has(email.email)) {
+ activeUsersFound.add(email.email)
+ return
+ }
+ }
+
+ if (COMMIT) {
await NotificationsBuilder.promises
- .ieeeCollabratecRetirement(id.toString())
+ .ieeeCollabratecRetirement(userDetails._id.toString())
.create()
}
- console.log(
- `Notification successfully added/updated for ${userIds.length} users`
- )
- } else {
- console.log('No users found')
- }
+ totalUsersNotified += 1
+ })
+
+ console.log(`Found ${totalUsers} users in IEEECollabratec group`)
+
+ console.log(
+ `Found ${totalUsersNotified} users in IEEECollabratec group to notify`
+ )
+
+ console.log(`Found ${activeUsersFound.size} active users`)
+
+ const activeUsersNotFound = Array.from(activeUsers).filter(
+ user => !activeUsersFound.has(user)
+ )
+
+ console.log(`${activeUsersNotFound.length} IEEE active users not found:`)
+ console.log(activeUsersNotFound)
+
+ const end = performance.now()
+ console.log(`Took ${end - start} ms`)
}
const setup = () => {
const argv = minimist(process.argv.slice(2))
COMMIT = argv.commit !== undefined
+ EMAILS_FILENAME = argv.filename
if (!COMMIT) {
console.warn('Doing dry run. Add --commit to commit changes')
}