Add trackConversion for sending conversion events#96
Conversation
Exposes a new public method on Parsely for firing conversion events (newsletter signups, subscriptions, purchases, link clicks, lead capture, and arbitrary custom conversions) using the existing event queue, flush, and mobileproxy pipeline. - `ConversionType` enum mirrors the categories accepted by the Parse.ly conversions backend (newsletter_signup, lead_capture, link_click, subscription, purchase, custom). - `trackConversion(url:conversionType:conversionLabel:...)` merges `_conversion_type` and `_conversion_label` into the event's `extra_data` alongside caller-supplied keys. Reserved keys can't be overridden by caller `extraData`. - The event is dispatched through `Track.conversion(...)` mirroring the existing `pageview(...)` shape — no changes to `Event`, the queue, the flush timer, or `RequestBuilder`. - New tests cover the queue path, the reserved-key guarantee, and the public Parsely.trackConversion entry point. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds two buttons on the First tab — "Track Pageview (sandbox)" and "Track Conversion (sandbox)" — that fire against the `sandbox.joshhanson.io` apikey on a shared test URL. The pageview lets a session accrue history before the conversion fires so the conversions topology has something to attribute to. Used during the manual end-to-end verification of trackConversion: with Proxyman attached, confirm action=conversion and the _conversion_type / _conversion_label keys land in the on-wire payload, then watch dash.parsely.com for the conversion in the conversions report. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds first-class conversion tracking to the iOS SDK so apps can emit conversion events (e.g., subscription/purchase/newsletter signup) via the existing event pipeline.
Changes:
- Introduces
Parsely.trackConversion(...)and a publicConversionTypeenum for supported conversion categories. - Implements conversion event creation in
Track.conversion(...)by emittingaction = "conversion"and merging reserved conversion keys intoextra_data. - Adds unit tests and Demo app UI actions to exercise sandbox pageview + conversion flows; updates changelog.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| Tests/TrackTests.swift | Adds unit tests validating conversion event shape and reserved-key overwrite behavior. |
| Tests/ParselyTrackerTests.swift | Adds a public-API test ensuring Parsely.trackConversion enqueues a conversion event. |
| Sources/Track.swift | Adds Track.conversion(...) to construct and enqueue conversion events with reserved keys in extra_data. |
| Sources/ParselyTracker.swift | Adds ConversionType enum and trackConversion(...) public API wired into the event processor. |
| Demo/ParselyDemo/FirstViewController.swift | Adds sandbox pageview + conversion IBAction handlers for manual verification. |
| Demo/ParselyDemo/Base.lproj/Main.storyboard | Adds “Sandbox” label and buttons wired to new demo actions. |
| CHANGES.rst | Documents the new trackConversion API and ConversionType. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| private func _trackConversion( | ||
| url: String, | ||
| conversionType: ConversionType, | ||
| conversionLabel: String, | ||
| urlref: String, | ||
| metadata: ParselyMetadata?, | ||
| extraData: Dictionary<String, Any>?, | ||
| siteId: String | ||
| ) { | ||
| var _siteId = siteId | ||
| if _siteId == "" { | ||
| _siteId = self.apikey | ||
| } | ||
| os_log("Tracking Conversion", log: OSLog.tracker, type: .debug) | ||
| track.conversion( |
There was a problem hiding this comment.
@randyriback I think this is a legit concern. However, I'm not sure logging an error and failing silently would be a good experience for the users.
I'd love to require a NonEmpty<String> but that might be too overbearing on the users, too.
Maybe we can accept the ambiguity for the time being and follow up at some point with a better SDK design. One that throws errors or that has DTOs with validation in the init as input parameters for the tracking calls.
What do you think?
There was a problem hiding this comment.
Thanks, Gio! Implemented Copilot's suggestion. Added a guard in _trackConversion-- if conversionLabel is empty, the SDK logs an .error via os_log and returns before enqueuing — same shape as the existing empty-idsite guard in Track.swift. Updated the doc on the public method to reflect this, and added testTrackConversionWithEmptyLabelDoesNotEnqueue
Skipped the NonEmpty route per your concern about caller ergonomics. Happy to tackle the broader DTO-with-validation refactor as a follow-up if/when we want to do it across all tracking methods.
mokagio
left a comment
There was a problem hiding this comment.
Looking good. The Copilot concern on validation and backend dropping events is legit, but I don't know how to handle it neatly here without restructuring the code.
Approving to unblock.
Notice that CI did not run on this PR, but I opened #97 to update the setup and verified it runs fine:
Co-authored-by: Gio Lodi <giovanni.lodi42@gmail.com>
Address review feedback (#PR): the doc promised the conversions backend drops events without a label, but the implementation would still enqueue and send them — a guaranteed no-op event from the customer's perspective. Now `_trackConversion` early-returns with a `.error`-level os_log when `conversionLabel.isEmpty`, so the SDK matches its own documentation and gives the developer a loud signal during integration instead of silently shipping events that will never appear in reporting. - Public doc updated to reflect the SDK-side skip. - New test `testTrackConversionWithEmptyLabelDoesNotEnqueue` verifies an empty label does not produce a queued event. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
@randyriback I'm going to close this PR because I merged all the commits in it via #97.
|


Summary
Parsely.trackConversion(url:conversionType:conversionLabel:...)for firing conversion events (newsletter signups, subscriptions, purchases, link clicks, lead capture, custom). Internally constructs an event withaction="conversion"and merges_conversion_typeand_conversion_labelintoextra_data, which the Parse.ly conversions backend (conversions-realtime) extracts and uses as the goal identity.ConversionTypeenum mirrors the categories accepted server-side (newsletter_signup,lead_capture,link_click,subscription,purchase,custom).mobileproxyPOST — zero changes to the network layer,Event,RequestBuilder, orHttpClient.Test plan
Unit tests (87 passing, 3 new)
TrackTests.testConversion— verifiesTrack.conversion(...)enqueues an event withaction == "conversion", that_conversion_typeand_conversion_labelare merged intoextra_data, and that caller-suppliedextra_datakeys are preserved.TrackTests.testConversionReservedKeysOverwriteCallerExtraData— verifies the reserved keys_conversion_typeand_conversion_labelcannot be overridden by caller-suppliedextraData.ParselyTrackerTests.testTrackConversion— verifies the publicParsely.trackConversion(...)entry point queues an event of the right shape via the standard event processor.Full suite (
make test): Executed 87 tests, with 0 failures (0 unexpected) on iPhone 16 / iOS 18.6.Manual end-to-end
iPhone 16/ iOS 18.6 andiPhone 17 Pro/ iOS 26.4).sandbox.joshhanson.ioapikey.os_logevent dump — confirmedaction: "conversion",data._conversion_type: "subscription",data._conversion_label: "...", caller-supplied keys (plan,source) preserved,parsely_site_uuidattached.Sending request to https://p1.parsely.com/mobileproxylogged after the 30s flush (also forced via Home/background lifecycle).dash.parsely.comfor thesandbox.joshhanson.ioapikey across multiple test runs and labels.Testing screenshots