fix: report page views to Customer.io in-app SDK#13560
Conversation
🎭 Playwright: ✅ 1691 passed, 0 failed · 3 flaky📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle: 7.78 MB gzip 🔴 +52 BDetailsSummary
Category Glance App Entry Points — 48 kB (baseline 48 kB) • ⚪ 0 BMain entry bundles and manifests
Status: 1 added / 1 removed Graph Workspace — 1.27 MB (baseline 1.27 MB) • ⚪ 0 BGraph editor runtime, canvas, workflow orchestration
Status: 1 added / 1 removed Views & Navigation — 110 kB (baseline 110 kB) • ⚪ 0 BTop-level views, pages, and routed surfaces
Status: 11 added / 11 removed / 3 unchanged Panels & Settings — 550 kB (baseline 550 kB) • ⚪ 0 BConfiguration panels, inspectors, and settings screens
Status: 11 added / 11 removed / 16 unchanged User & Accounts — 29.1 kB (baseline 29.1 kB) • ⚪ 0 BAuthentication, profile, and account management bundles
Status: 7 added / 7 removed / 3 unchanged Editors & Dialogs — 117 kB (baseline 117 kB) • ⚪ 0 BModals, dialogs, drawers, and in-app editors
Status: 4 added / 4 removed / 1 unchanged UI Components — 57.5 kB (baseline 57.5 kB) • ⚪ 0 BReusable component library chunks
Status: 5 added / 5 removed / 8 unchanged Data & Services — 270 kB (baseline 270 kB) • ⚪ 0 BStores, services, APIs, and repositories
Status: 13 added / 13 removed / 3 unchanged Utilities & Hooks — 3.39 MB (baseline 3.39 MB) • ⚪ 0 BHelpers, composables, and utility bundles
Status: 16 added / 16 removed / 19 unchanged Vendor & Third-Party — 15.3 MB (baseline 15.3 MB) • ⚪ 0 BExternal libraries and shared vendor chunks Status: 16 unchanged Other — 11.7 MB (baseline 11.7 MB) • ⚪ 0 BBundles that do not match a named category
Status: 67 added / 67 removed / 98 unchanged ⚡ Performance Report
Show regressions
All metrics
Historical variance (last 15 runs)
Trend (last 15 commits on main)
Raw data{
"timestamp": "2026-07-10T01:42:20.600Z",
"gitSha": "3283784489409126ee1241a8eec9f7de27fce8fc",
"branch": "agent/fix-customerio-page-tracking",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2056.8170000000237,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.165,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 403.281,
"heapDeltaBytes": -7281388,
"heapUsedBytes": 62507996,
"domNodes": -279,
"jsHeapTotalBytes": 18235392,
"scriptDurationMs": 16.864,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-idle",
"durationMs": 2038.819999999987,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.939999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 400.56100000000004,
"heapDeltaBytes": -2953700,
"heapUsedBytes": 50047944,
"domNodes": -292,
"jsHeapTotalBytes": 16379904,
"scriptDurationMs": 17.823999999999998,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1997.3110000000247,
"styleRecalcs": 78,
"styleRecalcDurationMs": 51.206999999999994,
"layouts": 12,
"layoutDurationMs": 3.6249999999999996,
"taskDurationMs": 921.275,
"heapDeltaBytes": -12644228,
"heapUsedBytes": 56945500,
"domNodes": -241,
"jsHeapTotalBytes": 19546112,
"scriptDurationMs": 135.524,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-mouse-sweep",
"durationMs": 1775.9640000000445,
"styleRecalcs": 73,
"styleRecalcDurationMs": 37.764,
"layouts": 12,
"layoutDurationMs": 3.8510000000000004,
"taskDurationMs": 759.327,
"heapDeltaBytes": -1266572,
"heapUsedBytes": 62217444,
"domNodes": 55,
"jsHeapTotalBytes": 15990784,
"scriptDurationMs": 122.18299999999999,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1746.4410000000044,
"styleRecalcs": 30,
"styleRecalcDurationMs": 15.142,
"layouts": 6,
"layoutDurationMs": 0.629,
"taskDurationMs": 328.781,
"heapDeltaBytes": -3299128,
"heapUsedBytes": 66325292,
"domNodes": -227,
"jsHeapTotalBytes": 13254656,
"scriptDurationMs": 16.557999999999996,
"eventListeners": -184,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1716.5640000000622,
"styleRecalcs": 30,
"styleRecalcDurationMs": 16.965999999999998,
"layouts": 6,
"layoutDurationMs": 0.598,
"taskDurationMs": 317.121,
"heapDeltaBytes": 593524,
"heapUsedBytes": 52669896,
"domNodes": -242,
"jsHeapTotalBytes": 7991296,
"scriptDurationMs": 16.711,
"eventListeners": -186,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "dom-widget-clipping",
"durationMs": 611.6779999999835,
"styleRecalcs": 12,
"styleRecalcDurationMs": 11.408999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 378.41999999999996,
"heapDeltaBytes": -22471748,
"heapUsedBytes": 47281212,
"domNodes": -276,
"jsHeapTotalBytes": 6176768,
"scriptDurationMs": 59.416000000000004,
"eventListeners": -201,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "dom-widget-clipping",
"durationMs": 613.1299999999555,
"styleRecalcs": 11,
"styleRecalcDurationMs": 11.691999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 390.09099999999995,
"heapDeltaBytes": -22591924,
"heapUsedBytes": 47236084,
"domNodes": -279,
"jsHeapTotalBytes": 5128192,
"scriptDurationMs": 56.760000000000005,
"eventListeners": -201,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2002.6619999999866,
"styleRecalcs": 10,
"styleRecalcDurationMs": 10.407,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 474.952,
"heapDeltaBytes": -9556816,
"heapUsedBytes": 62082200,
"domNodes": 20,
"jsHeapTotalBytes": 11939840,
"scriptDurationMs": 84.647,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2057.265999999913,
"styleRecalcs": 7,
"styleRecalcDurationMs": 6.548000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 533.066,
"heapDeltaBytes": -8872216,
"heapUsedBytes": 58599936,
"domNodes": -274,
"jsHeapTotalBytes": -1323008,
"scriptDurationMs": 91.71000000000001,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2085.2740000000267,
"styleRecalcs": 70,
"styleRecalcDurationMs": 16.719,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 965.1719999999999,
"heapDeltaBytes": 28314864,
"heapUsedBytes": 82001620,
"domNodes": -269,
"jsHeapTotalBytes": 466944,
"scriptDurationMs": 362.684,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 2056.3720000000103,
"styleRecalcs": 68,
"styleRecalcDurationMs": 18.166999999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 1042.854,
"heapDeltaBytes": 6945988,
"heapUsedBytes": 82911584,
"domNodes": 18,
"jsHeapTotalBytes": 10543104,
"scriptDurationMs": 378.61300000000006,
"eventListeners": 6,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-zoom",
"durationMs": 3116.1050000000046,
"styleRecalcs": 64,
"styleRecalcDurationMs": 17.066000000000003,
"layouts": 60,
"layoutDurationMs": 7.325,
"taskDurationMs": 1263.2629999999997,
"heapDeltaBytes": 13217424,
"heapUsedBytes": 76257836,
"domNodes": -270,
"jsHeapTotalBytes": 4468736,
"scriptDurationMs": 464.39,
"eventListeners": -128,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 3075.4950000000463,
"styleRecalcs": 63,
"styleRecalcDurationMs": 15.472999999999997,
"layouts": 60,
"layoutDurationMs": 6.850999999999999,
"taskDurationMs": 1197.613,
"heapDeltaBytes": 12170468,
"heapUsedBytes": 67693928,
"domNodes": 8,
"jsHeapTotalBytes": 2097152,
"scriptDurationMs": 460.42900000000003,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 1994.2970000000173,
"styleRecalcs": 6,
"styleRecalcDurationMs": 4.973999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 447.278,
"heapDeltaBytes": 6469500,
"heapUsedBytes": 61302748,
"domNodes": 12,
"jsHeapTotalBytes": 786432,
"scriptDurationMs": 84.50600000000001,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2025.9230000000343,
"styleRecalcs": 9,
"styleRecalcDurationMs": 8.378000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 530.716,
"heapDeltaBytes": -2443732,
"heapUsedBytes": 59556072,
"domNodes": -269,
"jsHeapTotalBytes": -569344,
"scriptDurationMs": 98.166,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 590.3170000000273,
"styleRecalcs": 46,
"styleRecalcDurationMs": 11.583,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 392.07800000000003,
"heapDeltaBytes": -3695392,
"heapUsedBytes": 48230932,
"domNodes": -273,
"jsHeapTotalBytes": 1437696,
"scriptDurationMs": 116.11600000000001,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.663333333333338,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 569.8250000000371,
"styleRecalcs": 46,
"styleRecalcDurationMs": 10.528,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 376.603,
"heapDeltaBytes": -3727368,
"heapUsedBytes": 48578184,
"domNodes": -275,
"jsHeapTotalBytes": 126976,
"scriptDurationMs": 119.082,
"eventListeners": -197,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000273
},
{
"name": "subgraph-idle",
"durationMs": 2028.9099999999962,
"styleRecalcs": 11,
"styleRecalcDurationMs": 10.152,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 391.61500000000007,
"heapDeltaBytes": -7530124,
"heapUsedBytes": 62588588,
"domNodes": -275,
"jsHeapTotalBytes": 17973248,
"scriptDurationMs": 12.642,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-idle",
"durationMs": 1998.126999999954,
"styleRecalcs": 11,
"styleRecalcDurationMs": 9.12,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 397.61500000000007,
"heapDeltaBytes": -3471344,
"heapUsedBytes": 48920892,
"domNodes": -287,
"jsHeapTotalBytes": 17428480,
"scriptDurationMs": 16.330000000000002,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1723.8909999999805,
"styleRecalcs": 75,
"styleRecalcDurationMs": 35.69500000000001,
"layouts": 16,
"layoutDurationMs": 3.973,
"taskDurationMs": 700.1220000000001,
"heapDeltaBytes": -6682436,
"heapUsedBytes": 45579864,
"domNodes": -262,
"jsHeapTotalBytes": 17690624,
"scriptDurationMs": 88.948,
"eventListeners": -199,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1701.3429999999516,
"styleRecalcs": 76,
"styleRecalcDurationMs": 35.247,
"layouts": 16,
"layoutDurationMs": 3.68,
"taskDurationMs": 671.597,
"heapDeltaBytes": -9580244,
"heapUsedBytes": 49530928,
"domNodes": 64,
"jsHeapTotalBytes": 24117248,
"scriptDurationMs": 91.288,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-transition-enter",
"durationMs": 982.5670000000173,
"styleRecalcs": 20,
"styleRecalcDurationMs": 30.741000000000003,
"layouts": 16,
"layoutDurationMs": 14.303999999999997,
"taskDurationMs": 748.0230000000001,
"heapDeltaBytes": 2514508,
"heapUsedBytes": 78244324,
"domNodes": 13673,
"jsHeapTotalBytes": 5242880,
"scriptDurationMs": 29.259000000000007,
"eventListeners": 2533,
"totalBlockingTimeMs": 148,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 8165.163000000007,
"styleRecalcs": 250,
"styleRecalcDurationMs": 51.812,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3668.121,
"heapDeltaBytes": 8972992,
"heapUsedBytes": 69599448,
"domNodes": -277,
"jsHeapTotalBytes": 1732608,
"scriptDurationMs": 1232.778,
"eventListeners": -183,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "viewport-pan-sweep",
"durationMs": 8155.826999999931,
"styleRecalcs": 250,
"styleRecalcDurationMs": 52.38000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 3627.712,
"heapDeltaBytes": 8505172,
"heapUsedBytes": 68674816,
"domNodes": -273,
"jsHeapTotalBytes": 159744,
"scriptDurationMs": 1209.534,
"eventListeners": -183,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 10817.902000000004,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10804.934,
"heapDeltaBytes": -42972552,
"heapUsedBytes": 167506196,
"domNodes": -3300,
"jsHeapTotalBytes": -3174400,
"scriptDurationMs": 474.82,
"eventListeners": -16376,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 11199.607000000015,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 11187.844,
"heapDeltaBytes": -66934124,
"heapUsedBytes": 152784208,
"domNodes": -3300,
"jsHeapTotalBytes": 7049216,
"scriptDurationMs": 492.537,
"eventListeners": -16378,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 14685.028999999986,
"styleRecalcs": 67,
"styleRecalcDurationMs": 15.949999999999964,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 14665.768,
"heapDeltaBytes": -71514296,
"heapUsedBytes": 154619168,
"domNodes": -3300,
"jsHeapTotalBytes": -3960832,
"scriptDurationMs": 753.655,
"eventListeners": -16372,
"totalBlockingTimeMs": 10,
"frameDurationMs": 17.219999999999953,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "vue-large-graph-pan",
"durationMs": 15134.789999999954,
"styleRecalcs": 72,
"styleRecalcDurationMs": 16.90899999999998,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 15112.139000000003,
"heapDeltaBytes": -42592072,
"heapUsedBytes": 164545080,
"domNodes": -3300,
"jsHeapTotalBytes": 9932800,
"scriptDurationMs": 760.9780000000001,
"eventListeners": -16374,
"totalBlockingTimeMs": 5,
"frameDurationMs": 17.223333333333358,
"p95FrameDurationMs": 16.80000000000291
},
{
"name": "workflow-execution",
"durationMs": 442.57699999991473,
"styleRecalcs": 14,
"styleRecalcDurationMs": 18.798000000000002,
"layouts": 3,
"layoutDurationMs": 0.738,
"taskDurationMs": 179.549,
"heapDeltaBytes": -22599992,
"heapUsedBytes": 46362724,
"domNodes": -193,
"jsHeapTotalBytes": 5128192,
"scriptDurationMs": 8.753,
"eventListeners": -134,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.669999999999998,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "workflow-execution",
"durationMs": 451.89299999992727,
"styleRecalcs": 13,
"styleRecalcDurationMs": 18.127,
"layouts": 2,
"layoutDurationMs": 0.3879999999999999,
"taskDurationMs": 160.46200000000002,
"heapDeltaBytes": -22875904,
"heapUsedBytes": 46127800,
"domNodes": -203,
"jsHeapTotalBytes": 5128192,
"scriptDurationMs": 9.322000000000003,
"eventListeners": -132,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
}
]
} |
📝 WalkthroughWalkthrough
ChangesCustomer.io page-view tracking
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant ClientSideRouter
participant CustomerIoTelemetryProvider
participant CustomerIoAnalytics
ClientSideRouter->>CustomerIoTelemetryProvider: trackPageView(pageName, properties)
CustomerIoTelemetryProvider->>CustomerIoAnalytics: page()
Suggested labels: Suggested reviewers: Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 inconclusive)
✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts`:
- Around line 77-99: Add an explicit test covering trackPageView before
analytics initialization completes: create the provider, call
provider.trackPageView before awaiting dynamic imports, assert analytics.page
has not been called yet, then await vi.dynamicImportSettled and assert it is
called exactly once after registration. Use the existing createProvider and
analytics mocks, and retain assertions verifying registration occurs before the
queued page view is flushed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 64fc06da-8e16-47f7-b543-c0deb4268172
📒 Files selected for processing (2)
src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.tssrc/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.ts
| it('reports the current page after registering the in-app plugin', async () => { | ||
| createProvider() | ||
| await vi.dynamicImportSettled() | ||
|
|
||
| expect(hoisted.analytics.page).toHaveBeenCalledOnce() | ||
| expect(hoisted.analytics.page).toHaveBeenCalledWith() | ||
| expect(hoisted.analytics.register.mock.invocationCallOrder[0]).toBeLessThan( | ||
| hoisted.analytics.page.mock.invocationCallOrder[0] | ||
| ) | ||
| }) | ||
|
|
||
| it('reports client-side route changes', async () => { | ||
| const provider = createProvider() | ||
| await vi.dynamicImportSettled() | ||
| hoisted.analytics.page.mockClear() | ||
|
|
||
| provider.trackPageView('workflow_editor', { | ||
| path: 'https://cloud.comfy.org/' | ||
| }) | ||
|
|
||
| expect(hoisted.analytics.page).toHaveBeenCalledOnce() | ||
| expect(hoisted.analytics.page).toHaveBeenCalledWith() | ||
| }) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
Consider adding a test for the "analytics not ready → queued" edge case.
The path instructions mention covering the "analytics not ready → queued" scenario. The first test implicitly covers this (the initial page view is queued via pageViewQueued = true and flushed after registration), but there's no explicit test calling trackPageView before analytics is ready and verifying the queue-then-flush behavior. As per test-quality guidelines, key edge cases implied by the change should be covered.
🧪 Suggested additional test
+ it('queues page views until analytics is ready', async () => {
+ const provider = createProvider()
+ // trackPageView called before dynamic import resolves — analytics is null
+ provider.trackPageView('workflow_editor', {
+ path: 'https://cloud.comfy.org/'
+ })
+ expect(hoisted.analytics.page).not.toHaveBeenCalled()
+
+ await vi.dynamicImportSettled()
+
+ expect(hoisted.analytics.page).toHaveBeenCalledOnce()
+ expect(hoisted.analytics.page).toHaveBeenCalledWith()
+ })Based on learnings, only add call-count assertions when the call-count itself is part of what the test is meant to verify — here, the "not called before ready, then called once after" sequence is the core behavior under test. As per path instructions, .agents/checks/test-quality.md requires covering the "analytics not ready → queued" edge case.
📝 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.
| it('reports the current page after registering the in-app plugin', async () => { | |
| createProvider() | |
| await vi.dynamicImportSettled() | |
| expect(hoisted.analytics.page).toHaveBeenCalledOnce() | |
| expect(hoisted.analytics.page).toHaveBeenCalledWith() | |
| expect(hoisted.analytics.register.mock.invocationCallOrder[0]).toBeLessThan( | |
| hoisted.analytics.page.mock.invocationCallOrder[0] | |
| ) | |
| }) | |
| it('reports client-side route changes', async () => { | |
| const provider = createProvider() | |
| await vi.dynamicImportSettled() | |
| hoisted.analytics.page.mockClear() | |
| provider.trackPageView('workflow_editor', { | |
| path: 'https://cloud.comfy.org/' | |
| }) | |
| expect(hoisted.analytics.page).toHaveBeenCalledOnce() | |
| expect(hoisted.analytics.page).toHaveBeenCalledWith() | |
| }) | |
| it('reports the current page after registering the in-app plugin', async () => { | |
| createProvider() | |
| await vi.dynamicImportSettled() | |
| expect(hoisted.analytics.page).toHaveBeenCalledOnce() | |
| expect(hoisted.analytics.page).toHaveBeenCalledWith() | |
| expect(hoisted.analytics.register.mock.invocationCallOrder[0]).toBeLessThan( | |
| hoisted.analytics.page.mock.invocationCallOrder[0] | |
| ) | |
| }) | |
| it('queues page views until analytics is ready', async () => { | |
| const provider = createProvider() | |
| // trackPageView called before dynamic import resolves — analytics is null | |
| provider.trackPageView('workflow_editor', { | |
| path: 'https://cloud.comfy.org/' | |
| }) | |
| expect(hoisted.analytics.page).not.toHaveBeenCalled() | |
| await vi.dynamicImportSettled() | |
| expect(hoisted.analytics.page).toHaveBeenCalledOnce() | |
| expect(hoisted.analytics.page).toHaveBeenCalledWith() | |
| }) | |
| it('reports client-side route changes', async () => { | |
| const provider = createProvider() | |
| await vi.dynamicImportSettled() | |
| hoisted.analytics.page.mockClear() | |
| provider.trackPageView('workflow_editor', { | |
| path: 'https://cloud.comfy.org/' | |
| }) | |
| expect(hoisted.analytics.page).toHaveBeenCalledOnce() | |
| expect(hoisted.analytics.page).toHaveBeenCalledWith() | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts`
around lines 77 - 99, Add an explicit test covering trackPageView before
analytics initialization completes: create the provider, call
provider.trackPageView before awaiting dynamic imports, assert analytics.page
has not been called yet, then await vi.dynamicImportSettled and assert it is
called exactly once after registration. Use the existing createProvider and
analytics mocks, and retain assertions verifying registration occurs before the
queued page view is flushed.
Sources: Path instructions, Learnings
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b7c7f16d47
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Codecov Report❌ Patch coverage is
@@ Coverage Diff @@
## main #13560 +/- ##
==========================================
- Coverage 77.83% 77.83% -0.01%
==========================================
Files 1656 1656
Lines 95099 95117 +18
Branches 33941 33944 +3
==========================================
+ Hits 74016 74030 +14
- Misses 20427 20432 +5
+ Partials 656 655 -1
Flags with carried forward coverage won't be shown. Click here to find out more.
... and 5 files with indirect coverage changes 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.ts (1)
119-132: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winReset
pageViewQueuedwhentrackPageViewsends directly to prevent double page views.If
trackPageViewis called afterthis.analyticsis set (line 62) but beforeinAppRegistrationresolves (line 74), it callssendPageView()directly without clearingpageViewQueued. When registration subsequently resolves,flushPageView()seespageViewQueuedstilltrueand sends a second redundantpage()call. This race window spans the entire in-app plugin registration duration, so it's reachable on any early SPA navigation.🐛 Proposed fix
trackPageView(_pageName: string, _properties?: PageViewMetadata): void { if (!this.isEnabled) return if (!this.analytics) { this.pageViewQueued = true return } + this.pageViewQueued = false this.sendPageView() }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.ts` around lines 119 - 132, Reset pageViewQueued before sending directly from trackPageView. Update the analytics-present branch in trackPageView to clear the queued flag, ensuring a later flushPageView cannot emit a duplicate page view during in-app registration.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts`:
- Around line 101-120: Extend the test case “continues tracking when the in-app
plugin fails to register” to assert that the mocked console.error was called
with the registration failure, using the captured Error or its message. Keep the
existing tracking assertions to verify tracking continues after registration
fails.
---
Outside diff comments:
In `@src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.ts`:
- Around line 119-132: Reset pageViewQueued before sending directly from
trackPageView. Update the analytics-present branch in trackPageView to clear the
queued flag, ensuring a later flushPageView cannot emit a duplicate page view
during in-app registration.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: fe3be429-3d34-484c-9a83-aedd6caadbe2
📒 Files selected for processing (2)
src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.tssrc/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.ts
| it('continues tracking when the in-app plugin fails to register', async () => { | ||
| vi.spyOn(console, 'error').mockImplementation(() => {}) | ||
| hoisted.analytics.register.mockRejectedValue( | ||
| new Error('in-app setup failed') | ||
| ) | ||
| const provider = createProvider() | ||
| provider.trackWorkflowExecution() | ||
|
|
||
| await vi.dynamicImportSettled() | ||
|
|
||
| expect(hoisted.analytics.track).toHaveBeenCalledWith( | ||
| 'execution_start', | ||
| SOURCE | ||
| ) | ||
| provider.trackAddApiCreditButtonClicked() | ||
| expect(hoisted.analytics.track).toHaveBeenCalledWith( | ||
| 'app:add_api_credit_button_clicked', | ||
| SOURCE | ||
| ) | ||
| }) |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
Consider asserting console.error was called with the registration error.
The test mocks console.error to suppress output but doesn't verify the error was logged. Asserting the error message confirms the registration failure is properly observed and logged, strengthening the behavioral coverage of the failure path.
🧪 Suggested addition
expect(hoisted.analytics.track).toHaveBeenCalledWith(
'app:add_api_credit_button_clicked',
SOURCE
)
+ expect(console.error).toHaveBeenCalledWith(
+ 'Failed to initialize Customer.io in-app plugin:',
+ expect.any(Error)
+ )
})Based on learnings, only add call-count assertions when the call-count itself is part of what the test is meant to verify — here, verifying the error is logged is a meaningful behavioral assertion for the failure path. As per path instructions, .agents/checks/test-quality.md requires covering continued tracking when registration fails, including error handling behavior.
📝 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.
| it('continues tracking when the in-app plugin fails to register', async () => { | |
| vi.spyOn(console, 'error').mockImplementation(() => {}) | |
| hoisted.analytics.register.mockRejectedValue( | |
| new Error('in-app setup failed') | |
| ) | |
| const provider = createProvider() | |
| provider.trackWorkflowExecution() | |
| await vi.dynamicImportSettled() | |
| expect(hoisted.analytics.track).toHaveBeenCalledWith( | |
| 'execution_start', | |
| SOURCE | |
| ) | |
| provider.trackAddApiCreditButtonClicked() | |
| expect(hoisted.analytics.track).toHaveBeenCalledWith( | |
| 'app:add_api_credit_button_clicked', | |
| SOURCE | |
| ) | |
| }) | |
| it('continues tracking when the in-app plugin fails to register', async () => { | |
| vi.spyOn(console, 'error').mockImplementation(() => {}) | |
| hoisted.analytics.register.mockRejectedValue( | |
| new Error('in-app setup failed') | |
| ) | |
| const provider = createProvider() | |
| provider.trackWorkflowExecution() | |
| await vi.dynamicImportSettled() | |
| expect(hoisted.analytics.track).toHaveBeenCalledWith( | |
| 'execution_start', | |
| SOURCE | |
| ) | |
| provider.trackAddApiCreditButtonClicked() | |
| expect(hoisted.analytics.track).toHaveBeenCalledWith( | |
| 'app:add_api_credit_button_clicked', | |
| SOURCE | |
| ) | |
| expect(console.error).toHaveBeenCalledWith( | |
| 'Failed to initialize Customer.io in-app plugin:', | |
| expect.any(Error) | |
| ) | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/platform/telemetry/providers/cloud/CustomerIoTelemetryProvider.test.ts`
around lines 101 - 120, Extend the test case “continues tracking when the in-app
plugin fails to register” to assert that the mocked console.error was called
with the registration failure, using the captured Error or its message. Keep the
existing tracking assertions to verify tracking continues after registration
fails.
Sources: Path instructions, Learnings
Summary
Report the current URL to Customer.io after its in-app plugin initializes and on every client-side navigation, allowing queued web in-app messages to satisfy page rules and render.
Changes
page()call, and consume the router's existing page-view dispatches for subsequent SPA navigation.Review Focus
Customer.io intentionally receives
page()without an explicit page name. Passing the telemetry page title would make the in-app SDK match page rules against that title instead of the full URL; the live campaign requirescontains cloud.comfy.org.Root cause: the production SDK debugger showed a correctly identified user and active SSE connection, but
Route: NONEand no eligible messages because the provider did not implementtrackPageView().Complements #13556, which addresses identity stitching for existing email-keyed Customer.io profiles.
Validation: targeted unit tests (21/21),
pnpm typecheck, targeted ESLint and oxfmt checks, and the pre-pushknipgate.