Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feature/62 resolve sendClassification & senderProfile in triggeredSend, emailSend and journey #1268

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
d020f55
#62: resolve sendClassification and senderProfile in triggeredSend
JoernBerkefeld Apr 16, 2024
5ca998f
#62: prioritize getting content builder emails over classic emails
JoernBerkefeld Apr 16, 2024
9f94ed6
#62: resolve sendClassification, deliveryProfile and senderProfile in…
JoernBerkefeld Apr 17, 2024
df6ce4e
#0: fix bad counter
JoernBerkefeld Apr 17, 2024
1517754
#62: resolve transactionalEmail & dataExtension for transactional jou…
JoernBerkefeld Apr 17, 2024
b223455
#62: resolve list, email/asset, sendClassification & senderProfile fo…
JoernBerkefeld Apr 17, 2024
5580961
#62: resolve details of journey subtype Quicksend
JoernBerkefeld Apr 17, 2024
9d1d2f5
#62: handle "event key not found" error gracefully
JoernBerkefeld Apr 17, 2024
b0a8acb
#62: simplify type event json
JoernBerkefeld Apr 17, 2024
bb1e726
#62: improve warnings
JoernBerkefeld Apr 18, 2024
7181eba
Merge branch 'develop' into feature/62-triggeredsendemailsend-do-not-…
JoernBerkefeld Apr 20, 2024
5dde228
Merge branch 'develop' into feature/62-triggeredsendemailsend-do-not-…
JoernBerkefeld Apr 24, 2024
8641d83
Merge branch 'develop' into feature/62-triggeredsendemailsend-do-not-…
JoernBerkefeld May 3, 2024
de04373
#62: add test for retrieve quicksend journey
JoernBerkefeld May 4, 2024
b58668a
#62: add message priority mapping
JoernBerkefeld May 4, 2024
33b0200
#62: added test for basic multi-step journey
JoernBerkefeld May 4, 2024
3936c08
#62: added decision split to multistep journey test
JoernBerkefeld May 4, 2024
5e4e99b
#62: added retrieve test for transactional email journey
JoernBerkefeld May 5, 2024
a82ed1d
#62: add journey tests for retrieve without key and for retrieve with id
JoernBerkefeld May 5, 2024
8f34313
#62: fix journey deletion test
JoernBerkefeld May 5, 2024
336bc38
#62: fix templating test for journeys
JoernBerkefeld May 5, 2024
3394066
Merge branch 'develop' into feature/62-triggeredsendemailsend-do-not-…
JoernBerkefeld May 6, 2024
5471525
Merge branch 'develop' into feature/62-triggeredsendemailsend-do-not-…
JoernBerkefeld May 13, 2024
cbc1ad9
#62: convert triggeredSend Priority (number) to c__priority (enum)
JoernBerkefeld May 13, 2024
861877f
#62: fixed test that confirms you cannot use changeKeyValue/changeKey…
JoernBerkefeld May 13, 2024
da527a5
Merge branch 'develop' into feature/62-triggeredsendemailsend-do-not-…
JoernBerkefeld May 13, 2024
269ccdd
Merge branch 'develop' into feature/62-triggeredsendemailsend-do-not-…
JoernBerkefeld May 14, 2024
f6d212d
#0: allow retrieving automations by objectid
JoernBerkefeld May 15, 2024
719e763
#0: retrieve & delete asset by id/name/key
JoernBerkefeld May 15, 2024
65f927f
#0: regression fix for f6d212d41213205996ad4e4f360ca1a85ca5547f
JoernBerkefeld May 15, 2024
13a4d1d
Merge branch 'develop' into feature/62-triggeredsendemailsend-do-not-…
JoernBerkefeld May 15, 2024
bffeecb
#62: reworked event processing based on SalesforceObjectTriggerV2
JoernBerkefeld May 15, 2024
de374c7
#62: remove redundant triggeredSend info
JoernBerkefeld May 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ class Mcdev {
return;
}
Util.logger.info(
Util.getGrayMsg(` - Deleting ${type} with key ${customerKey} on BU ${businessUnit}`)
Util.getGrayMsg(` - Deleting ${type} ${customerKey} on BU ${businessUnit}`)
);
try {
MetadataTypeInfo[type].properties = properties;
Expand Down
95 changes: 60 additions & 35 deletions lib/metadataTypes/Asset.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class Asset extends MetadataType {
for (const subType of subTypeArr) {
// each subtype contains multiple different specific types (images contains jpg and png for example)
// we use await here to limit the risk of too many concurrent api requests at time
items.push(...(await this.requestSubType(subType, retrieveDir, null, null, key)));
items.push(...(await this.requestSubType(subType, retrieveDir, key, null)));
}
const metadata = this.parseResponseBody({ items: items });
if (retrieveDir) {
Expand Down Expand Up @@ -139,7 +139,12 @@ class Asset extends MetadataType {
// each subtype contains multiple different specific types (images contains jpg and png for example)
// we use await here to limit the risk of too many concurrent api requests at time
items.push(
...(await this.requestSubType(subType, templateDir, name, templateVariables))
...(await this.requestSubType(
subType,
templateDir,
'name:' + name,
templateVariables
))
);
}
const metadata = this.parseResponseBody({ items: items });
Expand Down Expand Up @@ -200,12 +205,11 @@ class Asset extends MetadataType {
*
* @param {string} subType group of similar assets to put in a folder (ie. images)
* @param {string} [retrieveDir] target directory for saving assets
* @param {string} [templateName] name of the metadata file
* @param {string} [key] key/id/name to filter by
* @param {TemplateMap} [templateVariables] variables to be replaced in the metadata
* @param {string} [key] customer key to filter by
* @returns {Promise.<object[]>} Promise
*/
static async requestSubType(subType, retrieveDir, templateName, templateVariables, key) {
static async requestSubType(subType, retrieveDir, key, templateVariables) {
if (retrieveDir) {
Util.logger.info(`- Retrieving Subtype: ${subType}`);
} else {
Expand All @@ -227,34 +231,34 @@ class Asset extends MetadataType {
fields: ['category', 'createdDate', 'createdBy', 'modifiedDate', 'modifiedBy'], // get folder to allow duplicate name check against cache
};

if (templateName) {
if (key) {
payload.query = {
leftOperand: {
property: 'assetType.id',
simpleOperator: 'in',
value: subtypeIds,
},
logicalOperator: 'AND',
rightOperand: {
};
if (key.startsWith('id:')) {
payload.query.rightOperand = {
property: this.definition.idField,
simpleOperator: 'equal',
value: key.slice(3),
};
} else if (key.startsWith('name:')) {
payload.query.rightOperand = {
property: this.definition.nameField,
simpleOperator: 'equal',
value: templateName,
},
};
} else if (key) {
payload.query = {
leftOperand: {
property: 'assetType.id',
simpleOperator: 'in',
value: subtypeIds,
},
logicalOperator: 'AND',
rightOperand: {
value: key.slice(5),
};
} else {
payload.query.rightOperand = {
property: this.definition.keyField,
simpleOperator: 'equal',
value: key,
},
};
};
}
} else {
payload.query = {
property: 'assetType.id',
Expand Down Expand Up @@ -1774,9 +1778,9 @@ class Asset extends MetadataType {
*
* @private
* @param {string} key customer key
* @returns {Promise.<string>} objectId or enpty string
* @returns {Promise.<string>} id value or null
*/
static async _getObjectIdForSingleRetrieve(key) {
static async _getIdForSingleRetrieve(key) {
const name = key.startsWith('name:') ? key.slice(5) : null;
const filter = name
? '?$filter=name%20eq%20' + encodeURIComponent(name)
Expand All @@ -1789,40 +1793,61 @@ class Asset extends MetadataType {
);
return found?.id || null;
}
/**
* helper to allow us to select single metadata entries via REST
*
* @private
* @param {string} id id field
* @returns {Promise.<string>} key value or null
*/
static async _getKeyForSingleRetrieve(id) {
const results = await this.client.rest.get('/asset/v1/content/assets/' + id);
return results?.customerKey || null;
}

/**
* Delete a metadata item from the specified business unit
*
* @param {string} customerKey Identifier of data extension
* @param {string} key Identifier of item
* @returns {Promise.<boolean>} deletion success flag
*/
static async deleteByKey(customerKey) {
static async deleteByKey(key) {
// delete only works with the query's object id
const objectId = customerKey ? await this._getObjectIdForSingleRetrieve(customerKey) : null;
if (!objectId) {
let id;
if (key?.startsWith('id:')) {
id = key.slice(3);
// we need to get the actual key so that postDeletetTasks know what to do
key = await this._getKeyForSingleRetrieve(id);
} else {
id = key ? await this._getIdForSingleRetrieve(key) : null;
}
if (!id) {
Util.logger.error(` - ${this.definition.type} not found`);
return false;
}
return super.deleteByKeyREST('/asset/v1/content/assets/' + objectId, customerKey);
return super.deleteByKeyREST('/asset/v1/content/assets/' + id, key);
}

/**
* clean up after deleting a metadata item
* cannot use the generic method due to the complexity of how assets are saved to disk
*
* @param {string} customerKey Identifier of metadata item
* @param {string} key Identifier of metadata item
* @returns {Promise.<void>} -
*/
static async postDeleteTasks(customerKey) {
const fileArr = await this.getFilesToCommit([customerKey]);
static async postDeleteTasks(key) {
if (key?.startsWith('id:')) {
// sad
}
const fileArr = await this.getFilesToCommit([key]);

// check if asset sits in its own folder
const ownFolderIndex =
fileArr[0].indexOf(customerKey + '\\') > 0
? fileArr[0].indexOf(customerKey + '\\')
: fileArr[0].indexOf(customerKey + '/');
fileArr[0].indexOf(key + '\\') > 0
? fileArr[0].indexOf(key + '\\')
: fileArr[0].indexOf(key + '/');
if (ownFolderIndex > 0) {
fileArr.push(fileArr[0].slice(0, ownFolderIndex + customerKey.length));
fileArr.push(fileArr[0].slice(0, ownFolderIndex + key.length));
}

for (const filePath of fileArr) {
Expand Down
17 changes: 12 additions & 5 deletions lib/metadataTypes/Automation.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,10 @@ class Automation extends MetadataType {
} else {
/** @type {SoapRequestParams} */
let requestParams = null;
if (key) {
const objectIds = [];
if (key?.startsWith('id:')) {
objectIds.push(key.slice(3));
} else if (key) {
requestParams = {
filter: {
leftOperand: 'CustomerKey',
Expand All @@ -75,12 +78,16 @@ class Automation extends MetadataType {
['ObjectID'],
requestParams
);
if (results?.Results?.length) {
objectIds.push(...results.Results.map((item) => item.ObjectID));
}

// the API seems to handle 50 concurrent requests nicely
const response = results?.Results?.length
const response = objectIds.length
? await this.retrieveRESTcollection(
results?.Results.map((item) => ({
id: item.ObjectID,
uri: '/automation/v1/automations/' + item.ObjectID,
objectIds.map((objectID) => ({
id: objectID,
uri: '/automation/v1/automations/' + objectID,
})),
50,
!key
Expand Down
97 changes: 93 additions & 4 deletions lib/metadataTypes/EmailSend.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class EmailSend extends MetadataType {
if (sdl.r__dataExtension_Key) {
if (sdl.DataSourceTypeID !== 'CustomObject') {
throw new Error(
` ☇ skipping ${this.definition.type} ${metadata.Name} (${metadata.CustomerKey}): Expecting DataSourceTypeID to equal 'CustomObject' when r__dataExtension_Key is defined; Found '${sdl.DataSourceTypeID}'`
`Expecting DataSourceTypeID to equal 'CustomObject' when r__dataExtension_Key is defined; Found '${sdl.DataSourceTypeID}'`
);
}
sdl.CustomObjectID = cache.searchForField(
Expand All @@ -195,7 +195,7 @@ class EmailSend extends MetadataType {
delete sdl.r__dataExtension_Key;
} else if (sdl.DataSourceTypeID === 'CustomObject') {
throw new Error(
` ☇ skipping ${this.definition.type} ${metadata.Name} (${metadata.CustomerKey}): Expecting r__dataExtension_Key to be defined if DataSourceTypeID='CustomObject'`
`Expecting r__dataExtension_Key to be defined if DataSourceTypeID='CustomObject'`
);
}
if (!sdl.SalesForceObjectID || sdl.SalesForceObjectID === '') {
Expand All @@ -208,13 +208,49 @@ class EmailSend extends MetadataType {
ID: cache.getListObjectId(sdl.r__list_PathName, 'ID'),
};
delete sdl.r__list_PathName;
} else {
} else if (sdl.SendDefinitionListType === 'SourceList') {
// dont throw an error for type 'ExclusionList'
throw new Error(
` ☇ skipping ${this.definition.type} ${metadata.Name} (${metadata.CustomerKey}) Field SendDefinitionList.r__list_PathName was not defined. Please try re-retrieving this ESD from your source BU.`
`Field SendDefinitionList.r__list_PathName was not defined. Please try re-retrieving this ESD from your source BU.`
);
}
}

// sender profile
if (metadata.r__senderProfile_CustomerKey) {
cache.searchForField(
'senderProfile',
metadata.r__senderProfile_CustomerKey,
'CustomerKey',
'CustomerKey'
);
metadata.SenderProfile = {
CustomerKey: metadata.r__senderProfile_CustomerKey,
};
delete metadata.r__senderProfile_CustomerKey;
}
// send classification
if (metadata.r__sendClassification_CustomerKey) {
cache.searchForField(
'sendClassification',
metadata.r__sendClassification_CustomerKey,
'CustomerKey',
'CustomerKey'
);
metadata.SendClassification = {
CustomerKey: metadata.r__sendClassification_CustomerKey,
};
delete metadata.r__sendClassification_CustomerKey;
}
// delivery profile
if (metadata.r__deliveryProfile_key) {
cache.searchForField('deliveryProfile', metadata.r__deliveryProfile_key, 'key', 'key');
metadata.DeliveryProfile = {
CustomerKey: metadata.r__deliveryProfile_key,
};
delete metadata.r__deliveryProfile_key;
}

return metadata;
}

Expand Down Expand Up @@ -319,6 +355,59 @@ class EmailSend extends MetadataType {
}
}

// sender profile
if (metadata.SenderProfile?.CustomerKey) {
try {
cache.searchForField(
'senderProfile',
metadata.SenderProfile.CustomerKey,
'CustomerKey',
'CustomerKey'
);
metadata.r__senderProfile_CustomerKey = metadata.SenderProfile.CustomerKey;
delete metadata.SenderProfile;
} catch (ex) {
Util.logger.warn(
` - ${this.definition.type} ${metadata.CustomerKey}: ${ex.message}`
);
}
}
// send classification
if (metadata.SendClassification?.CustomerKey) {
try {
cache.searchForField(
'sendClassification',
metadata.SendClassification.CustomerKey,
'CustomerKey',
'CustomerKey'
);
metadata.r__sendClassification_CustomerKey =
metadata.SendClassification.CustomerKey;
delete metadata.SendClassification;
} catch (ex) {
Util.logger.warn(
` - ${this.definition.type} ${metadata.CustomerKey}: ${ex.message}`
);
}
}
// delivery profile
if (metadata.DeliveryProfile?.CustomerKey) {
try {
cache.searchForField(
'deliveryProfile',
metadata.DeliveryProfile.CustomerKey,
'key',
'key'
);
metadata.r__deliveryProfile_key = metadata.DeliveryProfile.CustomerKey;
delete metadata.DeliveryProfile;
} catch (ex) {
Util.logger.warn(
` - ${this.definition.type} ${metadata.CustomerKey}: ${ex.message}`
);
}
}

return metadata;
}
}
Expand Down
Loading
Loading