Skip to content

feat: enable attribute-based assignment for managed event types #22989

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

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: enable attribute-based assignment for managed event types
- Remove explicit disabling of segment functionality in handleChildrenEventTypes.ts
- Add segment assignment fields to unlocked managed event type properties
- Export MembersSegmentWithToggle component for reuse
- Include segment fields in managed event metadata initialization
- Add segment assignment UI to managed event types when 'Assign all team members' is enabled
- Update teams/index.ts to handle segment assignment for managed events

This allows managed event types to use the same attribute-based user assignment
functionality that was previously only available for round robin events.

Co-Authored-By: joe@cal.com <j.auyeung419@gmail.com>
  • Loading branch information
devin-ai-integration[bot] and joeauyeung committed Aug 8, 2025
commit 81d0ca30e9af8807810223a32125b58f2cab3b98
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,11 @@ export default async function handleChildrenEventTypes({
newUserIds.map((userId) => {
return prisma.eventType.create({
data: {
instantMeetingScheduleId: eventType.instantMeetingScheduleId ?? undefined,
profileId: profileId ?? null,
...managedEventTypeValues,
...unlockedEventTypeValues,
title: eventType.title,
slug: eventType.slug,
length: eventType.length,
bookingLimits:
(managedEventTypeValues.bookingLimits as unknown as Prisma.InputJsonObject) ?? undefined,
recurringEvent:
Expand All @@ -173,22 +174,19 @@ export default async function handleChildrenEventTypes({
durationLimits: (managedEventTypeValues.durationLimits as Prisma.InputJsonValue) ?? undefined,
eventTypeColor: (managedEventTypeValues.eventTypeColor as Prisma.InputJsonValue) ?? undefined,
onlyShowFirstAvailableSlot: managedEventTypeValues.onlyShowFirstAvailableSlot ?? false,
userId,
users: {
connect: [{ id: userId }],
},
parentId,
hidden: children?.find((ch) => ch.owner.id === userId)?.hidden ?? false,
workflows: currentWorkflowIds && {
create: currentWorkflowIds.map((wfId) => ({ workflowId: wfId })),
},
/**
* RR Segment isn't applicable for managed event types.
* RR Segment is now supported for managed event types.
*/
rrSegmentQueryValue: undefined,
assignRRMembersUsingSegment: false,
rrSegmentQueryValue: (eventType.rrSegmentQueryValue as Prisma.InputJsonValue) ?? undefined,
assignRRMembersUsingSegment: eventType.assignRRMembersUsingSegment ?? false,
useEventLevelSelectedCalendars: false,
restrictionScheduleId: null,
useBookerTimezone: false,
allowReschedulingCancelledBookings:
managedEventTypeValues.allowReschedulingCancelledBookings ?? false,
Expand Down Expand Up @@ -219,8 +217,7 @@ export default async function handleChildrenEventTypes({
: key === "beforeBufferTime"
? "beforeEventBuffer"
: key;
// @ts-expect-error Element implicitly has any type
acc[filteredKey] = true;
(acc as any)[filteredKey] = true;
return acc;
}, {});

Expand Down Expand Up @@ -257,8 +254,6 @@ export default async function handleChildrenEventTypes({
data: {
...updatePayloadFiltered,
hidden: children?.find((ch) => ch.owner.id === userId)?.hidden ?? false,
...("schedule" in unlockedFieldProps ? {} : { scheduleId: eventType.scheduleId || null }),
restrictionScheduleId: null,
useBookerTimezone: false,
hashedLink:
"multiplePrivateLinks" in unlockedFieldProps
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -368,4 +368,5 @@ const AddMembersWithSwitchWrapper = ({
);
};

export { MembersSegmentWithToggle };
export default AddMembersWithSwitchWrapper;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { v4 as uuidv4 } from "uuid";
import type { AddMembersWithSwitchCustomClassNames } from "@calcom/features/eventtypes/components/AddMembersWithSwitch";
import AddMembersWithSwitch, {
mapUserToValue,
MembersSegmentWithToggle,
} from "@calcom/features/eventtypes/components/AddMembersWithSwitch";
import AssignAllTeamMembers from "@calcom/features/eventtypes/components/AssignAllTeamMembers";
import type { ChildrenEventTypeSelectCustomClassNames } from "@calcom/features/eventtypes/components/ChildrenEventTypeSelect";
Expand Down Expand Up @@ -948,12 +949,31 @@ export const EventTeamAssignmentTab = ({
</>
)}
{team && isManagedEventType && (
<ChildrenEventTypes
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
childrenEventTypeOptions={childrenEventTypeOptions}
customClassNames={customClassNames?.childrenEventTypes}
/>
<>
<ChildrenEventTypes
assignAllTeamMembers={assignAllTeamMembers}
setAssignAllTeamMembers={setAssignAllTeamMembers}
childrenEventTypeOptions={childrenEventTypeOptions}
customClassNames={customClassNames?.childrenEventTypes}
/>
{assignAllTeamMembers && isSegmentApplicable && (
<div className="border-subtle mt-6 space-y-5 rounded-lg border px-4 py-6 sm:px-6">
<div className="flex flex-col gap-4">
<MembersSegmentWithToggle
teamId={team.id}
assignRRMembersUsingSegment={getValues("assignRRMembersUsingSegment")}
setAssignRRMembersUsingSegment={(value) =>
setValue("assignRRMembersUsingSegment", value, { shouldDirty: true })
}
rrSegmentQueryValue={getValues("rrSegmentQueryValue")}
setRrSegmentQueryValue={(value) =>
setValue("rrSegmentQueryValue", value, { shouldDirty: true })
}
/>
</div>
</div>
)}
</>
)}
</div>
);
Expand Down
6 changes: 5 additions & 1 deletion packages/lib/hooks/useCreateEventType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ export const useCreateEventTypeForm = () => {

useEffect(() => {
if (isManagedEventType) {
form.setValue("metadata.managedEventConfig.unlockedFields", unlockedManagedEventTypeProps);
form.setValue("metadata.managedEventConfig.unlockedFields", {
...unlockedManagedEventTypeProps,
assignRRMembersUsingSegment: true,
rrSegmentQueryValue: true,
});
} else {
form.setValue("metadata", null);
}
Expand Down
8 changes: 5 additions & 3 deletions packages/lib/server/queries/teams/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -459,21 +459,23 @@ export function generateNewChildEventTypeDataForDB({
return {
...managedEventTypeValues,
...unlockedEventTypeValues,
title: eventType.title,
slug: eventType.slug,
length: eventType.length,
bookingLimits: (managedEventTypeValues.bookingLimits as unknown as Prisma.InputJsonObject) ?? undefined,
recurringEvent: (managedEventTypeValues.recurringEvent as unknown as Prisma.InputJsonValue) ?? undefined,
metadata: (managedEventTypeValues.metadata as Prisma.InputJsonValue) ?? undefined,
bookingFields: (managedEventTypeValues.bookingFields as Prisma.InputJsonValue) ?? undefined,
durationLimits: (managedEventTypeValues.durationLimits as Prisma.InputJsonValue) ?? undefined,
eventTypeColor: (managedEventTypeValues.eventTypeColor as Prisma.InputJsonValue) ?? undefined,
rrSegmentQueryValue: (managedEventTypeValues.rrSegmentQueryValue as Prisma.InputJsonValue) ?? undefined,
rrSegmentQueryValue: (eventType.rrSegmentQueryValue as Prisma.InputJsonValue) ?? undefined,
assignRRMembersUsingSegment: eventType.assignRRMembersUsingSegment ?? false,
onlyShowFirstAvailableSlot: managedEventTypeValues.onlyShowFirstAvailableSlot ?? false,
userId,
...(includeUserConnect && {
users: {
connect: [{ id: userId }],
},
}),
parentId: eventType.id,
hidden: false,
...(includeWorkflow && {
workflows: currentWorkflowIds && {
Expand Down
21 changes: 18 additions & 3 deletions packages/prisma/zod-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -613,7 +613,20 @@ export const downloadLinkSchema = z.object({
});

// All properties within event type that can and will be updated if needed
export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect, "id">]: true } = {
export const allManagedEventTypeProps: {
[k in keyof Omit<
Prisma.EventTypeSelect,
| "id"
| "userId"
| "parentId"
| "teamId"
| "profileId"
| "scheduleId"
| "instantMeetingScheduleId"
| "secondaryEmailId"
| "restrictionScheduleId"
>]: true;
} = {
title: true,
description: true,
interfaceLanguage: true,
Expand Down Expand Up @@ -664,7 +677,6 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect
bookingLimits: true,
onlyShowFirstAvailableSlot: true,
slotInterval: true,
scheduleId: true,
workflows: true,
bookingFields: true,
durationLimits: true,
Expand All @@ -674,6 +686,8 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect
lockedTimeZone: true,
requiresBookerEmailVerification: true,
assignAllTeamMembers: true,
assignRRMembersUsingSegment: true,
rrSegmentQueryValue: true,
isRRWeightsEnabled: true,
eventTypeColor: true,
allowReschedulingPastBookings: true,
Expand All @@ -687,8 +701,9 @@ export const allManagedEventTypeProps: { [k in keyof Omit<Prisma.EventTypeSelect
// Eventually this is going to be just a default and the user can change the config through the UI
export const unlockedManagedEventTypeProps = {
locations: allManagedEventTypeProps.locations,
scheduleId: allManagedEventTypeProps.scheduleId,
destinationCalendar: allManagedEventTypeProps.destinationCalendar,
assignRRMembersUsingSegment: allManagedEventTypeProps.assignRRMembersUsingSegment,
rrSegmentQueryValue: allManagedEventTypeProps.rrSegmentQueryValue,
};

export const emailSchema = emailRegexSchema;
Expand Down
Loading