mcdev 5.1.0 → 5.2.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/.eslintrc.json +4 -4
- package/.github/ISSUE_TEMPLATE/bug.yml +1 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +2 -2
- package/.github/workflows/coverage-develop-branch.yml +0 -2
- package/.github/workflows/coverage-main-branch.yml +0 -2
- package/.github/workflows/coverage.yml +0 -2
- package/.husky/post-checkout +1 -0
- package/.husky/post-merge +1 -0
- package/.vscode/extensions.json +4 -0
- package/docs/dist/documentation.md +633 -286
- package/lib/Deployer.js +25 -25
- package/lib/MetadataTypeDefinitions.js +1 -1
- package/lib/MetadataTypeInfo.js +1 -1
- package/lib/Retriever.js +1 -1
- package/lib/cli.js +159 -9
- package/lib/index.js +395 -95
- package/lib/metadataTypes/Asset.js +10 -11
- package/lib/metadataTypes/AttributeGroup.js +76 -2
- package/lib/metadataTypes/AttributeSet.js +260 -0
- package/lib/metadataTypes/Automation.js +413 -96
- package/lib/metadataTypes/DataExtension.js +2 -2
- package/lib/metadataTypes/DataExtensionField.js +1 -1
- package/lib/metadataTypes/Event.js +2 -3
- package/lib/metadataTypes/Folder.js +1 -1
- package/lib/metadataTypes/Journey.js +5 -6
- package/lib/metadataTypes/MetadataType.js +149 -49
- package/lib/metadataTypes/MobileKeyword.js +8 -8
- package/lib/metadataTypes/MobileMessage.js +5 -5
- package/lib/metadataTypes/Query.js +26 -10
- package/lib/metadataTypes/Script.js +3 -3
- package/lib/metadataTypes/TransactionalSMS.js +5 -5
- package/lib/metadataTypes/TriggeredSend.js +25 -50
- package/lib/metadataTypes/User.js +7 -4
- package/lib/metadataTypes/definitions/AttributeGroup.definition.js +117 -106
- package/lib/metadataTypes/definitions/{SetDefinition.definition.js → AttributeSet.definition.js} +54 -27
- package/lib/metadataTypes/definitions/Automation.definition.js +22 -15
- package/lib/metadataTypes/definitions/ImportFile.definition.js +36 -6
- package/lib/metadataTypes/definitions/TriggeredSend.definition.js +1 -0
- package/lib/util/cache.js +9 -4
- package/lib/util/cli.js +40 -0
- package/lib/util/file.js +2 -2
- package/lib/util/init.js +84 -0
- package/lib/util/util.js +121 -13
- package/package.json +11 -11
- package/test/mockRoot/.mcdevrc.json +1 -1
- package/test/mockRoot/deploy/testInstance/testBU/automation/testExisting_automation.automation-meta.json +1 -2
- package/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json +5 -6
- package/test/mockRoot/deploy/testInstance/testBU/dataExtract/testExisting_dataExtract.dataExtract-meta.json +35 -0
- package/test/mockRoot/deploy/testInstance/testBU/dataExtract/testNew_dataExtract.dataExtract-meta.json +35 -0
- package/test/mockRoot/deploy/testInstance/testBU/fileTransfer/testExisting_fileTransfer.fileTransfer-meta.json +17 -0
- package/test/mockRoot/deploy/testInstance/testBU/fileTransfer/testNew_fileTransfer.fileTransfer-meta.json +17 -0
- package/test/mockRoot/deploy/testInstance/testBU/importFile/testExisting_importFile.importFile-meta.json +29 -0
- package/test/mockRoot/deploy/testInstance/testBU/importFile/testNew_importFile.importFile-meta.json +29 -0
- package/test/mockRoot/deploy/testInstance/testBU/query/testExisting_query_fixKeys.query-meta.json +11 -0
- package/test/mockRoot/deploy/testInstance/testBU/query/testExisting_query_fixKeys.query-meta.sql +6 -0
- package/test/mockRoot/deploy/testInstance/testBU/script/testExisting_script.script-meta.json +6 -0
- package/test/mockRoot/deploy/testInstance/testBU/script/testExisting_script.script-meta.ssjs +1 -0
- package/test/mockRoot/deploy/testInstance/testBU/script/testNew_script.script-meta.json +6 -0
- package/test/mockRoot/deploy/testInstance/testBU/script/testNew_script.script-meta.ssjs +1 -0
- package/test/mockRoot/deploy/testInstance/testBU/triggeredSend/testExisting_triggeredSend.triggeredSend-meta.json +29 -0
- package/test/mockRoot/deploy/testInstance/testBU/triggeredSend/testNew_triggeredSend.triggeredSend-meta.json +29 -0
- package/test/resourceFactory.js +77 -12
- package/test/resources/1111111/accountUser/retrieve-ActiveFlag=falseANDCustomerKey=testExisting_userANDEmaillike@-response.xml +27 -0
- package/test/resources/1111111/accountUser/retrieve-ActiveFlag=falseANDEmaillike@-response.xml +156 -0
- package/test/resources/1111111/accountUser/retrieve-ActiveFlag=trueANDEmailisNullORNamelikeapp userANDMustChangePassword=false-response.xml +87 -0
- package/test/resources/1111111/accountUser/retrieve-ActiveFlag=trueANDEmaillike@-response.xml +156 -0
- package/test/resources/1111111/accountUser/retrieve-CustomerKey=testExisting_userANDActiveFlag=trueANDEmailisNullORNamelikeapp userANDMustChangePassword=false-response.xml +27 -0
- package/test/resources/1111111/accountUserAccount/retrieve-AccountUser.AccountUserID=700301950-response.xml +60 -0
- package/test/resources/1111111/user/retrieve-expected.md +4 -2
- package/test/resources/9999999/attributeGroup/retrieve-expected.json +25 -0
- package/test/resources/9999999/attributeSet/retrieve-expected.json +748 -0
- package/test/resources/9999999/automation/build-expected.json +1 -2
- package/test/resources/9999999/automation/create-expected.json +7 -8
- package/test/resources/9999999/automation/create-testNew_automation-expected.md +4 -4
- package/test/resources/9999999/automation/patch_fixKeys-pause-expected.json +44 -0
- package/test/resources/9999999/automation/patch_fixKeys-schedule-expected.json +44 -0
- package/test/resources/9999999/automation/perform-08afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +42 -0
- package/test/resources/9999999/automation/perform-08afb0e2-b00a-4c88-fixKey_pause-response.xml +42 -0
- package/test/resources/9999999/automation/perform-08afb0e2-b00a-4c88-fixKey_schedule-response.xml +42 -0
- package/test/resources/9999999/automation/perform-a8afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +42 -0
- package/test/resources/9999999/automation/retrieve-expected.json +1 -2
- package/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md +2 -2
- package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +52 -0
- package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-pause-response.xml +38 -0
- package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-fixKey_pause-response.xml +52 -0
- package/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-fixKey_schedule-response.xml +52 -0
- package/test/resources/9999999/automation/schedule-a8afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml +52 -0
- package/test/resources/9999999/automation/template-expected.json +1 -2
- package/test/resources/9999999/automation/update-expected.json +1 -2
- package/test/resources/9999999/automation/update-testExisting_automation-expected.md +2 -2
- package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-pause/get-response.json +85 -0
- package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-pause/patch-response.json +85 -0
- package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_pause/get-response.json +85 -0
- package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_pause/patch-response.json +85 -0
- package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_schedule/get-response.json +85 -0
- package/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-fixKey_schedule/patch-response.json +85 -0
- package/test/resources/9999999/automation/v1/automations/a8afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json +1 -1
- package/test/resources/9999999/automation/v1/automations/post-response.json +20 -19
- package/test/resources/9999999/automation/v1/dataextracts/56c5370a-f988-4f36-b0ee-0f876573f6d7/patch-response.json +38 -0
- package/test/resources/9999999/automation/v1/dataextracts/post-response.json +38 -0
- package/test/resources/9999999/automation/v1/dataextracttypes/get-response.json +50 -0
- package/test/resources/9999999/automation/v1/filetransfers/72c328ac-f5b0-4e37-91d3-a775666f15a6/patch-response.json +18 -0
- package/test/resources/9999999/automation/v1/filetransfers/post-response.json +18 -0
- package/test/resources/9999999/automation/v1/ftplocations/get-response.json +18 -0
- package/test/resources/9999999/automation/v1/imports/9d16f42c-2260-ed11-b849-48df37d1de8b/patch-response.json +31 -0
- package/test/resources/9999999/automation/v1/imports/get-response.json +1 -1
- package/test/resources/9999999/automation/v1/imports/post-response.json +30 -0
- package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dae/actions/start/post-response.txt +1 -0
- package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat_fixKeys/get-response.json +17 -0
- package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat_fixKeys/patch-response.json +18 -0
- package/test/resources/9999999/automation/v1/queries/get-response.json +18 -1
- package/test/resources/9999999/automation/v1/scripts/39f6a488-20eb-4ba0-b0b9-023725b574e4/patch-response.json +10 -0
- package/test/resources/9999999/automation/v1/scripts/get-response.json +12 -2
- package/test/resources/9999999/automation/v1/scripts/post-response.json +10 -0
- package/test/resources/9999999/dataExtension/retrieve-Name=testExisting_dataExtension-response.xml +52 -0
- package/test/resources/9999999/dataExtensionField/retrieve-DataExtension.CustomerKey=testExisting_dataExtension-response.xml +98 -0
- package/test/resources/9999999/dataExtensionField/retrieve-DataExtension.CustomerKey=testNew_dataExtensionORDataExtension.CustomerKey=testExisting_dataExtension-response.xml +99 -0
- package/test/resources/9999999/dataExtract/build-expected.json +35 -0
- package/test/resources/9999999/dataExtract/get-expected.json +39 -0
- package/test/resources/9999999/dataExtract/patch-expected.json +37 -0
- package/test/resources/9999999/dataExtract/post-expected.json +37 -0
- package/test/resources/9999999/dataExtract/template-expected.json +35 -0
- package/test/resources/9999999/dataFolder/retrieve-ContentType=contextual_suppression_listORContentType=publicationORContentType=suppression_listORContentType=mysubsORContentType=list-response.xml +136 -0
- package/test/resources/9999999/dataFolder/retrieve-ContentType=ssjsactivity-response.xml +48 -0
- package/test/resources/9999999/dataFolder/retrieve-ContentType=triggered_send_journeybuilderORContentType=triggered_sendORContentType=hidden-response.xml +276 -0
- package/test/resources/9999999/dataFolder/retrieve-response.xml +23 -0
- package/test/resources/9999999/email/retrieve-response.xml +203 -0
- package/test/resources/9999999/fileTransfer/build-expected.json +15 -0
- package/test/resources/9999999/fileTransfer/get-expected.json +17 -0
- package/test/resources/9999999/fileTransfer/patch-expected.json +17 -0
- package/test/resources/9999999/fileTransfer/post-expected.json +17 -0
- package/test/resources/9999999/fileTransfer/template-expected.json +15 -0
- package/test/resources/9999999/hub/v1/contacts/schema/attributeGroups/get-response.json +585 -0
- package/test/resources/9999999/hub/v1/contacts/schema/setDefinitions/get-response.json +19807 -0
- package/test/resources/9999999/importFile/build-expected.json +27 -0
- package/test/resources/9999999/importFile/get-expected.json +29 -0
- package/test/resources/9999999/importFile/patch-expected.json +29 -0
- package/test/resources/9999999/importFile/post-expected.json +29 -0
- package/test/resources/9999999/importFile/template-expected.json +27 -0
- package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_fixKey_pause-response.xml +32 -0
- package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_fixKey_schedule-response.xml +32 -0
- package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_fixedKey_paused-response.xml +32 -0
- package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_fixedKey_scheduled-response.xml +32 -0
- package/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_pause-response.xml +30 -0
- package/test/resources/9999999/program/retrieve-response.xml +21 -3
- package/test/resources/9999999/query/patch_fixKeys-expected.json +11 -0
- package/test/resources/9999999/query/patch_fixKeys-expected.sql +6 -0
- package/test/resources/9999999/queryDefinition/retrieve-CustomerKey=testExisting_query_fixKeysANDStatus=Active-response.xml +30 -0
- package/test/resources/9999999/queryDefinition/retrieve-CustomerKey=testExisting_query_fixedKeysANDStatus=Active-response.xml +30 -0
- package/test/resources/9999999/queryDefinition/retrieve-CustomerKey=testNew_queryANDStatus=Active-response.xml +30 -0
- package/test/resources/9999999/script/build-expected.json +6 -0
- package/test/resources/9999999/script/build-expected.ssjs +1 -0
- package/test/resources/9999999/script/get-expected.json +8 -0
- package/test/resources/9999999/script/get-expected.ssjs +1 -0
- package/test/resources/9999999/script/get_noScriptTag-expected.html +1 -0
- package/test/resources/9999999/script/get_noScriptTag-expected.json +8 -0
- package/test/resources/9999999/script/patch-expected.json +8 -0
- package/test/resources/9999999/script/patch-expected.ssjs +1 -0
- package/test/resources/9999999/script/post-expected.json +8 -0
- package/test/resources/9999999/script/post-expected.ssjs +1 -0
- package/test/resources/9999999/script/template-expected.json +6 -0
- package/test/resources/9999999/script/template-expected.ssjs +1 -0
- package/test/resources/9999999/triggeredSend/build-expected.json +29 -0
- package/test/resources/9999999/triggeredSend/get-expected.json +29 -0
- package/test/resources/9999999/triggeredSend/patch-expected.json +29 -0
- package/test/resources/9999999/triggeredSend/post-expected.json +29 -0
- package/test/resources/9999999/triggeredSend/template-expected.json +29 -0
- package/test/resources/9999999/triggeredSendDefinition/create-response.xml +75 -0
- package/test/resources/9999999/triggeredSendDefinition/delete-response.xml +36 -0
- package/test/resources/9999999/triggeredSendDefinition/{retrieve-response.xml → retrieve-TriggeredSendStatusINNew,Active,Inactive,Moved,Canceled-response.xml} +4 -4
- package/test/resources/9999999/triggeredSendDefinition/update-response.xml +74 -0
- package/test/type.attributeGroup.test.js +55 -0
- package/test/type.attributeSet.test.js +55 -0
- package/test/type.automation.test.js +638 -11
- package/test/type.dataExtension.test.js +0 -1
- package/test/type.dataExtract.test.js +187 -0
- package/test/type.fileTransfer.test.js +185 -0
- package/test/type.importFile.test.js +186 -0
- package/test/type.mobileKeyword.test.js +0 -1
- package/test/type.query.test.js +464 -13
- package/test/type.script.test.js +367 -0
- package/test/type.triggeredSend.test.js +152 -0
- package/test/type.user.test.js +22 -10
- package/test/utils.js +4 -1
- package/lib/metadataTypes/SetDefinition.js +0 -37
- /package/test/resources/1111111/accountUser/{retrieve-response.xml → retrieve-ActiveFlag=trueANDCustomerKey=testExisting_userANDEmaillike@-response.xml} +0 -0
- /package/test/resources/1111111/accountUserAccount/{retrieve-response.xml → retrieve-AccountUser.AccountUserIDIN700301950,700301951,7471228-response.xml} +0 -0
- /package/test/resources/1111111/businessUnit/{retrieve-response.xml → retrieve-ID=1111111-response.xml} +0 -0
- /package/test/resources/1111111/list/{retrieve-response.xml → retrieve-CustomerKey=All SubscribersORListName=All Subscribers-response.xml} +0 -0
- /package/test/resources/1111111/role/{retrieve-response.xml → retrieve-IsPrivate=false-response.xml} +0 -0
- /package/test/resources/9999999/emailSendDefinition/{retrieve-response.xml → retrieve-IsPlatformObject=falseANDDescriptionnotEqualsSFSendDefinition-response.xml} +0 -0
- /package/test/resources/9999999/queryDefinition/{retrieve-response.xml → retrieve-CustomerKey=testExisting_queryANDStatus=Active-response.xml} +0 -0
|
@@ -47,34 +47,41 @@ class Automation extends MetadataType {
|
|
|
47
47
|
)
|
|
48
48
|
);
|
|
49
49
|
}
|
|
50
|
+
// the API seems to handle 50 concurrent requests nicely
|
|
51
|
+
const rateLimit = pLimit(50);
|
|
52
|
+
|
|
50
53
|
const details = results.Results
|
|
51
54
|
? await Promise.all(
|
|
52
|
-
results.Results.map(async (
|
|
53
|
-
|
|
54
|
-
return await this.client.rest.get(
|
|
55
|
-
'/automation/v1/automations/' + a.ObjectID
|
|
56
|
-
);
|
|
57
|
-
} catch (ex) {
|
|
55
|
+
results.Results.map(async (item) =>
|
|
56
|
+
rateLimit(async () => {
|
|
58
57
|
try {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
58
|
+
return await this.client.rest.get(
|
|
59
|
+
'/automation/v1/automations/' + item.ObjectID
|
|
60
|
+
);
|
|
61
|
+
} catch (ex) {
|
|
62
|
+
try {
|
|
63
|
+
if (ex.message == 'socket hang up') {
|
|
64
|
+
// one more retry; it's a rare case but retrying again should solve the issue gracefully
|
|
65
|
+
return await this.client.rest.get(
|
|
66
|
+
'/automation/v1/automations/' + item.ObjectID
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
} catch {
|
|
70
|
+
// no extra action needed, handled below
|
|
64
71
|
}
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
// if we do get here, we should log the error and continue instead of failing to download all automations
|
|
73
|
+
Util.logger.error(
|
|
74
|
+
` ☇ skipping Automation ${item.ObjectID}: ${ex.message} ${ex.code}`
|
|
75
|
+
);
|
|
76
|
+
return null;
|
|
67
77
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
` ☇ skipping Automation ${a.ObjectID}: ${ex.message} ${ex.code}`
|
|
71
|
-
);
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
})
|
|
78
|
+
})
|
|
79
|
+
)
|
|
75
80
|
)
|
|
76
81
|
: [];
|
|
77
|
-
|
|
82
|
+
|
|
83
|
+
// * if retrieving some automations fails, a null element would remain in the details-array for each of them that needs to be filtered to prevent it from causing issues elsewhere
|
|
84
|
+
let metadataMap = this.parseResponseBody({ items: details.filter(Boolean) });
|
|
78
85
|
|
|
79
86
|
if (Object.keys(metadataMap).length) {
|
|
80
87
|
// attach notification information to each automation that has any
|
|
@@ -144,11 +151,19 @@ class Automation extends MetadataType {
|
|
|
144
151
|
}));
|
|
145
152
|
found++;
|
|
146
153
|
} else {
|
|
147
|
-
|
|
154
|
+
if (
|
|
155
|
+
!notificationsResult ||
|
|
156
|
+
typeof notificationsResult !== 'object' ||
|
|
157
|
+
Object.keys(notificationsResult).length !== 1 ||
|
|
158
|
+
!notificationsResult?.programId
|
|
159
|
+
) {
|
|
160
|
+
throw new TypeError(JSON.stringify(notificationsResult));
|
|
161
|
+
}
|
|
162
|
+
// * if there are no automation notifications, the API returns a single object with the programId
|
|
148
163
|
}
|
|
149
164
|
} catch (ex) {
|
|
150
165
|
Util.logger.debug(
|
|
151
|
-
` ☇
|
|
166
|
+
` ☇ issue retrieving Notifications for automation ${automationLegacy.key}: ${ex.message} ${ex.code}`
|
|
152
167
|
);
|
|
153
168
|
skipped++;
|
|
154
169
|
}
|
|
@@ -242,6 +257,7 @@ class Automation extends MetadataType {
|
|
|
242
257
|
key: m.CustomerKey,
|
|
243
258
|
name: m.Name,
|
|
244
259
|
programId: automationsLegacy.metadata[m.CustomerKey]?.id,
|
|
260
|
+
status: automationsLegacy.metadata[m.CustomerKey]?.status,
|
|
245
261
|
};
|
|
246
262
|
}
|
|
247
263
|
}
|
|
@@ -318,6 +334,29 @@ class Automation extends MetadataType {
|
|
|
318
334
|
throw new Error(JSON.stringify(results));
|
|
319
335
|
}
|
|
320
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* helper for {@link Automation.postRetrieveTasks} and {@link Automation.execute}
|
|
339
|
+
*
|
|
340
|
+
* @param {TYPE.AutomationItem} metadata a single automation
|
|
341
|
+
* @returns {boolean} true if the automation schedule is valid
|
|
342
|
+
*/
|
|
343
|
+
static #isValidSchedule(metadata) {
|
|
344
|
+
if (metadata.type === 'scheduled' && metadata.schedule?.startDate) {
|
|
345
|
+
try {
|
|
346
|
+
if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) {
|
|
347
|
+
// if we found the id in our list, remove the redundant data
|
|
348
|
+
delete metadata.schedule.timezoneId;
|
|
349
|
+
}
|
|
350
|
+
} catch {
|
|
351
|
+
Util.logger.debug(
|
|
352
|
+
`- Schedule name '${metadata.schedule.timezoneName}' not found in definition.timeZoneMapping`
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
return true;
|
|
356
|
+
} else {
|
|
357
|
+
return false;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
321
360
|
/**
|
|
322
361
|
* manages post retrieve steps
|
|
323
362
|
*
|
|
@@ -332,34 +371,24 @@ class Automation extends MetadataType {
|
|
|
332
371
|
if (metadata.type === 'scheduled' && metadata.schedule?.startDate) {
|
|
333
372
|
// Starting Source == 'Schedule'
|
|
334
373
|
|
|
335
|
-
|
|
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
|
-
);
|
|
374
|
+
if (!this.#isValidSchedule(metadata)) {
|
|
354
375
|
return;
|
|
355
376
|
}
|
|
377
|
+
// type 'Running' is temporary status only, overwrite with Scheduled for storage.
|
|
378
|
+
if (metadata.type === 'scheduled' && metadata.status === 'Running') {
|
|
379
|
+
metadata.status = 'Scheduled';
|
|
380
|
+
}
|
|
356
381
|
} else if (metadata.type === 'triggered' && metadata.fileTrigger) {
|
|
357
382
|
// Starting Source == 'File Drop'
|
|
358
383
|
// Do nothing for now
|
|
359
384
|
}
|
|
360
385
|
if (metadata.steps) {
|
|
386
|
+
let i = 0;
|
|
387
|
+
|
|
361
388
|
for (const step of metadata.steps) {
|
|
362
|
-
|
|
389
|
+
i++;
|
|
390
|
+
|
|
391
|
+
const stepNumber = step.stepNumber || step.step || i;
|
|
363
392
|
delete step.stepNumber;
|
|
364
393
|
delete step.step;
|
|
365
394
|
|
|
@@ -387,14 +416,22 @@ class Automation extends MetadataType {
|
|
|
387
416
|
activity.activityObjectId == null
|
|
388
417
|
) {
|
|
389
418
|
Util.logger.debug(
|
|
390
|
-
` -
|
|
391
|
-
|
|
392
|
-
|
|
419
|
+
` - skipping ${
|
|
420
|
+
metadata[this.definition.keyField]
|
|
421
|
+
} activity ${stepNumber}.${
|
|
422
|
+
activity.displayOrder
|
|
423
|
+
} due to missing activityObjectId: ${JSON.stringify(activity)}`
|
|
393
424
|
);
|
|
394
425
|
// empty if block
|
|
395
426
|
} else if (!this.definition.dependencies.includes(activity.r__type)) {
|
|
396
427
|
Util.logger.debug(
|
|
397
|
-
` -
|
|
428
|
+
` - skipping ${
|
|
429
|
+
metadata[this.definition.keyField]
|
|
430
|
+
} activity ${stepNumber}.${
|
|
431
|
+
activity.displayOrder
|
|
432
|
+
} because the type ${
|
|
433
|
+
activity.r__type
|
|
434
|
+
} is not set up as a dependency for ${this.definition.type}`
|
|
398
435
|
);
|
|
399
436
|
}
|
|
400
437
|
// / if managed by cache we can update references to support deployment
|
|
@@ -445,6 +482,197 @@ class Automation extends MetadataType {
|
|
|
445
482
|
return null;
|
|
446
483
|
}
|
|
447
484
|
}
|
|
485
|
+
/**
|
|
486
|
+
* a function to start query execution via API
|
|
487
|
+
*
|
|
488
|
+
* @param {string[]} keyArr customerkey of the metadata
|
|
489
|
+
* @returns {Promise.<string[]>} Returns list of keys that were executed
|
|
490
|
+
*/
|
|
491
|
+
static async execute(keyArr) {
|
|
492
|
+
const metadataMap = {};
|
|
493
|
+
for (const key of keyArr) {
|
|
494
|
+
if (Util.OPTIONS.schedule) {
|
|
495
|
+
// schedule
|
|
496
|
+
const results = await this.retrieve(undefined, undefined, undefined, key);
|
|
497
|
+
if (Object.keys(results.metadata).length) {
|
|
498
|
+
for (const resultKey of Object.keys(results.metadata)) {
|
|
499
|
+
if (this.#isValidSchedule(results.metadata[resultKey])) {
|
|
500
|
+
metadataMap[resultKey] = results.metadata[resultKey];
|
|
501
|
+
} else {
|
|
502
|
+
Util.logger.error(
|
|
503
|
+
` - skipping ${this.definition.type} ${results.metadata[resultKey].name}: no valid schedule settings found.`
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
// runOnce
|
|
510
|
+
const objectId = await this.#getObjectIdForSingleRetrieve(key);
|
|
511
|
+
metadataMap[key] = {};
|
|
512
|
+
metadataMap[key][this.definition.idField] = objectId;
|
|
513
|
+
metadataMap[key][this.definition.keyField] = key;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (!Object.keys(metadataMap).length) {
|
|
517
|
+
Util.logger.error(`No ${this.definition.type} to execute`);
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
Util.logger.info(
|
|
521
|
+
`Starting automations ${
|
|
522
|
+
Util.OPTIONS.schedule
|
|
523
|
+
? 'according to schedule'
|
|
524
|
+
: 'to run once (use --schedule or --execute=schedule to schedule instead)'
|
|
525
|
+
}: ${Object.keys(metadataMap).length}`
|
|
526
|
+
);
|
|
527
|
+
const promiseResults = [];
|
|
528
|
+
for (const key of Object.keys(metadataMap)) {
|
|
529
|
+
if (Util.OPTIONS.schedule && metadataMap[key].status === 'Scheduled') {
|
|
530
|
+
// schedule
|
|
531
|
+
Util.logger.info(
|
|
532
|
+
` - skipping ${this.definition.type} ${metadataMap[key].name}: already scheduled.`
|
|
533
|
+
);
|
|
534
|
+
} else {
|
|
535
|
+
// schedule + runOnce
|
|
536
|
+
promiseResults.push(this.#executeItem(metadataMap, key));
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
const results = await Promise.all(promiseResults);
|
|
540
|
+
const executedKeyArr = results
|
|
541
|
+
.filter(Boolean)
|
|
542
|
+
.filter((r) => r.response.OverallStatus === 'OK')
|
|
543
|
+
.map((r) => r.key);
|
|
544
|
+
Util.logger.info(`Executed ${executedKeyArr.length} of ${keyArr.length} items`);
|
|
545
|
+
return executedKeyArr;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* helper for {@link Automation.execute}
|
|
549
|
+
*
|
|
550
|
+
* @param {TYPE.AutomationMap} metadataMap map of metadata
|
|
551
|
+
* @param {string} key key of the metadata
|
|
552
|
+
* @returns {Promise.<{key:string, response:object}>} metadata key and API response
|
|
553
|
+
*/
|
|
554
|
+
static async #executeItem(metadataMap, key) {
|
|
555
|
+
if (Util.OPTIONS.schedule) {
|
|
556
|
+
this.#preDeploySchedule(metadataMap[key]);
|
|
557
|
+
metadataMap[key].status = 'Scheduled';
|
|
558
|
+
return this.#scheduleAutomation(metadataMap, metadataMap, key);
|
|
559
|
+
} else {
|
|
560
|
+
return this.#runOnce(metadataMap[key]);
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* helper for {@link Automation.execute}
|
|
566
|
+
*
|
|
567
|
+
* @param {TYPE.AutomationItem} metadataEntry metadata object
|
|
568
|
+
* @returns {Promise.<{key:string, response:object}>} metadata key and API response
|
|
569
|
+
*/
|
|
570
|
+
static async #runOnce(metadataEntry) {
|
|
571
|
+
return super.executeSOAP(metadataEntry);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Standardizes a check for multiple messages but adds query specific filters to error texts
|
|
576
|
+
*
|
|
577
|
+
* @param {object} ex response payload from REST API
|
|
578
|
+
* @returns {string[] | void} formatted Error Message
|
|
579
|
+
*/
|
|
580
|
+
static getErrorsREST(ex) {
|
|
581
|
+
const errors = super.getErrorsREST(ex);
|
|
582
|
+
if (errors?.length > 0) {
|
|
583
|
+
return errors.map((msg) =>
|
|
584
|
+
msg
|
|
585
|
+
.split('403 Forbidden')
|
|
586
|
+
.join('403 Forbidden: Please check if the automation is currently running.')
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
return errors;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* a function to start query execution via API
|
|
594
|
+
*
|
|
595
|
+
* @param {string[]} keyArr customerkey of the metadata
|
|
596
|
+
* @returns {Promise.<string[]>} Returns list of keys that were paused
|
|
597
|
+
*/
|
|
598
|
+
static async pause(keyArr) {
|
|
599
|
+
const metadataMap = {};
|
|
600
|
+
for (const key of keyArr) {
|
|
601
|
+
if (key) {
|
|
602
|
+
const results = await this.retrieve(undefined, undefined, undefined, key);
|
|
603
|
+
if (Object.keys(results.metadata).length) {
|
|
604
|
+
for (const key of Object.keys(results.metadata)) {
|
|
605
|
+
if (this.#isValidSchedule(results.metadata[key])) {
|
|
606
|
+
metadataMap[key] = results.metadata[key];
|
|
607
|
+
} else {
|
|
608
|
+
Util.logger.error(
|
|
609
|
+
` - skipping ${this.definition.type} ${results.metadata[key].name}: no valid schedule settings found.`
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
Util.logger.info(`Pausing automations: ${Object.keys(metadataMap).length}`);
|
|
618
|
+
const promiseResults = [];
|
|
619
|
+
for (const key of Object.keys(metadataMap)) {
|
|
620
|
+
if (metadataMap[key].status === 'Scheduled') {
|
|
621
|
+
promiseResults.push(this.#pauseItem(metadataMap[key]));
|
|
622
|
+
} else if (metadataMap[key].status === 'PausedSchedule') {
|
|
623
|
+
Util.logger.info(
|
|
624
|
+
` - skipping ${this.definition.type} ${metadataMap[key].name}: already paused.`
|
|
625
|
+
);
|
|
626
|
+
} else {
|
|
627
|
+
Util.logger.error(
|
|
628
|
+
` - skipping ${this.definition.type} ${
|
|
629
|
+
metadataMap[key].name
|
|
630
|
+
}: currently ${metadataMap[
|
|
631
|
+
key
|
|
632
|
+
].status.toLowerCase()}. Please try again in a few minutes.`
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
const pausedKeyArr = (await Promise.all(promiseResults))
|
|
637
|
+
.filter(Boolean)
|
|
638
|
+
.filter((r) => r.response.OverallStatus === 'OK')
|
|
639
|
+
.map((r) => r.key);
|
|
640
|
+
|
|
641
|
+
Util.logger.info(`Paused ${pausedKeyArr.length} of ${keyArr.length} items`);
|
|
642
|
+
return pausedKeyArr;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* helper for {@link Automation.pause}
|
|
647
|
+
*
|
|
648
|
+
* @param {TYPE.AutomationItem} metadata automation metadata
|
|
649
|
+
* @returns {Promise.<{key:string, response:object}>} metadata key and API response
|
|
650
|
+
*/
|
|
651
|
+
static async #pauseItem(metadata) {
|
|
652
|
+
const schedule = {};
|
|
653
|
+
try {
|
|
654
|
+
const response = await this.client.soap.schedule(
|
|
655
|
+
'Automation',
|
|
656
|
+
schedule,
|
|
657
|
+
{
|
|
658
|
+
Interaction: {
|
|
659
|
+
ObjectID: metadata[this.definition.idField],
|
|
660
|
+
},
|
|
661
|
+
},
|
|
662
|
+
'pause',
|
|
663
|
+
{}
|
|
664
|
+
);
|
|
665
|
+
Util.logger.info(
|
|
666
|
+
` - paused ${this.definition.type}: ${metadata[this.definition.keyField]} / ${
|
|
667
|
+
metadata[this.definition.nameField]
|
|
668
|
+
}`
|
|
669
|
+
);
|
|
670
|
+
return { key: metadata[this.definition.keyField], response };
|
|
671
|
+
} catch (ex) {
|
|
672
|
+
this._handleSOAPErrors(ex, 'pausing', metadata, false);
|
|
673
|
+
return null;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
448
676
|
|
|
449
677
|
/**
|
|
450
678
|
* Deploys automation - the saved file is the original one due to large differences required for deployment
|
|
@@ -452,11 +680,10 @@ class Automation extends MetadataType {
|
|
|
452
680
|
* @param {TYPE.AutomationMap} metadata metadata mapped by their keyField
|
|
453
681
|
* @param {string} targetBU name/shorthand of target businessUnit for mapping
|
|
454
682
|
* @param {string} retrieveDir directory where metadata after deploy should be saved
|
|
455
|
-
* @param {boolean} [isRefresh] optional flag - so far not used by automation
|
|
456
683
|
* @returns {Promise.<TYPE.AutomationMap>} Promise
|
|
457
684
|
*/
|
|
458
|
-
static async deploy(metadata, targetBU, retrieveDir
|
|
459
|
-
const upsertResults = await this.upsert(metadata, targetBU
|
|
685
|
+
static async deploy(metadata, targetBU, retrieveDir) {
|
|
686
|
+
const upsertResults = await this.upsert(metadata, targetBU);
|
|
460
687
|
const savedMetadata = await this.saveResults(upsertResults, retrieveDir, null);
|
|
461
688
|
if (
|
|
462
689
|
this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type) &&
|
|
@@ -488,11 +715,47 @@ class Automation extends MetadataType {
|
|
|
488
715
|
* @returns {Promise} Promise
|
|
489
716
|
*/
|
|
490
717
|
static update(metadata, metadataBefore) {
|
|
491
|
-
|
|
718
|
+
if (metadataBefore.status === 'Running') {
|
|
719
|
+
Util.logger.error(
|
|
720
|
+
` ☇ error updating ${this.definition.type} ${
|
|
721
|
+
metadata[this.definition.keyField] || metadata[this.definition.nameField]
|
|
722
|
+
} / ${
|
|
723
|
+
metadata[this.definition.nameField]
|
|
724
|
+
}: You cannot update an automation that's currently running. Please wait a bit and retry.`
|
|
725
|
+
);
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
492
728
|
const uri = '/automation/v1/automations/' + metadata.id;
|
|
493
729
|
return super.updateREST(metadata, uri);
|
|
494
730
|
}
|
|
495
731
|
|
|
732
|
+
/**
|
|
733
|
+
* helper for {@link Automation.preDeployTasks} and {@link Automation.execute}
|
|
734
|
+
*
|
|
735
|
+
* @param {TYPE.AutomationItem} metadata metadata mapped by their keyField
|
|
736
|
+
*/
|
|
737
|
+
static #preDeploySchedule(metadata) {
|
|
738
|
+
delete metadata.schedule.rangeTypeId;
|
|
739
|
+
delete metadata.schedule.pattern;
|
|
740
|
+
delete metadata.schedule.scheduledTime;
|
|
741
|
+
delete metadata.schedule.scheduledStatus;
|
|
742
|
+
if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) {
|
|
743
|
+
metadata.schedule.timezoneId =
|
|
744
|
+
this.definition.timeZoneMapping[metadata.schedule.timezoneName];
|
|
745
|
+
} else {
|
|
746
|
+
Util.logger.error(
|
|
747
|
+
`Could not find timezone ${metadata.schedule.timezoneName} in definition.timeZoneMapping`
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// the upsert API needs this to be named scheduleTypeId; the retrieve API returns it as typeId
|
|
752
|
+
metadata.schedule.scheduleTypeId = metadata.schedule.typeId;
|
|
753
|
+
delete metadata.schedule.typeId;
|
|
754
|
+
|
|
755
|
+
// prep startSource
|
|
756
|
+
metadata.startSource = { schedule: metadata.schedule, typeId: 1 };
|
|
757
|
+
}
|
|
758
|
+
|
|
496
759
|
/**
|
|
497
760
|
* Gets executed before deploying metadata
|
|
498
761
|
*
|
|
@@ -516,25 +779,12 @@ class Automation extends MetadataType {
|
|
|
516
779
|
if (metadata.type === 'scheduled' && metadata?.schedule?.startDate) {
|
|
517
780
|
// Starting Source == 'Schedule'
|
|
518
781
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
delete metadata.schedule.scheduledStatus;
|
|
523
|
-
if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) {
|
|
524
|
-
metadata.schedule.timezoneId =
|
|
525
|
-
this.definition.timeZoneMapping[metadata.schedule.timezoneName];
|
|
526
|
-
} else {
|
|
527
|
-
Util.logger.error(
|
|
528
|
-
`Could not find timezone ${metadata.schedule.timezoneName} in definition.timeZoneMapping`
|
|
529
|
-
);
|
|
530
|
-
}
|
|
531
|
-
delete metadata.schedule.timezoneName;
|
|
532
|
-
// the upsert API needs this to be named scheduleTypeId; the retrieve API returns it as typeId
|
|
533
|
-
metadata.schedule.scheduleTypeId = metadata.schedule.typeId;
|
|
534
|
-
delete metadata.schedule.typeId;
|
|
782
|
+
this.#preDeploySchedule(metadata);
|
|
783
|
+
// * run _buildSchedule here but only to check if things look ok - do not use the returned schedule object for deploy
|
|
784
|
+
this._buildSchedule(metadata.schedule);
|
|
535
785
|
|
|
536
|
-
|
|
537
|
-
metadata.startSource
|
|
786
|
+
delete metadata.schedule.timezoneName;
|
|
787
|
+
delete metadata.startSource.schedule.timezoneName;
|
|
538
788
|
} else if (metadata.type === 'triggered' && metadata.fileTrigger) {
|
|
539
789
|
// Starting Source == 'File Drop'
|
|
540
790
|
|
|
@@ -632,6 +882,16 @@ class Automation extends MetadataType {
|
|
|
632
882
|
}
|
|
633
883
|
return deployable;
|
|
634
884
|
}
|
|
885
|
+
/**
|
|
886
|
+
* helper for {@link MetadataType.updateREST} and {@link MetadataType.updateSOAP} that removes old files after the key was changed
|
|
887
|
+
*
|
|
888
|
+
* @private
|
|
889
|
+
* @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry
|
|
890
|
+
* @returns {void}
|
|
891
|
+
*/
|
|
892
|
+
static async _postChangeKeyTasks(metadataEntry) {
|
|
893
|
+
super._postChangeKeyTasks(metadataEntry, true);
|
|
894
|
+
}
|
|
635
895
|
|
|
636
896
|
/**
|
|
637
897
|
* Gets executed after deployment of metadata type
|
|
@@ -642,21 +902,53 @@ class Automation extends MetadataType {
|
|
|
642
902
|
*/
|
|
643
903
|
static async postDeployTasks(metadataMap, originalMetadataMap) {
|
|
644
904
|
for (const key in metadataMap) {
|
|
905
|
+
const item = metadataMap[key];
|
|
906
|
+
|
|
907
|
+
const oldKey = Util.changedKeysMap?.[this.definition.type]?.[key] || key;
|
|
908
|
+
delete Util.changedKeysMap?.[this.definition.type]?.[key];
|
|
909
|
+
|
|
910
|
+
if (!item.type) {
|
|
911
|
+
// create response does not return the type attribute
|
|
912
|
+
|
|
913
|
+
const scheduleHelper = item.schedule || item.startSource.schedule;
|
|
914
|
+
|
|
915
|
+
// el.type
|
|
916
|
+
item.type = scheduleHelper
|
|
917
|
+
? 'scheduled'
|
|
918
|
+
: item.fileTrigger
|
|
919
|
+
? 'triggered'
|
|
920
|
+
: undefined;
|
|
921
|
+
|
|
922
|
+
// el.schedule.timezoneName
|
|
923
|
+
if (item.type === 'scheduled') {
|
|
924
|
+
// not existing for triggered automations
|
|
925
|
+
scheduleHelper.timezoneName ||= Util.inverseGet(
|
|
926
|
+
this.definition.timeZoneMapping,
|
|
927
|
+
scheduleHelper.timezoneId
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// el.status
|
|
932
|
+
item.status ||= Util.inverseGet(this.definition.statusMapping, item.statusId);
|
|
933
|
+
}
|
|
645
934
|
// need to put schedule on here if status is scheduled
|
|
646
|
-
await Automation.#scheduleAutomation(metadataMap, originalMetadataMap, key);
|
|
935
|
+
await Automation.#scheduleAutomation(metadataMap, originalMetadataMap, key, oldKey);
|
|
647
936
|
|
|
648
937
|
// need to update notifications separately if there are any
|
|
649
938
|
await Automation.#updateNotificationInfoREST(metadataMap, key);
|
|
650
939
|
|
|
651
940
|
// rewrite upsert to retrieve fields
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
for (const step of metadata.steps) {
|
|
941
|
+
if (item.steps) {
|
|
942
|
+
for (const step of item.steps) {
|
|
655
943
|
step.name = step.annotation;
|
|
656
944
|
delete step.annotation;
|
|
657
945
|
}
|
|
658
946
|
}
|
|
659
947
|
}
|
|
948
|
+
if (Util.OPTIONS.execute || Util.OPTIONS.schedule) {
|
|
949
|
+
Util.logger.info(`Executing: ${this.definition.type}`);
|
|
950
|
+
await this.execute(Object.keys(metadataMap));
|
|
951
|
+
}
|
|
660
952
|
}
|
|
661
953
|
/**
|
|
662
954
|
* helper for {@link Automation.postDeployTasks}
|
|
@@ -706,22 +998,26 @@ class Automation extends MetadataType {
|
|
|
706
998
|
}
|
|
707
999
|
|
|
708
1000
|
/**
|
|
709
|
-
* helper for {@link postDeployTasks}
|
|
1001
|
+
* helper for {@link Automation.postDeployTasks}
|
|
710
1002
|
*
|
|
711
1003
|
* @param {TYPE.AutomationMap} metadataMap metadata mapped by their keyField
|
|
712
1004
|
* @param {TYPE.AutomationMap} originalMetadataMap metadata to be updated (contains additioanl fields)
|
|
713
1005
|
* @param {string} key current customer key
|
|
1006
|
+
* @param {string} [oldKey] old customer key before fixKey / changeKeyValue / changeKeyField
|
|
1007
|
+
* @returns {Promise.<{key:string, response:object}>} metadata key and API response
|
|
714
1008
|
*/
|
|
715
|
-
static async #scheduleAutomation(metadataMap, originalMetadataMap, key) {
|
|
716
|
-
|
|
1009
|
+
static async #scheduleAutomation(metadataMap, originalMetadataMap, key, oldKey) {
|
|
1010
|
+
let response = null;
|
|
1011
|
+
oldKey ||= key;
|
|
1012
|
+
if (originalMetadataMap[oldKey]?.type === 'scheduled') {
|
|
717
1013
|
// Starting Source == 'Schedule': Try starting the automation
|
|
718
|
-
if (originalMetadataMap[
|
|
1014
|
+
if (originalMetadataMap[oldKey].status === 'Scheduled') {
|
|
719
1015
|
let schedule = null;
|
|
720
1016
|
try {
|
|
721
|
-
schedule = this._buildSchedule(originalMetadataMap[
|
|
1017
|
+
schedule = this._buildSchedule(originalMetadataMap[oldKey].schedule);
|
|
722
1018
|
} catch (ex) {
|
|
723
1019
|
Util.logger.error(
|
|
724
|
-
`- Could not create schedule for automation '${originalMetadataMap[
|
|
1020
|
+
`- Could not create schedule for automation '${originalMetadataMap[oldKey].name}' to start it: ${ex.message}`
|
|
725
1021
|
);
|
|
726
1022
|
}
|
|
727
1023
|
if (schedule !== null) {
|
|
@@ -734,12 +1030,12 @@ class Automation extends MetadataType {
|
|
|
734
1030
|
const schedule_timezoneString = schedule._timezoneString;
|
|
735
1031
|
delete schedule._timezoneString;
|
|
736
1032
|
// start the automation
|
|
737
|
-
await this.client.soap.schedule(
|
|
1033
|
+
response = await this.client.soap.schedule(
|
|
738
1034
|
'Automation',
|
|
739
1035
|
schedule,
|
|
740
1036
|
{
|
|
741
1037
|
Interaction: {
|
|
742
|
-
ObjectID: metadataMap[key].
|
|
1038
|
+
ObjectID: metadataMap[key][this.definition.idField],
|
|
743
1039
|
},
|
|
744
1040
|
},
|
|
745
1041
|
'start',
|
|
@@ -753,21 +1049,22 @@ class Automation extends MetadataType {
|
|
|
753
1049
|
(schedule_interval > 1 ? 's' : ''));
|
|
754
1050
|
Util.logger.warn(
|
|
755
1051
|
` - scheduled automation '${
|
|
756
|
-
originalMetadataMap[
|
|
1052
|
+
originalMetadataMap[oldKey].name
|
|
757
1053
|
}' deployed as Active: runs every ${intervalString} starting ${
|
|
758
1054
|
schedule_StartDateTime.split('T').join(' ').split('.')[0]
|
|
759
1055
|
} ${schedule_timezoneString}`
|
|
760
1056
|
);
|
|
761
|
-
} catch
|
|
1057
|
+
} catch {
|
|
1058
|
+
// API does not return anything usefull here. We have to know the rules instead
|
|
762
1059
|
Util.logger.error(
|
|
763
|
-
|
|
1060
|
+
` ☇ error starting scheduled ${this.definition.type}${key}: Please check schedule settings`
|
|
764
1061
|
);
|
|
765
1062
|
}
|
|
766
1063
|
}
|
|
767
1064
|
} else {
|
|
768
1065
|
Util.logger.info(
|
|
769
1066
|
Util.getGrayMsg(
|
|
770
|
-
` - scheduled automation '${originalMetadataMap[
|
|
1067
|
+
` - scheduled automation '${originalMetadataMap[oldKey].name}' deployed as Paused`
|
|
771
1068
|
)
|
|
772
1069
|
);
|
|
773
1070
|
}
|
|
@@ -781,6 +1078,7 @@ class Automation extends MetadataType {
|
|
|
781
1078
|
metadataMap[key].schedule.typeId = metadataMap[key].schedule.scheduleTypeId;
|
|
782
1079
|
delete metadataMap[key].schedule.scheduleTypeId;
|
|
783
1080
|
}
|
|
1081
|
+
return { key, response };
|
|
784
1082
|
}
|
|
785
1083
|
|
|
786
1084
|
/**
|
|
@@ -867,6 +1165,9 @@ class Automation extends MetadataType {
|
|
|
867
1165
|
const a = obj.split('=');
|
|
868
1166
|
recurHelper[a[0]] = a[1];
|
|
869
1167
|
}
|
|
1168
|
+
if (recurHelper.INTERVAL) {
|
|
1169
|
+
recurHelper.INTERVAL = Number.parseInt(recurHelper.INTERVAL);
|
|
1170
|
+
}
|
|
870
1171
|
// the ical schedule is all in caps but soap objects require Title Case.
|
|
871
1172
|
const keyStem = recurHelper.FREQ.charAt(0) + recurHelper.FREQ.slice(1, -2).toLowerCase();
|
|
872
1173
|
|
|
@@ -895,13 +1196,18 @@ class Automation extends MetadataType {
|
|
|
895
1196
|
'Scheduling automatically not supported for Weekly, Monthly and Yearly, please configure manually.'
|
|
896
1197
|
);
|
|
897
1198
|
}
|
|
1199
|
+
if (recurHelper.FREQ === 'MINUTELY' && recurHelper.INTERVAL && recurHelper.INTERVAL < 5) {
|
|
1200
|
+
throw new Error(
|
|
1201
|
+
'The smallest interval you can configure is 5 minutes. Please adjust your schedule.'
|
|
1202
|
+
);
|
|
1203
|
+
}
|
|
898
1204
|
|
|
899
1205
|
if (this.definition.timeZoneMapping[scheduleObject.timezoneName]) {
|
|
900
1206
|
scheduleObject.timezoneId =
|
|
901
1207
|
this.definition.timeZoneMapping[scheduleObject.timezoneName];
|
|
902
1208
|
} else {
|
|
903
|
-
|
|
904
|
-
|
|
1209
|
+
throw new Error(
|
|
1210
|
+
`Could not find timezone ${scheduleObject.timezoneName} in definition.timeZoneMapping`
|
|
905
1211
|
);
|
|
906
1212
|
}
|
|
907
1213
|
schedule.TimeZone.ID = scheduleObject.timezoneId;
|
|
@@ -995,8 +1301,8 @@ class Automation extends MetadataType {
|
|
|
995
1301
|
// create new Date object reflecting SFMC's servertime
|
|
996
1302
|
const dateServer = new Date(utc + 3600000 * offsetServer);
|
|
997
1303
|
|
|
998
|
-
// return time as a string without trailing "Z"
|
|
999
|
-
return dateServer.toISOString().slice(0, -1);
|
|
1304
|
+
// return time as a string without trailing "Z" and without miliseconds (separated by .)
|
|
1305
|
+
return dateServer.toISOString().slice(0, -1).split('.')[0];
|
|
1000
1306
|
}
|
|
1001
1307
|
/**
|
|
1002
1308
|
* Experimental: Only working for DataExtensions:
|
|
@@ -1023,7 +1329,7 @@ class Automation extends MetadataType {
|
|
|
1023
1329
|
const automationType = { scheduled: 'Schedule', triggered: 'File Drop' };
|
|
1024
1330
|
output += `**Started by:** ${automationType[json.type] || 'Not defined'}\n\n`;
|
|
1025
1331
|
output += `**Status:** ${json.status}\n\n`;
|
|
1026
|
-
if (json.type === 'scheduled') {
|
|
1332
|
+
if (json.type === 'scheduled' || json.schedule) {
|
|
1027
1333
|
const tz =
|
|
1028
1334
|
this.definition.timeZoneDifference[
|
|
1029
1335
|
this.definition.timeZoneMapping[json?.schedule?.timezoneName]
|
|
@@ -1033,7 +1339,7 @@ class Automation extends MetadataType {
|
|
|
1033
1339
|
output += `**Schedule:**\n\n`;
|
|
1034
1340
|
output += `* Start: ${json.schedule.startDate.split('T').join(' ')} ${tz}\n`;
|
|
1035
1341
|
output += `* End: ${json.schedule.endDate.split('T').join(' ')} ${tz}\n`;
|
|
1036
|
-
output += `* Timezone:
|
|
1342
|
+
output += `* Timezone: ${json.schedule.timezoneName}\n`;
|
|
1037
1343
|
|
|
1038
1344
|
const ical = {};
|
|
1039
1345
|
for (const item of json.schedule.icalRecur.split(';')) {
|
|
@@ -1042,9 +1348,20 @@ class Automation extends MetadataType {
|
|
|
1042
1348
|
}
|
|
1043
1349
|
const frequency = ical.FREQ.slice(0, -2).toLowerCase();
|
|
1044
1350
|
|
|
1045
|
-
output += `* Recurrance:
|
|
1046
|
-
|
|
1047
|
-
|
|
1351
|
+
output += `* Recurrance: `;
|
|
1352
|
+
output +=
|
|
1353
|
+
ical.COUNT == 1
|
|
1354
|
+
? 'run only once'
|
|
1355
|
+
: `every${ical.INTERVAL > 1 ? ' ' + ical.INTERVAL : ''} ${
|
|
1356
|
+
frequency === 'dai' ? 'day' : frequency
|
|
1357
|
+
}${ical.INTERVAL > 1 ? 's' : ''}${
|
|
1358
|
+
ical.COUNT
|
|
1359
|
+
? ` for ${ical.COUNT} times`
|
|
1360
|
+
: ical.UNTIL
|
|
1361
|
+
? ' until end date'
|
|
1362
|
+
: ''
|
|
1363
|
+
}`;
|
|
1364
|
+
output += '\n';
|
|
1048
1365
|
} else if (json.schedule) {
|
|
1049
1366
|
output += `**Schedule:** Not defined\n`;
|
|
1050
1367
|
}
|
|
@@ -1224,7 +1541,7 @@ class Automation extends MetadataType {
|
|
|
1224
1541
|
* @param {string} key customer key
|
|
1225
1542
|
* @returns {Promise.<string>} objectId or enpty string
|
|
1226
1543
|
*/
|
|
1227
|
-
static async
|
|
1544
|
+
static async #getObjectIdForSingleRetrieve(key) {
|
|
1228
1545
|
const response = await this.client.soap.retrieve('Program', ['ObjectID'], {
|
|
1229
1546
|
filter: {
|
|
1230
1547
|
leftOperand: 'CustomerKey',
|
|
@@ -1243,7 +1560,7 @@ class Automation extends MetadataType {
|
|
|
1243
1560
|
*/
|
|
1244
1561
|
static async deleteByKey(customerKey) {
|
|
1245
1562
|
// the delete endpoint returns a general exception if the automation does not exist; handle it gracefully instead by adding a retrieve first
|
|
1246
|
-
const objectId = customerKey ? await this
|
|
1563
|
+
const objectId = customerKey ? await this.#getObjectIdForSingleRetrieve(customerKey) : null;
|
|
1247
1564
|
if (!objectId) {
|
|
1248
1565
|
Util.logger.error(` - automation not found`);
|
|
1249
1566
|
return false;
|