feat(cloud): add LinkedIn Ads provider - #11769
Conversation
Adds `linkedin` to AdPlatform across schemas, DB typing, credit markup, the provider registry, and app-promotion validation, plus a real LinkedIn Marketing API provider (versioned REST gateway): - adAccounts search finder for account discovery/validation - campaign group + paused campaign creation with objective, budget, geo-targeting (urn:li:geo pass-through, worldwide default, loud failure on free-text locations), and #11621 bid-control mapping to costType/optimizationTargetType per the documented combinations - Rest.li PARTIAL_UPDATE for update/pause/activate and PENDING_DELETION deletes - Images/Videos API media upload (initializeUpload -> PUT -> finalizeUpload) owned by the account's organization reference - inline dark-post creative creation (creatives?action=createInline) - adAnalytics analytics-finder metrics mapping - OAuth2 refresh_token grant support Unit tests use fixtures lifted from the Microsoft Learn LinkedIn Marketing API reference pages and drive advertisingService with the real provider to prove the #11619 approval gate, #11621 bid metadata, and the fail-closed refund path apply to LinkedIn automatically. A credential-gated linkedin.real.test.ts live lane loud-skips without LINKEDIN_ADS_ACCESS_TOKEN. Closes #11663. Refs #11361. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Your trial has ended. Reactivate Greptile to resume code reviews.
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Reviewed and validated locally. What I checked:
Validation in
Residual: no live LinkedIn account/campaign/creative/media artifact was captured in this environment because approved LinkedIn Ads API credentials are not provisioned. That matches the issue's post-merge live-lane acceptance note. |
|
Claude encountered an error —— View job I'll analyze this and get back to you. |
Closes #11663. Refs #11361.
What
Adds
linkedinas a cloud advertising platform, wired end-to-end into the existingAdProvidercontract — same shape as the sibling X/Twitter (#11662) and Reddit (#11661) lanes:AdPlatform(DB typing +organization_urnmetadata),AdPlatformSchema, spend markup (1.1), provider registry, and the app-promotion route platform enum.packages/cloud/shared/src/lib/services/advertising/providers/linkedin.tsagainst the versioned LinkedIn Marketing API (api.linkedin.com/rest,LinkedIn-Version+X-Restli-Protocol-Version: 2.0.0, OAuth2 bearer):adAccountssearch finder (Rest.li structured query, raw-query builder so parens/colons stay unencoded).x-restli-idextraction), objective mapping, daily/total budget mapping (lifetime requires an end date — validated fail-fast), geo targeting (urn:li:geoURNs pass through, numeric ids wrapped, free-text fails loudly before any platform call, Worldwide default).costType/optimizationTargetTypeper the documented allowable-combinations table (cpm→CPM,cpc→CPC,cpa→CPM+MAX_CONVERSION; reach/clicks/conversions→MAX_IMPRESSION/MAX_CLICK/MAX_CONVERSION; objective-default auto-bid otherwise; never a manualunitCost).PARTIAL_UPDATEpatches (budget patch targets whichever of dailyBudget/totalBudget the live campaign uses); delete viaPENDING_DELETIONfor campaign then group.initializeUpload→ byte PUT →finalizeUploadfor multipart video), owner resolved from the ad account's organizationreference;getMediaStatusmapsAVAILABLE→ready.creatives?action=createInline) with CTA label + landing-page mapping; requires a prior provider asset (fail-closed otherwise).adAnalyticsanalytics finder (CAMPAIGN pivot, explicitfields, costInLocalCurrency/externalWebsiteConversions/oneClickLeads mapping).refresh_tokengrant (LINKEDIN_ADS_CLIENT_ID/LINKEDIN_ADS_CLIENT_SECRET).Tests
linkedin.test.ts— 16 pass / 0 fail (78 asserts), fixtures lifted from the Microsoft Learn LinkedIn Marketing API sample responses (account search, ad account fetch, image GET, adAnalytics rows,urn:li:sponsoredCreative:120491345), not invented. Includes a service-integration block that drivesadvertisingService.createCampaignwith the real registered provider (mockedfetch):pendingLinkedIn account is rejected before content safety, credits, or any LinkedIn call.bid_strategy/optimization_goalpersist to campaign metadata and the provider payload carriescostType: CPC+optimizationTargetType: MAX_CLICK.0.5 + 50×1.1) exactly once.linkedin.real.test.ts— credential-gated live lane; loud-skips ([LinkedInAdsRealTest] SKIPPED: set LINKEDIN_ADS_ACCESS_TOKEN ...) without credentials.Verification (local = merge gate)
bun test .../providers/linkedin.test.ts— 16 pass / 0 fail (re-run green after rebase onto latest develop)bun run --cwd packages/cloud/shared typecheck✅ ·bun run --cwd packages/cloud/api typecheck✅ · biome clean on touched files.github/issue-evidence/11663-linkedin-ads-provider.md(docs reviewed, fixture provenance, N/A items with reasons)Live-credential evidence is deferred until operator LinkedIn Ads API credentials are provisioned (the real lane is in place), matching the issue's post-merge live-lane acceptance criterion.
🤖 Generated with Claude Code