From 05553eb5cbf3aa0afd752dca9af74e32722876ed Mon Sep 17 00:00:00 2001 From: Sandeep Digumarty Date: Thu, 13 Feb 2025 21:18:49 +0530 Subject: [PATCH 1/5] feat(http): add support for isDefaultMapping (#4073) --- .../v2/destinations/http/procWorkflow.yaml | 3 +- test/integrations/destinations/http/common.ts | 23 +++++++++ .../http/processor/configuration.ts | 50 +++++++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/cdk/v2/destinations/http/procWorkflow.yaml b/src/cdk/v2/destinations/http/procWorkflow.yaml index 1db0fce1d72..1371eded905 100644 --- a/src/cdk/v2/destinations/http/procWorkflow.yaml +++ b/src/cdk/v2/destinations/http/procWorkflow.yaml @@ -55,7 +55,8 @@ steps: - name: prepareBody template: | - const payload = $.getCustomMappings(.message, .destination.Config.propertiesMapping); + const propertiesMapping = .destination.Config.isDefaultMapping ? [{"to": "$", "from": "$"}] : .destination.Config.propertiesMapping; + const payload = $.getCustomMappings(.message, propertiesMapping); $.context.payload = $.prepareBody(payload, $.context.format, .destination.Config.xmlRootKey); - name: buildResponseForProcessTransformation diff --git a/test/integrations/destinations/http/common.ts b/test/integrations/destinations/http/common.ts index 6c2887859f3..7f9cc61048f 100644 --- a/test/integrations/destinations/http/common.ts +++ b/test/integrations/destinations/http/common.ts @@ -623,6 +623,29 @@ const destinations: Destination[] = [ Transformations: [], WorkspaceID: 'test-workspace-id', }, + { + Config: { + apiUrl: 'http://abc.com/contacts', + auth: 'noAuth', + method: 'POST', + format: 'JSON', + isBatchingEnabled: true, + maxBatchSize: '2', + isDefaultMapping: true, + propertiesMapping: [], + }, + DestinationDefinition: { + DisplayName: displayName, + ID: '123', + Name: destTypeInUpperCase, + Config: { cdkV2Enabled: true }, + }, + Enabled: true, + ID: '123', + Name: destTypeInUpperCase, + Transformations: [], + WorkspaceID: 'test-workspace-id', + }, ]; const traits = { diff --git a/test/integrations/destinations/http/processor/configuration.ts b/test/integrations/destinations/http/processor/configuration.ts index 51a28fc2abf..62676ba63de 100644 --- a/test/integrations/destinations/http/processor/configuration.ts +++ b/test/integrations/destinations/http/processor/configuration.ts @@ -530,4 +530,54 @@ export const configuration: ProcessorTestData[] = [ }, }, }, + { + id: 'http-configuration-test-11', + name: destType, + description: 'Identify call with default properties mapping', + scenario: 'Business', + successCriteria: 'Response should be in json format with default properties mapping', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + destination: destinations[14], + message: { + type: 'identify', + userId: 'userId123', + anonymousId: 'anonId123', + }, + metadata: generateMetadata(1), + }, + ], + method: 'POST', + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: transformResultBuilder({ + method: 'POST', + userId: '', + endpoint: destinations[14].Config.apiUrl, + headers: { + 'Content-Type': 'application/json', + }, + JSON: { + type: 'identify', + userId: 'userId123', + anonymousId: 'anonId123', + }, + }), + statusCode: 200, + metadata: generateMetadata(1), + }, + ], + }, + }, + }, ]; From a27e61b512568b58a599b6585c35cc842c96eb7d Mon Sep 17 00:00:00 2001 From: Dilip Kola <33080863+koladilip@users.noreply.github.com> Date: Fri, 14 Feb 2025 12:26:38 +0530 Subject: [PATCH 2/5] refactor: zoho record processing (#4054) * refactor: zoho to not asssume same action in all events of a batch * refactor: zoho to not asssume same action in all events of a batch * refactor: zoho to not asssume same action in all events of a batch * refactor: remove unused import in zoho tests * refactor: remove unused import in zoho tests * refactor: add missing tests * refactor: zoho utils using cursor * refactor: zoho utils using cursor * chore: add more tests for zoho utils * chore: add more tests for zoho utils * refactor: update zoho search record id function * refactor: update zoho format multi select fields function * refactor: add more util tests for zoho * refactor: use function to express condition * chore: add more test for zoho * chore: add more test for zoho --- src/cdk/v2/destinations/zoho/config.js | 4 +- .../v2/destinations/zoho/transformRecord.js | 17 +- src/cdk/v2/destinations/zoho/utils.js | 165 ++-- src/cdk/v2/destinations/zoho/utils.test.js | 783 +++++++++++++----- src/util/common.js | 4 + src/util/common.test.js | 151 ++++ 6 files changed, 815 insertions(+), 309 deletions(-) create mode 100644 src/util/common.test.js diff --git a/src/cdk/v2/destinations/zoho/config.js b/src/cdk/v2/destinations/zoho/config.js index d942d9e369d..0eea0c096fd 100644 --- a/src/cdk/v2/destinations/zoho/config.js +++ b/src/cdk/v2/destinations/zoho/config.js @@ -10,8 +10,8 @@ const DATA_CENTRE_BASE_ENDPOINTS_MAP = { }; const getBaseEndpoint = (dataServer) => DATA_CENTRE_BASE_ENDPOINTS_MAP[dataServer]; -const COMMON_RECORD_ENDPOINT = (dataCenter = 'US') => - `${getBaseEndpoint(dataCenter)}/crm/v6/moduleType`; +const COMMON_RECORD_ENDPOINT = (dataCenter) => + `${getBaseEndpoint(dataCenter || 'US')}/crm/v6/moduleType`; // ref: https://www.zoho.com/crm/developer/docs/api/v6/insert-records.html#:~:text=%2DX%20POST-,System%2Ddefined%20mandatory%20fields%20for%20each%20module,-While%20inserting%20records const MODULE_MANDATORY_FIELD_CONFIG = { diff --git a/src/cdk/v2/destinations/zoho/transformRecord.js b/src/cdk/v2/destinations/zoho/transformRecord.js index 8f4586e46b4..20f97cf7ab6 100644 --- a/src/cdk/v2/destinations/zoho/transformRecord.js +++ b/src/cdk/v2/destinations/zoho/transformRecord.js @@ -33,7 +33,7 @@ const responseBuilder = ( identifierType, operationModuleType, commonEndPoint, - action, + isUpsert, metadata, ) => { const { trigger, addDefaultDuplicateCheck, multiSelectFieldLevelDecision } = config; @@ -43,7 +43,7 @@ const responseBuilder = ( Authorization: `Zoho-oauthtoken ${metadata[0].secret.accessToken}`, }; - if (action === 'insert' || action === 'update') { + if (isUpsert) { const payload = { duplicate_check_fields: handleDuplicateCheck( addDefaultDuplicateCheck, @@ -70,7 +70,6 @@ const batchResponseBuilder = ( identifierType, operationModuleType, upsertEndPoint, - action, ) => { const upsertResponseArray = []; const deletionResponseArray = []; @@ -101,7 +100,7 @@ const batchResponseBuilder = ( identifierType, operationModuleType, upsertEndPoint, - action, + true, upsertmetadataChunks.items[0], ), ); @@ -115,7 +114,7 @@ const batchResponseBuilder = ( identifierType, operationModuleType, upsertEndPoint, - action, + false, deletionmetadataChunks.items[0], ), ); @@ -226,13 +225,12 @@ const handleDeletion = async ( */ const processInput = async ( input, - action, operationModuleType, Config, transformedResponseToBeBatched, errorResponseList, ) => { - const { fields } = input.message; + const { fields, action } = input.message; if (isEmptyObject(fields)) { const emptyFieldsError = new InstrumentationError('`fields` cannot be empty'); @@ -285,7 +283,6 @@ const processRecordInputs = async (inputs, destination) => { const response = []; const errorResponseList = []; const { Config } = destination; - const { action } = inputs[0].message; const transformedResponseToBeBatched = { upsertData: [], @@ -296,13 +293,12 @@ const processRecordInputs = async (inputs, destination) => { const { operationModuleType, identifierType, upsertEndPoint } = deduceModuleInfo(inputs, Config); - validateConfigurationIssue(Config, operationModuleType, action); + validateConfigurationIssue(Config, operationModuleType); await Promise.all( inputs.map((input) => processInput( input, - action, operationModuleType, Config, transformedResponseToBeBatched, @@ -322,7 +318,6 @@ const processRecordInputs = async (inputs, destination) => { identifierType, operationModuleType, upsertEndPoint, - action, ); if (upsertResponseArray.length === 0 && deletionResponseArray.length === 0) { diff --git a/src/cdk/v2/destinations/zoho/utils.js b/src/cdk/v2/destinations/zoho/utils.js index 4f5c4e86206..d86ea05e334 100644 --- a/src/cdk/v2/destinations/zoho/utils.js +++ b/src/cdk/v2/destinations/zoho/utils.js @@ -1,48 +1,54 @@ const { - MappedToDestinationKey, getHashFromArray, isDefinedAndNotNull, ConfigurationError, isDefinedAndNotNullAndNotEmpty, } = require('@rudderstack/integrations-lib'); -const get = require('get-value'); const { getDestinationExternalIDInfoForRetl, isHttpStatusSuccess } = require('../../../../v0/util'); const zohoConfig = require('./config'); const { handleHttpRequest } = require('../../../../adapters/network'); +const { CommonUtils } = require('../../../../util/common'); const deduceModuleInfo = (inputs, Config) => { - const singleRecordInput = inputs[0].message; - const operationModuleInfo = {}; - const mappedToDestination = get(singleRecordInput, MappedToDestinationKey); - if (mappedToDestination) { - const { objectType, identifierType } = getDestinationExternalIDInfoForRetl( - singleRecordInput, - 'ZOHO', - ); - operationModuleInfo.operationModuleType = objectType; - operationModuleInfo.upsertEndPoint = zohoConfig - .COMMON_RECORD_ENDPOINT(Config.region) - .replace('moduleType', objectType); - operationModuleInfo.identifierType = identifierType; + if (!Array.isArray(inputs) || inputs.length === 0) { + return {}; + } + + const firstRecord = inputs[0].message; + const mappedToDestination = firstRecord?.context?.mappedToDestination; + + if (!mappedToDestination) { + return {}; } - return operationModuleInfo; + + const { objectType, identifierType } = getDestinationExternalIDInfoForRetl(firstRecord, 'ZOHO'); + return { + operationModuleType: objectType, + upsertEndPoint: zohoConfig + .COMMON_RECORD_ENDPOINT(Config.region) + .replace('moduleType', objectType), + identifierType, + }; }; -// eslint-disable-next-line consistent-return +// Keeping the original function name and return structure function validatePresenceOfMandatoryProperties(objectName, object) { - if (zohoConfig.MODULE_MANDATORY_FIELD_CONFIG.hasOwnProperty(objectName)) { - const requiredFields = zohoConfig.MODULE_MANDATORY_FIELD_CONFIG[objectName]; - const missingFields = - requiredFields.filter( - (field) => !object.hasOwnProperty(field) || !isDefinedAndNotNullAndNotEmpty(object[field]), - ) || []; - return { status: missingFields.length > 0, missingField: missingFields }; + if (!zohoConfig.MODULE_MANDATORY_FIELD_CONFIG.hasOwnProperty(objectName)) { + return undefined; // Maintaining original undefined return for custom objects } - // No mandatory check performed for custom objects + + const requiredFields = zohoConfig.MODULE_MANDATORY_FIELD_CONFIG[objectName]; + const missingFields = requiredFields.filter( + (field) => !object.hasOwnProperty(field) || !isDefinedAndNotNullAndNotEmpty(object[field]), + ); + + return { + status: missingFields.length > 0, + missingField: missingFields, + }; } const formatMultiSelectFields = (config, fields) => { - // Convert multiSelectFieldLevelDecision array into a hash map for quick lookups const multiSelectFields = getHashFromArray( config.multiSelectFieldLevelDecision, 'from', @@ -50,32 +56,33 @@ const formatMultiSelectFields = (config, fields) => { false, ); - Object.keys(fields).forEach((eachFieldKey) => { - if (multiSelectFields.hasOwnProperty(eachFieldKey)) { - // eslint-disable-next-line no-param-reassign - fields[eachFieldKey] = [fields[eachFieldKey]]; + // Creating a shallow copy to avoid mutations + const formattedFields = { ...fields }; + + Object.keys(formattedFields).forEach((eachFieldKey) => { + if ( + multiSelectFields.hasOwnProperty(eachFieldKey) && + isDefinedAndNotNull(formattedFields[eachFieldKey]) + ) { + formattedFields[eachFieldKey] = [formattedFields[eachFieldKey]]; } }); - return fields; + + return formattedFields; }; -// Utility to handle duplicate check const handleDuplicateCheck = (addDefaultDuplicateCheck, identifierType, operationModuleType) => { - let duplicateCheckFields = [identifierType]; + let additionalFields = []; if (addDefaultDuplicateCheck) { const moduleDuplicateCheckField = zohoConfig.MODULE_WISE_DUPLICATE_CHECK_FIELD[operationModuleType]; - - if (isDefinedAndNotNull(moduleDuplicateCheckField)) { - duplicateCheckFields = [...moduleDuplicateCheckField]; - duplicateCheckFields.unshift(identifierType); - } else { - duplicateCheckFields.push('Name'); // user chosen duplicate field always carries higher priority - } + additionalFields = isDefinedAndNotNull(moduleDuplicateCheckField) + ? moduleDuplicateCheckField + : ['Name']; } - return [...new Set(duplicateCheckFields)]; + return Array.from(new Set([identifierType, ...additionalFields])); }; function escapeAndEncode(value) { @@ -93,42 +100,53 @@ function transformToURLParams(fields, Config) { return `${regionBasedEndPoint}/crm/v6/Leads/search?criteria=${criteria}`; } -// ref : https://www.zoho.com/crm/developer/docs/api/v6/search-records.html const searchRecordId = async (fields, metadata, Config) => { - const searchURL = transformToURLParams(fields, Config); - const searchResult = await handleHttpRequest( - 'get', - searchURL, - { - headers: { - Authorization: `Zoho-oauthtoken ${metadata.secret.accessToken}`, + try { + const searchURL = transformToURLParams(fields, Config); + const searchResult = await handleHttpRequest( + 'get', + searchURL, + { + headers: { + Authorization: `Zoho-oauthtoken ${metadata.secret.accessToken}`, + }, }, - }, - { - destType: 'zoho', - feature: 'deleteRecords', - requestMethod: 'GET', - endpointPath: 'crm/v6/Leads/search?criteria=', - module: 'router', - }, - ); - if (!isHttpStatusSuccess(searchResult.processedResponse.status)) { + { + destType: 'zoho', + feature: 'deleteRecords', + requestMethod: 'GET', + endpointPath: 'crm/v6/Leads/search?criteria=', + module: 'router', + }, + ); + + if (!isHttpStatusSuccess(searchResult.processedResponse.status)) { + return { + erroneous: true, + message: searchResult.processedResponse.response, + }; + } + + if ( + searchResult.processedResponse.status === 204 || + !CommonUtils.isNonEmptyArray(searchResult.processedResponse.response?.data) + ) { + return { + erroneous: true, + message: 'No contact is found with record details', + }; + } + return { - erroneous: true, - message: searchResult.processedResponse.response, + erroneous: false, + message: searchResult.processedResponse.response.data.map((record) => record.id), }; - } - if (searchResult.processedResponse.status === 204) { + } catch (error) { return { erroneous: true, - message: 'No contact is found with record details', + message: error.message, }; } - const recordIds = searchResult.processedResponse.response.data.map((record) => record.id); - return { - erroneous: false, - message: recordIds, - }; }; // ref : https://www.zoho.com/crm/developer/docs/api/v6/upsert-records.html#:~:text=The%20trigger%20input%20can%20be%20workflow%2C%20approval%2C%20or%20blueprint.%20If%20the%20trigger%20is%20not%20mentioned%2C%20the%20workflows%2C%20approvals%20and%20blueprints%20related%20to%20the%20API%20will%20get%20executed.%20Enter%20the%20trigger%20value%20as%20%5B%5D%20to%20not%20execute%20the%20workflows. @@ -142,18 +160,15 @@ const calculateTrigger = (trigger) => { return [trigger]; }; -const validateConfigurationIssue = (Config, operationModuleType, action) => { +const validateConfigurationIssue = (Config, operationModuleType) => { const hashMapMultiselect = getHashFromArray( Config.multiSelectFieldLevelDecision, 'from', 'to', false, ); - if ( - Object.keys(hashMapMultiselect).length > 0 && - Config.module !== operationModuleType && - action !== 'delete' - ) { + + if (Object.keys(hashMapMultiselect).length > 0 && Config.module !== operationModuleType) { throw new ConfigurationError( 'Object Chosen in Visual Data Mapper is not consistent with Module type selected in destination configuration. Aborting Events.', ); diff --git a/src/cdk/v2/destinations/zoho/utils.test.js b/src/cdk/v2/destinations/zoho/utils.test.js index 5a11794ef57..e959f6b6c3b 100644 --- a/src/cdk/v2/destinations/zoho/utils.test.js +++ b/src/cdk/v2/destinations/zoho/utils.test.js @@ -1,263 +1,604 @@ +jest.mock('../../../../adapters/network'); +const { ConfigurationError } = require('@rudderstack/integrations-lib'); +const { handleHttpRequest } = require('../../../../adapters/network'); const { handleDuplicateCheck, deduceModuleInfo, validatePresenceOfMandatoryProperties, - formatMultiSelectFields, validateConfigurationIssue, + formatMultiSelectFields, + transformToURLParams, + calculateTrigger, + searchRecordId, } = require('./utils'); -const { ConfigurationError } = require('@rudderstack/integrations-lib'); - describe('handleDuplicateCheck', () => { - // Returns identifierType when addDefaultDuplicateCheck is false - it('should return identifierType when addDefaultDuplicateCheck is false', () => { - const identifierType = 'email'; - const addDefaultDuplicateCheck = false; - const operationModuleType = 'Leads'; - const moduleWiseDuplicateCheckField = {}; - - const result = handleDuplicateCheck( - addDefaultDuplicateCheck, - identifierType, - operationModuleType, - moduleWiseDuplicateCheckField, - ); - - expect(result).toEqual([identifierType]); + const testCases = [ + { + name: 'should return identifierType when addDefaultDuplicateCheck is false', + input: { + identifierType: 'email', + addDefaultDuplicateCheck: false, + operationModuleType: 'Leads', + moduleWiseDuplicateCheckField: {}, + }, + expected: ['email'], + }, + { + name: 'handles valid operationModuleType and already included identifierType', + input: { + identifierType: 'Email', + addDefaultDuplicateCheck: true, + operationModuleType: 'Leads', + }, + expected: ['Email'], + }, + { + name: "should return identifierType and 'Name' when addDefaultDuplicateCheck is true and moduleDuplicateCheckField is not defined", + input: { + identifierType: 'id', + addDefaultDuplicateCheck: true, + operationModuleType: 'type3', + }, + expected: ['id', 'Name'], + }, + { + name: 'should handle null values in moduleWiseDuplicateCheckField', + input: { + identifierType: 'Identifier', + addDefaultDuplicateCheck: true, + operationModuleType: 'type1', + }, + expected: ['Identifier', 'Name'], + }, + ]; + + testCases.forEach(({ name, input, expected }) => { + it(name, () => { + const result = handleDuplicateCheck( + input.addDefaultDuplicateCheck, + input.identifierType, + input.operationModuleType, + input.moduleWiseDuplicateCheckField, + ); + expect(result).toEqual(expected); + }); }); +}); - it('Handles valid operationModuleType and already included identifierType', () => { - const identifierType = 'Email'; - const addDefaultDuplicateCheck = true; - const operationModuleType = 'Leads'; - - const result = handleDuplicateCheck( - addDefaultDuplicateCheck, - identifierType, - operationModuleType, - ); - - expect(result).toEqual(['Email']); +describe('formatMultiSelectFields', () => { + const testCases = [ + { + name: 'should convert a field value to an array if a mapping exists in multiSelectFieldLevelDecision', + input: { + config: { + multiSelectFieldLevelDecision: [{ from: 'tags', to: 'tagsArray' }], + }, + fields: { tags: 'value' }, + }, + expected: { tags: ['value'] }, + }, + { + name: 'should leave fields unchanged if mapping fields exists but null', + input: { + config: { + multiSelectFieldLevelDecision: [{ from: 'tags', to: 'tagsArray' }], + }, + fields: { tags: null, other: 'val' }, + }, + expected: { tags: null, other: 'val' }, + }, + { + name: 'should leave fields unchanged if no mapping exists', + input: { + config: { + multiSelectFieldLevelDecision: [{ from: 'categories', to: 'catArray' }], + }, + fields: { tags: 'value', other: 'val' }, + }, + expected: { tags: 'value', other: 'val' }, + }, + ]; + + testCases.forEach(({ name, input, expected }) => { + it(name, () => { + const result = formatMultiSelectFields(input.config, { ...input.fields }); + expect(result).toEqual(expected); + }); }); +}); - // Returns identifierType and 'Name' when addDefaultDuplicateCheck is true and moduleDuplicateCheckField is not defined - it("should return identifierType and 'Name' when addDefaultDuplicateCheck is true and moduleDuplicateCheckField is not defined", () => { - const identifierType = 'id'; - const operationModuleType = 'type3'; - const addDefaultDuplicateCheck = true; - - const result = handleDuplicateCheck( - addDefaultDuplicateCheck, - identifierType, - operationModuleType, - ); - - expect(result).toEqual(['id', 'Name']); +describe('transformToURLParams', () => { + const testCases = [ + { + name: 'should build a proper URL with encoded criteria based on fields and config', + input: { + fields: { First_Name: 'John, Doe', Age: '30' }, + config: { region: 'US' }, + }, + expected: `https://www.zohoapis.com/crm/v6/Leads/search?criteria=(First_Name:equals:John%5C%2C%20Doe)and(Age:equals:30)`, + }, + ]; + + testCases.forEach(({ name, input, expected }) => { + it(name, () => { + const url = transformToURLParams(input.fields, input.config); + expect(url).toEqual(expected); + }); }); +}); - // Handles null values in moduleWiseDuplicateCheckField - it('should handle null values in moduleWiseDuplicateCheckField', () => { - const addDefaultDuplicateCheck = true; - const identifierType = 'Identifier'; - const operationModuleType = 'type1'; - - const result = handleDuplicateCheck( - addDefaultDuplicateCheck, - identifierType, - operationModuleType, - ); - - expect(result).toEqual(['Identifier', 'Name']); +describe('calculateTrigger', () => { + const testCases = [ + { + name: 'should return null when trigger is "Default"', + input: 'Default', + expected: null, + }, + { + name: 'should return an empty array when trigger is "None"', + input: 'None', + expected: [], + }, + { + name: 'should return an array containing the trigger for Custom', + input: 'Custom', + expected: ['Custom'], + }, + { + name: 'should return an array containing the trigger for Approval', + input: 'Approval', + expected: ['Approval'], + }, + ]; + + testCases.forEach(({ name, input, expected }) => { + it(name, () => { + expect(calculateTrigger(input)).toEqual(expected); + }); }); }); -describe('deduceModuleInfo', () => { - const Config = { region: 'US' }; +describe('searchRecordId', () => { + const mockFields = { Email: 'test@example.com' }; + const mockMetadata = { secret: { accessToken: 'mock-token' } }; + const mockConfig = { region: 'us' }; - it('should return empty object when mappedToDestination is not present', () => { - const inputs = [{}]; - const result = deduceModuleInfo(inputs, Config); - expect(result).toEqual({}); + beforeEach(() => { + jest.clearAllMocks(); }); - it('should return operationModuleInfo when mappedToDestination is present', () => { - const inputs = [ - { - message: { - context: { - externalId: [{ type: 'ZOHO-Leads', id: '12345', identifierType: 'Email' }], - mappedToDestination: true, + const testCases = [ + { + name: 'should handle non-array response data', + response: { + processedResponse: { + status: 200, + response: { + data: 'not-an-array', + }, + }, + }, + expected: { + erroneous: true, + message: 'No contact is found with record details', + }, + }, + { + name: 'should handle missing response data property', + response: { + processedResponse: { + status: 200, + response: {}, + }, + }, + expected: { + erroneous: true, + message: 'No contact is found with record details', + }, + }, + { + name: 'should handle null response data', + response: { + processedResponse: { + status: 200, + response: { + data: null, }, }, }, - ]; + expected: { + erroneous: true, + message: 'No contact is found with record details', + }, + }, + { + name: 'should handle empty array response data', + response: { + processedResponse: { + status: 200, + response: { + data: [], + }, + }, + }, + expected: { + erroneous: true, + message: 'No contact is found with record details', + }, + }, + { + name: 'should handle valid array response data with single record', + response: { + processedResponse: { + status: 200, + response: { + data: [{ id: '123' }], + }, + }, + }, + expected: { + erroneous: false, + message: ['123'], + }, + }, + { + name: 'should handle valid array response data with multiple records', + response: { + processedResponse: { + status: 200, + response: { + data: [{ id: '123' }, { id: '456' }], + }, + }, + }, + expected: { + erroneous: false, + message: ['123', '456'], + }, + }, + { + name: 'should handle non-success HTTP status code', + response: { + processedResponse: { + status: 400, + response: 'Bad Request Error', + }, + }, + expected: { + erroneous: true, + message: 'Bad Request Error', + }, + }, + { + name: 'should handle HTTP request error', + error: new Error('Network Error'), + expected: { + erroneous: true, + message: 'Network Error', + }, + }, + ]; + + testCases.forEach(({ name, response, error, expected }) => { + it(name, async () => { + if (error) { + handleHttpRequest.mockRejectedValueOnce(error); + } else { + handleHttpRequest.mockResolvedValueOnce(response); + } + + const result = await searchRecordId(mockFields, mockMetadata, mockConfig); - const result = deduceModuleInfo(inputs, Config); - expect(result).toEqual({ - operationModuleType: 'Leads', - upsertEndPoint: 'https://www.zohoapis.com/crm/v6/Leads', - identifierType: 'Email', + expect(result).toEqual(expected); }); }); +}); - it('should handle different regions in config', () => { - const inputs = [ - { - message: { - context: { - externalId: [{ type: 'ZOHO-Leads', id: '12345', identifierType: 'Email' }], - mappedToDestination: 'true', +describe('deduceModuleInfo', () => { + const testCases = [ + { + name: 'should return empty object when mappedToDestination is not present', + input: { + inputs: [{}], + config: { region: 'US' }, + }, + expected: {}, + }, + { + name: 'should return operationModuleInfo when mappedToDestination is present', + input: { + inputs: [ + { + message: { + context: { + externalId: [{ type: 'ZOHO-Leads', id: '12345', identifierType: 'Email' }], + mappedToDestination: true, + }, + }, }, - }, + ], + config: { region: 'US' }, + }, + expected: { + operationModuleType: 'Leads', + upsertEndPoint: 'https://www.zohoapis.com/crm/v6/Leads', + identifierType: 'Email', + }, + }, + { + name: 'should handle different regions in config', + input: { + inputs: [ + { + message: { + context: { + externalId: [{ type: 'ZOHO-Leads', id: '12345', identifierType: 'Email' }], + mappedToDestination: 'true', + }, + }, + }, + ], + config: { region: 'EU' }, + }, + expected: { + operationModuleType: 'Leads', + upsertEndPoint: 'https://www.zohoapis.eu/crm/v6/Leads', + identifierType: 'Email', }, - ]; - const Config = { region: 'EU' }; + }, + { + name: 'should handle null input', + input: { + inputs: null, + config: {}, + }, + expected: {}, + }, + { + name: 'should handle undefined input', + input: { + inputs: undefined, + config: {}, + }, + expected: {}, + }, + { + name: 'should handle non-array input', + input: { + inputs: 'not an array', + config: {}, + }, + expected: {}, + }, + { + name: 'should handle empty array', + input: { + inputs: [], + config: {}, + }, + expected: {}, + }, + { + name: 'should use default US region when config.region is null', + input: { + inputs: [ + { + message: { + context: { + externalId: [{ type: 'ZOHO-Leads', id: '12345', identifierType: 'Email' }], + mappedToDestination: true, + }, + }, + }, + ], + config: { region: null }, + }, + expected: { + operationModuleType: 'Leads', + upsertEndPoint: 'https://www.zohoapis.com/crm/v6/Leads', + identifierType: 'Email', + }, + }, + { + name: 'should use default US region when config.region is undefined', + input: { + inputs: [ + { + message: { + context: { + externalId: [{ type: 'ZOHO-Leads', id: '12345', identifierType: 'Email' }], + mappedToDestination: true, + }, + }, + }, + ], + config: {}, // region is undefined + }, + expected: { + operationModuleType: 'Leads', + upsertEndPoint: 'https://www.zohoapis.com/crm/v6/Leads', + identifierType: 'Email', + }, + }, + ]; - const result = deduceModuleInfo(inputs, Config); - expect(result).toEqual({ - operationModuleType: 'Leads', - upsertEndPoint: 'https://www.zohoapis.eu/crm/v6/Leads', - identifierType: 'Email', + testCases.forEach(({ name, input, expected }) => { + it(name, () => { + const result = deduceModuleInfo(input.inputs, input.config); + expect(result).toEqual(expected); }); }); }); describe('validatePresenceOfMandatoryProperties', () => { - it('should not throw an error if the object has all required fields', () => { - const objectName = 'Leads'; - const object = { Last_Name: 'Doe' }; - - expect(() => validatePresenceOfMandatoryProperties(objectName, object)).not.toThrow(); - }); - - it('should return missing field if mandatory field contains empty string', () => { - const objectName = 'Leads'; - const object = { Last_Name: '' }; - - const result = validatePresenceOfMandatoryProperties(objectName, object); - - expect(result).toEqual({ missingField: ['Last_Name'], status: true }); - }); - - it('should return missing field if mandatory field contains empty null', () => { - const objectName = 'Leads'; - const object = { Last_Name: null }; - - const result = validatePresenceOfMandatoryProperties(objectName, object); - - expect(result).toEqual({ missingField: ['Last_Name'], status: true }); - }); - - it('should not throw an error if the objectName is not in MODULE_MANDATORY_FIELD_CONFIG', () => { - const objectName = 'CustomObject'; - const object = { Some_Field: 'Some Value' }; - - expect(() => validatePresenceOfMandatoryProperties(objectName, object)).not.toThrow(); - }); - - it('should throw an error if the object is missing multiple required fields', () => { - const objectName = 'Deals'; - const object = { Deal_Name: 'Big Deal' }; - const output = validatePresenceOfMandatoryProperties(objectName, object); - expect(output).toEqual({ - missingField: ['Stage', 'Pipeline'], - status: true, + const testCases = [ + { + name: 'should not throw an error if the object has all required fields', + input: { + objectName: 'Leads', + object: { Last_Name: 'Doe' }, + }, + expected: { missingField: [], status: false }, + expectError: false, + }, + { + name: 'should return missing field if mandatory field contains empty string', + input: { + objectName: 'Leads', + object: { Last_Name: '' }, + }, + expected: { missingField: ['Last_Name'], status: true }, + expectError: false, + }, + { + name: 'should return missing field if mandatory field contains empty null', + input: { + objectName: 'Leads', + object: { Last_Name: null }, + }, + expected: { missingField: ['Last_Name'], status: true }, + expectError: false, + }, + { + name: 'should not throw an error if the objectName is not in MODULE_MANDATORY_FIELD_CONFIG', + input: { + objectName: 'CustomObject', + object: { Some_Field: 'Some Value' }, + }, + expected: undefined, + expectError: false, + }, + { + name: 'should return multiple missing fields for Deals', + input: { + objectName: 'Deals', + object: { Deal_Name: 'Big Deal' }, + }, + expected: { + missingField: ['Stage', 'Pipeline'], + status: true, + }, + expectError: false, + }, + { + name: 'should not throw an error if the object has all required fields for Deals', + input: { + objectName: 'Deals', + object: { Deal_Name: 'Big Deal', Stage: 'Negotiation', Pipeline: 'Sales' }, + }, + expected: { missingField: [], status: false }, + expectError: false, + }, + ]; + + testCases.forEach(({ name, input, expected, expectError }) => { + it(name, () => { + if (expectError) { + expect(() => + validatePresenceOfMandatoryProperties(input.objectName, input.object), + ).toThrow(); + } else { + const result = validatePresenceOfMandatoryProperties(input.objectName, input.object); + expect(result).toEqual(expected); + } }); }); - - it('should not throw an error if the object has all required fields for Deals', () => { - const objectName = 'Deals'; - const object = { Deal_Name: 'Big Deal', Stage: 'Negotiation', Pipeline: 'Sales' }; - - expect(() => validatePresenceOfMandatoryProperties(objectName, object)).not.toThrow(); - }); }); describe('validateConfigurationIssue', () => { - test('should throw ConfigurationError when hashMapMultiselect is not empty, Config.module is different from operationModuleType, and action is not delete', () => { - const Config = { - multiSelectFieldLevelDecision: [{ from: 'field1', to: 'true' }], - module: 'moduleA', - }; - const operationModuleType = 'moduleB'; - const action = 'create'; - - expect(() => validateConfigurationIssue(Config, operationModuleType, action)).toThrow( - ConfigurationError, - ); - expect(() => validateConfigurationIssue(Config, operationModuleType, action)).toThrow( - 'Object Chosen in Visual Data Mapper is not consistent with Module type selected in destination configuration. Aborting Events.', - ); - }); - - test('should not throw an error when hashMapMultiselect is not empty, Config.module is the same as operationModuleType, and action is not delete', () => { - const Config = { - multiSelectFieldLevelDecision: [{ from: 'field1', to: 'true' }], - module: 'moduleA', - }; - const operationModuleType = 'moduleA'; - const action = 'create'; - - expect(() => validateConfigurationIssue(Config, operationModuleType, action)).not.toThrow(); - }); - - test('should not throw an error when hashMapMultiselect is empty, Config.module is different from operationModuleType, and action is not delete', () => { - const Config = { - multiSelectFieldLevelDecision: [], - module: 'moduleA', - }; - const operationModuleType = 'moduleB'; - const action = 'create'; - - expect(() => validateConfigurationIssue(Config, operationModuleType, action)).not.toThrow(); - }); - - test('should not throw an error when hashMapMultiselect is empty, Config.module is the same as operationModuleType, and action is not delete', () => { - const Config = { - multiSelectFieldLevelDecision: [], - module: 'moduleA', - }; - const operationModuleType = 'moduleA'; - const action = 'create'; - - expect(() => validateConfigurationIssue(Config, operationModuleType, action)).not.toThrow(); - }); - - test('should not throw an error when multiSelectFieldLevelDecision has entries without from key', () => { - const Config = { - multiSelectFieldLevelDecision: [{ to: 'true' }], - module: 'moduleA', - }; - const operationModuleType = 'moduleB'; - const action = 'create'; - - expect(() => validateConfigurationIssue(Config, operationModuleType, action)).not.toThrow(); - }); - - test('should throw ConfigurationError when multiSelectFieldLevelDecision has mixed case from keys, Config.module is different from operationModuleType, and action is not delete', () => { - const Config = { - multiSelectFieldLevelDecision: [ - { from: 'FIELD1', to: 'true' }, - { from: 'field2', to: 'false' }, - ], - module: 'moduleA', - }; - const operationModuleType = 'moduleB'; - const action = 'create'; - - expect(() => validateConfigurationIssue(Config, operationModuleType, action)).toThrow( - ConfigurationError, - ); - }); - - test('should not throw an error when hashMapMultiselect is not empty, Config.module is different from operationModuleType, and action is delete', () => { - const Config = { - multiSelectFieldLevelDecision: [{ from: 'field1', to: 'true' }], - module: 'moduleA', - }; - const operationModuleType = 'moduleB'; - const action = 'delete'; - - expect(() => validateConfigurationIssue(Config, operationModuleType, action)).not.toThrow(); + const testCases = [ + { + name: 'should throw ConfigurationError when hashMapMultiselect is not empty, Config.module is different from operationModuleType, and action is not delete', + input: { + config: { + multiSelectFieldLevelDecision: [{ from: 'field1', to: 'true' }], + module: 'moduleA', + }, + operationModuleType: 'moduleB', + }, + expectError: true, + errorType: ConfigurationError, + errorMessage: + 'Object Chosen in Visual Data Mapper is not consistent with Module type selected in destination configuration. Aborting Events.', + }, + { + name: 'should not throw an error when hashMapMultiselect is not empty, Config.module is the same as operationModuleType', + input: { + config: { + multiSelectFieldLevelDecision: [{ from: 'field1', to: 'true' }], + module: 'moduleA', + }, + operationModuleType: 'moduleA', + }, + expectError: false, + }, + { + name: 'should not throw an error when hashMapMultiselect is empty, Config.module is different from operationModuleType', + input: { + config: { + multiSelectFieldLevelDecision: [], + module: 'moduleA', + }, + operationModuleType: 'moduleB', + }, + expectError: false, + }, + { + name: 'should not throw an error when hashMapMultiselect is empty, Config.module is the same as operationModuleType', + input: { + config: { + multiSelectFieldLevelDecision: [], + module: 'moduleA', + }, + operationModuleType: 'moduleA', + }, + expectError: false, + }, + { + name: 'should not throw an error when multiSelectFieldLevelDecision has entries without from key', + input: { + config: { + multiSelectFieldLevelDecision: [{ to: 'true' }], + module: 'moduleA', + }, + operationModuleType: 'moduleB', + }, + expectError: false, + }, + { + name: 'should throw ConfigurationError when multiSelectFieldLevelDecision has mixed case from keys, Config.module is different from operationModuleType', + input: { + config: { + multiSelectFieldLevelDecision: [ + { from: 'FIELD1', to: 'true' }, + { from: 'field2', to: 'false' }, + ], + module: 'moduleA', + }, + operationModuleType: 'moduleB', + }, + expectError: true, + errorType: ConfigurationError, + errorMessage: + 'Object Chosen in Visual Data Mapper is not consistent with Module type selected in destination configuration. Aborting Events.', + }, + ]; + + testCases.forEach(({ name, input, expectError, errorType, errorMessage }) => { + it(name, () => { + if (expectError) { + expect(() => validateConfigurationIssue(input.config, input.operationModuleType)).toThrow( + errorType, + ); + expect(() => validateConfigurationIssue(input.config, input.operationModuleType)).toThrow( + errorMessage, + ); + } else { + expect(() => + validateConfigurationIssue(input.config, input.operationModuleType), + ).not.toThrow(); + } + }); }); }); diff --git a/src/util/common.js b/src/util/common.js index 8bf34f2eca2..5e601cd8ed3 100644 --- a/src/util/common.js +++ b/src/util/common.js @@ -24,6 +24,10 @@ const CommonUtils = { setDiff(mainSet, comparisionSet) { return [...mainSet].filter((item) => !comparisionSet.has(item)); }, + + isNonEmptyArray(array) { + return Array.isArray(array) && array.length > 0; + }, }; module.exports = { diff --git a/src/util/common.test.js b/src/util/common.test.js new file mode 100644 index 00000000000..0259c5411e8 --- /dev/null +++ b/src/util/common.test.js @@ -0,0 +1,151 @@ +const { CommonUtils } = require('./common'); + +describe('CommonUtils', () => { + describe('isNonEmptyArray', () => { + const testCases = [ + { name: 'array with numbers', input: [1, 2, 3], expected: true }, + { name: 'array with single string', input: ['a'], expected: true }, + { name: 'array with object', input: [{}], expected: true }, + { name: 'empty array', input: [], expected: false }, + { name: 'null', input: null, expected: false }, + { name: 'undefined', input: undefined, expected: false }, + { name: 'number', input: 42, expected: false }, + { name: 'string', input: 'string', expected: false }, + { name: 'object', input: {}, expected: false }, + { name: 'boolean', input: false, expected: false }, + ]; + + test.each(testCases)('$name', ({ input, expected }) => { + expect(CommonUtils.isNonEmptyArray(input)).toBe(expected); + }); + }); + + describe('objectDiff', () => { + const testCases = [ + { + name: 'different values in flat objects', + obj1: { a: 1, b: 2 }, + obj2: { a: 1, b: 3 }, + expected: { b: [2, 3] }, + }, + { + name: 'nested objects with differences', + obj1: { a: { b: 1, c: 2 }, d: 3 }, + obj2: { a: { b: 1, c: 3 }, d: 3 }, + expected: { 'a.c': [2, 3] }, + }, + { + name: 'missing keys in second object', + obj1: { a: 1, b: 2 }, + obj2: { a: 1 }, + expected: { b: [2, undefined] }, + }, + { + name: 'missing keys in first object', + obj1: { a: 1 }, + obj2: { a: 1, b: 2 }, + expected: { b: [undefined, 2] }, + }, + { + name: 'null inputs', + obj1: null, + obj2: { a: 1 }, + expected: { a: [undefined, 1] }, + }, + { + name: 'empty objects', + obj1: {}, + obj2: {}, + expected: {}, + }, + ]; + + test.each(testCases)('$name', ({ obj1, obj2, expected }) => { + expect(CommonUtils.objectDiff(obj1, obj2)).toEqual(expected); + }); + }); + + describe('toArray', () => { + const testCases = [ + { + name: 'existing array remains unchanged', + input: [1, 2, 3], + expected: [1, 2, 3], + }, + { + name: 'single number becomes array', + input: 42, + expected: [42], + }, + { + name: 'string becomes array', + input: 'test', + expected: ['test'], + }, + { + name: 'object becomes array', + input: { a: 1 }, + expected: [{ a: 1 }], + }, + { + name: 'null becomes array', + input: null, + expected: [null], + }, + { + name: 'undefined becomes array', + input: undefined, + expected: [undefined], + }, + ]; + + test.each(testCases)('$name', ({ input, expected }) => { + expect(CommonUtils.toArray(input)).toEqual(expected); + }); + }); + + describe('setDiff', () => { + const testCases = [ + { + name: 'sets with different elements', + mainSet: new Set([1, 2, 3]), + comparisonSet: new Set([2, 3, 4]), + expected: [1], + }, + { + name: 'identical sets', + mainSet: new Set([1, 2, 3]), + comparisonSet: new Set([1, 2, 3]), + expected: [], + }, + { + name: 'completely different sets', + mainSet: new Set([1, 2, 3]), + comparisonSet: new Set([4, 5, 6]), + expected: [1, 2, 3], + }, + { + name: 'empty comparison set', + mainSet: new Set([1, 2, 3]), + comparisonSet: new Set([]), + expected: [1, 2, 3], + }, + { + name: 'empty main set', + mainSet: new Set([]), + comparisonSet: new Set([1, 2, 3]), + expected: [], + }, + { + name: 'both empty sets', + mainSet: new Set([]), + comparisonSet: new Set([]), + expected: [], + }, + ]; + + test.each(testCases)('$name', ({ mainSet, comparisonSet, expected }) => { + expect(CommonUtils.setDiff(mainSet, comparisonSet)).toEqual(expected); + }); + }); +}); From 711e18bfcec1518830076bbe7e49ba7b2f2e3757 Mon Sep 17 00:00:00 2001 From: Manish Kumar <144022547+manish339k@users.noreply.github.com> Date: Mon, 17 Feb 2025 10:00:12 +0530 Subject: [PATCH 3/5] fix: content_price should be a number with decimal point in twitter ads (#4075) * fix: content_price should be a numeric string with float point in twitter ads * fix: minor change * fix: added test and some minor change * chore: update twitter_ads transform.js * Revert "chore: update twitter_ads transform.js" This reverts commit cc173f4d4f09cb7cdfa57a0cd0fed3cc6107ab74. --------- Co-authored-by: Dilip Kola <33080863+koladilip@users.noreply.github.com> Co-authored-by: Dilip Kola --- src/v0/destinations/twitter_ads/transform.js | 6 +- .../twitter_ads/processor/data.ts | 133 +++++++++++++++++- .../destinations/twitter_ads/router/data.ts | 4 +- 3 files changed, 135 insertions(+), 8 deletions(-) diff --git a/src/v0/destinations/twitter_ads/transform.js b/src/v0/destinations/twitter_ads/transform.js index 4258935bfbb..74bb1fc4f34 100644 --- a/src/v0/destinations/twitter_ads/transform.js +++ b/src/v0/destinations/twitter_ads/transform.js @@ -100,7 +100,11 @@ function populateContents(requestJson) { transformed.content_type = type; } if (price && Number.isFinite(parseFloat(price))) { - transformed.content_price = parseFloat(price); + const parsedPrice = parseFloat(price); + // content_price should be a string + transformed.content_price = Number.isInteger(parsedPrice) + ? parsedPrice.toFixed(2) + : parsedPrice.toString(); } if (quantity && Number.isFinite(parseInt(quantity, 10))) { transformed.num_items = parseInt(quantity, 10); diff --git a/test/integrations/destinations/twitter_ads/processor/data.ts b/test/integrations/destinations/twitter_ads/processor/data.ts index 8c622f22393..61a03fc64b4 100644 --- a/test/integrations/destinations/twitter_ads/processor/data.ts +++ b/test/integrations/destinations/twitter_ads/processor/data.ts @@ -137,12 +137,12 @@ export const data = [ contents: [ { content_id: '12', - content_price: 123.3345, + content_price: '123.3345', num_items: 12, }, { content_id: '4', - content_price: 200, + content_price: '200.00', num_items: 11, }, ], @@ -851,12 +851,12 @@ export const data = [ contents: [ { content_id: '12', - content_price: 123.3345, + content_price: '123.3345', num_items: 12, }, { content_id: '4', - content_price: 200, + content_price: '200.00', num_items: 11, }, ], @@ -1883,7 +1883,7 @@ export const data = [ content_id: '13', content_name: 'Product 2', content_type: 'digital', - content_price: 200, + content_price: '200.00', }, { content_id: '14', @@ -2277,6 +2277,129 @@ export const data = [ }, }, }, + { + name: 'twitter_ads', + description: 'Test case for content transformations with invalid content price', + feature: 'processor', + module: 'destination', + version: 'v0', + input: { + request: { + body: [ + { + message: { + type: 'track', + event: 'ABC Searched', + timestamp: '2020-08-14T05:30:30.118Z', + properties: { + email: 'abc@ax.com', + contents: [ + { + // invalid price + id: '12', + price: 'random-price-string', + name: 'Product 1', + type: 'physical', + groupId: 'group1', + }, + { + // valid price + id: '13', + price: '0', + name: 'Product 2', + type: 'digital', + }, + ], + }, + }, + metadata: { + secret: { + consumerKey: 'qwe', + consumerSecret: 'fdghv', + accessToken: 'dummyAccessToken', + accessTokenSecret: 'testAccessTokenSecret', + }, + }, + destination: { + Config: { + pixelId: 'dummyPixelId', + twitterAdsEventNames: [ + { + rudderEventName: 'ABC Searched', + twitterEventId: 'tw-234234324234', + }, + ], + }, + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + output: { + version: '1', + type: 'REST', + method: 'POST', + endpoint: 'https://ads-api.twitter.com/12/measurement/conversions/dummyPixelId', + headers: { + Authorization: authHeaderConstant, + 'Content-Type': 'application/json', + }, + params: {}, + body: { + JSON: { + conversions: [ + { + conversion_time: '2020-08-14T05:30:30.118Z', + event_id: 'tw-234234324234', + identifiers: [ + { + hashed_email: + '4c3c8a8cba2f3bb1e9e617301f85d1f68e816a01c7b716f482f2ab9adb8181fb', + }, + ], + contents: [ + { + content_id: '12', + content_name: 'Product 1', + content_type: 'physical', + content_group_id: 'group1', + }, + { + content_id: '13', + content_name: 'Product 2', + content_type: 'digital', + content_price: '0.00', + }, + ], + }, + ], + }, + JSON_ARRAY: {}, + XML: {}, + FORM: {}, + }, + files: {}, + userId: '', + }, + metadata: { + secret: { + consumerKey: 'qwe', + consumerSecret: 'fdghv', + accessToken: 'dummyAccessToken', + accessTokenSecret: 'testAccessTokenSecret', + }, + }, + statusCode: 200, + }, + ], + }, + }, + }, ].map((tc) => ({ ...tc, mockFns: (_) => { diff --git a/test/integrations/destinations/twitter_ads/router/data.ts b/test/integrations/destinations/twitter_ads/router/data.ts index f1fe2a10e66..900387a654b 100644 --- a/test/integrations/destinations/twitter_ads/router/data.ts +++ b/test/integrations/destinations/twitter_ads/router/data.ts @@ -127,8 +127,8 @@ export const data = [ conversions: [ { contents: [ - { content_id: '12', content_price: 123.3345, num_items: 12 }, - { content_id: '4', content_price: 200, num_items: 11 }, + { content_id: '12', content_price: '123.3345', num_items: 12 }, + { content_id: '4', content_price: '200.00', num_items: 11 }, ], conversion_id: '213123', conversion_time: '2023-06-01T06:03:08.739Z', From 3aa2290ab73db13111af0471e070fd6b03774f15 Mon Sep 17 00:00:00 2001 From: Sandeep Digumarty Date: Mon, 17 Feb 2025 14:37:01 +0530 Subject: [PATCH 4/5] chore: now fetching docker hub token from DOCKERHUB_TOKEN instead of DOCKERHUB_PROD_TOKEN (#4081) * chore: now fetching docker hub token from DOCKERHUB_TOKEN instead of DOCKERHUB_PROD_TOKEN * chore: updated all references of DOCKERHUB_PROD_TOKEN to DOCKERHUB_TOKEN --- .github/workflows/build-pr-artifacts.yml | 4 ++-- .github/workflows/build-push-docker-image.yml | 8 ++++---- .github/workflows/prepare-for-dev-deploy.yml | 4 ++-- .github/workflows/prepare-for-prod-dt-deploy.yml | 2 +- .github/workflows/prepare-for-prod-ut-deploy.yml | 2 +- .github/workflows/prepare-for-staging-deploy.yml | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/build-pr-artifacts.yml b/.github/workflows/build-pr-artifacts.yml index 46670fba846..9053bac5221 100644 --- a/.github/workflows/build-pr-artifacts.yml +++ b/.github/workflows/build-pr-artifacts.yml @@ -53,7 +53,7 @@ jobs: load_target: development push_target: production secrets: - DOCKERHUB_PROD_TOKEN: ${{ secrets.DOCKERHUB_PROD_TOKEN }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} build-user-transformer-image: name: Build User Transformer Docker Image - PR @@ -69,4 +69,4 @@ jobs: load_target: development push_target: production secrets: - DOCKERHUB_PROD_TOKEN: ${{ secrets.DOCKERHUB_PROD_TOKEN }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/build-push-docker-image.yml b/.github/workflows/build-push-docker-image.yml index e0571a24f87..4a4f28dfa95 100644 --- a/.github/workflows/build-push-docker-image.yml +++ b/.github/workflows/build-push-docker-image.yml @@ -33,7 +33,7 @@ on: workflow_url: type: string secrets: - DOCKERHUB_PROD_TOKEN: + DOCKERHUB_TOKEN: required: true env: @@ -101,7 +101,7 @@ jobs: uses: docker/login-action@v3.3.0 with: username: ${{ env.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PROD_TOKEN }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3.7.1 @@ -154,7 +154,7 @@ jobs: uses: docker/login-action@v3.3.0 with: username: ${{ env.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PROD_TOKEN }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Setup Docker Buildx uses: docker/setup-buildx-action@v3.7.1 @@ -202,7 +202,7 @@ jobs: uses: docker/login-action@v3.3.0 with: username: ${{ env.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_PROD_TOKEN }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3.7.1 diff --git a/.github/workflows/prepare-for-dev-deploy.yml b/.github/workflows/prepare-for-dev-deploy.yml index b5482bcddf1..853e2c47dcc 100644 --- a/.github/workflows/prepare-for-dev-deploy.yml +++ b/.github/workflows/prepare-for-dev-deploy.yml @@ -62,7 +62,7 @@ jobs: push_target: production use_merge_sha: true secrets: - DOCKERHUB_PROD_TOKEN: ${{ secrets.DOCKERHUB_PROD_TOKEN }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} build-user-transformer-image: name: Build User Transformer Docker Image - Dev @@ -78,4 +78,4 @@ jobs: load_target: development push_target: production secrets: - DOCKERHUB_PROD_TOKEN: ${{ secrets.DOCKERHUB_PROD_TOKEN }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/prepare-for-prod-dt-deploy.yml b/.github/workflows/prepare-for-prod-dt-deploy.yml index 56fda56b708..c3a9bd1a7e5 100644 --- a/.github/workflows/prepare-for-prod-dt-deploy.yml +++ b/.github/workflows/prepare-for-prod-dt-deploy.yml @@ -60,7 +60,7 @@ jobs: use_merge_sha: true skip_tests: ${{startsWith(github.event.pull_request.head.ref, 'hotfix-release/')}} secrets: - DOCKERHUB_PROD_TOKEN: ${{ secrets.DOCKERHUB_PROD_TOKEN }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} create-pull-request: name: Update Helm Charts For Production and Create Pull Request diff --git a/.github/workflows/prepare-for-prod-ut-deploy.yml b/.github/workflows/prepare-for-prod-ut-deploy.yml index 4c108b23d64..a2240673603 100644 --- a/.github/workflows/prepare-for-prod-ut-deploy.yml +++ b/.github/workflows/prepare-for-prod-ut-deploy.yml @@ -64,7 +64,7 @@ jobs: use_merge_sha: true skip_tests: ${{startsWith(github.event.pull_request.head.ref, 'hotfix-release/')}} secrets: - DOCKERHUB_PROD_TOKEN: ${{ secrets.DOCKERHUB_PROD_TOKEN }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} create-pull-request: name: Update Helm Charts For Production and Create Pull Request diff --git a/.github/workflows/prepare-for-staging-deploy.yml b/.github/workflows/prepare-for-staging-deploy.yml index cdf27f78327..ab1e39c1770 100644 --- a/.github/workflows/prepare-for-staging-deploy.yml +++ b/.github/workflows/prepare-for-staging-deploy.yml @@ -54,7 +54,7 @@ jobs: push_target: production use_merge_sha: true secrets: - DOCKERHUB_PROD_TOKEN: ${{ secrets.DOCKERHUB_PROD_TOKEN }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} build-user-transformer-image: name: Build User Transformer Docker Image - Staging @@ -71,7 +71,7 @@ jobs: load_target: development push_target: production secrets: - DOCKERHUB_PROD_TOKEN: ${{ secrets.DOCKERHUB_PROD_TOKEN }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} create-pull-request: name: Update Helm Charts For Staging and Create Pull Request From c272267d2c46beaf37d043ab9448b9698053ecee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Feb 2025 15:03:43 +0530 Subject: [PATCH 5/5] chore(deps): bump koa from 2.15.3 to 2.15.4 (#4071) --- package-lock.json | 9 +++++---- package.json | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index be9e161d3ed..96393c9e4eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "json-diff": "^1.0.3", "json-size": "^1.0.0", "jsontoxml": "^1.0.1", - "koa": "^2.15.3", + "koa": "^2.15.4", "koa-bodyparser": "^4.4.0", "koa2-swagger-ui": "^5.7.0", "libphonenumber-js": "^1.11.18", @@ -16560,9 +16560,10 @@ } }, "node_modules/koa": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz", - "integrity": "sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==", + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.4.tgz", + "integrity": "sha512-7fNBIdrU2PEgLljXoPWoyY4r1e+ToWCmzS/wwMPbUNs7X+5MMET1ObhJBlUkF5uZG9B6QhM2zS1TsH6adegkiQ==", + "license": "MIT", "dependencies": { "accepts": "^1.3.5", "cache-content-type": "^1.0.0", diff --git a/package.json b/package.json index 1cd28a28b76..c4c6e74ff98 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "json-diff": "^1.0.3", "json-size": "^1.0.0", "jsontoxml": "^1.0.1", - "koa": "^2.15.3", + "koa": "^2.15.4", "koa-bodyparser": "^4.4.0", "koa2-swagger-ui": "^5.7.0", "libphonenumber-js": "^1.11.18",