Skip to content

Commit

Permalink
Navigation type (#4)
Browse files Browse the repository at this point in the history
* import more types from web-vitals

* add navigation type to event

* Add FCP metric to web vitals tracking

* reordered conditional

* add new params to fcp

* import more types from web-vitals

* add navigation type to event

* Add FCP metric to web vitals tracking

* reordered conditional

* add new params to fcp
  • Loading branch information
ethangardner authored Nov 25, 2024
1 parent 6198ef5 commit 9f7884b
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 72 deletions.
50 changes: 31 additions & 19 deletions src/format-event-data.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import type {
LCPAttribution,
CLSAttribution,
FCPAttribution,
INPAttribution,
LCPAttribution,
} from 'web-vitals';

export type WebVitalsName = 'LCP' | 'INP' | 'CLS';
export type WebVitalsName = 'CLS' | 'FCP' | 'INP' | 'LCP';
export type WebVitalsAttribution =
| LCPAttribution
| CLSAttribution
| FCPAttribution
| INPAttribution
| CLSAttribution;
| LCPAttribution;

export const formatEventData = (
name: WebVitalsName,
Expand All @@ -17,18 +19,21 @@ export const formatEventData = (
// In some cases there won't be any entries (e.g. if CLS is 0,
// or for LCP after a bfcache restore), so we have to check first.
if (attribution) {
if (name === 'LCP') {
if (name === 'CLS') {
return {
debug_url: (attribution as LCPAttribution).url,
debug_time_to_first_byte: (attribution as LCPAttribution)
debug_time: (attribution as CLSAttribution).largestShiftTime,
debug_load_state: (attribution as CLSAttribution).loadState,
debug_target:
(attribution as CLSAttribution).largestShiftTarget || '(not set)',
};
}
if (name === 'FCP') {
return {
debug_time_to_first_byte: (attribution as FCPAttribution)
.timeToFirstByte,
debug_resource_load_delay: (attribution as LCPAttribution)
.resourceLoadDelay,
debug_resource_load_duration: (attribution as LCPAttribution)
.resourceLoadDuration,
debug_element_render_delay: (attribution as LCPAttribution)
.elementRenderDelay,
debug_target: (attribution as LCPAttribution).element || '(not set)',
debug_first_byte_to_fcp: (attribution as FCPAttribution).firstByteToFCP,
debug_load_state: (attribution as FCPAttribution).loadState,
debug_target: (attribution as FCPAttribution).loadState || '(not set)',
};
}
if (name === 'INP') {
Expand All @@ -47,14 +52,21 @@ export const formatEventData = (
debug_presentation_delay: Math.round(
(attribution as INPAttribution).presentationDelay,
),
// TODO: add LoAf attribution here
};
}
if (name === 'CLS') {
if (name === 'LCP') {
return {
debug_time: (attribution as CLSAttribution).largestShiftTime,
debug_load_state: (attribution as CLSAttribution).loadState,
debug_target:
(attribution as CLSAttribution).largestShiftTarget || '(not set)',
debug_url: (attribution as LCPAttribution).url,
debug_time_to_first_byte: (attribution as LCPAttribution)
.timeToFirstByte,
debug_resource_load_delay: (attribution as LCPAttribution)
.resourceLoadDelay,
debug_resource_load_duration: (attribution as LCPAttribution)
.resourceLoadDuration,
debug_element_render_delay: (attribution as LCPAttribution)
.elementRenderDelay,
debug_target: (attribution as LCPAttribution).element || '(not set)',
};
}
}
Expand Down
17 changes: 10 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import { onCLS, onINP, onLCP } from './web-vitals.js';
import { onCLS, onINP, onFCP, onLCP } from './web-vitals.js';
import { sendToAnalytics } from './send-to-analytics.js';

if (typeof window !== 'undefined' && 'gas4' in window) {
performance.mark('dap-loaded');
const initWebVitalsEvents = () => {
onCLS(sendToAnalytics);
onFCP(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
};

if (typeof window !== 'undefined' && 'gas4' in window) {
initWebVitalsEvents();
} else {
window.addEventListener('dap-universal-federated-analytics-load', () => {
performance.mark('dap-loaded');
onCLS(sendToAnalytics);
onINP(sendToAnalytics);
onLCP(sendToAnalytics);
initWebVitalsEvents();
});
}

performance.mark('dap-performance-addon-loaded');
29 changes: 17 additions & 12 deletions src/send-to-analytics.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import {
formatEventData,
type WebVitalsAttribution,
type WebVitalsName,
} from './format-event-data.js';
import type {
CLSMetricWithAttribution,
FCPMetricWithAttribution,
INPMetricWithAttribution,
LCPMetricWithAttribution,
} from 'web-vitals';

import { formatEventData, type WebVitalsName } from './format-event-data.js';

export type WebVitalsWithAttribution =
| CLSMetricWithAttribution
| FCPMetricWithAttribution
| INPMetricWithAttribution
| LCPMetricWithAttribution;

declare const gas4: any;

Expand All @@ -11,14 +20,9 @@ export const sendToAnalytics = ({
delta,
value,
id,
navigationType,
attribution,
}: {
name: string;
delta: number;
value: number;
id: string;
attribution: WebVitalsAttribution;
}) => {
}: WebVitalsWithAttribution) => {
if (typeof gas4 === 'function') {
gas4(name, {
// Built-in params:
Expand All @@ -27,6 +31,7 @@ export const sendToAnalytics = ({
metric_id: id, // Needed to aggregate events.
metric_value: value, // Value for querying in BQ
metric_delta: delta, // Delta for querying in BQ
metric_navigation_type: navigationType,
// Send the returned values from getDebugInfo() as custom parameters
...formatEventData(name as WebVitalsName, attribution),
});
Expand Down
86 changes: 52 additions & 34 deletions test/unit/format-event-data.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,43 @@ import { it, describe, expect } from 'vitest';
import { formatEventData } from '../../src/format-event-data.js';

describe('formatEventData', () => {
it('should format CLS data correctly', () => {
const attribution = {
largestShiftTime: 1000,
loadState: 'complete',
largestShiftTarget: 'body>div#main',
};

// @ts-ignore
const result = formatEventData('CLS', attribution);

expect(result).toEqual({
debug_time: attribution.largestShiftTime,
debug_load_state: attribution.loadState,
debug_target: attribution.largestShiftTarget,
});
});

it('should format FCP data correctly', () => {
const attribution = {
timeToFirstByte: 7.199999999254942,
firstByteToFCP: 456.9000000022352,
loadState: 'dom-interactive',
};

// @ts-ignore
const result = formatEventData('FCP', attribution);

expect(result).toEqual({
debug_time_to_first_byte: attribution.timeToFirstByte,
debug_first_byte_to_fcp: attribution.firstByteToFCP,
debug_load_state: attribution.loadState,
debug_target: attribution.loadState,
});
});

it('should format LCP data correctly', () => {
const lcpAttribution = {
const attribution = {
url: 'https://localhost/',
timeToFirstByte: 100,
resourceLoadDelay: 50,
Expand All @@ -12,20 +47,20 @@ describe('formatEventData', () => {
element: 'body>div#main',
};

const result = formatEventData('LCP', lcpAttribution);
const result = formatEventData('LCP', attribution);

expect(result).toEqual({
debug_url: 'https://localhost/',
debug_time_to_first_byte: 100,
debug_resource_load_delay: 50,
debug_resource_load_duration: 120,
debug_element_render_delay: 10,
debug_target: 'body>div#main',
debug_url: attribution.url,
debug_time_to_first_byte: attribution.timeToFirstByte,
debug_resource_load_delay: attribution.resourceLoadDelay,
debug_resource_load_duration: attribution.resourceLoadDuration,
debug_element_render_delay: attribution.elementRenderDelay,
debug_target: attribution.element,
});
});

it('should format INP data correctly', () => {
const inpAttribution = {
const attribution = {
interactionType: 'mousedown',
interactionTime: 1000,
loadState: 'interactive',
Expand All @@ -36,33 +71,16 @@ describe('formatEventData', () => {
};

// @ts-ignore
const result = formatEventData('INP', inpAttribution);

expect(result).toEqual({
debug_event: 'mousedown',
debug_time: 1000,
debug_load_state: 'interactive',
debug_target: 'body>button#submit',
debug_interaction_delay: 50,
debug_processing_duration: 20,
debug_presentation_delay: 5,
});
});

it('should format CLS data correctly', () => {
const clsAttribution = {
largestShiftTime: 1000,
loadState: 'complete',
largestShiftTarget: 'body>div#main',
};

// @ts-ignore
const result = formatEventData('CLS', clsAttribution);
const result = formatEventData('INP', attribution);

expect(result).toEqual({
debug_time: 1000,
debug_load_state: 'complete',
debug_target: 'body>div#main',
debug_event: attribution.interactionType,
debug_time: attribution.interactionTime,
debug_load_state: attribution.loadState,
debug_target: attribution.interactionTarget,
debug_interaction_delay: attribution.inputDelay,
debug_processing_duration: attribution.processingDuration,
debug_presentation_delay: attribution.presentationDelay,
});
});

Expand Down

0 comments on commit 9f7884b

Please sign in to comment.