Skip to content

[CDX-708] Add the 9 Product Insights Agent tracking events - #290

Open
Mudaafi wants to merge 8 commits into
masterfrom
cdx-708-swift-sdk-product-insights-agent-add-the-9-pia-tracking
Open

Mudaafi wants to merge 8 commits into
masterfrom
cdx-708-swift-sdk-product-insights-agent-add-the-9-pia-tracking

Conversation

@Mudaafi

@Mudaafi Mudaafi commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the 9 Product Insights Agent (PIA) tracking events to the Swift SDK so native iOS apps can track PIA (CDX-708). Swift had no PIA tracking before this; Cencosud has more app traffic than web and can't measure PIA in-app. Android twin: CDX-709.

Changes

  • 9 public request structs + methods, CIOTrackProductInsightsAgent<Event>Request passed to trackProductInsightsAgent<Event>(_:completionHandler:): Focus, OutOfView, View, Views, QuestionClick, QuestionSubmit, AnswerFeedback, ResultClick, AnswerView. Method names match JS.
  • Public models: CIOProductInsightsAgentQuestion, CIOProductInsightsAgentViewTimespan, and the CIOProductInsightsAgentFeedbackLabel enum (.thumbsUp / .thumbsDown).
  • Internal CIOTrackProductInsightsAgentData protocol, which owns decorateRequest/httpMethod/httpBody. Each event only declares its URL and eventFields(). One private ConstructorIO helper resolves the section and analytics tags for all 9.
  • CIOItem.toImpressionDictionary() (internal), shared by trackProductInsightsAgentAnswerView and the existing trackResultsImpressionView, which already sent this exact shape. The wire output of trackResultsImpressionView is unchanged.
  • Docs: DocC on every public field and method, .jazzy.yaml globs and category for the new structs, and README §12 "Product Insights Agent Events".

Key things to note

  • Naming follows the ASA trackAgent* methods, sectionName/itemID/seedItemID/qnaResultID, not the newer section naming. This is intentional (decision C2 in the deviations doc).
  • section is a query param on all 9, resolved request.sectionName ?? config.defaultItemSectionName ?? "Products". It also ends up in the body via the baseParams merge, the same way as the siblings. The server accepts that (live probe: 204).
  • The spec wins over JS on three points: analyticsTags on all 9, question_topic on answer_view and inside questions[], and items/followUpQuestions capped at 100.
  • view_timespans are ISO-8601 UTC with ms (e.g. 2026-10-22T02:40:05.123Z). These are formatted with a fixed-format DateFormatter rather than ISO8601DateFormatter, because .withFractionalSeconds throws on iOS 11.0/11.1 (the SDK minimum is 11.0). The output is byte-identical to ISO8601DateFormatter, checked over 200k consecutive ms.
  • itemID is required on all 9. The spec marks it optional, but the server returns 400 when neither item_id nor item_name is sent.
  • answerText is limited to 2000 characters by the server, and longer bodies get a 400 that drops the whole event. That limit is documented; there is no client-side truncation. It may be worth a follow-up with the backend owners.
  • Out of scope: free-text question/answer_text sit in the body, so obfuscatePIIRequest (URL-only) doesn't see them. That's the same as every other body-based event, and JS doesn't do it either.

Testing

  • Per event: request-builder tests (presence and omission, plus an exact body key-set assertion, since the server rejects unknown keys with 400) and worker tests (default/config/request section precedence, analytics tag merge, 400/500/no connectivity).
  • Edge cases: position: 0 sent, answerText: "" sent, 100 caps on items/followUpQuestions, no quantity, timespan formatter pinned to UTC/en_US_POSIX/Gregorian, and round-trip to the original instant.
  • Live integration tests (test key): 2 per event, 18 total, all passing. One uses the default integration config; the other adds a userID and A/B testCells to confirm the merged ui/ef-* body keys are accepted.
  • The full suite passes: 787 tests, 0 failures (iPhone 15 / iOS 17.5 simulator). I also ran the timespan tests on iOS 18.1.
  • Local note: Xcode 27.1 can't build the 11.0 deployment target, so I passed IPHONEOS_DEPLOYMENT_TARGET=15.0 on the command line. No project settings changed.

Release

No version bump in this PR. It follows after merge, in its own "Version Bump" commit as usual.

🤖 Generated with Claude Code

Mudaafi and others added 8 commits October 22, 2026 06:04
Add trackProductInsightsAgent{Focus,OutOfView,View,Views,QuestionClick,
QuestionSubmit,AnswerFeedback,ResultClick,AnswerView} with a public request
struct per event, plus the CIOProductInsightsAgentQuestion,
CIOProductInsightsAgentViewTimespan and CIOProductInsightsAgentFeedbackLabel
models.

- section is sent as a query param, resolved from the request, the config
  default, then "Products"
- analyticsTags are merged with config.defaultAnalyticsTags
- view timespans are sent as ISO-8601 UTC date-times
- items and followUpQuestions are capped at 100; items never send quantity
- request-builder, worker and live integration tests for every event

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a Product Insights Agent Events subsection to README §12 and include the
new request and model structs in the jazzy docs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, live A/B tests

- Use distinct clicked-item and seed values in result click tests and docs
- Round view timespans to the nearest millisecond before formatting
- Document position as zero-based
- Add a live test per event with a userID and test cells set

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ISO8601DateFormatter already rounds to the nearest millisecond, so format
dates directly and cover more millisecond values in the test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Add an internal CIOTrackProductInsightsAgentData protocol whose
  addCommonFields(to:) writes item_name, variation_id, thread_id and
  analytics_tags for all 9 events
- Move the CIOItem body mapping to CIOItem.toDictionary() and reuse it in
  trackResultsImpressionView, which already sent the same shape

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… tests

- Move decorateRequest, httpMethod and httpBody into the
  CIOTrackProductInsightsAgentData protocol extension; each event now only
  provides eventFields()
- Add a test per event that the request section overrides the config default
- Rename CIOItem.toDictionary() to toImpressionDictionary() and document
  which events use that shape
- Document the 1-1000 character limit on question fields
- Define the timespan dates in the README example

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…et tests

- Format view timespans with a fixed-format DateFormatter, since
  ISO8601DateFormatter's .withFractionalSeconds throws on iOS 11.0/11.1
- Resolve section and analytics tags for all 9 events in one private helper
- Assert each event's body contains only keys accepted by the API
- Document the 2000 character limit on answerText

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…comment

- Test that the timespan formatter is UTC, en_US_POSIX and Gregorian, and
  that its output round-trips to the original instant
- Correct the CIOItem.toImpressionDictionary() comment about purchase items

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Mudaafi
Mudaafi requested a review from a team as a code owner October 22, 2026 17:03
Copilot AI balanced review requested due to automatic review settings October 22, 2026 17:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The round-trip test needs an availability guard to avoid crashing on supported iOS 11.0 and 11.1 runtimes.

1 open issue
What changed in this PR

Adds Product Insights Agent tracking to the Swift SDK so native iOS apps can measure interactions on product detail pages.

Changes:

  • Adds nine tracking methods, public request types, and supporting models.
  • Centralizes shared request serialization, section resolution, and analytics-tag merging while preserving existing impression payloads.
  • Adds request-builder, worker, and live integration tests, plus usage and API documentation.
File Description
README.md Documents all nine events with Swift examples.
AutocompleteClientTests/​FW/​Logic/​Worker/​ConstructorIOTrackProductInsightsAgentViewTests.swift Tests view payloads, configuration, and errors.
AutocompleteClientTests/​FW/​Logic/​Worker/​ConstructorIOTrackProductInsightsAgentViewsTests.swift Tests visibility-timespan tracking and configuration.
AutocompleteClientTests/​FW/​Logic/​Worker/​ConstructorIOTrackProductInsightsAgentResultClickTests.swift Tests recommendation clicks, including position zero.
AutocompleteClientTests/​FW/​Logic/​Worker/​ConstructorIOTrackProductInsightsAgentQuestionSubmitTests.swift Tests question submissions and error handling.
AutocompleteClientTests/​FW/​Logic/​Worker/​ConstructorIOTrackProductInsightsAgentQuestionClickTests.swift Tests question clicks and error handling.
AutocompleteClientTests/​FW/​Logic/​Worker/​ConstructorIOTrackProductInsightsAgentOutOfViewTests.swift Tests out-of-view tracking and configuration.
AutocompleteClientTests/​FW/​Logic/​Worker/​ConstructorIOTrackProductInsightsAgentFocusTests.swift Tests input-focus tracking and configuration.
AutocompleteClientTests/​FW/​Logic/​Worker/​ConstructorIOTrackProductInsightsAgentAnswerViewTests.swift Tests answer views, empty text, and collection caps.
AutocompleteClientTests/​FW/​Logic/​Worker/​ConstructorIOTrackProductInsightsAgentAnswerFeedbackTests.swift Tests answer feedback and configuration.
AutocompleteClientTests/​FW/​Logic/​Worker/​ConstructorIOIntegrationTests.swift Adds live coverage for all nine events.
AutocompleteClientTests/​FW/​Logic/​Request/​TrackProductInsightsAgentViewsRequestBuilderTests.swift Tests timespan formatting, round trips, and payload keys.
AutocompleteClientTests/​FW/​Logic/​Request/​TrackProductInsightsAgentViewRequestBuilderTests.swift Verifies view request serialization.
AutocompleteClientTests/​FW/​Logic/​Request/​TrackProductInsightsAgentResultClickRequestBuilderTests.swift Verifies click fields and position-zero serialization.
AutocompleteClientTests/​FW/​Logic/​Request/​TrackProductInsightsAgentQuestionSubmitRequestBuilderTests.swift Verifies question-submit payloads.
AutocompleteClientTests/​FW/​Logic/​Request/​TrackProductInsightsAgentQuestionClickRequestBuilderTests.swift Verifies question-click payloads.
AutocompleteClientTests/​FW/​Logic/​Request/​TrackProductInsightsAgentOutOfViewRequestBuilderTests.swift Verifies out-of-view payloads.
AutocompleteClientTests/​FW/​Logic/​Request/​TrackProductInsightsAgentFocusRequestBuilderTests.swift Verifies focus payloads.
AutocompleteClientTests/​FW/​Logic/​Request/​TrackProductInsightsAgentAnswerViewRequestBuilderTests.swift Verifies answer payloads and collection limits.
AutocompleteClientTests/​FW/​Logic/​Request/​TrackProductInsightsAgentAnswerFeedbackRequestBuilderTests.swift Verifies feedback payloads and both labels.
AutocompleteClient/​FW/​Logic/​Worker/​ConstructorIO.swift Exposes nine methods with shared configuration handling.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackResultsImpressionViewData.swift Reuses the shared impression-item serializer.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentViewsRequest.swift Defines the public visibility-timespan request.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentViewsData.swift Serializes questions and visibility timespans.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentViewRequest.swift Defines the public view request.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentViewData.swift Serializes view-event questions.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentResultClickRequest.swift Defines recommendation-click parameters.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentResultClickData.swift Serializes clicked and seed-product details.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentQuestionSubmitRequest.swift Defines question-submission parameters.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentQuestionSubmitData.swift Serializes submitted questions and topics.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentQuestionClickRequest.swift Defines question-click parameters.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentQuestionClickData.swift Serializes clicked questions and topics.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentOutOfViewRequest.swift Defines out-of-view parameters.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentOutOfViewData.swift Supplies the out-of-view endpoint.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentFocusRequest.swift Defines input-focus parameters.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentFocusData.swift Supplies the focus endpoint.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentData.swift Centralizes common POST fields and serialization.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentAnswerViewRequest.swift Defines answer-view parameters and documents limits.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentAnswerViewData.swift Serializes answers and caps related collections.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentAnswerFeedbackRequest.swift Defines answer-feedback parameters.
AutocompleteClient/​FW/​Logic/​Request/​CIOTrackProductInsightsAgentAnswerFeedbackData.swift Serializes feedback labels and answer identifiers.
AutocompleteClient/​FW/​Logic/​Request/​CIOProductInsightsAgentViewTimespan.swift Formats visibility intervals as UTC timestamps.
AutocompleteClient/​FW/​Logic/​Request/​CIOProductInsightsAgentQuestion.swift Models question text and optional topics.
AutocompleteClient/​FW/​Logic/​Request/​CIOProductInsightsAgentFeedbackLabel.swift Defines thumbs-up and thumbs-down values.
AutocompleteClient/​FW/​Logic/​Request/​CIOItem.swift Adds reusable impression-item serialization.
AutocompleteClient/​Constants/​Constants.swift Defines the nine tracking endpoints.
AutocompleteClient.xcodeproj/​project.pbxproj Registers new SDK and test sources.
.jazzy.yaml Includes and groups the new public API documentation.

Review effort: Balanced

Comment on lines +121 to +122
let parser = ISO8601DateFormatter()
parser.formatOptions = [.withInternetDateTime, .withFractionalSeconds]

@constructor-claude-bedrock constructor-claude-bedrock Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This PR adds 9 Product Insights Agent (PIA) tracking events to the Swift SDK following well-established patterns in the codebase. The implementation is thorough, well-documented, and comprehensively tested. The architecture is clean — a shared CIOTrackProductInsightsAgentData protocol eliminates duplication across all 9 events effectively.

Inline comments: 7 discussions added

Overall Assessment: ✅ Pass

Protocol for the request data shared by every Product Insights Agent tracking event.
Conforming types only provide their URL and event-specific body fields.
*/
protocol CIOTrackProductInsightsAgentData: CIORequestData {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The protocol is declared internal (default), which is intentional since conforming types' Data structs are internal. However, it might be worth adding an explicit internal modifier or a comment clarifying this is intentional, since all public-facing types in this codebase consistently use explicit access modifiers. Purely cosmetic, but aids future maintainers scanning for the public surface.

This is a minor consistency nit — not blocking.

}

dict["beacon"] = true
dict.merge(baseParams) { current, _ in current }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The merge strategy { current, _ in current } means eventFields() and the shared fields (item_id, beacon, etc.) win over baseParams. This is consistent with CIOTrackResultsImpressionViewData and presumably correct (base params shouldn't overwrite business logic fields). However, this silent "last-writer-loses" behaviour isn't obvious from context. A brief comment documenting the intent — e.g. // event fields take precedence over base params — would protect against future accidental reversals that could produce hard-to-debug 400 errors.

// A fixed-format DateFormatter is used instead of ISO8601DateFormatter because setting
// `.withFractionalSeconds` throws an exception on iOS 11.0 and 11.1. DateFormatter is thread-safe,
// so a single shared instance is used for all timespans
static let dateFormatter: DateFormatter = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: DateFormatter is documented as thread-safe for string(from:) calls, and the comment in the code references this. That said, the comment currently says "DateFormatter is thread-safe, so a single shared instance is used for all timespans". Consider clarifying the specific thread-safety scope: DateFormatter is thread-safe for reading (formatting) after initial creation, but not for writes (changing dateFormat, locale, etc.). Since this formatter is static let with a fixed configuration and never mutated after creation, it is safe. A brief comment noting that the formatter must not be mutated after creation would guard against someone inadvertently adding formatter.dateFormat = ... elsewhere.

This is a documentation nit only — the current code is correct.

// Builds the item object sent by trackResultsImpressionView and trackProductInsightsAgentAnswerView.
// Other events send different item shapes (purchase repeats each item `quantity` times and sends only item_id/variation_id,
// and ASA/search/browse/recommendations omit item_name)
func toImpressionDictionary() -> [String: String] {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: toImpressionDictionary() is internal (no access modifier) but it's used by two callers: the existing CIOTrackResultsImpressionViewData and the new CIOTrackProductInsightsAgentAnswerViewData. The method's comment accurately describes the shared shape. Consider whether internal is the right default here versus making it fileprivate — but given cross-file usage is intentional, internal is correct.

One minor naming nit: the method is on CIOItem but the shape it produces is also used by PIA's answer_view event. The existing comment (// Builds the item object sent by trackResultsImpressionView and trackProductInsightsAgentAnswerView) has been updated to reflect the two callers, which is good. No change required.

- completionHandler: The callback to execute on completion.
- makeData: Builds the event's request data from the resolved section and analytics tags
*/
private func trackProductInsightsAgent(sectionName: String?, analyticsTags: [String: String]?, completionHandler: TrackingCompletionHandler?, makeData: (String, [String: String]?) -> CIOTrackProductInsightsAgentData) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The closure parameter label makeData is clear and the pattern matches how the ASA (Agent Search Assistant) events are structured. One thing worth noting for maintainability: the closure's parameter types (String, [String: String]?) are positional and undocumented. A future developer could accidentally transpose sectionName and an analytics tag key. Consider using a named-parameter closure type, e.g.:

makeData: (_ sectionName: String, _ analyticsTags: [String: String]?) -> CIOTrackProductInsightsAgentData

This is a minor readability suggestion — the code is correct as-is and matches the SDK's existing style.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants