Show tracker type in cookie tracking policy
Trackers sharing a display name can differ in type, so the generated cookie and tracking technologies policy was ambiguous without it. Carry the tracker type through the banner version snapshot and surface it as a dedicated column in the policy table. Stop the snapshot from dropping non-cookie trackers so storage, IndexedDB and cache technologies appear in the policy and served banner config with their real type. Duration now reflects the type when no max-age applies: session storage clears with the tab, the remaining storage technologies persist. Legacy snapshots predate the field and only ever held cookies, so GetSnapshot backfills an empty type as COOKIE, keeping the non-null GraphQL enum and policy output valid without a migration. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -43,7 +43,7 @@ func TestSnapshotsEqual(t *testing.T) {
|
||||
Description: "Analytics cookies",
|
||||
Kind: coredata.CookieCategoryKindNormal,
|
||||
Cookies: coredata.CookieItems{
|
||||
{Name: "_ga", MaxAgeSeconds: &maxAge, Description: "Google Analytics"},
|
||||
{Name: "_ga", TrackerType: coredata.TrackerTypeCookie, MaxAgeSeconds: &maxAge, Description: "Google Analytics"},
|
||||
},
|
||||
GCMConsentTypes: []string{"analytics_storage"},
|
||||
PostHogConsent: false,
|
||||
|
||||
@@ -80,14 +80,11 @@ func buildSnapshot(
|
||||
cookiesByCategory := make(map[gid.GID]coredata.CookieItems)
|
||||
|
||||
for _, p := range allPatterns {
|
||||
if p.TrackerType != coredata.TrackerTypeCookie {
|
||||
continue
|
||||
}
|
||||
|
||||
cookiesByCategory[p.CookieCategoryID] = append(
|
||||
cookiesByCategory[p.CookieCategoryID],
|
||||
coredata.CookieItem{
|
||||
Name: p.DisplayName,
|
||||
TrackerType: p.TrackerType,
|
||||
MaxAgeSeconds: p.MaxAgeSeconds,
|
||||
Description: p.Description,
|
||||
},
|
||||
|
||||
@@ -126,6 +126,18 @@ func (v *CookieBannerVersion) GetSnapshot() (CookieBannerVersionSnapshot, error)
|
||||
return snapshot, fmt.Errorf("cannot unmarshal cookie banner version snapshot: %w", err)
|
||||
}
|
||||
|
||||
// Snapshots created before tracker types were captured only ever held
|
||||
// cookie-type trackers, so their cookie items carry an empty tracker
|
||||
// type. Backfill them as cookies so downstream consumers (policy
|
||||
// generation, GraphQL, served banner config) see a valid type.
|
||||
for i := range snapshot.Categories {
|
||||
for j := range snapshot.Categories[i].Cookies {
|
||||
if snapshot.Categories[i].Cookies[j].TrackerType == "" {
|
||||
snapshot.Categories[i].Cookies[j].TrackerType = TrackerTypeCookie
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -33,9 +33,10 @@ import (
|
||||
|
||||
type (
|
||||
CookieItem struct {
|
||||
Name string `json:"name"`
|
||||
MaxAgeSeconds *int `json:"max_age_seconds"`
|
||||
Description string `json:"description"`
|
||||
Name string `json:"name"`
|
||||
TrackerType TrackerType `json:"tracker_type"`
|
||||
MaxAgeSeconds *int `json:"max_age_seconds"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
CookieItems []CookieItem
|
||||
@@ -80,13 +81,22 @@ var cookieDurationUnits = [...]cookieDurationUnit{
|
||||
{1, "second", 0},
|
||||
}
|
||||
|
||||
// HumanizedDuration renders the cookie's max-age into a human-readable lifetime
|
||||
// using the same snapping and composition rules as the banner's
|
||||
// humanizeDuration helper. A nil or non-positive max-age denotes a session
|
||||
// cookie that is cleared when the browser closes.
|
||||
// HumanizedDuration renders the tracker's max-age into a human-readable
|
||||
// lifetime using the same snapping and composition rules as the banner's
|
||||
// humanizeDuration helper. A nil or non-positive max-age has no fixed
|
||||
// expiry, so the lifetime is described by the tracker type: session cookies
|
||||
// are cleared when the browser closes, session storage is cleared when the
|
||||
// tab closes, and the remaining storage technologies persist until cleared.
|
||||
func (c CookieItem) HumanizedDuration() string {
|
||||
if c.MaxAgeSeconds == nil || *c.MaxAgeSeconds <= 0 {
|
||||
return "Session"
|
||||
switch c.TrackerType {
|
||||
case TrackerTypeSessionStorage:
|
||||
return "Until the tab is closed"
|
||||
case TrackerTypeLocalStorage, TrackerTypeIndexedDB, TrackerTypeCacheStorage:
|
||||
return "Persistent"
|
||||
default:
|
||||
return "Session"
|
||||
}
|
||||
}
|
||||
|
||||
remaining := *c.MaxAgeSeconds
|
||||
|
||||
@@ -63,6 +63,26 @@ func (v TrackerType) String() string {
|
||||
return string(v)
|
||||
}
|
||||
|
||||
// Label returns a human-readable name for the tracker type, suitable for
|
||||
// display in visitor-facing documents such as the cookie and tracking
|
||||
// technologies policy.
|
||||
func (v TrackerType) Label() string {
|
||||
switch v {
|
||||
case TrackerTypeCookie:
|
||||
return "Cookie"
|
||||
case TrackerTypeLocalStorage:
|
||||
return "Local storage"
|
||||
case TrackerTypeSessionStorage:
|
||||
return "Session storage"
|
||||
case TrackerTypeIndexedDB:
|
||||
return "IndexedDB"
|
||||
case TrackerTypeCacheStorage:
|
||||
return "Cache storage"
|
||||
default:
|
||||
return string(v)
|
||||
}
|
||||
}
|
||||
|
||||
func (v TrackerType) MarshalText() ([]byte, error) {
|
||||
return []byte(v.String()), nil
|
||||
}
|
||||
|
||||
@@ -489,6 +489,7 @@ type (
|
||||
|
||||
TrackerPolicyTracker struct {
|
||||
Name string
|
||||
Type string
|
||||
Purpose string
|
||||
Duration string
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ The tables below describe the categories of trackers we use on the Website, the
|
||||
{{ with .Description }}{{ . }}
|
||||
{{ else }}Trackers in this category support the functionality described by its name.
|
||||
{{ end }}{{ if .Trackers }}
|
||||
| Tracker | Purpose | Duration |
|
||||
| --- | --- | --- |
|
||||
{{ range .Trackers }}| {{ .Name }} | {{ .Purpose }} | {{ .Duration }} |
|
||||
| Tracker | Type | Purpose | Duration |
|
||||
| --- | --- | --- | --- |
|
||||
{{ range .Trackers }}| {{ .Name }} | {{ .Type }} | {{ .Purpose }} | {{ .Duration }} |
|
||||
{{ end }}{{ else }}
|
||||
We are not currently using any trackers in this category.
|
||||
{{ end }}{{ end }}
|
||||
|
||||
@@ -191,6 +191,7 @@ func (s *GeneratedDocumentService) buildTrackerPolicyDocumentData(
|
||||
for _, cookie := range c.Cookies {
|
||||
trackers = append(trackers, docgen.TrackerPolicyTracker{
|
||||
Name: sanitizeTrackerCell(cookie.Name),
|
||||
Type: cookie.TrackerType.Label(),
|
||||
Purpose: trackerPurpose(cookie.Description),
|
||||
Duration: cookie.HumanizedDuration(),
|
||||
})
|
||||
|
||||
@@ -416,6 +416,7 @@ func (r *cookieBannerVersionResolver) Categories(ctx context.Context, obj *types
|
||||
for j, c := range cat.Cookies {
|
||||
cookies[j] = &types.CookieBannerVersionCookie{
|
||||
Name: c.Name,
|
||||
TrackerType: c.TrackerType,
|
||||
MaxAgeSeconds: c.MaxAgeSeconds,
|
||||
Description: c.Description,
|
||||
}
|
||||
|
||||
@@ -543,6 +543,7 @@ type CookieBannerVersionCategory {
|
||||
|
||||
type CookieBannerVersionCookie {
|
||||
name: String!
|
||||
trackerType: TrackerType!
|
||||
maxAgeSeconds: Int
|
||||
description: String!
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user