Skip to content

Commit

Permalink
feat: show ooo forwarding and emoji when not available (calcom#18054)
Browse files Browse the repository at this point in the history
* feat: show ooo when not available

* fix: send correct username for toUser if user belongs to org

* enrich ooo user with profile in getUserAvailability

* fix: set correct ooo slots

---------

Co-authored-by: Udit Takkar <[email protected]>
Co-authored-by: Anik Dhabal Babu <[email protected]>
  • Loading branch information
3 people authored Jan 30, 2025
1 parent 7ec928f commit a0f6e50
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 10 deletions.
27 changes: 18 additions & 9 deletions packages/core/getUserAvailability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ import { safeStringify } from "@calcom/lib/safeStringify";
import { EventTypeRepository } from "@calcom/lib/server/repository/eventType";
import { UserRepository } from "@calcom/lib/server/repository/user";
import prisma from "@calcom/prisma";
import { SchedulingType } from "@calcom/prisma/enums";
import { BookingStatus } from "@calcom/prisma/enums";
import { BookingStatus, SchedulingType } from "@calcom/prisma/enums";
import { EventTypeMetaDataSchema, stringToDayjsZod } from "@calcom/prisma/zod-utils";
import type { EventBusyDetails, IntervalLimitUnit } from "@calcom/types/Calendar";
import type { TimeRange } from "@calcom/types/schedule";
Expand Down Expand Up @@ -542,7 +541,7 @@ const _getUserAvailability = async function getUsersWorkingHoursLifeTheUniverseA
},
}));

const datesOutOfOffice: IOutOfOfficeData = calculateOutOfOfficeRanges(outOfOfficeDays, availability);
const datesOutOfOffice: IOutOfOfficeData = await calculateOutOfOfficeRanges(outOfOfficeDays, availability);

const { dateRanges, oooExcludedDateRanges } = buildDateRanges({
dateFrom,
Expand Down Expand Up @@ -629,15 +628,17 @@ export interface IOutOfOfficeData {
};
}

const calculateOutOfOfficeRanges = (
const calculateOutOfOfficeRanges = async (
outOfOfficeDays: GetUserAvailabilityInitialData["outOfOfficeDays"],
availability: GetUserAvailabilityParamsDTO["availability"]
): IOutOfOfficeData => {
): Promise<IOutOfOfficeData> => {
if (!outOfOfficeDays || outOfOfficeDays.length === 0) {
return {};
}

return outOfOfficeDays.reduce((acc: IOutOfOfficeData, { start, end, toUser, user, reason }) => {
const acc: IOutOfOfficeData = {};

for (const { start, end, toUser, user, reason } of outOfOfficeDays) {
// here we should use startDate or today if start is before today
// consider timezone in start and end date range
const startDateRange = dayjs(start).utc().isBefore(dayjs().startOf("day").utc())
Expand All @@ -658,19 +659,27 @@ const calculateOutOfOfficeRanges = (
continue; // Skip to the next iteration if day not found in flattenDays
}

const enrichedToUser = toUser ? await UserRepository.enrichUserWithItsProfile({ user: toUser }) : null;

acc[date.format("YYYY-MM-DD")] = {
// @TODO: would be good having start and end availability time here, but for now should be good
// you can obtain that from user availability defined outside of here
fromUser: { id: user.id, displayName: user.name },
// optional chaining destructuring toUser
toUser: !!toUser ? { id: toUser.id, displayName: toUser.name, username: toUser.username } : null,
...(!!enrichedToUser && {
toUser: {
id: enrichedToUser.id,
username: enrichedToUser.username,
displayName: enrichedToUser.name,
},
}),
reason: !!reason ? reason.reason : null,
emoji: !!reason ? reason.emoji : null,
};
}
}

return acc;
}, {});
return acc;
};

type GetUsersAvailabilityProps = {
Expand Down
22 changes: 21 additions & 1 deletion packages/lib/slots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ function buildSlotsWithDateRanges({
}
}

const processedOOODates = new Set<string>();
dateRanges.forEach((range) => {
const dateYYYYMMDD = range.start.format("YYYY-MM-DD");
const startTimeWithMinNotice = dayjs.utc().add(minimumBookingNotice, "minute");
Expand All @@ -206,6 +207,25 @@ function buildSlotsWithDateRanges({

slotStartTime = slotStartTime.add(offsetStart ?? 0, "minutes").tz(timeZone);

// Add OOO slot if exists and is before current range
if (datesOutOfOffice) {
Object.entries(datesOutOfOffice).forEach(([dateStr, oooData]) => {
const oooDate = dayjs(dateStr);
if (oooDate.isBefore(dateYYYYMMDD, "day") && !processedOOODates.has(dateStr)) {
slots.push({
time: oooDate.startOf("day"),
away: true,
...(oooData.fromUser && { fromUser: oooData.fromUser }),
...(oooData.toUser && { toUser: oooData.toUser }),
...(oooData.reason && { reason: oooData.reason }),
...(oooData.emoji && { emoji: oooData.emoji }),
});
// when the date range increases in the next iteration, we don't want to process this date again
processedOOODates.add(dateStr);
}
});
}

while (!slotStartTime.add(eventLength, "minutes").subtract(1, "second").utc().isAfter(rangeEnd)) {
const dateOutOfOfficeExists = datesOutOfOffice?.[dateYYYYMMDD];
let slotData: {
Expand All @@ -231,8 +251,8 @@ function buildSlotsWithDateRanges({
...(reason && { reason }),
...(emoji && { emoji }),
};
processedOOODates.add(dateYYYYMMDD);
}

slots.push(slotData);
slotStartTime = slotStartTime.add(frequency + (offsetStart ?? 0), "minutes");
}
Expand Down

0 comments on commit a0f6e50

Please sign in to comment.