Merge pull request #33345 from overleaf/rh-split-group-role

Update group_role in customer.io when changed

GitOrigin-RevId: d21866a9fe324a0468de74a45b6932dda27de8a1
This commit is contained in:
roo hutton
2026-05-28 08:06:43 +00:00
committed by Copybot
parent 5d0becf76b
commit 4f192564f2
10 changed files with 348 additions and 14 deletions
@@ -129,6 +129,7 @@ async function projectListPage(req, res, next) {
let usersIndividualSubscription
/** @type {any[]} */
let usersGroupSubscriptions = []
/** @type {any[]} */
let usersManagedGroupSubscriptions = []
let survey
let userIsMemberOfGroupSubscription = false
@@ -546,12 +547,23 @@ async function projectListPage(req, res, next) {
user
)
const groupRole = userIsMemberOfGroupSubscription
? usersManagedGroupSubscriptions?.length > 0 ||
usersGroupSubscriptions.some(sub => sub.userIsGroupManager)
? 'admin'
: 'member'
: undefined
let groupRole
if (userIsMemberOfGroupSubscription) {
const userIdStr = userId.toString()
const isGroupAdmin = usersManagedGroupSubscriptions?.some(
sub => sub.admin_id?._id?.toString() === userIdStr
)
const isGroupManager =
usersManagedGroupSubscriptions?.length > 0 ||
usersGroupSubscriptions?.some(sub => sub.userIsGroupManager)
if (isGroupAdmin) {
groupRole = 'admin'
} else if (isGroupManager) {
groupRole = 'manager'
} else {
groupRole = 'member'
}
}
Modules.promises.hooks
.fire('setUserProperties', userId, {
@@ -512,6 +512,34 @@ function shouldUseCommonsBestSubscription(
)
}
/**
* Determine the user's role in any group subscription they participate in.
*
* @param {MongoSubscription[]} memberGroupSubscriptions
* @param {MongoSubscription[]} managedGroupSubscriptions
* @param {string|object} userId
* @returns {''|'admin'|'manager'|'member'}
*/
function getGroupRole(
memberGroupSubscriptions = [],
managedGroupSubscriptions = [],
userId
) {
if (
managedGroupSubscriptions.length === 0 &&
memberGroupSubscriptions.length === 0
) {
return ''
}
const userIdStr = userId.toString()
const isAdmin = managedGroupSubscriptions.some(
sub => sub.admin_id?._id?.toString() === userIdStr
)
if (isAdmin) return 'admin'
if (managedGroupSubscriptions.length > 0) return 'manager'
return 'member'
}
/**
* Compute plan-related user properties for sending to customer.io.
*
@@ -525,6 +553,7 @@ function shouldUseCommonsBestSubscription(
* @param {boolean} options.hasCommons
* @param {Nullable<{ isPremium?: boolean }>} [options.writefullData]
* @param {Map<string, boolean>} [options.aiBlockedByPolicyId]
* @param {string|object} options.userId
*/
function getPlanProperties({
bestSubscription,
@@ -536,6 +565,7 @@ function getPlanProperties({
hasCommons,
writefullData,
aiBlockedByPolicyId,
userId,
}) {
const planType = normalizePlanType(bestSubscription)
const displayPlanType = getFriendlyPlanName(planType)
@@ -581,6 +611,11 @@ function getPlanProperties({
const properties = {
ai_plan: aiPlan,
group: userIsMemberOfGroupSubscription,
group_role: getGroupRole(
memberGroupSubscriptions,
managedGroupSubscriptions,
userId
),
commons: Boolean(hasCommons),
individual_subscription: Boolean(
individualSubscription && !individualSubscription.groupPlan
@@ -620,5 +655,6 @@ export default {
getAiPlanCadence,
hasPlanAiEnabled,
shouldUseCommonsBestSubscription,
getGroupRole,
getPlanProperties,
}
@@ -185,6 +185,7 @@ async function _updateCustomerIoSubscriptionProperties(user, features) {
hasCommons,
writefullData,
aiBlockedByPolicyId,
userId,
})
await Modules.promises.hooks.fire('setUserProperties', userId, {
@@ -15,6 +15,7 @@ import AccountMappingHelper from '../Analytics/AccountMappingHelper.mjs'
import { SSOConfig } from '../../models/SSOConfig.mjs'
import mongoose from '../../infrastructure/Mongoose.mjs'
import Modules from '../../infrastructure/Modules.mjs'
import CustomerIoPlanHelpers from './CustomerIoPlanHelpers.mjs'
/**
* @typedef {import('../../../../types/subscription/dashboard/subscription').Subscription} Subscription
@@ -73,6 +74,13 @@ async function updateAdmin(subscription, adminId) {
update.$set.manager_ids = [new ObjectId(adminId)]
}
await Subscription.updateOne(query, update).exec()
if (subscription.groupPlan) {
const previousAdminId = subscription.admin_id?.toString()
if (previousAdminId && previousAdminId !== adminId.toString()) {
await sendGroupRoleUserProperty(previousAdminId)
}
await sendGroupRoleUserProperty(adminId)
}
}
async function syncSubscription(
@@ -445,6 +453,30 @@ async function _sendUserGroupPlanCodeUserProperty(userId) {
}
}
async function sendGroupRoleUserProperty(userId) {
try {
const [memberSubscriptions, managedSubscriptions] = await Promise.all([
SubscriptionLocator.promises.getMemberSubscriptions(userId),
SubscriptionLocator.promises.getManagedGroupSubscriptions(userId),
])
const groupRole = CustomerIoPlanHelpers.getGroupRole(
memberSubscriptions,
managedSubscriptions,
userId
)
await Modules.promises.hooks.fire('setUserProperties', userId, {
group_role: groupRole,
})
} catch (error) {
logger.error(
{ err: error, userId },
'Failed to update group_role user property in customer.io'
)
}
}
async function handleExpiredSubscription(subscription, requesterData) {
const hasManagedUsersFeature =
Features.hasFeature('saas') && subscription?.managedUsersEnabled
@@ -547,6 +579,13 @@ async function transferSubscriptionOwnership(
update.$set.previousPaymentProvider = subscription.paymentProvider
}
await Subscription.updateOne(query, update).exec()
if (subscription.groupPlan) {
const previousAdminId = subscription.admin_id?.toString()
if (previousAdminId && previousAdminId !== adminId.toString()) {
await sendGroupRoleUserProperty(previousAdminId)
}
await sendGroupRoleUserProperty(adminId)
}
}
export default {
@@ -579,5 +618,6 @@ export default {
scheduleRefreshFeatures,
handleExpiredSubscription,
transferSubscriptionOwnership,
sendGroupRoleUserProperty,
},
}
@@ -8,6 +8,7 @@ import UserGetter from '../User/UserGetter.mjs'
import UserMembershipErrors from './UserMembershipErrors.mjs'
import Modules from '../../infrastructure/Modules.mjs'
import mongoose from '../../infrastructure/Mongoose.mjs'
import SubscriptionUpdater from '../Subscription/SubscriptionUpdater.mjs'
const { ObjectId } = mongodb
@@ -71,6 +72,14 @@ const UserMembershipHandler = {
}
await addUserToEntity(entity, attribute, user)
if (
entityConfig.modelName === 'Subscription' &&
attribute === 'manager_ids'
) {
await SubscriptionUpdater.promises.sendGroupRoleUserProperty(user._id)
}
return UserMembershipViewModel.build(user)
},
@@ -98,7 +107,16 @@ const UserMembershipHandler = {
await Modules.promises.hooks.fire('addGroupAuditLogEntry', auditLog)
}
return await removeUserFromEntity(entity, attribute, userId)
const result = await removeUserFromEntity(entity, attribute, userId)
if (
entityConfig.modelName === 'Subscription' &&
attribute === 'manager_ids'
) {
await SubscriptionUpdater.promises.sendGroupRoleUserProperty(userId)
}
return result
},
}