mcdev 7.6.1 → 7.6.3
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/workflows/close_issues_on_merge.yml +1 -1
- package/.github/workflows/coverage-base-update.yml +2 -2
- package/.github/workflows/coverage.yml +1 -1
- package/.vscode/extensions.json +1 -0
- package/.vscode/settings.json +21 -1
- package/@types/lib/index.d.ts +15 -15
- package/@types/lib/index.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Automation.d.ts +6 -0
- package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
- package/@types/lib/metadataTypes/DataExtract.d.ts +7 -0
- package/@types/lib/metadataTypes/DataExtract.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Event.d.ts.map +1 -1
- package/@types/lib/metadataTypes/FileLocation.d.ts +39 -5
- package/@types/lib/metadataTypes/FileLocation.d.ts.map +1 -1
- package/@types/lib/metadataTypes/Journey.d.ts +13 -3
- package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
- package/@types/lib/metadataTypes/MetadataType.d.ts.map +1 -1
- package/@types/lib/metadataTypes/SendClassification.d.ts +7 -0
- package/@types/lib/metadataTypes/SendClassification.d.ts.map +1 -1
- package/@types/lib/metadataTypes/SenderProfile.d.ts +7 -0
- package/@types/lib/metadataTypes/SenderProfile.d.ts.map +1 -1
- package/@types/lib/metadataTypes/definitions/Automation.definition.d.ts +6 -0
- package/@types/lib/metadataTypes/definitions/FileLocation.definition.d.ts +24 -0
- package/@types/lib/util/cache.d.ts +1 -1
- package/@types/lib/util/cache.d.ts.map +1 -1
- package/@types/lib/util/config.d.ts.map +1 -1
- package/@types/lib/util/file.d.ts.map +1 -1
- package/LICENSE +1 -1
- package/README.md +1 -1
- package/boilerplate/config.json +1 -0
- package/boilerplate/files/.vscode/extensions.json +1 -0
- package/boilerplate/forcedUpdates.json +4 -0
- package/lib/index.js +34 -17
- package/lib/metadataTypes/Asset.js +31 -8
- package/lib/metadataTypes/Automation.js +45 -25
- package/lib/metadataTypes/DataExtension.js +1 -1
- package/lib/metadataTypes/DataExtract.js +20 -0
- package/lib/metadataTypes/Event.js +20 -5
- package/lib/metadataTypes/FileLocation.js +43 -5
- package/lib/metadataTypes/Journey.js +176 -77
- package/lib/metadataTypes/MetadataType.js +22 -9
- package/lib/metadataTypes/SendClassification.js +20 -0
- package/lib/metadataTypes/SenderProfile.js +20 -0
- package/lib/metadataTypes/definitions/Automation.definition.js +6 -0
- package/lib/metadataTypes/definitions/FileLocation.definition.js +22 -2
- package/lib/util/cache.js +8 -3
- package/lib/util/config.js +6 -0
- package/lib/util/file.js +17 -14
- package/package.json +17 -18
- package/prepare-release.js +37 -0
- package/test/general.test.js +69 -6
- package/test/mockRoot/.mcdevrc.json +2 -1
- package/test/mockRoot/deploy/testInstance/testBU/journey/testExisting_journey_Multistep.journey-meta.json +418 -0
- package/test/mockRoot/deploy/testInstance/testBU/journey/testExisting_temail.journey-meta.json +212 -0
- package/test/mockRoot/deploy/testInstance/testBU/journey/testExisting_temail_notPublished.journey-meta.json +217 -0
- package/test/resourceFactory.js +12 -10
- package/test/resources/9999999/asset/build-templatebasedemail-expected.json +0 -1
- package/test/resources/9999999/asset/retrieve-templatebasedemail-expected.json +0 -1
- package/test/resources/9999999/asset/template-templatebasedemail-expected.json +0 -1
- package/test/resources/9999999/asset-deploy/block/testNew_asset_badExtension.bad-type-extension.json +39 -0
- package/test/resources/9999999/asset-deploy/block/testNew_asset_badName_bad.asset-block-meta.json +39 -0
- package/test/resources/9999999/dataExtract/patch-expected.json +3 -1
- package/test/resources/9999999/event/get-published-expected.json +30 -0
- package/test/resources/9999999/event/post_withExistingDE-callout-expected.json +211 -0
- package/test/resources/9999999/event/put-expected.json +12 -11
- package/test/resources/9999999/event-deploy/testNew_event_badExtension.bad-type-extension.json +200 -0
- package/test/resources/9999999/event-deploy/testNew_event_badName_bad.event-meta.json +200 -0
- package/test/resources/9999999/interaction/v1/eventDefinitions/key_DEAudience-2e3c73b6-48cc-2ec0-5522-48636e1a236e/get-response.json +39 -0
- package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_Multistep/put-response.json +461 -0
- package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail/put-response.json +219 -0
- package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail_notPublished/put-response.json +226 -0
- package/test/resources/9999999/journey/get-published-expected.json +217 -0
- package/test/resources/9999999/sendClassification/patch-expected.json +3 -1
- package/test/resources/9999999/senderProfile/patch-expected.json +12 -8
- package/test/resources/9999999/transactionalEmail/get-published-expected.json +20 -0
- package/test/type.dataExtract.test.js +1 -1
- package/test/type.event.test.js +3 -3
- package/test/type.journey.test.js +222 -13
- package/test/type.sendClassification.test.js +1 -1
- package/test/type.senderProfile.test.js +1 -1
- package/test/utils.js +5 -1
|
@@ -23,6 +23,7 @@ import yoctoSpinner from 'yocto-spinner';
|
|
|
23
23
|
* @typedef {import('../../types/mcdev.d.js').MetadataTypeMapObj} MetadataTypeMapObj
|
|
24
24
|
* @typedef {import('../../types/mcdev.d.js').SoapRequestParams} SoapRequestParams
|
|
25
25
|
* @typedef {import('../../types/mcdev.d.js').TemplateMap} TemplateMap
|
|
26
|
+
* @typedef {import('../../types/mcdev.d.js').TypeKeyCombo} TypeKeyCombo
|
|
26
27
|
*/
|
|
27
28
|
|
|
28
29
|
/**
|
|
@@ -737,7 +738,10 @@ class Journey extends MetadataType {
|
|
|
737
738
|
}
|
|
738
739
|
}
|
|
739
740
|
if (linkedTE.options) {
|
|
740
|
-
triggeredSend.isTrackingClicks =
|
|
741
|
+
triggeredSend.isTrackingClicks =
|
|
742
|
+
linkedTE.options.trackLinks || false;
|
|
743
|
+
triggeredSend.ccEmail = linkedTE.options.cc || '';
|
|
744
|
+
triggeredSend.bccEmail = linkedTE.options.bcc || '';
|
|
741
745
|
}
|
|
742
746
|
|
|
743
747
|
// send classification
|
|
@@ -1293,6 +1297,11 @@ class Journey extends MetadataType {
|
|
|
1293
1297
|
break;
|
|
1294
1298
|
}
|
|
1295
1299
|
case 'Transactional': {
|
|
1300
|
+
const cachedVersion = cache.getByKey('journey', metadata.key);
|
|
1301
|
+
if (cachedVersion.status === 'Published') {
|
|
1302
|
+
throw new Error(`Cannot update transactional-send journey in Published status`);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1296
1305
|
// Transactional Send Journey
|
|
1297
1306
|
// ~~~ TRIGGERS ~~~~
|
|
1298
1307
|
// ! journeys so far transactional EMAIL messages. SMS and Push do not create their own journey.
|
|
@@ -1319,13 +1328,16 @@ class Journey extends MetadataType {
|
|
|
1319
1328
|
activity.configurationArguments.triggeredSendKey =
|
|
1320
1329
|
activity.configurationArguments.r__transactionalEmail_key;
|
|
1321
1330
|
} catch (ex) {
|
|
1322
|
-
const isCreateMode = !
|
|
1323
|
-
if (
|
|
1331
|
+
const isCreateMode = !cachedVersion;
|
|
1332
|
+
if (
|
|
1333
|
+
(isCreateMode && !Util.OPTIONS.publish) ||
|
|
1334
|
+
(!isCreateMode && cachedVersion.status === 'Draft')
|
|
1335
|
+
) {
|
|
1324
1336
|
// no need to add a log entry if the publish-option was provided
|
|
1325
1337
|
Util.logger.info(
|
|
1326
1338
|
` - ${this.definition.type} ${metadata[this.definition.nameField]} (${
|
|
1327
1339
|
metadata[this.definition.keyField]
|
|
1328
|
-
}): To activate this transactional journey (and create the associated transactionalEmail record), please run 'mcdev publish ${this.buObject.credential}/${this.buObject.businessUnit} journey
|
|
1340
|
+
}): To activate this transactional journey (and create the associated transactionalEmail record), please run 'mcdev publish ${this.buObject.credential}/${this.buObject.businessUnit} -m journey:"${metadata.key}" ' or click on "Activate" in the GUI.`
|
|
1329
1341
|
);
|
|
1330
1342
|
} else if (!isCreateMode) {
|
|
1331
1343
|
// block deployment if we are in update mode
|
|
@@ -1357,9 +1369,7 @@ class Journey extends MetadataType {
|
|
|
1357
1369
|
default: {
|
|
1358
1370
|
// it is expected that we'll see 'sms' and 'push' here in the future
|
|
1359
1371
|
throw new Error(
|
|
1360
|
-
`
|
|
1361
|
-
metadata[this.definition.keyField]
|
|
1362
|
-
}): channel ${
|
|
1372
|
+
`channel ${
|
|
1363
1373
|
metadata.channel
|
|
1364
1374
|
} is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
|
|
1365
1375
|
);
|
|
@@ -1370,9 +1380,7 @@ class Journey extends MetadataType {
|
|
|
1370
1380
|
}
|
|
1371
1381
|
default: {
|
|
1372
1382
|
throw new Error(
|
|
1373
|
-
`
|
|
1374
|
-
metadata[this.definition.keyField]
|
|
1375
|
-
}): definitionType ${
|
|
1383
|
+
`definitionType ${
|
|
1376
1384
|
metadata.definitionType
|
|
1377
1385
|
} is not supported yet. Please open a ticket at https://github.com/Accenture/sfmc-devtools/issues/new/choose to request it`
|
|
1378
1386
|
);
|
|
@@ -1834,15 +1842,31 @@ class Journey extends MetadataType {
|
|
|
1834
1842
|
* @param {MetadataTypeMap} upsertResults metadata mapped by their keyField as returned by update/create
|
|
1835
1843
|
*/
|
|
1836
1844
|
static async postDeployTasks(upsertResults) {
|
|
1845
|
+
if (!upsertResults || !Object.keys(upsertResults).length) {
|
|
1846
|
+
// nothing to do. skip here to avoid unnecessary logs / api calls
|
|
1847
|
+
return;
|
|
1848
|
+
}
|
|
1849
|
+
let postDeployFlags = 0;
|
|
1850
|
+
if (Util.OPTIONS.publish) {
|
|
1851
|
+
postDeployFlags++;
|
|
1852
|
+
}
|
|
1853
|
+
if (Util.OPTIONS.validate) {
|
|
1854
|
+
postDeployFlags++;
|
|
1855
|
+
}
|
|
1856
|
+
if (postDeployFlags > 1) {
|
|
1857
|
+
Util.logger.warn(
|
|
1858
|
+
`Please provide only one of the following options (--publish, --validate). Flags are processed in this order and only the first one found is executed.`
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
|
|
1837
1862
|
if (Util.OPTIONS.publish) {
|
|
1838
1863
|
Util.logger.info(`Publishing: ${this.definition.type}`);
|
|
1839
1864
|
// pubslih
|
|
1840
1865
|
const idArr = Object.values(upsertResults).map(
|
|
1841
1866
|
(item) => 'id:' + item.id + '/' + item.version
|
|
1842
1867
|
);
|
|
1843
|
-
await this.publish(idArr);
|
|
1844
|
-
}
|
|
1845
|
-
if (Util.OPTIONS.validate) {
|
|
1868
|
+
await this.publish(idArr, upsertResults);
|
|
1869
|
+
} else if (Util.OPTIONS.validate) {
|
|
1846
1870
|
Util.logger.info(`Validating: ${this.definition.type}`);
|
|
1847
1871
|
// pubslih
|
|
1848
1872
|
const idArr = Object.values(upsertResults).map(
|
|
@@ -1856,14 +1880,18 @@ class Journey extends MetadataType {
|
|
|
1856
1880
|
* a function to publish the journey via API
|
|
1857
1881
|
*
|
|
1858
1882
|
* @param {string[]} keyArr keys or ids of the metadata
|
|
1883
|
+
* @param {MetadataTypeMap} [upsertResults] metadata mapped by their keyField as returned by update/create
|
|
1859
1884
|
* @returns {Promise.<string[]>} Returns list of updated keys/ids that were published. Success could only be seen with a delay in the UI because the publish-endpoint is async
|
|
1860
1885
|
*/
|
|
1861
|
-
static async publish(keyArr) {
|
|
1886
|
+
static async publish(keyArr, upsertResults) {
|
|
1862
1887
|
const resultsTransactional = [];
|
|
1863
1888
|
// works only with objectId
|
|
1864
1889
|
const statusUrls = [];
|
|
1865
1890
|
const executedKeyArr = [];
|
|
1866
|
-
const
|
|
1891
|
+
const refreshTransactionalKeys = [];
|
|
1892
|
+
const metadataMap = upsertResults
|
|
1893
|
+
? { metadata: upsertResults }
|
|
1894
|
+
: await this.retrieveForCache();
|
|
1867
1895
|
const spinnerTransactional = yoctoSpinner({
|
|
1868
1896
|
text: `Publishing transactional journey…`,
|
|
1869
1897
|
});
|
|
@@ -1927,11 +1955,20 @@ class Journey extends MetadataType {
|
|
|
1927
1955
|
}
|
|
1928
1956
|
if (journey.status === 'Published') {
|
|
1929
1957
|
// api would return error code 30000 and ask to open a support case when in fact we simply already have a transactionalEmail created based on this status
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1958
|
+
if (journey.definitionType === 'Transactional') {
|
|
1959
|
+
Util.logger.info(
|
|
1960
|
+
` ☇ skipping ${this.definition.type} ${
|
|
1961
|
+
journey[this.definition.nameField]
|
|
1962
|
+
} (${journey[this.definition.keyField]}): already published. Queueing for refresh.`
|
|
1963
|
+
);
|
|
1964
|
+
refreshTransactionalKeys.push(journey.key);
|
|
1965
|
+
} else {
|
|
1966
|
+
Util.logger.warn(
|
|
1967
|
+
` ☇ skipping ${this.definition.type} ${
|
|
1968
|
+
journey[this.definition.nameField]
|
|
1969
|
+
} (${journey[this.definition.keyField]}): already published.`
|
|
1970
|
+
);
|
|
1971
|
+
}
|
|
1935
1972
|
continue;
|
|
1936
1973
|
}
|
|
1937
1974
|
|
|
@@ -1957,7 +1994,7 @@ class Journey extends MetadataType {
|
|
|
1957
1994
|
statusUrls.push({ key, statusUrl: response.statusUrl });
|
|
1958
1995
|
}
|
|
1959
1996
|
spinnerTransactional.start();
|
|
1960
|
-
return
|
|
1997
|
+
return journey[this.definition.keyField];
|
|
1961
1998
|
} catch (ex) {
|
|
1962
1999
|
spinnerTransactional.stop();
|
|
1963
2000
|
if (
|
|
@@ -2006,6 +2043,11 @@ class Journey extends MetadataType {
|
|
|
2006
2043
|
} else {
|
|
2007
2044
|
throw new Error(response);
|
|
2008
2045
|
}
|
|
2046
|
+
if (Util.OPTIONS.skipStatusCheck) {
|
|
2047
|
+
Util.logger.warn(
|
|
2048
|
+
` - Skipping status check for publishing journey ${key} due to --skipStatusCheck flag`
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2009
2051
|
if (!Util.OPTIONS.skipStatusCheck && statusUrl) {
|
|
2010
2052
|
const spinner = yoctoSpinner({
|
|
2011
2053
|
text: `Publishing journey…`,
|
|
@@ -2064,6 +2106,10 @@ class Journey extends MetadataType {
|
|
|
2064
2106
|
}
|
|
2065
2107
|
} // for loop
|
|
2066
2108
|
|
|
2109
|
+
const publishedJourneyCounter = {
|
|
2110
|
+
multiStep: executedKeyArr.filter(Boolean).length,
|
|
2111
|
+
transactional: 0,
|
|
2112
|
+
};
|
|
2067
2113
|
// Transactional Send Journeys
|
|
2068
2114
|
if (resultsTransactional.length) {
|
|
2069
2115
|
const transactionalKeyArr = (await Promise.all(resultsTransactional)).filter(Boolean);
|
|
@@ -2072,56 +2118,103 @@ class Journey extends MetadataType {
|
|
|
2072
2118
|
// if all publish actions failed, we don't need to re-retrieve anything here
|
|
2073
2119
|
if (transactionalKeyArr.length) {
|
|
2074
2120
|
executedKeyArr.push(...transactionalKeyArr);
|
|
2121
|
+
publishedJourneyCounter.transactional = transactionalKeyArr.length;
|
|
2122
|
+
// reset transactionalEmail cache to trigger re-caching it.
|
|
2123
|
+
cache.clearCache(this.buObject.mid, 'transactionalEmail');
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2075
2126
|
|
|
2076
|
-
|
|
2077
|
-
|
|
2127
|
+
// reload published journeys including their events/transactionalEmails
|
|
2128
|
+
await this._reRetrieve(
|
|
2129
|
+
executedKeyArr,
|
|
2130
|
+
publishedJourneyCounter.transactional,
|
|
2131
|
+
publishedJourneyCounter.multiStep
|
|
2132
|
+
);
|
|
2078
2133
|
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2134
|
+
Util.logger.info(
|
|
2135
|
+
`Published ${executedKeyArr.filter(Boolean).length} of ${keyArr.length} items`
|
|
2136
|
+
);
|
|
2137
|
+
|
|
2138
|
+
if (refreshTransactionalKeys.length) {
|
|
2139
|
+
// in case we tried to publish a transactional journey that was already published we will instead run a refresh for those
|
|
2140
|
+
executedKeyArr.push(...(await this.refresh(refreshTransactionalKeys)));
|
|
2141
|
+
}
|
|
2142
|
+
return executedKeyArr.filter(Boolean);
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
/**
|
|
2146
|
+
*
|
|
2147
|
+
* @param {string[]} executedKeyArr list of journey keys
|
|
2148
|
+
* @param {number} transactionalCounter how many transactiona-send journeys did we expect to refresh
|
|
2149
|
+
* @param {number} multiStepCounter how many multi-step journeys did we expect to refresh
|
|
2150
|
+
* @returns {Promise.<void>} -
|
|
2151
|
+
*/
|
|
2152
|
+
static async _reRetrieve(executedKeyArr, transactionalCounter, multiStepCounter) {
|
|
2153
|
+
if (!executedKeyArr.filter(Boolean).length) {
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
Util.logger.info('Re-retrieving published journeys');
|
|
2157
|
+
const retriever = new Retriever(this.properties, this.buObject);
|
|
2158
|
+
try {
|
|
2159
|
+
// we need to retrieve the updated journeys and all dependencies
|
|
2160
|
+
const updatedJourneyRetrieve = await retriever.retrieve(
|
|
2161
|
+
['journey'],
|
|
2162
|
+
executedKeyArr.filter(Boolean)
|
|
2163
|
+
);
|
|
2164
|
+
|
|
2165
|
+
/** @type {MetadataTypeItem[]} */
|
|
2166
|
+
const updatedJourneys =
|
|
2167
|
+
updatedJourneyRetrieve?.journey?.length > 1
|
|
2168
|
+
? Object.values(
|
|
2169
|
+
updatedJourneyRetrieve?.journey.reduce(
|
|
2170
|
+
(previousValue, currentValue) =>
|
|
2171
|
+
Object.assign(previousValue, currentValue),
|
|
2172
|
+
{}
|
|
2173
|
+
)
|
|
2174
|
+
)
|
|
2175
|
+
: Object.values(updatedJourneyRetrieve?.journey[0]);
|
|
2176
|
+
|
|
2177
|
+
if (updatedJourneys) {
|
|
2178
|
+
// regardless of upsert vs publish-only mode, we need to retrieve the events/transactionalEmail and their dependencies
|
|
2179
|
+
const updatedEvents = [];
|
|
2180
|
+
const updatedTransactionalEmails = [];
|
|
2181
|
+
for (const journey of updatedJourneys) {
|
|
2182
|
+
// multi-step journeys
|
|
2183
|
+
updatedEvents.push(journey.triggers?.[0]?.metaData?.r__event_key);
|
|
2184
|
+
// transactional-send journeys
|
|
2185
|
+
updatedTransactionalEmails.push(
|
|
2186
|
+
journey.activities?.[0]?.configurationArguments?.r__transactionalEmail_key
|
|
2083
2187
|
);
|
|
2188
|
+
}
|
|
2189
|
+
/** @type {TypeKeyCombo} */
|
|
2190
|
+
const eventTransEmailCombo = {};
|
|
2191
|
+
if (updatedEvents.filter(Boolean).length) {
|
|
2192
|
+
eventTransEmailCombo.event = updatedEvents.filter(Boolean);
|
|
2193
|
+
} else if (multiStepCounter) {
|
|
2194
|
+
Util.logger.error(`Could not find events for the published journeys`);
|
|
2195
|
+
}
|
|
2196
|
+
if (updatedTransactionalEmails.filter(Boolean).length) {
|
|
2197
|
+
eventTransEmailCombo.transactionalEmail =
|
|
2198
|
+
updatedTransactionalEmails.filter(Boolean);
|
|
2199
|
+
Util.logger.info('Retrieving relevant transactionalEmails');
|
|
2200
|
+
} else if (transactionalCounter) {
|
|
2201
|
+
Util.logger.error(
|
|
2202
|
+
`Could not find transactional Emails for the published journeys`
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2084
2205
|
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
{}
|
|
2093
|
-
)
|
|
2094
|
-
)
|
|
2095
|
-
: Object.values(updatedJourneyRetrieve?.journey[0]);
|
|
2096
|
-
if (updatedJourneys) {
|
|
2097
|
-
const updatedTransactionalEmails = [];
|
|
2098
|
-
for (const journey of updatedJourneys) {
|
|
2099
|
-
updatedTransactionalEmails.push(
|
|
2100
|
-
journey.activities?.[0]?.configurationArguments
|
|
2101
|
-
?.r__transactionalEmail_key
|
|
2102
|
-
);
|
|
2103
|
-
}
|
|
2104
|
-
if (updatedTransactionalEmails.filter(Boolean).length) {
|
|
2105
|
-
Util.logger.info('Retrieving relevant transactionalEmails');
|
|
2106
|
-
await retriever.retrieve(
|
|
2107
|
-
['transactionalEmail'],
|
|
2108
|
-
updatedTransactionalEmails.filter(Boolean)
|
|
2109
|
-
);
|
|
2110
|
-
} else {
|
|
2111
|
-
Util.logger.error(
|
|
2112
|
-
`Could not find transactional Emails for the published journeys`
|
|
2113
|
-
);
|
|
2114
|
-
}
|
|
2115
|
-
}
|
|
2116
|
-
} catch (ex) {
|
|
2117
|
-
Util.logger.errorStack(ex, 'retrieve failed');
|
|
2206
|
+
const toBeRetrievedTypes = Object.keys(eventTransEmailCombo);
|
|
2207
|
+
if (toBeRetrievedTypes.length) {
|
|
2208
|
+
Util.logger.info(
|
|
2209
|
+
'Retrieving relevant ' +
|
|
2210
|
+
toBeRetrievedTypes.map((item) => item + 's').join(', ')
|
|
2211
|
+
);
|
|
2212
|
+
await retriever.retrieve(toBeRetrievedTypes, eventTransEmailCombo);
|
|
2118
2213
|
}
|
|
2119
2214
|
}
|
|
2215
|
+
} catch (ex) {
|
|
2216
|
+
Util.logger.errorStack(ex, 'retrieve failed');
|
|
2120
2217
|
}
|
|
2121
|
-
Util.logger.info(
|
|
2122
|
-
`Published ${executedKeyArr.filter(Boolean).length} of ${keyArr.length} items`
|
|
2123
|
-
);
|
|
2124
|
-
return executedKeyArr.filter(Boolean);
|
|
2125
2218
|
}
|
|
2126
2219
|
|
|
2127
2220
|
/**
|
|
@@ -2675,31 +2768,28 @@ class Journey extends MetadataType {
|
|
|
2675
2768
|
* TSD-specific refresh method that finds active TSDs and refreshes them
|
|
2676
2769
|
*
|
|
2677
2770
|
* @param {string[]} keyArr metadata keys
|
|
2678
|
-
* @param {boolean} [checkKey] whether to check if the key is valid
|
|
2679
2771
|
* @returns {Promise.<string[]>} Returns list of keys that were refreshed
|
|
2680
2772
|
*/
|
|
2681
|
-
static async refresh(keyArr
|
|
2773
|
+
static async refresh(keyArr) {
|
|
2682
2774
|
console.time('Time'); // eslint-disable-line no-console
|
|
2683
|
-
if (!keyArr) {
|
|
2775
|
+
if (!Array.isArray(keyArr) || !keyArr.length) {
|
|
2684
2776
|
Util.logger.error('No refresh-keys provided');
|
|
2685
2777
|
return [];
|
|
2686
2778
|
// keyArr = await this.getKeysForValidTSDs((await this.findRefreshableItems()).metadata);
|
|
2687
2779
|
// checkKey = false;
|
|
2688
2780
|
}
|
|
2689
|
-
|
|
2690
|
-
if (checkKey) {
|
|
2691
|
-
journeyCache = await this.retrieveForCache();
|
|
2692
|
-
}
|
|
2781
|
+
const journeyCache = await this.retrieveForCache();
|
|
2693
2782
|
// then executes pause, publish, start on them.
|
|
2694
2783
|
Util.logger.info(`Refreshing ${keyArr.length} ${this.definition.typeName}...`);
|
|
2695
2784
|
Util.logger.debug(`Refreshing keys: ${keyArr.join(', ')}`);
|
|
2696
2785
|
const refreshedKeyArr = [];
|
|
2697
2786
|
const tsKeys = [];
|
|
2698
2787
|
const rateLimit = pLimit(10);
|
|
2788
|
+
const transactionalJourneyKeys = [];
|
|
2699
2789
|
await Promise.all(
|
|
2700
2790
|
keyArr.map((key) =>
|
|
2701
2791
|
rateLimit(async () => {
|
|
2702
|
-
if (
|
|
2792
|
+
if (!journeyCache.metadata[key]) {
|
|
2703
2793
|
Util.logger.error(
|
|
2704
2794
|
` ☇ skipping refresh of ${this.definition.type} ${key}: not found on server`
|
|
2705
2795
|
);
|
|
@@ -2707,15 +2797,16 @@ class Journey extends MetadataType {
|
|
|
2707
2797
|
}
|
|
2708
2798
|
switch (journeyCache.metadata[key].definitionType) {
|
|
2709
2799
|
case 'Transactional': {
|
|
2710
|
-
if (
|
|
2711
|
-
Util.logger.error(
|
|
2712
|
-
` ☇ skipping refresh of ${this.definition.type} ${key}: Can only refresh journeys with status 'Published'. Found status: ${journeyCache.metadata[key]?.status}`
|
|
2713
|
-
);
|
|
2714
|
-
} else {
|
|
2800
|
+
if (journeyCache.metadata[key]?.status === 'Published') {
|
|
2715
2801
|
const result = await this._refreshItem(key, journeyCache);
|
|
2716
2802
|
if (result) {
|
|
2717
2803
|
refreshedKeyArr.push(key);
|
|
2804
|
+
transactionalJourneyKeys.push(key);
|
|
2718
2805
|
}
|
|
2806
|
+
} else {
|
|
2807
|
+
Util.logger.error(
|
|
2808
|
+
` ☇ skipping refresh of ${this.definition.type} ${key}: Can only refresh journeys with status 'Published'. Found status: ${journeyCache.metadata[key]?.status}`
|
|
2809
|
+
);
|
|
2719
2810
|
}
|
|
2720
2811
|
break;
|
|
2721
2812
|
}
|
|
@@ -2804,6 +2895,14 @@ class Journey extends MetadataType {
|
|
|
2804
2895
|
Util.logger.info(Util.getGrayMsg('No triggeredSends found to refresh'));
|
|
2805
2896
|
}
|
|
2806
2897
|
|
|
2898
|
+
// reload refreshed transactional journeys including their transactionalEmails
|
|
2899
|
+
if (transactionalJourneyKeys.length) {
|
|
2900
|
+
// reset transactionalEmail cache to trigger re-caching it.
|
|
2901
|
+
cache.clearCache(this.buObject.mid, 'transactionalEmail');
|
|
2902
|
+
|
|
2903
|
+
await this._reRetrieve(transactionalJourneyKeys, transactionalJourneyKeys.length, 0);
|
|
2904
|
+
}
|
|
2905
|
+
|
|
2807
2906
|
Util.logger.info(
|
|
2808
2907
|
`Refreshed ${refreshedKeyArr.length} of ${keyArr.length} ${this.definition.type}`
|
|
2809
2908
|
);
|
|
@@ -69,21 +69,34 @@ class MetadataType {
|
|
|
69
69
|
try {
|
|
70
70
|
if (fileName.endsWith('.json')) {
|
|
71
71
|
const fileContent = await File.readJSONFile(dir, fileName, false);
|
|
72
|
-
const fileNameWithoutEnding = File.reverseFilterIllegalFilenames(
|
|
73
|
-
fileName.split(/\.(\w|-)+-meta.json/)[0]
|
|
74
|
-
);
|
|
75
|
-
// We always store the filename using the External Key (CustomerKey or key) to avoid duplicate names.
|
|
76
|
-
// to ensure any changes are done to both the filename and external key do a check here
|
|
77
72
|
// ! convert numbers to string to allow numeric keys to be checked properly
|
|
78
73
|
const key = Number.isInteger(fileContent[this.definition.keyField])
|
|
79
74
|
? fileContent[this.definition.keyField].toString()
|
|
80
75
|
: fileContent[this.definition.keyField];
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
|
|
77
|
+
// ensure filename includes extended metadata extension
|
|
78
|
+
const regex = new RegExp(/\.(\w|-)+-meta.json/);
|
|
79
|
+
const errorDir = dir.split('\\').join('/');
|
|
80
|
+
if (regex.test(fileName)) {
|
|
81
|
+
const fileNameWithoutEnding = File.reverseFilterIllegalFilenames(
|
|
82
|
+
fileName.split(regex)[0]
|
|
83
|
+
);
|
|
84
|
+
// We always store the filename using the External Key (CustomerKey or key) to avoid duplicate names.
|
|
85
|
+
// to ensure any changes are done to both the filename and external key do a check here
|
|
86
|
+
if (key === fileNameWithoutEnding || listBadKeys) {
|
|
87
|
+
fileName2FileContent[fileNameWithoutEnding] = fileContent;
|
|
88
|
+
} else {
|
|
89
|
+
Util.logger.error(
|
|
90
|
+
` ☇ skipping ${this.definition.type} ${key}: Name of the metadata file and the JSON-key (${this.definition.keyField}) must match. Expected: ${key}.${this.definition.type}-meta.json. Actual: ` +
|
|
91
|
+
Util.getGrayMsg(`${errorDir}/`) +
|
|
92
|
+
fileName
|
|
93
|
+
);
|
|
94
|
+
}
|
|
83
95
|
} else {
|
|
84
96
|
Util.logger.error(
|
|
85
|
-
` ${this.definition.type} ${key}: Name of the metadata file
|
|
86
|
-
Util.getGrayMsg(
|
|
97
|
+
` ☇ skipping ${this.definition.type} ${key}: Name of the metadata file must end on the extended metadata suffix. Expected: ${key}.${this.definition.type}-meta.json. Actual: ` +
|
|
98
|
+
Util.getGrayMsg(`${errorDir}/`) +
|
|
99
|
+
fileName
|
|
87
100
|
);
|
|
88
101
|
}
|
|
89
102
|
}
|
|
@@ -131,6 +131,26 @@ class SendClassification extends MetadataType {
|
|
|
131
131
|
return metadata;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Gets executed after deployment of metadata type
|
|
136
|
+
*
|
|
137
|
+
* @param {MetadataTypeMap} upsertResults metadata mapped by their keyField as returned by update/create
|
|
138
|
+
* @returns {Promise.<void>} -
|
|
139
|
+
*/
|
|
140
|
+
static async postDeployTasks(upsertResults) {
|
|
141
|
+
// re-retrieve all upserted items to ensure we have all fields (createdDate and modifiedDate are otherwise not present)
|
|
142
|
+
Util.logger.debug(
|
|
143
|
+
`Caching all ${this.definition.type} post-deploy to ensure we have all fields`
|
|
144
|
+
);
|
|
145
|
+
const typeCache = await this.retrieveForCache();
|
|
146
|
+
// update values in upsertResults with retrieved values before saving to disk
|
|
147
|
+
for (const key of Object.keys(upsertResults)) {
|
|
148
|
+
if (typeCache.metadata[key]) {
|
|
149
|
+
upsertResults[key] = typeCache.metadata[key];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
134
154
|
/**
|
|
135
155
|
* manages post retrieve steps
|
|
136
156
|
*
|
|
@@ -162,6 +162,26 @@ class SenderProfile extends MetadataType {
|
|
|
162
162
|
return metadata;
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
+
/**
|
|
166
|
+
* Gets executed after deployment of metadata type
|
|
167
|
+
*
|
|
168
|
+
* @param {MetadataTypeMap} upsertResults metadata mapped by their keyField as returned by update/create
|
|
169
|
+
* @returns {Promise.<void>} -
|
|
170
|
+
*/
|
|
171
|
+
static async postDeployTasks(upsertResults) {
|
|
172
|
+
// re-retrieve all upserted items to ensure we have all fields (createdDate and modifiedDate are otherwise not present)
|
|
173
|
+
Util.logger.debug(
|
|
174
|
+
`Caching all ${this.definition.type} post-deploy to ensure we have all fields`
|
|
175
|
+
);
|
|
176
|
+
const typeCache = await this.retrieveForCache();
|
|
177
|
+
// update values in upsertResults with retrieved values before saving to disk
|
|
178
|
+
for (const key of Object.keys(upsertResults)) {
|
|
179
|
+
if (typeCache.metadata[key]) {
|
|
180
|
+
upsertResults[key] = typeCache.metadata[key];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
165
185
|
/**
|
|
166
186
|
*
|
|
167
187
|
* @param {MetadataTypeItem} item single metadata item
|
|
@@ -78,6 +78,12 @@ export default {
|
|
|
78
78
|
Scheduled: 6,
|
|
79
79
|
Stopped: 5,
|
|
80
80
|
},
|
|
81
|
+
fileNameOperatorMapping: {
|
|
82
|
+
Equals: 4,
|
|
83
|
+
Contains: 1,
|
|
84
|
+
'Begins with': 2,
|
|
85
|
+
'Ends with': 3,
|
|
86
|
+
},
|
|
81
87
|
timeZoneMapping: {
|
|
82
88
|
// bugs in SFMC timezones:
|
|
83
89
|
// * Yerevan GMT+4 always is changing to 'Caucasus Standard Time' GMT+4, so no id 29
|
|
@@ -17,6 +17,20 @@ export default {
|
|
|
17
17
|
'Used for export or import of files to/from Marketing Cloud. Previously this was labeled ftpLocation.',
|
|
18
18
|
typeRetrieveByDefault: true,
|
|
19
19
|
typeName: 'File Location',
|
|
20
|
+
locationTypeMapping: {
|
|
21
|
+
'Enhanced FTP Site Import Directory': 0,
|
|
22
|
+
'External FTP Site': 1,
|
|
23
|
+
'External SFTP Site': 2,
|
|
24
|
+
'External FTPS Site': 3,
|
|
25
|
+
'Salesforce Objects and Reports': 4,
|
|
26
|
+
Safehouse: 5,
|
|
27
|
+
'Enhanced FTP Site Export Directory': 6,
|
|
28
|
+
'Legacy Import Directory': 8,
|
|
29
|
+
'Relative location under FTP Site': 9,
|
|
30
|
+
'Amazon Simple Storage Service': 13,
|
|
31
|
+
'Azure Blob Storage': 15,
|
|
32
|
+
'Google Cloud Storage': 16,
|
|
33
|
+
},
|
|
20
34
|
fields: {
|
|
21
35
|
id: {
|
|
22
36
|
isCreateable: false,
|
|
@@ -34,13 +48,13 @@ export default {
|
|
|
34
48
|
isCreateable: false,
|
|
35
49
|
isUpdateable: false,
|
|
36
50
|
retrieving: true,
|
|
37
|
-
template:
|
|
51
|
+
template: true,
|
|
38
52
|
},
|
|
39
53
|
name: {
|
|
40
54
|
isCreateable: false,
|
|
41
55
|
isUpdateable: false,
|
|
42
56
|
retrieving: true,
|
|
43
|
-
template:
|
|
57
|
+
template: true,
|
|
44
58
|
},
|
|
45
59
|
relPath: {
|
|
46
60
|
isCreateable: true,
|
|
@@ -48,5 +62,11 @@ export default {
|
|
|
48
62
|
retrieving: true,
|
|
49
63
|
template: true,
|
|
50
64
|
},
|
|
65
|
+
c__locationType: {
|
|
66
|
+
isCreateable: false,
|
|
67
|
+
isUpdateable: false,
|
|
68
|
+
retrieving: true,
|
|
69
|
+
template: true,
|
|
70
|
+
},
|
|
51
71
|
},
|
|
52
72
|
};
|
package/lib/util/cache.js
CHANGED
|
@@ -60,12 +60,17 @@ export default {
|
|
|
60
60
|
/**
|
|
61
61
|
* clean cache for one BU if mid provided, otherwise whole cache
|
|
62
62
|
*
|
|
63
|
-
* @param {number} [mid]
|
|
63
|
+
* @param {number} [mid] limit clearing to provided business unit MID
|
|
64
|
+
* @param {string} [type] optionally limit clearing to specific metadata type; only used if mid is provided
|
|
64
65
|
* @returns {void}
|
|
65
66
|
*/
|
|
66
|
-
clearCache: (mid) =>
|
|
67
|
+
clearCache: (mid, type) =>
|
|
67
68
|
mid
|
|
68
|
-
? Object.keys(dataStore[mid]).forEach((key) =>
|
|
69
|
+
? Object.keys(dataStore[mid]).forEach((key) => {
|
|
70
|
+
if (!type || type === key) {
|
|
71
|
+
delete dataStore[mid][key];
|
|
72
|
+
}
|
|
73
|
+
})
|
|
69
74
|
: Object.keys(dataStore).forEach((key) => delete dataStore[key]),
|
|
70
75
|
/* eslint-enable unicorn/no-array-for-each */
|
|
71
76
|
|
package/lib/util/config.js
CHANGED
|
@@ -127,6 +127,10 @@ const config = {
|
|
|
127
127
|
`Your Accenture SFMC DevTools version ${Util.packageJsonMcdev.version} is lower than your project's config version ${properties.version}`
|
|
128
128
|
);
|
|
129
129
|
if (Util.skipInteraction) {
|
|
130
|
+
// print guidance for extension users
|
|
131
|
+
Util.logger.error(
|
|
132
|
+
`Run 'npm update -g mcdev@${properties.version}' now to fix this.`
|
|
133
|
+
);
|
|
130
134
|
return false;
|
|
131
135
|
}
|
|
132
136
|
|
|
@@ -277,6 +281,8 @@ const config = {
|
|
|
277
281
|
].join('\n- ')
|
|
278
282
|
);
|
|
279
283
|
if (Util.skipInteraction) {
|
|
284
|
+
// print guidance for extension users
|
|
285
|
+
Util.logger.error(`Run 'mcdev upgrade' now to fix this.`);
|
|
280
286
|
return false;
|
|
281
287
|
}
|
|
282
288
|
const runUpgradeNow = await confirm({
|
package/lib/util/file.js
CHANGED
|
@@ -270,6 +270,7 @@ const File = {
|
|
|
270
270
|
* @returns {Promise.<string>} original string on error; formatted string on success
|
|
271
271
|
*/
|
|
272
272
|
_beautify_prettier: async function (directory, filename, filetype, content) {
|
|
273
|
+
const properties = await config.getProperties();
|
|
273
274
|
let formatted = '';
|
|
274
275
|
try {
|
|
275
276
|
if (!FileFs.prettierConfig) {
|
|
@@ -337,20 +338,22 @@ const File = {
|
|
|
337
338
|
|
|
338
339
|
formatted = await prettier.format(content, FileFs.prettierConfig);
|
|
339
340
|
} catch (ex) {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
341
|
+
if (properties.options.formatErrorLog) {
|
|
342
|
+
// save prettier errror into log file
|
|
343
|
+
// Note: we have to filter color codes from prettier's error message before saving it to file
|
|
344
|
+
/* eslint-disable no-control-regex */
|
|
345
|
+
this.writeToFile(
|
|
346
|
+
directory,
|
|
347
|
+
filename + '.error',
|
|
348
|
+
'log',
|
|
349
|
+
`Error Log\nParser: ${FileFs.prettierConfig.parser}\n${ex.message.replaceAll(
|
|
350
|
+
/[\u001B\u009B][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g,
|
|
351
|
+
|
|
352
|
+
''
|
|
353
|
+
)}`
|
|
354
|
+
);
|
|
355
|
+
/* eslint-enable no-control-regex */
|
|
356
|
+
}
|
|
354
357
|
|
|
355
358
|
formatted = content;
|
|
356
359
|
}
|