* feat: make source required on EventBusyDetails for Troubleshooter display - Make source a required property on EventBusyDetails type - Update LimitManager to accept and store source when adding busy times - Add user-friendly source names for all busy time types: - 'Booking Limit' for booking limit busy times - 'Duration Limit' for duration limit busy times - 'Team Booking Limit' for team booking limit busy times - 'Buffer Time' for seated event buffer times - 'Calendar' for external calendar busy times - Ensure all entries in detailedBusyTimes have source set - Cover includeManagedEventsInLimits and teamBookingLimits branches Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: add limit value and unit metadata to busy time sources - Include limit value and unit in source strings for Troubleshooter display - Booking Limit: shows as 'Booking Limit: 5 per day' - Duration Limit: shows as 'Duration Limit: 120 min per week' - Team Booking Limit: shows as 'Team Booking Limit: 10 per month' - Preserves existing calendar sources (e.g., 'google-calendar') Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: enhance busy time management with new limit sources - Introduced new limit sources for busy times, including event booking and duration limits, with user-friendly titles for display. - Updated LimitManager to accept and store detailed busy time information, including title and source. - Refactored busy time addition logic across various services to utilize the new structure, improving clarity and maintainability. * fixes * feat: conditionally include source and translate busy time titles - Add withSource parameter to conditionally include/exclude source from response - Translate busy time titles on frontend using useLocale hook - Source is only included when withSource=true (for Troubleshooter display) Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: enhance user availability service and busy time handling - Updated LargeCalendar component to include event ID check for enabling busy times. - Added translation for "busy" in common.json for better user experience. - Refactored getUserAvailability service to include new method for fetching user availability with busy times from limits. - Introduced parseLimits function to streamline booking and duration limit parsing. - Improved error handling in user handler for better user feedback. * refactor: remove unnecessary timeZone: undefined from addBusyTime calls Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: add buffer_time and calendar translation keys for busy times Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * feat: add event source to calendar components and improve busy time handling - Updated EventList component to include event source in data attributes for better tracking. - Enhanced LargeCalendar component to pass event * fix: add missing source property to Date Override calendar event Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: use 'date-override' as source for Date Override calendar events Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * Avoid type assertion * fix: pass both bookingLimits and durationLimits to getStartEndDateforLimitCheck (#27898) * test: add unit tests for getUserAvailabilityIncludingBusyTimesFromLimits Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * fix: pass both bookingLimits and durationLimits to getStartEndDateforLimitCheck - Fix bug where bookingLimits || durationLimits was passed as single param - Skip getBusyTimesForLimitChecks when eventType has no limits - Remove as never casts, use proper typing for mock dependencies - Replace expect.any(String) with exact ISO date assertions Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> * refactor: replace loose assertions with exact values in tests Co-Authored-By: hariom@cal.com <hariombalhara@gmail.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix: address review feedback on busy time sources - Fix t("busy") fallback to t("busy_time.busy") for correct translation lookup - Add missing title property to buffer time entries for Troubleshooter display - Use descriptive debug strings for buffer time source field - Fix import ordering in LargeCalendar.tsx Co-authored-by: Hariom Balhara <hariombalhara@gmail.com> Co-Authored-By: unknown <> * fix: preserve pre-existing busyTimesFromLimitsBookings from initialData Address review feedback from @hariombalhara (comment #30, #31): - Initialize busyTimesFromLimitsBookings from initialData instead of [] to avoid silently overwriting pre-existing data with an empty array - Use conditional spread to only include busyTimesFromLimitsBookings when it has a value - Add test verifying pre-existing busyTimesFromLimitsBookings is preserved and passed through to _getUserAvailability - Add test verifying busyTimesFromLimitsBookings is not passed when there are no limits and no initialData bookings Co-authored-by: Hariom Balhara <hariombalhara@gmail.com> Co-Authored-By: bot_apk <apk@cognition.ai> * fix: pass fetched eventType to _getUserAvailability to avoid duplicate DB query Addresses Devin Review comment r2863593564: the wrapper method fetches eventType but wasn't passing it through, causing _getUserAvailability to re-fetch the same eventType from the database. Also adds a test verifying eventType is forwarded correctly. Co-Authored-By: bot_apk <apk@cognition.ai> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com> Co-authored-by: bot_apk <apk@cognition.ai>
112 lines
3.7 KiB
TypeScript
112 lines
3.7 KiB
TypeScript
import type { Dayjs } from "@calcom/dayjs";
|
|
import type { EventBusyDetails } from "@calcom/types/Calendar";
|
|
import type { IntervalLimitUnit } from "./intervalLimitSchema";
|
|
|
|
type BusyMapKey = `${IntervalLimitUnit}-${ReturnType<Dayjs["toISOString"]>}`;
|
|
|
|
/**
|
|
* Limit sources with both title and source information
|
|
* Returns user-facing title (translation key) and internal debug string
|
|
*/
|
|
export const LimitSources = {
|
|
eventBookingLimit: (params: { limit: number; unit: IntervalLimitUnit }) => ({
|
|
title: "busy_time.event_booking_limit",
|
|
source: `Event Booking Limit for User: ${params.limit} per ${params.unit}`,
|
|
}),
|
|
|
|
eventDurationLimit: (params: { limit: number; unit: IntervalLimitUnit }) => ({
|
|
title: "busy_time.event_duration_limit",
|
|
source: `Event Duration Limit for User: ${params.limit} minutes per ${params.unit}`,
|
|
}),
|
|
|
|
teamBookingLimit: (params: { limit: number; unit: IntervalLimitUnit }) => ({
|
|
title: "busy_time.team_booking_limit",
|
|
source: `Team Booking Limit: ${params.limit} per ${params.unit}`,
|
|
}),
|
|
} as const;
|
|
|
|
/**
|
|
* Helps create, check, and return busy times from limits (with parallel support)
|
|
*/
|
|
export default class LimitManager {
|
|
private busyMap: Map<BusyMapKey, EventBusyDetails> = new Map();
|
|
|
|
/**
|
|
* Creates a busy map key
|
|
*/
|
|
private static createKey(start: Dayjs, unit: IntervalLimitUnit, timeZone?: string): BusyMapKey {
|
|
const tzStart = timeZone ? start.tz(timeZone) : start;
|
|
return `${unit}-${tzStart.startOf(unit).toISOString()}`;
|
|
}
|
|
|
|
/**
|
|
* Checks if already marked busy by ancestors or siblings
|
|
*/
|
|
isAlreadyBusy(start: Dayjs, unit: IntervalLimitUnit, timeZone?: string) {
|
|
if (this.busyMap.has(LimitManager.createKey(start, "year", timeZone))) return true;
|
|
|
|
if (unit === "month" && this.busyMap.has(LimitManager.createKey(start, "month", timeZone))) {
|
|
return true;
|
|
} else if (
|
|
unit === "week" &&
|
|
// weeks can be part of two months
|
|
((this.busyMap.has(LimitManager.createKey(start, "month", timeZone)) &&
|
|
this.busyMap.has(LimitManager.createKey(start.endOf("week"), "month", timeZone))) ||
|
|
this.busyMap.has(LimitManager.createKey(start, "week", timeZone)))
|
|
) {
|
|
return true;
|
|
} else if (
|
|
unit === "day" &&
|
|
(this.busyMap.has(LimitManager.createKey(start, "month", timeZone)) ||
|
|
this.busyMap.has(LimitManager.createKey(start, "week", timeZone)) ||
|
|
this.busyMap.has(LimitManager.createKey(start, "day", timeZone)))
|
|
) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a new busy time with title and source
|
|
* @param params.start - Start time of the busy period
|
|
* @param params.unit - Interval unit (day, week, month, year)
|
|
* @param params.timeZone - Optional timezone
|
|
* @param params.title - User-facing translation key (displayed in calendars)
|
|
* @param params.source - Internal debug string (for logging/troubleshooting)
|
|
*/
|
|
addBusyTime(params: {
|
|
start: Dayjs;
|
|
unit: IntervalLimitUnit;
|
|
timeZone?: string;
|
|
title: string;
|
|
source: string;
|
|
}) {
|
|
const tzStart = params.timeZone ? params.start.tz(params.timeZone) : params.start;
|
|
this.busyMap.set(`${params.unit}-${tzStart.toISOString()}`, {
|
|
start: tzStart.toISOString(),
|
|
end: tzStart.endOf(params.unit).toISOString(),
|
|
title: params.title,
|
|
source: params.source,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Merges busy times from another LimitManager
|
|
*/
|
|
mergeBusyTimes(otherManager: LimitManager) {
|
|
otherManager.busyMap.forEach((busyTime, key) => {
|
|
if (!this.busyMap.has(key)) {
|
|
this.busyMap.set(key, busyTime);
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Returns all busy times
|
|
*/
|
|
getBusyTimes() {
|
|
return Array.from(this.busyMap.values());
|
|
}
|
|
}
|