Quellcode durchsuchen

Merge pull request #30745 from overleaf/rh-cio-rollout-props

Expose additional user properties to customer.io

GitOrigin-RevId: 109c2dab13613d590ebcf70d685b7f4fb2e8f4af
roo hutton vor 6 Monaten
Ursprung
Commit
0315b79f9e

+ 29 - 22
services/web/app/src/Features/Project/ProjectListController.mjs

@@ -125,6 +125,7 @@ async function projectListPage(req, res, next) {
   let usersBestSubscription
   let usersIndividualSubscription
   let usersGroupSubscriptions = []
+  let usersManagedGroupSubscriptions = []
   let survey
   let userIsMemberOfGroupSubscription = false
   let groupSubscriptionsPendingEnrollment = []
@@ -186,6 +187,7 @@ async function projectListPage(req, res, next) {
         bestSubscription: usersBestSubscription,
         individualSubscription: usersIndividualSubscription,
         memberGroupSubscriptions: usersGroupSubscriptions,
+        managedGroupSubscriptions: usersManagedGroupSubscriptions,
       } = await SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails(
         { _id: userId }
       ))
@@ -195,20 +197,16 @@ async function projectListPage(req, res, next) {
         "Failed to get user's best subscription"
       )
     }
-    try {
-      userIsMemberOfGroupSubscription = usersGroupSubscriptions?.length > 0
 
-      // TODO use helper function
-      if (!user.enrollment?.managedBy) {
-        groupSubscriptionsPendingEnrollment = usersGroupSubscriptions.filter(
-          subscription =>
-            subscription.groupPlan && subscription.managedUsersEnabled
-        )
-      }
-    } catch (error) {
-      logger.error(
-        { err: error },
-        'Failed to check whether user is a member of group subscription'
+    userIsMemberOfGroupSubscription =
+      usersGroupSubscriptions.length > 0 ||
+      usersManagedGroupSubscriptions.length > 0
+
+    // TODO use helper function
+    if (!user.enrollment?.managedBy) {
+      groupSubscriptionsPendingEnrollment = usersGroupSubscriptions.filter(
+        subscription =>
+          subscription.groupPlan && subscription.managedUsersEnabled
       )
     }
 
@@ -447,14 +445,13 @@ async function projectListPage(req, res, next) {
   let showInrGeoBanner = false
   let showLATAMBanner = false
   let recommendedCurrency
+  const { countryCode, currencyCode } =
+    await GeoIpLookup.promises.getCurrencyCode(req.ip)
 
   if (
     usersBestSubscription?.type === 'free' ||
     usersBestSubscription?.type === 'standalone-ai-add-on'
   ) {
-    const { countryCode, currencyCode } =
-      await GeoIpLookup.promises.getCurrencyCode(req.ip)
-
     if (countryCode === 'IN') {
       showInrGeoBanner = true
     }
@@ -478,9 +475,9 @@ async function projectListPage(req, res, next) {
   }
 
   const affiliations = userAffiliations || []
-  const inEnterpriseCommons = affiliations.some(
-    affiliation => affiliation.institution?.enterpriseCommons
-  )
+  const commonsInstitution = affiliations.find(
+    affiliation => affiliation.institution?.commonsAccount
+  )?.institution?.name
 
   let onboardingDataCollection
   let subjectArea
@@ -493,10 +490,9 @@ async function projectListPage(req, res, next) {
   let customerIoEnabled = false
   const aiBlocked = !(await _canUseAIAssist(user))
   const hasAiAssist = await _userHasAIAssist(user)
-  if (!userIsMemberOfGroupSubscription && !inEnterpriseCommons && isSaas) {
+
+  if (!userIsMemberOfGroupSubscription && !commonsInstitution && isSaas) {
     try {
-      const ip = req.ip
-      const { countryCode } = await GeoIpLookup.promises.getCurrencyCode(ip)
       const excludedCountries = ['IN', 'CN']
 
       if (!excludedCountries.includes(countryCode)) {
@@ -548,6 +544,13 @@ async function projectListPage(req, res, next) {
     user
   )
 
+  const groupRole = userIsMemberOfGroupSubscription
+    ? usersManagedGroupSubscriptions?.length > 0 ||
+      usersGroupSubscriptions.some(sub => sub.userIsGroupManager)
+      ? 'admin'
+      : 'member'
+    : undefined
+
   res.render('project/list-react', {
     title: 'your_projects',
     usersBestSubscription,
@@ -595,6 +598,10 @@ async function projectListPage(req, res, next) {
     role,
     usedLatex,
     inactiveTutorials,
+    countryCode,
+    commonsInstitution,
+    groupRole,
+    isManagedUser: Boolean(user.enrollment?.managedBy),
   })
 }
 

+ 9 - 2
services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.mjs

@@ -393,16 +393,18 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') {
 
 /**
  * @param {{_id: string}} user
- * @returns {Promise<{bestSubscription:Subscription,individualSubscription:DBSubscription|null,memberGroupSubscriptions:DBSubscription[]}>}
+ * @returns {Promise<{bestSubscription:Subscription,individualSubscription:DBSubscription|null,memberGroupSubscriptions:DBSubscription[],managedGroupSubscriptions:DBSubscription[]}>}
  */
 async function getUsersSubscriptionDetails(user) {
   let [
     individualSubscription,
     memberGroupSubscriptions,
+    managedGroupSubscriptions,
     currentInstitutionsWithLicence,
   ] = await Promise.all([
     SubscriptionLocator.promises.getUsersSubscription(user),
     SubscriptionLocator.promises.getMemberSubscriptions(user),
+    SubscriptionLocator.promises.getManagedGroupSubscriptions(user),
     InstitutionsGetter.promises.getCurrentInstitutionsWithLicence(user._id),
   ])
   if (
@@ -483,7 +485,12 @@ async function getUsersSubscriptionDetails(user) {
       }
     }
   }
-  return { bestSubscription, individualSubscription, memberGroupSubscriptions }
+  return {
+    bestSubscription,
+    individualSubscription,
+    memberGroupSubscriptions,
+    managedGroupSubscriptions,
+  }
 }
 
 function buildPlansList(currentPlan, isInTrial) {

+ 13 - 1
services/web/app/views/_customer_io.pug

@@ -3,7 +3,7 @@ if(customerIoEnabled && ExposedSettings.cioWriteKey && ExposedSettings.cioSiteId
 		function boolAttr(value) {
 			return value !== undefined ? String(value) : null;
 		}
-	script(type="text/javascript", id="cio-loader", nonce=scriptNonce, data-best-subscription=(usersBestSubscription && usersBestSubscription.type), data-ai-blocked=boolAttr(aiBlocked), data-has-ai-assist=boolAttr(hasAiAssist), data-cio-write-key=ExposedSettings.cioWriteKey, data-cio-site-id=ExposedSettings.cioSiteId, data-session-analytics-id=getSessionAnalyticsId(), data-user-id=getLoggedInUserId(), data-last-active=lastActive, data-sign-up-date=signUpDate, data-subject-area=subjectArea, data-role=role, data-used-latex=usedLatex, data-primary-occupation=primaryOccupation).
+	script(type="text/javascript", id="cio-loader", nonce=scriptNonce, data-best-subscription=(usersBestSubscription && usersBestSubscription.type), data-ai-blocked=boolAttr(aiBlocked), data-has-ai-assist=boolAttr(hasAiAssist), data-cio-write-key=ExposedSettings.cioWriteKey, data-cio-site-id=ExposedSettings.cioSiteId, data-session-analytics-id=getSessionAnalyticsId(), data-user-id=getLoggedInUserId(), data-last-active=lastActive, data-sign-up-date=signUpDate, data-subject-area=subjectArea, data-role=role, data-used-latex=usedLatex, data-primary-occupation=primaryOccupation, data-country=countryCode, data-commons-institution=commonsInstitution, data-group-role=groupRole, data-features=user.features, data-is-managed-user=boolAttr(isManagedUser)).
 		
 		function parseBool(value) {
 			return value === 'true' ? true : value === 'false' ? false : undefined;
@@ -23,6 +23,13 @@ if(customerIoEnabled && ExposedSettings.cioWriteKey && ExposedSettings.cioSiteId
 		var role = cioSettings.role;
 		var primaryOccupation = cioSettings.primaryOccupation;
 		var usedLatex = cioSettings.usedLatex;
+		var features = cioSettings.features;
+
+		var countryCode = cioSettings.country;
+		var commonsInstitution = cioSettings.commonsInstitution;
+		var groupRole = cioSettings.groupRole;
+		var isManagedUser = parseBool(cioSettings.isManagedUser);
+
 
 
 		!function(){var i="cioanalytics", analytics=(window[i]=window[i]||[]);if(!analytics.initialize)if(analytics.invoked)window.console&&console.error&&console.error("Snippet included twice.");else{analytics.invoked=!0;analytics.methods=["trackSubmit","trackClick","trackLink","trackForm","pageview","identify","reset","group","track","ready","alias","debug","page","once","off","on","addSourceMiddleware","addIntegrationMiddleware","setAnonymousId","addDestinationMiddleware"];analytics.factory=function(e){return function(){var t=Array.prototype.slice.call(arguments);t.unshift(e);analytics.push(t);return analytics}};for(var e=0;e<analytics.methods.length;e++){var key=analytics.methods[e];analytics[key]=analytics.factory(key)}analytics.load=function(key,e){var t=document.createElement("script");t.type="text/javascript";t.async=!0;t.setAttribute('data-global-customerio-analytics-key', i);t.src="https://cdp.customer.io/v1/analytics-js/snippet/" + key + "/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(t,n);analytics._writeKey=key;analytics._loadOptions=e};analytics.SNIPPET_VERSION="4.15.3";
@@ -53,6 +60,11 @@ if(customerIoEnabled && ExposedSettings.cioWriteKey && ExposedSettings.cioSiteId
 			addIfDefined(identifyData, 'role', role);
 			addIfDefined(identifyData, 'primaryOccupation', primaryOccupation);
 			addIfDefined(identifyData, 'usedLatex', usedLatex);
+			addIfDefined(identifyData, 'country', countryCode);
+			addIfDefined(identifyData, 'commonsInstitution', commonsInstitution);
+			addIfDefined(identifyData, 'groupRole', groupRole);
+			addIfDefined(identifyData, 'isManagedUser', isManagedUser);
+			addIfDefined(identifyData, 'features', features);
 			
 			analytics.identify(analyticsId, identifyData);
 		}}();

+ 6 - 1
services/web/test/unit/src/Project/ProjectListController.test.mjs

@@ -148,6 +148,7 @@ describe('ProjectListController', function () {
           bestSubscription: { type: 'free' },
           individualSubscription: null,
           memberGroupSubscriptions: [],
+          managedGroupSubscriptions: [],
         }),
       },
     }
@@ -472,6 +473,8 @@ describe('ProjectListController', function () {
       ctx.Features.hasFeature.withArgs('saas').returns(true)
       ctx.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves(
         {
+          memberGroupSubscriptions: [],
+          managedGroupSubscriptions: [],
           bestSubscription: {
             type: 'free',
           },
@@ -491,6 +494,8 @@ describe('ProjectListController', function () {
       ctx.Features.hasFeature.withArgs('saas').returns(true)
       ctx.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves(
         {
+          memberGroupSubscriptions: [],
+          managedGroupSubscriptions: [],
           bestSubscription: {
             type: 'individual',
           },
@@ -897,7 +902,7 @@ describe('ProjectListController', function () {
       beforeEach(function (ctx) {
         ctx.Features.hasFeature.withArgs('saas').returns(true)
         ctx.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves(
-          { memberGroupSubscriptions: [] }
+          { memberGroupSubscriptions: [], managedGroupSubscriptions: [] }
         )
         ctx.UserGetter.promises.getUserFullEmails.resolves([
           {

+ 1 - 0
services/web/test/unit/src/Subscription/SubscriptionViewModelBuilder.test.mjs

@@ -113,6 +113,7 @@ describe('SubscriptionViewModelBuilder', function () {
       promises: {
         getUsersSubscription: sinon.stub().resolves(),
         getMemberSubscriptions: sinon.stub().resolves(),
+        getManagedGroupSubscriptions: sinon.stub().resolves([]),
       },
       getUsersSubscription: sinon.stub().yields(),
       getMemberSubscriptions: sinon.stub().yields(null, []),