Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Populate gCal calendar cache via webhooks #11928

Merged
merged 87 commits into from
Nov 15, 2024

Conversation

zomars
Copy link
Member

@zomars zomars commented Oct 17, 2023

fixes #11524

What does this PR do?

  • NEW Loom Demo
  • OLD Loom Demo
  • Inbound Webhooks Cache Refresh Demo
  • Adds a mechanism to listen to changes in Google Calendar
  • Adds an inbound webhook to pre-populate gCal cache
  • Adds a team feature flag so we can enable this progressively
  • Adds a cron job that handles calendar subscriptions and renewals before expiration.

Calendar Cache and Cron Jobs Enhancements:

API Handler Refactoring:

Authentication and Security Improvements:

Calendar Cache and Google Calendar Integration:

These changes aim to improve the efficiency, security, and reliability of calendar-related features in the application.

TODO (Follow up maybe)

  • Add a mechanism to auto-refresh about-to-expire webhooks
  • Add a mechanism to bypass cache either via request header or query parameter
  • Add a way to bulk enable for ALL users, once we feel confident enough to do so

Type of change

  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)

How should this be tested?

  1. Create an accessible tunnel from a service like tmole
  2. Put the resulting URL .env like GOOGLE_WEBHOOK_URL="https://xxxxxxxxx.tunnelmole.net"
  3. Make sure your running the cron using yarn dev:cron inside apps/web directory.
  4. As a team member, Install a Google Calendar from the App Store
  5. Once installed, enable a selected calendar
  6. In the DB create a new TeamFeature entry with the proper teamId and a featureId named calendar-cache
  7. You should receive a POST request populating your first cache
  8. Add an event to your Google Calendar, you should receive another POST request, refreshing the cache.

Mandatory Tasks

  • Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.

@vercel
Copy link

vercel bot commented Oct 17, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
api ❌ Failed (Inspect) Nov 15, 2024 2:40am
dev ✅ Ready (Inspect) Visit Preview 💬 Add feedback Nov 15, 2024 2:40am
6 Skipped Deployments
Name Status Preview Comments Updated (UTC)
ai ⬜️ Ignored (Inspect) Visit Preview Nov 15, 2024 2:40am
cal ⬜️ Ignored (Inspect) Visit Preview Nov 15, 2024 2:40am
cal-demo ⬜️ Ignored (Inspect) Visit Preview Nov 15, 2024 2:40am
calcom-web-canary ⬜️ Ignored (Inspect) Visit Preview Nov 15, 2024 2:40am
qa ⬜️ Ignored (Inspect) Visit Preview Nov 15, 2024 2:40am
ui ⬜️ Ignored (Inspect) Visit Preview Nov 15, 2024 2:40am

@github-actions
Copy link
Contributor

github-actions bot commented Oct 17, 2023

Thank you for following the naming conventions! 🙏 Feel free to join our discord and post your PR link.

Copy link
Member Author

Choose a reason for hiding this comment

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

Migrated procedures to new boilerplate syntax

@github-actions
Copy link
Contributor

github-actions bot commented Oct 17, 2023

📦 Next.js Bundle Analysis for @calcom/web

This analysis was generated by the Next.js Bundle Analysis action. 🤖

New Page Added

The following page was added to the bundle from the code in this PR:

Page Size (compressed) First Load % of Budget (350 KB)
/settings/admin/calendar-cache 271.49 KB 459.65 KB 131.33%

@deploysentinel
Copy link

deploysentinel bot commented Oct 17, 2023

Current Playwright Test Results Summary

✅ 447 Passing - ⚠️ 15 Flaky

Run may still be in progress, this comment will be updated as current testing workflow or job completes...

(Last updated on 02/14/2024 11:21:12pm UTC)

Run Details

Running Workflow PR Update on Github Actions

Commit: d77b02f

Started: 02/14/2024 11:12:29pm UTC

⚠️ Flakes

📄   apps/web/playwright/booking/multipleEmailQuestion.e2e.ts • 1 Flake

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Booking With Multiple Email Question and Each Other Question Booking With Multiple Email Question and checkbox group Question Multiple Email required and checkbox group required
Retry 1Initial Attempt
0.40% (1) 1 / 253 run
failed over last 7 days
4.74% (12) 12 / 253 runs
flaked over last 7 days

📄   apps/web/playwright/booking/selectQuestion.e2e.ts • 3 Flakes

Top 1 Common Error Messages

null

3 Test Cases Affected

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Booking With Phone Question and Each Other Question Booking With Select Question and checkbox group Question Select and checkbox group not required
Retry 1Initial Attempt
0% (0) 0 / 256 runs
failed over last 7 days
5.47% (14) 14 / 256 runs
flaked over last 7 days
Booking With Phone Question and Each Other Question Booking With Select Question and Radio group Question Select required and Radio group required
Retry 1Initial Attempt
0% (0) 0 / 256 runs
failed over last 7 days
3.91% (10) 10 / 256 runs
flaked over last 7 days
Booking With Phone Question and Each Other Question Booking With Select Question and Short text question Select and Short text not required
Retry 1Initial Attempt
0% (0) 0 / 256 runs
failed over last 7 days
4.30% (11) 11 / 256 runs
flaked over last 7 days

📄   apps/web/playwright/booking/responsiveBooking.e2e.ts • 2 Flakes

Top 1 Common Error Messages

null

2 Test Cases Affected

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Booking page with no questions Booking page with 1920x1080 resolution
Retry 1Initial Attempt
0% (0) 0 / 256 runs
failed over last 7 days
8.20% (21) 21 / 256 runs
flaked over last 7 days
Booking page with no questions Booking page with 640x480 resolution
Retry 1Initial Attempt
1.56% (4) 4 / 256 runs
failed over last 7 days
6.64% (17) 17 / 256 runs
flaked over last 7 days

📄   apps/web/playwright/login.e2e.ts • 1 Flake

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
user can login & logout succesfully -- future login flow user & logout using dashboard
Retry 1Initial Attempt
0.78% (2) 2 / 256 runs
failed over last 7 days
45.31% (116) 116 / 256 runs
flaked over last 7 days

📄   apps/web/playwright/integrations-stripe.e2e.ts • 1 Flake

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Stripe integration Paid booking should be able to be rescheduled
Retry 1Initial Attempt
0.76% (2) 2 / 262 runs
failed over last 7 days
3.82% (10) 10 / 262 runs
flaked over last 7 days

📄   apps/web/playwright/booking/addressQuestione2e/addressQuestion.e2e.ts • 1 Flake

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Booking With Address Question and Each Other Question Booking With Address Question and select Question Address required and select required
Retry 1Initial Attempt
0% (0) 0 / 261 runs
failed over last 7 days
4.21% (11) 11 / 261 runs
flaked over last 7 days

📄   apps/web/playwright/event-types.e2e.ts • 1 Flake

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Event Types tests -- future user Different Locations Tests can add Attendee Phone Number location and book with it
Retry 1Initial Attempt
0.74% (2) 2 / 271 runs
failed over last 7 days
11.44% (31) 31 / 271 runs
flaked over last 7 days

📄   apps/web/playwright/profile.e2e.ts • 2 Flakes

Top 1 Common Error Messages

null

2 Test Cases Affected

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Update Profile Cannot update a users email when existing user has same email (verification enabled)
Retry 1Initial Attempt
0.42% (1) 1 / 239 run
failed over last 7 days
50.63% (121) 121 / 239 runs
flaked over last 7 days
Update Profile Can update a users email (verification enabled)
Retry 1Initial Attempt
11.93% (29) 29 / 243 runs
failed over last 7 days
52.67% (128) 128 / 243 runs
flaked over last 7 days

📄   packages/embeds/embed-core/playwright/tests/preview.e2e.ts • 1 Flake

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Preview Preview - embed-core should load
Retry 1Initial Attempt
0% (0) 0 / 273 runs
failed over last 7 days
35.53% (97) 97 / 273 runs
flaked over last 7 days

📄   packages/app-store/routing-forms/playwright/tests/basic.e2e.ts • 1 Flake

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Routing Forms Seeded Routing Form Router URL should work
Retry 1Initial Attempt
0% (0) 0 / 272 runs
failed over last 7 days
14.34% (39) 39 / 272 runs
flaked over last 7 days

📄   packages/embeds/embed-core/playwright/tests/inline.e2e.ts • 1 Flake

Test Case Results

Test Case Last 7 days Failures Last 7 days Flakes
Inline Iframe Inline Iframe - Configured with Dark Theme
Retry 1Initial Attempt
0.73% (2) 2 / 274 runs
failed over last 7 days
42.34% (116) 116 / 274 runs
flaked over last 7 days

View Detailed Build Results


Copy link
Member Author

@zomars zomars left a comment

Choose a reason for hiding this comment

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

Ready for review. Happy anniversary!

* This runs each minute and we need fresh data each time
* @see https://nextjs.org/docs/app/building-your-application/caching#opting-out-2
**/
export const revalidate = 0;
Copy link
Member Author

Choose a reason for hiding this comment

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

So cron is always fresh

() => ""
);

const Page = () => <h1>Calendar cache index</h1>;
Copy link
Member Author

Choose a reason for hiding this comment

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

This is a placeholder page where we might control cache entries or remove them if necessary.

@@ -20,7 +23,7 @@ try {
"*/5 * * * * *",
async function () {
await Promise.allSettled([
fetchCron("/tasks/cron"),
fetchCron("/calendar-cache/cron"),
Copy link
Member Author

Choose a reason for hiding this comment

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

For testing locally

const user = req.userWithCredentials;
const { integration, externalId, credentialId } = selectedCalendarSelectSchema.parse(req.query);
const calendarCacheRepository = await CalendarCache.initFromCredentialId(credentialId);
await calendarCacheRepository.unwatchCalendar({ calendarId: externalId });
Copy link
Member Author

Choose a reason for hiding this comment

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

Even tho we already handle subscriptions in the cron, we make sure we purge cache when removing a selected calendar.

@@ -8,6 +8,10 @@
"path": "/api/tasks/cron",
"schedule": "* * * * *"
},
{
"path": "/api/calendar-cache/cron",
"schedule": "* * * * *"
Copy link
Member Author

Choose a reason for hiding this comment

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

Running each minute


import type * as OAuthManager from "../../_utils/oauth/OAuthManager";

vi.mock("../../_utils/oauth/OAuthManager", () => oAuthManagerMock);

beforeEach(() => {
mockReset(oAuthManagerMock);
mockClear(oAuthManagerMock);
Copy link
Member Author

Choose a reason for hiding this comment

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

@hariombalhara this was causing errors. IDK if this can have unintended behaviors. Left more context on Campsite.

const passedSelectedCalendars = selectedCalendars.filter((sc) => sc.integration === type);
const passedSelectedCalendars = selectedCalendars
.filter((sc) => sc.integration === type)
// Needed to ensure cache keys are consistent
Copy link
Member Author

Choose a reason for hiding this comment

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

Again, to increase chance of cache hits.

handleCalendarsToUnwatch(),
]);

// TODO: Credentials can be installed on a whole team, check for selected calendars on the team
Copy link
Member Author

Choose a reason for hiding this comment

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

cc @joeauyeung you might have some ideas here?

Comment on lines +31 to +32
// new URLSearchParams does not accept numbers
credentialId: String(credentialId),
Copy link
Member Author

Choose a reason for hiding this comment

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

We use coerce on server side so we're good.

Copy link
Member Author

Choose a reason for hiding this comment

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

Useful query to have. Also we could use it in as typedRaw query once we upgrade prisma


export default defaultResponder(async (req: NextApiRequest, res: NextApiResponse) => {
await authMiddleware(req);
return defaultHandler({
Copy link
Contributor

Choose a reason for hiding this comment

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

Very nice changes in this file 🙏

const isCalendarCacheEnabledGlobally = await featureRepo.checkIfFeatureIsEnabledGlobally(
"calendar-cache"
);
if (isCalendarCacheEnabledGlobally) return new CalendarCacheRepository(calendar);
Copy link
Contributor

Choose a reason for hiding this comment

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

Very nice approach

Copy link
Contributor

Choose a reason for hiding this comment

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

This is a great example of pulling the "if" statements as high up in the app as possible and letting IoC take over.

@zomars zomars requested a review from keithwillcode November 15, 2024 02:10
@zomars
Copy link
Member Author

zomars commented Nov 15, 2024

@keithwillcode feedback addressed. 🙏

);

if (!selectedCalendar) {
throw new HttpError({
Copy link
Contributor

Choose a reason for hiding this comment

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

This is weird to throw an exception for a 200. Maybe in a follow-up we can refactor this.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm aware. But defaultResponder just handles the proper response and code.

Copy link
Contributor

@keithwillcode keithwillcode left a comment

Choose a reason for hiding this comment

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

Great work, @zomars.

As discussed. This will be launched dark and tested from there.

@keithwillcode keithwillcode merged commit d294a74 into main Nov 15, 2024
38 of 39 checks passed
@keithwillcode keithwillcode deleted the feat/calendar-cache-inbound branch November 15, 2024 17:59
@PeerRich
Copy link
Member

yeah lets test with i.cal.com org for now

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api area: API, enterprise API, access token, OAuth app-store area: app store, apps, calendar integrations, google calendar, outlook, lark, apple calendar core area: core, team members only ❗️ .env changes contains changes to env variables ✨ feature New feature or request foundation High priority Created by Linear-GitHub Sync Medium priority Created by Linear-GitHub Sync ❗️ migrations contains migration files performance area: performance, page load, slow, slow endpoints, loading screen, unresponsive ready-for-e2e webhooks area: webhooks, callback, webhook payload
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[CAL-2524] Inbound Webooks from gCal
9 participants