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",
|
Description: "Analytics cookies",
|
||||||
Kind: coredata.CookieCategoryKindNormal,
|
Kind: coredata.CookieCategoryKindNormal,
|
||||||
Cookies: coredata.CookieItems{
|
Cookies: coredata.CookieItems{
|
||||||
{Name: "_ga", MaxAgeSeconds: &maxAge, Description: "Google Analytics"},
|
{Name: "_ga", TrackerType: coredata.TrackerTypeCookie, MaxAgeSeconds: &maxAge, Description: "Google Analytics"},
|
||||||
},
|
},
|
||||||
GCMConsentTypes: []string{"analytics_storage"},
|
GCMConsentTypes: []string{"analytics_storage"},
|
||||||
PostHogConsent: false,
|
PostHogConsent: false,
|
||||||
|
|||||||
@@ -80,14 +80,11 @@ func buildSnapshot(
|
|||||||
cookiesByCategory := make(map[gid.GID]coredata.CookieItems)
|
cookiesByCategory := make(map[gid.GID]coredata.CookieItems)
|
||||||
|
|
||||||
for _, p := range allPatterns {
|
for _, p := range allPatterns {
|
||||||
if p.TrackerType != coredata.TrackerTypeCookie {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
cookiesByCategory[p.CookieCategoryID] = append(
|
cookiesByCategory[p.CookieCategoryID] = append(
|
||||||
cookiesByCategory[p.CookieCategoryID],
|
cookiesByCategory[p.CookieCategoryID],
|
||||||
coredata.CookieItem{
|
coredata.CookieItem{
|
||||||
Name: p.DisplayName,
|
Name: p.DisplayName,
|
||||||
|
TrackerType: p.TrackerType,
|
||||||
MaxAgeSeconds: p.MaxAgeSeconds,
|
MaxAgeSeconds: p.MaxAgeSeconds,
|
||||||
Description: p.Description,
|
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)
|
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
|
return snapshot, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ import (
|
|||||||
|
|
||||||
type (
|
type (
|
||||||
CookieItem struct {
|
CookieItem struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
MaxAgeSeconds *int `json:"max_age_seconds"`
|
TrackerType TrackerType `json:"tracker_type"`
|
||||||
Description string `json:"description"`
|
MaxAgeSeconds *int `json:"max_age_seconds"`
|
||||||
|
Description string `json:"description"`
|
||||||
}
|
}
|
||||||
|
|
||||||
CookieItems []CookieItem
|
CookieItems []CookieItem
|
||||||
@@ -80,13 +81,22 @@ var cookieDurationUnits = [...]cookieDurationUnit{
|
|||||||
{1, "second", 0},
|
{1, "second", 0},
|
||||||
}
|
}
|
||||||
|
|
||||||
// HumanizedDuration renders the cookie's max-age into a human-readable lifetime
|
// HumanizedDuration renders the tracker's max-age into a human-readable
|
||||||
// using the same snapping and composition rules as the banner's
|
// lifetime using the same snapping and composition rules as the banner's
|
||||||
// humanizeDuration helper. A nil or non-positive max-age denotes a session
|
// humanizeDuration helper. A nil or non-positive max-age has no fixed
|
||||||
// cookie that is cleared when the browser closes.
|
// 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 {
|
func (c CookieItem) HumanizedDuration() string {
|
||||||
if c.MaxAgeSeconds == nil || *c.MaxAgeSeconds <= 0 {
|
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
|
remaining := *c.MaxAgeSeconds
|
||||||
|
|||||||
@@ -63,6 +63,26 @@ func (v TrackerType) String() string {
|
|||||||
return string(v)
|
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) {
|
func (v TrackerType) MarshalText() ([]byte, error) {
|
||||||
return []byte(v.String()), nil
|
return []byte(v.String()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -489,6 +489,7 @@ type (
|
|||||||
|
|
||||||
TrackerPolicyTracker struct {
|
TrackerPolicyTracker struct {
|
||||||
Name string
|
Name string
|
||||||
|
Type string
|
||||||
Purpose string
|
Purpose string
|
||||||
Duration string
|
Duration string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ The tables below describe the categories of trackers we use on the Website, the
|
|||||||
{{ with .Description }}{{ . }}
|
{{ with .Description }}{{ . }}
|
||||||
{{ else }}Trackers in this category support the functionality described by its name.
|
{{ else }}Trackers in this category support the functionality described by its name.
|
||||||
{{ end }}{{ if .Trackers }}
|
{{ end }}{{ if .Trackers }}
|
||||||
| Tracker | Purpose | Duration |
|
| Tracker | Type | Purpose | Duration |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
{{ range .Trackers }}| {{ .Name }} | {{ .Purpose }} | {{ .Duration }} |
|
{{ range .Trackers }}| {{ .Name }} | {{ .Type }} | {{ .Purpose }} | {{ .Duration }} |
|
||||||
{{ end }}{{ else }}
|
{{ end }}{{ else }}
|
||||||
We are not currently using any trackers in this category.
|
We are not currently using any trackers in this category.
|
||||||
{{ end }}{{ end }}
|
{{ end }}{{ end }}
|
||||||
|
|||||||
@@ -191,6 +191,7 @@ func (s *GeneratedDocumentService) buildTrackerPolicyDocumentData(
|
|||||||
for _, cookie := range c.Cookies {
|
for _, cookie := range c.Cookies {
|
||||||
trackers = append(trackers, docgen.TrackerPolicyTracker{
|
trackers = append(trackers, docgen.TrackerPolicyTracker{
|
||||||
Name: sanitizeTrackerCell(cookie.Name),
|
Name: sanitizeTrackerCell(cookie.Name),
|
||||||
|
Type: cookie.TrackerType.Label(),
|
||||||
Purpose: trackerPurpose(cookie.Description),
|
Purpose: trackerPurpose(cookie.Description),
|
||||||
Duration: cookie.HumanizedDuration(),
|
Duration: cookie.HumanizedDuration(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -416,6 +416,7 @@ func (r *cookieBannerVersionResolver) Categories(ctx context.Context, obj *types
|
|||||||
for j, c := range cat.Cookies {
|
for j, c := range cat.Cookies {
|
||||||
cookies[j] = &types.CookieBannerVersionCookie{
|
cookies[j] = &types.CookieBannerVersionCookie{
|
||||||
Name: c.Name,
|
Name: c.Name,
|
||||||
|
TrackerType: c.TrackerType,
|
||||||
MaxAgeSeconds: c.MaxAgeSeconds,
|
MaxAgeSeconds: c.MaxAgeSeconds,
|
||||||
Description: c.Description,
|
Description: c.Description,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -543,6 +543,7 @@ type CookieBannerVersionCategory {
|
|||||||
|
|
||||||
type CookieBannerVersionCookie {
|
type CookieBannerVersionCookie {
|
||||||
name: String!
|
name: String!
|
||||||
|
trackerType: TrackerType!
|
||||||
maxAgeSeconds: Int
|
maxAgeSeconds: Int
|
||||||
description: String!
|
description: String!
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user