Skip to content

Commit 903026a

Browse files
joe-ayoub-segmentkc-ong-taguchi
authored andcommitted
[STRATCONN-6006] - CM360 handle 200 responses with errors (#3085)
* [STRATCONN-6006] - CM360 handle 200 responses with errors * updating type * minor refactor to make the code safer
1 parent 0c66e58 commit 903026a

File tree

4 files changed

+295
-4
lines changed

4 files changed

+295
-4
lines changed

packages/destination-actions/src/destinations/google-campaign-manager-360/conversionAdjustmentUpload/__tests__/index.test.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -937,5 +937,140 @@ describe('CampaignManager360.conversionAdjustmentUpload', () => {
937937
})
938938
).rejects.toThrowError()
939939
})
940+
941+
it('throws an error when a 200 response with errors returned', async () => {
942+
const event = createTestEvent({
943+
timestamp: '2023-01-01T00:00:00.000Z', // old timestamp to force a 200 response with errors
944+
event: 'Test Event',
945+
context: {
946+
traits: {
947+
948+
phone: '1234567890',
949+
firstName: 'Daffy',
950+
lastName: 'Duck',
951+
streetAddress: '123 Daffy St',
952+
city: 'Burbank',
953+
state: 'CA',
954+
postalCode: '98765',
955+
countryCode: 'US'
956+
}
957+
},
958+
properties: {
959+
ordinal: '1',
960+
quantity: '2',
961+
value: '100',
962+
gclid: '54321',
963+
limitAdTracking: true,
964+
childDirectedTreatment: true,
965+
nonPersonalizedAd: true,
966+
treatmentForUnderage: true
967+
}
968+
})
969+
970+
delete event.userId
971+
972+
const resp200WithErrors = {
973+
hasFailures: true,
974+
status: [
975+
{
976+
conversion: {
977+
floodlightConfigurationId: '7327628',
978+
floodlightActivityId: '11095382',
979+
timestampMicros: '1752650157000',
980+
value: 1,
981+
quantity: '1',
982+
ordinal: '123455',
983+
matchId: 'b5725c01-3f29-43c4-a0d7-afdff53578bb',
984+
kind: 'dfareporting#conversion'
985+
},
986+
errors: [
987+
{
988+
code: 'INVALID_ARGUMENT',
989+
message: 'Conversions over 28 days old may not be updated.',
990+
kind: 'dfareporting#conversionError'
991+
}
992+
],
993+
kind: 'dfareporting#conversionStatus'
994+
}
995+
],
996+
kind: 'dfareporting#conversionsBatchUpdateResponse'
997+
}
998+
999+
nock(`https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/${profileId}/conversions/batchupdate`)
1000+
.post('')
1001+
.reply(200, resp200WithErrors)
1002+
1003+
await expect(
1004+
testDestination.testAction('conversionAdjustmentUpload', {
1005+
event,
1006+
mapping: {
1007+
requiredId: {
1008+
gclid: {
1009+
'@path': '$.properties.gclid'
1010+
}
1011+
},
1012+
timestamp: {
1013+
'@path': '$.timestamp'
1014+
},
1015+
value: {
1016+
'@path': '$.properties.value'
1017+
},
1018+
quantity: {
1019+
'@path': '$.properties.quantity'
1020+
},
1021+
ordinal: {
1022+
'@path': '$.properties.ordinal'
1023+
},
1024+
userDetails: {
1025+
email: {
1026+
'@path': '$.context.traits.email'
1027+
},
1028+
phone: {
1029+
'@path': '$.context.traits.phone'
1030+
},
1031+
firstName: {
1032+
'@path': '$.context.traits.firstName'
1033+
},
1034+
lastName: {
1035+
'@path': '$.context.traits.lastName'
1036+
},
1037+
streetAddress: {
1038+
'@path': '$.context.traits.streetAddress'
1039+
},
1040+
city: {
1041+
'@path': '$.context.traits.city'
1042+
},
1043+
state: {
1044+
'@path': '$.context.traits.state'
1045+
},
1046+
postalCode: {
1047+
'@path': '$.context.traits.postalCode'
1048+
},
1049+
countryCode: {
1050+
'@path': '$.context.traits.countryCode'
1051+
}
1052+
},
1053+
limitAdTracking: {
1054+
'@path': '$.properties.limitAdTracking'
1055+
},
1056+
childDirectedTreatment: {
1057+
'@path': '$.properties.childDirectedTreatment'
1058+
},
1059+
nonPersonalizedAd: {
1060+
'@path': '$.properties.nonPersonalizedAd'
1061+
},
1062+
treatmentForUnderage: {
1063+
'@path': '$.properties.treatmentForUnderage'
1064+
}
1065+
},
1066+
useDefaultMappings: true,
1067+
settings: {
1068+
profileId,
1069+
defaultFloodlightActivityId: floodlightActivityId,
1070+
defaultFloodlightConfigurationId: floodlightConfigurationId
1071+
}
1072+
})
1073+
).rejects.toThrowError('Conversions over 28 days old may not be updated.')
1074+
})
9401075
})
9411076
})

packages/destination-actions/src/destinations/google-campaign-manager-360/conversionUpload/__tests__/index.test.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1051,5 +1051,138 @@ describe('Cm360.conversionUpload', () => {
10511051
})
10521052
).rejects.toThrowError()
10531053
})
1054+
1055+
it('throws an error when a 20X response with errors returned', async () => {
1056+
const event = createTestEvent({
1057+
timestamp: '2023-01-01T00:00:00.000Z', // old timestamp to force a 200 response with errors
1058+
event: 'Test Event',
1059+
context: {
1060+
traits: {
1061+
1062+
phone: '1234567890',
1063+
firstName: 'Daffy',
1064+
lastName: 'Duck',
1065+
streetAddress: '123 Daffy St',
1066+
city: 'Burbank',
1067+
state: 'CA',
1068+
postalCode: '98765',
1069+
countryCode: 'US'
1070+
}
1071+
},
1072+
properties: {
1073+
ordinal: '1',
1074+
quantity: '1',
1075+
value: '123',
1076+
gclid: '54321',
1077+
limitAdTracking: true,
1078+
childDirectedTreatment: true,
1079+
nonPersonalizedAd: true,
1080+
treatmentForUnderage: true
1081+
}
1082+
})
1083+
1084+
const resp20XWithErrors = {
1085+
hasFailures: true,
1086+
status: [
1087+
{
1088+
conversion: {
1089+
floodlightConfigurationId: '7327628',
1090+
floodlightActivityId: '11095382',
1091+
timestampMicros: '1752650157000',
1092+
value: 1,
1093+
quantity: '1',
1094+
ordinal: '123455',
1095+
matchId: 'b5725c01-3f29-43c4-a0d7-afdff53578bb',
1096+
kind: 'dfareporting#conversion'
1097+
},
1098+
errors: [
1099+
{
1100+
code: 'INVALID_ARGUMENT',
1101+
message: 'Conversions over 28 days old may not be updated.',
1102+
kind: 'dfareporting#conversionError'
1103+
}
1104+
],
1105+
kind: 'dfareporting#conversionStatus'
1106+
}
1107+
],
1108+
kind: 'dfareporting#conversionsBatchUpdateResponse'
1109+
}
1110+
1111+
nock(`https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/${profileId}/conversions/batchinsert`)
1112+
.post('')
1113+
.reply(201, resp20XWithErrors)
1114+
1115+
await expect(
1116+
testDestination.testAction('conversionUpload', {
1117+
event,
1118+
mapping: {
1119+
requiredId: {
1120+
gclid: {
1121+
'@path': '$.properties.gclid'
1122+
}
1123+
},
1124+
timestamp: {
1125+
'@path': '$.timestamp'
1126+
},
1127+
value: {
1128+
'@path': '$.properties.value'
1129+
},
1130+
quantity: {
1131+
'@path': '$.properties.quantity'
1132+
},
1133+
ordinal: {
1134+
'@path': '$.properties.ordinal'
1135+
},
1136+
userDetails: {
1137+
email: {
1138+
'@path': '$.context.traits.email'
1139+
},
1140+
phone: {
1141+
'@path': '$.context.traits.phone'
1142+
},
1143+
firstName: {
1144+
'@path': '$.context.traits.firstName'
1145+
},
1146+
lastName: {
1147+
'@path': '$.context.traits.lastName'
1148+
},
1149+
streetAddress: {
1150+
'@path': '$.context.traits.streetAddress'
1151+
},
1152+
city: {
1153+
'@path': '$.context.traits.city'
1154+
},
1155+
state: {
1156+
'@path': '$.context.traits.state'
1157+
},
1158+
postalCode: {
1159+
'@path': '$.context.traits.postalCode'
1160+
},
1161+
countryCode: {
1162+
'@path': '$.context.traits.countryCode'
1163+
}
1164+
},
1165+
limitAdTracking: {
1166+
'@path': '$.properties.limitAdTracking'
1167+
},
1168+
childDirectedTreatment: {
1169+
'@path': '$.properties.childDirectedTreatment'
1170+
},
1171+
nonPersonalizedAd: {
1172+
'@path': '$.properties.nonPersonalizedAd'
1173+
},
1174+
treatmentForUnderage: {
1175+
'@path': '$.properties.treatmentForUnderage'
1176+
}
1177+
},
1178+
useDefaultMappings: true,
1179+
settings: {
1180+
profileId,
1181+
defaultFloodlightActivityId: floodlightActivityId,
1182+
defaultFloodlightConfigurationId: floodlightConfigurationId
1183+
}
1184+
})
1185+
).rejects.toThrowError('Conversions over 28 days old may not be updated.')
1186+
})
10541187
})
10551188
})

packages/destination-actions/src/destinations/google-campaign-manager-360/types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,13 @@ export type Source = typeof Source[keyof typeof Source]
127127
export const CustomVarTypeChoices = Array.from({ length: 100 }, (_, i) => `U${i + 1}`)
128128

129129
export type CustomVarTypeChoices = typeof CustomVarTypeChoices[number]
130+
131+
export interface SuccessMaybeErrorResponse {
132+
hasFailures: boolean
133+
status?: Array<{
134+
errors?: Array<{
135+
code: string
136+
message: string
137+
}>
138+
}>
139+
}

packages/destination-actions/src/destinations/google-campaign-manager-360/utils.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { RequestClient, PayloadValidationError } from '@segment/actions-core'
1+
import { RequestClient, PayloadValidationError, IntegrationError, ErrorCodes } from '@segment/actions-core'
22
import { Payload as UploadPayload } from './conversionUpload/generated-types'
33
import { Payload as Adjustayload } from './conversionAdjustmentUpload/generated-types'
44
import { AuthTokens } from '@segment/actions-core/destination-kit/parse-settings'
@@ -14,7 +14,8 @@ import {
1414
Conversion,
1515
CartData,
1616
ConsentType,
17-
EncryptionInfo
17+
EncryptionInfo,
18+
SuccessMaybeErrorResponse
1819
} from './types'
1920
import { processHashing } from '../../lib/hashing-utils'
2021

@@ -31,7 +32,7 @@ export async function send(
3132
maybeThrow(`No valid payloads found in batch of size ${payloads.length}`, true)
3233
}
3334

34-
const response = await request(
35+
const response = await request<SuccessMaybeErrorResponse>(
3536
`https://dfareporting.googleapis.com/dfareporting/v4/userprofiles/${settings.profileId}/conversions/batch` +
3637
(isAdjustment ? 'update' : 'insert'),
3738
{
@@ -41,9 +42,21 @@ export async function send(
4142
'Content-Type': 'application/json',
4243
Host: 'dfareporting.googleapis.com'
4344
},
44-
json
45+
json,
46+
throwHttpErrors: false
4547
}
4648
)
49+
50+
const isSuccess = response.status >= 200 && response.status < 300
51+
const hasFailures = response?.data?.hasFailures === true
52+
53+
if (isSuccess && hasFailures) {
54+
const firstError = response?.data?.status?.[0]?.errors?.[0]
55+
const message = firstError?.message ?? '200 response contained unknown error'
56+
const code = firstError?.code ?? ErrorCodes.UNKNOWN_ERROR
57+
throw new IntegrationError(message, code, 400)
58+
}
59+
4760
return response
4861
}
4962

0 commit comments

Comments
 (0)