mcdev 7.3.1 → 7.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
- package/@types/lib/Deployer.d.ts.map +1 -1
- package/@types/lib/index.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Asset.d.ts +25 -0
- package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
- package/@types/lib/metadataTypes/DataExtension.d.ts +3 -2
- package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
- package/@types/lib/metadataTypes/DataExtensionField.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Event.d.ts +39 -7
- package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Journey.d.ts +4 -3
- package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
- package/@types/lib/metadataTypes/MetadataType.d.ts +22 -3
- package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
- package/@types/lib/util/init.config.d.ts.map +1 -1
- package/@types/lib/util/replaceContentBlockReference.d.ts +4 -5
- package/@types/lib/util/replaceContentBlockReference.d.ts.map +1 -1
- package/@types/lib/util/util.d.ts +18 -2
- package/@types/lib/util/util.d.ts.map +1 -1
- package/@types/lib/util/validations.d.ts +9 -0
- package/@types/lib/util/validations.d.ts.map +1 -0
- package/@types/types/mcdev.d.d.ts +195 -0
- package/@types/types/mcdev.d.d.ts.map +1 -1
- package/boilerplate/config.json +22 -0
- package/boilerplate/files/.gitattributes +1 -1
- package/boilerplate/files/README.md +1 -1
- package/boilerplate/forcedUpdates.json +8 -0
- package/boilerplate/gitignore-template +1 -0
- package/lib/Deployer.js +5 -0
- package/lib/cli.js +28 -3
- package/lib/index.js +3 -2
- package/lib/metadataTypes/Asset.js +87 -7
- package/lib/metadataTypes/DataExtension.js +74 -17
- package/lib/metadataTypes/DataExtensionField.js +11 -3
- package/lib/metadataTypes/Event.js +171 -105
- package/lib/metadataTypes/Journey.js +207 -89
- package/lib/metadataTypes/MetadataType.js +182 -37
- package/lib/metadataTypes/definitions/TriggeredSend.definition.js +4 -2
- package/lib/util/config.js +4 -4
- package/lib/util/init.config.js +10 -6
- package/lib/util/replaceContentBlockReference.js +15 -11
- package/lib/util/util.js +43 -9
- package/lib/util/validations.js +66 -0
- package/package.json +8 -8
- package/test/general.test.js +4 -4
- package/test/mockRoot/.mcdevrc.json +15 -1
- package/test/mockRoot/deploy/testInstance/testBU/triggeredSend/testExisting_triggeredSend.triggeredSend-meta.json +0 -1
- package/test/mockRoot/deploy/testInstance/testBU/triggeredSend/testNew_triggeredSend.triggeredSend-meta.json +0 -1
- package/test/resources/9999999/journey/build-expected.json +4 -2
- package/test/resources/9999999/journey/get-multistep-expected.json +61 -61
- package/test/resources/9999999/journey/get-quicksend-expected.json +4 -2
- package/test/resources/9999999/journey/get-quicksend-rcb-id-expected.json +2 -2
- package/test/resources/9999999/journey/get-quicksend-rcb-key-expected.json +2 -2
- package/test/resources/9999999/journey/get-quicksend-rcb-name-expected.json +6 -2
- package/test/resources/9999999/journey/get-transactionalEmail-expected.json +2 -2
- package/test/resources/9999999/journey/template-expected.json +4 -2
- package/test/resources/9999999/triggeredSend/build-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/get-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/get-rcb-id-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/get-rcb-key-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/get-rcb-name-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/patch-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/post-expected.json +0 -1
- package/test/resources/9999999/triggeredSend/template-expected.json +0 -1
- package/test/resources/9999999/triggeredSendDefinition/create-response.xml +0 -1
- package/test/resources/9999999/triggeredSendDefinition/retrieve-TriggeredSendStatusINNew,Active,Inactive,Moved,Canceled-response.xml +0 -2
- package/test/resources/9999999/triggeredSendDefinition/update-response.xml +0 -1
- package/test/type.dataExtension.test.js +3 -3
- package/test/type.journey.test.js +2 -2
- package/test/utils.js +1 -0
- package/types/mcdev.d.js +66 -0
|
@@ -42,22 +42,10 @@ class DataExtension extends MetadataType {
|
|
|
42
42
|
* if create or update operation is needed.
|
|
43
43
|
*
|
|
44
44
|
* @param {DataExtensionMap} metadataMap dataExtensions mapped by their customerKey
|
|
45
|
-
* @
|
|
45
|
+
* @param {string} deployDir directory where deploy metadata are saved
|
|
46
|
+
* @returns {Promise.<MetadataTypeMap>} keyField => metadata map
|
|
46
47
|
*/
|
|
47
|
-
static async upsert(metadataMap) {
|
|
48
|
-
// get existing DE-fields for DE-keys in deployment package to properly handle add/update/delete of fields
|
|
49
|
-
// we need to use IN here because it would fail otherwise if we try to deploy too many DEs at the same time
|
|
50
|
-
/** @type {SoapRequestParams} */
|
|
51
|
-
const fieldOptions = {
|
|
52
|
-
filter: {
|
|
53
|
-
leftOperand: 'DataExtension.CustomerKey',
|
|
54
|
-
operator: 'IN',
|
|
55
|
-
rightOperand: Object.keys(metadataMap),
|
|
56
|
-
},
|
|
57
|
-
};
|
|
58
|
-
Util.logger.info(` - Caching dependent Metadata: dataExtensionField`);
|
|
59
|
-
await this.#attachFields(metadataMap, fieldOptions);
|
|
60
|
-
|
|
48
|
+
static async upsert(metadataMap, deployDir) {
|
|
61
49
|
/** @type {object[]} */
|
|
62
50
|
const metadataToCreate = [];
|
|
63
51
|
/** @type {object[]} */
|
|
@@ -66,6 +54,11 @@ class DataExtension extends MetadataType {
|
|
|
66
54
|
for (const metadataKey in metadataMap) {
|
|
67
55
|
try {
|
|
68
56
|
metadataMap[metadataKey] = await this.preDeployTasks(metadataMap[metadataKey]);
|
|
57
|
+
metadataMap[metadataKey] = await this.validation(
|
|
58
|
+
'deploy',
|
|
59
|
+
metadataMap[metadataKey],
|
|
60
|
+
deployDir
|
|
61
|
+
);
|
|
69
62
|
} catch (ex) {
|
|
70
63
|
// output error & remove from deploy list
|
|
71
64
|
Util.logger.error(
|
|
@@ -187,7 +180,7 @@ class DataExtension extends MetadataType {
|
|
|
187
180
|
DataExtension.oldFields[metadataMap[metadataKey][this.definition.keyField]] =
|
|
188
181
|
await DataExtensionField.prepareDeployColumnsOnUpdate(
|
|
189
182
|
metadataMap[metadataKey].Fields,
|
|
190
|
-
metadataKey
|
|
183
|
+
Util.matchedByName?.[this.definition.type]?.[metadataKey] || metadataKey
|
|
191
184
|
);
|
|
192
185
|
|
|
193
186
|
if (
|
|
@@ -893,7 +886,7 @@ class DataExtension extends MetadataType {
|
|
|
893
886
|
if (metadata[field?.DataExtension?.CustomerKey]) {
|
|
894
887
|
metadata[field.DataExtension.CustomerKey].Fields.push(field);
|
|
895
888
|
} else {
|
|
896
|
-
|
|
889
|
+
// field was retrieved for which we do not have the right dataExtension. This might be due to us having to resort to not using a DE filter to avoid the "String or binary data would be truncated." error
|
|
897
890
|
}
|
|
898
891
|
}
|
|
899
892
|
|
|
@@ -1632,6 +1625,70 @@ class DataExtension extends MetadataType {
|
|
|
1632
1625
|
return super.getFilesToCommit(keyArr);
|
|
1633
1626
|
}
|
|
1634
1627
|
}
|
|
1628
|
+
/**
|
|
1629
|
+
* helper for {@link MetadataType.createOrUpdate}
|
|
1630
|
+
*
|
|
1631
|
+
* @param {MetadataTypeItem} metadataItem to be deployed item
|
|
1632
|
+
* @returns {MetadataTypeItem} cached item or undefined
|
|
1633
|
+
*/
|
|
1634
|
+
static getCacheMatchedByName(metadataItem) {
|
|
1635
|
+
let cacheMatchedByName;
|
|
1636
|
+
|
|
1637
|
+
if (Util.OPTIONS.matchName) {
|
|
1638
|
+
// make sure to run the search ONLY if OPTIONS.matchName is true and definition.allowMatchingByName signals support
|
|
1639
|
+
const typeCache = cache.getCache()?.[this.definition.type];
|
|
1640
|
+
const potentials = [];
|
|
1641
|
+
for (const key in typeCache) {
|
|
1642
|
+
const cachedItem = typeCache[key];
|
|
1643
|
+
if (
|
|
1644
|
+
cachedItem[this.definition.nameField] ===
|
|
1645
|
+
metadataItem[this.definition.nameField]
|
|
1646
|
+
) {
|
|
1647
|
+
potentials.push(cachedItem);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
if (potentials.length > 1) {
|
|
1651
|
+
throw new Error(
|
|
1652
|
+
`found multiple name matches in cache for ${this.definition.type} ${metadataItem[this.definition.keyField]} / ${metadataItem[this.definition.nameField]}. Check their keys for more details: ${potentials.map((p) => p[this.definition.keyField]).join(', ')}`
|
|
1653
|
+
);
|
|
1654
|
+
} else if (potentials.length === 1) {
|
|
1655
|
+
// only one item found, confirm that it's in the same folder
|
|
1656
|
+
const deployFolderPath = cache.searchForField(
|
|
1657
|
+
'folder',
|
|
1658
|
+
metadataItem[this.definition.folderIdField],
|
|
1659
|
+
'ID',
|
|
1660
|
+
'Path'
|
|
1661
|
+
);
|
|
1662
|
+
if (
|
|
1663
|
+
potentials[0][this.definition.folderIdField] ===
|
|
1664
|
+
metadataItem[this.definition.folderIdField]
|
|
1665
|
+
) {
|
|
1666
|
+
cacheMatchedByName = potentials[0];
|
|
1667
|
+
|
|
1668
|
+
Util.logger.info(
|
|
1669
|
+
Util.getGrayMsg(
|
|
1670
|
+
` - found ${this.definition.type} ${metadataItem[this.definition.keyField]} in cache by name "${metadataItem[this.definition.nameField]}" and folder "${deployFolderPath}": ${cacheMatchedByName[this.definition.keyField]}`
|
|
1671
|
+
)
|
|
1672
|
+
);
|
|
1673
|
+
} else {
|
|
1674
|
+
const cacheFolderPath = cache.searchForField(
|
|
1675
|
+
'folder',
|
|
1676
|
+
potentials[0][this.definition.folderIdField],
|
|
1677
|
+
'ID',
|
|
1678
|
+
'Path'
|
|
1679
|
+
);
|
|
1680
|
+
throw new Error(
|
|
1681
|
+
`found ${this.definition.type} ${metadataItem[this.definition.keyField]} in cache by name "${metadataItem[this.definition.nameField]}" but in different folders: "${deployFolderPath}" vs "${cacheFolderPath}": ${potentials[0][this.definition.keyField]}`
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
} else {
|
|
1685
|
+
Util.logger.debug(
|
|
1686
|
+
` - no name-match found for ${this.definition.type} ${metadataItem[this.definition.keyField]}. Creating new ${this.definition.type} instead.`
|
|
1687
|
+
);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
return cacheMatchedByName;
|
|
1691
|
+
}
|
|
1635
1692
|
}
|
|
1636
1693
|
|
|
1637
1694
|
// Assign definition to static attributes
|
|
@@ -48,7 +48,13 @@ class DataExtensionField extends MetadataType {
|
|
|
48
48
|
* @returns {Promise.<{metadata: DataExtensionFieldMap, type: string}>} Promise of items
|
|
49
49
|
*/
|
|
50
50
|
static async retrieveForCacheDE(requestParams, additionalFields) {
|
|
51
|
-
|
|
51
|
+
let response;
|
|
52
|
+
response = await super.retrieveSOAP(null, requestParams, null, additionalFields);
|
|
53
|
+
if (!response) {
|
|
54
|
+
// try again but without filters as a workaround for the "String or binary data would be truncated." issue
|
|
55
|
+
response = await super.retrieveSOAP(null, {}, null, additionalFields);
|
|
56
|
+
}
|
|
57
|
+
return response;
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
/**
|
|
@@ -143,11 +149,13 @@ class DataExtensionField extends MetadataType {
|
|
|
143
149
|
const existingFieldByName = {};
|
|
144
150
|
|
|
145
151
|
for (const key of Object.keys(fieldsObj)) {
|
|
146
|
-
|
|
152
|
+
// make sure we stringify the name in case it looked numeric and then lowercase it for easy comparison as the server is comparing field names case-insensitive
|
|
153
|
+
existingFieldByName[(fieldsObj[key].Name + '').toLowerCase()] = fieldsObj[key];
|
|
147
154
|
}
|
|
148
155
|
for (let i = deployColumns.length - 1; i >= 0; i--) {
|
|
149
156
|
const item = deployColumns[i];
|
|
150
|
-
|
|
157
|
+
// make sure we stringify the name in case it looked numeric and then lowercase it for easy comparison as the server is comparing field names case-insensitive
|
|
158
|
+
const itemOld = existingFieldByName[(item.Name + '').toLowerCase()];
|
|
151
159
|
if (itemOld) {
|
|
152
160
|
// field is getting updated ---
|
|
153
161
|
|
|
@@ -21,6 +21,8 @@ import pLimit from 'p-limit';
|
|
|
21
21
|
*
|
|
22
22
|
* @typedef {import('../../types/mcdev.d.js').ReferenceObject} ReferenceObject
|
|
23
23
|
* @typedef {import('../../types/mcdev.d.js').SfObjectField} SfObjectField
|
|
24
|
+
* @typedef {import('../../types/mcdev.d.js').configurationArguments} configurationArguments
|
|
25
|
+
* @typedef {import('../../types/mcdev.d.js').Conditions} Conditions
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
28
|
/**
|
|
@@ -42,10 +44,10 @@ class Event extends MetadataType {
|
|
|
42
44
|
* @param {string} [key] customer key of single item to retrieve
|
|
43
45
|
* @returns {Promise.<MetadataTypeMapObj>} Promise of metadata
|
|
44
46
|
*/
|
|
45
|
-
static retrieve(retrieveDir, _, __, key) {
|
|
47
|
+
static async retrieve(retrieveDir, _, __, key) {
|
|
46
48
|
Util.logBeta(this.definition.type);
|
|
47
49
|
try {
|
|
48
|
-
return super.retrieveREST(
|
|
50
|
+
return await super.retrieveREST(
|
|
49
51
|
retrieveDir,
|
|
50
52
|
`/interaction/v1/eventDefinitions${
|
|
51
53
|
key ? '/key:' + encodeURIComponent(key) : ''
|
|
@@ -447,7 +449,8 @@ class Event extends MetadataType {
|
|
|
447
449
|
await this.postRetrieveTasks_SalesforceEntryEvents(
|
|
448
450
|
metadata.type,
|
|
449
451
|
metadata.configurationArguments,
|
|
450
|
-
metadata.eventDefinitionKey
|
|
452
|
+
metadata.eventDefinitionKey,
|
|
453
|
+
metadata.publishedInteractionCount >= 1
|
|
451
454
|
);
|
|
452
455
|
} catch (ex) {
|
|
453
456
|
Util.logger.warn(
|
|
@@ -464,6 +467,12 @@ class Event extends MetadataType {
|
|
|
464
467
|
referencedObjects: {},
|
|
465
468
|
/** @type {Object.<string, Object.<string, SfObjectField>>} object-name > field-name > field data */
|
|
466
469
|
objectFields: {},
|
|
470
|
+
/** @type {Object.<string, Promise.<any>>} */
|
|
471
|
+
loadingFields: {},
|
|
472
|
+
/** @type {Object.<string, Promise.<any>>} */
|
|
473
|
+
loadingRelatedObjects: {},
|
|
474
|
+
/** @type {Promise.<any>} */
|
|
475
|
+
loadingWorkflowObjects: null,
|
|
467
476
|
};
|
|
468
477
|
|
|
469
478
|
/**
|
|
@@ -479,53 +488,20 @@ class Event extends MetadataType {
|
|
|
479
488
|
// 1 get all available Salesforce objects
|
|
480
489
|
// similar response to /jbint/getWorkflowObjects
|
|
481
490
|
if (!this.sfObjects.workflowObjects) {
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
this.sfObjects.workflowObjects = workflowObjectsResponse
|
|
487
|
-
? workflowObjectsResponse.map((o) => o.apiname)
|
|
488
|
-
: [];
|
|
491
|
+
if (!this.sfObjects.loadingWorkflowObjects) {
|
|
492
|
+
this.sfObjects.loadingWorkflowObjects = this._getWorkflowObjects();
|
|
493
|
+
}
|
|
494
|
+
await this.sfObjects.loadingWorkflowObjects;
|
|
489
495
|
}
|
|
490
496
|
|
|
491
497
|
// 2 get objects related to the selected object
|
|
492
498
|
// same response as /jbint/getRelatedObjects?type=<objectAPIName>
|
|
493
499
|
if (!this.sfObjects.referencedObjects?.[objectAPIName]) {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
try {
|
|
498
|
-
const referenceObjectsResponse = await this.client.rest.get(
|
|
499
|
-
`/data/v1/integration/member/salesforce/object/${objectAPIName}/referenceobjects`
|
|
500
|
-
);
|
|
501
|
-
// add itself first so that we get the fields for objectAPIName as well
|
|
502
|
-
const selfReference = {
|
|
503
|
-
referenceObjectName: objectAPIName,
|
|
504
|
-
relationshipName: objectAPIName,
|
|
505
|
-
};
|
|
506
|
-
this.sfObjects.referencedObjects[objectAPIName] = referenceObjectsResponse
|
|
507
|
-
? [selfReference, ...referenceObjectsResponse]
|
|
508
|
-
: [selfReference];
|
|
509
|
-
if (
|
|
510
|
-
referenceObjectsResponse.some((el) => el.referenceObjectName === 'Lead') &&
|
|
511
|
-
referenceObjectsResponse.some((el) => el.referenceObjectName === 'Contact')
|
|
512
|
-
) {
|
|
513
|
-
// add fake object "Common" to referenced objects for testing
|
|
514
|
-
this.sfObjects.referencedObjects[objectAPIName].push({
|
|
515
|
-
displayname: 'Common',
|
|
516
|
-
relationshipIdName: 'Id',
|
|
517
|
-
relationshipName: 'Common',
|
|
518
|
-
isPolymorphic: false,
|
|
519
|
-
referenceObjectName: 'Common',
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
} catch (ex) {
|
|
523
|
-
if (ex.code === 'ERR_BAD_RESPONSE') {
|
|
524
|
-
throw new Error(
|
|
525
|
-
`Could not find Salesforce entry object ${objectAPIName} on connected org.`
|
|
526
|
-
);
|
|
527
|
-
}
|
|
500
|
+
if (!this.sfObjects.loadingRelatedObjects[objectAPIName]) {
|
|
501
|
+
this.sfObjects.loadingRelatedObjects[objectAPIName] =
|
|
502
|
+
this._getRelatedSfObjects(objectAPIName);
|
|
528
503
|
}
|
|
504
|
+
await this.sfObjects.loadingRelatedObjects[objectAPIName];
|
|
529
505
|
|
|
530
506
|
// 3 get fields
|
|
531
507
|
const rateLimit = pLimit(20);
|
|
@@ -541,47 +517,9 @@ class Event extends MetadataType {
|
|
|
541
517
|
await Promise.all(
|
|
542
518
|
uniqueSfObjectNames.map((objectAPIName) =>
|
|
543
519
|
rateLimit(async () => {
|
|
544
|
-
|
|
545
|
-
this.
|
|
546
|
-
|
|
547
|
-
) {
|
|
548
|
-
return;
|
|
549
|
-
}
|
|
550
|
-
Util.logger.info(
|
|
551
|
-
Util.getGrayMsg(
|
|
552
|
-
' - Caching Fields for Salesforce Object ' + objectAPIName
|
|
553
|
-
)
|
|
554
|
-
);
|
|
555
|
-
|
|
556
|
-
const referenceObjectsFieldsResponse = await this.client.rest.get(
|
|
557
|
-
`/legacy/v1/beta/integration/member/salesforce/object/${objectAPIName}`
|
|
558
|
-
);
|
|
559
|
-
if (referenceObjectsFieldsResponse?.sfobjectfields?.length) {
|
|
560
|
-
Util.logger.debug(
|
|
561
|
-
`Found ${referenceObjectsFieldsResponse?.sfobjectfields?.length} fields for Salesforce Object ${objectAPIName}`
|
|
562
|
-
);
|
|
563
|
-
this.sfObjects.objectFields[objectAPIName] = {};
|
|
564
|
-
// !add default fields that are somehow not always returned by this legacy beta API
|
|
565
|
-
for (const field of this.defaultSalesforceFields) {
|
|
566
|
-
// @ts-expect-error hack to work around shortcomings of legacy beta API
|
|
567
|
-
this.sfObjects.objectFields[objectAPIName][field] = {
|
|
568
|
-
label: field,
|
|
569
|
-
name: field,
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
// add fields returned by API
|
|
573
|
-
for (const field of referenceObjectsFieldsResponse.sfobjectfields) {
|
|
574
|
-
this.sfObjects.objectFields[objectAPIName][field.name] = field;
|
|
575
|
-
}
|
|
576
|
-
} else {
|
|
577
|
-
Util.logger.debug(
|
|
578
|
-
`Could not find any fields for Salesforce Object ${objectAPIName}`
|
|
579
|
-
);
|
|
580
|
-
throw new Error(
|
|
581
|
-
`Could not find any fields for Salesforce Object ${objectAPIName}`
|
|
582
|
-
);
|
|
583
|
-
}
|
|
584
|
-
return;
|
|
520
|
+
this.sfObjects.loadingFields[objectAPIName] ||=
|
|
521
|
+
this._getSalesforceObjectFields(objectAPIName);
|
|
522
|
+
return this.sfObjects.loadingFields[objectAPIName];
|
|
585
523
|
})
|
|
586
524
|
)
|
|
587
525
|
);
|
|
@@ -619,6 +557,101 @@ class Event extends MetadataType {
|
|
|
619
557
|
this.sfObjects.objectFields[contactLeadName];
|
|
620
558
|
}
|
|
621
559
|
}
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* helper that allows skipping to run this again in multi-key retrieval
|
|
564
|
+
*/
|
|
565
|
+
static async _getWorkflowObjects() {
|
|
566
|
+
Util.logger.info(Util.getGrayMsg(' - Caching Salesforce Objects'));
|
|
567
|
+
const workflowObjectsResponse = await this.client.rest.get(
|
|
568
|
+
`/data/v1/integration/member/salesforce/workflowobjects`
|
|
569
|
+
);
|
|
570
|
+
this.sfObjects.workflowObjects = workflowObjectsResponse
|
|
571
|
+
? workflowObjectsResponse.map((o) => o.apiname)
|
|
572
|
+
: [];
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* helper that allows skipping to run this again in multi-key retrieval
|
|
576
|
+
*
|
|
577
|
+
* @param {string} objectAPIName SF entry object of the current event
|
|
578
|
+
*/
|
|
579
|
+
static async _getRelatedSfObjects(objectAPIName) {
|
|
580
|
+
Util.logger.info(
|
|
581
|
+
Util.getGrayMsg(' - Caching Related Salesforce Objects for ' + objectAPIName)
|
|
582
|
+
);
|
|
583
|
+
try {
|
|
584
|
+
const referenceObjectsResponse = await this.client.rest.get(
|
|
585
|
+
`/data/v1/integration/member/salesforce/object/${objectAPIName}/referenceobjects`
|
|
586
|
+
);
|
|
587
|
+
// add itself first so that we get the fields for objectAPIName as well
|
|
588
|
+
const selfReference = {
|
|
589
|
+
referenceObjectName: objectAPIName,
|
|
590
|
+
relationshipName: objectAPIName,
|
|
591
|
+
};
|
|
592
|
+
this.sfObjects.referencedObjects[objectAPIName] = referenceObjectsResponse
|
|
593
|
+
? [selfReference, ...referenceObjectsResponse]
|
|
594
|
+
: [selfReference];
|
|
595
|
+
if (
|
|
596
|
+
referenceObjectsResponse.some((el) => el.referenceObjectName === 'Lead') &&
|
|
597
|
+
referenceObjectsResponse.some((el) => el.referenceObjectName === 'Contact')
|
|
598
|
+
) {
|
|
599
|
+
// add fake object "Common" to referenced objects for testing
|
|
600
|
+
this.sfObjects.referencedObjects[objectAPIName].push({
|
|
601
|
+
displayname: 'Common',
|
|
602
|
+
relationshipIdName: 'Id',
|
|
603
|
+
relationshipName: 'Common',
|
|
604
|
+
isPolymorphic: false,
|
|
605
|
+
referenceObjectName: 'Common',
|
|
606
|
+
});
|
|
607
|
+
}
|
|
608
|
+
} catch (ex) {
|
|
609
|
+
if (ex.code === 'ERR_BAD_RESPONSE') {
|
|
610
|
+
throw new Error(
|
|
611
|
+
`Could not find Salesforce entry object ${objectAPIName} on connected org.`
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* helper that allows skipping to run this again in multi-key retrieval
|
|
618
|
+
*
|
|
619
|
+
* @param {string} objectAPIName SF object for which to get the fields
|
|
620
|
+
*/
|
|
621
|
+
static async _getSalesforceObjectFields(objectAPIName) {
|
|
622
|
+
if (this.sfObjects.objectFields[objectAPIName] || objectAPIName === 'Common') {
|
|
623
|
+
return;
|
|
624
|
+
}
|
|
625
|
+
Util.logger.info(
|
|
626
|
+
Util.getGrayMsg(' - Caching Fields for Salesforce Object ' + objectAPIName)
|
|
627
|
+
);
|
|
628
|
+
|
|
629
|
+
const referenceObjectsFieldsResponse = await this.client.rest.get(
|
|
630
|
+
`/legacy/v1/beta/integration/member/salesforce/object/${objectAPIName}`
|
|
631
|
+
);
|
|
632
|
+
if (referenceObjectsFieldsResponse?.sfobjectfields?.length) {
|
|
633
|
+
Util.logger.debug(
|
|
634
|
+
`Found ${referenceObjectsFieldsResponse?.sfobjectfields?.length} fields for Salesforce Object ${objectAPIName}`
|
|
635
|
+
);
|
|
636
|
+
this.sfObjects.objectFields[objectAPIName] = {};
|
|
637
|
+
// !add default fields that are somehow not always returned by this legacy beta API
|
|
638
|
+
for (const field of this.defaultSalesforceFields) {
|
|
639
|
+
// @ts-expect-error hack to work around shortcomings of legacy beta API
|
|
640
|
+
this.sfObjects.objectFields[objectAPIName][field] = {
|
|
641
|
+
label: field,
|
|
642
|
+
name: field,
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
// add fields returned by API
|
|
646
|
+
for (const field of referenceObjectsFieldsResponse.sfobjectfields) {
|
|
647
|
+
this.sfObjects.objectFields[objectAPIName][field.name] = field;
|
|
648
|
+
}
|
|
649
|
+
} else {
|
|
650
|
+
Util.logger.warn(
|
|
651
|
+
`Could not cache fields for Salesforce Object '${objectAPIName}'. This is likely caused by insufficient access of your MC-Connect integration user. Please check assigned permission sets / the profile.`
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
return;
|
|
622
655
|
}
|
|
623
656
|
|
|
624
657
|
static defaultSalesforceFields = [
|
|
@@ -637,26 +670,41 @@ class Event extends MetadataType {
|
|
|
637
670
|
|
|
638
671
|
/**
|
|
639
672
|
*
|
|
640
|
-
* @param {
|
|
673
|
+
* @param {configurationArguments} ca trigger[0].configurationArguments
|
|
674
|
+
* @param {boolean} isPublished if the current item is published it means we do not need to do contact vs common checks
|
|
641
675
|
*/
|
|
642
|
-
static checkSalesforceEntryEvents(ca) {
|
|
676
|
+
static checkSalesforceEntryEvents(ca, isPublished) {
|
|
643
677
|
// 1 check eventDataConfig
|
|
644
|
-
const edcObjects = ca.eventDataConfig.objects
|
|
678
|
+
const edcObjects = ca.eventDataConfig.objects.sort((a, b) =>
|
|
679
|
+
a.dePrefix.localeCompare(b.dePrefix)
|
|
680
|
+
);
|
|
645
681
|
const errors = [];
|
|
646
682
|
const dePrefixFields = {};
|
|
647
683
|
const dePrefixRelationshipMap = {};
|
|
648
684
|
const dePrefixReferenceObjectMap = {};
|
|
649
|
-
|
|
685
|
+
// SFMC only uses "Common" to aggreagate Contacts and Leads if that was actively selected in the entry event. Also, already published journeys/events continue to work even if fields would later be changed, leading to a shift from or to the "common" fake-object.
|
|
686
|
+
const checkCommon =
|
|
687
|
+
ca.whoToInject === 'Contact ID/Lead ID (Contacts and Leads)' && !isPublished;
|
|
650
688
|
for (const object of edcObjects) {
|
|
651
689
|
// create secondary object to quickly check eventDataSummary against
|
|
652
690
|
dePrefixFields[object.dePrefix] = object.fields;
|
|
653
|
-
|
|
654
|
-
|
|
691
|
+
|
|
692
|
+
// if the current object is the entry object then relationshipName and referenceObject are set to empty strings because it's not "referencing" a "relationship" but just listing its own fields
|
|
693
|
+
dePrefixRelationshipMap[object.dePrefix] =
|
|
694
|
+
object.relationshipName === ''
|
|
695
|
+
? object.dePrefix.split(':')[0]
|
|
696
|
+
: object.relationshipName;
|
|
697
|
+
dePrefixReferenceObjectMap[object.dePrefix] =
|
|
698
|
+
object.referenceObject === ''
|
|
699
|
+
? object.dePrefix.split(':')[0]
|
|
700
|
+
: object.referenceObject;
|
|
655
701
|
|
|
656
702
|
// 1.1 check if fields in eventDataConfig exist in Salesforce
|
|
657
703
|
// if it has no value this is the entry-source object itself
|
|
658
704
|
const referencedObject =
|
|
659
705
|
object.referenceObject === '' ? ca.objectAPIName : object.referenceObject;
|
|
706
|
+
// sort list of fields alphabetically
|
|
707
|
+
object.fields.sort();
|
|
660
708
|
// check if object was cached earlier
|
|
661
709
|
if (!this.sfObjects.workflowObjects.includes(referencedObject)) {
|
|
662
710
|
errors.push(`Salesforce object ${referencedObject} not found on connected org.`);
|
|
@@ -666,7 +714,8 @@ class Event extends MetadataType {
|
|
|
666
714
|
) {
|
|
667
715
|
// check if we found fields for the object
|
|
668
716
|
errors.push(
|
|
669
|
-
`
|
|
717
|
+
`Fields for Salesforce object ${referencedObject} could not be checked. Fields selected in entry event: ` +
|
|
718
|
+
object.fields.join(', ')
|
|
670
719
|
);
|
|
671
720
|
} else {
|
|
672
721
|
// check if the fields selected in the eventDefinition are actually available
|
|
@@ -689,7 +738,7 @@ class Event extends MetadataType {
|
|
|
689
738
|
if (!ca.eventDataSummary.includes(object.dePrefix + fieldName)) {
|
|
690
739
|
// TODO instead, remove in postRetrieve and re-add in preDeploy
|
|
691
740
|
errors.push(
|
|
692
|
-
`Field ${object.dePrefix + fieldName} is listed under eventDataConfig for referenceObject
|
|
741
|
+
`Field ${object.dePrefix + fieldName} is listed under eventDataConfig${object.referenceObject ? ` for referenceObject ` + object.referenceObject : ''} but not in eventDataSummary`
|
|
693
742
|
);
|
|
694
743
|
}
|
|
695
744
|
}
|
|
@@ -704,7 +753,11 @@ class Event extends MetadataType {
|
|
|
704
753
|
const fieldPath = fieldName.split(':');
|
|
705
754
|
fieldName = fieldPath.pop();
|
|
706
755
|
const dePrefix = fieldPath.join(':') + ':';
|
|
707
|
-
if (!dePrefixFields[dePrefix]
|
|
756
|
+
if (!dePrefixFields[dePrefix]) {
|
|
757
|
+
errors.push(
|
|
758
|
+
`Field ${dePrefix + fieldName} is listed under eventDataSummary but object ${dePrefix} was not found in eventDataConfig`
|
|
759
|
+
);
|
|
760
|
+
} else if (!dePrefixFields[dePrefix]?.includes(fieldName)) {
|
|
708
761
|
errors.push(
|
|
709
762
|
`Field ${dePrefix + fieldName} is listed under eventDataSummary but not in eventDataConfig`
|
|
710
763
|
);
|
|
@@ -859,19 +912,23 @@ class Event extends MetadataType {
|
|
|
859
912
|
/**
|
|
860
913
|
*
|
|
861
914
|
* @param {string} triggerType e.g. SalesforceObjectTriggerV2, APIEvent, ...
|
|
862
|
-
* @param {
|
|
915
|
+
* @param {configurationArguments} ca trigger[0].configurationArguments
|
|
863
916
|
* @param {string} key of event / journey
|
|
864
|
-
* @param {
|
|
917
|
+
* @param {boolean} isPublished if the current item is published it means we do not need to do contact vs common checks
|
|
918
|
+
* @param {string} [type] optionally provide metadatype for error on missing configurationArguments attributes
|
|
865
919
|
* @returns {Promise.<void>} -
|
|
866
920
|
*/
|
|
867
|
-
static async postRetrieveTasks_SalesforceEntryEvents(triggerType, ca, key, type) {
|
|
921
|
+
static async postRetrieveTasks_SalesforceEntryEvents(triggerType, ca, key, isPublished, type) {
|
|
868
922
|
if (triggerType !== 'SalesforceObjectTriggerV2' || !ca) {
|
|
869
923
|
return;
|
|
870
924
|
}
|
|
871
925
|
// normalize payload because these fields are sometimes set as strings and sometimes as objects
|
|
926
|
+
// @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
|
|
872
927
|
if (ca.objectApiName) {
|
|
873
928
|
// on event only the uppercase version is used. lets make sure we normalize that here.
|
|
929
|
+
// @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
|
|
874
930
|
ca.objectAPIName = ca.objectApiName;
|
|
931
|
+
// @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
|
|
875
932
|
delete ca.objectApiName;
|
|
876
933
|
}
|
|
877
934
|
|
|
@@ -893,7 +950,8 @@ class Event extends MetadataType {
|
|
|
893
950
|
: ca.eventDataConfig;
|
|
894
951
|
ca.eventDataSummary =
|
|
895
952
|
'string' === typeof ca.eventDataSummary
|
|
896
|
-
?
|
|
953
|
+
? // @ts-expect-error transforming this from API-string-format to from mcdev-format
|
|
954
|
+
ca.eventDataSummary.split('; ').filter(Boolean).sort()
|
|
897
955
|
: ca.eventDataSummary;
|
|
898
956
|
ca.passThroughArgument =
|
|
899
957
|
'string' === typeof ca.passThroughArgument
|
|
@@ -913,13 +971,13 @@ class Event extends MetadataType {
|
|
|
913
971
|
await this.getSalesforceObjects(ca.objectAPIName);
|
|
914
972
|
|
|
915
973
|
// check if whats on the journey matches what SF Core offers
|
|
916
|
-
this.checkSalesforceEntryEvents(ca);
|
|
974
|
+
this.checkSalesforceEntryEvents(ca, isPublished);
|
|
917
975
|
}
|
|
918
976
|
|
|
919
977
|
/**
|
|
920
978
|
*
|
|
921
979
|
* @param {string} triggerType e.g. SalesforceObjectTriggerV2, APIEvent, ...
|
|
922
|
-
* @param {
|
|
980
|
+
* @param {configurationArguments} ca trigger[0].configurationArguments
|
|
923
981
|
* @returns {Promise.<void>} -
|
|
924
982
|
*/
|
|
925
983
|
static async preDeployTasks_SalesforceEntryEvents(triggerType, ca) {
|
|
@@ -947,35 +1005,43 @@ class Event extends MetadataType {
|
|
|
947
1005
|
await this.getSalesforceObjects(ca.objectAPIName);
|
|
948
1006
|
|
|
949
1007
|
// check if whats on the journey matches what SF Core offers
|
|
950
|
-
this.checkSalesforceEntryEvents(ca);
|
|
1008
|
+
this.checkSalesforceEntryEvents(ca, false);
|
|
951
1009
|
|
|
952
1010
|
// normalize payload because these fields are sometimes set as strings and sometimes as objects
|
|
1011
|
+
// @ts-expect-error reverting this back from mcdev-format to API format
|
|
953
1012
|
ca.contactKey =
|
|
954
1013
|
'object' === typeof ca.contactKey ? JSON.stringify(ca.contactKey) : ca.contactKey;
|
|
1014
|
+
// @ts-expect-error reverting this back from mcdev-format to API format
|
|
955
1015
|
ca.eventDataConfig =
|
|
956
1016
|
'object' === typeof ca.eventDataConfig
|
|
957
1017
|
? JSON.stringify(ca.eventDataConfig)
|
|
958
1018
|
: ca.eventDataConfig;
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1019
|
+
// @ts-expect-error reverting this back from mcdev-format to API format
|
|
1020
|
+
ca.eventDataSummary = Array.isArray(ca.eventDataSummary)
|
|
1021
|
+
? ca.eventDataSummary.join('; ') + '; '
|
|
1022
|
+
: ca.eventDataSummary;
|
|
1023
|
+
// @ts-expect-error reverting this back from mcdev-format to API format
|
|
963
1024
|
ca.passThroughArgument =
|
|
964
1025
|
'object' === typeof ca.passThroughArgument
|
|
965
1026
|
? JSON.stringify(ca.passThroughArgument)
|
|
966
1027
|
: ca.passThroughArgument;
|
|
1028
|
+
// @ts-expect-error reverting this back from mcdev-format to API format
|
|
967
1029
|
ca.primaryObjectFilterCriteria =
|
|
968
1030
|
'object' === typeof ca.primaryObjectFilterCriteria
|
|
969
1031
|
? JSON.stringify(ca.primaryObjectFilterCriteria)
|
|
970
1032
|
: ca.primaryObjectFilterCriteria;
|
|
1033
|
+
// @ts-expect-error reverting this back from mcdev-format to API format
|
|
971
1034
|
ca.relatedObjectFilterCriteria =
|
|
972
1035
|
'object' === typeof ca.relatedObjectFilterCriteria
|
|
973
1036
|
? JSON.stringify(ca.relatedObjectFilterCriteria)
|
|
974
1037
|
: ca.relatedObjectFilterCriteria;
|
|
975
1038
|
|
|
1039
|
+
// @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
|
|
976
1040
|
if (ca.objectApiName) {
|
|
977
1041
|
// on event only the uppercase version is used. lets make sure we normalize that here.
|
|
1042
|
+
// @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
|
|
978
1043
|
ca.objectAPIName = ca.objectApiName;
|
|
1044
|
+
// @ts-expect-error journeys SOMETIMES spell it "Api" and this script aims to auto-correct that
|
|
979
1045
|
delete ca.objectApiName;
|
|
980
1046
|
}
|
|
981
1047
|
}
|