Add explicit third-party import from catalog
With the tracker-mapping worker no longer materializing org third parties, add the deliberate action that does: ThirdPartyService.Import FromCommon seeds an org ThirdParty from a CommonThirdParty catalog entry or returns the one the organization already imported, making it idempotent on the (organization_id, common_third_party_id) pair. On both the create and reuse paths it backfills tracker_patterns.third_ party_id for the organization's unlinked patterns whose catalog row resolves to the same common third party, via the new TrackerPatterns.Link ThirdPartyByCommonThirdPartyID. Patterns that previously surfaced the catalog entry then surface the managed org vendor in the trackers UI and the tracker-policy document. Only unlinked patterns are touched, so the backfill is idempotent and picks up newly detected patterns on re-import. End-to-end coverage (idempotency and pattern backfill) lands with the GraphQL mutation in a following commit. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -16,6 +16,7 @@ package probo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@@ -88,8 +89,22 @@ type (
|
||||
BusinessImpact coredata.BusinessImpact
|
||||
Notes *string
|
||||
}
|
||||
|
||||
ImportThirdPartyFromCommonRequest struct {
|
||||
OrganizationID gid.GID
|
||||
CommonThirdPartyID gid.GID
|
||||
}
|
||||
)
|
||||
|
||||
func (r *ImportThirdPartyFromCommonRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
v.Check(r.OrganizationID, "organization_id", validator.Required(), validator.GID(coredata.OrganizationEntityType))
|
||||
v.Check(r.CommonThirdPartyID, "common_third_party_id", validator.Required(), validator.GID(coredata.CommonThirdPartyEntityType))
|
||||
|
||||
return v.Error()
|
||||
}
|
||||
|
||||
func (cvr *CreateThirdPartyRequest) Validate() error {
|
||||
v := validator.New()
|
||||
|
||||
@@ -669,6 +684,131 @@ func (s ThirdPartyService) Create(
|
||||
return thirdParty, nil
|
||||
}
|
||||
|
||||
// ImportFromCommon creates an org ThirdParty seeded from the global
|
||||
// CommonThirdParty catalog entry, or returns the existing one when the
|
||||
// organization already imported it (idempotent on the
|
||||
// (organization_id, common_third_party_id) pair). It then backfills
|
||||
// tracker_patterns.third_party_id for the organization's unlinked
|
||||
// patterns whose catalog row resolves to the same common third party, so
|
||||
// the trackers UI and tracker-policy document surface the managed vendor
|
||||
// instead of the catalog entry. The backfill runs on both the create and
|
||||
// reuse paths because new patterns may have been detected since a prior
|
||||
// import; it only touches patterns with no existing link. Returns the
|
||||
// org ThirdParty and whether it was newly created.
|
||||
func (s ThirdPartyService) ImportFromCommon(
|
||||
ctx context.Context, scope coredata.Scoper,
|
||||
req ImportThirdPartyFromCommonRequest,
|
||||
) (*coredata.ThirdParty, bool, error) {
|
||||
if err := req.Validate(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
thirdParty := &coredata.ThirdParty{}
|
||||
created := false
|
||||
|
||||
err := s.svc.pg.WithTx(
|
||||
ctx,
|
||||
func(ctx context.Context, conn pg.Tx) error {
|
||||
organization := &coredata.Organization{}
|
||||
if err := organization.LoadByID(ctx, conn, scope, req.OrganizationID); err != nil {
|
||||
return fmt.Errorf("cannot load organization: %w", err)
|
||||
}
|
||||
|
||||
existing := &coredata.ThirdParty{}
|
||||
|
||||
err := existing.LoadByOrganizationIDAndCommonThirdPartyID(
|
||||
ctx,
|
||||
conn,
|
||||
scope,
|
||||
organization.ID,
|
||||
req.CommonThirdPartyID,
|
||||
)
|
||||
|
||||
switch {
|
||||
case err == nil:
|
||||
*thirdParty = *existing
|
||||
case errors.Is(err, coredata.ErrResourceNotFound):
|
||||
commonParty := &coredata.CommonThirdParty{}
|
||||
if err := commonParty.LoadByID(ctx, conn, req.CommonThirdPartyID); err != nil {
|
||||
return fmt.Errorf("cannot load common third party: %w", err)
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
commonID := commonParty.ID
|
||||
|
||||
certifications := commonParty.Certifications
|
||||
if certifications == nil {
|
||||
certifications = []string{}
|
||||
}
|
||||
|
||||
*thirdParty = coredata.ThirdParty{
|
||||
ID: gid.New(scope.GetTenantID(), coredata.ThirdPartyEntityType),
|
||||
OrganizationID: organization.ID,
|
||||
CommonThirdPartyID: &commonID,
|
||||
Name: commonParty.Name,
|
||||
Category: commonParty.Category,
|
||||
HeadquarterAddress: commonParty.HeadquarterAddress,
|
||||
LegalName: commonParty.LegalName,
|
||||
WebsiteURL: commonParty.WebsiteURL,
|
||||
PrivacyPolicyURL: commonParty.PrivacyPolicyURL,
|
||||
ServiceLevelAgreementURL: commonParty.ServiceLevelAgreementURL,
|
||||
DataProcessingAgreementURL: commonParty.DataProcessingAgreementURL,
|
||||
BusinessAssociateAgreementURL: commonParty.BusinessAssociateAgreementURL,
|
||||
SubprocessorsListURL: commonParty.SubprocessorsListURL,
|
||||
Certifications: certifications,
|
||||
Countries: coredata.CountryCodes{},
|
||||
StatusPageURL: commonParty.StatusPageURL,
|
||||
TermsOfServiceURL: commonParty.TermsOfServiceURL,
|
||||
SecurityPageURL: commonParty.SecurityPageURL,
|
||||
TrustPageURL: commonParty.TrustPageURL,
|
||||
ShowOnTrustCenter: false,
|
||||
FirstLevel: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
|
||||
if err := thirdParty.Insert(ctx, conn, scope); err != nil {
|
||||
return fmt.Errorf("cannot insert third party: %w", err)
|
||||
}
|
||||
|
||||
created = true
|
||||
|
||||
if err := webhook.InsertData(
|
||||
ctx,
|
||||
conn,
|
||||
scope,
|
||||
organization.ID,
|
||||
coredata.WebhookEventTypeThirdPartyCreated,
|
||||
webhooktypes.NewThirdParty(thirdParty),
|
||||
); err != nil {
|
||||
return fmt.Errorf("cannot insert webhook event: %w", err)
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("cannot load third party by common id: %w", err)
|
||||
}
|
||||
|
||||
var patterns coredata.TrackerPatterns
|
||||
if err := patterns.LinkThirdPartyByCommonThirdPartyID(
|
||||
ctx,
|
||||
conn,
|
||||
scope,
|
||||
organization.ID,
|
||||
req.CommonThirdPartyID,
|
||||
thirdParty.ID,
|
||||
); err != nil {
|
||||
return fmt.Errorf("cannot link tracker patterns to imported third party: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return thirdParty, created, nil
|
||||
}
|
||||
|
||||
func (s ThirdPartyService) CountForAssetID(
|
||||
ctx context.Context, scope coredata.Scoper,
|
||||
assetID gid.GID,
|
||||
|
||||
Reference in New Issue
Block a user