Skip to content

Conversation

@MatteoGabriele
Copy link
Owner

@MatteoGabriele MatteoGabriele commented Mar 30, 2025

sets utm tags manually when using pageview

closes #517
closes #351
closes #405

@dosubot dosubot bot added the size:M This PR changes 30-99 lines, ignoring generated files. label Mar 30, 2025
@coderabbitai
Copy link

coderabbitai bot commented Mar 30, 2025

Walkthrough

The changes introduce several new utility functions for handling UTM parameters and URL manipulation. The useUtmParams function extracts UTM parameters from a URL, transforms certain keys, and returns the cleaned parameters and URL. The hasUtmParams function checks if a URL contains any UTM parameters. The urlQueryReplace function replaces the browser's current URL query parameters without reloading the page. The getPathWithBase function concatenates a base URL and a path with proper normalization. In the pageview function, these utilities are used to detect and extract UTM parameters from the page URL, update the URL and router query parameters to remove UTM data, and store the extracted campaign information. Comprehensive tests are added to verify the correct extraction, cleaning, and handling of UTM parameters and query strings.

Assessment against linked issues

Objective Addressed Explanation
Implement UTM tracking with no dependencies (#517, #351)
Extract UTM parameters from URLs and store as campaign info (#517, #351)
Clean UTM parameters from URLs and router queries after extraction (#517, #351)
Ensure tracking occurs before possible router redirects to avoid race conditions (#405)
Provide utility functions for UTM detection and manipulation compatible with Vue Router (#351)
✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

@dosubot dosubot bot added the feature label Mar 30, 2025
Copy link

@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: 2

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 874d048 and 1d20df1.

📒 Files selected for processing (6)
  • src/api/pageview/pageview.test.ts (1 hunks)
  • src/api/pageview/pageview.ts (2 hunks)
  • src/api/set/set.test.ts (1 hunks)
  • src/api/set/set.ts (1 hunks)
  • src/api/time/time.test.ts (1 hunks)
  • src/api/time/time.ts (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
src/api/pageview/pageview.test.ts (1)
src/api/pageview/pageview.ts (1)
  • pageview (17-63)
🪛 GitHub Check: build
src/api/pageview/pageview.test.ts

[failure] 130-130: src/api/pageview/pageview.test.ts > pageview > should send utm parameters manually outside the page_view event
AssertionError: expected 1st "query" call to have been called with [ 'set', 'utm_tracking', { …(3) } ]

  • Expected
  • Received

    [
    "set",

  • "utm_tracking",
  • "campaign",
    {
    "utm_campaign": "summer_sale",
    "utm_medium": "cpc",
    "utm_source": "google",
    },
    ]

❯ src/api/pageview/pageview.test.ts:130:19

🪛 GitHub Actions: CI
src/api/pageview/pageview.test.ts

[error] 130-130: AssertionError: expected 1st 'query' call to have been called with [ 'set', 'utm_tracking', { …(3) } ]

🔇 Additional comments (7)
src/api/set/set.test.ts (1)

2-2: Import syntax updated correctly.

The import statement has been appropriately updated from a default import to a named import, aligning with the corresponding changes in the implementation file.

src/api/time/time.ts (1)

4-4: Export declaration correctly updated.

The export declaration has been properly changed from a default export to a named export, which improves consistency and makes imports more explicit.

src/api/set/set.ts (1)

6-6: Export declaration correctly updated.

The export declaration has been properly changed from a default export to a named export, which improves consistency and makes imports more explicit.

src/api/time/time.test.ts (1)

2-2: Import syntax updated correctly.

The import statement has been appropriately updated from a default import to a named import, aligning with the corresponding changes in the implementation file.

src/api/pageview/pageview.test.ts (1)

101-121: LGTM! Good test for URL normalization.

This test correctly verifies that paths with and without trailing slashes are normalized to the same format, ensuring consistent tracking.

src/api/pageview/pageview.ts (2)

2-2: Good addition of the set import.

Properly importing the set function needed for UTM parameter handling.


48-50: Good implementation of URL normalization.

This correctly removes trailing slashes from non-root paths, ensuring consistent URL tracking.

Copy link

@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 (1)
src/api/pageview/pageview.ts (1)

12-20: Filter out only UTM parameters to avoid unintentional extraction.

Currently, the code removes the "utm_" prefix but does not exclude non-UTM parameters. If you intend to extract only those parameters starting with "utm_", you could filter them:

 function extractUtmParams(url: URL): Record<string, string> {
   const params: Record<string, string> = {};

-  url.searchParams.forEach((value, key) => {
-    params[key.replace(UTM_PREFIX, "")] = value;
+  [...url.searchParams.entries()].forEach(([key, value]) => {
+    if (key.startsWith(UTM_PREFIX)) {
+      params[key.replace(UTM_PREFIX, "")] = value;
+    }
   });

   return params;
 }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 5391c99 and bda7165.

📒 Files selected for processing (2)
  • src/api/pageview/pageview.test.ts (1 hunks)
  • src/api/pageview/pageview.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/api/pageview/pageview.test.ts
🔇 Additional comments (2)
src/api/pageview/pageview.ts (2)

2-2: Import usage validated.

This import is used to set the extracted UTM parameters as campaign data. No issues found at this line.


10-10: Naming convention is clear.

Defining UTM_PREFIX is straightforward and enhances clarity for prefix-based parameter manipulation.

Copy link

@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: 0

🧹 Nitpick comments (1)
package.json (1)

19-19: Potential Impact: Type-check Removal in Build Script

By changing the "build" script from "tsc --noEmit && vite build" to "vite build", the project no longer performs a TypeScript type-check during the build phase. This might speed up the build process, but it also means that type errors could go unnoticed if not caught during continuous integration or via a separate type-checking process. Please ensure that you have an alternative mechanism (e.g., a dedicated CI step or a separate npm script like "typecheck": "tsc --noEmit") to catch type errors.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between bda7165 and f6a30f3.

📒 Files selected for processing (1)
  • package.json (1 hunks)

Copy link

@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: 0

🧹 Nitpick comments (2)
src/utils.ts (2)

95-115: Add error handling for invalid URLs

The useUtmParams function creates a new URL object which will throw if the input is not a valid URL. Consider adding error handling to manage invalid URLs gracefully.

 export function useUtmParams(pageviewUrl: string): UseUtmParams {
-  const url = new URL(pageviewUrl);
+  try {
+    const url = new URL(pageviewUrl);
+    const utmParams: Record<string, string> = {};
+    const params: string[] = [];
+
+    url.searchParams.forEach((value, key) => {
+      if (key.includes(UTM_PREFIX)) {
+        utmParams[key.replace(UTM_PREFIX, "")] = value;
+        params.push(key);
+      }
+    });
+
+    for (const utmParam of params) {
+      url.searchParams.delete(utmParam);
+    }
+
+    return {
+      utmParams,
+      cleanUrl: url.toString(),
+    };
+  } catch (error) {
+    // Return original URL if invalid
+    return {
+      utmParams: {},
+      cleanUrl: pageviewUrl,
+    };
+  }
 }

117-122: Ensure URL normalization handles all edge cases

The getPathWithBase function handles common edge cases like trailing and leading slashes, but may not handle all URL formatting scenarios. Consider using URL constructor or path libraries for more robust path handling, especially for complex URL structures.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between f6a30f3 and 4f43f9d.

📒 Files selected for processing (3)
  • src/api/pageview/pageview.test.ts (1 hunks)
  • src/api/pageview/pageview.ts (2 hunks)
  • src/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/api/pageview/pageview.test.ts
  • src/api/pageview/pageview.ts
🔇 Additional comments (3)
src/utils.ts (3)

83-86: Type definition looks good

The UseUtmParams type definition clearly defines the structure for the return value of the useUtmParams function, containing both the extracted UTM parameters and the cleaned URL.


88-88: Good use of constant for UTM prefix

Using a constant for the UTM prefix instead of hardcoding "utm_" throughout the code improves maintainability and consistency.


90-93: Simple and efficient UTM detection

The hasUtmParams function uses a regular expression to efficiently detect the presence of UTM parameters in a URL. The implementation is concise and effective.

Copy link

@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: 0

🧹 Nitpick comments (3)
src/utils.ts (3)

90-110: Consider adding URL validation for error handling.

The function correctly extracts UTM parameters and cleans the URL, but it lacks input validation. If an invalid URL is passed, the new URL(url) constructor will throw an error.

export function useUtmParams(url: string): UseUtmParams {
+  try {
    const urlObject = new URL(url);
    const utmParams: Record<string, string> = {};
    const params: string[] = [];

    urlObject.searchParams.forEach((value, key) => {
      if (key.includes(UTM_PREFIX)) {
        utmParams[key.replace(UTM_PREFIX, "")] = value;
        params.push(key);
      }
    });

    for (const utmParam of params) {
      urlObject.searchParams.delete(utmParam);
    }

    return {
      utmParams,
      cleanUrl: urlObject.toString(),
    };
+  } catch (error) {
+    // Return original URL if invalid
+    return { utmParams: {}, cleanUrl: url };
+  }
}

112-115: RegEx pattern might match partial parameters.

The current regex pattern might match URLs where the UTM parameter is part of another parameter name (e.g., custom_utm_source). Consider using word boundaries or a more specific pattern.

export function hasUtmParams(url: string): boolean {
-  const utmRegex = new RegExp(`[?&]${UTM_PREFIX}`);
+  const utmRegex = new RegExp(`[?&](${UTM_PREFIX}[^=&]*=)`);
  return !!url.match(utmRegex);
}

117-122: Path concatenation looks good, but could handle more edge cases.

The function properly handles trailing and leading slashes, but doesn't address URLs with query parameters or fragments.

Consider using the URL API for more robust URL handling:

export function getPathWithBase(path: string, base: string): string {
-  const normalizedBase = base.endsWith("/") ? base : `${base}/`;
-  const normalizedPath = path.startsWith("/") ? path.substring(1) : path;
-
-  return `${normalizedBase}${normalizedPath}`;
+  try {
+    // Handle relative paths correctly
+    const baseUrl = new URL(base.endsWith("/") ? base : `${base}/`, window.location.href);
+    const fullPath = new URL(path, baseUrl);
+    return fullPath.toString();
+  } catch (error) {
+    // Fallback to simple concatenation if URL parsing fails
+    const normalizedBase = base.endsWith("/") ? base : `${base}/`;
+    const normalizedPath = path.startsWith("/") ? path.substring(1) : path;
+    return `${normalizedBase}${normalizedPath}`;
+  }
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4f43f9d and 360897c.

📒 Files selected for processing (1)
  • src/utils.ts (1 hunks)
🔇 Additional comments (2)
src/utils.ts (2)

83-83: Good use of a constant for UTM prefix.

Using a constant for the UTM prefix ensures consistency across the codebase and avoids magic strings.


85-89: Clean type definition for UseUtmParams.

The type definition is clear and well-structured, making the return value of the useUtmParams function easy to understand.

This was referenced Apr 4, 2025
Copy link

@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: 2

🧹 Nitpick comments (1)
src/utils.ts (1)

112-115: Improve UTM parameter detection regex.

The regex in hasUtmParams should be more specific to avoid false positives.

export function hasUtmParams(url: string): boolean {
-  const utmRegex = new RegExp(`[?&]${UTM_PREFIX}`);
+  // Only match utm_ parameters in the query string, not in fragments
+  const utmRegex = new RegExp(`[?&]${UTM_PREFIX}[^&#=]*=`);
  return !!url.match(utmRegex);
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between efdae53 and 1452414.

📒 Files selected for processing (2)
  • src/api/pageview/pageview.ts (3 hunks)
  • src/utils.ts (1 hunks)
🧰 Additional context used
🧬 Code Definitions (1)
src/api/pageview/pageview.ts (2)
src/core/settings/types.ts (1)
  • Route (11-11)
src/utils.ts (2)
  • hasUtmParams (112-115)
  • useUtmParams (90-110)
🔇 Additional comments (7)
src/api/pageview/pageview.ts (4)

2-2: Appropriate use of the new import.

The import of the set function is correctly added to support the new UTM tracking functionality.


5-5: Import statement is properly updated.

The import statement correctly includes the new utility functions hasUtmParams and useUtmParams which are essential for the UTM parameter detection and handling.


11-11: Settings retrieval properly updated.

The destructuring of getSettings() correctly includes the new useUtmTracking option which controls the UTM parameter tracking feature.


24-24: Good defensive programming practice.

Converting params.name to a string ensures type safety for the page_title property.

src/utils.ts (3)

83-83: Good use of constants for maintainability.

Using a constant for the UTM prefix improves code maintainability and reduces the risk of inconsistencies.


85-88: Well-defined return type.

The UseUtmParams type clearly defines the structure for the return value of useUtmParams, enhancing type safety and documentation.


117-122: Well-implemented path concatenation.

The getPathWithBase function correctly normalizes both the base and path components to ensure proper URL formation without duplicated or missing slashes.

Comment on lines 45 to 52
if (useUtmTracking && hasUtmParams(template.page_location)) {
const { utmParams, cleanUrl } = useUtmParams(template.page_location);

template.page_location = cleanUrl;
window.location.href = cleanUrl;

set("campaign", utmParams);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Reconsider modifying window.location.href.

The UTM tracking logic works correctly to extract parameters and clean the URL, but modifying window.location.href will cause a page reload, disrupting the user experience. Consider removing this line or making it optional.

  if (useUtmTracking && hasUtmParams(template.page_location)) {
    const { utmParams, cleanUrl } = useUtmParams(template.page_location);

    template.page_location = cleanUrl;
-   window.location.href = cleanUrl;
    
    set("campaign", utmParams);
  }
📝 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.

Suggested change
if (useUtmTracking && hasUtmParams(template.page_location)) {
const { utmParams, cleanUrl } = useUtmParams(template.page_location);
template.page_location = cleanUrl;
window.location.href = cleanUrl;
set("campaign", utmParams);
}
if (useUtmTracking && hasUtmParams(template.page_location)) {
const { utmParams, cleanUrl } = useUtmParams(template.page_location);
template.page_location = cleanUrl;
set("campaign", utmParams);
}

Comment on lines 90 to 110
export function useUtmParams(url: string): UseUtmParams {
const urlObject = new URL(url);
const utmParams: Record<string, string> = {};
const params: string[] = [];

urlObject.searchParams.forEach((value, key) => {
if (key.includes(UTM_PREFIX)) {
utmParams[key.replace(UTM_PREFIX, "").replace("campaign", "id")] = value;
params.push(key);
}
});

for (const utmParam of params) {
urlObject.searchParams.delete(utmParam);
}

return {
utmParams,
cleanUrl: urlObject.toString(),
};
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling and document the campaign->id transformation.

The useUtmParams function is well structured but has two issues:

  1. It lacks error handling for invalid URLs
  2. The transformation of "campaign" to "id" in line 97 is not well documented and might be confusing
export function useUtmParams(url: string): UseUtmParams {
-  const urlObject = new URL(url);
-  const utmParams: Record<string, string> = {};
-  const params: string[] = [];
+  try {
+    const urlObject = new URL(url);
+    const utmParams: Record<string, string> = {};
+    const params: string[] = [];

-  urlObject.searchParams.forEach((value, key) => {
-    if (key.includes(UTM_PREFIX)) {
-      utmParams[key.replace(UTM_PREFIX, "").replace("campaign", "id")] = value;
-      params.push(key);
-    }
-  });
+    urlObject.searchParams.forEach((value, key) => {
+      if (key.includes(UTM_PREFIX)) {
+        // Replace "campaign" with "id" to match Google Analytics campaign parameter naming
+        utmParams[key.replace(UTM_PREFIX, "").replace("campaign", "id")] = value;
+        params.push(key);
+      }
+    });

-  for (const utmParam of params) {
-    urlObject.searchParams.delete(utmParam);
-  }
+    for (const utmParam of params) {
+      urlObject.searchParams.delete(utmParam);
+    }

-  return {
-    utmParams,
-    cleanUrl: urlObject.toString(),
-  };
+    return {
+      utmParams,
+      cleanUrl: urlObject.toString(),
+    };
+  } catch (error) {
+    console.error("Invalid URL:", url, error);
+    return {
+      utmParams: {},
+      cleanUrl: url,
+    };
+  }
}
📝 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.

Suggested change
export function useUtmParams(url: string): UseUtmParams {
const urlObject = new URL(url);
const utmParams: Record<string, string> = {};
const params: string[] = [];
urlObject.searchParams.forEach((value, key) => {
if (key.includes(UTM_PREFIX)) {
utmParams[key.replace(UTM_PREFIX, "").replace("campaign", "id")] = value;
params.push(key);
}
});
for (const utmParam of params) {
urlObject.searchParams.delete(utmParam);
}
return {
utmParams,
cleanUrl: urlObject.toString(),
};
}
export function useUtmParams(url: string): UseUtmParams {
try {
const urlObject = new URL(url);
const utmParams: Record<string, string> = {};
const params: string[] = [];
urlObject.searchParams.forEach((value, key) => {
if (key.includes(UTM_PREFIX)) {
// Replace "campaign" with "id" to match Google Analytics campaign parameter naming
utmParams[key.replace(UTM_PREFIX, "").replace("campaign", "id")] = value;
params.push(key);
}
});
for (const utmParam of params) {
urlObject.searchParams.delete(utmParam);
}
return {
utmParams,
cleanUrl: urlObject.toString(),
};
} catch (error) {
console.error("Invalid URL:", url, error);
return {
utmParams: {},
cleanUrl: url,
};
}
}

Copy link

@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: 2

🧹 Nitpick comments (1)
src/types/settings.ts (1)

208-213: Add @default false (and/or an explicit default) for the new useUtmTracking flag

All other boolean flags in Settings document their default behaviour.
For consistency and to avoid ambiguity for integrators, please state that the flag is false by default (and, ideally, make the runtime default explicit in core/settings.ts).

  /**
   * When enabled, automatically captures UTM parameters from URLs
   * and sends them as separate tracking events.
+  * @default false
   */
   useUtmTracking?: boolean;
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1452414 and 09eb2a3.

📒 Files selected for processing (3)
  • src/api/pageview.ts (3 hunks)
  • src/tests/api/pageview.test.ts (1 hunks)
  • src/types/settings.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/api/pageview.ts (5)
src/types/gtag.ts (1)
  • GtagConfigParams (69-78)
src/types/settings.ts (1)
  • Route (11-11)
src/core/settings.ts (1)
  • getSettings (17-19)
src/utils.ts (2)
  • hasUtmParams (112-115)
  • useUtmParams (90-110)
src/api/set.ts (1)
  • set (6-8)
🔇 Additional comments (1)
src/api/pageview.ts (1)

46-53: Possible mismatch between extracted UTM keys and test expectations

useUtmParams() currently converts utm_campaignid, yet the tests expect the key campaign.
If the GA4 “campaign” field is intended, keep the original key; otherwise the new tests will fail.

-      utmParams[key.replace(UTM_PREFIX, "").replace("campaign", "id")] = value;
+      utmParams[key.replace(UTM_PREFIX, "")] = value;

Please align either the utility or the tests before merging.

Copy link

@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: 0

🧹 Nitpick comments (1)
src/tests/api/pageview.test.ts (1)

124-202: Consider adding tests for edge cases

The current tests cover the main functionality well, but consider adding tests for edge cases:

  1. When only some UTM parameters are present
  2. When useUtmTracking is enabled but no UTM parameters exist in the URL

These would enhance the robustness of your test suite.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1d8b2c2 and c508d2f.

📒 Files selected for processing (4)
  • package.json (1 hunks)
  • src/api/pageview.ts (3 hunks)
  • src/tests/api/pageview.test.ts (2 hunks)
  • src/utils.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/utils.ts
  • src/api/pageview.ts
🔇 Additional comments (5)
src/tests/api/pageview.test.ts (5)

22-22: Added router replace spy for monitoring UTM cleaning operations

This spy is effectively used in the new test at lines 178-202 to verify that UTM parameters are properly removed from the router query.


124-141: LGTM: Baseline test for UTM parameters behavior

This test properly verifies the default behavior where UTM parameters are preserved in the page_location URL when UTM tracking is not enabled.


143-169: Good implementation of the custom UTM tracking test

This test correctly verifies that:

  1. UTM parameters are extracted and sent as a separate "campaign" object with the expected mapping
  2. The UTM parameters are removed from the page_location in the subsequent page_view event
  3. Other query parameters remain intact

The test meets the requirements of the PR objective to enable manual setting of UTM tags.


154-158: Fixed assertion to match implementation

This correctly uses id instead of campaign for the UTM campaign parameter, resolving the issue identified in a previous review.


178-202: Good test for router query cleaning behavior

This test effectively verifies that when both pageTracker and useUtmTracking are enabled, the router's query is properly updated to remove UTM parameters while preserving other parameters.

Copy link

@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)
src/utils.ts (1)

108-133: ⚠️ Potential issue

Add error handling for invalid URLs.

The function is well-structured but lacks error handling for invalid URLs, which could cause runtime errors. The campaign->id transformation is now properly documented, which addresses part of the previous feedback.

export function useUtmParams(url: string): UseUtmParams {
-  const urlObject = new URL(url);
-  const utmParams: Record<string, string> = {};
-  const params: string[] = [];
-  const cleanQueryParams: Record<string, string> = {};
+  try {
+    const urlObject = new URL(url);
+    const utmParams: Record<string, string> = {};
+    const params: string[] = [];
+    const cleanQueryParams: Record<string, string> = {};

-  urlObject.searchParams.forEach((value, key) => {
-    if (key.includes(UTM_PREFIX)) {
-      // Replace "campaign" with "id" to match Google Analytics campaign parameter naming
-      utmParams[key.replace(UTM_PREFIX, "").replace("campaign", "id")] = value;
-      params.push(key);
-    } else {
-      cleanQueryParams[key] = value;
-    }
-  });
+    urlObject.searchParams.forEach((value, key) => {
+      if (key.includes(UTM_PREFIX)) {
+        // Replace "campaign" with "id" to match Google Analytics campaign parameter naming
+        utmParams[key.replace(UTM_PREFIX, "").replace("campaign", "id")] = value;
+        params.push(key);
+      } else {
+        cleanQueryParams[key] = value;
+      }
+    });

-  for (const utmParam of params) {
-    urlObject.searchParams.delete(utmParam);
-  }
+    for (const utmParam of params) {
+      urlObject.searchParams.delete(utmParam);
+    }

-  return {
-    utmParams,
-    cleanQueryParams,
-    cleanUrl: urlObject.toString(),
-  };
+    return {
+      utmParams,
+      cleanQueryParams,
+      cleanUrl: urlObject.toString(),
+    };
+  } catch (error) {
+    console.error("Invalid URL:", url, error);
+    return {
+      utmParams: {},
+      cleanQueryParams: {},
+      cleanUrl: url,
+    };
+  }
}
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4b617b3 and 2c562fb.

📒 Files selected for processing (4)
  • src/api/pageview.ts (3 hunks)
  • src/tests/api/pageview.test.ts (3 hunks)
  • src/utils.test.ts (1 hunks)
  • src/utils.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/tests/api/pageview.test.ts
  • src/api/pageview.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/utils.ts (1)
src/api/query.ts (1)
  • QueryParams (5-5)
🔇 Additional comments (4)
src/utils.ts (4)

83-97: Clean implementation of URL query parameter replacement.

This new utility function correctly handles replacing query parameters without triggering a page reload, with appropriate server-side check. It clears existing search parameters and sets new ones from the provided object.


99-106: Well-structured type definitions for UTM parameter handling.

The UTM_PREFIX constant and UseUtmParams type clearly define the structure needed for UTM parameter handling, making the code more maintainable and type-safe.


135-138: Efficient implementation of UTM parameter detection.

The regex-based approach is a performant way to check for UTM parameters in a URL without needing to create a URL object and iterate through all parameters.


140-145: Robust path concatenation handling.

This utility correctly handles the edge cases of trailing and leading slashes when combining base URLs with paths, preventing common issues like double slashes or missing slashes.

Copy link

@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: 0

🧹 Nitpick comments (1)
src/tests/utils.test.ts (1)

77-138: Well-structured and comprehensive test suite for URL query parameter replacement.

The test suite for urlQueryReplace is well-implemented with proper setup and teardown of mocks for window.location and window.history.replaceState. The tests cover key scenarios including replacing parameters, clearing all parameters, and handling empty values.

Consider adding tests for the following edge cases:

  • URL with special characters or URL-encoded values
  • URL with hash fragments (e.g., #section)
  • URL with multiple occurrences of the same parameter
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2c562fb and 115f8cd.

📒 Files selected for processing (2)
  • src/tests/index.test.ts (1 hunks)
  • src/tests/utils.test.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • src/tests/index.test.ts

Copy link

@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: 0

🧹 Nitpick comments (1)
src/api/pageview.ts (1)

51-61: Well-implemented UTM parameter handling

The implementation correctly:

  1. Detects presence of UTM parameters in the URL
  2. Extracts and transforms UTM parameters to campaign parameters
  3. Updates the page_location with a clean URL
  4. Updates the browser's URL query without reloading the page
  5. Sets the campaign data using the extracted UTM parameters

Consider adding error handling for potential URL parsing failures in case the page_location is invalid.

  if (hasUtmParams(template.page_location)) {
-    const { utmParams, cleanUrl, cleanQueryParams } = useUtmParams(
-      template.page_location,
-    );
+    try {
+      const { utmParams, cleanUrl, cleanQueryParams } = useUtmParams(
+        template.page_location,
+      );
+      
+      template.page_location = cleanUrl;
+      
+      urlQueryReplace(cleanQueryParams);
+      
+      set("campaign", utmParams);
+    } catch (error) {
+      // Log error but continue with pageview
+      console.error("Failed to process UTM parameters:", error);
+    }
-
-    template.page_location = cleanUrl;
-
-    urlQueryReplace(cleanQueryParams);
-
-    set("campaign", utmParams);
  }
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 115f8cd and 93fdc01.

📒 Files selected for processing (2)
  • src/api/pageview.ts (3 hunks)
  • src/tests/api/pageview.test.ts (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/api/pageview.ts (2)
src/utils.ts (3)
  • hasUtmParams (135-138)
  • useUtmParams (108-133)
  • urlQueryReplace (83-97)
src/api/set.ts (1)
  • set (6-8)
🔇 Additional comments (6)
src/tests/api/pageview.test.ts (3)

15-15: Good test setup for URL manipulation validation

Setting up the spy on urlQueryReplace is a good approach to verify that the function is called correctly with the expected parameters when UTM parameters are present.


126-148: Well-structured test for UTM parameter extraction and URL cleaning

This test correctly validates two key aspects of the UTM handling functionality:

  1. That UTM parameters are extracted and set as campaign data
  2. That the page_location is cleaned from UTM parameters while preserving other query parameters

The test uses proper assertions and accurate expectations based on the implementation in the utils module.


157-174: Comprehensive test for router query UTM parameter cleanup

This test thoroughly verifies that when using the router with UTM parameters in the query, the urlQueryReplace utility is called with a cleaned query object that preserves non-UTM parameters. The test structure is clear and focused on a single responsibility.

src/api/pageview.ts (3)

6-11: Good modularization of utility functions

Importing these utility functions from a shared module improves code organization and reusability. The functions have clear, descriptive names that indicate their purpose.


30-30: Safer type handling with explicit toString()

Using explicit toString() method instead of type assertion is a safer approach that avoids potential type errors at runtime.


51-61:

✅ Verification successful

Verify URL handling when pageTracker is enabled

When pageTracker is enabled, there could be potential duplication in URL handling since the code always processes template.page_location for UTM parameters, but there's a separate check for router query cleanup in the test. Ensure that this doesn't lead to double cleanup of UTM parameters.


🏁 Script executed:

#!/bin/bash
# Check how pageview.ts and router integration work together

# Search for places where urlQueryReplace is called
rg -A 3 -B 3 "urlQueryReplace" --type ts

Length of output: 2934


🏁 Script executed:

#!/bin/bash
# Search for references to `pageTracker` in the codebase
rg -R "pageTracker" --type ts

Length of output: 434


🏁 Script executed:

#!/bin/bash
# Corrected search for `pageTracker` references in the TypeScript codebase
rg "pageTracker" --type ts -n

Length of output: 2915


No duplicate URL cleanup detected

I’ve verified that urlQueryReplace is only called once in src/api/pageview.ts when processing UTM parameters. There are no additional calls in any pageTracker hooks or router integrations:

  • Only occurrence of urlQueryReplace in the runtime code is inside the UTM‐handling block in pageview.
  • No other modules (including core tracking or add-router-tracking) invoke urlQueryReplace.

No changes are needed here.

@MatteoGabriele MatteoGabriele merged commit b147fb9 into master May 4, 2025
5 checks passed
@MatteoGabriele MatteoGabriele deleted the feat/set-utm-manually branch May 4, 2025 18:10
MatteoGabriele pushed a commit that referenced this pull request May 4, 2025
# [3.5.0](v3.4.0...v3.5.0) (2025-05-04)

### Features

* **pageview:** extract and track utm parameters ([#618](#618)) ([b147fb9](b147fb9))
@MatteoGabriele
Copy link
Owner Author

🎉 This PR is included in version 3.5.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

feature released size:M This PR changes 30-99 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Track landing page view before router redirect UTM Campaign Tracking

2 participants