diff --git a/src/v0/destinations/zendesk/config.js b/src/v0/destinations/zendesk/config.js index dee31be94da..23d57ca8e90 100644 --- a/src/v0/destinations/zendesk/config.js +++ b/src/v0/destinations/zendesk/config.js @@ -1,5 +1,9 @@ const { getMappingConfig } = require("../../util"); +const getBaseEndpoint = domain => { + return `https://${domain}.zendesk.com/api/v2/`; +}; +const NAME = "zendesk"; const ConfigCategory = { IDENTIFY: { name: "ZDIdentifyConfig", @@ -44,10 +48,6 @@ const ZENDESK_MARKET_PLACE_NAME = "RudderStack"; const ZENDESK_MARKET_PLACE_ORG_ID = "3339"; const ZENDESK_MARKET_PLACE_APP_ID = "263241"; -const getBaseEndpoint = domain => { - return `https://${domain}.zendesk.com/api/v2/`; -}; - module.exports = { getBaseEndpoint, ConfigCategory, @@ -55,5 +55,6 @@ module.exports = { defaultFields, ZENDESK_MARKET_PLACE_NAME, ZENDESK_MARKET_PLACE_ORG_ID, - ZENDESK_MARKET_PLACE_APP_ID + ZENDESK_MARKET_PLACE_APP_ID, + NAME }; diff --git a/src/v0/destinations/zendesk/transform.js b/src/v0/destinations/zendesk/transform.js index ad0083f249c..5a0682db639 100644 --- a/src/v0/destinations/zendesk/transform.js +++ b/src/v0/destinations/zendesk/transform.js @@ -10,6 +10,7 @@ const { ZENDESK_MARKET_PLACE_NAME, ZENDESK_MARKET_PLACE_ORG_ID, ZENDESK_MARKET_PLACE_APP_ID, + NAME, getBaseEndpoint } = require("./config"); const { @@ -27,8 +28,11 @@ const logger = require("../../../logger"); const { httpGET } = require("../../../adapters/network"); const { NetworkInstrumentationError, - InstrumentationError + InstrumentationError, + NetworkError } = require("../../util/errorTypes"); +const { getDynamicErrorType } = require("../../../adapters/utils/networkUtils"); +const tags = require("../../util/tags"); function responseBuilder(message, headers, payload, endpoint) { const response = defaultRequestConfig(); @@ -121,9 +125,12 @@ const payloadBuilderforUpdatingEmail = async ( } } } - logger.debug("Failed in fetching Identity details"); + logger.debug(`${NAME}:: Failed in fetching Identity details`); } catch (error) { - logger.debug("Error :", error.response ? error.response.data : error); + logger.debug( + `${NAME}:: Error :`, + error.response ? error.response.data : error + ); } return {}; }; @@ -146,11 +153,11 @@ async function createUserFields(url, config, newFields, fieldJson) { try { const response = await axios.post(url, fieldData, config); if (response.status !== 201) { - logger.debug("Failed to create User Field : ", field); + logger.debug(`${NAME}:: Failed to create User Field : `, field); } } catch (error) { if (error.response && error.response.status !== 422) { - logger.debug("Cannot create User field ", field, error); + logger.debug(`${NAME}:: Cannot create User field `, field, error); } } }); @@ -187,7 +194,10 @@ async function checkAndCreateUserFields( } } } catch (error) { - logger.debug("Error :", error.response ? error.response.data : error); + logger.debug( + `${NAME}:: Error :`, + error.response ? error.response.data : error + ); } } @@ -237,7 +247,7 @@ function getIdentifyPayload(message, category, destinationConfig, type) { const getUserIdByExternalId = async (message, headers, baseEndpoint) => { const externalId = getFieldValueFromMessage(message, "userIdOnly"); if (!externalId) { - logger.debug("externalId is required for getting zenuserId"); + logger.debug(`${NAME}:: externalId is required for getting zenuserId`); return undefined; } const url = `${baseEndpoint}users/search.json?query=${externalId}`; @@ -250,10 +260,10 @@ const getUserIdByExternalId = async (message, headers, baseEndpoint) => { const zendeskUserId = get(resp, "response.data.users.0.id"); return zendeskUserId; } - logger.debug("Failed in fetching User details"); + logger.debug(`${NAME}:: Failed in fetching User details`); } catch (error) { logger.debug( - `Cannot get userId for externalId : ${externalId}`, + `${NAME}:: Cannot get userId for externalId : ${externalId}`, error.response ); return undefined; @@ -267,7 +277,7 @@ async function getUserId(message, headers, baseEndpoint, type) { : getFieldValueFromMessage(message, "traits"); const userEmail = traits?.email || traits?.primaryEmail; if (!userEmail) { - logger.debug("Email ID is required for getting zenuserId"); + logger.debug(`${NAME}:: Email ID is required for getting zenuserId`); return undefined; } const url = `${baseEndpoint}users/search.json?query=${userEmail}`; @@ -276,11 +286,11 @@ async function getUserId(message, headers, baseEndpoint, type) { try { const resp = await axios.get(url, config); if (!resp || !resp.data || resp.data.count === 0) { - logger.debug("User not found"); + logger.debug(`${NAME}:: User not found`); return undefined; } - const zendeskUserId = resp.data.users[0].id; + const zendeskUserId = resp?.data?.users?.[0]?.id; return zendeskUserId; } catch (error) { // logger.debug( @@ -294,13 +304,16 @@ async function getUserId(message, headers, baseEndpoint, type) { async function isUserAlreadyAssociated(userId, orgId, headers, baseEndpoint) { const url = `${baseEndpoint}/users/${userId}/organization_memberships.json`; const config = { headers }; - const response = await axios.get(url, config); - if ( - response.data && - response.data.organization_memberships.length > 0 && - response.data.organization_memberships[0].organization_id === orgId - ) { - return true; + try { + const response = await axios.get(url, config); + if ( + response?.data?.organization_memberships?.[0]?.organization_id === orgId + ) { + return true; + } + } catch (error) { + logger.debug(`${NAME}:: Error :`); + logger.debug(error?.response?.data || error); } return false; } @@ -332,12 +345,12 @@ async function createUser( const resp = await axios.post(url, payload, config); if (!resp.data || !resp.data.user || !resp.data.user.id) { - logger.debug(`Couldn't create User: ${name}`); + logger.debug(`${NAME}:: Couldn't create User: ${name}`); throw new NetworkInstrumentationError("user not found"); } - const userID = resp.data.user.id; - const userEmail = resp.data.user.email; + const userID = resp?.data?.user?.id; + const userEmail = resp?.data?.user.email; return { zendeskUserId: userID, email: userEmail }; } catch (error) { logger.debug(error); @@ -431,14 +444,18 @@ async function createOrganization( const resp = await axios.post(url, payload, config); if (!resp.data || !resp.data.organization) { - logger.debug(`Couldn't create Organization: ${message.traits.name}`); + logger.debug( + `${NAME}:: Couldn't create Organization: ${message.traits.name}` + ); return undefined; } - const orgId = resp.data.organization.id; + const orgId = resp?.data?.organization?.id; return orgId; } catch (error) { - logger.debug(`Couldn't create Organization: ${message.traits.name}`); + logger.debug( + `${NAME}:: Couldn't create Organization: ${message.traits.name}` + ); return undefined; } } @@ -512,15 +529,11 @@ async function processIdentify( try { const config = { headers }; const response = await axios.get(membershipUrl, config); - if ( - response.data && - response.data.organization_memberships && - response.data.organization_memberships.length > 0 - ) { + if (response?.data?.organization_memberships?.length > 0) { if ( - orgId === response.data.organization_memberships[0].organization_id + orgId === response.data.organization_memberships[0]?.organization_id ) { - const membershipId = response.data.organization_memberships[0].id; + const membershipId = response.data.organization_memberships[0]?.id; const deleteResponse = defaultRequestConfig(); deleteResponse.endpoint = `${baseEndpoint}users/${userId}/organization_memberships/${membershipId}.json`; @@ -536,7 +549,7 @@ async function processIdentify( } } } catch (error) { - logger.debug(error); + logger.debug(`${NAME}:: ${error}`); } } } @@ -557,26 +570,40 @@ async function processTrack(message, destinationConfig, headers, baseEndpoint) { } let zendeskUserID; - let url = `${baseEndpoint}users/search.json?query=${userEmail}`; + const url = `${baseEndpoint}users/search.json?query=${userEmail}`; const config = { headers }; - const userResponse = await axios.get(url, config); - if (!get(userResponse, "data.users.0.id") || userResponse.data.count === 0) { - const { zendeskUserId, email } = await createUser( - message, - headers, - destinationConfig, - baseEndpoint - ); - if (!zendeskUserId) { - throw new NetworkInstrumentationError("User not found"); - } - if (!email) { - throw new NetworkInstrumentationError("User email not found", 400); + try { + const userResponse = await axios.get(url, config); + if ( + !get(userResponse, "data.users.0.id") || + userResponse.data.count === 0 + ) { + const { zendeskUserId, email } = await createUser( + message, + headers, + destinationConfig, + baseEndpoint + ); + if (!zendeskUserId) { + throw new NetworkInstrumentationError("User not found"); + } + if (!email) { + throw new NetworkInstrumentationError("User email not found", 400); + } + zendeskUserID = zendeskUserId; + userEmail = email; } - zendeskUserID = zendeskUserId; - userEmail = email; + zendeskUserID = zendeskUserID || userResponse?.data?.users?.[0]?.id; + } catch (error) { + throw new NetworkError( + `Failed to fetch user with email: ${userEmail} due to ${error.message}`, + error.status, + { + [tags.TAG_NAMES.ERROR_TYPE]: getDynamicErrorType(error.status) + }, + error?.response?.data || error?.response || error + ); } - zendeskUserID = zendeskUserID || userResponse.data.users[0].id; const eventObject = {}; eventObject.description = message.event; @@ -590,9 +617,14 @@ async function processTrack(message, destinationConfig, headers, baseEndpoint) { profileObject.identifiers = [{ type: "email", value: userEmail }]; const eventPayload = { event: eventObject, profile: profileObject }; - url = `${baseEndpoint}users/${zendeskUserID}/events`; + const eventEndpoint = `${baseEndpoint}users/${zendeskUserID}/events`; - const response = responseBuilder(message, headers, eventPayload, url); + const response = responseBuilder( + message, + headers, + eventPayload, + eventEndpoint + ); return response; } @@ -606,7 +638,8 @@ async function processGroup(message, destinationConfig, headers, baseEndpoint) { message, category, headers, - destinationConfig + destinationConfig, + baseEndpoint ); url = baseEndpoint + category.createEndpoint; } else { diff --git a/test/__mocks__/data/zendesk/response.json b/test/__mocks__/data/zendesk/response.json index 6cb6ba6b4f1..c5ca2f96c5b 100644 --- a/test/__mocks__/data/zendesk/response.json +++ b/test/__mocks__/data/zendesk/response.json @@ -286,5 +286,64 @@ "next_page": null, "previous_page": null, "count": 1 + }, + "https://rudderlabtest1.zendesk.com/api/v2/users/search.json?query=testemail1@email": { + "users": [], + "next_page": null, + "previous_page": null, + "count": 0 + }, + "https://rudderlabtest2.zendesk.com/api/v2/users/search.json?query=testemail2@email": { + "users": [], + "next_page": null, + "previous_page": null, + "count": 0 + }, + "https://rudderlabtest1.zendesk.com/api/v2/users/create_or_update.json": { + "user": { + "id": 900113780483, + "url": "https://rudderlabtest1.zendesk.com/api/v2/users/900113780483.json", + "name": "John Wick", + "email": "testemail1@email", + "created_at": "2020-03-17T10:21:15Z", + "updated_at": "2020-03-23T15:56:56Z", + "time_zone": "Eastern Time (US & Canada)", + "iana_time_zone": "America/New_York", + "phone": null, + "shared_phone_number": null, + "photo": null, + "locale_id": 1, + "locale": "en-US", + "organization_id": 900001329943, + "role": "end-user", + "verified": true, + "external_id": "exId-123", + "tags": [], + "alias": null, + "active": true, + "shared": false, + "shared_agent": false, + "last_login_at": null, + "two_factor_auth_enabled": false, + "signature": null, + "details": null, + "notes": null, + "role_type": null, + "custom_role_id": null, + "moderator": false, + "ticket_restriction": "requested", + "only_private_comments": false, + "restricted_agent": true, + "suspended": false, + "chat_only": false, + "default_group_id": null, + "report_csv": false, + "user_fields": { + "birthday": null + } + } + }, + "https://rudderlabtest2.zendesk.com/api/v2/users/create_or_update.json": { + "user": {} } } diff --git a/test/__tests__/data/zendesk_input.json b/test/__tests__/data/zendesk_input.json index 8f138733a1d..05a050fc57f 100644 --- a/test/__tests__/data/zendesk_input.json +++ b/test/__tests__/data/zendesk_input.json @@ -1426,5 +1426,83 @@ "name": "name_abcd144" } } + }, + { + "message": { + "anonymousId": "223b5f40-9543-4456-a7aa-945c43048185", + "channel": "web", + "context": { + "traits": { + "country": "UK", + "name": "John Wick", + "userId": "exId-123", + "email": "testemail1@email" + }, + "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" + }, + "event": "Order completed", + "messageId": "017f5227-ead3-4b7d-9794-1465022327be", + "originalTimestamp": "2021-03-25T14:36:47.695Z", + "properties": { + "category": "category", + "label": "label", + "userId": "exId-123", + "value": "value" + }, + "rudderId": "d1b1b23f-c855-4b86-bad7-091f7bbe99fe", + "type": "track", + "userId": "0000000000" + }, + "destination": { + "ID": "1qEz66UWpXTKwgCc8BPvyWPorpz", + "Name": "Zendesk", + "Config": { + "apiToken": "yPJwcLTFSsvIkFhY23SzittHoYADJQ7eKDoxNu4x", + "createUsersAsVerified": true, + "domain": "rudderlabtest1", + "email": "rudderlabtest1@email.com", + "removeUsersFromOrganization": false, + "sendGroupCallsWithoutUserId": false + } + } + }, + { + "message": { + "anonymousId": "223b5f40-9543-4456-a7aa-945c43048185", + "channel": "web", + "context": { + "traits": { + "country": "UK", + "name": "John Wick", + "userId": "exId-123", + "email": "testemail2@email" + }, + "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" + }, + "event": "Order completed", + "messageId": "017f5227-ead3-4b7d-9794-1465022327be", + "originalTimestamp": "2021-03-25T14:36:47.695Z", + "properties": { + "category": "category", + "label": "label", + "userId": "exId-123", + "value": "value" + }, + "rudderId": "d1b1b23f-c855-4b86-bad7-091f7bbe99fe", + "type": "track", + "userId": "0000000000" + }, + "destination": { + "ID": "1qEz66UWpXTKwgCc8BPvyWPorpz", + "Name": "Zendesk", + "Config": { + "apiToken": "yPJwcLTFSsvIkFhY23SzittHoYADJQ7eKDoxNu4x", + "createUsersAsVerified": true, + "domain": "rudderlabtest2", + "email": "rudderlabtest2@email.com", + "removeUsersFromOrganization": false, + "sendGroupCallsWithoutUserId": false + } + } } ] diff --git a/test/__tests__/data/zendesk_output.json b/test/__tests__/data/zendesk_output.json index 7930103d76b..3fc2d764de5 100644 --- a/test/__tests__/data/zendesk_output.json +++ b/test/__tests__/data/zendesk_output.json @@ -548,5 +548,53 @@ }, "files": {} } - ] + ], + { + "body": { + "FORM": {}, + "JSON": { + "event": { + "description": "Order completed", + "properties": { + "category": "category", + "label": "label", + "userId": "exId-123", + "value": "value" + }, + "source": "Rudder", + "type": "Order completed" + }, + "profile": { + "identifiers": [ + { + "type": "email", + "value": "testemail1@email" + } + ], + "source": "Rudder", + "type": "Order completed" + } + }, + "JSON_ARRAY": {}, + "XML": {} + }, + "endpoint": "https://rudderlabtest1.zendesk.com/api/v2/users/900113780483/events", + "files": {}, + "headers": { + "Authorization": "Basic cnVkZGVybGFidGVzdDFAZW1haWwuY29tL3Rva2VuOnlQSndjTFRGU3N2SWtGaFkyM1N6aXR0SG9ZQURKUTdlS0RveE51NHg=", + "Content-Type": "application/json", + "X-Zendesk-Marketplace-App-Id": "263241", + "X-Zendesk-Marketplace-Name": "RudderStack", + "X-Zendesk-Marketplace-Organization-Id": "3339" + }, + "method": "POST", + "params": {}, + "type": "REST", + "userId": "223b5f40-9543-4456-a7aa-945c43048185", + "version": "1" + }, + { + "statusCode": 400, + "error": "Failed to fetch user with email: testemail2@email due to Couldn't find user: John Wick" + } ] diff --git a/test/__tests__/data/zendesk_router_input.json b/test/__tests__/data/zendesk_router_input.json index af4232213a5..8c559ae7767 100644 --- a/test/__tests__/data/zendesk_router_input.json +++ b/test/__tests__/data/zendesk_router_input.json @@ -136,5 +136,54 @@ "type": "group", "userId": "abcd-124" } + }, + { + "destination": { + "Config": { + "apiToken": "yPJwcLTFSsvIkFhY23SzittHoYADJQ7eKDoxNu4x", + "createUsersAsVerified": true, + "domain": "rudderlabtest2", + "email": "rudderlabtest2@email.com", + "removeUsersFromOrganization": false, + "sendGroupCallsWithoutUserId": false + }, + "DestinationDefinition": { + "DisplayName": "Zendesk", + "ID": "1YknZ1ENqB8UurJQJE2VrEA61tr", + "Name": "ZENDESK" + }, + "Enabled": true, + "ID": "1Z3zFXE6zwvNJBVOUzCuJxeO51P", + "Name": "zendesk", + "Transformations": [] + }, + "metadata": { + "jobId": 3 + }, + "message": { + "anonymousId": "223b5f40-9543-4456-a7aa-945c43048185", + "channel": "web", + "context": { + "traits": { + "country": "UK", + "name": "John Wick", + "userId": "exId-123", + "email": "testemail2@email" + }, + "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" + }, + "event": "Order completed", + "messageId": "017f5227-ead3-4b7d-9794-1465022327be", + "originalTimestamp": "2021-03-25T14:36:47.695Z", + "properties": { + "category": "category", + "label": "label", + "userId": "exId-123", + "value": "value" + }, + "rudderId": "d1b1b23f-c855-4b86-bad7-091f7bbe99fe", + "type": "track", + "userId": "0000000000" + } } -] \ No newline at end of file +] diff --git a/test/__tests__/data/zendesk_router_output.json b/test/__tests__/data/zendesk_router_output.json index cf0107bbcb1..776ffb89422 100644 --- a/test/__tests__/data/zendesk_router_output.json +++ b/test/__tests__/data/zendesk_router_output.json @@ -117,5 +117,19 @@ "Name": "zendesk", "Transformations": [] } + }, + { + "batched": false, + "error": "Failed to fetch user with email: testemail2@email due to Couldn't find user: John Wick", + "metadata": [ + { + "jobId": 3 + } + ], + "statTags": { + "errorCategory": "network", + "errorType": "aborted" + }, + "statusCode": 400 } ]