Fix tracker pattern review issues

- Fix TotalCount resolver to dispatch by parent type instead
  of always using the uncategorised banner counter
- Sync MCP tracker_type enum with canonical TrackerType values
- Add validation for UpdateTrackerPatternRequest
- Validate tracker_type on CreateTrackerPatternRequest
- Set LastMatchedAt when creating pattern from detection
- Use COALESCE for SOURCE cursor pagination with NULLs
- Make source nullable in CLI tracker-pattern list

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-05-06 11:06:22 +04:00
parent 5302a9da7e
commit 9de6af936d
5 changed files with 51 additions and 10 deletions

View File

@@ -58,7 +58,7 @@ type trackerPattern struct {
MatchType string `json:"matchType"`
TrackerType string `json:"trackerType"`
DisplayName string `json:"displayName"`
Source string `json:"source"`
Source *string `json:"source"`
Excluded bool `json:"excluded"`
LastMatchedAt *string `json:"lastMatchedAt"`
}
@@ -143,11 +143,15 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command {
if p.Excluded {
excluded = "yes"
}
source := ""
if p.Source != nil {
source = *p.Source
}
lastMatched := ""
if p.LastMatchedAt != nil {
lastMatched = cmdutil.FormatTime(*p.LastMatchedAt)
}
rows = append(rows, []string{p.ID, p.Pattern, p.MatchType, p.TrackerType, p.DisplayName, p.Source, excluded, lastMatched})
rows = append(rows, []string{p.ID, p.Pattern, p.MatchType, p.TrackerType, p.DisplayName, source, excluded, lastMatched})
}
t := cmdutil.NewTable("ID", "PATTERN", "MATCH TYPE", "TRACKER TYPE", "DISPLAY NAME", "SOURCE", "EXCLUDED", "LAST MATCHED").Rows(rows...)

View File

@@ -321,6 +321,16 @@ func (r *CreateTrackerPatternRequest) Validate() error {
v := validator.New()
v.Check(r.CookieCategoryID, "cookie_category_id", validator.Required(), validator.GID(coredata.CookieCategoryEntityType))
v.Check(string(r.TrackerType), "tracker_type", validator.Required(), validator.OneOfSlice(
func() []string {
types := coredata.TrackerTypes()
s := make([]string, len(types))
for i, t := range types {
s[i] = string(t)
}
return s
}(),
))
v.Check(r.Pattern, "pattern", validator.Required(), validator.SafeTextNoNewLine(255))
v.Check(string(r.MatchType), "match_type", validator.Required(), validator.OneOfSlice(
func() []string {
@@ -338,6 +348,20 @@ func (r *CreateTrackerPatternRequest) Validate() error {
return v.Error()
}
func (r *UpdateTrackerPatternRequest) Validate() error {
v := validator.New()
v.Check(r.TrackerPatternID, "tracker_pattern_id", validator.Required(), validator.GID(coredata.TrackerPatternEntityType))
if r.DisplayName != nil {
v.Check(*r.DisplayName, "display_name", validator.Required(), validator.SafeTextNoNewLine(255))
}
if r.Description != nil {
v.Check(*r.Description, "description", validator.SafeText(1000))
}
return v.Error()
}
func CanonicalizeOrigin(raw string) string {
u, err := url.Parse(raw)
if err != nil {
@@ -2004,6 +2028,7 @@ func (s *Service) reportDetectedTracker(
Description: "",
MaxAgeSeconds: info.MaxAgeSeconds,
Source: info.Source,
LastMatchedAt: &now,
CreatedAt: now,
UpdatedAt: now,
}
@@ -2184,6 +2209,10 @@ func (s *Service) UpdateTrackerPattern(
scope coredata.Scoper,
req UpdateTrackerPatternRequest,
) (*coredata.TrackerPattern, error) {
if err := req.Validate(); err != nil {
return nil, err
}
var pattern coredata.TrackerPattern
err := s.pg.WithTx(

View File

@@ -37,7 +37,7 @@ func (p TrackerPatternOrderField) Column() string {
case TrackerPatternOrderFieldUpdatedAt:
return "updated_at"
case TrackerPatternOrderFieldSource:
return "source"
return "COALESCE(source, '')"
}
panic(fmt.Sprintf("unsupported order by: %s", p))
}

View File

@@ -972,14 +972,22 @@ func (r *trackerPatternResolver) Permission(ctx context.Context, obj *types.Trac
func (r *trackerPatternConnectionResolver) TotalCount(ctx context.Context, obj *types.TrackerPatternConnection) (int, error) {
scope := coredata.NewScopeFromObjectID(obj.ParentID)
filter := coredata.NewTrackerPatternFilter(nil, nil, nil)
if obj.Filter != nil {
filter = filter.WithQuery(obj.Filter.Query).WithSource(obj.Filter.Source)
var count int
var err error
switch obj.Resolver.(type) {
case *cookieCategoryResolver:
count, err = r.cookieBanner.CountTrackerPatternsForCategory(ctx, scope, obj.ParentID)
default:
filter := coredata.NewTrackerPatternFilter(nil, nil, nil)
if obj.Filter != nil {
filter = filter.WithQuery(obj.Filter.Query).WithSource(obj.Filter.Source)
}
count, err = r.cookieBanner.CountUncategorisedTrackerPatterns(ctx, scope, obj.ParentID, filter)
}
count, err := r.cookieBanner.CountUncategorisedTrackerPatterns(ctx, scope, obj.ParentID, filter)
if err != nil {
r.logger.ErrorCtx(ctx, "cannot count uncategorised tracker patterns", log.Error(err))
r.logger.ErrorCtx(ctx, "cannot count tracker patterns", log.Error(err))
return 0, gqlutils.Internal(ctx)
}

View File

@@ -9163,7 +9163,7 @@ components:
description: Cookie category ID
tracker_type:
type: string
enum: [COOKIE, LOCAL_STORAGE, SESSION_STORAGE, PIXEL]
enum: [COOKIE, LOCAL_STORAGE, SESSION_STORAGE, INDEXED_DB, SCRIPT, IFRAME]
description: Type of tracker
pattern:
type: string
@@ -9689,7 +9689,7 @@ components:
$ref: "#/components/schemas/GID"
tracker_type:
type: string
enum: [COOKIE, LOCAL_STORAGE, SESSION_STORAGE, PIXEL]
enum: [COOKIE, LOCAL_STORAGE, SESSION_STORAGE, INDEXED_DB, SCRIPT, IFRAME]
pattern:
type: string
match_type: