mcdev 5.0.1 → 5.1.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/.coverage-comment-template.svelte +177 -161
- package/.eslintrc.json +1 -0
- package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
- package/.github/dependabot.yml +8 -0
- package/.github/workflows/coverage-base-update.yml +6 -2
- package/.github/workflows/coverage-develop-branch.yml +7 -6
- package/.github/workflows/coverage-main-branch.yml +7 -6
- package/.github/workflows/coverage.yml +7 -2
- package/.husky/commit-msg +1 -1
- package/.husky/post-checkout +35 -3
- package/.husky/post-merge +21 -0
- package/.husky/pre-commit +1 -0
- package/docs/dist/documentation.md +222 -62
- package/lib/Deployer.js +3 -4
- package/lib/cli.js +36 -8
- package/lib/index.js +175 -3
- package/lib/metadataTypes/Asset.js +4 -2
- package/lib/metadataTypes/Automation.js +413 -195
- package/lib/metadataTypes/DataExtension.js +6 -8
- package/lib/metadataTypes/DataExtensionField.js +5 -5
- package/lib/metadataTypes/Journey.js +11 -10
- package/lib/metadataTypes/MetadataType.js +76 -22
- package/lib/metadataTypes/MobileKeyword.js +165 -20
- package/lib/metadataTypes/MobileMessage.js +20 -28
- package/lib/metadataTypes/Query.js +26 -0
- package/lib/metadataTypes/Role.js +2 -3
- package/lib/metadataTypes/TransactionalSMS.js +5 -5
- package/lib/metadataTypes/User.js +3 -17
- package/lib/metadataTypes/definitions/Asset.definition.js +1 -0
- package/lib/metadataTypes/definitions/Automation.definition.js +52 -6
- package/lib/metadataTypes/definitions/DataExtension.definition.js +1 -0
- package/lib/metadataTypes/definitions/DataExtract.definition.js +1 -0
- package/lib/metadataTypes/definitions/EmailSend.definition.js +1 -0
- package/lib/metadataTypes/definitions/Event.definition.js +1 -0
- package/lib/metadataTypes/definitions/Filter.definition.js +1 -0
- package/lib/metadataTypes/definitions/ImportFile.definition.js +1 -0
- package/lib/metadataTypes/definitions/MobileKeyword.definition.js +20 -7
- package/lib/metadataTypes/definitions/MobileMessage.definition.js +50 -8
- package/lib/metadataTypes/definitions/Query.definition.js +1 -0
- package/lib/metadataTypes/definitions/Role.definition.js +1 -0
- package/lib/metadataTypes/definitions/TriggeredSend.definition.js +1 -0
- package/lib/metadataTypes/definitions/User.definition.js +2 -0
- package/lib/util/auth.js +4 -1
- package/lib/util/cli.js +1 -1
- package/lib/util/devops.js +13 -11
- package/lib/util/file.js +5 -3
- package/lib/util/util.js +153 -129
- package/package.json +11 -11
- package/test/general.test.js +26 -0
- package/test/mockRoot/.mcdevrc.json +3 -1
- package/test/mockRoot/deploy/testInstance/testBU/automation/testExisting_automation.automation-meta.json +53 -0
- package/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json +46 -0
- package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/{testNew_keyword.mobileKeyword-meta.json → 4912312345678.TESTNEW_KEYWORD.mobileKeyword-meta.json} +1 -4
- package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/{testNew_keyword_blocked.mobileKeyword-meta.json → 4912312345678.TESTNEW_KEYWORD_BLOCKED.mobileKeyword-meta.json} +1 -4
- package/test/mockRoot/deploy/testInstance/testBU/query/{testExistingQuery.query-meta.json → testExisting_query.query-meta.json} +2 -2
- package/test/mockRoot/deploy/testInstance/testBU/query/{testNewQuery.query-meta.json → testNew_query.query-meta.json} +2 -2
- package/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testExisting_tsms.transactionalSMS-meta.json +1 -1
- package/test/mockRoot/deploy/testInstance/testBU/transactionalSMS/testNew_tsms.transactionalSMS-meta.json +1 -1
- package/test/resourceFactory.js +64 -21
- package/test/resources/1111111/user/retrieve-expected.md +19 -0
- package/test/resources/9999999/automation/build-expected.json +58 -0
- package/test/resources/9999999/automation/create-expected.json +46 -0
- package/test/resources/9999999/automation/create-testNew_automation-expected.md +28 -0
- package/test/resources/9999999/automation/delete-response.xml +40 -0
- package/test/resources/9999999/automation/retrieve-expected.json +58 -0
- package/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md +30 -0
- package/test/resources/9999999/automation/template-expected.json +58 -0
- package/test/resources/9999999/automation/update-expected.json +46 -0
- package/test/resources/9999999/automation/update-testExisting_automation-expected.md +28 -0
- package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json +85 -0
- package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/patch-response.json +85 -0
- package/test/resources/9999999/automation/v1/automations/a8afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json +85 -0
- package/test/resources/9999999/automation/v1/automations/post-response.json +85 -0
- package/test/resources/9999999/automation/v1/dataextracts/56c5370a-f988-4f36-b0ee-0f876573f6d7/get-response.json +38 -0
- package/test/resources/9999999/automation/v1/dataextracts/get-response.json +20 -0
- package/test/resources/9999999/automation/v1/filetransfers/72c328ac-f5b0-4e37-91d3-a775666f15a6/get-response.json +18 -0
- package/test/resources/9999999/automation/v1/filetransfers/get-response.json +15 -0
- package/test/resources/9999999/automation/v1/imports/get-response.json +38 -0
- package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/actions/start/post-response.txt +1 -0
- package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/get-response.json +2 -2
- package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +2 -2
- package/test/resources/9999999/automation/v1/queries/get-response.json +4 -4
- package/test/resources/9999999/automation/v1/queries/post-response.json +2 -2
- package/test/resources/9999999/automation/v1/scripts/get-response.json +17 -0
- package/test/resources/9999999/dataExtension/retrieve-expected.md +18 -0
- package/test/resources/9999999/dataFolder/retrieve-ContentType=automations-response.xml +48 -0
- package/test/resources/9999999/dataFolder/retrieve-ContentType=queryactivity-response.xml +48 -0
- package/test/resources/9999999/dataFolder/retrieve-response.xml +22 -0
- package/test/resources/9999999/emailSendDefinition/retrieve-response.xml +85 -0
- package/test/resources/9999999/journey/build-expected.json +1 -1
- package/test/resources/9999999/journey/get-expected.json +1 -1
- package/test/resources/9999999/journey/put-expected.json +1 -1
- package/test/resources/9999999/journey/template-expected.json +1 -1
- package/test/resources/9999999/legacy/v1/beta/automations/notifications/RkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow/get-response.json +21 -0
- package/test/resources/9999999/legacy/v1/beta/automations/notifications/RkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow/post-response.json +0 -0
- package/test/resources/9999999/legacy/v1/beta/bulk/automations/automation/definition/get-response.json +30 -0
- package/test/resources/9999999/legacy/v1/beta/mobile/keyword/NXV4ZFMwTEFwRVczd3RaLUF5X3p5dzo4Njow/get-response.json +1 -1
- package/test/resources/9999999/legacy/v1/beta/mobile/keyword/get-response.json +1 -1
- package/test/resources/9999999/legacy/v1/beta/mobile/message/NTIzOjc4OjA/get-response.json +1 -1
- package/test/resources/9999999/legacy/v1/beta/mobile/message/get-response.json +1 -1
- package/test/resources/9999999/messaging/v1/sms/definitions/post-response.json +1 -1
- package/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/get-response.json +1 -1
- package/test/resources/9999999/messaging/v1/sms/definitions/testExisting_tsms/patch-response.json +1 -1
- package/test/resources/9999999/mobileKeyword/build-expected.json +1 -4
- package/test/resources/9999999/mobileKeyword/get-expected.json +1 -4
- package/test/resources/9999999/mobileKeyword/post-create-expected.json +1 -4
- package/test/resources/9999999/mobileKeyword/template-expected.json +1 -4
- package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation-response.xml +30 -0
- package/test/resources/9999999/program/retrieve-CustomerKey=testNew_automation-response.xml +30 -0
- package/test/resources/9999999/program/retrieve-Name=testExisting_automation-response.xml +31 -0
- package/test/resources/9999999/program/retrieve-response.xml +32 -0
- package/test/resources/9999999/query/build-expected.json +2 -2
- package/test/resources/9999999/query/build-expected.sql +1 -1
- package/test/resources/9999999/query/get-expected.json +2 -2
- package/test/resources/9999999/query/get-expected.sql +1 -1
- package/test/resources/9999999/query/get2-expected.json +2 -2
- package/test/resources/9999999/query/patch-expected.json +2 -2
- package/test/resources/9999999/query/patch-expected.sql +1 -1
- package/test/resources/9999999/query/post-expected.json +2 -2
- package/test/resources/9999999/query/post-expected.sql +1 -1
- package/test/resources/9999999/query/template-expected.json +2 -2
- package/test/resources/9999999/query/template-expected.sql +1 -1
- package/test/resources/9999999/transactionalSMS/build-expected.json +1 -1
- package/test/resources/9999999/transactionalSMS/get-expected.json +1 -1
- package/test/resources/9999999/transactionalSMS/patch-expected.json +1 -1
- package/test/resources/9999999/transactionalSMS/post-expected.json +1 -1
- package/test/resources/9999999/transactionalSMS/template-expected.json +1 -1
- package/test/type.automation.test.js +259 -0
- package/test/type.dataExtension.test.js +16 -1
- package/test/type.mobileKeyword.test.js +57 -19
- package/test/type.query.test.js +39 -26
- package/test/type.user.test.js +44 -1
- package/test/utils.js +16 -5
- package/.coverage-comment-template.md +0 -20
- /package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/{testNew_keyword.mobileKeyword-meta.amp → 4912312345678.TESTNEW_KEYWORD.mobileKeyword-meta.amp} +0 -0
- /package/test/mockRoot/deploy/testInstance/testBU/mobileKeyword/{testNew_keyword_blocked.mobileKeyword-meta.amp → 4912312345678.TESTNEW_KEYWORD_BLOCKED.mobileKeyword-meta.amp} +0 -0
- /package/test/mockRoot/deploy/testInstance/testBU/query/{testExistingQuery.query-meta.sql → testExisting_query.query-meta.sql} +0 -0
- /package/test/mockRoot/deploy/testInstance/testBU/query/{testNewQuery.query-meta.sql → testNew_query.query-meta.sql} +0 -0
|
@@ -6,6 +6,7 @@ const Util = require('../util/util');
|
|
|
6
6
|
const File = require('../util/file');
|
|
7
7
|
const Definitions = require('../MetadataTypeDefinitions');
|
|
8
8
|
const cache = require('../util/cache');
|
|
9
|
+
const pLimit = require('p-limit');
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Automation MetadataType
|
|
@@ -13,6 +14,7 @@ const cache = require('../util/cache');
|
|
|
13
14
|
* @augments MetadataType
|
|
14
15
|
*/
|
|
15
16
|
class Automation extends MetadataType {
|
|
17
|
+
static notificationUpdates = {};
|
|
16
18
|
/**
|
|
17
19
|
* Retrieves Metadata of Automation
|
|
18
20
|
*
|
|
@@ -35,11 +37,13 @@ class Automation extends MetadataType {
|
|
|
35
37
|
};
|
|
36
38
|
}
|
|
37
39
|
const results = await this.client.soap.retrieveBulk('Program', ['ObjectID'], requestParams);
|
|
38
|
-
if (results.Results?.length) {
|
|
40
|
+
if (results.Results?.length && !key) {
|
|
39
41
|
// empty results will come back without "Results" defined
|
|
40
42
|
Util.logger.info(
|
|
41
43
|
Util.getGrayMsg(
|
|
42
|
-
` - ${results.Results?.length}
|
|
44
|
+
` - ${results.Results?.length} automation${
|
|
45
|
+
results.Results?.length === 1 ? '' : 's'
|
|
46
|
+
} found. Retrieving details...`
|
|
43
47
|
)
|
|
44
48
|
);
|
|
45
49
|
}
|
|
@@ -70,19 +74,96 @@ class Automation extends MetadataType {
|
|
|
70
74
|
})
|
|
71
75
|
)
|
|
72
76
|
: [];
|
|
73
|
-
|
|
77
|
+
let metadataMap = this.parseResponseBody({ items: details });
|
|
78
|
+
|
|
79
|
+
if (Object.keys(metadataMap).length) {
|
|
80
|
+
// attach notification information to each automation that has any
|
|
81
|
+
await this.#getAutomationNotificationsREST(metadataMap);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// * retrieveDir can be empty when we use it in the context of postDeployTasks
|
|
85
|
+
if (retrieveDir) {
|
|
86
|
+
metadataMap = await this.saveResults(metadataMap, retrieveDir, null, null);
|
|
87
|
+
Util.logger.info(
|
|
88
|
+
`Downloaded: ${this.definition.type} (${Object.keys(metadataMap).length})` +
|
|
89
|
+
Util.getKeysString(key)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
await this.runDocumentOnRetrieve(key, metadataMap);
|
|
93
|
+
}
|
|
94
|
+
return { metadata: metadataMap, type: this.definition.type };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* helper for {@link Automation.retrieve} to get Automation Notifications
|
|
99
|
+
*
|
|
100
|
+
* @private
|
|
101
|
+
* @param {TYPE.MetadataTypeMap} metadataMap keyField => metadata map
|
|
102
|
+
* @returns {Promise.<void>} Promise of nothing
|
|
103
|
+
*/
|
|
104
|
+
static async #getAutomationNotificationsREST(metadataMap) {
|
|
105
|
+
Util.logger.info(Util.getGrayMsg(` Retrieving Automation Notification information...`));
|
|
106
|
+
|
|
107
|
+
// get list of keys that we retrieved so far
|
|
108
|
+
const foundKeys = Object.keys(metadataMap);
|
|
74
109
|
|
|
75
|
-
//
|
|
76
|
-
const
|
|
110
|
+
// get encodedAutomationID to retrieve notification information
|
|
111
|
+
const iteratorBackup = this.definition.bodyIteratorField;
|
|
112
|
+
this.definition.bodyIteratorField = 'entry';
|
|
113
|
+
const automationLegacyMapObj = await super.retrieveREST(
|
|
114
|
+
undefined,
|
|
115
|
+
`/legacy/v1/beta/bulk/automations/automation/definition/`
|
|
116
|
+
);
|
|
117
|
+
this.definition.bodyIteratorField = iteratorBackup;
|
|
118
|
+
const automationLegacyMap = Object.keys(automationLegacyMapObj.metadata)
|
|
119
|
+
.filter((key) => foundKeys.includes(key))
|
|
120
|
+
// ! using the `id` field to retrieve notifications does not work. instead one needs to use the URL in the `notifications` field
|
|
121
|
+
.map((key) => ({
|
|
122
|
+
id: automationLegacyMapObj.metadata[key].id,
|
|
123
|
+
key,
|
|
124
|
+
}));
|
|
125
|
+
|
|
126
|
+
// get notifications for each automation
|
|
127
|
+
const rateLimit = pLimit(5);
|
|
128
|
+
let found = 0;
|
|
129
|
+
let skipped = 0;
|
|
130
|
+
const promiseMap = await Promise.all(
|
|
131
|
+
automationLegacyMap.map((automationLegacy) =>
|
|
132
|
+
rateLimit(async () => {
|
|
133
|
+
// this is a file so extended is at another endpoint
|
|
134
|
+
try {
|
|
135
|
+
const notificationsResult = await this.client.rest.get(
|
|
136
|
+
'/legacy/v1/beta/automations/notifications/' + automationLegacy.id
|
|
137
|
+
);
|
|
138
|
+
if (Array.isArray(notificationsResult?.workers)) {
|
|
139
|
+
metadataMap[automationLegacy.key].notifications =
|
|
140
|
+
notificationsResult.workers.map((n) => ({
|
|
141
|
+
email: n.definition.split(',').map((item) => item.trim()),
|
|
142
|
+
message: n.body,
|
|
143
|
+
type: n.notificationType,
|
|
144
|
+
}));
|
|
145
|
+
found++;
|
|
146
|
+
} else {
|
|
147
|
+
throw new TypeError(JSON.stringify(notificationsResult));
|
|
148
|
+
}
|
|
149
|
+
} catch (ex) {
|
|
150
|
+
Util.logger.debug(
|
|
151
|
+
` ☇ skipping Notifications for Automation ${automationLegacy.key}: ${ex.message} ${ex.code}`
|
|
152
|
+
);
|
|
153
|
+
skipped++;
|
|
154
|
+
}
|
|
155
|
+
})
|
|
156
|
+
)
|
|
157
|
+
);
|
|
77
158
|
Util.logger.info(
|
|
78
|
-
`
|
|
79
|
-
Util.getKeysString(key)
|
|
159
|
+
Util.getGrayMsg(` Notifications found for ${found} automation${found === 1 ? '' : 's'}`)
|
|
80
160
|
);
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return
|
|
161
|
+
Util.logger.debug(
|
|
162
|
+
`Notifications not found for ${skipped} automation${skipped === 1 ? '' : 's'}`
|
|
163
|
+
);
|
|
164
|
+
return promiseMap;
|
|
85
165
|
}
|
|
166
|
+
|
|
86
167
|
/**
|
|
87
168
|
* Retrieves Metadata of Automation
|
|
88
169
|
*
|
|
@@ -134,6 +215,7 @@ class Automation extends MetadataType {
|
|
|
134
215
|
* @returns {Promise.<TYPE.AutomationMapObj>} Promise of metadata
|
|
135
216
|
*/
|
|
136
217
|
static async retrieveForCache() {
|
|
218
|
+
// get automations for cache
|
|
137
219
|
const results = await this.client.soap.retrieveBulk('Program', [
|
|
138
220
|
'ObjectID',
|
|
139
221
|
'CustomerKey',
|
|
@@ -141,14 +223,29 @@ class Automation extends MetadataType {
|
|
|
141
223
|
]);
|
|
142
224
|
const resultsConverted = {};
|
|
143
225
|
if (Array.isArray(results?.Results)) {
|
|
226
|
+
// get encodedAutomationID to retrieve notification information
|
|
227
|
+
const keyBackup = this.definition.keyField;
|
|
228
|
+
const iteratorBackup = this.definition.bodyIteratorField;
|
|
229
|
+
this.definition.keyField = 'key';
|
|
230
|
+
this.definition.bodyIteratorField = 'entry';
|
|
231
|
+
const automationsLegacy = await super.retrieveREST(
|
|
232
|
+
undefined,
|
|
233
|
+
`/legacy/v1/beta/bulk/automations/automation/definition/`
|
|
234
|
+
);
|
|
235
|
+
this.definition.keyField = keyBackup;
|
|
236
|
+
this.definition.bodyIteratorField = iteratorBackup;
|
|
237
|
+
|
|
238
|
+
// merge encodedAutomationID into results
|
|
144
239
|
for (const m of results.Results) {
|
|
145
240
|
resultsConverted[m.CustomerKey] = {
|
|
146
241
|
id: m.ObjectID,
|
|
147
242
|
key: m.CustomerKey,
|
|
148
243
|
name: m.Name,
|
|
244
|
+
programId: automationsLegacy.metadata[m.CustomerKey]?.id,
|
|
149
245
|
};
|
|
150
246
|
}
|
|
151
247
|
}
|
|
248
|
+
|
|
152
249
|
return { metadata: resultsConverted, type: this.definition.type };
|
|
153
250
|
}
|
|
154
251
|
|
|
@@ -175,14 +272,21 @@ class Automation extends MetadataType {
|
|
|
175
272
|
Util.logger.error(`${this.definition.type} '${name}' not found on server.`);
|
|
176
273
|
return;
|
|
177
274
|
}
|
|
178
|
-
|
|
275
|
+
let details = await this.client.rest.get(
|
|
179
276
|
'/automation/v1/automations/' + metadata.ObjectID
|
|
180
277
|
);
|
|
278
|
+
const metadataMap = this.parseResponseBody({ items: [details] });
|
|
279
|
+
if (Object.keys(metadataMap).length) {
|
|
280
|
+
// attach notification information to each automation that has any
|
|
281
|
+
await this.#getAutomationNotificationsREST(metadataMap);
|
|
282
|
+
details = Object.values(metadataMap)[0];
|
|
283
|
+
}
|
|
284
|
+
|
|
181
285
|
let val = null;
|
|
182
286
|
let originalKey;
|
|
183
287
|
// if parsing fails, we should just save what we get
|
|
184
288
|
try {
|
|
185
|
-
const parsedDetails = this.
|
|
289
|
+
const parsedDetails = this.postRetrieveTasks(details);
|
|
186
290
|
originalKey = parsedDetails[this.definition.keyField];
|
|
187
291
|
if (parsedDetails !== null) {
|
|
188
292
|
val = JSON.parse(
|
|
@@ -218,10 +322,128 @@ class Automation extends MetadataType {
|
|
|
218
322
|
* manages post retrieve steps
|
|
219
323
|
*
|
|
220
324
|
* @param {TYPE.AutomationItem} metadata a single automation
|
|
221
|
-
* @returns {TYPE.AutomationItem}
|
|
325
|
+
* @returns {TYPE.AutomationItem | void} parsed item
|
|
222
326
|
*/
|
|
223
327
|
static postRetrieveTasks(metadata) {
|
|
224
|
-
|
|
328
|
+
// folder
|
|
329
|
+
this.setFolderPath(metadata);
|
|
330
|
+
// automations are often skipped due to lack of support.
|
|
331
|
+
try {
|
|
332
|
+
if (metadata.type === 'scheduled' && metadata.schedule?.startDate) {
|
|
333
|
+
// Starting Source == 'Schedule'
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) {
|
|
337
|
+
// if we found the id in our list, remove the redundant data
|
|
338
|
+
delete metadata.schedule.timezoneId;
|
|
339
|
+
}
|
|
340
|
+
} catch {
|
|
341
|
+
Util.logger.debug(
|
|
342
|
+
`- Schedule name '${metadata.schedule.timezoneName}' not found in definition.timeZoneMapping`
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
try {
|
|
346
|
+
// type 'Running' is temporary status only, overwrite with Scheduled for storage.
|
|
347
|
+
if (metadata.type === 'scheduled' && metadata.status === 'Running') {
|
|
348
|
+
metadata.status = 'Scheduled';
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
Util.logger.error(
|
|
352
|
+
`- ${this.definition.type} ${metadata.name} does not have a valid schedule setting.`
|
|
353
|
+
);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
} else if (metadata.type === 'triggered' && metadata.fileTrigger) {
|
|
357
|
+
// Starting Source == 'File Drop'
|
|
358
|
+
// Do nothing for now
|
|
359
|
+
}
|
|
360
|
+
if (metadata.steps) {
|
|
361
|
+
for (const step of metadata.steps) {
|
|
362
|
+
const stepNumber = step.stepNumber || step.step;
|
|
363
|
+
delete step.stepNumber;
|
|
364
|
+
delete step.step;
|
|
365
|
+
|
|
366
|
+
for (const activity of step.activities) {
|
|
367
|
+
try {
|
|
368
|
+
// get metadata type of activity
|
|
369
|
+
activity.r__type = Util.inverseGet(
|
|
370
|
+
this.definition.activityTypeMapping,
|
|
371
|
+
activity.objectTypeId
|
|
372
|
+
);
|
|
373
|
+
delete activity.objectTypeId;
|
|
374
|
+
} catch {
|
|
375
|
+
Util.logger.warn(
|
|
376
|
+
` - Unknown activity type '${activity.objectTypeId}'` +
|
|
377
|
+
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
378
|
+
` of Automation '${metadata.name}'`
|
|
379
|
+
);
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// if no activityObjectId then either serialized activity
|
|
384
|
+
// (config in Automation ) or unconfigured so no further action to be taken
|
|
385
|
+
if (
|
|
386
|
+
activity.activityObjectId === '00000000-0000-0000-0000-000000000000' ||
|
|
387
|
+
activity.activityObjectId == null
|
|
388
|
+
) {
|
|
389
|
+
Util.logger.debug(
|
|
390
|
+
` - skip parsing of activity due to missing activityObjectId: ${JSON.stringify(
|
|
391
|
+
activity
|
|
392
|
+
)}`
|
|
393
|
+
);
|
|
394
|
+
// empty if block
|
|
395
|
+
} else if (!this.definition.dependencies.includes(activity.r__type)) {
|
|
396
|
+
Util.logger.debug(
|
|
397
|
+
` - skip parsing because the type is not set up as a dependecy for ${this.definition.type}`
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
// / if managed by cache we can update references to support deployment
|
|
401
|
+
else if (
|
|
402
|
+
Definitions[activity.r__type]?.['idField'] &&
|
|
403
|
+
cache.getCache(this.buObject.mid)[activity.r__type]
|
|
404
|
+
) {
|
|
405
|
+
try {
|
|
406
|
+
// this will override the name returned by the API in case this activity's name was changed since the automation was last updated, keeping things nicely in sync for mcdev
|
|
407
|
+
const name = cache.searchForField(
|
|
408
|
+
activity.r__type,
|
|
409
|
+
activity.activityObjectId,
|
|
410
|
+
Definitions[activity.r__type].idField,
|
|
411
|
+
Definitions[activity.r__type].nameField
|
|
412
|
+
);
|
|
413
|
+
if (name !== activity.name) {
|
|
414
|
+
Util.logger.debug(
|
|
415
|
+
` - updated name of step ${stepNumber}.${activity.displayOrder}` +
|
|
416
|
+
` in Automation '${metadata.name}' from ${activity.name} to ${name}`
|
|
417
|
+
);
|
|
418
|
+
activity.name = name;
|
|
419
|
+
}
|
|
420
|
+
} catch (ex) {
|
|
421
|
+
// getFromCache throws error where the dependent metadata is not found
|
|
422
|
+
Util.logger.warn(
|
|
423
|
+
` - Missing ${activity.r__type} activity '${activity.name}'` +
|
|
424
|
+
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
425
|
+
` of Automation '${metadata.name}' (${ex.message})`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
} else {
|
|
429
|
+
Util.logger.warn(
|
|
430
|
+
` - Missing ${activity.r__type} activity '${activity.name}'` +
|
|
431
|
+
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
432
|
+
` of Automation '${metadata.name}' (Not Found in Cache)`
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
return JSON.parse(JSON.stringify(metadata));
|
|
439
|
+
} catch (ex) {
|
|
440
|
+
Util.logger.warn(
|
|
441
|
+
` - ${this.definition.typeName} '${metadata[this.definition.nameField]}': ${
|
|
442
|
+
ex.message
|
|
443
|
+
}`
|
|
444
|
+
);
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
225
447
|
}
|
|
226
448
|
|
|
227
449
|
/**
|
|
@@ -235,7 +457,15 @@ class Automation extends MetadataType {
|
|
|
235
457
|
*/
|
|
236
458
|
static async deploy(metadata, targetBU, retrieveDir, isRefresh) {
|
|
237
459
|
const upsertResults = await this.upsert(metadata, targetBU, isRefresh);
|
|
238
|
-
await this.saveResults(upsertResults, retrieveDir, null);
|
|
460
|
+
const savedMetadata = await this.saveResults(upsertResults, retrieveDir, null);
|
|
461
|
+
if (
|
|
462
|
+
this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type) &&
|
|
463
|
+
!this.definition.documentInOneFile
|
|
464
|
+
) {
|
|
465
|
+
const count = Object.keys(savedMetadata).length;
|
|
466
|
+
Util.logger.debug(` - Running document for ${count} record${count === 1 ? '' : 's'}`);
|
|
467
|
+
await this.document(savedMetadata);
|
|
468
|
+
}
|
|
239
469
|
return upsertResults;
|
|
240
470
|
}
|
|
241
471
|
|
|
@@ -270,6 +500,15 @@ class Automation extends MetadataType {
|
|
|
270
500
|
* @returns {Promise.<TYPE.AutomationItem>} Promise
|
|
271
501
|
*/
|
|
272
502
|
static async preDeployTasks(metadata) {
|
|
503
|
+
if (metadata.notifications) {
|
|
504
|
+
this.notificationUpdates[metadata.key] = metadata.notifications;
|
|
505
|
+
} else {
|
|
506
|
+
const cached = cache.getByKey(metadata.key);
|
|
507
|
+
if (cached?.notifications) {
|
|
508
|
+
// if notifications existed but are no longer present in the deployment package, we need to run an empty update call to remove them
|
|
509
|
+
this.notificationUpdates[metadata.key] = [];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
273
512
|
if (this.validateDeployMetadata(metadata)) {
|
|
274
513
|
// folder
|
|
275
514
|
this.setFolderId(metadata);
|
|
@@ -397,82 +636,153 @@ class Automation extends MetadataType {
|
|
|
397
636
|
/**
|
|
398
637
|
* Gets executed after deployment of metadata type
|
|
399
638
|
*
|
|
400
|
-
* @param {TYPE.AutomationMap}
|
|
401
|
-
* @param {TYPE.AutomationMap}
|
|
639
|
+
* @param {TYPE.AutomationMap} metadataMap metadata mapped by their keyField
|
|
640
|
+
* @param {TYPE.AutomationMap} originalMetadataMap metadata to be updated (contains additioanl fields)
|
|
402
641
|
* @returns {Promise.<void>} -
|
|
403
642
|
*/
|
|
404
|
-
static async postDeployTasks(
|
|
405
|
-
for (const key in
|
|
643
|
+
static async postDeployTasks(metadataMap, originalMetadataMap) {
|
|
644
|
+
for (const key in metadataMap) {
|
|
406
645
|
// need to put schedule on here if status is scheduled
|
|
646
|
+
await Automation.#scheduleAutomation(metadataMap, originalMetadataMap, key);
|
|
407
647
|
|
|
408
|
-
if
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
648
|
+
// need to update notifications separately if there are any
|
|
649
|
+
await Automation.#updateNotificationInfoREST(metadataMap, key);
|
|
650
|
+
|
|
651
|
+
// rewrite upsert to retrieve fields
|
|
652
|
+
const metadata = metadataMap[key];
|
|
653
|
+
if (metadata.steps) {
|
|
654
|
+
for (const step of metadata.steps) {
|
|
655
|
+
step.name = step.annotation;
|
|
656
|
+
delete step.annotation;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* helper for {@link Automation.postDeployTasks}
|
|
663
|
+
*
|
|
664
|
+
* @param {TYPE.AutomationMap} metadataMap metadata mapped by their keyField
|
|
665
|
+
* @param {string} key current customer key
|
|
666
|
+
* @returns {Promise.<void>} -
|
|
667
|
+
*/
|
|
668
|
+
static async #updateNotificationInfoREST(metadataMap, key) {
|
|
669
|
+
if (this.notificationUpdates[key]) {
|
|
670
|
+
// create & update automation calls return programId as 'legacyId'; retrieve does not return it
|
|
671
|
+
const programId = metadataMap[key]?.legacyId;
|
|
672
|
+
if (programId) {
|
|
673
|
+
const notificationBody = {
|
|
674
|
+
programId,
|
|
675
|
+
workers: this.notificationUpdates[key].map((notification) => ({
|
|
676
|
+
programId,
|
|
677
|
+
notificationType: notification.type,
|
|
678
|
+
definition: Array.isArray(notification.email)
|
|
679
|
+
? notification.email.join(',')
|
|
680
|
+
: notification.email,
|
|
681
|
+
body: notification.message,
|
|
682
|
+
channelType: 'Account',
|
|
683
|
+
})),
|
|
684
|
+
};
|
|
685
|
+
try {
|
|
686
|
+
const result = await this.client.rest.post(
|
|
687
|
+
'/legacy/v1/beta/automations/notifications/' + programId,
|
|
688
|
+
notificationBody
|
|
689
|
+
);
|
|
690
|
+
if (result) {
|
|
691
|
+
// should be empty if all OK
|
|
692
|
+
throw new Error(result);
|
|
693
|
+
}
|
|
694
|
+
} catch (ex) {
|
|
695
|
+
Util.logger.error(
|
|
696
|
+
`Error updating notifications for automation '${metadataMap[key].name}': ${ex.message} (${ex.code}))`
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
Util.logger.info(
|
|
700
|
+
Util.getGrayMsg(
|
|
701
|
+
` - updated notifications for automation '${metadataMap[key].name}'`
|
|
702
|
+
)
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* helper for {@link postDeployTasks}
|
|
710
|
+
*
|
|
711
|
+
* @param {TYPE.AutomationMap} metadataMap metadata mapped by their keyField
|
|
712
|
+
* @param {TYPE.AutomationMap} originalMetadataMap metadata to be updated (contains additioanl fields)
|
|
713
|
+
* @param {string} key current customer key
|
|
714
|
+
*/
|
|
715
|
+
static async #scheduleAutomation(metadataMap, originalMetadataMap, key) {
|
|
716
|
+
if (originalMetadataMap[key]?.type === 'scheduled') {
|
|
717
|
+
// Starting Source == 'Schedule': Try starting the automation
|
|
718
|
+
if (originalMetadataMap[key].status === 'Scheduled') {
|
|
719
|
+
let schedule = null;
|
|
720
|
+
try {
|
|
721
|
+
schedule = this._buildSchedule(originalMetadataMap[key].schedule);
|
|
722
|
+
} catch (ex) {
|
|
723
|
+
Util.logger.error(
|
|
724
|
+
`- Could not create schedule for automation '${originalMetadataMap[key].name}' to start it: ${ex.message}`
|
|
725
|
+
);
|
|
726
|
+
}
|
|
727
|
+
if (schedule !== null) {
|
|
412
728
|
try {
|
|
413
|
-
schedule
|
|
729
|
+
// remove the fields that are not needed for the schedule but only for CLI output
|
|
730
|
+
const schedule_StartDateTime = schedule._StartDateTime;
|
|
731
|
+
delete schedule._StartDateTime;
|
|
732
|
+
const schedule_interval = schedule._interval;
|
|
733
|
+
delete schedule._interval;
|
|
734
|
+
const schedule_timezoneString = schedule._timezoneString;
|
|
735
|
+
delete schedule._timezoneString;
|
|
736
|
+
// start the automation
|
|
737
|
+
await this.client.soap.schedule(
|
|
738
|
+
'Automation',
|
|
739
|
+
schedule,
|
|
740
|
+
{
|
|
741
|
+
Interaction: {
|
|
742
|
+
ObjectID: metadataMap[key].id,
|
|
743
|
+
},
|
|
744
|
+
},
|
|
745
|
+
'start',
|
|
746
|
+
{}
|
|
747
|
+
);
|
|
748
|
+
const intervalString =
|
|
749
|
+
(schedule_interval > 1 ? `${schedule_interval} ` : '') +
|
|
750
|
+
(schedule.RecurrenceType === 'Daily'
|
|
751
|
+
? 'Day'
|
|
752
|
+
: schedule.RecurrenceType.slice(0, -2) +
|
|
753
|
+
(schedule_interval > 1 ? 's' : ''));
|
|
754
|
+
Util.logger.warn(
|
|
755
|
+
` - scheduled automation '${
|
|
756
|
+
originalMetadataMap[key].name
|
|
757
|
+
}' deployed as Active: runs every ${intervalString} starting ${
|
|
758
|
+
schedule_StartDateTime.split('T').join(' ').split('.')[0]
|
|
759
|
+
} ${schedule_timezoneString}`
|
|
760
|
+
);
|
|
414
761
|
} catch (ex) {
|
|
415
762
|
Util.logger.error(
|
|
416
|
-
`- Could not
|
|
763
|
+
`- Could not start scheduled automation '${originalMetadataMap[key].name}': ${ex.message}`
|
|
417
764
|
);
|
|
418
765
|
}
|
|
419
|
-
if (schedule !== null) {
|
|
420
|
-
try {
|
|
421
|
-
// remove the fields that are not needed for the schedule but only for CLI output
|
|
422
|
-
const schedule_StartDateTime = schedule._StartDateTime;
|
|
423
|
-
delete schedule._StartDateTime;
|
|
424
|
-
const schedule_interval = schedule._interval;
|
|
425
|
-
delete schedule._interval;
|
|
426
|
-
const schedule_timezoneString = schedule._timezoneString;
|
|
427
|
-
delete schedule._timezoneString;
|
|
428
|
-
// start the automation
|
|
429
|
-
await this.client.soap.schedule(
|
|
430
|
-
'Automation',
|
|
431
|
-
schedule,
|
|
432
|
-
{
|
|
433
|
-
Interaction: {
|
|
434
|
-
ObjectID: metadata[key].id,
|
|
435
|
-
},
|
|
436
|
-
},
|
|
437
|
-
'start',
|
|
438
|
-
{}
|
|
439
|
-
);
|
|
440
|
-
const intervalString =
|
|
441
|
-
(schedule_interval > 1 ? `${schedule_interval} ` : '') +
|
|
442
|
-
(schedule.RecurrenceType === 'Daily'
|
|
443
|
-
? 'Day'
|
|
444
|
-
: schedule.RecurrenceType.slice(0, -2) +
|
|
445
|
-
(schedule_interval > 1 ? 's' : ''));
|
|
446
|
-
Util.logger.warn(
|
|
447
|
-
` - scheduled automation '${
|
|
448
|
-
originalMetadata[key].name
|
|
449
|
-
}' deployed Active: runs every ${intervalString} starting ${
|
|
450
|
-
schedule_StartDateTime.split('T').join(' ').split('.')[0]
|
|
451
|
-
} ${schedule_timezoneString}`
|
|
452
|
-
);
|
|
453
|
-
} catch (ex) {
|
|
454
|
-
Util.logger.error(
|
|
455
|
-
`- Could not start scheduled automation '${originalMetadata[key].name}': ${ex.message}`
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
} else {
|
|
460
|
-
Util.logger.warn(
|
|
461
|
-
` - scheduled automation '${originalMetadata[key].name}' deployed Paused`
|
|
462
|
-
);
|
|
463
766
|
}
|
|
767
|
+
} else {
|
|
768
|
+
Util.logger.info(
|
|
769
|
+
Util.getGrayMsg(
|
|
770
|
+
` - scheduled automation '${originalMetadataMap[key].name}' deployed as Paused`
|
|
771
|
+
)
|
|
772
|
+
);
|
|
464
773
|
}
|
|
465
|
-
|
|
466
|
-
|
|
774
|
+
}
|
|
775
|
+
if (metadataMap[key].startSource) {
|
|
776
|
+
metadataMap[key].schedule = metadataMap[key].startSource.schedule;
|
|
467
777
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
}
|
|
778
|
+
delete metadataMap[key].startSource;
|
|
779
|
+
}
|
|
780
|
+
if (metadataMap[key].schedule?.scheduleTypeId) {
|
|
781
|
+
metadataMap[key].schedule.typeId = metadataMap[key].schedule.scheduleTypeId;
|
|
782
|
+
delete metadataMap[key].schedule.scheduleTypeId;
|
|
474
783
|
}
|
|
475
784
|
}
|
|
785
|
+
|
|
476
786
|
/**
|
|
477
787
|
* generic script that retrieves the folder path from cache and updates the given metadata with it after retrieve
|
|
478
788
|
*
|
|
@@ -538,118 +848,6 @@ class Automation extends MetadataType {
|
|
|
538
848
|
}
|
|
539
849
|
}
|
|
540
850
|
|
|
541
|
-
/**
|
|
542
|
-
* parses retrieved Metadata before saving
|
|
543
|
-
*
|
|
544
|
-
* @param {TYPE.AutomationItem} metadata a single automation definition
|
|
545
|
-
* @returns {TYPE.AutomationItem | void} parsed item
|
|
546
|
-
*/
|
|
547
|
-
static parseMetadata(metadata) {
|
|
548
|
-
// folder
|
|
549
|
-
this.setFolderPath(metadata);
|
|
550
|
-
// automations are often skipped due to lack of support.
|
|
551
|
-
try {
|
|
552
|
-
if (metadata.type === 'scheduled' && metadata.schedule?.startDate) {
|
|
553
|
-
// Starting Source == 'Schedule'
|
|
554
|
-
|
|
555
|
-
try {
|
|
556
|
-
if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) {
|
|
557
|
-
// if we found the id in our list, remove the redundant data
|
|
558
|
-
delete metadata.schedule.timezoneId;
|
|
559
|
-
}
|
|
560
|
-
} catch {
|
|
561
|
-
Util.logger.debug(
|
|
562
|
-
`- Schedule name '${metadata.schedule.timezoneName}' not found in definition.timeZoneMapping`
|
|
563
|
-
);
|
|
564
|
-
}
|
|
565
|
-
try {
|
|
566
|
-
// type 'Running' is temporary status only, overwrite with Scheduled for storage.
|
|
567
|
-
if (metadata.type === 'scheduled' && metadata.status === 'Running') {
|
|
568
|
-
metadata.status = 'Scheduled';
|
|
569
|
-
}
|
|
570
|
-
} catch {
|
|
571
|
-
Util.logger.error(
|
|
572
|
-
`- ${this.definition.type} ${metadata.name} does not have a valid schedule setting.`
|
|
573
|
-
);
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
} else if (metadata.type === 'triggered' && metadata.fileTrigger) {
|
|
577
|
-
// Starting Source == 'File Drop'
|
|
578
|
-
// Do nothing for now
|
|
579
|
-
}
|
|
580
|
-
if (metadata.steps) {
|
|
581
|
-
for (const step of metadata.steps) {
|
|
582
|
-
const stepNumber = step.stepNumber || step.step;
|
|
583
|
-
delete step.stepNumber;
|
|
584
|
-
delete step.step;
|
|
585
|
-
|
|
586
|
-
for (const activity of step.activities) {
|
|
587
|
-
try {
|
|
588
|
-
// get metadata type of activity
|
|
589
|
-
activity.r__type = Util.inverseGet(
|
|
590
|
-
this.definition.activityTypeMapping,
|
|
591
|
-
activity.objectTypeId
|
|
592
|
-
);
|
|
593
|
-
delete activity.objectTypeId;
|
|
594
|
-
} catch {
|
|
595
|
-
Util.logger.warn(
|
|
596
|
-
` - Unknown activity type '${activity.objectTypeId}'` +
|
|
597
|
-
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
598
|
-
` of Automation '${metadata.name}'`
|
|
599
|
-
);
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
// if no activityObjectId then either serialized activity
|
|
604
|
-
// (config in Automation ) or unconfigured so no further action to be taken
|
|
605
|
-
if (
|
|
606
|
-
activity.activityObjectId === '00000000-0000-0000-0000-000000000000' ||
|
|
607
|
-
activity.activityObjectId == null ||
|
|
608
|
-
!this.definition.dependencies.includes(activity.r__type)
|
|
609
|
-
) {
|
|
610
|
-
// empty if block
|
|
611
|
-
}
|
|
612
|
-
// / if managed by cache we can update references to support deployment
|
|
613
|
-
else if (
|
|
614
|
-
Definitions[activity.r__type]?.['idField'] &&
|
|
615
|
-
cache.getCache(this.buObject.mid)[activity.r__type]
|
|
616
|
-
) {
|
|
617
|
-
try {
|
|
618
|
-
activity.activityObjectId = cache.searchForField(
|
|
619
|
-
activity.r__type,
|
|
620
|
-
activity.activityObjectId,
|
|
621
|
-
Definitions[activity.r__type].idField,
|
|
622
|
-
Definitions[activity.r__type].nameField
|
|
623
|
-
);
|
|
624
|
-
} catch (ex) {
|
|
625
|
-
// getFromCache throws error where the dependent metadata is not found
|
|
626
|
-
Util.logger.warn(
|
|
627
|
-
` - Missing ${activity.r__type} activity '${activity.name}'` +
|
|
628
|
-
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
629
|
-
` of Automation '${metadata.name}' (${ex.message})`
|
|
630
|
-
);
|
|
631
|
-
}
|
|
632
|
-
} else {
|
|
633
|
-
Util.logger.warn(
|
|
634
|
-
` - Missing ${activity.r__type} activity '${activity.name}'` +
|
|
635
|
-
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
636
|
-
` of Automation '${metadata.name}' (Not Found in Cache)`
|
|
637
|
-
);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
return JSON.parse(JSON.stringify(metadata));
|
|
643
|
-
} catch (ex) {
|
|
644
|
-
Util.logger.warn(
|
|
645
|
-
` - ${this.definition.typeName} '${metadata[this.definition.nameField]}': ${
|
|
646
|
-
ex.message
|
|
647
|
-
}`
|
|
648
|
-
);
|
|
649
|
-
return null;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
851
|
/**
|
|
654
852
|
* Builds a schedule object to be used for scheduling an automation
|
|
655
853
|
* based on combination of ical string and start/end dates.
|
|
@@ -857,6 +1055,27 @@ class Automation extends MetadataType {
|
|
|
857
1055
|
output += `* Pattern: ${json.fileTrigger.fileNamingPattern}\n`;
|
|
858
1056
|
output += `* Folder: ${json.fileTrigger.folderLocationText}\n`;
|
|
859
1057
|
}
|
|
1058
|
+
// add empty line to ensure the following notifications are rendered properly
|
|
1059
|
+
output += '\n';
|
|
1060
|
+
if (json.notifications?.length) {
|
|
1061
|
+
output += `**Notifications:**\n\n`;
|
|
1062
|
+
// ensure notifications are sorted by type regardless of how the API returns it
|
|
1063
|
+
const notifications = {};
|
|
1064
|
+
for (const n of json.notifications) {
|
|
1065
|
+
notifications[n.type] =
|
|
1066
|
+
(Array.isArray(n.email) ? n.email.join(',') : n.email) +
|
|
1067
|
+
(n.message ? ` ("${n.message}")` : '');
|
|
1068
|
+
}
|
|
1069
|
+
if (notifications.Complete) {
|
|
1070
|
+
output += `* Complete: ${notifications.Complete}\n`;
|
|
1071
|
+
}
|
|
1072
|
+
if (notifications.Error) {
|
|
1073
|
+
output += `* Error: ${notifications.Error}\n`;
|
|
1074
|
+
}
|
|
1075
|
+
} else {
|
|
1076
|
+
output += `**Notifications:** _none_\n\n`;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
860
1079
|
// show table with automation steps
|
|
861
1080
|
if (tabled && tabled.length) {
|
|
862
1081
|
// add empty line to ensure the following table is rendered properly
|
|
@@ -964,11 +1183,10 @@ class Automation extends MetadataType {
|
|
|
964
1183
|
// as part of retrieve & manual execution we could face an empty folder
|
|
965
1184
|
return;
|
|
966
1185
|
}
|
|
967
|
-
await Promise.all(
|
|
968
|
-
Object.keys(metadata).map((key) =>
|
|
969
|
-
this._writeDoc(docPath + '/', key, metadata[key], 'md')
|
|
970
|
-
|
|
971
|
-
})
|
|
1186
|
+
return await Promise.all(
|
|
1187
|
+
Object.keys(metadata).map((key) =>
|
|
1188
|
+
this._writeDoc(docPath + '/', key, metadata[key], 'md')
|
|
1189
|
+
)
|
|
972
1190
|
);
|
|
973
1191
|
}
|
|
974
1192
|
}
|