Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: salesforce v2 refactor #3773

Closed
wants to merge 14 commits into from
2 changes: 2 additions & 0 deletions src/v0/destinations/salesforce/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const DESTINATION = 'Salesforce';
const SALESFORCE_OAUTH_SANDBOX = 'salesforce_oauth_sandbox';
const OAUTH = 'oauth';
const LEGACY = 'legacy';
const SALESFORCE_OAUTH = 'salesforce_oauth';

const mappingConfig = getMappingConfig(ConfigCategory, __dirname);

Expand All @@ -42,5 +43,6 @@ module.exports = {
DESTINATION,
OAUTH,
LEGACY,
SALESFORCE_OAUTH,
SALESFORCE_OAUTH_SANDBOX,
};
16 changes: 10 additions & 6 deletions src/v0/destinations/salesforce/networkHandler.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
const { proxyRequest, prepareProxyRequest } = require('../../../adapters/network');
const { processAxiosResponse } = require('../../../adapters/utils/networkUtils');
const { isHttpStatusSuccess } = require('../../util');
const { LEGACY } = require('./config');
const { salesforceResponseHandler } = require('./utils');

const responseHandler = (responseParams) => {
const { destinationResponse, destType, rudderJobMetadata } = responseParams;
const message = `Request for destination: ${destType} Processed Successfully`;
const { status } = destinationResponse;

salesforceResponseHandler(
destinationResponse,
'during Salesforce Response Handling',
rudderJobMetadata?.destInfo?.authKey,
LEGACY,
);
if (!isHttpStatusSuccess(status) && status >= 400) {
salesforceResponseHandler(
destinationResponse,
'during Salesforce Response Handling',
rudderJobMetadata?.destInfo?.authKey,
LEGACY,
);
}

// else successfully return status as 200, message and original destination response
return {
Expand Down
175 changes: 114 additions & 61 deletions src/v0/destinations/salesforce/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
ThrottledError,
AbortedError,
OAuthSecretError,
isDefinedAndNotNull,
} = require('@rudderstack/integrations-lib');
const { handleHttpRequest } = require('../../../adapters/network');
const {
isHttpStatusSuccess,
getAuthErrCategoryFromStCode,
isDefinedAndNotNull,
} = require('../../util');
const { getAuthErrCategoryFromStCode } = require('../../util');
const Cache = require('../../util/cache');
const {
ACCESS_TOKEN_CACHE_TTL,
Expand All @@ -18,11 +15,69 @@
DESTINATION,
LEGACY,
OAUTH,
SALESFORCE_OAUTH,
SALESFORCE_OAUTH_SANDBOX,
} = require('./config');

const ACCESS_TOKEN_CACHE = new Cache(ACCESS_TOKEN_CACHE_TTL);

// ref: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/errorcodes.htm?q=error%20code
/**
*
* @param {*} response is of structure
* [
* {
* "message" : "The requested resource does not exist",
* "errorCode" : "NOT_FOUND"
}
]
* @returns error message
*/
const getErrorMessage = (response) => {
if (response && Array.isArray(response) && response[0]?.message?.length > 0) {
return response[0].message;
}
return JSON.stringify(response);
};

const handleAuthError = (
Copy link
Contributor

Choose a reason for hiding this comment

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

I feel we can rewrite the function in a simpler way like below

const handleAuthError = (
  errorCode,
  authKey,
  authorizationFlow,
  sourceMessage,
  destResponse,
  status,
) => {
  if (errorCode === 'INVALID_SESSION_ID') {
    let authErrCategory = '';
    if (authorizationFlow === OAUTH) {
      authErrCategory = getAuthErrCategoryFromStCode(status);
    } else {
      ACCESS_TOKEN_CACHE.del(authKey);
    }
    throw new RetryableError(
      `${DESTINATION} Request Failed - due to "INVALID_SESSION_ID", (${authErrCategory}) ${sourceMessage}`,
      500,
      destResponse,
      authErrCategory,
    );
  }

  throw new AbortedError(
    `${DESTINATION} Request Failed: "${status}" due to "${getErrorMessage(destResponse.response)}", (Aborted) ${sourceMessage}`,
    400,
    destResponse,
  );
};

errorCode,
authKey,
authorizationFlow,
sourceMessage,
destResponse,
status,
) => {
if (errorCode === 'INVALID_SESSION_ID') {
let authErrCategory = '';
if (authorizationFlow === OAUTH) {
authErrCategory = getAuthErrCategoryFromStCode(status);
} else {
ACCESS_TOKEN_CACHE.del(authKey);
}
throw new RetryableError(
`${DESTINATION} Request Failed - due to "INVALID_SESSION_ID", (${authErrCategory}) ${sourceMessage}`,
500,
destResponse,
authErrCategory,
);
}

throw new AbortedError(
`${DESTINATION} Request Failed: "${status}" due to "${getErrorMessage(destResponse.response)}", (Aborted) ${sourceMessage}`,
400,
destResponse,
);
};

const handleCommonAbortableError = (destResponse, sourceMessage, status) => {
throw new AbortedError(
`${DESTINATION} Request Failed: "${status}" due to "${getErrorMessage(destResponse.response)}", (Aborted) ${sourceMessage}`,
400,
destResponse,
);
};

/**
* ref: https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/errorcodes.htm
* handles Salesforce application level failures
Expand All @@ -34,72 +89,70 @@
const salesforceResponseHandler = (destResponse, sourceMessage, authKey, authorizationFlow) => {
const { status, response } = destResponse;

// if the response from destination is not a success case build an explicit error
if (!isHttpStatusSuccess(status) && status >= 400) {
const matchErrorCode = (errorCode) =>
response && Array.isArray(response) && response.some((resp) => resp?.errorCode === errorCode);
if (status === 401 && authKey && matchErrorCode('INVALID_SESSION_ID')) {
if (authorizationFlow === OAUTH) {
/**
*
* @param {*} errorCode
* response is of structure
* [
* {
* "message" : "Request limit exceeded",
* "errorCode" : "REQUEST_LIMIT_EXCEEDED"
* }
* ]
* @returns true if errorCode is found in the response
*/
const matchErrorCode = (errorCode) =>
shrouti1507 marked this conversation as resolved.
Show resolved Hide resolved
response && Array.isArray(response) && response.some((resp) => resp?.errorCode === errorCode);

switch (status) {
case 401: {
let errorCode = 'DEFAULT';
if (authKey && matchErrorCode('INVALID_SESSION_ID')) {
errorCode = 'INVALID_SESSION_ID';
}
handleAuthError(errorCode, authKey, authorizationFlow, sourceMessage, destResponse, status);
break;

Check warning on line 114 in src/v0/destinations/salesforce/utils.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/salesforce/utils.js#L114

Added line #L114 was not covered by tests
}
case 403:
if (matchErrorCode('REQUEST_LIMIT_EXCEEDED')) {
throw new ThrottledError(
`${DESTINATION} Request Failed - due to "REQUEST_LIMIT_EXCEEDED", (Throttled) ${sourceMessage}`,
destResponse,
);
}
shrouti1507 marked this conversation as resolved.
Show resolved Hide resolved
handleCommonAbortableError(destResponse, sourceMessage, status);
break;

Check warning on line 124 in src/v0/destinations/salesforce/utils.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/salesforce/utils.js#L123-L124

Added lines #L123 - L124 were not covered by tests

case 400:
if (
matchErrorCode('CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY') &&
(response?.message?.includes('UNABLE_TO_LOCK_ROW') ||
response?.message?.includes('Too many SOQL queries'))

Check warning on line 130 in src/v0/destinations/salesforce/utils.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/salesforce/utils.js#L129-L130

Added lines #L129 - L130 were not covered by tests
) {
throw new RetryableError(
`${DESTINATION} Request Failed - due to "INVALID_SESSION_ID", (Retryable) ${sourceMessage}`,
`${DESTINATION} Request Failed - "${response.message}", (Retryable) ${sourceMessage}`,
500,
destResponse,
getAuthErrCategoryFromStCode(status),
);
}
// checking for invalid/expired token errors and evicting cache in that case
// rudderJobMetadata contains some destination info which is being used to evict the cache
ACCESS_TOKEN_CACHE.del(authKey);
throw new RetryableError(
`${DESTINATION} Request Failed - due to "INVALID_SESSION_ID", (Retryable) ${sourceMessage}`,
500,
destResponse,
);
} else if (status === 403 && matchErrorCode('REQUEST_LIMIT_EXCEEDED')) {
// If the error code is REQUEST_LIMIT_EXCEEDED, you’ve exceeded API request limits in your org.
throw new ThrottledError(
`${DESTINATION} Request Failed - due to "REQUEST_LIMIT_EXCEEDED", (Throttled) ${sourceMessage}`,
destResponse,
);
} else if (
status === 400 &&
matchErrorCode('CANNOT_INSERT_UPDATE_ACTIVATE_ENTITY') &&
(response?.message?.includes('UNABLE_TO_LOCK_ROW') ||
response?.message?.includes('Too many SOQL queries'))
) {
// handling the error case where the record is locked by another background job
// this is a retryable error
handleCommonAbortableError(destResponse, sourceMessage, status);
break;

Check warning on line 139 in src/v0/destinations/salesforce/utils.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/salesforce/utils.js#L139

Added line #L139 was not covered by tests

case 503:
case 500:
throw new RetryableError(
`${DESTINATION} Request Failed - "${response.message}", (Retryable) ${sourceMessage}`,
`${DESTINATION} Request Failed: ${status} - due to "${getErrorMessage(response)}", (Retryable) ${sourceMessage}`,
500,
destResponse,
);
} else if (status === 503 || status === 500) {
// The salesforce server is unavailable to handle the request. Typically this occurs if the server is down
// for maintenance or is currently overloaded.
throw new RetryableError(
`${DESTINATION} Request Failed - due to "${
response && Array.isArray(response) && response[0]?.message?.length > 0
? response[0].message
: JSON.stringify(response)
}", (Retryable) ${sourceMessage}`,
500,

default:

Check warning on line 149 in src/v0/destinations/salesforce/utils.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/salesforce/utils.js#L149

Added line #L149 was not covered by tests
// Default case: aborting for all other error codes
throw new AbortedError(

Check warning on line 151 in src/v0/destinations/salesforce/utils.js

View check run for this annotation

Codecov / codecov/patch

src/v0/destinations/salesforce/utils.js#L151

Added line #L151 was not covered by tests
`${DESTINATION} Request Failed: "${status}" due to "${getErrorMessage(response)}", (Aborted) ${sourceMessage}`,
400,
destResponse,
);
}
// check the error message
let errorMessage = '';
if (response && Array.isArray(response)) {
errorMessage = response[0].message;
}
// aborting for all other error codes
throw new AbortedError(
`${DESTINATION} Request Failed: "${status}" due to "${
errorMessage || JSON.stringify(response)
}", (Aborted) ${sourceMessage}`,
400,
destResponse,
);
}
};

Expand Down Expand Up @@ -182,7 +235,7 @@
let authorizationData;
const { Name } = event.destination.DestinationDefinition;
const lowerCaseName = Name?.toLowerCase?.();
if (isDefinedAndNotNull(event?.metadata?.secret) || lowerCaseName === SALESFORCE_OAUTH_SANDBOX) {
if (lowerCaseName === SALESFORCE_OAUTH_SANDBOX || lowerCaseName === SALESFORCE_OAUTH) {
authorizationFlow = OAUTH;
authorizationData = getAccessTokenOauth(event.metadata);
} else {
Expand Down
16 changes: 10 additions & 6 deletions src/v0/destinations/salesforce_oauth/networkHandler.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
const { proxyRequest, prepareProxyRequest } = require('../../../adapters/network');
const { processAxiosResponse } = require('../../../adapters/utils/networkUtils');
const { isHttpStatusSuccess } = require('../../util');
const { OAUTH } = require('../salesforce/config');
const { salesforceResponseHandler } = require('../salesforce/utils');

const responseHandler = (responseParams) => {
const { destinationResponse, destType, rudderJobMetadata } = responseParams;
const message = `Request for destination: ${destType} Processed Successfully`;
const { status } = destinationResponse;

salesforceResponseHandler(
destinationResponse,
'during Salesforce Response Handling',
rudderJobMetadata?.destInfo?.authKey,
OAUTH,
);
if (!isHttpStatusSuccess(status) && status >= 400) {
salesforceResponseHandler(
destinationResponse,
'during Salesforce Response Handling',
rudderJobMetadata?.destInfo?.authKey,
OAUTH,
);
}

// else successfully return status as 200, message and original destination response
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ export const testScenariosForV1API: ProxyV1TestData[] = [
output: {
status: 500,
message:
'Salesforce Request Failed - due to "Session expired or invalid", (Retryable) during Salesforce Response Handling',
'Salesforce Request Failed: 500 - due to "Session expired or invalid", (Retryable) during Salesforce Response Handling',
response: [
{
error:
Expand Down Expand Up @@ -277,7 +277,7 @@ export const testScenariosForV1API: ProxyV1TestData[] = [
body: {
output: {
message:
'Salesforce Request Failed - due to "Server Unavailable", (Retryable) during Salesforce Response Handling',
'Salesforce Request Failed: 503 - due to "Server Unavailable", (Retryable) during Salesforce Response Handling',
response: [
{
error: '[{"message":"Server Unavailable","errorCode":"SERVER_UNAVAILABLE"}]',
Expand Down
10 changes: 5 additions & 5 deletions test/integrations/destinations/salesforce/dataDelivery/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const legacyTests = [
output: {
status: 500,
message:
'Salesforce Request Failed - due to "INVALID_SESSION_ID", (Retryable) during Salesforce Response Handling',
'Salesforce Request Failed - due to "INVALID_SESSION_ID", () during Salesforce Response Handling',
destinationResponse: {
response: [
{
Expand Down Expand Up @@ -319,7 +319,7 @@ const legacyTests = [
output: {
status: 500,
message:
'Salesforce Request Failed - due to "Server Unavailable", (Retryable) during Salesforce Response Handling',
'Salesforce Request Failed: 503 - due to "Server Unavailable", (Retryable) during Salesforce Response Handling',
destinationResponse: {
response: [
{
Expand Down Expand Up @@ -461,7 +461,7 @@ const legacyTests = [
status: 503,
},
message:
'Salesforce Request Failed - due to "{"message":"Server Unavailable","errorCode":"SERVER_UNAVAILABLE"}", (Retryable) during Salesforce Response Handling',
'Salesforce Request Failed: 503 - due to "{"message":"Server Unavailable","errorCode":"SERVER_UNAVAILABLE"}", (Retryable) during Salesforce Response Handling',
statTags: {
destType: 'SALESFORCE',
errorCategory: 'network',
Expand Down Expand Up @@ -603,7 +603,7 @@ const legacyTests = [
output: {
status: 500,
message:
'Salesforce Request Failed - due to ""[ECONNABORTED] :: Connection aborted"", (Retryable) during Salesforce Response Handling',
'Salesforce Request Failed: 500 - due to ""[ECONNABORTED] :: Connection aborted"", (Retryable) during Salesforce Response Handling',
destinationResponse: {
response: '[ECONNABORTED] :: Connection aborted',
status: 500,
Expand Down Expand Up @@ -696,7 +696,7 @@ const legacyTests = [
output: {
status: 500,
message:
'Salesforce Request Failed - due to ""[EAI_AGAIN] :: Temporary failure in name resolution"", (Retryable) during Salesforce Response Handling',
'Salesforce Request Failed: 500 - due to ""[EAI_AGAIN] :: Temporary failure in name resolution"", (Retryable) during Salesforce Response Handling',
destinationResponse: {
response: '[EAI_AGAIN] :: Temporary failure in name resolution',
status: 500,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export const otherSalesforceScenariosV1: ProxyV1TestData[] = [
],
statTags,
message:
'Salesforce Request Failed - due to "{"error":{"message":"Service Unavailable","description":"The server is currently unable to handle the request due to temporary overloading or maintenance of the server. Please try again later."}}", (Retryable) during Salesforce Response Handling',
'Salesforce Request Failed: 503 - due to "{"error":{"message":"Service Unavailable","description":"The server is currently unable to handle the request due to temporary overloading or maintenance of the server. Please try again later."}}", (Retryable) during Salesforce Response Handling',
status: 500,
},
},
Expand Down Expand Up @@ -96,7 +96,7 @@ export const otherSalesforceScenariosV1: ProxyV1TestData[] = [
],
statTags,
message:
'Salesforce Request Failed - due to ""Internal Server Error"", (Retryable) during Salesforce Response Handling',
'Salesforce Request Failed: 500 - due to ""Internal Server Error"", (Retryable) during Salesforce Response Handling',
status: 500,
},
},
Expand Down
Loading
Loading