Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 49 additions & 10 deletions packages/app-store/googlecalendar/lib/CalendarService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,12 @@ interface GoogleCalError extends Error {

const MS_PER_DAY = 24 * 60 * 60 * 1000;
const ONE_MONTH_IN_MS = 30 * MS_PER_DAY;
// eslint-disable-next-line turbo/no-undeclared-env-vars -- GOOGLE_WEBHOOK_URL only for local testing

const GOOGLE_WEBHOOK_URL_BASE = process.env.GOOGLE_WEBHOOK_URL || process.env.NEXT_PUBLIC_WEBAPP_URL;
const GOOGLE_WEBHOOK_URL = `${GOOGLE_WEBHOOK_URL_BASE}/api/integrations/googlecalendar/webhook`;

const isGaxiosResponse = (error: unknown): error is GaxiosResponse<calendar_v3.Schema$Event> =>
typeof error === "object" && !!error && error.hasOwnProperty("config");
typeof error === "object" && !!error && Object.prototype.hasOwnProperty.call(error, "config");

type GoogleChannelProps = {
kind?: string | null;
Expand Down Expand Up @@ -181,6 +181,15 @@ export default class GoogleCalendarService implements Calendar {
): Promise<NewCalendarEventType> {
this.log.debug("Creating event");

// Determine optional custom reminder minutes from event payload or destination calendar metadata
// We intentionally access via `any` to avoid widening Calendar types and to keep this change isolated.
const customReminderMinutes: number | undefined =
(calEvent as any)?.customReminderMinutes ??
(calEvent.destinationCalendar?.find((cal) => cal.credentialId === credentialId)
? (calEvent.destinationCalendar.find((cal) => cal.credentialId === credentialId) as any)
?.customReminderMinutes
: undefined);
Comment on lines +184 to +191
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove unnecessary any-casts for type safety.

The type definitions in packages/types/Calendar.d.ts now include customReminderMinutes?: number on both CalendarEvent and CalendarServiceEvent. Since calEvent is typed as CalendarServiceEvent, you can access this field directly without casting to any.

The comment claims this avoids "widening Calendar types," but the types have already been widened in the type definition file. These casts bypass type safety without benefit.

Apply this diff to access the field safely:

-    // Determine optional custom reminder minutes from event payload or destination calendar metadata
-    // We intentionally access via `any` to avoid widening Calendar types and to keep this change isolated.
-    const customReminderMinutes: number | undefined =
-      (calEvent as any)?.customReminderMinutes ??
-      (calEvent.destinationCalendar?.find((cal) => cal.credentialId === credentialId)
-        ? (calEvent.destinationCalendar.find((cal) => cal.credentialId === credentialId) as any)
-            ?.customReminderMinutes
-        : undefined);
+    // Determine optional custom reminder minutes from event payload or destination calendar metadata
+    const customReminderMinutes: number | undefined =
+      calEvent.customReminderMinutes ??
+      calEvent.destinationCalendar?.find((cal) => cal.credentialId === credentialId)?.customReminderMinutes;
📝 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
// Determine optional custom reminder minutes from event payload or destination calendar metadata
// We intentionally access via `any` to avoid widening Calendar types and to keep this change isolated.
const customReminderMinutes: number | undefined =
(calEvent as any)?.customReminderMinutes ??
(calEvent.destinationCalendar?.find((cal) => cal.credentialId === credentialId)
? (calEvent.destinationCalendar.find((cal) => cal.credentialId === credentialId) as any)
?.customReminderMinutes
: undefined);
// Determine optional custom reminder minutes from event payload or destination calendar metadata
const customReminderMinutes: number | undefined =
calEvent.customReminderMinutes ??
calEvent.destinationCalendar?.find((cal) => cal.credentialId === credentialId)?.customReminderMinutes;
🤖 Prompt for AI Agents
In packages/app-store/googlecalendar/lib/CalendarService.ts around lines 184 to
191, remove the unnecessary any casts and access customReminderMinutes directly
from the typed CalendarServiceEvent and its destinationCalendar entries; replace
the double any-cast/find with a single typed find result (e.g., assign
destinationCalendar.find(...) to a const typed as Calendar | undefined) and use
optional chaining (event.customReminderMinutes ?? found?.customReminderMinutes)
so the code relies on the declared types in packages/types/Calendar.d.ts and
preserves type safety.


const payload: calendar_v3.Schema$Event = {
summary: calEvent.title,
description: calEvent.calendarDescription,
Expand All @@ -193,10 +202,21 @@ export default class GoogleCalendarService implements Calendar {
timeZone: calEvent.organizer.timeZone,
},
attendees: this.getAttendees({ event: calEvent, hostExternalCalendarId: externalCalendarId }),
reminders: {
useDefault: true,
},
guestsCanSeeOtherGuests: !!calEvent.seatsPerTimeSlot ? calEvent.seatsShowAttendees : true,
reminders:
customReminderMinutes !== undefined && Number.isFinite(customReminderMinutes)
? {
useDefault: false,
overrides: [
{
method: "popup",
minutes: Math.max(0, Math.floor(customReminderMinutes)),
},
],
}
: {
useDefault: true,
},
guestsCanSeeOtherGuests: calEvent.seatsPerTimeSlot ? calEvent.seatsShowAttendees : true,
iCalUID: calEvent.iCalUID,
};
if (calEvent.hideCalendarEventDetails) {
Expand Down Expand Up @@ -340,6 +360,14 @@ export default class GoogleCalendarService implements Calendar {
}

async updateEvent(uid: string, event: CalendarServiceEvent, externalCalendarId: string): Promise<any> {
// Determine optional custom reminder minutes from event payload or destination calendar metadata
const updateCustomReminderMinutes: number | undefined =
(event as any)?.customReminderMinutes ??
(event.destinationCalendar && externalCalendarId
? ((event.destinationCalendar.find((cal) => cal.externalId === externalCalendarId) as any)
?.customReminderMinutes as number | undefined)
: undefined);
Comment on lines +363 to +369
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Remove unnecessary any-casts for type safety (updateEvent).

Same issue as in createEvent: these any-casts are unnecessary since the types now include customReminderMinutes. Additionally, this code has a redundant find() operation - it searches destinationCalendar array twice.

Apply this diff:

-    // Determine optional custom reminder minutes from event payload or destination calendar metadata
-    const updateCustomReminderMinutes: number | undefined =
-      (event as any)?.customReminderMinutes ??
-      (event.destinationCalendar && externalCalendarId
-        ? ((event.destinationCalendar.find((cal) => cal.externalId === externalCalendarId) as any)
-            ?.customReminderMinutes as number | undefined)
-        : undefined);
+    // Determine optional custom reminder minutes from event payload or destination calendar metadata
+    const updateCustomReminderMinutes: number | undefined =
+      event.customReminderMinutes ??
+      event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.customReminderMinutes;
📝 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
// Determine optional custom reminder minutes from event payload or destination calendar metadata
const updateCustomReminderMinutes: number | undefined =
(event as any)?.customReminderMinutes ??
(event.destinationCalendar && externalCalendarId
? ((event.destinationCalendar.find((cal) => cal.externalId === externalCalendarId) as any)
?.customReminderMinutes as number | undefined)
: undefined);
// Determine optional custom reminder minutes from event payload or destination calendar metadata
const updateCustomReminderMinutes: number | undefined =
event.customReminderMinutes ??
event.destinationCalendar?.find((cal) => cal.externalId === externalCalendarId)?.customReminderMinutes;
🤖 Prompt for AI Agents
In packages/app-store/googlecalendar/lib/CalendarService.ts around lines 363 to
369, the code uses unnecessary any-casts and repeats a
destinationCalendar.find() call when resolving customReminderMinutes for
updateEvent; remove the any casts and perform a single find into a typed
variable (e.g. const destCal = event.destinationCalendar?.find(c => c.externalId
=== externalCalendarId)) then set updateCustomReminderMinutes =
event.customReminderMinutes ?? destCal?.customReminderMinutes, ensuring proper
typing (number | undefined) without using any.


const payload: calendar_v3.Schema$Event = {
summary: event.title,
description: event.calendarDescription,
Expand All @@ -352,10 +380,21 @@ export default class GoogleCalendarService implements Calendar {
timeZone: event.organizer.timeZone,
},
attendees: this.getAttendees({ event, hostExternalCalendarId: externalCalendarId }),
reminders: {
useDefault: true,
},
guestsCanSeeOtherGuests: !!event.seatsPerTimeSlot ? event.seatsShowAttendees : true,
reminders:
updateCustomReminderMinutes !== undefined && Number.isFinite(updateCustomReminderMinutes)
? {
useDefault: false,
overrides: [
{
method: "popup",
minutes: Math.max(0, Math.floor(updateCustomReminderMinutes)),
},
],
}
: {
useDefault: true,
},
guestsCanSeeOtherGuests: event.seatsPerTimeSlot ? event.seatsShowAttendees : true,
};

if (event.location) {
Expand Down
8 changes: 5 additions & 3 deletions packages/features/ee/teams/components/MemberList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,8 @@ function MemberListContent(props: Props) {
const canImpersonate = props.permissions?.canImpersonate ?? false;
const canResendInvitation = props.permissions?.canInvite ?? false;
const editMode =
[canChangeRole, canRemove, canImpersonate, canResendInvitation].some(Boolean) && !isSelf;
[canChangeRole, canRemove, canImpersonate, canResendInvitation].some(Boolean) &&
(!isSelf || props.isOrgAdminOrOwner);

const impersonationMode =
canImpersonate &&
Expand Down Expand Up @@ -668,14 +669,15 @@ function MemberListContent(props: Props) {
getFacetedUniqueValues: (_, columnId) => () => {
if (facetedTeamValues) {
switch (columnId) {
case "role":
case "role": {
// Include both traditional roles and PBAC custom roles
const allRoles = facetedTeamValues.roles.map((role) => ({
label: role.name,
value: role.id,
}));

return convertFacetedValuesToMap(allRoles);
}
default:
return new Map();
}
Expand All @@ -685,7 +687,7 @@ function MemberListContent(props: Props) {
getRowId: (row) => `${row.id}`,
});

const fetchMoreOnBottomReached = useFetchMoreOnBottomReached({
const _fetchMoreOnBottomReached = useFetchMoreOnBottomReached({
tableContainerRef,
hasNextPage,
fetchNextPage,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ function UserListTableContent({
);

// TODO (SEAN): Make Column filters a trpc query param so we can fetch serverside even if the data is not loaded
const totalRowCount = data?.meta?.totalRowCount ?? 0;
const _totalRowCount = data?.meta?.totalRowCount ?? 0;
const adminOrOwner = checkAdminOrOwner(org?.user?.role);

//we must flatten the array of arrays from the useInfiniteQuery hook
Expand Down Expand Up @@ -448,7 +448,7 @@ function UserListTableContent({
const isSelf = user.id === session?.user.id;

const permissionsForUser = {
canEdit: permissionsRaw.canEdit && user.accepted && !isSelf,
canEdit: permissionsRaw.canEdit && user.accepted && (!isSelf || adminOrOwner),
canRemove: permissionsRaw.canRemove && !isSelf,
canImpersonate:
user.accepted &&
Expand Down Expand Up @@ -514,7 +514,7 @@ function UserListTableContent({
value: team.name,
}))
);
default:
default: {
const attribute = facetedTeamValues.attributes.find((attr) => attr.id === columnId);
if (attribute) {
return convertFacetedValuesToMap(
Expand All @@ -525,6 +525,7 @@ function UserListTableContent({
);
}
return new Map();
}
}
}
return new Map();
Expand Down
6 changes: 6 additions & 0 deletions packages/types/Calendar.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Time } from "ical.js";
import type { Frequency } from "rrule";
import type z from "zod";

/* eslint-disable @typescript-eslint/no-explicit-any */
import type { bookingResponse } from "@calcom/features/bookings/lib/getBookingResponsesSchema";
import type { Calendar } from "@calcom/features/calendars/weeklyview";
import type { TimeFormat } from "@calcom/lib/timeFormat";
Expand Down Expand Up @@ -210,6 +211,9 @@ export interface CalendarEvent {
disableCancelling?: boolean;
disableRescheduling?: boolean;

// Optional per-calendar reminder override in minutes for integrations that support it
customReminderMinutes?: number;

// It has responses to all the fields(system + user)
responses?: CalEventResponses | null;

Expand Down Expand Up @@ -260,6 +264,8 @@ export interface IntegrationCalendar extends Ensure<Partial<_SelectedCalendar>,
export type SelectedCalendarEventTypeIds = (number | null)[];

export interface CalendarServiceEvent extends CalendarEvent {
// Optional per-calendar reminder override in minutes for integrations that support it
customReminderMinutes?: number;
calendarDescription: string;
}

Expand Down
Loading