From 8bb7426b9d465c6383029ffaa1b06b5e7840d2c7 Mon Sep 17 00:00:00 2001 From: Dan Date: Fri, 10 Jan 2025 18:11:51 +0530 Subject: [PATCH 1/2] feat: identify and track for loops --- .../v2/destinations/loops/procWorkflow.yaml | 65 ++++ .../destinations/loops/processor/data.ts | 360 ++++++++++++++++++ 2 files changed, 425 insertions(+) create mode 100644 src/cdk/v2/destinations/loops/procWorkflow.yaml create mode 100644 test/integrations/destinations/loops/processor/data.ts diff --git a/src/cdk/v2/destinations/loops/procWorkflow.yaml b/src/cdk/v2/destinations/loops/procWorkflow.yaml new file mode 100644 index 0000000000..6dc76b39a4 --- /dev/null +++ b/src/cdk/v2/destinations/loops/procWorkflow.yaml @@ -0,0 +1,65 @@ +bindings: + - name: EventType + path: ../../../../constants + - path: ../../bindings/jsontemplate + exportAll: true + - name: removeUndefinedAndNullValues + path: ../../../../v0/util + +steps: + - name: messageType + template: | + .message.type.toLowerCase(); + + - name: validateInput + template: | + $.assert(.message.type, "message Type is not present. Aborting message."); + $.assert(.message.type in {{$.EventType.([.TRACK, .IDENTIFY])}}, + "message type " + .message.type + " is not supported"); + + - name: validateIdentifyEmail + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} + template: | + console.log(.message.context.traits); + $.assert(.message.context.traits.email, "email is required. Aborting"); + + - name: validateTrackIdentifier + condition: $.outputs.messageType === {{$.EventType.TRACK}} + template: | + const userId = .message.({{{{$.getGenericPaths("userIdOnly")}}}}); + $.assert(.message.context.traits?.email || userId, + "Either email or userId is required. Aborting"); + + - name: validateEventName + condition: $.outputs.messageType === {{$.EventType.TRACK}} + template: | + $.assert(.message.event, "event is required for track call") + + - name: prepareContext + template: | + $.context.messageType = .message.type.toLowerCase(); + $.context.payload = {}; + $.context.finalHeaders = { + "authorization": "Bearer " + .destination.Config.apiKey, + "content-type": "application/json" + }; + + - name: identifyPayload + condition: $.outputs.messageType === {{$.EventType.IDENTIFY}} + template: | + $.context.endpoint = "https://app.loops.so/api/v1/contacts/update"; + const payload = {} + Object.assign(payload, .message.context.traits); + payload.userId = .message.userId; + $.context.payload = payload; + + - name: trackPayload + condition: $.outputs.messageType === {{$.EventType.TRACK}} + template: | + $.context.endpoint = "https://app.loops.so/api/v1/events/send"; + const payload = {} + Object.assign(payload, .message.context.traits); + payload.userId = .message.userId; + payload.eventName = .message.event; + payload.eventProperties = .message.properties; + $.context.payload = payload; diff --git a/test/integrations/destinations/loops/processor/data.ts b/test/integrations/destinations/loops/processor/data.ts new file mode 100644 index 0000000000..53af4e9639 --- /dev/null +++ b/test/integrations/destinations/loops/processor/data.ts @@ -0,0 +1,360 @@ +export const data = [ + { + name: 'loops', + description: 'Identify event with traits', + feature: 'processor', + module: 'destination', + version: 'v2', + input: { + request: { + body: [ + { + message: { + userId: 'dummy-user001', + channel: 'web', + context: { + traits: { + email: 'dummyuser@domain.com', + firstName: 'Bob', + lastName: 'Brown', + phone: '099-999-9999', + }, + }, + timestamp: '2020-01-27T17:50:57.109+05:30', + type: 'identify', + }, + destination: { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'Loops', + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiKey: 'dummyApiKey', + }, + Enabled: true, + Transformations: [], + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + output: { + email: 'dummyuser@domain.com', + firstName: 'Bob', + lastName: 'Brown', + phone: '099-999-9999', + userId: 'dummy-user001', + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'loops', + description: 'Identify event with missing email trait', + feature: 'processor', + module: 'destination', + version: 'v2', + input: { + request: { + body: [ + { + message: { + userId: 'dummy-user001', + channel: 'web', + context: { + traits: { + firstName: 'Bob', + lastName: 'Brown', + phone: '099-999-9999', + }, + }, + timestamp: '2020-01-27T17:50:57.109+05:30', + type: 'identify', + }, + destination: { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'Loops', + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiKey: 'dummyApiKey', + }, + Enabled: true, + Transformations: [], + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'email is required. Aborting: Workflow: procWorkflow, Step: validateIdentifyEmail, ChildStep: undefined, OriginalError: email is required. Aborting', + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + statTags: { + destType: 'LOOPS', + destinationId: 'destId', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + workspaceId: 'wspId', + }, + statusCode: 400, + }, + ], + }, + }, + }, + { + name: 'loops', + description: 'Track event with traits', + feature: 'processor', + module: 'destination', + version: 'v2', + input: { + request: { + body: [ + { + message: { + userId: 'dummy-user001', + channel: 'web', + event: 'signup', + properties: { + subscriptionStatus: 'trial', + plan: null, + }, + timestamp: '2020-01-27T17:50:57.109+05:30', + type: 'track', + context: { + traits: { + email: 'dummyuser@domain.com', + firstName: 'Bob', + lastName: 'Brown', + phone: '099-999-9999', + }, + }, + }, + destination: { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'Loops', + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiKey: 'dummyApiKey', + }, + Enabled: true, + Transformations: [], + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + output: { + email: 'dummyuser@domain.com', + firstName: 'Bob', + lastName: 'Brown', + phone: '099-999-9999', + userId: 'dummy-user001', + eventName: 'signup', + eventProperties: { + subscriptionStatus: 'trial', + plan: null, + }, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'loops', + description: 'Track event with no email in traits', + feature: 'processor', + module: 'destination', + version: 'v2', + input: { + request: { + body: [ + { + message: { + userId: 'dummy-user001', + channel: 'web', + event: 'signup', + properties: { + subscriptionStatus: 'trial', + plan: null, + }, + timestamp: '2020-01-27T17:50:57.109+05:30', + type: 'track', + context: { + traits: {}, + }, + }, + destination: { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'Loops', + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiKey: 'dummyApiKey', + }, + Enabled: true, + Transformations: [], + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + output: { + userId: 'dummy-user001', + eventName: 'signup', + eventProperties: { + subscriptionStatus: 'trial', + plan: null, + }, + }, + statusCode: 200, + }, + ], + }, + }, + }, + { + name: 'loops', + description: 'Track event with missing email and userId', + feature: 'processor', + module: 'destination', + version: 'v2', + input: { + request: { + body: [ + { + message: { + userId: 'dummy-user001', + channel: 'web', + event: 'signup', + properties: { + subscriptionStatus: 'trial', + plan: null, + }, + timestamp: '2020-01-27T17:50:57.109+05:30', + type: 'track', + context: {}, + }, + destination: { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'Loops', + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiKey: 'dummyApiKey', + }, + Enabled: true, + Transformations: [], + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + error: + 'Either email or userId is required. Aborting: Workflow: procWorkflow, Step: validateTrackIdentifier, ChildStep: undefined, OriginalError: Either email or userId is required. Aborting', + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + statTags: { + destType: 'LOOPS', + destinationId: 'destId', + errorCategory: 'dataValidation', + errorType: 'instrumentation', + feature: 'processor', + implementation: 'cdkV2', + module: 'destination', + workspaceId: 'wspId', + }, + statusCode: 400, + }, + ], + }, + }, + }, +]; From 86eef8e95b907bd14480c8ef3a8dcfdfdadd73e9 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 13 Jan 2025 13:46:17 +0530 Subject: [PATCH 2/2] feat: test for mailing lists --- .../destinations/loops/processor/data.ts | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/test/integrations/destinations/loops/processor/data.ts b/test/integrations/destinations/loops/processor/data.ts index 53af4e9639..e24d51f443 100644 --- a/test/integrations/destinations/loops/processor/data.ts +++ b/test/integrations/destinations/loops/processor/data.ts @@ -67,6 +67,82 @@ export const data = [ }, }, }, + { + name: 'loops', + description: 'Identify event with mailing lists', + feature: 'processor', + module: 'destination', + version: 'v2', + input: { + request: { + body: [ + { + message: { + userId: 'dummy-user001', + channel: 'web', + context: { + traits: { + email: 'dummyuser@domain.com', + firstName: 'Bob', + lastName: 'Brown', + phone: '099-999-9999', + mailinglists: { + list_001: true, + list_002: false, + }, + }, + }, + timestamp: '2020-01-27T17:50:57.109+05:30', + type: 'identify', + }, + destination: { + ID: '1pYpzzvcn7AQ2W9GGIAZSsN6Mfq', + Name: 'Loops', + DestinationDefinition: { + Config: { + cdkV2Enabled: true, + }, + }, + Config: { + apiKey: 'dummyApiKey', + }, + Enabled: true, + Transformations: [], + }, + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + }, + ], + }, + }, + output: { + response: { + status: 200, + body: [ + { + metadata: { + destinationId: 'destId', + workspaceId: 'wspId', + }, + output: { + email: 'dummyuser@domain.com', + firstName: 'Bob', + lastName: 'Brown', + phone: '099-999-9999', + userId: 'dummy-user001', + mailinglists: { + list_001: true, + list_002: false, + }, + }, + statusCode: 200, + }, + ], + }, + }, + }, { name: 'loops', description: 'Identify event with missing email trait',