mcdev 5.0.2 → 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/.coverage-comment-template.svelte +177 -161
- package/.eslintrc.json +4 -4
- package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +2 -2
- package/.github/dependabot.yml +8 -0
- package/.github/workflows/coverage-base-update.yml +6 -2
- package/.github/workflows/coverage-develop-branch.yml +7 -8
- package/.github/workflows/coverage-main-branch.yml +7 -8
- package/.github/workflows/coverage.yml +7 -4
- package/.husky/post-checkout +4 -2
- package/.husky/post-merge +1 -0
- package/.vscode/extensions.json +4 -0
- package/docs/dist/documentation.md +756 -294
- package/lib/Deployer.js +28 -28
- package/lib/MetadataTypeDefinitions.js +1 -1
- package/lib/MetadataTypeInfo.js +1 -1
- package/lib/Retriever.js +1 -1
- package/lib/cli.js +184 -6
- package/lib/index.js +493 -22
- 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 +771 -247
- package/lib/metadataTypes/DataExtension.js +7 -7
- 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 +187 -60
- package/lib/metadataTypes/MobileKeyword.js +8 -8
- package/lib/metadataTypes/MobileMessage.js +5 -5
- package/lib/metadataTypes/Query.js +47 -5
- 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/Asset.definition.js +1 -0
- 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 +74 -21
- 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 +37 -6
- package/lib/metadataTypes/definitions/MobileKeyword.definition.js +1 -0
- 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 +2 -0
- package/lib/metadataTypes/definitions/User.definition.js +1 -0
- package/lib/util/cache.js +9 -4
- package/lib/util/cli.js +40 -0
- package/lib/util/devops.js +13 -11
- package/lib/util/file.js +2 -2
- package/lib/util/init.js +84 -0
- package/lib/util/util.js +268 -137
- package/package.json +11 -11
- package/test/general.test.js +26 -0
- package/test/mockRoot/.mcdevrc.json +1 -1
- package/test/mockRoot/deploy/testInstance/testBU/automation/testExisting_automation.automation-meta.json +52 -0
- package/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json +45 -0
- 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/{testExistingQuery.query-meta.json → testExisting_query.query-meta.json} +2 -2
- 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/query/{testNewQuery.query-meta.json → testNew_query.query-meta.json} +2 -2
- 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 +132 -24
- 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 +57 -0
- package/test/resources/9999999/automation/create-expected.json +45 -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/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 +57 -0
- package/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md +30 -0
- 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 +57 -0
- package/test/resources/9999999/automation/update-expected.json +45 -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/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 +85 -0
- package/test/resources/9999999/automation/v1/automations/post-response.json +86 -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/56c5370a-f988-4f36-b0ee-0f876573f6d7/patch-response.json +38 -0
- package/test/resources/9999999/automation/v1/dataextracts/get-response.json +20 -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/get-response.json +18 -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/get-response.json +15 -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 +38 -0
- 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/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/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 +22 -5
- package/test/resources/9999999/automation/v1/queries/post-response.json +2 -2
- 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 +27 -0
- 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=automations-response.xml +48 -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=queryactivity-response.xml +48 -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 +45 -0
- package/test/resources/9999999/email/retrieve-response.xml +203 -0
- package/test/resources/9999999/emailSendDefinition/retrieve-IsPlatformObject=falseANDDescriptionnotEqualsSFSendDefinition-response.xml +85 -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/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/program/retrieve-CustomerKey=testExisting_automation-response.xml +30 -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-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 +50 -0
- package/test/resources/9999999/query/build-expected.json +2 -2
- package/test/resources/9999999/query/get-expected.json +2 -2
- 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_fixKeys-expected.json +11 -0
- package/test/resources/9999999/query/patch_fixKeys-expected.sql +6 -0
- package/test/resources/9999999/query/post-expected.json +2 -2
- package/test/resources/9999999/query/template-expected.json +2 -2
- 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 +886 -0
- package/test/type.dataExtension.test.js +3 -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 +497 -33
- package/test/type.script.test.js +367 -0
- package/test/type.triggeredSend.test.js +152 -0
- package/test/type.user.test.js +37 -11
- package/test/utils.js +10 -6
- package/.coverage-comment-template.md +0 -20
- package/lib/metadataTypes/SetDefinition.js +0 -37
- /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
- /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/queryDefinition/{retrieve-response.xml → retrieve-CustomerKey=testExisting_queryANDStatus=Active-response.xml} +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,42 +37,57 @@ 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
|
}
|
|
50
|
+
// the API seems to handle 50 concurrent requests nicely
|
|
51
|
+
const rateLimit = pLimit(50);
|
|
52
|
+
|
|
46
53
|
const details = results.Results
|
|
47
54
|
? await Promise.all(
|
|
48
|
-
results.Results.map(async (
|
|
49
|
-
|
|
50
|
-
return await this.client.rest.get(
|
|
51
|
-
'/automation/v1/automations/' + a.ObjectID
|
|
52
|
-
);
|
|
53
|
-
} catch (ex) {
|
|
55
|
+
results.Results.map(async (item) =>
|
|
56
|
+
rateLimit(async () => {
|
|
54
57
|
try {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
|
60
71
|
}
|
|
61
|
-
|
|
62
|
-
|
|
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;
|
|
63
77
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
` ☇ skipping Automation ${a.ObjectID}: ${ex.message} ${ex.code}`
|
|
67
|
-
);
|
|
68
|
-
return null;
|
|
69
|
-
}
|
|
70
|
-
})
|
|
78
|
+
})
|
|
79
|
+
)
|
|
71
80
|
)
|
|
72
81
|
: [];
|
|
73
|
-
|
|
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) });
|
|
85
|
+
|
|
86
|
+
if (Object.keys(metadataMap).length) {
|
|
87
|
+
// attach notification information to each automation that has any
|
|
88
|
+
await this.#getAutomationNotificationsREST(metadataMap);
|
|
89
|
+
}
|
|
90
|
+
|
|
74
91
|
// * retrieveDir can be empty when we use it in the context of postDeployTasks
|
|
75
92
|
if (retrieveDir) {
|
|
76
93
|
metadataMap = await this.saveResults(metadataMap, retrieveDir, null, null);
|
|
@@ -83,6 +100,85 @@ class Automation extends MetadataType {
|
|
|
83
100
|
}
|
|
84
101
|
return { metadata: metadataMap, type: this.definition.type };
|
|
85
102
|
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* helper for {@link Automation.retrieve} to get Automation Notifications
|
|
106
|
+
*
|
|
107
|
+
* @private
|
|
108
|
+
* @param {TYPE.MetadataTypeMap} metadataMap keyField => metadata map
|
|
109
|
+
* @returns {Promise.<void>} Promise of nothing
|
|
110
|
+
*/
|
|
111
|
+
static async #getAutomationNotificationsREST(metadataMap) {
|
|
112
|
+
Util.logger.info(Util.getGrayMsg(` Retrieving Automation Notification information...`));
|
|
113
|
+
|
|
114
|
+
// get list of keys that we retrieved so far
|
|
115
|
+
const foundKeys = Object.keys(metadataMap);
|
|
116
|
+
|
|
117
|
+
// get encodedAutomationID to retrieve notification information
|
|
118
|
+
const iteratorBackup = this.definition.bodyIteratorField;
|
|
119
|
+
this.definition.bodyIteratorField = 'entry';
|
|
120
|
+
const automationLegacyMapObj = await super.retrieveREST(
|
|
121
|
+
undefined,
|
|
122
|
+
`/legacy/v1/beta/bulk/automations/automation/definition/`
|
|
123
|
+
);
|
|
124
|
+
this.definition.bodyIteratorField = iteratorBackup;
|
|
125
|
+
const automationLegacyMap = Object.keys(automationLegacyMapObj.metadata)
|
|
126
|
+
.filter((key) => foundKeys.includes(key))
|
|
127
|
+
// ! using the `id` field to retrieve notifications does not work. instead one needs to use the URL in the `notifications` field
|
|
128
|
+
.map((key) => ({
|
|
129
|
+
id: automationLegacyMapObj.metadata[key].id,
|
|
130
|
+
key,
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
// get notifications for each automation
|
|
134
|
+
const rateLimit = pLimit(5);
|
|
135
|
+
let found = 0;
|
|
136
|
+
let skipped = 0;
|
|
137
|
+
const promiseMap = await Promise.all(
|
|
138
|
+
automationLegacyMap.map((automationLegacy) =>
|
|
139
|
+
rateLimit(async () => {
|
|
140
|
+
// this is a file so extended is at another endpoint
|
|
141
|
+
try {
|
|
142
|
+
const notificationsResult = await this.client.rest.get(
|
|
143
|
+
'/legacy/v1/beta/automations/notifications/' + automationLegacy.id
|
|
144
|
+
);
|
|
145
|
+
if (Array.isArray(notificationsResult?.workers)) {
|
|
146
|
+
metadataMap[automationLegacy.key].notifications =
|
|
147
|
+
notificationsResult.workers.map((n) => ({
|
|
148
|
+
email: n.definition.split(',').map((item) => item.trim()),
|
|
149
|
+
message: n.body,
|
|
150
|
+
type: n.notificationType,
|
|
151
|
+
}));
|
|
152
|
+
found++;
|
|
153
|
+
} else {
|
|
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
|
|
163
|
+
}
|
|
164
|
+
} catch (ex) {
|
|
165
|
+
Util.logger.debug(
|
|
166
|
+
` ☇ issue retrieving Notifications for automation ${automationLegacy.key}: ${ex.message} ${ex.code}`
|
|
167
|
+
);
|
|
168
|
+
skipped++;
|
|
169
|
+
}
|
|
170
|
+
})
|
|
171
|
+
)
|
|
172
|
+
);
|
|
173
|
+
Util.logger.info(
|
|
174
|
+
Util.getGrayMsg(` Notifications found for ${found} automation${found === 1 ? '' : 's'}`)
|
|
175
|
+
);
|
|
176
|
+
Util.logger.debug(
|
|
177
|
+
`Notifications not found for ${skipped} automation${skipped === 1 ? '' : 's'}`
|
|
178
|
+
);
|
|
179
|
+
return promiseMap;
|
|
180
|
+
}
|
|
181
|
+
|
|
86
182
|
/**
|
|
87
183
|
* Retrieves Metadata of Automation
|
|
88
184
|
*
|
|
@@ -134,6 +230,7 @@ class Automation extends MetadataType {
|
|
|
134
230
|
* @returns {Promise.<TYPE.AutomationMapObj>} Promise of metadata
|
|
135
231
|
*/
|
|
136
232
|
static async retrieveForCache() {
|
|
233
|
+
// get automations for cache
|
|
137
234
|
const results = await this.client.soap.retrieveBulk('Program', [
|
|
138
235
|
'ObjectID',
|
|
139
236
|
'CustomerKey',
|
|
@@ -141,14 +238,30 @@ class Automation extends MetadataType {
|
|
|
141
238
|
]);
|
|
142
239
|
const resultsConverted = {};
|
|
143
240
|
if (Array.isArray(results?.Results)) {
|
|
241
|
+
// get encodedAutomationID to retrieve notification information
|
|
242
|
+
const keyBackup = this.definition.keyField;
|
|
243
|
+
const iteratorBackup = this.definition.bodyIteratorField;
|
|
244
|
+
this.definition.keyField = 'key';
|
|
245
|
+
this.definition.bodyIteratorField = 'entry';
|
|
246
|
+
const automationsLegacy = await super.retrieveREST(
|
|
247
|
+
undefined,
|
|
248
|
+
`/legacy/v1/beta/bulk/automations/automation/definition/`
|
|
249
|
+
);
|
|
250
|
+
this.definition.keyField = keyBackup;
|
|
251
|
+
this.definition.bodyIteratorField = iteratorBackup;
|
|
252
|
+
|
|
253
|
+
// merge encodedAutomationID into results
|
|
144
254
|
for (const m of results.Results) {
|
|
145
255
|
resultsConverted[m.CustomerKey] = {
|
|
146
256
|
id: m.ObjectID,
|
|
147
257
|
key: m.CustomerKey,
|
|
148
258
|
name: m.Name,
|
|
259
|
+
programId: automationsLegacy.metadata[m.CustomerKey]?.id,
|
|
260
|
+
status: automationsLegacy.metadata[m.CustomerKey]?.status,
|
|
149
261
|
};
|
|
150
262
|
}
|
|
151
263
|
}
|
|
264
|
+
|
|
152
265
|
return { metadata: resultsConverted, type: this.definition.type };
|
|
153
266
|
}
|
|
154
267
|
|
|
@@ -175,14 +288,21 @@ class Automation extends MetadataType {
|
|
|
175
288
|
Util.logger.error(`${this.definition.type} '${name}' not found on server.`);
|
|
176
289
|
return;
|
|
177
290
|
}
|
|
178
|
-
|
|
291
|
+
let details = await this.client.rest.get(
|
|
179
292
|
'/automation/v1/automations/' + metadata.ObjectID
|
|
180
293
|
);
|
|
294
|
+
const metadataMap = this.parseResponseBody({ items: [details] });
|
|
295
|
+
if (Object.keys(metadataMap).length) {
|
|
296
|
+
// attach notification information to each automation that has any
|
|
297
|
+
await this.#getAutomationNotificationsREST(metadataMap);
|
|
298
|
+
details = Object.values(metadataMap)[0];
|
|
299
|
+
}
|
|
300
|
+
|
|
181
301
|
let val = null;
|
|
182
302
|
let originalKey;
|
|
183
303
|
// if parsing fails, we should just save what we get
|
|
184
304
|
try {
|
|
185
|
-
const parsedDetails = this.
|
|
305
|
+
const parsedDetails = this.postRetrieveTasks(details);
|
|
186
306
|
originalKey = parsedDetails[this.definition.keyField];
|
|
187
307
|
if (parsedDetails !== null) {
|
|
188
308
|
val = JSON.parse(
|
|
@@ -214,14 +334,344 @@ class Automation extends MetadataType {
|
|
|
214
334
|
throw new Error(JSON.stringify(results));
|
|
215
335
|
}
|
|
216
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
|
+
}
|
|
217
360
|
/**
|
|
218
361
|
* manages post retrieve steps
|
|
219
362
|
*
|
|
220
363
|
* @param {TYPE.AutomationItem} metadata a single automation
|
|
221
|
-
* @returns {TYPE.AutomationItem}
|
|
364
|
+
* @returns {TYPE.AutomationItem | void} parsed item
|
|
222
365
|
*/
|
|
223
366
|
static postRetrieveTasks(metadata) {
|
|
224
|
-
|
|
367
|
+
// folder
|
|
368
|
+
this.setFolderPath(metadata);
|
|
369
|
+
// automations are often skipped due to lack of support.
|
|
370
|
+
try {
|
|
371
|
+
if (metadata.type === 'scheduled' && metadata.schedule?.startDate) {
|
|
372
|
+
// Starting Source == 'Schedule'
|
|
373
|
+
|
|
374
|
+
if (!this.#isValidSchedule(metadata)) {
|
|
375
|
+
return;
|
|
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
|
+
}
|
|
381
|
+
} else if (metadata.type === 'triggered' && metadata.fileTrigger) {
|
|
382
|
+
// Starting Source == 'File Drop'
|
|
383
|
+
// Do nothing for now
|
|
384
|
+
}
|
|
385
|
+
if (metadata.steps) {
|
|
386
|
+
let i = 0;
|
|
387
|
+
|
|
388
|
+
for (const step of metadata.steps) {
|
|
389
|
+
i++;
|
|
390
|
+
|
|
391
|
+
const stepNumber = step.stepNumber || step.step || i;
|
|
392
|
+
delete step.stepNumber;
|
|
393
|
+
delete step.step;
|
|
394
|
+
|
|
395
|
+
for (const activity of step.activities) {
|
|
396
|
+
try {
|
|
397
|
+
// get metadata type of activity
|
|
398
|
+
activity.r__type = Util.inverseGet(
|
|
399
|
+
this.definition.activityTypeMapping,
|
|
400
|
+
activity.objectTypeId
|
|
401
|
+
);
|
|
402
|
+
delete activity.objectTypeId;
|
|
403
|
+
} catch {
|
|
404
|
+
Util.logger.warn(
|
|
405
|
+
` - Unknown activity type '${activity.objectTypeId}'` +
|
|
406
|
+
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
407
|
+
` of Automation '${metadata.name}'`
|
|
408
|
+
);
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// if no activityObjectId then either serialized activity
|
|
413
|
+
// (config in Automation ) or unconfigured so no further action to be taken
|
|
414
|
+
if (
|
|
415
|
+
activity.activityObjectId === '00000000-0000-0000-0000-000000000000' ||
|
|
416
|
+
activity.activityObjectId == null
|
|
417
|
+
) {
|
|
418
|
+
Util.logger.debug(
|
|
419
|
+
` - skipping ${
|
|
420
|
+
metadata[this.definition.keyField]
|
|
421
|
+
} activity ${stepNumber}.${
|
|
422
|
+
activity.displayOrder
|
|
423
|
+
} due to missing activityObjectId: ${JSON.stringify(activity)}`
|
|
424
|
+
);
|
|
425
|
+
// empty if block
|
|
426
|
+
} else if (!this.definition.dependencies.includes(activity.r__type)) {
|
|
427
|
+
Util.logger.debug(
|
|
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}`
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
// / if managed by cache we can update references to support deployment
|
|
438
|
+
else if (
|
|
439
|
+
Definitions[activity.r__type]?.['idField'] &&
|
|
440
|
+
cache.getCache(this.buObject.mid)[activity.r__type]
|
|
441
|
+
) {
|
|
442
|
+
try {
|
|
443
|
+
// 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
|
|
444
|
+
const name = cache.searchForField(
|
|
445
|
+
activity.r__type,
|
|
446
|
+
activity.activityObjectId,
|
|
447
|
+
Definitions[activity.r__type].idField,
|
|
448
|
+
Definitions[activity.r__type].nameField
|
|
449
|
+
);
|
|
450
|
+
if (name !== activity.name) {
|
|
451
|
+
Util.logger.debug(
|
|
452
|
+
` - updated name of step ${stepNumber}.${activity.displayOrder}` +
|
|
453
|
+
` in Automation '${metadata.name}' from ${activity.name} to ${name}`
|
|
454
|
+
);
|
|
455
|
+
activity.name = name;
|
|
456
|
+
}
|
|
457
|
+
} catch (ex) {
|
|
458
|
+
// getFromCache throws error where the dependent metadata is not found
|
|
459
|
+
Util.logger.warn(
|
|
460
|
+
` - Missing ${activity.r__type} activity '${activity.name}'` +
|
|
461
|
+
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
462
|
+
` of Automation '${metadata.name}' (${ex.message})`
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
} else {
|
|
466
|
+
Util.logger.warn(
|
|
467
|
+
` - Missing ${activity.r__type} activity '${activity.name}'` +
|
|
468
|
+
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
469
|
+
` of Automation '${metadata.name}' (Not Found in Cache)`
|
|
470
|
+
);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
return JSON.parse(JSON.stringify(metadata));
|
|
476
|
+
} catch (ex) {
|
|
477
|
+
Util.logger.warn(
|
|
478
|
+
` - ${this.definition.typeName} '${metadata[this.definition.nameField]}': ${
|
|
479
|
+
ex.message
|
|
480
|
+
}`
|
|
481
|
+
);
|
|
482
|
+
return null;
|
|
483
|
+
}
|
|
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
|
+
}
|
|
225
675
|
}
|
|
226
676
|
|
|
227
677
|
/**
|
|
@@ -230,12 +680,19 @@ class Automation extends MetadataType {
|
|
|
230
680
|
* @param {TYPE.AutomationMap} metadata metadata mapped by their keyField
|
|
231
681
|
* @param {string} targetBU name/shorthand of target businessUnit for mapping
|
|
232
682
|
* @param {string} retrieveDir directory where metadata after deploy should be saved
|
|
233
|
-
* @param {boolean} [isRefresh] optional flag - so far not used by automation
|
|
234
683
|
* @returns {Promise.<TYPE.AutomationMap>} Promise
|
|
235
684
|
*/
|
|
236
|
-
static async deploy(metadata, targetBU, retrieveDir
|
|
237
|
-
const upsertResults = await this.upsert(metadata, targetBU
|
|
238
|
-
await this.saveResults(upsertResults, retrieveDir, null);
|
|
685
|
+
static async deploy(metadata, targetBU, retrieveDir) {
|
|
686
|
+
const upsertResults = await this.upsert(metadata, targetBU);
|
|
687
|
+
const savedMetadata = await this.saveResults(upsertResults, retrieveDir, null);
|
|
688
|
+
if (
|
|
689
|
+
this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type) &&
|
|
690
|
+
!this.definition.documentInOneFile
|
|
691
|
+
) {
|
|
692
|
+
const count = Object.keys(savedMetadata).length;
|
|
693
|
+
Util.logger.debug(` - Running document for ${count} record${count === 1 ? '' : 's'}`);
|
|
694
|
+
await this.document(savedMetadata);
|
|
695
|
+
}
|
|
239
696
|
return upsertResults;
|
|
240
697
|
}
|
|
241
698
|
|
|
@@ -258,11 +715,47 @@ class Automation extends MetadataType {
|
|
|
258
715
|
* @returns {Promise} Promise
|
|
259
716
|
*/
|
|
260
717
|
static update(metadata, metadataBefore) {
|
|
261
|
-
|
|
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
|
+
}
|
|
262
728
|
const uri = '/automation/v1/automations/' + metadata.id;
|
|
263
729
|
return super.updateREST(metadata, uri);
|
|
264
730
|
}
|
|
265
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
|
+
|
|
266
759
|
/**
|
|
267
760
|
* Gets executed before deploying metadata
|
|
268
761
|
*
|
|
@@ -270,6 +763,15 @@ class Automation extends MetadataType {
|
|
|
270
763
|
* @returns {Promise.<TYPE.AutomationItem>} Promise
|
|
271
764
|
*/
|
|
272
765
|
static async preDeployTasks(metadata) {
|
|
766
|
+
if (metadata.notifications) {
|
|
767
|
+
this.notificationUpdates[metadata.key] = metadata.notifications;
|
|
768
|
+
} else {
|
|
769
|
+
const cached = cache.getByKey(metadata.key);
|
|
770
|
+
if (cached?.notifications) {
|
|
771
|
+
// if notifications existed but are no longer present in the deployment package, we need to run an empty update call to remove them
|
|
772
|
+
this.notificationUpdates[metadata.key] = [];
|
|
773
|
+
}
|
|
774
|
+
}
|
|
273
775
|
if (this.validateDeployMetadata(metadata)) {
|
|
274
776
|
// folder
|
|
275
777
|
this.setFolderId(metadata);
|
|
@@ -277,25 +779,12 @@ class Automation extends MetadataType {
|
|
|
277
779
|
if (metadata.type === 'scheduled' && metadata?.schedule?.startDate) {
|
|
278
780
|
// Starting Source == 'Schedule'
|
|
279
781
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
delete metadata.schedule.scheduledStatus;
|
|
284
|
-
if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) {
|
|
285
|
-
metadata.schedule.timezoneId =
|
|
286
|
-
this.definition.timeZoneMapping[metadata.schedule.timezoneName];
|
|
287
|
-
} else {
|
|
288
|
-
Util.logger.error(
|
|
289
|
-
`Could not find timezone ${metadata.schedule.timezoneName} in definition.timeZoneMapping`
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
delete metadata.schedule.timezoneName;
|
|
293
|
-
// the upsert API needs this to be named scheduleTypeId; the retrieve API returns it as typeId
|
|
294
|
-
metadata.schedule.scheduleTypeId = metadata.schedule.typeId;
|
|
295
|
-
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);
|
|
296
785
|
|
|
297
|
-
|
|
298
|
-
metadata.startSource
|
|
786
|
+
delete metadata.schedule.timezoneName;
|
|
787
|
+
delete metadata.startSource.schedule.timezoneName;
|
|
299
788
|
} else if (metadata.type === 'triggered' && metadata.fileTrigger) {
|
|
300
789
|
// Starting Source == 'File Drop'
|
|
301
790
|
|
|
@@ -393,6 +882,16 @@ class Automation extends MetadataType {
|
|
|
393
882
|
}
|
|
394
883
|
return deployable;
|
|
395
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
|
+
}
|
|
396
895
|
|
|
397
896
|
/**
|
|
398
897
|
* Gets executed after deployment of metadata type
|
|
@@ -403,87 +902,185 @@ class Automation extends MetadataType {
|
|
|
403
902
|
*/
|
|
404
903
|
static async postDeployTasks(metadataMap, originalMetadataMap) {
|
|
405
904
|
for (const key in metadataMap) {
|
|
406
|
-
|
|
905
|
+
const item = metadataMap[key];
|
|
407
906
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
await this.client.soap.schedule(
|
|
430
|
-
'Automation',
|
|
431
|
-
schedule,
|
|
432
|
-
{
|
|
433
|
-
Interaction: {
|
|
434
|
-
ObjectID: metadataMap[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
|
-
originalMetadataMap[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 '${originalMetadataMap[key].name}': ${ex.message}`
|
|
456
|
-
);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
} else {
|
|
460
|
-
Util.logger.warn(
|
|
461
|
-
` - scheduled automation '${originalMetadataMap[key].name}' deployed Paused`
|
|
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
|
|
462
928
|
);
|
|
463
929
|
}
|
|
930
|
+
|
|
931
|
+
// el.status
|
|
932
|
+
item.status ||= Util.inverseGet(this.definition.statusMapping, item.statusId);
|
|
464
933
|
}
|
|
465
|
-
if
|
|
466
|
-
|
|
934
|
+
// need to put schedule on here if status is scheduled
|
|
935
|
+
await Automation.#scheduleAutomation(metadataMap, originalMetadataMap, key, oldKey);
|
|
936
|
+
|
|
937
|
+
// need to update notifications separately if there are any
|
|
938
|
+
await Automation.#updateNotificationInfoREST(metadataMap, key);
|
|
467
939
|
|
|
468
|
-
|
|
940
|
+
// rewrite upsert to retrieve fields
|
|
941
|
+
if (item.steps) {
|
|
942
|
+
for (const step of item.steps) {
|
|
943
|
+
step.name = step.annotation;
|
|
944
|
+
delete step.annotation;
|
|
945
|
+
}
|
|
469
946
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
+
}
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* helper for {@link Automation.postDeployTasks}
|
|
955
|
+
*
|
|
956
|
+
* @param {TYPE.AutomationMap} metadataMap metadata mapped by their keyField
|
|
957
|
+
* @param {string} key current customer key
|
|
958
|
+
* @returns {Promise.<void>} -
|
|
959
|
+
*/
|
|
960
|
+
static async #updateNotificationInfoREST(metadataMap, key) {
|
|
961
|
+
if (this.notificationUpdates[key]) {
|
|
962
|
+
// create & update automation calls return programId as 'legacyId'; retrieve does not return it
|
|
963
|
+
const programId = metadataMap[key]?.legacyId;
|
|
964
|
+
if (programId) {
|
|
965
|
+
const notificationBody = {
|
|
966
|
+
programId,
|
|
967
|
+
workers: this.notificationUpdates[key].map((notification) => ({
|
|
968
|
+
programId,
|
|
969
|
+
notificationType: notification.type,
|
|
970
|
+
definition: Array.isArray(notification.email)
|
|
971
|
+
? notification.email.join(',')
|
|
972
|
+
: notification.email,
|
|
973
|
+
body: notification.message,
|
|
974
|
+
channelType: 'Account',
|
|
975
|
+
})),
|
|
976
|
+
};
|
|
977
|
+
try {
|
|
978
|
+
const result = await this.client.rest.post(
|
|
979
|
+
'/legacy/v1/beta/automations/notifications/' + programId,
|
|
980
|
+
notificationBody
|
|
981
|
+
);
|
|
982
|
+
if (result) {
|
|
983
|
+
// should be empty if all OK
|
|
984
|
+
throw new Error(result);
|
|
985
|
+
}
|
|
986
|
+
} catch (ex) {
|
|
987
|
+
Util.logger.error(
|
|
988
|
+
`Error updating notifications for automation '${metadataMap[key].name}': ${ex.message} (${ex.code}))`
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
Util.logger.info(
|
|
992
|
+
Util.getGrayMsg(
|
|
993
|
+
` - updated notifications for automation '${metadataMap[key].name}'`
|
|
994
|
+
)
|
|
995
|
+
);
|
|
473
996
|
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
474
999
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
1000
|
+
/**
|
|
1001
|
+
* helper for {@link Automation.postDeployTasks}
|
|
1002
|
+
*
|
|
1003
|
+
* @param {TYPE.AutomationMap} metadataMap metadata mapped by their keyField
|
|
1004
|
+
* @param {TYPE.AutomationMap} originalMetadataMap metadata to be updated (contains additioanl fields)
|
|
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
|
|
1008
|
+
*/
|
|
1009
|
+
static async #scheduleAutomation(metadataMap, originalMetadataMap, key, oldKey) {
|
|
1010
|
+
let response = null;
|
|
1011
|
+
oldKey ||= key;
|
|
1012
|
+
if (originalMetadataMap[oldKey]?.type === 'scheduled') {
|
|
1013
|
+
// Starting Source == 'Schedule': Try starting the automation
|
|
1014
|
+
if (originalMetadataMap[oldKey].status === 'Scheduled') {
|
|
1015
|
+
let schedule = null;
|
|
1016
|
+
try {
|
|
1017
|
+
schedule = this._buildSchedule(originalMetadataMap[oldKey].schedule);
|
|
1018
|
+
} catch (ex) {
|
|
1019
|
+
Util.logger.error(
|
|
1020
|
+
`- Could not create schedule for automation '${originalMetadataMap[oldKey].name}' to start it: ${ex.message}`
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
if (schedule !== null) {
|
|
1024
|
+
try {
|
|
1025
|
+
// remove the fields that are not needed for the schedule but only for CLI output
|
|
1026
|
+
const schedule_StartDateTime = schedule._StartDateTime;
|
|
1027
|
+
delete schedule._StartDateTime;
|
|
1028
|
+
const schedule_interval = schedule._interval;
|
|
1029
|
+
delete schedule._interval;
|
|
1030
|
+
const schedule_timezoneString = schedule._timezoneString;
|
|
1031
|
+
delete schedule._timezoneString;
|
|
1032
|
+
// start the automation
|
|
1033
|
+
response = await this.client.soap.schedule(
|
|
1034
|
+
'Automation',
|
|
1035
|
+
schedule,
|
|
1036
|
+
{
|
|
1037
|
+
Interaction: {
|
|
1038
|
+
ObjectID: metadataMap[key][this.definition.idField],
|
|
1039
|
+
},
|
|
1040
|
+
},
|
|
1041
|
+
'start',
|
|
1042
|
+
{}
|
|
1043
|
+
);
|
|
1044
|
+
const intervalString =
|
|
1045
|
+
(schedule_interval > 1 ? `${schedule_interval} ` : '') +
|
|
1046
|
+
(schedule.RecurrenceType === 'Daily'
|
|
1047
|
+
? 'Day'
|
|
1048
|
+
: schedule.RecurrenceType.slice(0, -2) +
|
|
1049
|
+
(schedule_interval > 1 ? 's' : ''));
|
|
1050
|
+
Util.logger.warn(
|
|
1051
|
+
` - scheduled automation '${
|
|
1052
|
+
originalMetadataMap[oldKey].name
|
|
1053
|
+
}' deployed as Active: runs every ${intervalString} starting ${
|
|
1054
|
+
schedule_StartDateTime.split('T').join(' ').split('.')[0]
|
|
1055
|
+
} ${schedule_timezoneString}`
|
|
1056
|
+
);
|
|
1057
|
+
} catch {
|
|
1058
|
+
// API does not return anything usefull here. We have to know the rules instead
|
|
1059
|
+
Util.logger.error(
|
|
1060
|
+
` ☇ error starting scheduled ${this.definition.type}${key}: Please check schedule settings`
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
} else {
|
|
1065
|
+
Util.logger.info(
|
|
1066
|
+
Util.getGrayMsg(
|
|
1067
|
+
` - scheduled automation '${originalMetadataMap[oldKey].name}' deployed as Paused`
|
|
1068
|
+
)
|
|
483
1069
|
);
|
|
484
1070
|
}
|
|
485
1071
|
}
|
|
1072
|
+
if (metadataMap[key].startSource) {
|
|
1073
|
+
metadataMap[key].schedule = metadataMap[key].startSource.schedule;
|
|
1074
|
+
|
|
1075
|
+
delete metadataMap[key].startSource;
|
|
1076
|
+
}
|
|
1077
|
+
if (metadataMap[key].schedule?.scheduleTypeId) {
|
|
1078
|
+
metadataMap[key].schedule.typeId = metadataMap[key].schedule.scheduleTypeId;
|
|
1079
|
+
delete metadataMap[key].schedule.scheduleTypeId;
|
|
1080
|
+
}
|
|
1081
|
+
return { key, response };
|
|
486
1082
|
}
|
|
1083
|
+
|
|
487
1084
|
/**
|
|
488
1085
|
* generic script that retrieves the folder path from cache and updates the given metadata with it after retrieve
|
|
489
1086
|
*
|
|
@@ -549,118 +1146,6 @@ class Automation extends MetadataType {
|
|
|
549
1146
|
}
|
|
550
1147
|
}
|
|
551
1148
|
|
|
552
|
-
/**
|
|
553
|
-
* parses retrieved Metadata before saving
|
|
554
|
-
*
|
|
555
|
-
* @param {TYPE.AutomationItem} metadata a single automation definition
|
|
556
|
-
* @returns {TYPE.AutomationItem | void} parsed item
|
|
557
|
-
*/
|
|
558
|
-
static parseMetadata(metadata) {
|
|
559
|
-
// folder
|
|
560
|
-
this.setFolderPath(metadata);
|
|
561
|
-
// automations are often skipped due to lack of support.
|
|
562
|
-
try {
|
|
563
|
-
if (metadata.type === 'scheduled' && metadata.schedule?.startDate) {
|
|
564
|
-
// Starting Source == 'Schedule'
|
|
565
|
-
|
|
566
|
-
try {
|
|
567
|
-
if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) {
|
|
568
|
-
// if we found the id in our list, remove the redundant data
|
|
569
|
-
delete metadata.schedule.timezoneId;
|
|
570
|
-
}
|
|
571
|
-
} catch {
|
|
572
|
-
Util.logger.debug(
|
|
573
|
-
`- Schedule name '${metadata.schedule.timezoneName}' not found in definition.timeZoneMapping`
|
|
574
|
-
);
|
|
575
|
-
}
|
|
576
|
-
try {
|
|
577
|
-
// type 'Running' is temporary status only, overwrite with Scheduled for storage.
|
|
578
|
-
if (metadata.type === 'scheduled' && metadata.status === 'Running') {
|
|
579
|
-
metadata.status = 'Scheduled';
|
|
580
|
-
}
|
|
581
|
-
} catch {
|
|
582
|
-
Util.logger.error(
|
|
583
|
-
`- ${this.definition.type} ${metadata.name} does not have a valid schedule setting.`
|
|
584
|
-
);
|
|
585
|
-
return;
|
|
586
|
-
}
|
|
587
|
-
} else if (metadata.type === 'triggered' && metadata.fileTrigger) {
|
|
588
|
-
// Starting Source == 'File Drop'
|
|
589
|
-
// Do nothing for now
|
|
590
|
-
}
|
|
591
|
-
if (metadata.steps) {
|
|
592
|
-
for (const step of metadata.steps) {
|
|
593
|
-
const stepNumber = step.stepNumber || step.step;
|
|
594
|
-
delete step.stepNumber;
|
|
595
|
-
delete step.step;
|
|
596
|
-
|
|
597
|
-
for (const activity of step.activities) {
|
|
598
|
-
try {
|
|
599
|
-
// get metadata type of activity
|
|
600
|
-
activity.r__type = Util.inverseGet(
|
|
601
|
-
this.definition.activityTypeMapping,
|
|
602
|
-
activity.objectTypeId
|
|
603
|
-
);
|
|
604
|
-
delete activity.objectTypeId;
|
|
605
|
-
} catch {
|
|
606
|
-
Util.logger.warn(
|
|
607
|
-
` - Unknown activity type '${activity.objectTypeId}'` +
|
|
608
|
-
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
609
|
-
` of Automation '${metadata.name}'`
|
|
610
|
-
);
|
|
611
|
-
continue;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// if no activityObjectId then either serialized activity
|
|
615
|
-
// (config in Automation ) or unconfigured so no further action to be taken
|
|
616
|
-
if (
|
|
617
|
-
activity.activityObjectId === '00000000-0000-0000-0000-000000000000' ||
|
|
618
|
-
activity.activityObjectId == null ||
|
|
619
|
-
!this.definition.dependencies.includes(activity.r__type)
|
|
620
|
-
) {
|
|
621
|
-
// empty if block
|
|
622
|
-
}
|
|
623
|
-
// / if managed by cache we can update references to support deployment
|
|
624
|
-
else if (
|
|
625
|
-
Definitions[activity.r__type]?.['idField'] &&
|
|
626
|
-
cache.getCache(this.buObject.mid)[activity.r__type]
|
|
627
|
-
) {
|
|
628
|
-
try {
|
|
629
|
-
activity.activityObjectId = cache.searchForField(
|
|
630
|
-
activity.r__type,
|
|
631
|
-
activity.activityObjectId,
|
|
632
|
-
Definitions[activity.r__type].idField,
|
|
633
|
-
Definitions[activity.r__type].nameField
|
|
634
|
-
);
|
|
635
|
-
} catch (ex) {
|
|
636
|
-
// getFromCache throws error where the dependent metadata is not found
|
|
637
|
-
Util.logger.warn(
|
|
638
|
-
` - Missing ${activity.r__type} activity '${activity.name}'` +
|
|
639
|
-
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
640
|
-
` of Automation '${metadata.name}' (${ex.message})`
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
} else {
|
|
644
|
-
Util.logger.warn(
|
|
645
|
-
` - Missing ${activity.r__type} activity '${activity.name}'` +
|
|
646
|
-
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
647
|
-
` of Automation '${metadata.name}' (Not Found in Cache)`
|
|
648
|
-
);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
return JSON.parse(JSON.stringify(metadata));
|
|
654
|
-
} catch (ex) {
|
|
655
|
-
Util.logger.warn(
|
|
656
|
-
` - ${this.definition.typeName} '${metadata[this.definition.nameField]}': ${
|
|
657
|
-
ex.message
|
|
658
|
-
}`
|
|
659
|
-
);
|
|
660
|
-
return null;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
1149
|
/**
|
|
665
1150
|
* Builds a schedule object to be used for scheduling an automation
|
|
666
1151
|
* based on combination of ical string and start/end dates.
|
|
@@ -680,6 +1165,9 @@ class Automation extends MetadataType {
|
|
|
680
1165
|
const a = obj.split('=');
|
|
681
1166
|
recurHelper[a[0]] = a[1];
|
|
682
1167
|
}
|
|
1168
|
+
if (recurHelper.INTERVAL) {
|
|
1169
|
+
recurHelper.INTERVAL = Number.parseInt(recurHelper.INTERVAL);
|
|
1170
|
+
}
|
|
683
1171
|
// the ical schedule is all in caps but soap objects require Title Case.
|
|
684
1172
|
const keyStem = recurHelper.FREQ.charAt(0) + recurHelper.FREQ.slice(1, -2).toLowerCase();
|
|
685
1173
|
|
|
@@ -708,13 +1196,18 @@ class Automation extends MetadataType {
|
|
|
708
1196
|
'Scheduling automatically not supported for Weekly, Monthly and Yearly, please configure manually.'
|
|
709
1197
|
);
|
|
710
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
|
+
}
|
|
711
1204
|
|
|
712
1205
|
if (this.definition.timeZoneMapping[scheduleObject.timezoneName]) {
|
|
713
1206
|
scheduleObject.timezoneId =
|
|
714
1207
|
this.definition.timeZoneMapping[scheduleObject.timezoneName];
|
|
715
1208
|
} else {
|
|
716
|
-
|
|
717
|
-
|
|
1209
|
+
throw new Error(
|
|
1210
|
+
`Could not find timezone ${scheduleObject.timezoneName} in definition.timeZoneMapping`
|
|
718
1211
|
);
|
|
719
1212
|
}
|
|
720
1213
|
schedule.TimeZone.ID = scheduleObject.timezoneId;
|
|
@@ -808,8 +1301,8 @@ class Automation extends MetadataType {
|
|
|
808
1301
|
// create new Date object reflecting SFMC's servertime
|
|
809
1302
|
const dateServer = new Date(utc + 3600000 * offsetServer);
|
|
810
1303
|
|
|
811
|
-
// return time as a string without trailing "Z"
|
|
812
|
-
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];
|
|
813
1306
|
}
|
|
814
1307
|
/**
|
|
815
1308
|
* Experimental: Only working for DataExtensions:
|
|
@@ -836,7 +1329,7 @@ class Automation extends MetadataType {
|
|
|
836
1329
|
const automationType = { scheduled: 'Schedule', triggered: 'File Drop' };
|
|
837
1330
|
output += `**Started by:** ${automationType[json.type] || 'Not defined'}\n\n`;
|
|
838
1331
|
output += `**Status:** ${json.status}\n\n`;
|
|
839
|
-
if (json.type === 'scheduled') {
|
|
1332
|
+
if (json.type === 'scheduled' || json.schedule) {
|
|
840
1333
|
const tz =
|
|
841
1334
|
this.definition.timeZoneDifference[
|
|
842
1335
|
this.definition.timeZoneMapping[json?.schedule?.timezoneName]
|
|
@@ -846,7 +1339,7 @@ class Automation extends MetadataType {
|
|
|
846
1339
|
output += `**Schedule:**\n\n`;
|
|
847
1340
|
output += `* Start: ${json.schedule.startDate.split('T').join(' ')} ${tz}\n`;
|
|
848
1341
|
output += `* End: ${json.schedule.endDate.split('T').join(' ')} ${tz}\n`;
|
|
849
|
-
output += `* Timezone:
|
|
1342
|
+
output += `* Timezone: ${json.schedule.timezoneName}\n`;
|
|
850
1343
|
|
|
851
1344
|
const ical = {};
|
|
852
1345
|
for (const item of json.schedule.icalRecur.split(';')) {
|
|
@@ -855,9 +1348,20 @@ class Automation extends MetadataType {
|
|
|
855
1348
|
}
|
|
856
1349
|
const frequency = ical.FREQ.slice(0, -2).toLowerCase();
|
|
857
1350
|
|
|
858
|
-
output += `* Recurrance:
|
|
859
|
-
|
|
860
|
-
|
|
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';
|
|
861
1365
|
} else if (json.schedule) {
|
|
862
1366
|
output += `**Schedule:** Not defined\n`;
|
|
863
1367
|
}
|
|
@@ -868,6 +1372,27 @@ class Automation extends MetadataType {
|
|
|
868
1372
|
output += `* Pattern: ${json.fileTrigger.fileNamingPattern}\n`;
|
|
869
1373
|
output += `* Folder: ${json.fileTrigger.folderLocationText}\n`;
|
|
870
1374
|
}
|
|
1375
|
+
// add empty line to ensure the following notifications are rendered properly
|
|
1376
|
+
output += '\n';
|
|
1377
|
+
if (json.notifications?.length) {
|
|
1378
|
+
output += `**Notifications:**\n\n`;
|
|
1379
|
+
// ensure notifications are sorted by type regardless of how the API returns it
|
|
1380
|
+
const notifications = {};
|
|
1381
|
+
for (const n of json.notifications) {
|
|
1382
|
+
notifications[n.type] =
|
|
1383
|
+
(Array.isArray(n.email) ? n.email.join(',') : n.email) +
|
|
1384
|
+
(n.message ? ` ("${n.message}")` : '');
|
|
1385
|
+
}
|
|
1386
|
+
if (notifications.Complete) {
|
|
1387
|
+
output += `* Complete: ${notifications.Complete}\n`;
|
|
1388
|
+
}
|
|
1389
|
+
if (notifications.Error) {
|
|
1390
|
+
output += `* Error: ${notifications.Error}\n`;
|
|
1391
|
+
}
|
|
1392
|
+
} else {
|
|
1393
|
+
output += `**Notifications:** _none_\n\n`;
|
|
1394
|
+
}
|
|
1395
|
+
|
|
871
1396
|
// show table with automation steps
|
|
872
1397
|
if (tabled && tabled.length) {
|
|
873
1398
|
// add empty line to ensure the following table is rendered properly
|
|
@@ -975,11 +1500,10 @@ class Automation extends MetadataType {
|
|
|
975
1500
|
// as part of retrieve & manual execution we could face an empty folder
|
|
976
1501
|
return;
|
|
977
1502
|
}
|
|
978
|
-
await Promise.all(
|
|
979
|
-
Object.keys(metadata).map((key) =>
|
|
980
|
-
this._writeDoc(docPath + '/', key, metadata[key], 'md')
|
|
981
|
-
|
|
982
|
-
})
|
|
1503
|
+
return await Promise.all(
|
|
1504
|
+
Object.keys(metadata).map((key) =>
|
|
1505
|
+
this._writeDoc(docPath + '/', key, metadata[key], 'md')
|
|
1506
|
+
)
|
|
983
1507
|
);
|
|
984
1508
|
}
|
|
985
1509
|
}
|
|
@@ -1017,7 +1541,7 @@ class Automation extends MetadataType {
|
|
|
1017
1541
|
* @param {string} key customer key
|
|
1018
1542
|
* @returns {Promise.<string>} objectId or enpty string
|
|
1019
1543
|
*/
|
|
1020
|
-
static async
|
|
1544
|
+
static async #getObjectIdForSingleRetrieve(key) {
|
|
1021
1545
|
const response = await this.client.soap.retrieve('Program', ['ObjectID'], {
|
|
1022
1546
|
filter: {
|
|
1023
1547
|
leftOperand: 'CustomerKey',
|
|
@@ -1036,7 +1560,7 @@ class Automation extends MetadataType {
|
|
|
1036
1560
|
*/
|
|
1037
1561
|
static async deleteByKey(customerKey) {
|
|
1038
1562
|
// the delete endpoint returns a general exception if the automation does not exist; handle it gracefully instead by adding a retrieve first
|
|
1039
|
-
const objectId = customerKey ? await this
|
|
1563
|
+
const objectId = customerKey ? await this.#getObjectIdForSingleRetrieve(customerKey) : null;
|
|
1040
1564
|
if (!objectId) {
|
|
1041
1565
|
Util.logger.error(` - automation not found`);
|
|
1042
1566
|
return false;
|