Skip to content

Commit 0161397

Browse files
KSD-24: Apply Connect related changes and other small fixes (#221)
* fix: reduce cognitve complexity of some functions, other small fixes * fix: upgrade esbuild dependency version to reduce vulnerabilities * fix: set E2E report artifact name by project id * fix: upgrade axios version * fix: remove unneeded else statement
1 parent 94dcc4b commit 0161397

8 files changed

Lines changed: 320 additions & 232 deletions

File tree

.github/actions/postman/action.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,5 +55,5 @@ runs:
5555
uses: actions/upload-artifact@v4
5656
if: always()
5757
with:
58-
name: E2E-reports
58+
name: E2E-reports_${{ inputs.ct_project_id }}
5959
path: plugin/src/test/e2e/postman/newman

.trivyignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
# Already defined a newer version of cross-spawn for this.
22
# Dependency chain is jest - jest/core - execa - cross-spawn.
33
#CVE-2024-21538
4+
5+
# Same for axios 1.7.9, chain is klaviyo-api-node > axios
6+
CVE-2025-27152

plugin/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"chai": "^4.3.7",
4343
"chai-http": "^4.3.0",
4444
"dts-gen": ">=0.6.1",
45-
"esbuild": "^0.17.15",
45+
"esbuild": "^0.25.0",
4646
"eslint": "^8.37.0",
4747
"eslint-config-prettier": "^8.8.0",
4848
"eslint-plugin-prettier": "^4.2.1",
@@ -59,6 +59,7 @@
5959
"@breejs/ts-worker": "^2.0.0",
6060
"@commercetools/sdk-client-v2": "^2.2.0",
6161
"@mswjs/interceptors": "^0.25.13",
62+
"axios": "^1.8.2",
6263
"bree": "^9.1.3",
6364
"config": "^3.3.9",
6465
"country-data": "^0.0.31",
@@ -68,6 +69,7 @@
6869
"winston": "^3.8.2"
6970
},
7071
"resolutions": {
71-
"cross-spawn": "^7.0.6"
72+
"**/cross-spawn": "^7.0.6",
73+
"**/axios": "^1.8.2"
7274
}
7375
}

plugin/src/domain/bulkSync/OrdersSync.ts

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ export class OrdersSync {
4040
await this.syncOrders(this.ctOrderService.getOrdersByStartId, ' by start id', [startId]);
4141
};
4242

43+
private getProductsForOrder = async (
44+
ctProductsByOrder: Record<string, object[]>,
45+
ctOrdersResult: PaginatedOrderResults | undefined,
46+
) => {
47+
for (const order of (ctOrdersResult as PaginatedOrderResults).data) {
48+
ctProductsByOrder[order.id] = [];
49+
let ctProductsResult: PaginatedProductResults | undefined;
50+
do {
51+
try {
52+
ctProductsResult = await this.ctProductService.getProductsByIdRange(
53+
order.lineItems.map((item) => item.productId),
54+
ctProductsResult?.lastId,
55+
);
56+
ctProductsByOrder[order.id] = ctProductsByOrder[order.id].concat(ctProductsResult.data);
57+
} catch (err) {
58+
logger.info(`Failed to get product details for order: ${order.id}`);
59+
}
60+
} while (ctProductsResult?.hasMore);
61+
}
62+
};
63+
4364
private syncOrders = async (ordersMethod: any, importTypeText: string, args: unknown[]) => {
4465
try {
4566
//ensures that only one sync at the time is running
@@ -56,23 +77,9 @@ export class OrdersSync {
5677
do {
5778
ctOrdersResult = await ordersMethod(...args, ctOrdersResult?.lastId);
5879

59-
// Used to set Categories for Order properties in Klaviyo
80+
// Used to set Products/Categories for Order properties in Klaviyo
6081
ctProductsByOrder = {};
61-
for (const order of (ctOrdersResult as PaginatedOrderResults).data) {
62-
ctProductsByOrder[order.id] = [];
63-
let ctProductsResult: PaginatedProductResults | undefined;
64-
do {
65-
try {
66-
ctProductsResult = await this.ctProductService.getProductsByIdRange(
67-
order.lineItems.map((item) => item.productId),
68-
ctProductsResult?.lastId,
69-
);
70-
ctProductsByOrder[order.id] = ctProductsByOrder[order.id].concat(ctProductsResult.data);
71-
} catch (err) {
72-
logger.info(`Failed to get product details for order: ${order.id}`);
73-
}
74-
} while (ctProductsResult?.hasMore);
75-
}
82+
await this.getProductsForOrder(ctProductsByOrder, ctOrdersResult);
7683

7784
const promiseResults = await Promise.allSettled(
7885
(ctOrdersResult as PaginatedOrderResults).data.flatMap((order) =>
@@ -115,7 +122,12 @@ export class OrdersSync {
115122

116123
//Order placed event
117124
events.push(
118-
this.orderMapper.mapCtOrderToKlaviyoEvent(order, orderProducts, config.get('order.metrics.placedOrder'), false),
125+
this.orderMapper.mapCtOrderToKlaviyoEvent(
126+
order,
127+
orderProducts,
128+
config.get('order.metrics.placedOrder'),
129+
false,
130+
),
119131
);
120132

121133
//Ordered product event

plugin/src/domain/shared/mappers/DefaultProductMapper.ts

Lines changed: 41 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ export class DefaultProductMapper implements ProductMapper {
277277
};
278278
}
279279

280-
public getProductInventoryByPriority(availability?: ProductVariantAvailability | InventoryEntry): number | null {
280+
public getProductInventoryByPriority(availability?: ProductVariantAvailability | InventoryEntry): number | null | undefined {
281281
if (!availability) {
282282
return 0;
283283
}
@@ -286,31 +286,52 @@ export class DefaultProductMapper implements ProductMapper {
286286
const productInventoryChannel = config.get('product.inventory.useChannelInventory') as string;
287287
const variantAvailabilityChannels = (availability as ProductVariantAvailability).channels;
288288
const inventoryEntryChannel = (availability as InventoryEntry).supplyChannel;
289-
if (productInventoryChannel && (variantAvailabilityChannels || inventoryEntryChannel)) {
290-
const variantChannelAvailableQuantity = variantAvailabilityChannels
291-
? variantAvailabilityChannels[productInventoryChannel]?.availableQuantity
292-
: undefined;
293-
const inventoryChannelAvailableQuantity =
294-
inventoryEntryChannel?.id === productInventoryChannel ? availability.availableQuantity : undefined;
295-
if (variantChannelAvailableQuantity) {
296-
return variantChannelAvailableQuantity;
297-
}
289+
const returnValue = this.getInventoryQuantityByPriority(
290+
availability,
291+
productInventoryChannel,
292+
variantAvailabilityChannels,
293+
inventoryEntryChannel,
294+
);
295+
return isNaN(returnValue as number) ? availability?.availableQuantity || 0 : returnValue;
296+
}
297+
298+
return availability?.availableQuantity || 0;
299+
}
300+
301+
private getInventoryQuantityByPriority(
302+
availability: ProductVariantAvailability | InventoryEntry,
303+
productInventoryChannel: string,
304+
variantAvailabilityChannels: ProductVariantAvailability['channels'],
305+
inventoryEntryChannel: InventoryEntry['supplyChannel'],
306+
) {
307+
if (productInventoryChannel && (variantAvailabilityChannels || inventoryEntryChannel)) {
308+
const variantChannelAvailableQuantity = this.getVariantChannelAvailableQuantity(productInventoryChannel, variantAvailabilityChannels)
309+
const inventoryChannelAvailableQuantity =
310+
inventoryEntryChannel?.id === productInventoryChannel ? availability.availableQuantity : undefined;
311+
if (variantChannelAvailableQuantity) {
312+
return variantChannelAvailableQuantity;
313+
}
298314

299-
if (inventoryChannelAvailableQuantity) {
300-
return inventoryChannelAvailableQuantity;
301-
} else {
302-
if (!variantAvailabilityChannels) {
303-
return null;
304-
}
305-
}
315+
if (inventoryChannelAvailableQuantity) {
316+
return inventoryChannelAvailableQuantity;
306317
}
307-
// Prevents bulk sync and inventory update events from stepping on each other
308-
else if (!productInventoryChannel && inventoryEntryChannel) {
318+
319+
if (!variantAvailabilityChannels) {
309320
return null;
310321
}
311322
}
323+
// Prevents bulk sync and inventory update events from stepping on each other
324+
else if (!productInventoryChannel && inventoryEntryChannel) {
325+
return null;
326+
}
312327

313-
return availability?.availableQuantity || 0;
328+
return NaN;
329+
}
330+
331+
private getVariantChannelAvailableQuantity(productInventoryChannel: string, variantAvailabilityChannels: ProductVariantAvailability['channels']): number | undefined {
332+
return variantAvailabilityChannels
333+
? variantAvailabilityChannels[productInventoryChannel]?.availableQuantity
334+
: undefined;
314335
}
315336

316337
public mapKlaviyoItemIdToDeleteItemRequest(klaviyoItemId: string): ItemDeletedRequest {

plugin/src/infrastructure/driven/klaviyo/KlaviyoSdkService.ts

Lines changed: 61 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -106,57 +106,71 @@ export class KlaviyoSdkService extends KlaviyoService {
106106

107107
private async createOrUpdateProfile(body: KlaviyoRequestType, create: boolean) {
108108
try {
109-
return await (create ? Profiles.createProfile(body) : Profiles.updateProfile(body.data.id!, body));
109+
return await this.processProfileOperation(body, create);
110110
} catch (e: any) {
111-
if (e.status === 400) {
112-
let errorCauses;
113-
try {
114-
errorCauses = (e.response?.data?.errors || []).map((err: any) => err?.source?.pointer);
115-
} catch (e) {
116-
logger.error('Error getting error source pointer from error response', e);
117-
throw new StatusError(
118-
400,
119-
`Bad request, error getting error source pointer from error response. Request body: ${JSON.stringify(
120-
body,
121-
)}`,
122-
);
123-
}
124-
if (errorCauses.includes('/data/attributes/phone_number')) {
125-
logger.info(
126-
`Invalid phone number when ${
127-
create ? 'creating' : 'updating'
128-
} profile. Retrying after removing phone number from profile...`,
129-
e.response?.data,
130-
);
131-
const modifiedBody: any = {
132-
data: {
133-
...body.data,
134-
attributes: {
135-
...(body.data as any).attributes,
136-
phoneNumber: undefined,
137-
},
138-
},
139-
};
140-
try {
141-
return await (create
142-
? Profiles.createProfile(modifiedBody)
143-
: Profiles.updateProfile(modifiedBody.data?.id, modifiedBody));
144-
} catch (e: any) {
145-
logger.error(
146-
`Error ${
147-
create ? 'creating' : 'updating'
148-
} profile in Klaviyo after removing phone_number. Response code ${e.status}, ${e.message}`,
149-
e.response?.data || e,
150-
);
151-
throw e;
152-
}
153-
}
111+
if (e.status !== 400) {
112+
logger.error(
113+
`Error ${create ? 'creating' : 'updating'} profile in Klaviyo. Response code ${e.status}, ${
114+
e.message
115+
}`,
116+
e,
117+
);
118+
throw e;
119+
}
120+
121+
const errorCauses = this.extractErrorCauses(e);
122+
if (errorCauses.includes('/data/attributes/phone_number')) {
123+
return this.retryProfileOperationWithoutPhoneNumber(body, create, e);
154124
}
125+
126+
throw new StatusError(
127+
400,
128+
`Bad request. Error causes: ${errorCauses.join(', ')}. Request body: ${JSON.stringify(body)}`,
129+
);
130+
}
131+
}
132+
133+
private async processProfileOperation(body: KlaviyoRequestType, create: boolean) {
134+
return create ? Profiles.createProfile(body) : Profiles.updateProfile(body.data.id!, body);
135+
}
136+
137+
private extractErrorCauses(e: any): string[] {
138+
try {
139+
return (e.response?.data?.errors || []).map((err: any) => err?.source?.pointer);
140+
} catch (error) {
141+
logger.error('Error getting error source pointer from error response', error);
142+
throw new StatusError(400, 'Bad request, error getting error source pointer from error response.');
143+
}
144+
}
145+
146+
private async retryProfileOperationWithoutPhoneNumber(body: KlaviyoRequestType, create: boolean, e: any) {
147+
logger.info(
148+
`Invalid phone number when ${
149+
create ? 'creating' : 'updating'
150+
} profile. Retrying after removing phone number from profile...`,
151+
e.response?.data,
152+
);
153+
154+
const modifiedBody: any = {
155+
data: {
156+
...body.data,
157+
attributes: {
158+
...(body.data as any).attributes,
159+
phoneNumber: undefined,
160+
},
161+
},
162+
};
163+
164+
try {
165+
return await this.processProfileOperation(modifiedBody, create);
166+
} catch (error: any) {
155167
logger.error(
156-
`Error ${create ? 'creating' : 'updating'} profile in Klaviyo. Response code ${e.status}, ${e.message}`,
157-
e,
168+
`Error ${
169+
create ? 'creating' : 'updating'
170+
} profile in Klaviyo after removing phone_number. Response code ${error.status}, ${error.message}`,
171+
error.response?.data || error,
158172
);
159-
throw e;
173+
throw error;
160174
}
161175
}
162176

plugin/src/utils/validate-configurations.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ describe('validateEnvironment', () => {
44
it('should exit without issues if all required env variables are defined', async () => {
55
let error;
66
try {
7-
await validateEnvironment();
7+
validateEnvironment();
88
} catch (err) {
99
error = err
1010
}
@@ -16,7 +16,7 @@ describe('validateEnvironment', () => {
1616
process.env.CT_API_URL = '';
1717
process.env.CT_API_CLIENT = '{abc}';
1818
try {
19-
await validateEnvironment();
19+
validateEnvironment();
2020
} catch (err) {
2121
error = err
2222
}

0 commit comments

Comments
 (0)