Skip to content

feat: custom OOO reason#27943

Open
bandhan-majumder wants to merge 7 commits intocalcom:mainfrom
bandhan-majumder:fix-27886
Open

feat: custom OOO reason#27943
bandhan-majumder wants to merge 7 commits intocalcom:mainfrom
bandhan-majumder:fix-27886

Conversation

@bandhan-majumder
Copy link
Contributor

@bandhan-majumder bandhan-majumder commented Feb 13, 2026

What does this PR do?

Visual Demo (For contributors especially)

A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).

Video Demo (if applicable):

Screencast.From.2026-02-14.01-47-26.mp4

Image Demo (if applicable):

image image

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  • Are there environment variables that should be set?
  • What are the minimal test data to have?
  • What is expected (happy path) to have (input and output)?
  • Any other important info that could help to test that PR

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings
  • My PR is too large (>500 lines or >10 files) and should be split into smaller PRs

Summary by cubic

Adds custom out-of-office reasons (emoji + short text) and displays them on the booking page for custom entries. Meets Linear CAL-7199 by letting users create, select, and manage personal reasons.

  • New Features

    • TRPC endpoints to create/delete custom reasons with duplicate checks against user and system defaults; unit tests added.
    • OOO modal: inline custom reason form (emoji + 50-char text), auto-select on create, list and delete “My custom reasons,” reset form on close; if a selected custom reason is deleted, fall back to a default.
    • Reason list merges system defaults with the user’s reasons; booking page shows a localized “Reason: ” line for custom entries; public note behavior unchanged.
  • Migration

    • Adds a composite unique index on OutOfOfficeReason (reason, userId).

Written for commit 230c90e. Summary will update on new commits.


Open with Devin

@graphite-app graphite-app bot added the community Created by Linear-GitHub Sync label Feb 13, 2026
@github-actions github-actions bot added Low priority Created by Linear-GitHub Sync 🧹 Improvements Improvements to existing features. Mostly UX/UI labels Feb 13, 2026
cubic-dev-ai[bot]

This comment was marked as resolved.

onSuccess: (_data, variables) => {
showToast(t("custom_reason_deleted"), "success");

const currentReasonId = getValues("reasonId");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is imp cause when we delete, even after invalidating the list, if the deleted item was previously selected and we click on save anyway, it will throw foreign key error as the data would not be existing by then

export const outOfOfficeReasonList = async ({ ctx }: GetOptions) => {
const outOfOfficeReasons = await prisma.outOfOfficeReason.findMany({
where: {
enabled: true,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

making sure if X has created custom reasons, it does not get showed in Y's list. Only defaults and the user created reasons

@sahitya-chandra sahitya-chandra self-assigned this Feb 14, 2026
devin-ai-integration[bot]

This comment was marked as resolved.

@bandhan-majumder bandhan-majumder requested a review from a team as a code owner February 14, 2026 14:53
@github-actions github-actions bot added the ❗️ migrations contains migration files label Feb 14, 2026
devin-ai-integration[bot]

This comment was marked as resolved.

model OutOfOfficeReason {
id Int @id @default(autoincrement())
emoji String
reason String @unique
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this was important so that reasons which are created by users as custom, is avl for other users to create too. So made the combo of userId and reason as unique. When userId is null, then only the reasons will be unique and this will only happen in the global reasons (like what we had previously)

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Contributor

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 1 new potential issue.

View 10 additional findings in Devin Review.

Open in Devin Review

Comment on lines +29 to +41
const existingSystemDefault = await prisma.outOfOfficeReason.findFirst({
where: {
userId: null,
reason: input.reason,
},
});

if (existingSystemDefault) {
throw new TRPCError({
code: "CONFLICT",
message: "This reason already exists as a system default",
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🟡 System default duplicate check is ineffective because it compares user text against translation keys

The duplicate-detection query at outOfOfficeCreateReason.handler.ts:29-34 searches for a system default with reason: input.reason, where input.reason is user-typed plain text (e.g., "Vacation"). However, system default reasons are stored in the database as i18n translation keys (e.g., "ooo_reasons_vacation"), as seen in the seed migration at packages/prisma/migrations/20240305054508_default_reasons_out_of_office/migration.sql.

Root Cause

The findFirst query compares the user's plain-text input directly against the DB reason column:

const existingSystemDefault = await prisma.outOfOfficeReason.findFirst({
  where: {
    userId: null,
    reason: input.reason, // e.g. "Vacation"
  },
});

But the stored system defaults use translation keys:

INSERT INTO "OutOfOfficeReason" ... VALUES ('🏝️', 'ooo_reasons_vacation', true);
INSERT INTO "OutOfOfficeReason" ... VALUES ('🛫', 'ooo_reasons_travel', true);

"Vacation" will never match "ooo_reasons_vacation", so this check is dead code. Users can freely create custom reasons that duplicate system defaults (e.g., a custom "Vacation" reason alongside the built-in one), which is the exact scenario this code was designed to prevent.

The unit test at outOfOfficeCreateReason.handler.test.ts:88-111 masks this by mocking the system default's reason field as "Vacation" instead of the real value "ooo_reasons_vacation".

Impact: The intended guard against duplicate system-default reasons never fires. Users see both a translated system "Vacation" and their custom "Vacation" in the reason dropdown.

Prompt for agents
In packages/trpc/server/routers/viewer/ooo/outOfOfficeCreateReason.handler.ts, the system default duplicate check at lines 29-41 compares user-typed plain text (e.g. "Vacation") against DB-stored translation keys (e.g. "ooo_reasons_vacation"). To fix this, either: (1) Fetch all system defaults (userId: null), translate their reason keys server-side, and compare the translated text against input.reason (case-insensitive). Or (2) Store a mapping of known system default display names and check input against that list. Also update the test at outOfOfficeCreateReason.handler.test.ts lines 88-111 to use real translation keys like "ooo_reasons_vacation" for the mocked system default's reason field.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@sahitya-chandra
Copy link
Member

@bandhan-majumder can you address the Devin comment?

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

Labels

community Created by Linear-GitHub Sync 🧹 Improvements Improvements to existing features. Mostly UX/UI Low priority Created by Linear-GitHub Sync ❗️ migrations contains migration files size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature Request: Custom Out of Office Status

2 participants