mcdev 4.2.1 → 4.3.0
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/.github/PULL_REQUEST_TEMPLATE.md +1 -2
- package/.github/pr-labeler.yml +3 -0
- package/.github/workflows/close_issues_on_merge.yml +18 -0
- package/.github/workflows/pr-labeler.yml +19 -0
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/docs/dist/documentation.md +700 -281
- package/lib/Deployer.js +21 -15
- package/lib/Retriever.js +23 -19
- package/lib/cli.js +36 -6
- package/lib/index.js +56 -10
- package/lib/metadataTypes/AccountUser.js +17 -23
- package/lib/metadataTypes/Asset.js +28 -21
- package/lib/metadataTypes/AttributeGroup.js +1 -2
- package/lib/metadataTypes/Automation.js +75 -37
- package/lib/metadataTypes/Campaign.js +4 -3
- package/lib/metadataTypes/ContentArea.js +2 -3
- package/lib/metadataTypes/DataExtension.js +56 -47
- package/lib/metadataTypes/DataExtensionField.js +9 -12
- package/lib/metadataTypes/DataExtensionTemplate.js +2 -3
- package/lib/metadataTypes/DataExtract.js +1 -2
- package/lib/metadataTypes/DataExtractType.js +1 -2
- package/lib/metadataTypes/Discovery.js +3 -4
- package/lib/metadataTypes/Email.js +20 -6
- package/lib/metadataTypes/EmailSendDefinition.js +5 -8
- package/lib/metadataTypes/EventDefinition.js +29 -2
- package/lib/metadataTypes/FileTransfer.js +1 -2
- package/lib/metadataTypes/Filter.js +1 -2
- package/lib/metadataTypes/Folder.js +12 -14
- package/lib/metadataTypes/FtpLocation.js +1 -2
- package/lib/metadataTypes/ImportFile.js +1 -2
- package/lib/metadataTypes/Interaction.js +678 -12
- package/lib/metadataTypes/List.js +36 -33
- package/lib/metadataTypes/MetadataType.js +170 -124
- package/lib/metadataTypes/MobileCode.js +1 -2
- package/lib/metadataTypes/MobileKeyword.js +1 -2
- package/lib/metadataTypes/Query.js +15 -6
- package/lib/metadataTypes/Role.js +10 -11
- package/lib/metadataTypes/Script.js +2 -5
- package/lib/metadataTypes/SetDefinition.js +1 -2
- package/lib/metadataTypes/TransactionalMessage.js +25 -32
- package/lib/metadataTypes/TransactionalSMS.js +3 -4
- package/lib/metadataTypes/TriggeredSendDefinition.js +232 -56
- package/lib/metadataTypes/definitions/Asset.definition.js +1 -1
- package/lib/metadataTypes/definitions/Automation.definition.js +1 -1
- package/lib/metadataTypes/definitions/DataExtension.definition.js +10 -1
- package/lib/metadataTypes/definitions/Email.definition.js +1 -1
- package/lib/metadataTypes/definitions/EmailSendDefinition.definition.js +1 -1
- package/lib/metadataTypes/definitions/EventDefinition.definition.js +40 -1
- package/lib/metadataTypes/definitions/Folder.definition.js +31 -0
- package/lib/metadataTypes/definitions/ImportFile.definition.js +1 -1
- package/lib/metadataTypes/definitions/Interaction.definition.js +47 -26
- package/lib/metadataTypes/definitions/List.definition.js +1 -1
- package/lib/metadataTypes/definitions/Query.definition.js +1 -1
- package/lib/metadataTypes/definitions/Script.definition.js +1 -1
- package/lib/metadataTypes/definitions/TransactionalEmail.definition.js +2 -1
- package/lib/metadataTypes/definitions/TransactionalPush.definition.js +2 -1
- package/lib/metadataTypes/definitions/TransactionalSMS.definition.js +2 -1
- package/lib/metadataTypes/definitions/TriggeredSendDefinition.definition.js +10 -2
- package/lib/util/auth.js +10 -2
- package/lib/util/cli.js +4 -1
- package/lib/util/file.js +7 -3
- package/lib/util/init.js +62 -0
- package/lib/util/util.js +131 -11
- package/package.json +22 -9
- package/test/dataExtension.test.js +10 -10
- package/test/interaction.test.js +123 -0
- package/test/mockRoot/.mcdevrc.json +1 -1
- package/test/mockRoot/deploy/testInstance/testBU/interaction/testExisting_interaction.interaction-meta.json +266 -0
- package/test/mockRoot/deploy/testInstance/testBU/interaction/testNew_interaction.interaction-meta.json +266 -0
- package/test/mockRoot/deploy/testInstance/testBU/transactionalEmail/testExisting_temail.transactionalEmail-meta.json +0 -3
- package/test/query.test.js +8 -8
- package/test/resourceFactory.js +12 -7
- package/test/resources/1111111/dataExtension/retrieve-response.xml +26 -0
- package/test/resources/9999999/data/v1/customobjectdata/key/childBU_dataextension_test/rowset/get-response.json +13 -0
- package/test/resources/9999999/dataFolder/retrieve-response.xml +22 -0
- package/test/resources/9999999/eventDefinition/get-expected.json +34 -0
- package/test/resources/9999999/interaction/build-expected.json +260 -0
- package/test/resources/9999999/interaction/get-expected.json +264 -0
- package/test/resources/9999999/interaction/post-expected.json +264 -0
- package/test/resources/9999999/interaction/put-expected.json +264 -0
- package/test/resources/9999999/interaction/template-expected.json +260 -0
- package/test/resources/9999999/interaction/v1/EventDefinitions/get-response.json +43 -0
- package/test/resources/9999999/interaction/v1/interactions/get-response.json +222 -3
- package/test/resources/9999999/interaction/v1/interactions/post-response.json +280 -0
- package/test/resources/9999999/interaction/v1/interactions/put-response.json +280 -0
- package/test/resources/9999999/messaging/v1/email/definitions/post-response.json +1 -1
- package/test/resources/9999999/query/post-expected.sql +1 -1
- package/test/resources/9999999/transactionalEmail/post-expected.json +1 -1
- package/test/transactionalEmail.test.js +7 -7
- package/test/transactionalPush.test.js +7 -7
- package/test/transactionalSMS.test.js +7 -7
- package/test/utils.js +50 -0
- package/types/mcdev.d.js +1 -0
|
@@ -2,34 +2,700 @@
|
|
|
2
2
|
|
|
3
3
|
const TYPE = require('../../types/mcdev.d');
|
|
4
4
|
const MetadataType = require('./MetadataType');
|
|
5
|
+
const TransactionalEmail = require('./TransactionalEmail');
|
|
6
|
+
const Util = require('../util/util');
|
|
7
|
+
const cache = require('../util/cache');
|
|
8
|
+
const File = require('../util/file');
|
|
5
9
|
|
|
6
10
|
/**
|
|
7
|
-
*
|
|
11
|
+
* Interaction MetadataType
|
|
12
|
+
* ! BETA RELEASE of journey support (v4.3.0); it so far only resolves a limited amount of dependencies and will likely break during cross-BU deployments!
|
|
13
|
+
* id: A unique id of the journey assigned by the journey’s API during its creation
|
|
14
|
+
* key: A unique id of the journey within the MID. Can be generated by the developer
|
|
15
|
+
* definitionId: A unique UUID provided by Salesforce Marketing Cloud. Each version of a journey has a unique DefinitionID while the Id and Key remain the same. Version 1 will have id == definitionId
|
|
8
16
|
*
|
|
9
17
|
* @augments MetadataType
|
|
10
18
|
*/
|
|
11
19
|
class Interaction extends MetadataType {
|
|
12
20
|
/**
|
|
13
21
|
* Retrieves Metadata of Interaction
|
|
14
|
-
* Endpoint /interaction/v1/interactions?extras=all&pageSize=50000 return 50000 Scripts with all details.
|
|
15
22
|
*
|
|
16
23
|
* @param {string} retrieveDir Directory where retrieved metadata directory will be saved
|
|
17
24
|
* @param {void} [_] unused parameter
|
|
18
25
|
* @param {void} [__] unused parameter
|
|
19
|
-
* @param {void} [___] unused parameter
|
|
20
26
|
* @param {string} [key] customer key of single item to retrieve
|
|
21
27
|
* @returns {Promise.<TYPE.MetadataTypeMapObj>} Promise
|
|
22
28
|
*/
|
|
23
|
-
static retrieve(retrieveDir, _, __,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
static async retrieve(retrieveDir, _, __, key) {
|
|
30
|
+
if (retrieveDir) {
|
|
31
|
+
// only print this during retrieve, not during retrieveForCache
|
|
32
|
+
Util.logBeta(this.definition.type);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let singleKey = '';
|
|
36
|
+
let mode = 'key';
|
|
37
|
+
if (key) {
|
|
38
|
+
/* eslint-disable unicorn/prefer-ternary */
|
|
39
|
+
|
|
40
|
+
if (key.startsWith('id:') || key.startsWith('%23')) {
|
|
41
|
+
// ! allow selecting journeys by ID because that's what users see in the URL
|
|
42
|
+
// if the key started with %23 assume an ID was copied from the URL but the user forgot to prefix it with id:
|
|
43
|
+
|
|
44
|
+
// remove id: or %23
|
|
45
|
+
singleKey = key.slice(3);
|
|
46
|
+
if (singleKey.startsWith('%23')) {
|
|
47
|
+
// in the journey URL the Id is prefixed with an HTML-encoded "#" which could accidentally be copied by users
|
|
48
|
+
// despite the slicing above, this still needs testing here because users might have prefixed the ID with id: but did not know to remove the #23
|
|
49
|
+
singleKey = singleKey.slice(3);
|
|
50
|
+
}
|
|
51
|
+
if (singleKey.includes('/')) {
|
|
52
|
+
// in the journey URL the version is appended after the ID, separated by a forward-slash. Needs to be removed from the ID for the retrieve as we always aim to retrieve the latest version only
|
|
53
|
+
singleKey = singleKey.split('/')[0];
|
|
54
|
+
}
|
|
55
|
+
mode = 'id';
|
|
56
|
+
} else {
|
|
57
|
+
// assume actual key was provided
|
|
58
|
+
singleKey = 'key:' + encodeURIComponent(key);
|
|
59
|
+
}
|
|
60
|
+
/* eslint-enable unicorn/prefer-ternary */
|
|
61
|
+
}
|
|
62
|
+
// full details for retrieve, only base data for caching; reduces caching time from minutes to seconds
|
|
63
|
+
const extras = retrieveDir ? 'all' : '';
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
return await super.retrieveREST(
|
|
67
|
+
retrieveDir,
|
|
68
|
+
`/interaction/v1/interactions/${singleKey}?extras=${extras}`,
|
|
69
|
+
null,
|
|
70
|
+
null,
|
|
71
|
+
key
|
|
72
|
+
);
|
|
73
|
+
} catch (ex) {
|
|
74
|
+
// if the interaction does not exist, the API returns an error code which would otherwise bring execution to a hold
|
|
75
|
+
if (
|
|
76
|
+
[
|
|
77
|
+
'Interaction matching key not found.',
|
|
78
|
+
'Must provide a valid ID or Key parameter',
|
|
79
|
+
].includes(ex.message)
|
|
80
|
+
) {
|
|
81
|
+
Util.logger.info(
|
|
82
|
+
`Downloaded: ${this.definition.type} (0)${Util.getKeysString(
|
|
83
|
+
mode === 'id' ? singleKey : key,
|
|
84
|
+
mode === 'id'
|
|
85
|
+
)}`
|
|
86
|
+
);
|
|
87
|
+
} else {
|
|
88
|
+
throw ex;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Delete a metadata item from the specified business unit
|
|
94
|
+
*
|
|
95
|
+
* @param {string} key Identifier of item
|
|
96
|
+
* @returns {Promise.<boolean>} deletion success status
|
|
97
|
+
*/
|
|
98
|
+
static async deleteByKey(key) {
|
|
99
|
+
let version;
|
|
100
|
+
let singleKey = '';
|
|
101
|
+
/* eslint-disable unicorn/prefer-ternary */
|
|
102
|
+
if (key.startsWith('id:') || key.startsWith('%23')) {
|
|
103
|
+
// ! allow selecting journeys by ID because that's what users see in the URL
|
|
104
|
+
// if the key started with %23 assume an ID was copied from the URL but the user forgot to prefix it with id:
|
|
105
|
+
|
|
106
|
+
// remove id: or %23
|
|
107
|
+
singleKey = key.slice(3);
|
|
108
|
+
if (singleKey.startsWith('%23')) {
|
|
109
|
+
// in the journey URL the Id is prefixed with an HTML-encoded "#" which could accidentally be copied by users
|
|
110
|
+
// despite the slicing above, this still needs testing here because users might have prefixed the ID with id: but did not know to remove the #23
|
|
111
|
+
singleKey = singleKey.slice(3);
|
|
112
|
+
}
|
|
113
|
+
if (singleKey.includes('/')) {
|
|
114
|
+
// in the journey URL the version is appended after the ID, separated by a forward-slash.
|
|
115
|
+
[singleKey, version] = singleKey.split('/');
|
|
116
|
+
}
|
|
117
|
+
} else {
|
|
118
|
+
if (key.includes('/')) {
|
|
119
|
+
// in the journey URL the version is appended after the ID, separated by a forward-slash.
|
|
120
|
+
[key, version] = key.split('/');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// delete by key with specified version does not work, therefore we need to get the ID first
|
|
124
|
+
const response = await this.client.rest.get(
|
|
125
|
+
`/interaction/v1/interactions/key:${encodeURIComponent(key)}?extras=`
|
|
126
|
+
);
|
|
127
|
+
const results = this.parseResponseBody(response, key);
|
|
128
|
+
singleKey = results[key].id;
|
|
129
|
+
Util.logger.debug(`Deleting interaction ${key} via its ID ${singleKey}`);
|
|
130
|
+
}
|
|
131
|
+
if (!/^\d+$/.test(version)) {
|
|
132
|
+
throw new TypeError(
|
|
133
|
+
'Version is required for deleting interactions to avoid accidental deletion of the wrong item. Please append it at the end of the key or id, separated by forward-slash. Example for deleting version 4: ' +
|
|
134
|
+
key +
|
|
135
|
+
'/4'
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
Util.logger.warn(
|
|
139
|
+
`Deleting Interactions via this command breaks following retrieve-by-key/id requests until you've deployed/created a new draft version! You can get still get the latest available version of your journey by retrieving all interactions on this BU.`
|
|
32
140
|
);
|
|
141
|
+
/* eslint-enable unicorn/prefer-ternary */
|
|
142
|
+
return super.deleteByKeyREST(
|
|
143
|
+
'/interaction/v1/interactions/' + singleKey + `?versionNumber=${version}`,
|
|
144
|
+
key,
|
|
145
|
+
false
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Deploys metadata - merely kept here to be able to print {@link Util.logBeta} once per deploy
|
|
150
|
+
*
|
|
151
|
+
* @param {TYPE.MetadataTypeMap} metadata metadata mapped by their keyField
|
|
152
|
+
* @param {string} deployDir directory where deploy metadata are saved
|
|
153
|
+
* @param {string} retrieveDir directory where metadata after deploy should be saved
|
|
154
|
+
* @returns {Promise.<TYPE.MetadataTypeMap>} Promise of keyField => metadata map
|
|
155
|
+
*/
|
|
156
|
+
static async deploy(metadata, deployDir, retrieveDir) {
|
|
157
|
+
Util.logBeta(this.definition.type);
|
|
158
|
+
return super.deploy(metadata, deployDir, retrieveDir);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Updates a single item
|
|
163
|
+
*
|
|
164
|
+
* @param {TYPE.MetadataTypeItem} metadata a single item
|
|
165
|
+
* @returns {Promise} Promise
|
|
166
|
+
*/
|
|
167
|
+
static update(metadata) {
|
|
168
|
+
return super.updateREST(metadata, '/interaction/v1/interactions/', true);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Creates a single item
|
|
173
|
+
*
|
|
174
|
+
* @param {TYPE.MetadataTypeItem} metadata a single item
|
|
175
|
+
* @returns {Promise} Promise
|
|
176
|
+
*/
|
|
177
|
+
static create(metadata) {
|
|
178
|
+
return super.createREST(metadata, '/interaction/v1/interactions/');
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Helper for writing Metadata to disk, used for Retrieve and deploy
|
|
182
|
+
*
|
|
183
|
+
* @param {TYPE.MetadataTypeMap} results metadata results from deploy
|
|
184
|
+
* @param {string} retrieveDir directory where metadata should be stored after deploy/retrieve
|
|
185
|
+
* @param {string} [overrideType] for use when there is a subtype (such as folder-queries)
|
|
186
|
+
* @param {TYPE.TemplateMap} [templateVariables] variables to be replaced in the metadata
|
|
187
|
+
* @returns {Promise.<TYPE.MetadataTypeMap>} Promise of saved metadata
|
|
188
|
+
*/
|
|
189
|
+
static async saveResults(results, retrieveDir, overrideType, templateVariables) {
|
|
190
|
+
if (Object.keys(results).length) {
|
|
191
|
+
// only execute the following if records were found
|
|
192
|
+
await this._postRetrieveTasksBulk(results);
|
|
193
|
+
}
|
|
194
|
+
return super.saveResults(results, retrieveDir, overrideType, templateVariables);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* helper for Interaction's {@link saveResults}. Gets executed after retreive of metadata type and
|
|
199
|
+
*
|
|
200
|
+
* @param {TYPE.MetadataTypeMap} metadataMap key=customer key, value=metadata
|
|
201
|
+
*/
|
|
202
|
+
static async _postRetrieveTasksBulk(metadataMap) {
|
|
203
|
+
let needTransactionalEmail = false;
|
|
204
|
+
for (const key in metadataMap) {
|
|
205
|
+
if (metadataMap[key].definitionType == 'Transactional') {
|
|
206
|
+
needTransactionalEmail = true;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
if (needTransactionalEmail && !cache.getCache()?.transactionalEmail) {
|
|
211
|
+
// ! interaction and transactionalEmail both link to each other. caching transactionalEmail here "manually", assuming that it's quicker than the other way round
|
|
212
|
+
Util.logger.info(' - Caching dependent Metadata: transactionalEmail');
|
|
213
|
+
TransactionalEmail.buObject = this.buObject;
|
|
214
|
+
TransactionalEmail.client = this.client;
|
|
215
|
+
TransactionalEmail.properties = this.properties;
|
|
216
|
+
const result = await TransactionalEmail.retrieveForCache();
|
|
217
|
+
cache.setMetadata('transactionalEmail', result.metadata);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* manages post retrieve steps
|
|
223
|
+
* ! BETA RELEASE of journey support (v4.3.0); it so far only resolves a limited amount of dependencies and will likely break during cross-BU deployments!
|
|
224
|
+
*
|
|
225
|
+
* @param {TYPE.MetadataTypeItem} metadata a single query
|
|
226
|
+
* @returns {TYPE.MetadataTypeItem} Array with one metadata object and one query string
|
|
227
|
+
*/
|
|
228
|
+
static postRetrieveTasks(metadata) {
|
|
229
|
+
// folder
|
|
230
|
+
super.setFolderPath(metadata);
|
|
231
|
+
|
|
232
|
+
switch (metadata.definitionType) {
|
|
233
|
+
case 'Multistep': {
|
|
234
|
+
// Multi-Step Journey
|
|
235
|
+
// ~~~ TRIGGERS ~~~~
|
|
236
|
+
// eventDefinition / definitionType==='Multistep' && channel==='' && triggers[].type === 'EmailAudience'|'APIEvent'
|
|
237
|
+
if (
|
|
238
|
+
metadata.triggers?.length > 0 &&
|
|
239
|
+
metadata.triggers[0].metaData?.eventDefinitionKey
|
|
240
|
+
) {
|
|
241
|
+
// trigger found; there can only be one entry in this array
|
|
242
|
+
try {
|
|
243
|
+
const edId = cache.searchForField(
|
|
244
|
+
'eventDefinition',
|
|
245
|
+
metadata.triggers[0].metaData.eventDefinitionKey,
|
|
246
|
+
'eventDefinitionKey',
|
|
247
|
+
'id'
|
|
248
|
+
);
|
|
249
|
+
if (metadata.triggers[0].metaData.eventDefinitionId !== edId) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`eventDefinitionId not matching Id found on eventDefinition with key in eventDefinitionKey`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
delete metadata.triggers[0].metaData.eventDefinitionId;
|
|
255
|
+
} catch (ex) {
|
|
256
|
+
Util.logger.warn(
|
|
257
|
+
` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
|
|
258
|
+
metadata[this.definition.keyField]
|
|
259
|
+
}): ${ex.message}.`
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// ~~~ ACTIVITIES ~~~~
|
|
265
|
+
|
|
266
|
+
// triggeredSend + email+asset / activities[].type === 'EMAILV2'
|
|
267
|
+
// TODO email / asset
|
|
268
|
+
for (const item of metadata.activities) {
|
|
269
|
+
// check if all triggeredSends are there
|
|
270
|
+
try {
|
|
271
|
+
if (item.configurationArguments?.triggeredSendKey) {
|
|
272
|
+
// triggeredSendKey is not always set but triggeredSendId is
|
|
273
|
+
cache.searchForField(
|
|
274
|
+
'triggeredSendDefinition',
|
|
275
|
+
item.configurationArguments.triggeredSendKey,
|
|
276
|
+
'CustomerKey',
|
|
277
|
+
'CustomerKey'
|
|
278
|
+
);
|
|
279
|
+
delete item.configurationArguments.triggeredSendId;
|
|
280
|
+
} else if (item.configurationArguments?.triggeredSendId) {
|
|
281
|
+
// triggeredSendKey is not always set but triggeredSendId is
|
|
282
|
+
item.configurationArguments.triggeredSendKey = cache.searchForField(
|
|
283
|
+
'triggeredSendDefinition',
|
|
284
|
+
item.configurationArguments.triggeredSendId,
|
|
285
|
+
'ObjectID',
|
|
286
|
+
'CustomerKey'
|
|
287
|
+
);
|
|
288
|
+
delete item.configurationArguments.triggeredSendId;
|
|
289
|
+
}
|
|
290
|
+
} catch (ex) {
|
|
291
|
+
Util.logger.warn(
|
|
292
|
+
` - ${this.definition.type} '${metadata[this.definition.nameField]}' (${
|
|
293
|
+
metadata[this.definition.keyField]
|
|
294
|
+
}): Could not find triggeredSendDefinition (${ex.message})`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// TODO: Filters / activities[].type === 'MULTICRITERIADECISION'
|
|
300
|
+
// - activities[].arguments.filterResult
|
|
301
|
+
// - activities[].arguments.configurationArguments.criteria
|
|
302
|
+
|
|
303
|
+
// TODO: wait activity / activities[].type === 'WAIT'
|
|
304
|
+
|
|
305
|
+
// TODO: journey template id? / metaData.templateId
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
case 'Quicksend': {
|
|
309
|
+
// Single Send Journey
|
|
310
|
+
Util.logger.warn(
|
|
311
|
+
` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
|
|
312
|
+
metadata[this.definition.keyField]
|
|
313
|
+
}): definitionType Quicksend is not fully supported yet.`
|
|
314
|
+
);
|
|
315
|
+
// ~~~ TRIGGERS ~~~~
|
|
316
|
+
// eventDefinition && triggers[].type === 'ContactAudience'
|
|
317
|
+
if (
|
|
318
|
+
metadata.triggers?.length > 0 &&
|
|
319
|
+
metadata.triggers[0].metaData?.eventDefinitionKey
|
|
320
|
+
) {
|
|
321
|
+
// trigger found; there can only be one entry in this array
|
|
322
|
+
try {
|
|
323
|
+
const edId = cache.searchForField(
|
|
324
|
+
'eventDefinition',
|
|
325
|
+
metadata.triggers[0].metaData.eventDefinitionKey,
|
|
326
|
+
'eventDefinitionKey',
|
|
327
|
+
'id'
|
|
328
|
+
);
|
|
329
|
+
if (metadata.triggers[0].metaData.eventDefinitionId !== edId) {
|
|
330
|
+
throw new Error(
|
|
331
|
+
` - ${this.definition.type} ${
|
|
332
|
+
metadata[this.definition.nameField]
|
|
333
|
+
} (${
|
|
334
|
+
metadata[this.definition.keyField]
|
|
335
|
+
}): eventDefinitionId not matching Id found on eventDefinition with key in eventDefinitionKey`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
delete metadata.triggers[0].metaData.eventDefinitionId;
|
|
339
|
+
} catch (ex) {
|
|
340
|
+
Util.logger.warn(
|
|
341
|
+
` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
|
|
342
|
+
metadata[this.definition.keyField]
|
|
343
|
+
}): ${ex.message}.`
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ~~~ ACTIVITIES ~~~~
|
|
349
|
+
try {
|
|
350
|
+
// TODO channel=='email'
|
|
351
|
+
// TODO channel=='sms'
|
|
352
|
+
// TODO channel=='push' / activities[].type === 'PUSHNOTIFICATIONACTIVITY'
|
|
353
|
+
} catch (ex) {
|
|
354
|
+
Util.logger.warn(
|
|
355
|
+
` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
|
|
356
|
+
metadata[this.definition.keyField]
|
|
357
|
+
}): ${ex.message}.`
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
case 'Transactional': {
|
|
363
|
+
// Transactional Send Journey
|
|
364
|
+
// ~~~ TRIGGERS ~~~~
|
|
365
|
+
// ! journeys so far only supports transactional EMAIL messages. SMS and Push do not create their own journey.
|
|
366
|
+
// ! transactional (email) journeys only have a dummy trigger without real content.
|
|
367
|
+
// transactionalEmail / definitionType==='Transactional' && channel==='email' && triggers[].type === 'transactional-api'
|
|
368
|
+
// --> nothing to do here
|
|
369
|
+
|
|
370
|
+
// ~~~ ACTIVITIES ~~~~
|
|
371
|
+
// ! transactional (email) journeys only have one activity (type=EMAILV2) which links back to the transactionalEmail ()
|
|
372
|
+
switch (metadata.channel) {
|
|
373
|
+
case 'email': {
|
|
374
|
+
if (
|
|
375
|
+
metadata.activities?.length > 0 &&
|
|
376
|
+
metadata.activities[0].configurationArguments?.triggeredSendKey
|
|
377
|
+
) {
|
|
378
|
+
// trigger found; there can only be one entry in this array
|
|
379
|
+
try {
|
|
380
|
+
const tEmailId = cache.searchForField(
|
|
381
|
+
'transactionalEmail',
|
|
382
|
+
metadata.activities[0].configurationArguments?.triggeredSendKey,
|
|
383
|
+
'definitionKey',
|
|
384
|
+
'definitionId'
|
|
385
|
+
);
|
|
386
|
+
if (
|
|
387
|
+
tEmailId !=
|
|
388
|
+
metadata.activities[0].configurationArguments?.triggeredSendId
|
|
389
|
+
) {
|
|
390
|
+
throw new Error(
|
|
391
|
+
` - ${this.definition.type} ${
|
|
392
|
+
metadata[this.definition.nameField]
|
|
393
|
+
} (${
|
|
394
|
+
metadata[this.definition.keyField]
|
|
395
|
+
}): transactionalEmailId not matching Id found on transactionalEmail with key in transactionalEmailKey`
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
if (
|
|
399
|
+
metadata.activities[0].metaData?.highThroughput
|
|
400
|
+
?.definitionKey &&
|
|
401
|
+
metadata.activities[0].metaData?.highThroughput
|
|
402
|
+
?.definitionKey !=
|
|
403
|
+
metadata.activities[0].configurationArguments
|
|
404
|
+
?.triggeredSendKey
|
|
405
|
+
) {
|
|
406
|
+
throw new Error(
|
|
407
|
+
` - ${this.definition.type} ${
|
|
408
|
+
metadata[this.definition.nameField]
|
|
409
|
+
} (${
|
|
410
|
+
metadata[this.definition.keyField]
|
|
411
|
+
}): metaData.highThroughput.definitionKey not matching key in configurationArguments.transactionalEmailKey`
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
} catch (ex) {
|
|
415
|
+
Util.logger.warn(
|
|
416
|
+
` - ${this.definition.type} ${
|
|
417
|
+
metadata[this.definition.nameField]
|
|
418
|
+
} (${metadata[this.definition.keyField]}): ${ex.message}.`
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
default: {
|
|
426
|
+
// it is expected that we'll see 'sms' and 'push' here in the future
|
|
427
|
+
Util.logger.warn(
|
|
428
|
+
` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
|
|
429
|
+
metadata[this.definition.keyField]
|
|
430
|
+
}): channel ${
|
|
431
|
+
metadata.channel
|
|
432
|
+
} is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
default: {
|
|
440
|
+
Util.logger.warn(
|
|
441
|
+
` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
|
|
442
|
+
metadata[this.definition.keyField]
|
|
443
|
+
}): definitionType ${
|
|
444
|
+
metadata.definitionType
|
|
445
|
+
} is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return metadata;
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* prepares a TSD for deployment
|
|
454
|
+
* ! BETA RELEASE of journey support (v4.3.0); it so far only resolves a limited amount of dependencies and will likely break during cross-BU deployments!
|
|
455
|
+
*
|
|
456
|
+
* @param {TYPE.MetadataTypeItem} metadata of a single TSD
|
|
457
|
+
* @returns {TYPE.MetadataTypeItem} metadata object
|
|
458
|
+
*/
|
|
459
|
+
static async preDeployTasks(metadata) {
|
|
460
|
+
if (metadata.status !== 'Draft') {
|
|
461
|
+
metadata.status !== 'Draft';
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// folder
|
|
465
|
+
super.setFolderId(metadata);
|
|
466
|
+
|
|
467
|
+
switch (metadata.definitionType) {
|
|
468
|
+
case 'Multistep': {
|
|
469
|
+
// Multi-Step Journey
|
|
470
|
+
// ~~~ TRIGGERS ~~~~
|
|
471
|
+
|
|
472
|
+
// eventDefinition / definitionType==='Multistep' && channel==='' && triggers[].type === 'EmailAudience'|'APIEvent'
|
|
473
|
+
if (
|
|
474
|
+
metadata.triggers?.length > 0 &&
|
|
475
|
+
metadata.triggers[0].metaData?.eventDefinitionKey
|
|
476
|
+
) {
|
|
477
|
+
// trigger found; there can only be one entry in this array
|
|
478
|
+
metadata.triggers[0].metaData.eventDefinitionId = cache.searchForField(
|
|
479
|
+
'eventDefinition',
|
|
480
|
+
metadata.triggers[0].metaData.eventDefinitionKey,
|
|
481
|
+
'eventDefinitionKey',
|
|
482
|
+
'id'
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// transactionalEmail / definitionType==='Transactional' && channel==='email' && triggers[].type === 'transactional-api'
|
|
487
|
+
|
|
488
|
+
// ~~~ ACTIVITIES ~~~~
|
|
489
|
+
|
|
490
|
+
// triggeredSend + email+asset / activities[].type === 'EMAILV2'
|
|
491
|
+
// TODO email / asset
|
|
492
|
+
for (const item of metadata.activities) {
|
|
493
|
+
// check if all triggeredSends are there
|
|
494
|
+
if (!item.configurationArguments?.triggeredSendKey) {
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
// triggeredSendKey is not always set but triggeredSendId is
|
|
498
|
+
item.configurationArguments.triggeredSendId = cache.searchForField(
|
|
499
|
+
'triggeredSendDefinition',
|
|
500
|
+
item.configurationArguments.triggeredSendKey,
|
|
501
|
+
'CustomerKey',
|
|
502
|
+
'ObjectID'
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// TODO: Filters / activities[].type === 'MULTICRITERIADECISION'
|
|
507
|
+
// - activities[].arguments.filterResult
|
|
508
|
+
// - activities[].arguments.configurationArguments.criteria
|
|
509
|
+
|
|
510
|
+
// TODO: wait activity / activities[].type === 'WAIT'
|
|
511
|
+
|
|
512
|
+
// TODO: journey template id? / metaData.templateId
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
case 'Quicksend': {
|
|
516
|
+
// Single Send Journey
|
|
517
|
+
Util.logger.warn(
|
|
518
|
+
` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
|
|
519
|
+
metadata[this.definition.keyField]
|
|
520
|
+
}): definitionType Quicksend is not supported yet and might fail to deploy.`
|
|
521
|
+
);
|
|
522
|
+
// ~~~ TRIGGERS ~~~~
|
|
523
|
+
// eventDefinition && triggers[].type === 'ContactAudience'
|
|
524
|
+
if (
|
|
525
|
+
metadata.triggers?.length > 0 &&
|
|
526
|
+
metadata.triggers[0].metaData?.eventDefinitionKey
|
|
527
|
+
) {
|
|
528
|
+
// trigger found; there can only be one entry in this array
|
|
529
|
+
try {
|
|
530
|
+
metadata.triggers[0].metaData.eventDefinitionId = cache.searchForField(
|
|
531
|
+
'eventDefinition',
|
|
532
|
+
metadata.triggers[0].metaData?.eventDefinitionKey,
|
|
533
|
+
'eventDefinitionKey',
|
|
534
|
+
'id'
|
|
535
|
+
);
|
|
536
|
+
} catch (ex) {
|
|
537
|
+
Util.logger.warn(
|
|
538
|
+
` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
|
|
539
|
+
metadata[this.definition.keyField]
|
|
540
|
+
}): ${ex.message}.`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// ~~~ ACTIVITIES ~~~~
|
|
546
|
+
try {
|
|
547
|
+
// TODO channel=='email'
|
|
548
|
+
// TODO channel=='sms'
|
|
549
|
+
// TODO channel=='push' / activities[].type === 'PUSHNOTIFICATIONACTIVITY'
|
|
550
|
+
} catch (ex) {
|
|
551
|
+
Util.logger.warn(
|
|
552
|
+
` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
|
|
553
|
+
metadata[this.definition.keyField]
|
|
554
|
+
}): ${ex.message}.`
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
case 'Transactional': {
|
|
560
|
+
// Transactional Send Journey
|
|
561
|
+
// ~~~ TRIGGERS ~~~~
|
|
562
|
+
// ! journeys so far transactional EMAIL messages. SMS and Push do not create their own journey.
|
|
563
|
+
// ! transactional (email) journeys only have a dummy trigger without real content.
|
|
564
|
+
|
|
565
|
+
// transactionalEmail / definitionType==='Transactional' && channel==='email' && triggers[].type === 'transactional-api'
|
|
566
|
+
// --> nothing to do here
|
|
567
|
+
|
|
568
|
+
// ~~~ ACTIVITIES ~~~~
|
|
569
|
+
// ! transactional (email) journeys only have one activity (type=EMAILV2) which links back to the transactionalEmail ()
|
|
570
|
+
switch (metadata.channel) {
|
|
571
|
+
case 'email': {
|
|
572
|
+
if (
|
|
573
|
+
metadata.activities?.length > 0 &&
|
|
574
|
+
metadata.activities[0].configurationArguments?.triggeredSendKey
|
|
575
|
+
) {
|
|
576
|
+
// trigger found; there can only be one entry in this array
|
|
577
|
+
metadata.activities[0].configurationArguments.triggeredSendId =
|
|
578
|
+
cache.searchForField(
|
|
579
|
+
'transactionalEmail',
|
|
580
|
+
metadata.activities[0].configurationArguments?.triggeredSendKey,
|
|
581
|
+
'definitionKey',
|
|
582
|
+
'definitionId'
|
|
583
|
+
);
|
|
584
|
+
if (
|
|
585
|
+
metadata.activities[0].metaData?.highThroughput?.definitionKey &&
|
|
586
|
+
metadata.activities[0].metaData?.highThroughput?.definitionKey !=
|
|
587
|
+
metadata.activities[0].configurationArguments?.triggeredSendKey
|
|
588
|
+
) {
|
|
589
|
+
throw new Error(
|
|
590
|
+
` - ${this.definition.type} ${
|
|
591
|
+
metadata[this.definition.nameField]
|
|
592
|
+
} (${
|
|
593
|
+
metadata[this.definition.keyField]
|
|
594
|
+
}): metaData.highThroughput.definitionKey not matching key in configurationArguments.transactionalEmailKey`
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
default: {
|
|
602
|
+
// it is expected that we'll see 'sms' and 'push' here in the future
|
|
603
|
+
throw new Error(
|
|
604
|
+
` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
|
|
605
|
+
metadata[this.definition.keyField]
|
|
606
|
+
}): channel ${
|
|
607
|
+
metadata.channel
|
|
608
|
+
} is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
default: {
|
|
616
|
+
throw new Error(
|
|
617
|
+
` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
|
|
618
|
+
metadata[this.definition.keyField]
|
|
619
|
+
}): definitionType ${
|
|
620
|
+
metadata.definitionType
|
|
621
|
+
} is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return metadata;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
*
|
|
630
|
+
* @param {TYPE.MetadataTypeItem} metadata single metadata itme
|
|
631
|
+
* @param {string} metadataKey key of item we are looking at
|
|
632
|
+
* @param {boolean} hasError error flag from previous code
|
|
633
|
+
* @param {TYPE.MetadataTypeItemDiff[]} metadataToUpdate list of items to update
|
|
634
|
+
* @param {TYPE.MetadataTypeItem[]} metadataToCreate list of items to create
|
|
635
|
+
*/
|
|
636
|
+
static createOrUpdate(metadata, metadataKey, hasError, metadataToUpdate, metadataToCreate) {
|
|
637
|
+
const normalizedKey = File.reverseFilterIllegalFilenames(
|
|
638
|
+
metadata[metadataKey][this.definition.keyField]
|
|
639
|
+
);
|
|
640
|
+
// Update if it already exists; Create it if not
|
|
641
|
+
if (Util.logger.level === 'debug' && metadata[metadataKey][this.definition.idField]) {
|
|
642
|
+
// TODO: re-evaluate in future releases if & when we managed to solve folder dependencies once and for all
|
|
643
|
+
// only used if resource is excluded from cache and we still want to update it
|
|
644
|
+
// needed e.g. to rewire lost folders
|
|
645
|
+
Util.logger.warn(
|
|
646
|
+
' - Hotfix for non-cachable resource found in deploy folder. Trying update:'
|
|
647
|
+
);
|
|
648
|
+
Util.logger.warn(JSON.stringify(metadata[metadataKey]));
|
|
649
|
+
if (hasError) {
|
|
650
|
+
metadataToUpdate.push(null);
|
|
651
|
+
} else {
|
|
652
|
+
metadataToUpdate.push({
|
|
653
|
+
before: {},
|
|
654
|
+
after: metadata[metadataKey],
|
|
655
|
+
});
|
|
656
|
+
}
|
|
657
|
+
} else {
|
|
658
|
+
const cachedVersion = cache.getByKey(this.definition.type, normalizedKey);
|
|
659
|
+
if (cachedVersion && cachedVersion.status === 'Draft') {
|
|
660
|
+
// normal way of processing update files
|
|
661
|
+
if (!this.hasChanged(cachedVersion, metadata[metadataKey])) {
|
|
662
|
+
hasError = true;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
if (hasError) {
|
|
666
|
+
// do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
|
|
667
|
+
metadataToUpdate.push(null);
|
|
668
|
+
} else {
|
|
669
|
+
// add ObjectId to allow actual update
|
|
670
|
+
metadata[metadataKey][this.definition.idField] =
|
|
671
|
+
cachedVersion[this.definition.idField];
|
|
672
|
+
// add ObjectId to allow actual update
|
|
673
|
+
metadata[metadataKey].version = cachedVersion.version;
|
|
674
|
+
|
|
675
|
+
metadataToUpdate.push({
|
|
676
|
+
before: cachedVersion,
|
|
677
|
+
after: metadata[metadataKey],
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
} else {
|
|
681
|
+
if (hasError) {
|
|
682
|
+
// do this in case something went wrong during pre-deploy steps to ensure the total counter is correct
|
|
683
|
+
metadataToCreate.push(null);
|
|
684
|
+
} else {
|
|
685
|
+
if (cachedVersion) {
|
|
686
|
+
Util.logger.info(
|
|
687
|
+
` - Found ${this.definition.type} ${
|
|
688
|
+
metadata[metadataKey][this.definition.nameField]
|
|
689
|
+
} (${
|
|
690
|
+
metadata[metadataKey][this.definition.keyField]
|
|
691
|
+
}) on BU, but it is not in Draft status. Will create new version.`
|
|
692
|
+
);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
metadataToCreate.push(metadata[metadataKey]);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
33
699
|
}
|
|
34
700
|
}
|
|
35
701
|
|