Skip to content

Implement cookie consent for Analytics#1739

Merged
fdevans merged 9 commits into4.0.xfrom
GA4-withaccept
Dec 10, 2025
Merged

Implement cookie consent for Analytics#1739
fdevans merged 9 commits into4.0.xfrom
GA4-withaccept

Conversation

@fdevans
Copy link
Copy Markdown
Contributor

@fdevans fdevans commented Dec 3, 2025

  • Remove auto-loading analytics plugin from config
  • Add CookieConsent component with bottom banner design
  • Implement manual analytics loading only after user consent
  • Add comprehensive tracking: page views, downloads, outbound links, video engagement
  • Track VidStack video interactions (start, progress milestones, completion)
  • Redirect privacy policy to PagerDuty main privacy policy
  • All tracking checks consent before firing (verifiable in Network tab)

- Remove auto-loading analytics plugin from config
- Add CookieConsent component with bottom banner design
- Implement manual analytics loading only after user consent
- Add comprehensive tracking: page views, downloads, outbound links, video engagement
- Track VidStack video interactions (start, progress milestones, completion)
- Redirect privacy policy to PagerDuty main privacy policy
- All tracking checks consent before firing (verifiable in Network tab)
@fdevans fdevans requested review from a team and Copilot December 3, 2025 00:25
@fdevans fdevans added this to the 5.18.0 milestone Dec 3, 2025
@fdevans fdevans changed the title Implement GDPR/CCPA compliant analytics with cookie consent Implement cookie consent for Analytics Dec 3, 2025

This comment was marked as outdated.

- Use correct selector (media-player elements instead of VidStack tags)
- Extract video ID from YouTube iframe embed URL
- Get duration from player element directly (not from event.detail)
- Add console logging for debugging video events
- Track all milestones correctly: video_start, video_progress (25/50/75/100%), video_complete
- Events queue correctly in dataLayer (verified working)
- Remove console.log statements from video tracking functions
- Remove script loading debug logs
- All tracking verified working in testing:
  * video_start events send correctly with video_id
  * video_progress events send with numeric milestone (25/50/75/100)
  * video_complete events send with video_id
  * All events return 204/302 success status
- Ready for production deployment
…de quality

Accessibility improvements:
- Add aria-modal="true" to cookie consent banner
- Add focus management after consent (moves focus to main content)

Performance improvements:
- Add guards to prevent duplicate event listener initialization
- Optimize MutationObserver to only trigger on relevant changes (media-player elements)

Security improvements:
- Sanitize link text before sending to analytics (200 char limit)

Error handling:
- Add try-catch blocks for localStorage operations
- Gracefully handle localStorage unavailability (private browsing)

Code quality:
- Extract shared constants (GA_MEASUREMENT_ID, CONSENT_KEY) to constants.ts
- Remove production console.log statements
- Replace setTimeout with nextTick for page title tracking

All 11 Copilot feedback items addressed

This comment was marked as outdated.

CRITICAL BUG FIX:
- Fix gtag function to spread args instead of pushing arguments object
  This was causing dataLayer to have incorrect structure

Additional improvements:
- Add error handling to hasConsent() for localStorage access
- Extract VIDEO_MILESTONES constant for better maintainability
- Add type safety check for media player duration property
- Enhance text sanitization to remove newlines, tabs, carriage returns
- Add tabindex=-1 to main content for proper focus management
- Create initializeAnalytics() helper to reduce code duplication
- Add { once: true } to consent event listener to prevent memory leaks
- Add consent check to router.afterEach to avoid unnecessary overhead
- Update JSDoc comments to be more generic (not YouTube-specific)

All 17 Copilot feedback items now addressed
Implements ability for users to change their cookie consent preference at any time.

Features:
- New CookieSettings component (button to reopen consent banner)
- Added footer links: 'Privacy Policy' and 'Cookie Settings'
- Footer appears at bottom of all pages using pageBottom slot
- Users can now change consent from Accept→Reject or Reject→Accept

GDPR compliance:
- Users must have easy way to withdraw consent (GDPR requirement)
- Cookie Settings link provides this access at any time
- Previous consent is tracked to avoid duplicate GA4 initializations

Behavior:
- Clicking 'Cookie Settings' reopens the consent banner
- Changing Accept→Reject fires 'ga-consent-revoked' event
- Changing Reject→Accept fires 'ga-consent-granted' event (only if not already accepted)
- Privacy Policy link opens PagerDuty's policy in new tab

Files:
- CookieSettings.vue (new) - Reopens banner button component
- CookieConsent.vue - Added reopenBanner function, tracks previous consent
- Layout.vue - Added pageBottom slot with footer links
- client.ts - Added ga-consent-revoked event listener
Comment thread docs/.vuepress/components/CookieConsent.vue Outdated
Comment thread docs/.vuepress/utils/constants.ts
Copy link
Copy Markdown
Contributor

@smartinellibenedetti smartinellibenedetti left a comment

Choose a reason for hiding this comment

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

I'm thinking that might be necessary to save the timestamp of when the consent was saved and clear it from time to time (I think some places the rule is 6 months, others is a year)

…d denial tracking

1. Add consent expiration for GDPR compliance:
   - Store consent with timestamp and version
   - Consent expires after 6 months (strictest GDPR requirement)
   - Expired consent triggers banner to re-appear
   - Backward compatible with old format (treats old 'true' as expired)
   - Both acceptance and rejection store timestamp

2. Add custom event name constants:
   - CONSENT_GRANTED_EVENT = 'ga-consent-granted'
   - CONSENT_REVOKED_EVENT = 'ga-consent-revoked'
   - CONSENT_DENIED_EVENT = 'ga-consent-denied' (new)
   - Prevents typos, easier to maintain
   - Used consistently across all files

3. Add ga-consent-denied event:
   - Fires when user initially clicks Reject (not Accept→Reject)
   - Distinguishes between 'never accepted' vs 'accepted then revoked'
   - Better analytics granularity for consent tracking

GDPR compliance improved:
- Consent must be renewed every 6 months (strictest requirement)
- Users can track rejection vs revocation events
- Migration path from old localStorage format

Reviewer: Sarah's feedback addressed
@fdevans
Copy link
Copy Markdown
Contributor Author

fdevans commented Dec 8, 2025

Added 6 month time out.

Copy link
Copy Markdown
Contributor

@smartinellibenedetti smartinellibenedetti left a comment

Choose a reason for hiding this comment

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

LGTM! 🚀

@fdevans fdevans merged commit f1b29cd into 4.0.x Dec 10, 2025
3 checks passed
@fdevans fdevans deleted the GA4-withaccept branch December 10, 2025 00:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants