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"` MatchType string `json:"matchType"`
TrackerType string `json:"trackerType"` TrackerType string `json:"trackerType"`
DisplayName string `json:"displayName"` DisplayName string `json:"displayName"`
Source string `json:"source"` Source *string `json:"source"`
Excluded bool `json:"excluded"` Excluded bool `json:"excluded"`
LastMatchedAt *string `json:"lastMatchedAt"` LastMatchedAt *string `json:"lastMatchedAt"`
} }
@@ -143,11 +143,15 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command {
if p.Excluded { if p.Excluded {
excluded = "yes" excluded = "yes"
} }
source := ""
if p.Source != nil {
source = *p.Source
}
lastMatched := "" lastMatched := ""
if p.LastMatchedAt != nil { if p.LastMatchedAt != nil {
lastMatched = cmdutil.FormatTime(*p.LastMatchedAt) 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...) 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 := validator.New()
v.Check(r.CookieCategoryID, "cookie_category_id", validator.Required(), validator.GID(coredata.CookieCategoryEntityType)) 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(r.Pattern, "pattern", validator.Required(), validator.SafeTextNoNewLine(255))
v.Check(string(r.MatchType), "match_type", validator.Required(), validator.OneOfSlice( v.Check(string(r.MatchType), "match_type", validator.Required(), validator.OneOfSlice(
func() []string { func() []string {
@@ -338,6 +348,20 @@ func (r *CreateTrackerPatternRequest) Validate() error {
return v.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 { func CanonicalizeOrigin(raw string) string {
u, err := url.Parse(raw) u, err := url.Parse(raw)
if err != nil { if err != nil {
@@ -2004,6 +2028,7 @@ func (s *Service) reportDetectedTracker(
Description: "", Description: "",
MaxAgeSeconds: info.MaxAgeSeconds, MaxAgeSeconds: info.MaxAgeSeconds,
Source: info.Source, Source: info.Source,
LastMatchedAt: &now,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
} }
@@ -2184,6 +2209,10 @@ func (s *Service) UpdateTrackerPattern(
scope coredata.Scoper, scope coredata.Scoper,
req UpdateTrackerPatternRequest, req UpdateTrackerPatternRequest,
) (*coredata.TrackerPattern, error) { ) (*coredata.TrackerPattern, error) {
if err := req.Validate(); err != nil {
return nil, err
}
var pattern coredata.TrackerPattern var pattern coredata.TrackerPattern
err := s.pg.WithTx( err := s.pg.WithTx(

View File

@@ -37,7 +37,7 @@ func (p TrackerPatternOrderField) Column() string {
case TrackerPatternOrderFieldUpdatedAt: case TrackerPatternOrderFieldUpdatedAt:
return "updated_at" return "updated_at"
case TrackerPatternOrderFieldSource: case TrackerPatternOrderFieldSource:
return "source" return "COALESCE(source, '')"
} }
panic(fmt.Sprintf("unsupported order by: %s", p)) 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) { func (r *trackerPatternConnectionResolver) TotalCount(ctx context.Context, obj *types.TrackerPatternConnection) (int, error) {
scope := coredata.NewScopeFromObjectID(obj.ParentID) scope := coredata.NewScopeFromObjectID(obj.ParentID)
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) filter := coredata.NewTrackerPatternFilter(nil, nil, nil)
if obj.Filter != nil { if obj.Filter != nil {
filter = filter.WithQuery(obj.Filter.Query).WithSource(obj.Filter.Source) 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 { 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) return 0, gqlutils.Internal(ctx)
} }

View File

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