Skip to content

feat: add hidden event toggle support for managed events#29752

Open
Sayan8945 wants to merge 1 commit into
calcom:mainfrom
Sayan8945:feat/managed-event-hidden-toggle
Open

feat: add hidden event toggle support for managed events#29752
Sayan8945 wants to merge 1 commit into
calcom:mainfrom
Sayan8945:feat/managed-event-hidden-toggle

Conversation

@Sayan8945

Copy link
Copy Markdown

What does this PR do?

Adds support for the "Hide from profile" toggle for managed event types and propagates the value to assigned child event types, matching the behavior of standard event types.

When configured on a managed parent event, the hidden value is synced to child events and respects the existing lock/unlock mechanism via managedEventConfig.unlockedFields.

Fixes #25903

Motivation

The "Hide from profile" setting was available for standard event types but missing for managed events, preventing team admins from hiding managed event bookings from assignees' public profiles.

This PR brings managed events to feature parity with standard event types.

Changes

handleChildrenEventTypes.ts

  • Implemented child event synchronization logic.
  • Creates child event types for newly assigned users.
  • Updates existing children with locked parent properties.
  • Removes children for unassigned users.
  • Supports syncing hidden through the listing quick-toggle flow.

Hidden field behavior

  • Locked (default): children inherit the parent's hidden value.
  • Unlocked: children retain their own hidden value.

update.handler.ts

  • Replaced the previous stub implementation with the new synchronization engine.

event-types-listing-view.tsx

  • Enabled the toggle for managed parent events.
  • Disabled the toggle for child events when the field is locked.
  • Added a "Locked by team admin" tooltip.

Notes

updateNewTeamMemberEventTypes (member joins after event creation) is still a stub in this implementation. Newly added members receive the updated child event on the next managed event save or via Assign All.

Support for that flow can be added in a follow-up PR if required.

Adds the 'Hide from profile' (hidden) toggle to managed event types and propagates it to assigned child event types, with lock/unlock semantics via managedEventConfig.unlockedFields.

- Implement handleChildrenEventTypes engine (previously a no-op stub) that creates/updates/deletes child event types for assigned users and propagates the parent's locked managed props, including hidden.
- Lock semantics: when hidden is locked (default), children inherit the parent's value; when unlocked, each child keeps its own.
- Handle the listing quick-toggle path (payload without children) by syncing hidden to existing children.
- Show the hidden toggle for managed parents in the event type listing and disable it for locked children.
- Add unit tests covering create/update/delete, lock/unlock, quick-toggle and slug-collision cases.
@github-actions

Copy link
Copy Markdown
Contributor

Welcome to Cal.diy, @Sayan8945! Thanks for opening this pull request.

A few things to keep in mind:

  • This is Cal.diy, not Cal.com. Cal.diy is a community-driven, fully open-source fork of Cal.com licensed under MIT. Your changes here will be part of Cal.diy — they will not be deployed to the Cal.com production app.
  • Please review our Contributing Guidelines if you haven't already.
  • Make sure your PR title follows the Conventional Commits format.

A maintainer will review your PR soon. Thanks for contributing!

@github-actions github-actions Bot added enterprise area: enterprise, audit log, organisation, SAML, SSO Medium priority Created by Linear-GitHub Sync ⚡ Quick Wins A collection of quick wins/quick fixes that are less than 30 minutes of work ✅ good first issue Good for newcomers ✨ feature New feature or request labels Jul 10, 2026
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Managed event-type updates now use a dedicated child synchronizer that propagates locked fields, preserves unlocked hidden values, creates and removes children as assignments change, and avoids slug conflicts. The update handler delegates child processing to this synchronizer. The event-type listing disables or hides hidden controls when managed settings lock the field, with distinct desktop tooltip messages. Vitest coverage validates the synchronization scenarios.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding hidden toggle support for managed events.
Description check ✅ Passed The description is directly related to the PR and accurately describes the managed-event hidden toggle work.
Linked Issues check ✅ Passed The changes implement the linked issue’s managed parent hidden toggle with lock/unlock behavior and child synchronization.
Out of Scope Changes check ✅ Passed The modified files and tests all support the hidden toggle and child-event sync flow, with no clear unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/modules/event-types/views/event-types-listing-view.tsx`:
- Around line 604-614: Add a tooltip branch for lockedByOrg in the tooltip
content surrounding the Hidden Switch, ensuring it takes precedence alongside
isChildHiddenLocked and displays an explanatory locked message whenever either
condition disables the switch. Preserve the existing show/hide messages for
enabled states.

In
`@packages/trpc/server/routers/viewer/eventTypes/heavy/handleChildrenEventTypes.test.ts`:
- Around line 90-107: Add a test alongside the existing locked case that mocks
the parent as hidden false, invokes handleChildrenEventTypes with an existing
child, and asserts updateMany is called with the expected parent/user filter
while updateArg.data does not contain hidden, verifying the child’s existing
hidden value is preserved when unlocked.

In
`@packages/trpc/server/routers/viewer/eventTypes/heavy/handleChildrenEventTypes.ts`:
- Around line 172-188: The existing-child update path in
handleChildrenEventTypes currently overwrites child-specific hidden values with
false when hidden is unlocked. Build the update data once, add hidden only when
isHiddenLocked is true, and replace the per-user Promise.all/updateMany loop
with a single updateMany targeting parentId and oldUserIds via an appropriate
userId filter.
- Around line 139-141: Update the child event synchronization logic to propagate
parent metadata changes to existing children, not only during creation. Locate
the update path in the child event handling logic alongside the childMetadata
initialization, include the parent’s current metadata when building child
updates, and ensure managedEventConfig.unlockedFields is refreshed on every
affected child.

In `@packages/trpc/server/routers/viewer/eventTypes/heavy/update.handler.ts`:
- Around line 725-734: Wrap the parent update and handleChildrenEventTypes call
in a single Prisma transaction so failures roll back both parent and child
changes. Pass the transaction client instead of ctx.prisma to
handleChildrenEventTypes, updating its signature and all required callers to
accept the transaction-scoped client.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cf7a9fd4-ebbf-4658-aa3c-aedfdb90ce08

📥 Commits

Reviewing files that changed from the base of the PR and between f004349 and d55410e.

📒 Files selected for processing (4)
  • apps/web/modules/event-types/views/event-types-listing-view.tsx
  • packages/trpc/server/routers/viewer/eventTypes/heavy/handleChildrenEventTypes.test.ts
  • packages/trpc/server/routers/viewer/eventTypes/heavy/handleChildrenEventTypes.ts
  • packages/trpc/server/routers/viewer/eventTypes/heavy/update.handler.ts

Comment on lines +604 to +614
content={
isChildHiddenLocked
? t("locked_by_team_admin")
: type.hidden
? t("show_eventtype_on_profile")
: t("hide_from_profile")
}>
<div className="self-center rounded-md p-2">
<Switch
name="Hidden"
disabled={lockedByOrg || isChildHiddenLocked}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Tooltip doesn't cover the lockedByOrg case.

The tooltip content only special-cases isChildHiddenLocked, but the switch is disabled when lockedByOrg || isChildHiddenLocked (line 614). When lockedByOrg is true but isChildHiddenLocked is false, the tooltip shows a normal "show/hide from profile" message while the switch is disabled — misleading the user. The change details indicate the old tooltip logic considered lockedByOrg; that case appears to have been dropped.

Consider adding a lockedByOrg branch to the tooltip content so all disabled states have an explanatory message.

💡 Suggested tooltip fix
                             content={
+                              lockedByOrg
+                                ? t("locked_by_organization")
+                                : isChildHiddenLocked
+                                ? t("locked_by_team_admin")
+                                : type.hidden
+                                ? t("show_eventtype_on_profile")
+                                : t("hide_from_profile")
-                              isChildHiddenLocked
-                                ? t("locked_by_team_admin")
-                                : type.hidden
-                                ? t("show_eventtype_on_profile")
-                                : t("hide_from_profile")
                             }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
content={
isChildHiddenLocked
? t("locked_by_team_admin")
: type.hidden
? t("show_eventtype_on_profile")
: t("hide_from_profile")
}>
<div className="self-center rounded-md p-2">
<Switch
name="Hidden"
disabled={lockedByOrg || isChildHiddenLocked}
content={
lockedByOrg
? t("locked_by_organization")
: isChildHiddenLocked
? t("locked_by_team_admin")
: type.hidden
? t("show_eventtype_on_profile")
: t("hide_from_profile")
}>
<div className="self-center rounded-md p-2">
<Switch
name="Hidden"
disabled={lockedByOrg || isChildHiddenLocked}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/modules/event-types/views/event-types-listing-view.tsx` around lines
604 - 614, Add a tooltip branch for lockedByOrg in the tooltip content
surrounding the Hidden Switch, ensuring it takes precedence alongside
isChildHiddenLocked and displays an explanatory locked message whenever either
condition disables the switch. Preserve the existing show/hide messages for
enabled states.

Comment on lines +90 to +107
it("updates existing children with the parent's hidden value when locked", async () => {
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue(buildParent({ hidden: true }) as never);

const res = await handleChildrenEventTypes({
eventTypeId: PARENT_ID,
updatedEventType: { slug: "managed", schedulingType: SchedulingType.MANAGED },
oldEventType: { children: [{ userId: 2 }] },
children: [buildChildInput(2, false)],
prisma: prismaMock,
});

expect(res).toMatchObject({ newUserIds: [], oldUserIds: [2], deletedUserIds: [] });
expect(prismaMock.eventType.create).not.toHaveBeenCalled();
expect(prismaMock.eventType.updateMany).toHaveBeenCalledTimes(1);
const updateArg = prismaMock.eventType.updateMany.mock.calls[0][0];
expect(updateArg.where).toEqual({ parentId: PARENT_ID, userId: 2 });
expect(updateArg.data.hidden).toBe(true);
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add a test for updating existing children when hidden is unlocked.

The suite covers "update existing children when locked" (this test) but misses the unlocked case. This is the exact scenario where the bug at line 178 of handleChildrenEventTypes.ts manifests: when unlocked, the child's existing hidden should be preserved, not overwritten with false. A test asserting that updateMany data omits hidden when unlocked would catch the regression.

✅ Suggested test: unlocked update preserves child's hidden
+  it("preserves the child's own hidden value when updating and the field is unlocked", async () => {
+    prismaMock.eventType.findUniqueOrThrow.mockResolvedValue(
+      buildParent({
+        hidden: false,
+        metadata: { managedEventConfig: { unlockedFields: { hidden: true } } },
+      }) as never
+    );
+
+    await handleChildrenEventTypes({
+      eventTypeId: PARENT_ID,
+      updatedEventType: { slug: "managed", schedulingType: SchedulingType.MANAGED },
+      oldEventType: { children: [{ userId: 2 }] },
+      children: [buildChildInput(2, true)],
+      prisma: prismaMock,
+    });
+
+    expect(prismaMock.eventType.updateMany).toHaveBeenCalledTimes(1);
+    const updateArg = prismaMock.eventType.updateMany.mock.calls[0][0];
+    // Unlocked → hidden should be omitted from the update data to preserve the child's existing value.
+    expect(updateArg.data).not.toHaveProperty("hidden");
+  });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("updates existing children with the parent's hidden value when locked", async () => {
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue(buildParent({ hidden: true }) as never);
const res = await handleChildrenEventTypes({
eventTypeId: PARENT_ID,
updatedEventType: { slug: "managed", schedulingType: SchedulingType.MANAGED },
oldEventType: { children: [{ userId: 2 }] },
children: [buildChildInput(2, false)],
prisma: prismaMock,
});
expect(res).toMatchObject({ newUserIds: [], oldUserIds: [2], deletedUserIds: [] });
expect(prismaMock.eventType.create).not.toHaveBeenCalled();
expect(prismaMock.eventType.updateMany).toHaveBeenCalledTimes(1);
const updateArg = prismaMock.eventType.updateMany.mock.calls[0][0];
expect(updateArg.where).toEqual({ parentId: PARENT_ID, userId: 2 });
expect(updateArg.data.hidden).toBe(true);
});
it("updates existing children with the parent's hidden value when locked", async () => {
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue(buildParent({ hidden: true }) as never);
const res = await handleChildrenEventTypes({
eventTypeId: PARENT_ID,
updatedEventType: { slug: "managed", schedulingType: SchedulingType.MANAGED },
oldEventType: { children: [{ userId: 2 }] },
children: [buildChildInput(2, false)],
prisma: prismaMock,
});
expect(res).toMatchObject({ newUserIds: [], oldUserIds: [2], deletedUserIds: [] });
expect(prismaMock.eventType.create).not.toHaveBeenCalled();
expect(prismaMock.eventType.updateMany).toHaveBeenCalledTimes(1);
const updateArg = prismaMock.eventType.updateMany.mock.calls[0][0];
expect(updateArg.where).toEqual({ parentId: PARENT_ID, userId: 2 });
expect(updateArg.data.hidden).toBe(true);
});
it("preserves the child's own hidden value when updating and the field is unlocked", async () => {
prismaMock.eventType.findUniqueOrThrow.mockResolvedValue(
buildParent({
hidden: false,
metadata: { managedEventConfig: { unlockedFields: { hidden: true } } },
}) as never
);
await handleChildrenEventTypes({
eventTypeId: PARENT_ID,
updatedEventType: { slug: "managed", schedulingType: SchedulingType.MANAGED },
oldEventType: { children: [{ userId: 2 }] },
children: [buildChildInput(2, true)],
prisma: prismaMock,
});
expect(prismaMock.eventType.updateMany).toHaveBeenCalledTimes(1);
const updateArg = prismaMock.eventType.updateMany.mock.calls[0][0];
// Unlocked → hidden should be omitted from the update data to preserve the child's existing value.
expect(updateArg.data).not.toHaveProperty("hidden");
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/trpc/server/routers/viewer/eventTypes/heavy/handleChildrenEventTypes.test.ts`
around lines 90 - 107, Add a test alongside the existing locked case that mocks
the parent as hidden false, invokes handleChildrenEventTypes with an existing
child, and asserts updateMany is called with the expected parent/user filter
while updateArg.data does not contain hidden, verifying the child’s existing
hidden value is preserved when unlocked.

Comment on lines +139 to +141
// Children carry the parent's metadata (including managedEventConfig.unlockedFields)
// so the child editor knows which fields are locked. Copied verbatim on creation.
const childMetadata = coerceValue("metadata", parent.metadata);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for where the child event type editor reads unlockedFields to determine lock state.
# Look for managedEventConfig.unlockedFields usage in the eventtype editor components.

rg -n -C5 'unlockedFields' --type=ts --type=tsx -g '!**/node_modules/**' -g '!**/*.test.*' packages/ apps/ | head -100

Repository: calcom/cal.diy

Length of output: 180


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- handleChildrenEventTypes.ts outline ---'
ast-grep outline packages/trpc/server/routers/viewer/eventTypes/heavy/handleChildrenEventTypes.ts --view expanded || true

echo
echo '--- find unlockedFields usages ---'
rg -n -C 4 'unlockedFields' packages apps \
  -g '*.ts' -g '*.tsx' \
  -g '!**/node_modules/**' -g '!**/*.test.*' \
  | head -200

echo
echo '--- inspect relevant slice of handleChildrenEventTypes.ts ---'
nl -ba packages/trpc/server/routers/viewer/eventTypes/heavy/handleChildrenEventTypes.ts | sed -n '120,210p'

Repository: calcom/cal.diy

Length of output: 15775


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- handleChildrenEventTypes.ts top section ---'
nl -ba packages/trpc/server/routers/viewer/eventTypes/heavy/handleChildrenEventTypes.ts | sed -n '1,120p'

echo
echo '--- all metadata.unlockedFields reads ---'
rg -n -C 3 'metadata\?.*managedEventConfig\?.unlockedFields|managedEventConfig\?.unlockedFields|unlockedFields' packages apps \
  -g '*.ts' -g '*.tsx' \
  -g '!**/node_modules/**' -g '!**/*.test.*' \
  | sed -n '1,240p'

echo
echo '--- event type editor related files ---'
fd -a 'event-type' packages apps | sed -n '1,120p'

Repository: calcom/cal.diy

Length of output: 240


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- constants and update logic ---'
sed -n '1,220p' packages/trpc/server/routers/viewer/eventTypes/heavy/handleChildrenEventTypes.ts

echo
echo '--- child metadata / unlockedFields consumers ---'
rg -n -C 5 'managedEventConfig\.unlockedFields|unlockedFields' packages apps \
  -g '*.ts' -g '*.tsx' \
  -g '!**/node_modules/**' -g '!**/*.test.*' \
  | sed -n '1,260p'

Repository: calcom/cal.diy

Length of output: 25681


Propagate metadata updates to existing children
metadata is copied only on creation and excluded from the update path, so existing children keep stale managedEventConfig.unlockedFields. The child UI reads lock state from each child’s own metadata, so unlocking a field on the parent can leave existing children shown as still locked.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/trpc/server/routers/viewer/eventTypes/heavy/handleChildrenEventTypes.ts`
around lines 139 - 141, Update the child event synchronization logic to
propagate parent metadata changes to existing children, not only during
creation. Locate the update path in the child event handling logic alongside the
childMetadata initialization, include the parent’s current metadata when
building child updates, and ensure managedEventConfig.unlockedFields is
refreshed on every affected child.

Comment on lines +172 to +188
// 2) Update existing children with the parent's locked managed props.
if (oldUserIds.length) {
const updateData = buildPropagatedData(parent, UPDATE_EXCLUDED_PROPS, unlockedFields);
await Promise.all(
oldUserIds.map((userId) => {
const childInput = children.find((child) => child.owner.id === userId);
const hidden = isHiddenLocked ? parent.hidden : childInput?.hidden ?? false;
return prisma.eventType.updateMany({
where: { parentId: eventTypeId, userId },
data: {
...(updateData as Prisma.EventTypeUpdateManyMutationInput),
hidden,
},
});
})
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Update path overwrites child hidden with false when unlocked — inconsistent with quick-toggle path.

Line 178 sets hidden = isHiddenLocked ? parent.hidden : childInput?.hidden ?? false. When hidden is unlocked, childInput?.hidden ?? false defaults to false if the payload omits it, overwriting the child's existing hidden value. This directly contradicts the PR objective of preserving child-specific hidden settings when unlocked.

The quick-toggle path (line 127) correctly omits hidden from the update data when unlocked: ...(isHiddenLocked ? { hidden: parent.hidden } : {}). The full update path should follow the same pattern. Additionally, since all old users receive identical update data after this fix, the per-user Promise.all + updateMany loop can be consolidated into a single updateMany — matching the quick-toggle pattern and reducing N concurrent DB calls to 1.

🐛 Proposed fix: omit `hidden` when unlocked and consolidate to single `updateMany`
   // 2) Update existing children with the parent's locked managed props.
   if (oldUserIds.length) {
     const updateData = buildPropagatedData(parent, UPDATE_EXCLUDED_PROPS, unlockedFields);
-    await Promise.all(
-      oldUserIds.map((userId) => {
-        const childInput = children.find((child) => child.owner.id === userId);
-        const hidden = isHiddenLocked ? parent.hidden : childInput?.hidden ?? false;
-        return prisma.eventType.updateMany({
-          where: { parentId: eventTypeId, userId },
-          data: {
-            ...(updateData as Prisma.EventTypeUpdateManyMutationInput),
-            hidden,
-          },
-        });
-      })
-    );
+    await prisma.eventType.updateMany({
+      where: { parentId: eventTypeId, userId: { in: oldUserIds } },
+      data: {
+        ...(updateData as Prisma.EventTypeUpdateManyMutationInput),
+        // Only propagate hidden when locked; when unlocked, children own their value.
+        ...(isHiddenLocked ? { hidden: parent.hidden } : {}),
+      },
+    });
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// 2) Update existing children with the parent's locked managed props.
if (oldUserIds.length) {
const updateData = buildPropagatedData(parent, UPDATE_EXCLUDED_PROPS, unlockedFields);
await Promise.all(
oldUserIds.map((userId) => {
const childInput = children.find((child) => child.owner.id === userId);
const hidden = isHiddenLocked ? parent.hidden : childInput?.hidden ?? false;
return prisma.eventType.updateMany({
where: { parentId: eventTypeId, userId },
data: {
...(updateData as Prisma.EventTypeUpdateManyMutationInput),
hidden,
},
});
})
);
}
// 2) Update existing children with the parent's locked managed props.
if (oldUserIds.length) {
const updateData = buildPropagatedData(parent, UPDATE_EXCLUDED_PROPS, unlockedFields);
await prisma.eventType.updateMany({
where: { parentId: eventTypeId, userId: { in: oldUserIds } },
data: {
...(updateData as Prisma.EventTypeUpdateManyMutationInput),
// Only propagate hidden when locked; when unlocked, children own their value.
...(isHiddenLocked ? { hidden: parent.hidden } : {}),
},
});
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/trpc/server/routers/viewer/eventTypes/heavy/handleChildrenEventTypes.ts`
around lines 172 - 188, The existing-child update path in
handleChildrenEventTypes currently overwrites child-specific hidden values with
false when hidden is unlocked. Build the update data once, add hidden only when
isHiddenLocked is true, and replace the per-user Promise.all/updateMany loop
with a single updateMany targeting parentId and oldUserIds via an appropriate
userId filter.

Comment on lines +725 to 734
// Handling updates to children event types (managed events types).
// Propagates the parent's locked managed props (including the `hidden` /
// "Hide from profile" toggle) to the assigned child event types.
await handleChildrenEventTypes({
eventTypeId: id,
currentUserId: ctx.user.id,
oldEventType: eventType,
updatedEventType,
oldEventType: eventType,
children,
profileId: ctx.user.profile.id,
prisma: ctx.prisma,
updatedValues,
calVideoSettings: calVideoSettingsForChildren,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if other callers of handleChildrenEventTypes exist and whether they use transactions.
rg -n 'handleChildrenEventTypes' --type=ts -g '!**/*.test.*' packages/ apps/ | head -20

# Check the PrismaClient vs TransactionClient type compatibility
rg -n 'Prisma.TransactionClient' --type=ts packages/trpc/server/routers/viewer/eventTypes/ | head -20

Repository: calcom/cal.diy

Length of output: 702


Wrap the parent update and child sync in one transaction
The parent is saved first, then handleChildrenEventTypes runs separate child writes afterward, so a failure there can leave the parent and children out of sync. Passing a transaction client into handleChildrenEventTypes would let this update stay atomic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/trpc/server/routers/viewer/eventTypes/heavy/update.handler.ts`
around lines 725 - 734, Wrap the parent update and handleChildrenEventTypes call
in a single Prisma transaction so failures roll back both parent and child
changes. Pass the transaction client instead of ctx.prisma to
handleChildrenEventTypes, updating its signature and all required callers to
accept the transaction-scoped client.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enterprise area: enterprise, audit log, organisation, SAML, SSO ✨ feature New feature or request ✅ good first issue Good for newcomers Medium priority Created by Linear-GitHub Sync ⚡ Quick Wins A collection of quick wins/quick fixes that are less than 30 minutes of work size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Managed Event doesn't yet support a Hidden Event toggle in parent event

1 participant