-
Notifications
You must be signed in to change notification settings - Fork 6
feat: add custom pageview property support #16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dgrebb
wants to merge
2
commits into
accuser:develop
Choose a base branch
from
dgrebb:feature/support-custom-pageview-properties
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,19 @@ | |
plausible: PlausibleTracker; | ||
} | ||
|
||
/** | ||
* Type definition for properties that can be sent as part of a page view event to Plausible. | ||
* These properties can be of type boolean, number, or string. | ||
*/ | ||
export type PageviewProp = boolean | number | string; | ||
|
||
/** | ||
* Type definition for pageview properties. | ||
* Can be either a boolean or an array containing a record with keys of type number or string, | ||
* and values of type boolean, number, or string. | ||
*/ | ||
export type PageviewProps = { [key: number | string]: PageviewProp } | boolean; | ||
|
||
declare let window: PlausibleWindow; | ||
|
||
const plausible: PlausibleTracker = (event, options) => window.plausible(event, options); | ||
|
@@ -85,14 +98,76 @@ | |
*/ | ||
export let outboundLinks = false; | ||
|
||
/** | ||
* Holds the pageview properties to be used in the application. | ||
* The properties can be used for filtering in the Plausible dashboard. The pageview properties | ||
* are subject to certain limitations: | ||
* - Limited to 30 total custom properties per event. | ||
* - Limited to 300 characters per property name. | ||
* - Limited to 2000 characters per property value. | ||
* | ||
* @link https://plausible.io/docs/guided-tour#filtering for dashboard filtering by custom property details. | ||
* @defaultValue `false` Indicates no custom properties by default. | ||
*/ | ||
export let pageviewProps: PageviewProps = false; | ||
|
||
/** | ||
* Function references for property validation guards. | ||
* These are dynamically imported during development for validation purposes. | ||
*/ | ||
let isCustomPropsLimit: Function, isCustomPropEntryLimit: Function; | ||
|
||
/** | ||
* Sets the pageview properties as attributes on a given HTML script element. | ||
* This function is responsible for validating and applying the custom properties | ||
* defined in `pageviewProps` to the provided HTML element. | ||
* | ||
* @param {HTMLScriptElement} node - The HTML script element on which to set the attributes. | ||
*/ | ||
const setPageviewProps: (node: HTMLScriptElement) => void = async (node) => { | ||
// Early exit if pageviewProps is not defined or falsy. | ||
if (!pageviewProps) return; | ||
|
||
// In development mode, load validation guards dynamically and validate custom property limits. | ||
if (dev) { | ||
const guards = await import('./guards.js'); | ||
const length = Object.entries(pageviewProps).length; | ||
({ isCustomPropsLimit, isCustomPropEntryLimit } = guards); | ||
|
||
// Validate the total number of custom properties against Plausible's limit. | ||
if (!isCustomPropsLimit(pageviewProps)) { | ||
throw Error( | ||
`Plausible Analytics has a limit of 30 custom properties per event. ${length} properties counted.` | ||
); | ||
} | ||
} | ||
|
||
// Iterate over each key-value pair in pageviewProps to set them as attributes. | ||
Object.entries(pageviewProps).forEach(([key, value]) => { | ||
// In development mode, validate the length of property names and values. | ||
if (dev) { | ||
if (!isCustomPropEntryLimit(300, key)) { | ||
throw Error(`Plausible Analytics limit custom property names to 300 characters.`); | ||
} | ||
if (!isCustomPropEntryLimit(2000, value)) { | ||
throw Error(`Plausible Analytics limit custom property values to 3000 characters.`); | ||
}; | ||
} | ||
|
||
// Set the attribute on the script node. Format: 'event-key' | ||
node.setAttribute(`event-${key}`, String(value)); | ||
}); | ||
}; | ||
|
||
$: api = `${apiHost}/api/event`; | ||
$: src = [ | ||
$: plausibleSrc = [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #12 identified an issue with |
||
`${apiHost}/js/script`, | ||
compat ? 'compat' : undefined, | ||
fileDownloads ? 'file-downloads' : undefined, | ||
hash ? 'hash' : undefined, | ||
local ? 'local' : undefined, | ||
outboundLinks ? 'outbound-links' : undefined, | ||
pageviewProps ? 'pageview-props' : undefined, | ||
'js' | ||
] | ||
.filter(Boolean) | ||
|
@@ -101,7 +176,13 @@ | |
|
||
<svelte:head> | ||
{#if enabled} | ||
<script data-api={api} data-domain={domain.toString()} defer {src}></script> | ||
<script | ||
data-api={api} | ||
data-domain={domain.toString()} | ||
defer | ||
src={plausibleSrc} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #12 identified an issue with |
||
use:setPageviewProps | ||
></script> | ||
<script> | ||
window.plausible = | ||
window.plausible || | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
import { type PageviewProp, type PageviewProps } from './PlausibleAnalytics.svelte'; | ||
|
||
/** | ||
* Handles various value types and issues warnings if the values are not suitable for passing to Plausible. | ||
* Specifically checks for DOMTokenList, HTMLInputElement, Array, RegExp, and Date instances, | ||
* and warns about potential errors if these are not parsed as strings. | ||
* @see {@link https://plausible.io/docs/custom-props/introduction#accepted-values} | ||
* | ||
* @param {unknown} value - The value to be handled and checked. | ||
* @returns {boolean} Returns true if the value is acceptable, otherwise throws an error. | ||
* @throws {Error} Throws an error if the value is neither a number nor a string. | ||
*/ | ||
export const handleEntry = function handleEntry(entry: unknown): boolean { | ||
const typeName = Object.prototype.toString.call(entry).slice(8, -1); | ||
|
||
const warn = (type: string) => | ||
console.warn( | ||
`Warning: Passing ${type} to Plausible may result in error unless parsed as a string.` | ||
); | ||
|
||
switch (typeName) { | ||
case 'DOMTokenList': | ||
case 'HTMLInputElement': | ||
case 'Array': | ||
case 'RegExp': | ||
case 'Date': | ||
warn(typeName); | ||
return true; | ||
|
||
case 'String': | ||
case 'Number': | ||
return true; | ||
|
||
default: | ||
console.warn( | ||
`Plausible Error: Custom property entry ${entry} is not a boolean, number, or string.` | ||
); | ||
return false; | ||
} | ||
}; | ||
|
||
/** | ||
* Checks if the number of custom properties in a PageviewProps object exceeds a specified limit. | ||
* @see {@link https://plausible.io/docs/custom-props/introduction#limits} | ||
* | ||
* @param {PageviewProps} props - The PageviewProps object to check. | ||
* @returns {boolean} Returns true if the number of properties is within the limit, otherwise returns false and logs an error. | ||
*/ | ||
export const isCustomPropsLimit = function isCustomPropsLimit( | ||
dgrebb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
props: PageviewProps | ||
): props is PageviewProps { | ||
const limit = 30; | ||
const length = Object.entries(props).length; | ||
|
||
if (typeof props === 'object' && length > limit) { | ||
return false; | ||
} | ||
|
||
return true; | ||
}; | ||
|
||
/** | ||
* Checks if the length of a custom property name or value exceeds a specified limit. | ||
* @see {@link https://plausible.io/docs/custom-props/introduction#limits} | ||
* | ||
* @param {number} limit - The maximum allowed character length. | ||
* @param {PageviewProp} entry - The custom property entry to check. | ||
* @returns {boolean} Returns true if the entry's length does not exceed the limit, otherwise logs a warning. | ||
*/ | ||
export const isCustomPropEntryLimit = function isCustomPropEntryLimit( | ||
limit: number, | ||
entry: PageviewProp | ||
): entry is PageviewProp { | ||
const asString = entry.toString(); | ||
|
||
// No limit checks needed for boolean | ||
if (typeof entry === 'boolean') return true; | ||
|
||
// Check character length of number with `toString()` | ||
if (typeof entry === 'number' && asString.length > limit) { | ||
return false; | ||
} | ||
|
||
// Error if DOM node passed as prop name or value | ||
if (!handleEntry(entry)) return false; | ||
|
||
if (asString.length > limit) { | ||
return false; | ||
} | ||
|
||
return true; | ||
}; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.