Skip to content

perf: optimize analytics app imports to avoid loading entire app store#23372

Merged
keithwillcode merged 5 commits intomainfrom
devin/analytics-import-optimization-1756236003
Aug 26, 2025
Merged

perf: optimize analytics app imports to avoid loading entire app store#23372
keithwillcode merged 5 commits intomainfrom
devin/analytics-import-optimization-1756236003

Conversation

@keithwillcode
Copy link
Copy Markdown
Contributor

@keithwillcode keithwillcode commented Aug 26, 2025

What does this PR do?

This PR optimizes analytics app imports to avoid loading the entire app store (100+ apps) when only analytics functionality is needed, following the same pattern established in PR #22450 for calendar services.

Problem: The import trace showed that accessing localhost:3000/api/trpc/i18n was importing the entire app store through getAnalytics.ts, causing unnecessary bundle bloat and performance issues.

Solution:

  • Creates a dedicated analytics.services.generated.ts file that only imports analytics apps with AnalyticsService implementations
  • Updates getAnalytics.ts to use AnalyticsServiceMap instead of the full appStore
  • Adds generation logic to the app-store-cli build process
  • Currently includes only the dub app which has an AnalyticsService implementation

Visual Demo (For contributors especially)

Before: Import trace shows entire app store being loaded:

./packages/app-store/index.ts
./packages/app-store/_utils/getAnalytics.ts

After: Only analytics-specific apps are imported:

./packages/app-store/analytics.services.generated.ts
./packages/app-store/_utils/getAnalytics.ts

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. N/A - internal optimization, no user-facing changes.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

Critical testing needed:

  1. Import trace verification: Access localhost:3000/api/trpc/i18n and verify the import trace no longer includes the full app store
  2. Analytics functionality: Test that analytics events are still properly tracked and sent to configured analytics services
  3. Bundle analysis: Compare bundle sizes before/after to confirm the optimization impact

Test configuration:

  • Set up a test booking with analytics tracking enabled (e.g., Dub integration)
  • Verify analytics events are fired correctly during booking flow
  • Check that no analytics functionality is broken

⚠️ High-priority review areas:

  • The module wrapping logic in getAnalytics.ts (lines 40-41) looks suspicious and may indicate module structure mismatch
  • Only one analytics app (dub) currently benefits from this optimization - impact may be limited
  • Verify the filter logic correctly identifies analytics apps with AnalyticsService implementations

Checklist

  • My code follows the style guidelines of this project
  • I have checked if my changes generate no new warnings
  • I have tested the build process and type checking

Link to Devin run: https://app.devin.ai/sessions/37c64bafb0ff40ef8ae9f34c7e14be5c
Requested by: @keithwillcode

⚠️ Note: This PR requires thorough testing of analytics functionality as the module import structure had to be adjusted, which could indicate potential compatibility issues.

- Add analytics service generation to app-store-cli build process
- Generate analytics.services.generated.ts with only analytics apps (dub)
- Update getAnalytics.ts to use AnalyticsServiceMap instead of full appStore
- Add NEXT_PUBLIC_IS_E2E to turbo.json globalEnv for generated files
- Reduces import footprint from 100+ apps to only analytics apps with AnalyticsService
- Follows same pattern as calendar services optimization from PR #22450

Co-Authored-By: [email protected] <[email protected]>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Aug 26, 2025

Walkthrough

Adds generation of packages/app-store/analytics.services.generated.ts exporting AnalyticsServiceMap, conditionally defined as process.env.NEXT_PUBLIC_IS_E2E ? {} : { ... } and currently containing a dub dynamic import. Updates packages/app-store/_utils/getAnalytics.ts to resolve analytics implementations from AnalyticsServiceMap and to read AnalyticsService from the imported module's default export. The build output list now includes analytics.services.generated.ts. turbo.json adds NEXT_PUBLIC_IS_E2E to globalEnv.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/analytics-import-optimization-1756236003

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@keithwillcode keithwillcode added core area: core, team members only foundation self-hosted labels Aug 26, 2025
@keithwillcode keithwillcode self-assigned this Aug 26, 2025
@devin-ai-integration devin-ai-integration bot marked this pull request as ready for review August 26, 2025 19:24
@graphite-app graphite-app bot requested a review from a team August 26, 2025 19:24
@dosubot dosubot bot added app-store area: app store, apps, calendar integrations, google calendar, outlook, lark, apple calendar performance area: performance, page load, slow, slow endpoints, loading screen, unresponsive labels Aug 26, 2025
@calcom calcom deleted a comment from devin-ai-integration bot Aug 26, 2025
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: 1

🧹 Nitpick comments (6)
turbo.json (1)

465-469: Consider updating outputs to include new generated files for future caching

The @calcom/app-store-cli#build task currently lists only two outputs. Since this PR adds analytics.services.generated.ts (and calendar.services.generated.ts exists), it’s safer to enumerate them to avoid surprises if caching is enabled later or for tooling that inspects outputs.

Apply this diff to reflect the new generated artifacts:

     "@calcom/app-store-cli#build": {
       "cache": false,
       "inputs": ["../../app-store/**"],
-      "outputs": ["../../app-store/apps.server.generated.ts", "../../app-store/apps.browser.generated.tsx"]
+      "outputs": [
+        "../../app-store/apps.server.generated.ts",
+        "../../app-store/apps.browser.generated.tsx",
+        "../../app-store/calendar.services.generated.ts",
+        "../../app-store/analytics.services.generated.ts",
+        "../../app-store/apps.metadata.generated.ts",
+        "../../app-store/apps.schemas.generated.ts",
+        "../../app-store/apps.keys-schemas.generated.ts",
+        "../../app-store/bookerApps.metadata.generated.ts",
+        "../../app-store/crm.apps.generated.ts"
+      ]
     },
packages/app-store-cli/src/build.ts (3)

381-397: Analytics map generation is correct and filter is precise

  • Uses the same generator as other maps with lazy dynamic imports.
  • Filters apps by existence of lib/AnalyticsService.ts, which is sufficient and avoids false positives.

Minor: the filter duplicates file existence checks already performed in getExportedObject; keeping it is fine for clarity.

If you want to DRY this, you can drop the explicit filter here since getExportedObject already skips missing files.


399-417: E2E guard wrapping logic mirrors calendar — LGTM, but consider extracting a helper

The wrapping that rewrites export const AnalyticsServiceMap = { to a guarded variant is correct and consistent with CalendarServiceMap.

Consider extracting a small helper to avoid duplication across Calendar/Analytics:

+function wrapWithE2EGuard(objectName: string, lines: string[]) {
+  const idx = lines.findIndex((l) => l.startsWith(`export const ${objectName}`));
+  if (idx === -1) return lines;
+  const exportLine = lines[idx];
+  const objectContent = lines.slice(idx + 1, -1);
+  return [
+    exportLine.replace(
+      `export const ${objectName} = {`,
+      `export const ${objectName} = process.env.NEXT_PUBLIC_IS_E2E ? {} : {`
+    ),
+    ...objectContent,
+    "};",
+  ];
+}

Then reuse it for both CalendarServiceMap and AnalyticsServiceMap.


385-390: Default exports in generated imports — consider standardizing on named exports

Repository guidelines prefer named exports for better tree-shaking and refactors. The generator currently assumes default for AnalyticsService. Not blocking, but worth aligning longer-term (while still supporting legacy defaults).

Example future-friendly generation:

-  importConfig: {
-    fileToBeImported: "lib/AnalyticsService.ts",
-    importName: "default",
-  },
+  importConfig: [
+    { fileToBeImported: "lib/AnalyticsService.ts", importName: "AnalyticsService" },
+    { fileToBeImported: "lib/AnalyticsService.ts", importName: "default" }
+  ],

This tries a named export first, falling back to default if needed.

packages/app-store/_utils/getAnalytics.ts (2)

33-38: Improve log clarity and variable naming

  • analyticsAppImportFn is a Promise, not a function. Rename for clarity.
  • Include the analyticsName in the “not implemented” warning to aid debugging.

Apply this diff:

-  const analyticsAppImportFn = AnalyticsServiceMap[analyticsName as keyof typeof AnalyticsServiceMap];
+  const analyticsAppModulePromise = AnalyticsServiceMap[analyticsName as keyof typeof AnalyticsServiceMap];

-  if (!analyticsAppImportFn) {
-    log.warn(`analytics app not implemented`);
+  if (!analyticsAppModulePromise) {
+    log.warn(`Analytics app not implemented for "${analyticsName}"`);
     return null;
   }

48-52: Optional: reduce log verbosity and attach context

Current info log prints the class reference; prefer message with app name to avoid noisy object dumps, especially in prod logs.

-  log.info("Got analyticsApp", AnalyticsService);
+  log.info({ analyticsName }, "Analytics service resolved");
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1cc5482 and 229d8f1.

📒 Files selected for processing (4)
  • packages/app-store-cli/src/build.ts (2 hunks)
  • packages/app-store/_utils/getAnalytics.ts (2 hunks)
  • packages/app-store/analytics.services.generated.ts (1 hunks)
  • turbo.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/app-store/analytics.services.generated.ts
  • packages/app-store/_utils/getAnalytics.ts
  • packages/app-store-cli/src/build.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/app-store/analytics.services.generated.ts
  • packages/app-store/_utils/getAnalytics.ts
  • packages/app-store-cli/src/build.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/app-store/analytics.services.generated.ts
  • packages/app-store/_utils/getAnalytics.ts
  • packages/app-store-cli/src/build.ts
🧬 Code graph analysis (1)
packages/app-store/_utils/getAnalytics.ts (1)
packages/app-store/analytics.services.generated.ts (1)
  • AnalyticsServiceMap (5-9)
🔇 Additional comments (3)
turbo.json (1)

88-90: Expose NEXT_PUBLIC_IS_E2E to all tasks — LGTM

Adding NEXT_PUBLIC_IS_E2E to globalEnv is consistent with its use in analytics.services.generated.ts and ensures the guard is available during builds and runtime. No concerns.

packages/app-store/analytics.services.generated.ts (1)

5-9: Guarded, minimal analytics import map — LGTM

The conditional export prevents loading analytics services during E2E (NEXT_PUBLIC_IS_E2E) and only includes known AnalyticsService implementers (currently dub) otherwise. This aligns with the PR’s objective to avoid loading the full app store.

packages/app-store-cli/src/build.ts (1)

432-433: Adding analytics.services.generated.ts to the generation list — LGTM

The file will now always be emitted with the rest of the generated app-store artifacts.

@keithwillcode
Copy link
Copy Markdown
Contributor Author

Helps towards #23104

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Aug 26, 2025

E2E results are ready!

@vercel
Copy link
Copy Markdown

vercel bot commented Aug 26, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Preview Comments Updated (UTC)
cal Ignored Ignored Aug 26, 2025 8:19pm
cal-eu Ignored Ignored Aug 26, 2025 8:19pm

volnei
volnei previously approved these changes Aug 26, 2025
devin-ai-integration bot and others added 3 commits August 26, 2025 20:16
…ical order in turbo.json

- Remove unnecessary object wrapping in getAnalytics.ts to match getCalendar.ts pattern
- Move NEXT_PUBLIC_IS_E2E to correct alphabetical position in turbo.json globalEnv
- Address PR feedback from keithwillcode

Co-Authored-By: [email protected] <[email protected]>
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: 1

♻️ Duplicate comments (1)
packages/app-store/_utils/getAnalytics.ts (1)

19-36: Make analytics imports truly lazy, harden error handling, and support both default and named exports

  • Current map values are awaited directly, which assumes they’re Promises and also eagerly triggers dynamic imports at module-evaluation time in the generated file. This undermines laziness and can still load modules when this file is imported.
  • If the dynamic import rejects, this function will throw instead of returning null.
  • Only the default export is considered; some apps may expose a named AnalyticsService.

Refactor to:

  • Treat the map entry as either a Promise or a thunk (() => Promise), calling it if it’s a function.
  • Wrap the import in try/catch and return null on failure.
  • Accept either a default export or a named AnalyticsService export.
  • Normalize analyticsName to lowercase to avoid case/format drift.

Apply this diff within this range:

-  const analyticsName = analyticsType.split("_")[0];
+  const analyticsName = analyticsType.split("_")[0]?.toLowerCase();

-  const analyticsAppImportFn = AnalyticsServiceMap[analyticsName as keyof typeof AnalyticsServiceMap];
+  const importer = AnalyticsServiceMap[analyticsName as keyof typeof AnalyticsServiceMap];

-  if (!analyticsAppImportFn) {
-    log.warn(`analytics app not implemented`);
-    return null;
-  }
+  if (!importer) {
+    log.warn(`analytics app "${analyticsName}" not implemented (type="${analyticsType}")`);
+    return null;
+  }

-  const analyticsApp = await analyticsAppImportFn;
+  let analyticsModule: AnalyticsModule | null = null;
+  try {
+    const modulePromise =
+      typeof importer === "function"
+        ? (importer as () => Promise<AnalyticsModule>)()
+        : (importer as Promise<AnalyticsModule>);
+    analyticsModule = await modulePromise;
+  } catch (err) {
+    log.warn(`failed to load analytics module "${analyticsName}": ${(err as Error)?.message}`);
+    return null;
+  }

-  const AnalyticsService = analyticsApp.default;
+  const AnalyticsServiceCtor = (analyticsModule?.default ??
+    (analyticsModule as any)?.AnalyticsService) as AnalyticsServiceCtor | undefined;

-  if (!AnalyticsService || typeof AnalyticsService !== "function") {
-    log.warn(`analytics of type ${analyticsType} is not implemented`);
-    return null;
-  }
+  if (typeof AnalyticsServiceCtor !== "function") {
+    log.warn(
+      `analytics of type "${analyticsType}" is not implemented (no default export or named "AnalyticsService")`
+    );
+    return null;
+  }

-  return new AnalyticsService(credential);
+  return new AnalyticsServiceCtor(credential);

Add these supporting types near the top of the file (outside this range):

type AnalyticsServiceCtor = new (credential: CredentialPayload) => AnalyticsService;
type AnalyticsModule = { default?: AnalyticsServiceCtor; AnalyticsService?: AnalyticsServiceCtor };
🧹 Nitpick comments (1)
packages/app-store/_utils/getAnalytics.ts (1)

2-2: Avoid value/type name collision with AnalyticsService

You import the interface AnalyticsService as a type and then create a runtime value with the same identifier later. While legal in TS, it’s easy to misread. The diff above renames the runtime value to AnalyticsServiceCtor, which aligns with its semantics (a constructor).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1f5b953 and 167b86b.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (2)
  • packages/app-store/_utils/getAnalytics.ts (2 hunks)
  • turbo.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • turbo.json
🧰 Additional context used
📓 Path-based instructions (3)
**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

**/*.ts: For Prisma queries, only select data you need; never use include, always use select
Ensure the credential.key field is never returned from tRPC endpoints or APIs

Files:

  • packages/app-store/_utils/getAnalytics.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/review.mdc)

Flag excessive Day.js use in performance-critical code; prefer native Date or Day.js .utc() in hot paths like loops

Files:

  • packages/app-store/_utils/getAnalytics.ts
**/*.{ts,tsx,js,jsx}

⚙️ CodeRabbit configuration file

Flag default exports and encourage named exports. Named exports provide better tree-shaking, easier refactoring, and clearer imports. Exempt main components like pages, layouts, and components that serve as the primary export of a module.

Files:

  • packages/app-store/_utils/getAnalytics.ts
🧬 Code graph analysis (1)
packages/app-store/_utils/getAnalytics.ts (2)
packages/app-store/analytics.services.generated.ts (1)
  • AnalyticsServiceMap (5-9)
packages/types/AnalyticsService.d.ts (1)
  • AnalyticsService (9-11)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Install dependencies / Yarn install & cache

@keithwillcode keithwillcode enabled auto-merge (squash) August 26, 2025 20:30
@keithwillcode keithwillcode merged commit 520e711 into main Aug 26, 2025
40 checks passed
@keithwillcode keithwillcode deleted the devin/analytics-import-optimization-1756236003 branch August 26, 2025 21:57
devin-ai-integration bot added a commit that referenced this pull request Aug 27, 2025
- Add PaymentServiceMap generation to app-store-cli build process
- Generate payment.services.generated.ts with lazy imports for 6 payment services
- Update handlePayment.ts, deletePayment.ts, handlePaymentRefund.ts to use PaymentServiceMap
- Update getConnectedApps.ts and tRPC payment routers to use PaymentServiceMap
- Follow same pattern as analytics optimization in PR #23372
- Reduces bundle size by avoiding import of 100+ apps when only payment functionality needed

Co-Authored-By: [email protected] <[email protected]>
keithwillcode added a commit that referenced this pull request Aug 28, 2025
…23408)

* perf: optimize payment app imports to avoid loading entire app store

- Add PaymentServiceMap generation to app-store-cli build process
- Generate payment.services.generated.ts with lazy imports for 6 payment services
- Update handlePayment.ts, deletePayment.ts, handlePaymentRefund.ts to use PaymentServiceMap
- Update getConnectedApps.ts and tRPC payment routers to use PaymentServiceMap
- Follow same pattern as analytics optimization in PR #23372
- Reduces bundle size by avoiding import of 100+ apps when only payment functionality needed

Co-Authored-By: [email protected] <[email protected]>

* Update build.ts

* fix: update payment service test mocking to work with PaymentServiceMap

- Remove obsolete appStoreMock line from bookingScenario.ts since handlePayment now uses PaymentServiceMap
- Update setupVitest.ts to import prismaMock from correct PrismockClient instance
- Add PaymentServiceMap mock following PR #22450 pattern for calendar services
- Ensure MockPaymentService uses consistent externalId across test files
- Fix webhook handler to return 200 status by ensuring payment records are found correctly

Co-Authored-By: [email protected] <[email protected]>

* fix: revert prismaMock import to avoid interfering with other tests' vi.spyOn() calls

- Remove global prismaMock import from setupVitest.ts that was causing 'is not a spy' errors
- Update MockPaymentService to import prismaMock locally to maintain payment test functionality
- Fixes organization and outOfOffice tests while preserving payment service optimization

Co-Authored-By: [email protected] <[email protected]>

* fix: remove E2E conditional check from payment services map generation

- Payment services map now always includes all payment apps regardless of E2E environment
- Ensures payment functionality is consistently available across all environments
- Addresses CI failures caused by conditional payment service loading

Co-Authored-By: [email protected] <[email protected]>

* refactor: use direct PaymentService imports instead of .lib structure

- Update app-store-cli to import directly from lib/PaymentService.ts files
- Modify all payment handlers to access PaymentService directly
- Update test mocks to match new direct import structure
- Remove .lib property access pattern across payment system
- Maintain backward compatibility while improving import efficiency

Co-Authored-By: [email protected] <[email protected]>

* fix: revert chargeCard booking.id parameter additions

- Remove booking.id parameter from chargeCard calls in chargeCard.handler.ts and payments.tsx
- Addresses GitHub feedback to investigate chargeCard signature changes in separate PR
- Keeps all other direct PaymentService import refactor changes intact

Co-Authored-By: [email protected] <[email protected]>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app-store area: app store, apps, calendar integrations, google calendar, outlook, lark, apple calendar core area: core, team members only foundation performance area: performance, page load, slow, slow endpoints, loading screen, unresponsive ready-for-e2e self-hosted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants