Surface third party links on TrackerPattern in GraphQL
Each tracker pattern carries either a direct org-scoped third_party_id or an indirect link via common_tracker_pattern_id, but the console API never surfaced either. Expose two optional resolver-driven fields on the GraphQL TrackerPattern node: thirdParty: ThirdParty commonThirdParty: CommonThirdParty The org-scoped ThirdParty takes priority. When ThirdPartyID is set the commonThirdParty resolver short-circuits to nil, so the chained common_tracker_pattern -> common_third_party lookup is only paid for when a pattern has not been promoted to a tenant-managed third party. To make the resolver pattern viable across paginated banner trackers listings, the model now uses @goModel and a custom struct that carries the foreign-key handles (ThirdPartyID, CommonTrackerPatternID) without exposing them in the schema. NewTrackerPatternNode populates them from coredata. Two new request-scoped dataloaders (CommonTrackerPattern, CommonThirdParty) batch the chained lookup, mirroring the existing ThirdParty / CookieCategory loaders. The console mux now wires the third-party service through dataloader.NewMiddleware so the second loader has its backing service. Authorization follows existing precedent: ActionThirdPartyGet for the org-scoped lookup, ActionCommonThirdPartyGet (granted by the identity-scoped CommonThirdPartyCatalogPolicy) for the catalog lookup. ErrResourceNotFound and dataloadgen.ErrNotFound are mapped to a null field rather than an error. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -16,6 +16,7 @@ import (
|
||||
"go.probo.inc/probo/pkg/coredata"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
"go.probo.inc/probo/pkg/probo"
|
||||
"go.probo.inc/probo/pkg/server/api/authn"
|
||||
"go.probo.inc/probo/pkg/server/api/console/v1/dataloader"
|
||||
"go.probo.inc/probo/pkg/server/api/console/v1/schema"
|
||||
"go.probo.inc/probo/pkg/server/api/console/v1/types"
|
||||
@@ -1273,6 +1274,78 @@ func (r *trackerPatternResolver) DetectedCount(ctx context.Context, obj *types.T
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// ThirdParty is the resolver for the thirdParty field.
|
||||
func (r *trackerPatternResolver) ThirdParty(ctx context.Context, obj *types.TrackerPattern) (*types.ThirdParty, error) {
|
||||
if obj.ThirdPartyID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if _, err := r.authorize(ctx, *obj.ThirdPartyID, probo.ActionThirdPartyGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loaders := dataloader.FromContext(ctx)
|
||||
|
||||
tp, err := loaders.ThirdParty.Load(ctx, *obj.ThirdPartyID)
|
||||
if err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get tracker pattern third party", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewThirdParty(tp), nil
|
||||
}
|
||||
|
||||
// CommonThirdParty is the resolver for the commonThirdParty field.
|
||||
//
|
||||
// The org-scoped thirdParty takes priority: when ThirdPartyID is set we
|
||||
// short-circuit to nil so the chained common-tracker-pattern lookup is
|
||||
// never paid for.
|
||||
func (r *trackerPatternResolver) CommonThirdParty(ctx context.Context, obj *types.TrackerPattern) (*types.CommonThirdParty, error) {
|
||||
if obj.ThirdPartyID != nil || obj.CommonTrackerPatternID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
identity := authn.IdentityFromContext(ctx)
|
||||
if _, err := r.authorize(ctx, identity.ID, probo.ActionCommonThirdPartyGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loaders := dataloader.FromContext(ctx)
|
||||
|
||||
pattern, err := loaders.CommonTrackerPattern.Load(ctx, *obj.CommonTrackerPatternID)
|
||||
if err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get common tracker pattern", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
if pattern.CommonThirdPartyID == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
party, err := loaders.CommonThirdParty.Load(ctx, *pattern.CommonThirdPartyID)
|
||||
if err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot get common third party", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewCommonThirdParty(party), nil
|
||||
}
|
||||
|
||||
// DetectedTrackers is the resolver for the detectedTrackers field.
|
||||
func (r *trackerPatternResolver) DetectedTrackers(ctx context.Context, obj *types.TrackerPattern, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DetectedTrackerOrderBy) (*types.DetectedTrackerConnection, error) {
|
||||
scope, err := r.authorize(ctx, obj.ID, probo.ActionTrackerPatternGet)
|
||||
|
||||
Reference in New Issue
Block a user