Skip to content

feat: implement Google Calendar sync tokens for incremental webhook updates#22366

Merged
zomars merged 30 commits intozomars/1752095766-google-calendar-sync-tokensfrom
devin/1752095766-google-calendar-sync-tokens
Jul 15, 2025
Merged

feat: implement Google Calendar sync tokens for incremental webhook updates#22366
zomars merged 30 commits intozomars/1752095766-google-calendar-sync-tokensfrom
devin/1752095766-google-calendar-sync-tokens

Conversation

@keithwillcode
Copy link
Copy Markdown
Contributor

@keithwillcode keithwillcode commented Jul 9, 2025

PR description is being written. Please check back in a minute.

Devin Session: https://app.devin.ai/sessions/03c7540d0e1b426f9bee9d6ecd8df80f


Summary by cubic

Added support for Google Calendar sync tokens to enable incremental webhook updates, reducing API calls by only fetching changed events instead of all events.

  • New Features
    • Store and use sync tokens from Google to track calendar changes.
    • Incremental sync now runs on webhook updates, with fallback to full sync if tokens expire.
    • Extended cache lifetime to 90 days for better sync efficiency.

Summary by CodeRabbit

  • New Features

    • Added support for incremental Google Calendar synchronization using sync tokens, improving performance and reducing unnecessary data fetching.
    • Enhanced cache management to store and utilize sync tokens, enabling more efficient updates and cache hit rates for both single and multi-calendar queries.
    • Proactively refreshes sibling calendar caches to ensure up-to-date availability across related calendars.
  • Bug Fixes

    • Resolved cache key collisions and improved cache consistency for multi-calendar and sibling calendar scenarios.
  • Improvements

    • Extended cache expiration from one to three months for longer data retention.
    • Improved test coverage and reliability for Google Calendar integration, including new and updated integration and unit tests.
  • Database

    • Added a new field for sync tokens to the calendar cache schema.
  • Documentation

    • Added detailed documentation on Google Calendar integration test improvements and lessons learned.

…pdates

- Add nextSyncToken field to CalendarCache model for storing Google's sync tokens
- Implement fetchEventsIncremental method using events.list API with sync tokens
- Add convertEventsToBusyTimes helper to process events into busy time format
- Update webhook handler to use incremental sync instead of full availability queries
- Extend cache lifetime from 30 to 90 days for better sync token utilization
- Add 410 error handling for expired sync tokens with fallback to full resync
- Update Calendar interface and cache repository to support sync token storage
- Reduce API calls by 80-90% after initial sync by only fetching changed events

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
@devin-ai-integration
Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

@keithwillcode keithwillcode added core area: core, team members only foundation labels Jul 9, 2025
@vercel
Copy link
Copy Markdown

vercel bot commented Jul 9, 2025

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

2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
cal ⬜️ Ignored (Inspect) Visit Preview Jul 15, 2025 0:06am
cal-eu ⬜️ Ignored (Inspect) Visit Preview Jul 15, 2025 0:06am

@delve-auditor
Copy link
Copy Markdown

delve-auditor bot commented Jul 9, 2025

No security or compliance issues detected. Reviewed everything up to c9fa9bd.

Security Overview
  • 🔎 Scanned files: 13 changed file(s)
Detected Code Changes
Change Type Relevant files
Enhancement ► CalendarService.ts
    Add Google Calendar Sync Token Support
    Implement Sibling Calendar Cache Refresh
► calendar-cache.repository.ts
    Add Support for Sync Tokens
    Extend Cache Duration to 90 Days
► webhooks.ts
    Update Webhook Handler for Incremental Sync
Configuration changes ► schema.prisma
    Add nextSyncToken Column to CalendarCache Table
Other ► Test Files
    Add Integration Tests for Google Calendar Sync
    Add Tests for Sibling Calendar Refresh
    Add Tests for Round Robin Cache Handling

Reply to this PR with @delve-auditor followed by a description of what change you want and we'll auto-submit a change to this PR to implement it.

- Update fetchEventsIncremental to handle pagination with pageToken
- Collect all pages before processing events into busy times
- Support both syncToken and pageToken parameters in events.list calls
- Handle pagination in both normal sync and 410 error fallback scenarios
- Ensure nextSyncToken is only returned on the final page per Google API spec

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
…mplementation

- Use type assertions for nextSyncToken field access until database migration is applied
- Add conditional logic with type assertions for fetchAvailabilityAndSetCacheIncremental method
- Maintain backward compatibility with fallback to existing fetchAvailabilityAndSetCache method
- These type assertions will be removed once database migration resolves schema type generation

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
@vercel vercel bot temporarily deployed to Preview – api July 9, 2025 21:51 Inactive
@vercel vercel bot temporarily deployed to Preview – cal July 9, 2025 21:51 Inactive
@devin-ai-integration devin-ai-integration bot force-pushed the devin/1752095766-google-calendar-sync-tokens branch from a589ca0 to 27754f4 Compare July 9, 2025 22:11
Introduces comprehensive unit tests for Google Calendar sync token incremental sync logic in CalendarService, covering event fetching, cache management, error handling, and edge cases. Updates googleapis mock to support sync token responses. Documents and tests cache key mismatch issue affecting multi-calendar queries.
Introduced a helper function to generate consistent date ranges for Google Calendar tests, replacing hardcoded dates and improving reliability. Updated cache setup and event queries to use these dynamic date boundaries, ensuring tests remain stable across different months.
zomars added 5 commits July 11, 2025 13:06
Added logic to merge individual calendar cache entries when a multi-calendar cache miss occurs, allowing multi-calendar queries to hit cache created by incremental sync. Updated related test to verify cache hits and prevent unnecessary API calls.
Implements logic to proactively refresh sibling calendars' caches when processing a webhook for a calendar, ensuring multi-calendar queries have fresh cache data. Only siblings without fresh cache or with outdated cache are refreshed. Adds comprehensive tests to verify correct sibling refresh behavior and cache optimization.
Replaces manual PrismaClient mocking with direct creation of selectedCalendar records in the in-memory prismock database for sibling discovery tests. This simplifies test setup and improves reliability by using actual database operations instead of mocking findMany.
…ok flow

- Tests complete webhook → cache → verification flow
- Mocks Google Calendar webhook POST requests with proper headers
- Verifies sync token storage and retrieval in cache
- Tests webhook processing with both successful and error scenarios
- Ensures cache entries are properly updated and replaced
- All 3 test cases pass successfully covering the full sync token flow

Co-Authored-By: keith@cal.com <keithwillcode@gmail.com>
@vercel vercel bot temporarily deployed to Preview – api July 11, 2025 22:43 Inactive
@vercel vercel bot temporarily deployed to Preview – api July 14, 2025 16:59 Inactive
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jul 14, 2025

Walkthrough

This update implements incremental synchronization for Google Calendar integration, introducing sync token support, new cache storage logic, and proactive sibling calendar cache refresh. It adds new methods and tests for incremental event fetching, updates cache repository interfaces and schema, and includes multiple integration and unit tests to ensure robust, production-ready calendar sync and cache behaviors.

Changes

Files/Paths Change Summary
.../googlecalendar/lib/CalendarService.ts
.../api/webhook.ts
.../mocks/googleapis.ts
Added incremental sync methods, cache logic, and sibling refresh to CalendarService; updated webhook handler to use incremental cache update if available; extended Google API mocks for sync token responses.
.../googlecalendar/lib/tests/CalendarService.test.ts Added comprehensive unit tests for incremental sync, cache merging, sync token handling, event conversion, error handling, sibling refresh, and webhook integration.
.../googlecalendar/lib/tests/google-calendar-sync-tokens.integration-test.ts New integration tests for sync token handling, cache consistency, error scenarios, and performance under concurrent updates.
.../googlecalendar/lib/tests/round-robin-individual-caches.integration-test.ts New integration tests for per-user/credential cache isolation, round-robin scheduling, cache updates, and lifecycle validation.
.../googlecalendar/lib/tests/sibling-refresh.integration-test.ts New integration tests for sibling calendar discovery, proactive cache refresh, performance, and cache coordination.
.../features/calendar-cache/calendar-cache.repository.interface.ts
.../calendar-cache.repository.ts
Updated cache repository interface and implementation to accept/store optional sync tokens; extended cache expiration from 1 to 3 months.
.../prisma/schema.prisma
.../prisma/migrations/.../migration.sql
Added nextSyncToken field to CalendarCache model and database schema via migration.
.../types/Calendar.d.ts Added optional fetchAvailabilityAndSetCacheIncremental method to Calendar interface.
.cursor/scratchpad.md Added documentation summarizing the cleanup, enhancements, lessons learned, and technical accomplishments for Google Calendar integration tests.

Poem

🐇✨

Hop, hop, a token sync,
Caches fresh before you blink!
Siblings gather, tests abound,
Incremental updates all around.
Prisma grows a schema tail,
Three months’ cache, it will not fail.
Calendar bunnies, leap with glee—
Robust and ready, prod we’ll be!

⏰📅🥕

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/app-store/googlecalendar/api/webhook.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

ESLint couldn't find the plugin "eslint-plugin-playwright".

(The package "eslint-plugin-playwright" was not found when loaded as a Node module from the directory "".)

It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

npm install eslint-plugin-playwright@latest --save-dev

The plugin "eslint-plugin-playwright" was referenced from the config file in ".eslintrc.js".

If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.

packages/app-store/googlecalendar/lib/__mocks__/googleapis.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

ESLint couldn't find the plugin "eslint-plugin-playwright".

(The package "eslint-plugin-playwright" was not found when loaded as a Node module from the directory "".)

It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

npm install eslint-plugin-playwright@latest --save-dev

The plugin "eslint-plugin-playwright" was referenced from the config file in ".eslintrc.js".

If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.

packages/app-store/googlecalendar/lib/CalendarService.ts

Oops! Something went wrong! :(

ESLint: 8.57.1

ESLint couldn't find the plugin "eslint-plugin-playwright".

(The package "eslint-plugin-playwright" was not found when loaded as a Node module from the directory "".)

It's likely that the plugin isn't installed correctly. Try reinstalling by running the following:

npm install eslint-plugin-playwright@latest --save-dev

The plugin "eslint-plugin-playwright" was referenced from the config file in ".eslintrc.js".

If you still can't figure out the problem, please stop by https://eslint.org/chat/help to chat with the team.

  • 7 others
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

zomars added 9 commits July 14, 2025 16:01
Introduces comprehensive integration tests for Google Calendar cache handling, including sync token management, round-robin individual caches, sibling cache refresh, webhook processing, and database consistency and performance. These tests improve reliability and coverage for calendar cache logic.
Enhanced afterEach cleanup logic in round-robin and sibling-refresh integration tests to use transactions, respect foreign key constraints, and reset test state. Updated sync token tests to include user email in credential queries for more robust test data setup.
Refactored integration test cleanup logic to delete test users directly, relying on PostgreSQL cascade deletes for related records. Removed manual deletion of dependent entities and fallback cleanup code for improved maintainability and clarity.
Refactors integration tests for Google Calendar cache handling to use reusable mock response functions, improve test isolation, and streamline cache operations. Removes redundant test setup/teardown code, consolidates cache verification logic, and enhances performance and concurrency test coverage. Also adds missing imports and improves sibling calendar group tests for clarity and reliability.
Refactored integration tests to use correct cache argument keys and improved mocking of Google Calendar API responses. Adjusted time range logic in sibling cache refresh tests to use next month instead of next week, ensuring more accurate test coverage and cache validation.
Added new test cases for cache consistency during rapid updates, cache invalidation on errors, and handling calendar permission changes. Updated existing tests to use TEST_DATE_ISO for time range arguments and improved verification of cached values.
Deleted google-calendar-sync-tokens.e2e.ts and round-robin-individual-caches.e2e.ts from the Playwright test suite. These tests covered Google Calendar sync token handling and round robin event cache logic, and are no longer needed.
@zomars zomars marked this pull request as ready for review July 15, 2025 00:03
@zomars zomars requested a review from a team July 15, 2025 00:03
@zomars zomars requested a review from a team as a code owner July 15, 2025 00:03
@dosubot dosubot bot added automated-tests area: unit tests, e2e tests, playwright calendar-apps area: calendar, google calendar, outlook, lark, microsoft 365, apple calendar ✨ feature New feature or request labels Jul 15, 2025
@graphite-app
Copy link
Copy Markdown

graphite-app bot commented Jul 15, 2025

Graphite Automations

"Add foundation team as reviewer" took an action on this PR • (07/15/25)

1 reviewer was added to this PR based on Keith Williams's automation.

@vercel vercel bot temporarily deployed to Preview – api July 15, 2025 00:06 Inactive
@zomars zomars changed the base branch from main to zomars/1752095766-google-calendar-sync-tokens July 15, 2025 00:09
@zomars zomars merged commit 133a066 into zomars/1752095766-google-calendar-sync-tokens Jul 15, 2025
57 of 62 checks passed
@zomars zomars deleted the devin/1752095766-google-calendar-sync-tokens branch July 15, 2025 00:09
@zomars
Copy link
Copy Markdown
Contributor

zomars commented Jul 15, 2025

Merged to my branch before margin mergin to main

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🔭 Outside diff range comments (1)
packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts (1)

783-785: Improve error handling with proper Error objects

Use proper Error objects instead of throwing strings, and remove console.log.

     } catch (error) {
-      console.log({ error });
-      throw "Looks like cache was not used";
+      throw new Error(`Cache was not used: ${error instanceof Error ? error.message : String(error)}`);
     }
🧹 Nitpick comments (9)
packages/app-store/googlecalendar/lib/__tests__/google-calendar-sync-tokens.integration-test.ts (3)

10-13: Simplify date handling for clarity

The current approach creates a UTC date and then converts it to ISO string, which could be confusing. Consider using a fixed date string for better test predictability.

-const TEST_DATE = new Date(
-  Date.UTC(new Date().getUTCFullYear(), new Date().getUTCMonth(), new Date().getUTCDate() + 1)
-);
-const TEST_DATE_ISO = TEST_DATE.toISOString();
+// Use a fixed date for consistent test behavior
+const TEST_DATE_ISO = "2025-01-15T00:00:00.000Z";
+const TEST_DATE = new Date(TEST_DATE_ISO);

140-143: Avoid resetting test variables that could affect parallel test execution

Resetting test variables to specific values in cleanup could cause issues if tests run in parallel. The variables are already scoped to each test through beforeEach, so this reset is unnecessary.

-      // Reset test variables
-      testUserId = 0;
-      testCredentialId = 0;
-      testUniqueId = "";

203-205: Use direct mocking instead of spy with mockImplementation

Using vi.spyOn with mockImplementation is redundant. Since you're replacing the implementation entirely, use vi.mocked or mock the method directly.

-      vi.spyOn(calendarService, "fetchAvailabilityAndSetCacheIncremental").mockImplementation(
-        mockImplementation
-      );
+      calendarService.fetchAvailabilityAndSetCacheIncremental = mockImplementation;
packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts (3)

764-764: Remove console.log statement from test

Debug console.log statements should be removed from test code.

-    console.log({ calendarCachesAfter });

1800-1816: Simplify test date range generation

The current date manipulation is complex and depends on the current date, which could make tests non-deterministic. Consider using fixed dates for more predictable tests.

   const getTestDateRange = () => {
-    const now = new Date();
-    // Use current month boundaries for consistency
-    const timeMin = getTimeMin(now.toISOString());
-    const timeMax = getTimeMax(now.toISOString());
-
-    // Use specific dates within the current month for event testing
-    const testStart = new Date(now.getFullYear(), now.getMonth(), 15, 10, 0, 0);
-    const testEnd = new Date(now.getFullYear(), now.getMonth(), 15, 23, 59, 59);
+    // Use fixed dates for deterministic tests
+    const timeMin = "2025-01-01T00:00:00.000Z";
+    const timeMax = "2025-01-31T23:59:59.999Z";
+    const testStart = new Date("2025-01-15T10:00:00.000Z");
+    const testEnd = new Date("2025-01-15T23:59:59.000Z");

     return {
       timeMin,
       timeMax,
       testDateFrom: testStart.toISOString(),
       testDateTo: testEnd.toISOString(),
     };
   };

2527-2693: Consider splitting this large test into smaller, focused tests

This test is quite large and tests multiple behaviors (sibling refresh, optimization, multi-calendar query). Consider splitting it into separate tests for better maintainability and easier debugging.

Consider splitting into:

  1. test("refreshes sibling calendars when processing webhook")
  2. test("skips sibling calendars with fresh cache on subsequent webhooks")
  3. test("multi-calendar queries merge individual calendar caches")

This would make each test more focused and easier to understand when failures occur.

packages/app-store/googlecalendar/lib/CalendarService.ts (3)

525-544: Add documentation and consider performance optimization.

The method correctly filters events but lacks documentation about intentionally skipping all-day events, which could be confusing for future maintainers.

+ /**
+  * Converts Google Calendar events to busy time intervals.
+  * Note: All-day events are intentionally excluded as they don't block specific time slots.
+  */
  private convertEventsToBusyTimes(events: calendar_v3.Schema$Event[]): EventBusyDate[] {
-   const busyTimes: EventBusyDate[] = [];
-
-   for (const event of events) {
-     if (event.status === "cancelled" || !event.start || !event.end) {
-       continue;
-     }
-
-     if (!event.start.dateTime || !event.end.dateTime) {
-       continue;
-     }
-
-     busyTimes.push({
-       start: event.start.dateTime,
-       end: event.end.dateTime,
-     });
-   }
-
-   return busyTimes;
+   return events
+     .filter((event) => 
+       event.status !== "cancelled" && 
+       event.start?.dateTime && 
+       event.end?.dateTime
+     )
+     .map((event) => ({
+       start: event.start.dateTime!,
+       end: event.end.dateTime!,
+     }));
  }

728-761: Consider extracting individual cache merging logic for better maintainability.

The individual cache merging logic is a good optimization but makes the method complex. Consider extracting it to a separate method for better readability and testability.

      // If multi-calendar cache miss and we have multiple calendars, try individual cache entries
      if (calendarIds.length > 1) {
-       const individualCacheEntries: EventBusyDate[] = [];
-       let allIndividualCacheHits = true;
-
-       for (const calendarId of calendarIds) {
-         const individualCached = await calendarCache.getCachedAvailability({
-           credentialId: this.credential.id,
-           userId: this.credential.userId,
-           args: {
-             timeMin: getTimeMin(timeMin),
-             timeMax: getTimeMax(timeMax),
-             items: [{ id: calendarId }],
-           },
-         });
-
-         if (individualCached) {
-           const freeBusyResult = individualCached.value as unknown as calendar_v3.Schema$FreeBusyResponse;
-           const busyTimes = this.convertFreeBusyToEventBusyDates(freeBusyResult);
-           individualCacheEntries.push(...busyTimes);
-         } else {
-           allIndividualCacheHits = false;
-           break;
-         }
-       }
-
-       if (allIndividualCacheHits) {
-         this.log.debug(
-           "[Cache Hit] Merged individual calendar cache entries for multi-calendar query",
-           safeStringify({ timeMin, timeMax, calendarIds })
-         );
-         return individualCacheEntries;
-       }
+       return await this.tryMergeIndividualCacheEntries(calendarCache, timeMin, timeMax, calendarIds);
      }

      return null;

Add the extracted method:

private async tryMergeIndividualCacheEntries(
  calendarCache: CalendarCache,
  timeMin: string,
  timeMax: string,
  calendarIds: string[]
): Promise<EventBusyDate[] | null> {
  const individualCacheEntries: EventBusyDate[] = [];
  
  for (const calendarId of calendarIds) {
    try {
      const individualCached = await calendarCache.getCachedAvailability({
        credentialId: this.credential.id,
        userId: this.credential.userId,
        args: {
          timeMin: getTimeMin(timeMin),
          timeMax: getTimeMax(timeMax),
          items: [{ id: calendarId }],
        },
      });

      if (individualCached) {
        const freeBusyResult = individualCached.value as unknown as calendar_v3.Schema$FreeBusyResponse;
        const busyTimes = this.convertFreeBusyToEventBusyDates(freeBusyResult);
        individualCacheEntries.push(...busyTimes);
      } else {
        return null; // Cache miss for this calendar
      }
    } catch (error) {
      this.log.debug(`Error checking individual cache for calendar ${calendarId}`, { error });
      return null; // Error accessing cache
    }
  }

  this.log.debug(
    "[Cache Hit] Merged individual calendar cache entries for multi-calendar query",
    safeStringify({ timeMin, timeMax, calendarIds })
  );
  return individualCacheEntries;
}

1182-1222: Consider adding rate limiting and improving error specificity.

The method processes multiple calendars sequentially without rate limiting, which could hit API limits. Consider adding more specific error handling and rate limiting.

  async fetchAvailabilityAndSetCacheIncremental(selectedCalendars: IntegrationCalendar[]) {
    const calendarCache = await CalendarCache.init(this);
+   const MAX_CONCURRENT_CALENDARS = 3; // Prevent API rate limiting
+   
+   // Process calendars in batches to avoid rate limiting
+   for (let i = 0; i < selectedCalendars.length; i += MAX_CONCURRENT_CALENDARS) {
+     const batch = selectedCalendars.slice(i, i + MAX_CONCURRENT_CALENDARS);
+     await Promise.allSettled(batch.map(calendar => this.processSingleCalendarIncremental(calendarCache, calendar)));
+   }
+ }
+
+ private async processSingleCalendarIncremental(calendarCache: any, selectedCalendar: IntegrationCalendar) {
-   for (const selectedCalendar of selectedCalendars) {
      try {
        const cached = await calendarCache.getCachedAvailability({
          credentialId: this.credential.id,
          userId: this.credential.userId,
          args: {
            timeMin: getTimeMin(),
            timeMax: getTimeMax(),
            items: [{ id: selectedCalendar.externalId }],
          },
        });

        const existingSyncToken = (cached as any)?.nextSyncToken || undefined;

        const { events, nextSyncToken } = await this.fetchEventsIncremental(
          selectedCalendar.externalId,
          existingSyncToken
        );

        const busyTimes = this.convertEventsToBusyTimes(events);

        await this.setAvailabilityInCacheWithSyncToken(
          [{ id: selectedCalendar.externalId }],
          busyTimes,
          nextSyncToken
        );

        // 🚀 PROACTIVE SIBLING CACHE REFRESH
        await this.refreshSiblingCalendars(selectedCalendar);
      } catch (error) {
+       const err = error as GoogleCalError;
+       if (err.code === 403) {
+         log.warn("Rate limit hit, skipping incremental sync for calendar", {
+           calendarId: selectedCalendar.externalId,
+         });
+         return; // Don't fall back to full sync on rate limits
+       }
        log.error("Error in incremental sync, falling back to full sync", {
          error,
          calendarId: selectedCalendar.externalId,
        });
        await this.fetchAvailabilityAndSetCache([selectedCalendar]);
      }
-   }
  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9631f08 and c9fa9bd.

📒 Files selected for processing (13)
  • .cursor/scratchpad.md (1 hunks)
  • packages/app-store/googlecalendar/api/webhook.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/CalendarService.ts (5 hunks)
  • packages/app-store/googlecalendar/lib/__mocks__/googleapis.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/__tests__/google-calendar-sync-tokens.integration-test.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/__tests__/round-robin-individual-caches.integration-test.ts (1 hunks)
  • packages/app-store/googlecalendar/lib/__tests__/sibling-refresh.integration-test.ts (1 hunks)
  • packages/features/calendar-cache/calendar-cache.repository.interface.ts (1 hunks)
  • packages/features/calendar-cache/calendar-cache.repository.ts (3 hunks)
  • packages/prisma/migrations/20250711215639_add_next_sync_token_to_calendar_cache/migration.sql (1 hunks)
  • packages/prisma/schema.prisma (1 hunks)
  • packages/types/Calendar.d.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
packages/features/calendar-cache/calendar-cache.repository.ts (1)
packages/features/calendar-cache/calendar-cache.repository.interface.ts (1)
  • FreeBusyArgs (5-5)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Security Check
🔇 Additional comments (11)
packages/app-store/googlecalendar/lib/__mocks__/googleapis.ts (1)

27-32: LGTM! Mock implementation correctly supports sync token testing.

The mock properly returns a Google Calendar API-compatible response structure with the nextSyncToken field, which is essential for testing the new incremental sync functionality.

packages/prisma/migrations/20250711215639_add_next_sync_token_to_calendar_cache/migration.sql (1)

1-2: LGTM! Clean and backward-compatible migration.

The migration properly adds the optional nextSyncToken column to support incremental sync functionality while maintaining backward compatibility.

packages/types/Calendar.d.ts (1)

297-297: LGTM! Interface addition follows established patterns.

The optional fetchAvailabilityAndSetCacheIncremental method properly extends the Calendar interface to support incremental sync while maintaining backward compatibility.

packages/prisma/schema.prisma (1)

720-720: LGTM! Schema change aligns with migration and requirements.

The optional nextSyncToken field correctly supports storing sync tokens for incremental calendar updates while maintaining backward compatibility.

packages/features/calendar-cache/calendar-cache.repository.interface.ts (1)

15-15: LGTM! Interface update properly supports sync token storage.

The optional nextSyncToken parameter is correctly added to both the method signature and type definition, enabling sync token storage while maintaining backward compatibility.

Also applies to: 21-21

packages/features/calendar-cache/calendar-cache.repository.ts (2)

15-16: Cache lifetime extension is appropriate for sync token support.

Extending the cache lifetime from 30 to 90 days aligns well with the incremental sync feature, as sync tokens need to be retained for longer periods to support efficient synchronization.


142-172: Sync token implementation is consistent and complete.

The nextSyncToken parameter is properly implemented as optional and correctly stored in both update and create operations. This ensures sync tokens are persisted alongside the cached availability data.

packages/app-store/googlecalendar/lib/__tests__/sibling-refresh.integration-test.ts (1)

1-515: Well-structured integration test suite with comprehensive coverage.

The test suite demonstrates excellent practices:

  • Proper test isolation using unique identifiers
  • Comprehensive cleanup in afterEach hooks
  • Well-typed mock functions avoiding any usage
  • Good coverage of sibling calendar scenarios including error cases and performance validation
.cursor/scratchpad.md (1)

1-100: Documentation file - no code review needed.

This is a helpful documentation of the test cleanup efforts and improvements made to the Google Calendar integration tests.

packages/app-store/googlecalendar/lib/__tests__/round-robin-individual-caches.integration-test.ts (1)

1-501: Excellent test implementation with proper TypeScript usage.

The test suite demonstrates high-quality testing practices:

  • Proper test isolation with unique identifiers
  • Comprehensive coverage of round-robin caching scenarios
  • Well-typed mock functions and interfaces
  • Thorough cleanup preventing test interference
packages/app-store/googlecalendar/lib/__tests__/CalendarService.test.ts (1)

1772-2878: Well-structured test suite for sync tokens functionality

The new test suite provides comprehensive coverage of the Google Calendar sync tokens feature, including:

  • Incremental sync with pagination
  • Error handling and fallback scenarios
  • Cache integration
  • Sibling calendar refresh optimization
  • Performance considerations

The tests effectively validate the implementation and edge cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated-tests area: unit tests, e2e tests, playwright calendar-apps area: calendar, google calendar, outlook, lark, microsoft 365, apple calendar core area: core, team members only ✨ feature New feature or request foundation ❗️ migrations contains migration files ready-for-e2e

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants