Skip to content

Commit

Permalink
chore: Restrict prompt to join org to a smaller set of orgs (#9265)
Browse files Browse the repository at this point in the history
Prefer higher tiers and bigger over smaller orgs.
  • Loading branch information
Dschoordsch authored Dec 11, 2023
1 parent fb34aa1 commit 8cbc121
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 6 deletions.
253 changes: 249 additions & 4 deletions packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ type TestOrganizationUser = Pick<
const addOrg = async (
activeDomain: string | null,
members: TestOrganizationUser[],
featureFlags?: string[]
rest?: {featureFlags?: string[]; tier?: string}
) => {
const {featureFlags, tier} = rest ?? {}
const orgId = generateUID()
const org = {
id: orgId,
activeDomain,
featureFlags: featureFlags ?? []
featureFlags: featureFlags ?? [],
tier: tier ?? 'starter'
}

const orgUsers = members.map((member) => ({
Expand Down Expand Up @@ -143,7 +145,7 @@ test('Org with noPromptToJoinOrg feature flag is ignored', async () => {
userId: 'user2'
}
],
['noPromptToJoinOrg']
{featureFlags: ['noPromptToJoinOrg']}
)

const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader)
Expand Down Expand Up @@ -285,7 +287,84 @@ test('Org matching the user are ignored', async () => {
expect(userLoader.loadMany).toHaveBeenCalledTimes(0)
})

test('All orgs with verified emails qualify', async () => {
test('Only the biggest org with verified emails qualify', async () => {
const org = await addOrg('parabol.co', [
{
joinedAt: new Date('2023-09-06'),
userId: 'founder1'
},
{
joinedAt: new Date('2023-09-07'),
userId: 'member1'
}
])
const biggerOrg = await addOrg('parabol.co', [
{
joinedAt: new Date('2023-09-06'),
userId: 'founder2'
},
{
joinedAt: new Date('2023-09-07'),
userId: 'member2'
},
{
joinedAt: new Date('2023-09-07'),
userId: 'member3'
}
])
await addOrg('parabol.co', [
{
joinedAt: new Date('2023-09-06'),
userId: 'founder3'
},
{
joinedAt: new Date('2023-09-07'),
userId: 'member3'
}
])

userLoader.loadMany.mockImplementation((userIds) => {
const users = {
founder1: {
email: 'user1@parabol.co',
identities: [
{
isEmailVerified: true
}
]
},
founder2: {
email: 'user2@parabol.co',
identities: [
{
isEmailVerified: true
}
]
},
founder3: {
email: 'user3@parabol.co',
identities: [
{
isEmailVerified: false
}
]
}
}
return userIds.map((id) => ({
id,
...users[id]
}))
})

const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader)
expect(userLoader.loadMany).toHaveBeenCalledTimes(3)
expect(userLoader.loadMany).toHaveBeenCalledWith(['founder1'])
expect(userLoader.loadMany).toHaveBeenCalledWith(['founder2'])
expect(userLoader.loadMany).toHaveBeenCalledWith(['founder3'])
expect(orgIds).toIncludeSameMembers([biggerOrg])
})

test('All the biggest orgs with verified emails qualify', async () => {
const org1 = await addOrg('parabol.co', [
{
joinedAt: new Date('2023-09-06'),
Expand Down Expand Up @@ -358,6 +437,172 @@ test('All orgs with verified emails qualify', async () => {
expect(orgIds).toIncludeSameMembers([org1, org2])
})

test('Team trumps starter tier with more users org', async () => {
const teamOrg = await addOrg(
'parabol.co',
[
{
joinedAt: new Date('2023-09-06'),
userId: 'founder1'
},
{
joinedAt: new Date('2023-09-07'),
userId: 'member1'
}
],
{tier: 'team'}
)
const biggerStarterOrg = await addOrg('parabol.co', [
{
joinedAt: new Date('2023-09-06'),
userId: 'founder2'
},
{
joinedAt: new Date('2023-09-07'),
userId: 'member2'
},
{
joinedAt: new Date('2023-09-07'),
userId: 'member3'
}
])
await addOrg('parabol.co', [
{
joinedAt: new Date('2023-09-06'),
userId: 'founder3'
},
{
joinedAt: new Date('2023-09-07'),
userId: 'member3'
}
])

userLoader.loadMany.mockImplementation((userIds) => {
const users = {
founder1: {
email: 'user1@parabol.co',
identities: [
{
isEmailVerified: true
}
]
},
founder2: {
email: 'user2@parabol.co',
identities: [
{
isEmailVerified: true
}
]
},
founder3: {
email: 'user3@parabol.co',
identities: [
{
isEmailVerified: false
}
]
}
}
return userIds.map((id) => ({
id,
...users[id]
}))
})

const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader)
expect(userLoader.loadMany).toHaveBeenCalledTimes(3)
expect(userLoader.loadMany).toHaveBeenCalledWith(['founder1'])
expect(userLoader.loadMany).toHaveBeenCalledWith(['founder2'])
expect(userLoader.loadMany).toHaveBeenCalledWith(['founder3'])
expect(orgIds).toIncludeSameMembers([teamOrg])
})

test('Enterprise trumps team tier with more users org', async () => {
const enterpriseOrg = await addOrg(
'parabol.co',
[
{
joinedAt: new Date('2023-09-06'),
userId: 'founder1'
},
{
joinedAt: new Date('2023-09-07'),
userId: 'member1'
}
],
{tier: 'enterprise'}
)
const starterOrg = await addOrg(
'parabol.co',
[
{
joinedAt: new Date('2023-09-06'),
userId: 'founder2'
},
{
joinedAt: new Date('2023-09-07'),
userId: 'member2'
},
{
joinedAt: new Date('2023-09-07'),
userId: 'member3'
}
],
{tier: 'team'}
)
await addOrg('parabol.co', [
{
joinedAt: new Date('2023-09-06'),
userId: 'founder3'
},
{
joinedAt: new Date('2023-09-07'),
userId: 'member3'
}
])

userLoader.loadMany.mockImplementation((userIds) => {
const users = {
founder1: {
email: 'user1@parabol.co',
identities: [
{
isEmailVerified: true
}
]
},
founder2: {
email: 'user2@parabol.co',
identities: [
{
isEmailVerified: true
}
]
},
founder3: {
email: 'user3@parabol.co',
identities: [
{
isEmailVerified: false
}
]
}
}
return userIds.map((id) => ({
id,
...users[id]
}))
})

const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader)
expect(userLoader.loadMany).toHaveBeenCalledTimes(3)
expect(userLoader.loadMany).toHaveBeenCalledWith(['founder1'])
expect(userLoader.loadMany).toHaveBeenCalledWith(['founder2'])
expect(userLoader.loadMany).toHaveBeenCalledWith(['founder3'])
expect(orgIds).toIncludeSameMembers([enterpriseOrg])
})

test('Orgs with verified emails from different domains do not qualify', async () => {
const org1 = await addOrg('parabol.co', [
{
Expand Down
31 changes: 29 additions & 2 deletions packages/server/utils/isRequestToJoinDomainAllowed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import User from '../database/types/User'
import {DataLoaderWorker} from '../graphql/graphql'
import isValid from '../graphql/isValid'
import TeamMember from '../database/types/TeamMember'
import Organization from '../database/types/Organization'

export const getEligibleOrgIdsByDomain = async (
activeDomain: string,
Expand Down Expand Up @@ -41,7 +42,8 @@ export const getEligibleOrgIdsByDomain = async (
)
.run()

const eligibleOrgs = await Promise.all(
type OrgWithActiveMembers = Organization & {activeMembers: number}
const eligibleOrgs = (await Promise.all(
orgs.map(async (org) => {
const {founder} = org
const importantMembers = org.billingLeads.slice() as TeamMember[]
Expand All @@ -64,8 +66,33 @@ export const getEligibleOrgIdsByDomain = async (
}
return org
})
)) as OrgWithActiveMembers[]

const highestTierOrgs = eligibleOrgs.filter(isValid).reduce((acc, org) => {
if (acc.length === 0) {
return [org]
}
const highestTier = acc[0]!.tier
if (org.tier === highestTier) {
return [...acc, org]
}
if (org.tier === 'enterprise') {
return [org]
}
if (highestTier === 'starter' && org.tier === 'team') {
return [org]
}
return acc
}, [] as OrgWithActiveMembers[])

const biggestSize = highestTierOrgs.reduce(
(acc, org) => (org.activeMembers > acc ? org.activeMembers : acc),
0
)
return eligibleOrgs.filter(isValid).map(({id}) => id)

return highestTierOrgs
.filter(({activeMembers}) => activeMembers === biggestSize)
.map(({id}) => id)
}

const isRequestToJoinDomainAllowed = async (
Expand Down

0 comments on commit 8cbc121

Please sign in to comment.