calcom / cal.com

Scheduling infrastructure for absolutely everyone.
https://cal.com
Other
30.7k stars 7.38k forks source link

[CAL-3978] API `GET /slots` with `eventTypeSlug/usernameList` doesn't work as expected #15530

Open nicolasrouanne opened 2 months ago

nicolasrouanne commented 2 months ago

Issue Summary

When querying /slots using eventTypeSlug&usernameList for a Round Robin event from a team, it returns time slots taking availabilities of the all the members of the event instead of the subset contained in the usernameList param.

Steps to Reproduce

Query the slots for the all-hands using usernameList=[alice,bob]

/slots?eventTypeSlug=all-hands&usernameList=[alice,bob]&startTime=2024-06-01&endTime=2024-06-30

-> slots appear for both morning and afternoon ✅

Actual Results

Query the slots for the all-hands using usernameList=[alice]

/slots?eventTypeSlug=all-hands&usernameList=[alice]&startTime=2024-06-01&endTime=2024-06-30

-> slots appear for both morning and afternoon ❌

Expected Results

When querying with only alice in usernameList, only slots available for Alice should be displayed. In our case, only slots available in the morning.

CAL-3978

dosubot[bot] commented 2 months ago

@nicolasrouanne Hello there! I'm here to assist you with any bugs, questions, or contributions you have. Let's work together to solve the issue you've encountered.

To ensure that querying the /slots API endpoint with a specific usernameList for a Round Robin event returns time slots considering only the availabilities of the specified subset of users, you need to filter the users based on the usernameList and then compute the available slots accordingly.

Here is the relevant code snippet that demonstrates how to achieve this:

export async function getAvailableSlots({ input, ctx }: GetScheduleOptions): Promise<IGetAvailableSlots> {
  // ... other code ...

  const eventType = await getRegularOrDynamicEventType(input, orgDetails);

  // ... other code ...

  const hosts = eventType.hosts?.length && eventType.schedulingType
    ? eventType.hosts
    : eventType.users.map((user) => {
        return {
          isFixed: !eventType.schedulingType || eventType.schedulingType === SchedulingType.COLLECTIVE,
          user: user,
        };
      });

  // Filter users based on the provided usernameList
  let usersWithCredentials = hosts
    .map(({ isFixed, user }) => ({ isFixed, ...user }))
    .filter(user => input.usernameList.includes(user.username));

  // ... other code ...

  const timeSlots = getSlots({
    inviteeDate: startTime,
    eventLength: input.duration || eventType.length,
    offsetStart: eventType.offsetStart,
    dateRanges: aggregatedAvailability,
    minimumBookingNotice: eventType.minimumBookingNotice,
    frequency: eventType.slotInterval || input.duration || eventType.length,
    organizerTimeZone: eventType.timeZone || eventType?.schedule?.timeZone || allUsersAvailability?.[0]?.timeZone,
    datesOutOfOffice: !isTeamEvent ? allUsersAvailability[0]?.datesOutOfOffice : undefined,
  });

  // ... other code ...

  return availableTimeSlots;
}

In this code, the usersWithCredentials array is filtered to include only those users whose usernames are in the input.usernameList. This ensures that the availability computation considers only the specified subset of users [1].

Additionally, the ensureAvailableUsers function ensures that the availability computation is accurate and reflects the real-time availability of the specified users:

export async function ensureAvailableUsers(
  eventType: Awaited<ReturnType<typeof getEventTypesFromDB>> & {
    users: IsFixedAwareUser[];
  },
  input: { dateFrom: string; dateTo: string; timeZone: string; originalRescheduledBooking?: BookingType },
  loggerWithEventDetails: Logger<unknown>
) {
  const availableUsers: IsFixedAwareUser[] = [];
  const getStartDateTimeUtc = (startDateTimeInput: string, timeZone?: string) => {
    return timeZone === "Etc/GMT"
      ? dayjs.utc(startDateTimeInput)
      : dayjs(startDateTimeInput).tz(timeZone).utc();
  };

  const startDateTimeUtc = getStartDateTimeUtc(input.dateFrom, input.timeZone);
  const endDateTimeUtc =
    input.timeZone === "Etc/GMT" ? dayjs.utc(input.dateTo) : dayjs(input.dateTo).tz(input.timeZone).utc();

  const duration = dayjs(input.dateTo).diff(input.dateFrom, "minute");
  const originalBookingDuration = input.originalRescheduledBooking
    ? dayjs(input.originalRescheduledBooking.endTime).diff(
        dayjs(input.originalRescheduledBooking.startTime),
        "minutes"
      )
    : undefined;

  const bookingLimits = parseBookingLimit(eventType?.bookingLimits);
  const durationLimits = parseDurationLimit(eventType?.durationLimits);
  let busyTimesFromLimitsBookingsAllUsers: Awaited<ReturnType<typeof getBusyTimesForLimitChecks>> = [];

  if (eventType && (bookingLimits || durationLimits)) {
    busyTimesFromLimitsBookingsAllUsers = await getBusyTimesForLimitChecks({
      userIds: eventType.users.map((u) => u.id),
      eventTypeId: eventType.id,
      startDate: startDateTimeUtc.format(),
      endDate: endDateTimeUtc.format(),
      rescheduleUid: input.originalRescheduledBooking?.uid ?? null,
      bookingLimits,
      durationLimits,
    });
  }
}

This function ensures that the availability computation is accurate and reflects the real-time availability of the specified users [2].

Moreover, the test cases validate the functionality of the /slots API endpoint, ensuring that it correctly handles the usernameList parameter and returns the appropriate available slots for the specified users [3].

To continue talking to Dosu, mention @dosu.

nicolasrouanne commented 2 months ago

@dosu I don't really understand your answer since I'm using cal.com as a hosted project. Hence I can't change the codebase, or maybe I can if maintainers consider my bug valid? As for now I can only interact with the external REST API.

Can you please confirm the issue and expected behavior I describe are correct? So I can open a PR to fix it.

dosubot[bot] commented 2 months ago

Yes, the issue and expected behavior you described are correct. The GET /slots API endpoint should filter available time slots based on the usernameList parameter for a Round Robin event. Currently, it seems to consider the availability of all members instead of the specified subset.

You can proceed to open a PR to fix this issue. The fix should ensure that the usernameList parameter is correctly processed to filter the users and compute the available slots accordingly.

@keithwillcode, could you step in and provide your guidance on this?

To continue talking to Dosu, mention @dosu.