forked from segmentio/action-destinations
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
STRATCONN 2552 - LiveRamp Audiences Destination (segmentio#1251)
* Initial scaffolding for LiveRamp * temporarily remove warnings from scaffold * STRATCONN-2553 Add mapping and destination settings for LiveRamp * STRATCONN-2556 Add support for an S3 upload module * improve formatting * add support for arrays * fix bugs * add testcases * fix documentation * last fixes * last fixes * update lock file * add missing description * refactor S3 into its own action * Revert "refactor S3 into its own action" This reverts commit 508566e. * fix enquoting to be global
- Loading branch information
1 parent
2a821d9
commit 8109403
Showing
12 changed files
with
478 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
...inations/liveramp-audiences/audienceEntered/__tests__/__snapshots__/snapshot.test.ts.snap
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Jest Snapshot v1, https://goo.gl/fbAQLP | ||
|
||
exports[`Testing snapshot for LiverampAudiences's audienceEntered destination action: all fields 1`] = ` | ||
"audience_key,testType | ||
\\"PywYAY\\",\\"PywYAY\\"" | ||
`; | ||
|
||
exports[`Testing snapshot for LiverampAudiences's audienceEntered destination action: enquotated indentifier data 1`] = ` | ||
Array [ | ||
"\\"LCD TV,50\\"\\"\\"", | ||
"\\"\\"\\"early-bird\\"\\" special\\"", | ||
"\\"5'8\\"\\"\\"", | ||
] | ||
`; | ||
|
||
exports[`Testing snapshot for LiverampAudiences's audienceEntered destination action: required fields 1`] = ` | ||
"audience_key,testType | ||
\\"PywYAY\\",\\"PywYAY\\"" | ||
`; | ||
|
||
exports[`Testing snapshot for LiverampAudiences's audienceEntered destination action: required fields 2`] = ` | ||
Headers { | ||
Symbol(map): Object { | ||
"authorization": Array [ | ||
"AWS4-HMAC-SHA256 Credential=PywYAY/19700101/PywYAY/s3/aws4_request, SignedHeaders=content-length;content-type;host;x-amz-content-sha256;x-amz-date, Signature=3273c678e9edaf86b444eeadfd6021f8814141ee94adcb7409c7931c1f286dff", | ||
], | ||
"content-length": Array [ | ||
"39", | ||
], | ||
"content-type": Array [ | ||
"application/x-www-form-urlencoded; charset=utf-8", | ||
], | ||
"host": Array [ | ||
"PywYAY.s3.amazonaws.com", | ||
], | ||
"user-agent": Array [ | ||
"Segment (Actions)", | ||
], | ||
"x-amz-content-sha256": Array [ | ||
"c52b8202716894946461f92ea7d3ae902483d4c0339c06e616618d84bce9d642", | ||
], | ||
"x-amz-date": Array [ | ||
"19700101T000012Z", | ||
], | ||
}, | ||
} | ||
`; |
91 changes: 91 additions & 0 deletions
91
...on-actions/src/destinations/liveramp-audiences/audienceEntered/__tests__/snapshot.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
import { createTestEvent, createTestIntegration } from '@segment/actions-core' | ||
import { generateTestData } from '../../../../lib/test-data' | ||
import destination from '../../index' | ||
import nock from 'nock' | ||
import { enquoteIdentifier } from '../operations' | ||
|
||
const testDestination = createTestIntegration(destination) | ||
const actionSlug = 'audienceEntered' | ||
const destinationSlug = 'LiverampAudiences' | ||
const seedName = `${destinationSlug}#${actionSlug}` | ||
|
||
describe(`Testing snapshot for ${destinationSlug}'s ${actionSlug} destination action:`, () => { | ||
beforeAll(() => { | ||
const mockDate = new Date(12345) | ||
jest.spyOn(global, 'Date').mockImplementation(() => mockDate as unknown as string) | ||
}) | ||
|
||
it('required fields', async () => { | ||
const action = destination.actions[actionSlug] | ||
const [eventData, settingsData] = generateTestData(seedName, destination, action, true) | ||
eventData.delimiter = ',' | ||
settingsData.upload_mode = 'S3' | ||
nock(/.*/).persist().get(/.*/).reply(200) | ||
nock(/.*/).persist().post(/.*/).reply(200) | ||
nock(/.*/).persist().put(/.*/).reply(200) | ||
|
||
const event = createTestEvent({ | ||
properties: eventData | ||
}) | ||
|
||
const responses = await testDestination.testAction(actionSlug, { | ||
event: event, | ||
mapping: event.properties, | ||
settings: settingsData, | ||
auth: undefined | ||
}) | ||
|
||
const request = responses[0].request | ||
const rawBody = await request.text() | ||
|
||
try { | ||
const json = JSON.parse(rawBody) | ||
expect(json).toMatchSnapshot() | ||
return | ||
} catch (err) { | ||
expect(rawBody).toMatchSnapshot() | ||
} | ||
|
||
expect(request.headers).toMatchSnapshot() | ||
}) | ||
|
||
it('all fields', async () => { | ||
const action = destination.actions[actionSlug] | ||
const [eventData, settingsData] = generateTestData(seedName, destination, action, false) | ||
eventData.delimiter = ',' | ||
settingsData.upload_mode = 'S3' | ||
|
||
nock(/.*/).persist().get(/.*/).reply(200) | ||
nock(/.*/).persist().post(/.*/).reply(200) | ||
nock(/.*/).persist().put(/.*/).reply(200) | ||
|
||
const event = createTestEvent({ | ||
properties: eventData | ||
}) | ||
|
||
const responses = await testDestination.testAction(actionSlug, { | ||
event: event, | ||
mapping: event.properties, | ||
settings: settingsData, | ||
auth: undefined | ||
}) | ||
|
||
const request = responses[0].request | ||
const rawBody = await request.text() | ||
|
||
try { | ||
const json = JSON.parse(rawBody) | ||
expect(json).toMatchSnapshot() | ||
return | ||
} catch (err) { | ||
expect(rawBody).toMatchSnapshot() | ||
} | ||
}) | ||
|
||
it('enquotated indentifier data', async () => { | ||
const identifiers = [`LCD TV,50"`, `"early-bird" special`, `5'8"`] | ||
const enquotedIdentifiers = identifiers.map(enquoteIdentifier) | ||
|
||
expect(enquotedIdentifiers).toMatchSnapshot() | ||
}) | ||
}) |
26 changes: 26 additions & 0 deletions
26
...estination-actions/src/destinations/liveramp-audiences/audienceEntered/generated-types.ts
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
56 changes: 56 additions & 0 deletions
56
packages/destination-actions/src/destinations/liveramp-audiences/audienceEntered/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { ActionDefinition } from '@segment/actions-core' | ||
import type { Settings } from '../generated-types' | ||
import type { Payload } from './generated-types' | ||
import { processData } from './operations' | ||
|
||
const action: ActionDefinition<Settings, Payload> = { | ||
title: 'Audience Entered', | ||
description: 'Uploads audience membership data to a file for LiveRamp ingestion.', | ||
defaultSubscription: 'event = "Audience Entered"', | ||
fields: { | ||
audience_key: { | ||
label: 'Audience Key', | ||
description: 'Identifies the user within the entered audience.', | ||
type: 'string', | ||
required: true, | ||
default: { '@path': '$.userId' } | ||
}, | ||
identifier_data: { | ||
label: 'Identifier Data', | ||
description: `Additional data pertaining to the user.`, | ||
type: 'object', | ||
required: false, | ||
defaultObjectUI: 'keyvalue:only', | ||
default: { '@path': '$.context.traits' } | ||
}, | ||
delimiter: { | ||
label: 'Delimeter', | ||
description: `Character used to separate tokens in the resulting file.`, | ||
type: 'string', | ||
required: true, | ||
default: ',' | ||
}, | ||
audience_name: { | ||
label: 'Audience name', | ||
description: `Name of the audience the user has entered.`, | ||
type: 'string', | ||
required: true, | ||
default: { '@path': '$.properties.audience_key' } | ||
}, | ||
received_at: { | ||
label: 'Received At', | ||
description: `Datetime at which the event was received. Used to disambiguate the resulting file.`, | ||
type: 'datetime', | ||
required: true, | ||
default: { '@path': '$.receivedAt' } | ||
} | ||
}, | ||
perform: async (request, { settings, payload }) => { | ||
return processData(request, settings, [payload]) | ||
}, | ||
performBatch: (request, { settings, payload }) => { | ||
return processData(request, settings, payload) | ||
} | ||
} | ||
|
||
export default action |
59 changes: 59 additions & 0 deletions
59
...ges/destination-actions/src/destinations/liveramp-audiences/audienceEntered/operations.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { PayloadValidationError, RequestClient } from '@segment/actions-core' | ||
import type { Settings } from '../generated-types' | ||
import type { Payload } from './generated-types' | ||
import { uploadS3, validateS3 } from '../s3' | ||
|
||
async function processData(request: RequestClient, settings: Settings, payloads: Payload[]) { | ||
// STRATCONN-2554: Add support for SFTP | ||
if (settings.upload_mode == 'S3') { | ||
validateS3(settings) | ||
} else { | ||
throw new PayloadValidationError(`Unrecognized upload mode: ${settings.upload_mode}`) | ||
} | ||
// STRATCONN-2584: error if less than 25 elements in payload | ||
|
||
// Prepare header row. Expected format: | ||
// liveramp_audience_key[1],identifier_data[0..n] | ||
const rows = [] | ||
const headers = ['audience_key'] | ||
if (payloads[0].identifier_data) { | ||
for (const identifier of Object.getOwnPropertyNames(payloads[0].identifier_data)) { | ||
headers.push(identifier) | ||
} | ||
} | ||
rows.push(headers.join(payloads[0].delimiter)) | ||
|
||
// Prepare data rows | ||
for (const payload of payloads) { | ||
const row = [] | ||
row.push(payload.audience_key) | ||
if (payload.identifier_data) { | ||
for (const identifier of Object.getOwnPropertyNames(payload.identifier_data)) { | ||
row.push(payload.identifier_data[identifier] as string) | ||
} | ||
} | ||
rows.push(row.map(enquoteIdentifier).join(payload.delimiter)) | ||
} | ||
|
||
// STRATCONN-2584: verify multiple emails are handled | ||
const filename = `${payloads[0].audience_name}_PII_${payloads[0].received_at}.csv` | ||
const fileContent = rows.join('\n') | ||
|
||
if (settings.upload_mode == 'S3') { | ||
return await uploadS3(settings, filename, fileContent, request) | ||
} | ||
} | ||
|
||
/* | ||
To avoid collision with delimeters, we should surround identifiers with quotation marks. | ||
https://docs.liveramp.com/connect/en/formatting-file-data.html#idm45998667347936 | ||
Examples: | ||
LCD TV -> "LCD TV" | ||
LCD TV,50" -> "LCD TV,50""" | ||
*/ | ||
function enquoteIdentifier(identifier: string) { | ||
return `"${identifier.replace(/"/g, '""')}"` | ||
} | ||
|
||
export { processData, enquoteIdentifier } |
36 changes: 36 additions & 0 deletions
36
packages/destination-actions/src/destinations/liveramp-audiences/generated-types.ts
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.