Skip to content

STRATCONN-6937 [Braze] Fix ecommerce batch error index mismatch and sent/body population - #3929

Open
joe-ayoub-segment wants to merge 3 commits into
mainfrom
STRATCONN-6937-braze-ecommerce-index-fix
Open

STRATCONN-6937 [Braze] Fix ecommerce batch error index mismatch and sent/body population#3929
joe-ayoub-segment wants to merge 3 commits into
mainfrom
STRATCONN-6937-braze-ecommerce-index-fix

Conversation

@joe-ayoub-segment

@joe-ayoub-segment joe-ayoub-segment commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes STRATCONN-6937. Two related fixes in braze/ecommerce/functions.ts send() (shared by both the ecommerce and ecommerceSingleProduct actions):

1. Batch error index mismatch

Batch-mode error matching used the original loop index to look up Braze's errors[].index, but Braze's index refers to the position within the events array actually sent (which excludes payloads filtered out by validate()). When an earlier payload failed validation, the indices diverged and Braze errors were attributed to the wrong payload.

Now matches on the sent-array index (payload.index, already tracked in getJSON()), while still recording the MultiStatus response at the original payload position.

2. MultiStatusResponse sent / body population

  • sent — only set when JSON was actually sent to Braze, populated with the per-item JSON that was sent. Omitted when nothing was sent.
  • body — only set when a response exists: { success: true } for 2XX, error details for non-2XX. Omitted when nothing was sent.
  • Payloads never sent (filtered by validate(), or an invalid syncMode batch) now carry neither sent nor body — so core correctly classifies them as INTEGRATIONS (pre-send validation) rather than DESTINATION errors.

Testing

  • Updated two batch multi-status tests whose expectations depended on the old sent/body shapes.
  • Strengthened the index-mismatch test to assert sent/body on both the Braze-rejected and successful items.
  • Added a "fully successful batch" test asserting sent + { success: true }.
  • All 21 braze/ecommerce tests pass; lint + typecheck clean.

Requires stage test.

🤖 Generated with Claude Code

…ent/body population

Batch error matching in ecommerce send() used the original loop index to
look up Braze's errors[].index, but Braze's index refers to the events
array actually sent (excluding payloads filtered by validate()). Match on
the sent-array index (payload.index) instead, while still recording the
MultiStatus response at the original payload position.

Also populate the MultiStatusResponse sent/body attributes correctly:
- Only set `sent` when JSON was actually sent, to the per-item JSON.
- Only set `body` when a response exists: { success: true } for 2XX,
  error details for non-2XX.
- Omit both when a payload was never sent (filtered by validate() or an
  invalid syncMode batch).

Shared by the ecommerce and ecommerceSingleProduct actions.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 7, 2026 10:51
@joe-ayoub-segment
joe-ayoub-segment requested a review from a team as a code owner August 7, 2026 10:51
@joe-ayoub-segment joe-ayoub-segment self-assigned this Aug 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Fixes Braze ecommerce batch MultiStatus handling by correctly aligning Braze per-item errors to the sent events[] indices and by adjusting when/how sent and body are populated to improve error classification (pre-send vs destination).

Changes:

  • Match Braze errors[].index against the index in the sent events array (payload.index) instead of the original payload loop index.
  • Populate MultiStatus sent / body only for payloads actually sent; use { success: true } for successful items.
  • Update/extend batch multi-status tests to cover index mismatch, fully successful batches, and new sent/body shapes.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
packages/destination-actions/src/destinations/braze/ecommerce/functions.ts Fixes batch error attribution and revises MultiStatus sent/body population semantics.
packages/destination-actions/src/destinations/braze/ecommerce/tests/index.test.ts Updates expectations and adds coverage for the corrected index mapping and new sent/body behavior.

Comment thread packages/destination-actions/src/destinations/braze/ecommerce/functions.ts Outdated
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 7, 2026 11:12
…esponse.data

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (3)

packages/destination-actions/src/destinations/braze/ecommerce/tests/index.test.ts:1241

  • In the invalid-syncMode batch test, send() returns early and should not call /users/track. Leaving this nock interceptor in place can cause the test suite to fail if it enforces that all nocks are consumed. Remove this interceptor (or change the test to explicitly assert that no request was made).
      const responseJSON = [invalidSyncModeError, invalidSyncModeError, invalidSyncModeError, invalidSyncModeError]

      nock(settings.endpoint).post('/users/track', json).reply(200)

packages/destination-actions/src/destinations/braze/ecommerce/functions.ts:69

  • This does an errors.find(...) for each payload (O(n*m)). Since errors[].index is a direct lookup key, consider building a Map<number, Error> once (e.g., const errorByIndex = new Map(errors.map(e => [e.index, e]))) and then doing errorByIndex.get(sentIndex) inside the loop.
  const errors = Array.isArray(response.data?.errors) ? response.data.errors : []

  payloadsWithIndexes.forEach((payload, index) => {
    const sentIndex = payload.index
    if (sentIndex === undefined) {
      return
    }

    const error = errors.find((e) => e.index === sentIndex)

packages/destination-actions/src/destinations/braze/ecommerce/functions.ts:78

  • errormessage and body are currently the same string (error.type), while successful items set body to an object ({ success: true }). To make the MultiStatus shape more consistent and more useful for debugging, consider setting body to a structured object (e.g., the full Braze error object) and keep errormessage as the summary string.
      msResponse.setErrorResponseAtIndex(index, {
        status: 400,
        errortype: 'BAD_REQUEST',
        errormessage: error.type,
        sent: json.events[sentIndex] as object as JSONLikeObject,
        body: error.type
      })

Copilot AI review requested due to automatic review settings August 7, 2026 11:29
@joe-ayoub-segment joe-ayoub-segment added the needs-stage-test Must be tested in Stage before deployment label Aug 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (6)

packages/destination-actions/src/destinations/braze/ecommerce/functions.ts:63

  • The lookup errors.find(...) inside the payloadsWithIndexes.forEach(...) loop is O(n²) for batch sizes and also obscures intent. Consider pre-indexing Braze errors into a Map<number, Error> keyed by index (sent-array index) and doing O(1) lookups per payload.
  const errors = Array.isArray(response.data?.errors) ? response.data.errors : []

  payloadsWithIndexes.forEach((payload, index) => {

packages/destination-actions/src/destinations/braze/ecommerce/functions.ts:69

  • The lookup errors.find(...) inside the payloadsWithIndexes.forEach(...) loop is O(n²) for batch sizes and also obscures intent. Consider pre-indexing Braze errors into a Map<number, Error> keyed by index (sent-array index) and doing O(1) lookups per payload.
    const error = errors.find((e) => e.index === sentIndex)

packages/destination-actions/src/destinations/braze/ecommerce/functions.ts:96

  • This function mutates the incoming payload objects (payload.index = ...) to track sent-array indices. Since index is a fairly generic field name and payload objects can be reused/inspected later in the pipeline, this side-effect can be surprising and hard to reason about. Consider keeping the mapping in a separate structure (e.g., sentIndexByOriginalIndex: Array<number | undefined>) or wrapping payloads in a new object { payload, sentIndex } rather than mutating the payload itself.
  payloadsWithIndexes.forEach((payload, index) => {
    const message = validate(payload, isBatch)
    if (message) {
      payload.index = undefined

packages/destination-actions/src/destinations/braze/ecommerce/functions.ts:103

  • This function mutates the incoming payload objects (payload.index = ...) to track sent-array indices. Since index is a fairly generic field name and payload objects can be reused/inspected later in the pipeline, this side-effect can be surprising and hard to reason about. Consider keeping the mapping in a separate structure (e.g., sentIndexByOriginalIndex: Array<number | undefined>) or wrapping payloads in a new object { payload, sentIndex } rather than mutating the payload itself.
      const event = getJSONItem(payload, settings, syncMode)
      payload.index = events.length

packages/destination-actions/src/destinations/braze/ecommerce/functions.ts:78

  • For destination-side per-item failures, body is currently set to the same string as errormessage (error.type). This drops potentially useful diagnostic detail from the Braze response (e.g., the full error object including the index/type and any additional fields). Consider storing richer error details in body (for example, the full error object or a structured { error: ... }) so downstream debugging and observability have more context.
      msResponse.setErrorResponseAtIndex(index, {
        status: 400,
        errortype: 'BAD_REQUEST',
        errormessage: error.type,
        sent: json.events[sentIndex] as object as JSONLikeObject,
        body: error.type
      })

packages/destination-actions/src/destinations/braze/ecommerce/tests/index.test.ts:1241

  • In the invalid-syncMode batch test, the implementation now returns early without making a request, so this Nock interceptor is unused. Leaving unused interceptors can make tests brittle if the suite later enforces nock.isDone()/no pending mocks. Consider removing this interceptor (or explicitly asserting no request was made, if that’s the intent).
      nock(settings.endpoint).post('/users/track', json).reply(200)

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants