New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
refactor: organization creation handler #14911
base: main
Are you sure you want to change the base?
Conversation
Thank you for following the naming conventions! 🙏 Feel free to join our discord and post your PR link. |
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 3 Ignored Deployments
|
Graphite Automations"Add platform team as reviewer" took an action on this PR • (05/07/24)1 reviewer was added to this PR based on Keith Williams's automation. |
📦 Next.js Bundle Analysis for @calcom/webThis analysis was generated by the Next.js Bundle Analysis action. 🤖 Three Pages Changed SizeThe following pages changed size from the code in this PR compared to its base branch:
DetailsOnly the gzipped size is provided here based on an expert tip. First Load is the size of the global bundle plus the bundle for the individual page. If a user were to show up to your website and land on a given page, the first load size represents the amount of javascript that user would need to download. If Any third party scripts you have added directly to your app using the The "Budget %" column shows what percentage of your performance budget the First Load total takes up. For example, if your budget was 100kb, and a given page's first load size was 10kb, it would be 10% of your budget. You can also see how much this has increased or decreased compared to the base branch of your PR. If this percentage has increased by 20% or more, there will be a red status indicator applied, indicating that special attention should be given to this. If you see "+/- <0.01%" it means that there was a change in bundle size, but it is a trivial enough amount that it can be ignored. |
Current Playwright Test Results Summary✅ 310 Passing - ❌ 5 Failing - Run may still be in progress, this comment will be updated as current testing workflow or job completes... (Last updated on 05/17/2024 08:23:57am UTC) Run DetailsRunning Workflow PR Update on Github Actions Commit: bee7ff3 Started: 05/17/2024 08:20:29am UTC ❌ Failures📄 apps/web/playwright/profile.e2e.ts • 2 FailuresTest Case Results
📄 apps/web/playwright/webhook.e2e.ts • 1 FailureTest Case Results
📄 apps/web/playwright/team/team-invitation.e2e.ts • 1 FailureTest Case Results
📄 apps/web/playwright/organization/organization-creation.e2e.ts • 1 FailureTest Case Results
|
Test Case | Last 7 days Failures | Last 7 days Flakes |
---|---|---|
Stripe integration Paid booking should be able to be rescheduled
Retry 1 • Initial Attempt |
3.03% (6)6 / 198 runsfailed over last 7 days |
14.14% (28)28 / 198 runsflaked over last 7 days |
📄 apps/web/playwright/event-types.e2e.ts • 2 Flakes
Top 1 Common Error Messages
|
2 Test Cases Affected |
Test Case Results
Test Case | Last 7 days Failures | Last 7 days Flakes |
---|---|---|
Event Types tests -- legacy user Different Locations Tests Can remove location from multiple locations that are saved
Retry 2 • Retry 1 • Initial Attempt |
3.72% (8)8 / 215 runsfailed over last 7 days |
19.53% (42)42 / 215 runsflaked over last 7 days |
Event Types tests -- legacy user Different Locations Tests can add single organizer address location without display location public option
Retry 1 • Initial Attempt |
0% (0)0 / 216 runsfailed over last 7 days |
5.09% (11)11 / 216 runsflaked over last 7 days |
📄 apps/web/playwright/teams.e2e.ts • 1 Flake
Test Case Results
Test Case | Last 7 days Failures | Last 7 days Flakes |
---|---|---|
Teams - NonOrg -- legacy Can create a booking for Round Robin EventType
Retry 1 • Initial Attempt |
7.11% (16)16 / 225 runsfailed over last 7 days |
28.89% (65)65 / 225 runsflaked over last 7 days |
📄 apps/web/playwright/team/team-invitation.e2e.ts • 1 Flake
Test Case Results
Test Case | Last 7 days Failures | Last 7 days Flakes |
---|---|---|
Team Invitation (verified)
Retry 2 • Retry 1 • Initial Attempt |
3.49% (8)8 / 229 runsfailed over last 7 days |
6.99% (16)16 / 229 runsflaked over last 7 days |
📄 packages/embeds/embed-core/playwright/tests/namespacing.e2e.ts • 4 Flakes
Top 1 Common Error Messages
|
4 Test Cases Affected |
Test Case Results
Test Case | Last 7 days Failures | Last 7 days Flakes |
---|---|---|
Namespacing Inline Embed Add inline embed using a namespace without reload
Retry 1 • Initial Attempt |
0.47% (1)1 / 213 runfailed over last 7 days |
60.56% (129)129 / 213 runsflaked over last 7 days |
Namespacing Inline Embed Double install Embed Snippet with inline embed using a namespace
Retry 1 • Initial Attempt |
0.47% (1)1 / 213 runfailed over last 7 days |
59.62% (127)127 / 213 runsflaked over last 7 days |
Namespacing Inline Embed Double install Embed Snippet with inline embed without a namespace(i.e. default namespace)
Retry 1 • Initial Attempt |
0% (0)0 / 213 runsfailed over last 7 days |
63.38% (135)135 / 213 runsflaked over last 7 days |
Namespacing Different namespaces can have different init configs
Retry 1 • Initial Attempt |
0% (0)0 / 211 runsfailed over last 7 days |
60.19% (127)127 / 211 runsflaked over last 7 days |
📄 apps/web/playwright/profile.e2e.ts • 1 Flake
Test Case Results
Test Case | Last 7 days Failures | Last 7 days Flakes |
---|---|---|
Update Profile Newly added secondary email should show as Unverified
Retry 1 • Initial Attempt |
2.06% (5)5 / 243 runsfailed over last 7 days |
26.75% (65)65 / 243 runsflaked over last 7 days |
📄 packages/app-store/routing-forms/playwright/tests/basic.e2e.ts • 1 Flake
Test Case Results
Test Case | Last 7 days Failures | Last 7 days Flakes |
---|---|---|
Routing Forms Seeded Routing Form Router URL should work
Retry 1 • Initial Attempt |
0.47% (1)1 / 215 runfailed over last 7 days |
15.35% (33)33 / 215 runsflaked over last 7 days |
📄 apps/web/playwright/organization/booking.e2e.ts • 1 Flake
Test Case Results
Test Case | Last 7 days Failures | Last 7 days Flakes |
---|---|---|
Bookings Team Event Can create a booking for Round Robin EventType
Retry 1 • Initial Attempt |
8.11% (18)18 / 222 runsfailed over last 7 days |
26.58% (59)59 / 222 runsflaked over last 7 days |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this is in draft so feel free to tell me to shut up
few thoughts i had when i scanned through this
@@ -38,6 +47,93 @@ const getIPAddress = async (url: string): Promise<string> => { | |||
}); | |||
}; | |||
|
|||
const checkUserIsAdmin = ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we rename this to something a bit more descriptive - In my eyes checkUserISAdmin would just return a true or false but this is doing a bit more than just checking if a user is admin as it throws also
const checkUserIsAdmin = ( | |
const checkUserIsAdminOrThrow = ( |
const publishedTeams = teams.filter((team) => !!team.team.slug); | ||
if (!isAdmin && publishedTeams.length < ORG_MINIMUM_PUBLISHED_TEAMS_SELF_SERVE) { | ||
throw new TRPCError({ code: "FORBIDDEN", message: "You need to have atleast two published teams." }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Lets make this a little easier to figure out whats going on
const publishedTeams = teams.filter((team) => !!team.team.slug); | |
if (!isAdmin && publishedTeams.length < ORG_MINIMUM_PUBLISHED_TEAMS_SELF_SERVE) { | |
throw new TRPCError({ code: "FORBIDDEN", message: "You need to have atleast two published teams." }); | |
if(isAdmin) return; | |
const publishedTeams = teams.filter((team) => !!team.team.slug); | |
if (publishedTeams.length < ORG_MINIMUM_PUBLISHED_TEAMS_SELF_SERVE) { | |
throw new TRPCError({ code: "FORBIDDEN", message: "You need to have atleast two published teams." }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, this is actually much much better
packages/trpc/server/routers/viewer/organizations/create.handler.ts
Outdated
Show resolved
Hide resolved
!orgData.isPlatform && | ||
(await sendEmailVerification({ | ||
email: orgOwnerEmail, | ||
language: userLocale, | ||
username: ownerProfile.username || "", | ||
})); | ||
|
||
const user = await sendEmailAndEnrichProfile({ | ||
org: { | ||
owner: organizationOwner, | ||
id: organization.id, | ||
name: organization.name, | ||
ownerEmail: orgOwnerEmail, | ||
}, | ||
slug: orgData.slug, | ||
userName: userName, | ||
ownerProfileUsername: ownerProfile.username, | ||
nonOrgUsernameForOwner: null, | ||
inputLanguageTranslation: translation, | ||
isPlatform: orgData.isPlatform, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a fan of using isPlatform here and also inside of sendEmailAndEnrichProfile passed as a parameter, we should try to extract those functions and call them directly in the createOrgUser/Platform functions
return !orgOwner | ||
? persistOrganizationWithNonExistentOwner({ | ||
orgData: orgData, | ||
orgOwner: orgOwner, | ||
orgOwnerEmail: orgOwnerEmail, | ||
userLocale: userLocale, | ||
userName: userName, | ||
translation: inputLanguageTranslation, | ||
}) | ||
: persistOrganizationWithExistingUserAsOwner({ | ||
orgData: orgData, | ||
orgOwner: orgOwner, | ||
orgOwnerEmail: orgOwnerEmail, | ||
userName: userName, | ||
existingOwnerDetails: { | ||
ownerId: orgOwner.id, | ||
orgId: orgId, | ||
ownerUsername: orgOwner.username, | ||
}, | ||
translation: inputLanguageTranslation, | ||
loggedInUserId: loggedInUser.id, | ||
availability: availability, | ||
}); | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return !orgOwner | |
? persistOrganizationWithNonExistentOwner({ | |
orgData: orgData, | |
orgOwner: orgOwner, | |
orgOwnerEmail: orgOwnerEmail, | |
userLocale: userLocale, | |
userName: userName, | |
translation: inputLanguageTranslation, | |
}) | |
: persistOrganizationWithExistingUserAsOwner({ | |
orgData: orgData, | |
orgOwner: orgOwner, | |
orgOwnerEmail: orgOwnerEmail, | |
userName: userName, | |
existingOwnerDetails: { | |
ownerId: orgOwner.id, | |
orgId: orgId, | |
ownerUsername: orgOwner.username, | |
}, | |
translation: inputLanguageTranslation, | |
loggedInUserId: loggedInUser.id, | |
availability: availability, | |
}); | |
}; | |
const persistParams = { | |
orgData: orgData, | |
orgOwner: orgOwner, | |
orgOwnerEmail: orgOwnerEmail, | |
userName: userName, | |
existingOwnerDetails: { | |
ownerId: orgOwner.id, | |
orgId: orgId, | |
ownerUsername: orgOwner.username, | |
}, | |
translation: inputLanguageTranslation, | |
loggedInUserId: loggedInUser.id, | |
availability: availability, | |
}; | |
return !orgOwner | |
? persistOrganizationWithNonExistentOwner(persistParams) | |
: persistOrganizationWithExistingUserAsOwner(persistParams); |
const user = isPlatform | ||
? await createPlatformUser({ | ||
userId: ctx.user.id, | ||
name: name, | ||
slug: slug, | ||
orgOwnerEmail: orgOwnerEmail, | ||
seats: seats, | ||
pricePerSeat: pricePerSeat, | ||
isPlatform: isPlatform, | ||
inputLanguageTranslation: inputLanguageTranslation, | ||
availability: availability, | ||
autoAcceptEmail: autoAcceptEmail, | ||
ctx: { userLocale: ctx.user.locale, userName: ctx.user.name, orgId: ctx.user.profile.organizationId }, | ||
}) | ||
: await createOrgUser({ | ||
userId: ctx.user.id, | ||
name: name, | ||
slug: slug, | ||
orgOwnerEmail: orgOwnerEmail, | ||
seats: seats, | ||
pricePerSeat: pricePerSeat, | ||
isPlatform: isPlatform, | ||
inputLanguageTranslation: inputLanguageTranslation, | ||
availability: availability, | ||
autoAcceptEmail: autoAcceptEmail, | ||
ctx: { userLocale: ctx.user.locale, userName: ctx.user.name, orgId: ctx.user.profile.organizationId }, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const user = isPlatform | |
? await createPlatformUser({ | |
userId: ctx.user.id, | |
name: name, | |
slug: slug, | |
orgOwnerEmail: orgOwnerEmail, | |
seats: seats, | |
pricePerSeat: pricePerSeat, | |
isPlatform: isPlatform, | |
inputLanguageTranslation: inputLanguageTranslation, | |
availability: availability, | |
autoAcceptEmail: autoAcceptEmail, | |
ctx: { userLocale: ctx.user.locale, userName: ctx.user.name, orgId: ctx.user.profile.organizationId }, | |
}) | |
: await createOrgUser({ | |
userId: ctx.user.id, | |
name: name, | |
slug: slug, | |
orgOwnerEmail: orgOwnerEmail, | |
seats: seats, | |
pricePerSeat: pricePerSeat, | |
isPlatform: isPlatform, | |
inputLanguageTranslation: inputLanguageTranslation, | |
availability: availability, | |
autoAcceptEmail: autoAcceptEmail, | |
ctx: { userLocale: ctx.user.locale, userName: ctx.user.name, orgId: ctx.user.profile.organizationId }, | |
}); | |
const createUserParams = { | |
userId: ctx.user.id, | |
name: name, | |
slug: slug, | |
orgOwnerEmail: orgOwnerEmail, | |
seats: seats, | |
pricePerSeat: pricePerSeat, | |
isPlatform: isPlatform, | |
inputLanguageTranslation: inputLanguageTranslation, | |
availability: availability, | |
autoAcceptEmail: autoAcceptEmail, | |
ctx: { userLocale: ctx.user.locale, userName: ctx.user.name, orgId: ctx.user.profile.organizationId }, | |
} | |
const user = isPlatform | |
? await createPlatformUser(createUserParams) | |
: await createOrgUser(createUserParams); |
userName, | ||
translation, | ||
}: PersistOrganizationWithNonExistentOwnerProps) => { | ||
let organizationOwner = orgOwner; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure to understand why we need to do this, organizationOwner is fully replaced with data.orgOwner,
the orgOwner passed as a param of this function does not seem to be used at all really
What does this PR do?
This PR refactors the create handler that we use for organizations. We use the same handler in order to create a platform user as well, thats why now we have two different handlers, one that handles the creation of a platform user i.e.
createPlatformUser
and the other one that handles the creation of an org user i.e.createOrgUser
. I've tried to split everything else in smaller and more meaningful functions for better readability and understanding of what piece does what.