diff --git a/docs/dist/documentation.md b/docs/dist/documentation.md index 82eedc210..8869a8e2e 100644 --- a/docs/dist/documentation.md +++ b/docs/dist/documentation.md @@ -186,11 +186,32 @@ Provides default functionality that can be overwritten by child metadata type cl
csvToArray(csv)Array.<string>

helper to convert CSVs into an array. if only one value was given, it's also returned as an array

+
Mcdev.(methodName, businessUnit, [selectedType], [keys])Promise.<boolean>
+

run a method across BUs

+
+
Mcdev.(methodName, cred, bu, [type], keyArr)Promise.<boolean>
+

helper for Mcdev.#runMethod

+
+
Mcdev.(selectedType, buObject)Array.<string>
+

helper for Mcdev.#runOnBU

+
+
Automation.(metadata)boolean
+

helper for postRetrieveTasks and execute

+
+
Automation.(metadataMap, key)Promise.<object>
+

helper for execute

+
+
Automation.(metadata)Promise.<object>
+

helper for pause

+
+
Automation.(metadata)
+

helper for preDeployTasks and execute

+
Automation.(metadataMap, key)Promise.<void>

helper for postDeployTasks

-
Automation.(metadataMap, originalMetadataMap, key)
-

helper for postDeployTasks

+
Automation.(metadataMap, originalMetadataMap, key)Promise.<object>
+

helper for postDeployTasks

getUserName(userList, item, fieldname)string
@@ -425,7 +446,7 @@ Deploys all metadata located in the 'deploy' directory to the specified business ### Deployer.\_deployBU(cred, bu, properties, [typeArr], [keyArr], [fromRetrieve]) ⇒ Promise.<TYPE.MultiMetadataTypeMap> -helper for [deploy](deploy) +helper for [deploy](#Deployer.deploy) **Kind**: static method of [Deployer](#Deployer) **Returns**: Promise.<TYPE.MultiMetadataTypeMap> - ensure that BUs are worked on sequentially @@ -496,8 +517,7 @@ main class * [.buildDefinitionBulk(listName, type, name)](#Mcdev.buildDefinitionBulk) ⇒ Promise.<void> * [.getFilesToCommit(businessUnit, selectedType, keyArr)](#Mcdev.getFilesToCommit) ⇒ Promise.<Array.<string>> * [.execute(businessUnit, [selectedType], [keys])](#Mcdev.execute) ⇒ Promise.<boolean> - * [._executeBU(cred, bu, [type], keyArr)](#Mcdev._executeBU) ⇒ Promise.<boolean> - * [._retrieveKeysWithLike(selectedType, buObject)](#Mcdev._retrieveKeysWithLike) ⇒ Array.<string> + * [.pause(businessUnit, [selectedType], [keys])](#Mcdev.pause) ⇒ Promise.<boolean> @@ -753,7 +773,7 @@ Build a specific metadata file based on a template using a list of bu-market com ### Mcdev.execute(businessUnit, [selectedType], [keys]) ⇒ Promise.<boolean> -Start an item (query) +Start/execute an item **Kind**: static method of [Mcdev](#Mcdev) **Returns**: Promise.<boolean> - true if all started successfully, false if not @@ -764,33 +784,19 @@ Start an item (query) | [selectedType] | TYPE.SupportedMetadataTypes | limit to given metadata types | | [keys] | Array.<string> | customerkey of the metadata | - + -### Mcdev.\_executeBU(cred, bu, [type], keyArr) ⇒ Promise.<boolean> -helper for [execute](#Mcdev.execute) +### Mcdev.pause(businessUnit, [selectedType], [keys]) ⇒ Promise.<boolean> +pause an item **Kind**: static method of [Mcdev](#Mcdev) -**Returns**: Promise.<boolean> - true if all items were executed, false otherwise - -| Param | Type | Description | -| --- | --- | --- | -| cred | string | name of Credential | -| bu | string | name of BU | -| [type] | TYPE.SupportedMetadataTypes | limit execution to given metadata type | -| keyArr | Array.<string> | customerkey of the metadata | - - - -### Mcdev.\_retrieveKeysWithLike(selectedType, buObject) ⇒ Array.<string> -helper for [_executeBU](#Mcdev._executeBU) - -**Kind**: static method of [Mcdev](#Mcdev) -**Returns**: Array.<string> - keyArr +**Returns**: Promise.<boolean> - true if all started successfully, false if not | Param | Type | Description | | --- | --- | --- | -| selectedType | TYPE.SupportedMetadataTypes | limit execution to given metadata type | -| buObject | TYPE.BuObject | properties for auth | +| businessUnit | string | name of BU | +| [selectedType] | TYPE.SupportedMetadataTypes | limit to given metadata types | +| [keys] | Array.<string> | customerkey of the metadata | @@ -944,7 +950,7 @@ This method retrieves these and saves them alongside the metadata json ### Asset.\_readExtendedFileFromFS(metadata, subType, deployDir, [pathOnly]) ⇒ Promise.<string> -helper for [preDeployTasks](preDeployTasks) +helper for [preDeployTasks](#Asset.preDeployTasks) Some metadata types store their actual content as a separate file, e.g. images This method reads these from the local FS stores them in the metadata object allowing to deploy it @@ -1090,7 +1096,7 @@ Asset-specific script that retrieves the folder ID from cache and updates the gi ### Asset.\_mergeCode(metadata, deployDir, subType, [templateName], [fileListOnly]) ⇒ Promise.<Array.<TYPE.CodeExtract>> -helper for [preDeployTasks](preDeployTasks) that loads extracted code content back into JSON +helper for [preDeployTasks](#Asset.preDeployTasks) that loads extracted code content back into JSON **Kind**: static method of [Asset](#Asset) **Returns**: Promise.<Array.<TYPE.CodeExtract>> - fileList for templating (disregarded during deployment) @@ -1106,7 +1112,7 @@ helper for [preDeployTasks](preDeployTasks) that loads extracted code content ba ### Asset.\_mergeCode\_slots(prefix, metadataSlots, readDirArr, subtypeExtension, subDirArr, fileList, customerKey, [templateName], [fileListOnly]) ⇒ Promise.<void> -helper for [preDeployTasks](preDeployTasks) that loads extracted code content back into JSON +helper for [preDeployTasks](#Asset.preDeployTasks) that loads extracted code content back into JSON **Kind**: static method of [Asset](#Asset) **Returns**: Promise.<void> - - @@ -1126,7 +1132,7 @@ helper for [preDeployTasks](preDeployTasks) that loads extracted code content ba ### Asset.\_extractCode(metadata) ⇒ TYPE.CodeExtractItem -helper for [postRetrieveTasks](postRetrieveTasks) that finds code content in JSON and extracts it +helper for [postRetrieveTasks](#Asset.postRetrieveTasks) that finds code content in JSON and extracts it to allow saving that separately and formatted **Kind**: static method of [Asset](#Asset) @@ -1250,6 +1256,8 @@ Automation MetadataType * [.retrieveForCache()](#Automation.retrieveForCache) ⇒ Promise.<TYPE.AutomationMapObj> * [.retrieveAsTemplate(templateDir, name, templateVariables)](#Automation.retrieveAsTemplate) ⇒ Promise.<TYPE.AutomationItemObj> * [.postRetrieveTasks(metadata)](#Automation.postRetrieveTasks) ⇒ TYPE.AutomationItem \| void + * [.execute(keyArr)](#Automation.execute) ⇒ Promise.<boolean> + * [.pause(keyArr)](#Automation.pause) ⇒ Promise.<boolean> * [.deploy(metadata, targetBU, retrieveDir)](#Automation.deploy) ⇒ Promise.<TYPE.AutomationMap> * [.create(metadata)](#Automation.create) ⇒ Promise * [.update(metadata, metadataBefore)](#Automation.update) ⇒ Promise @@ -1320,6 +1328,30 @@ manages post retrieve steps | --- | --- | --- | | metadata | TYPE.AutomationItem | a single automation | + + +### Automation.execute(keyArr) ⇒ Promise.<boolean> +a function to start query execution via API + +**Kind**: static method of [Automation](#Automation) +**Returns**: Promise.<boolean> - Returns true if all items were executed successfully, otherwise false + +| Param | Type | Description | +| --- | --- | --- | +| keyArr | Array.<string> | customerkey of the metadata | + + + +### Automation.pause(keyArr) ⇒ Promise.<boolean> +a function to start query execution via API + +**Kind**: static method of [Automation](#Automation) +**Returns**: Promise.<boolean> - Returns true if all items were executed successfully, otherwise false + +| Param | Type | Description | +| --- | --- | --- | +| keyArr | Array.<string> | customerkey of the metadata | + ### Automation.deploy(metadata, targetBU, retrieveDir) ⇒ Promise.<TYPE.AutomationMap> @@ -3025,7 +3057,7 @@ Helper for writing Metadata to disk, used for Retrieve and deploy ### Journey.\_postRetrieveTasksBulk(metadataMap) -helper for Journey's [saveResults](saveResults). Gets executed after retreive of metadata type and +helper for Journey's [saveResults](#Journey.saveResults). Gets executed after retreive of metadata type and **Kind**: static method of [Journey](#Journey) @@ -3183,6 +3215,7 @@ Provides default functionality that can be overwritten by child metadata type cl * [.update(metadata, [metadataBefore])](#MetadataType.update) ⇒ void * [.refresh()](#MetadataType.refresh) ⇒ void * [.execute()](#MetadataType.execute) ⇒ void + * [.pause()](#MetadataType.pause) ⇒ void * [.hasChanged(cachedVersion, metadata, [fieldName])](#MetadataType.hasChanged) ⇒ boolean * [.hasChangedGeneric(cachedVersion, metadata, [fieldName], [silent])](#MetadataType.hasChangedGeneric) ⇒ boolean * [.upsert(metadataMap, deployDir)](#MetadataType.upsert) ⇒ Promise.<TYPE.MetadataTypeMap> @@ -3294,7 +3327,7 @@ Gets executed after deployment of metadata type ### MetadataType.postCreateTasks(metadataEntry, apiResponse) ⇒ void -helper for [createREST](createREST) +helper for [createREST](#MetadataType.createREST) **Kind**: static method of [MetadataType](#MetadataType) @@ -3306,7 +3339,7 @@ helper for [createREST](createREST) ### MetadataType.postUpdateTasks(metadataEntry, apiResponse) ⇒ void -helper for [updateREST](updateREST) +helper for [updateREST](#MetadataType.updateREST) **Kind**: static method of [MetadataType](#MetadataType) @@ -3318,7 +3351,7 @@ helper for [updateREST](updateREST) ### MetadataType.postDeployTasks\_legacyApi(metadataEntry, apiResponse) ⇒ Promise.<void> -helper for [createREST](createREST) when legacy API endpoints as these do not return the created item but only their new id +helper for [createREST](#MetadataType.createREST) when legacy API endpoints as these do not return the created item but only their new id **Kind**: static method of [MetadataType](#MetadataType) **Returns**: Promise.<void> - - @@ -3499,6 +3532,12 @@ Abstract refresh method that needs to be implemented in child metadata type ### MetadataType.execute() ⇒ void Abstract execute method that needs to be implemented in child metadata type +**Kind**: static method of [MetadataType](#MetadataType) + + +### MetadataType.pause() ⇒ void +Abstract pause method that needs to be implemented in child metadata type + **Kind**: static method of [MetadataType](#MetadataType) @@ -3626,7 +3665,7 @@ Updates a single metadata entry via fuel-soap (generic lib not wrapper) ### MetadataType.getSOAPErrorMsg(ex) ⇒ string -helper for [_handleSOAPErrors](_handleSOAPErrors) +helper for [_handleSOAPErrors](#MetadataType._handleSOAPErrors) **Kind**: static method of [MetadataType](#MetadataType) **Returns**: string - error message @@ -3681,7 +3720,7 @@ Used to execute a query/automation etc. ### MetadataType.runDocumentOnRetrieve([singleRetrieve], metadataMap) ⇒ Promise.<void> -helper for [retrieveREST](retrieveREST) and [retrieveSOAP](retrieveSOAP) +helper for [retrieveREST](#MetadataType.retrieveREST) and [retrieveSOAP](#MetadataType.retrieveSOAP) **Kind**: static method of [MetadataType](#MetadataType) **Returns**: Promise.<void> - - @@ -3811,7 +3850,7 @@ Helper for writing Metadata to disk, used for Retrieve and deploy ### MetadataType.applyTemplateValues(code, templateVariables) ⇒ string -helper for [buildDefinitionForNested](buildDefinitionForNested) +helper for [buildDefinitionForNested](#MetadataType.buildDefinitionForNested) searches extracted file for template variable names and applies the market values **Kind**: static method of [MetadataType](#MetadataType) @@ -3825,7 +3864,7 @@ searches extracted file for template variable names and applies the market value ### MetadataType.applyTemplateNames(code, templateVariables) ⇒ string -helper for [buildTemplateForNested](buildTemplateForNested) +helper for [buildTemplateForNested](#MetadataType.buildTemplateForNested) searches extracted file for template variable values and applies the market variable names **Kind**: static method of [MetadataType](#MetadataType) @@ -3839,7 +3878,7 @@ searches extracted file for template variable values and applies the market vari ### MetadataType.buildDefinitionForNested(templateDir, targetDir, metadata, variables, templateName) ⇒ Promise.<Array.<Array.<string>>> -helper for [buildDefinition](buildDefinition) +helper for [buildDefinition](#MetadataType.buildDefinition) handles extracted code if any are found for complex types (e.g script, asset, query) **Kind**: static method of [MetadataType](#MetadataType) @@ -3856,7 +3895,7 @@ handles extracted code if any are found for complex types (e.g script, asset, qu ### MetadataType.buildTemplateForNested(templateDir, targetDir, metadata, templateVariables, templateName) ⇒ Promise.<Array.<Array.<string>>> -helper for [buildTemplate](buildTemplate) +helper for [buildTemplate](#MetadataType.buildTemplate) handles extracted code if any are found for complex types **Kind**: static method of [MetadataType](#MetadataType) @@ -4196,7 +4235,7 @@ manages post retrieve steps ### MobileKeyword.prepExtractedCode(metadataScript) ⇒ Object -helper for [parseMetadata](parseMetadata) and [_buildForNested](_buildForNested) +helper for [postRetrieveTasks](#MobileKeyword.postRetrieveTasks) and [_buildForNested](#MobileKeyword._buildForNested) **Kind**: static method of [MobileKeyword](#MobileKeyword) **Returns**: Object - returns found extension and file content @@ -4246,7 +4285,7 @@ scripts are saved as 1 json and 1 ssjs file. both files need to be run through t ### MobileKeyword.\_buildForNested(templateDir, targetDir, metadata, templateVariables, templateName, mode) ⇒ Promise.<Array.<Array.<string>>> -helper for [buildTemplateForNested](buildTemplateForNested) / [buildDefinitionForNested](buildDefinitionForNested) +helper for [buildTemplateForNested](#MobileKeyword.buildTemplateForNested) / [buildDefinitionForNested](#MobileKeyword.buildDefinitionForNested) handles extracted code if any are found for complex types **Kind**: static method of [MobileKeyword](#MobileKeyword) @@ -4277,7 +4316,7 @@ prepares an event definition for deployment ### MobileKeyword.postCreateTasks(metadataEntry, apiResponse) ⇒ void -helper for [createREST](createREST) +helper for [createREST](#MetadataType.createREST) **Kind**: static method of [MobileKeyword](#MobileKeyword) @@ -4289,7 +4328,7 @@ helper for [createREST](createREST) ### MobileKeyword.postUpdateTasks(metadataEntry, apiResponse) ⇒ void -helper for [updateREST](updateREST) +helper for [updateREST](#MetadataType.updateREST) **Kind**: static method of [MobileKeyword](#MobileKeyword) @@ -4301,7 +4340,7 @@ helper for [updateREST](updateREST) ### MobileKeyword.\_mergeCode(metadata, deployDir, [templateName]) ⇒ Promise.<string> -helper for [preDeployTasks](preDeployTasks) that loads extracted code content back into JSON +helper for [preDeployTasks](#MobileKeyword.preDeployTasks) that loads extracted code content back into JSON **Kind**: static method of [MobileKeyword](#MobileKeyword) **Returns**: Promise.<string> - content for metadata.script @@ -4429,7 +4468,7 @@ Creates a single item ### MobileMessage.\_mergeCode(metadata, deployDir, [templateName]) ⇒ Promise.<string> -helper for [preDeployTasks](preDeployTasks) that loads extracted code content back into JSON +helper for [preDeployTasks](#MobileMessage.preDeployTasks) that loads extracted code content back into JSON **Kind**: static method of [MobileMessage](#MobileMessage) **Returns**: Promise.<string> - code @@ -4443,7 +4482,7 @@ helper for [preDeployTasks](preDeployTasks) that loads extracted code content ba ### MobileMessage.prepExtractedCode(code) ⇒ Object -helper for [parseMetadata](parseMetadata) and [_buildForNested](_buildForNested) +helper for [postRetrieveTasks](#MobileMessage.postRetrieveTasks) and [_buildForNested](#MobileMessage._buildForNested) **Kind**: static method of [MobileMessage](#MobileMessage) **Returns**: Object - returns found extension and file content @@ -4493,7 +4532,7 @@ prepares an event definition for deployment ### MobileMessage.postCreateTasks(metadataEntry, apiResponse) ⇒ void -helper for [createREST](createREST) +helper for [createREST](#MetadataType.createREST) **Kind**: static method of [MobileMessage](#MobileMessage) @@ -4505,7 +4544,7 @@ helper for [createREST](createREST) ### MobileMessage.postUpdateTasks(metadataEntry, apiResponse) ⇒ void -helper for [updateREST](updateREST) +helper for [updateREST](#MetadataType.updateREST) **Kind**: static method of [MobileMessage](#MobileMessage) @@ -4555,7 +4594,7 @@ scripts are saved as 1 json and 1 ssjs file. both files need to be run through t ### MobileMessage.\_buildForNested(templateDir, targetDir, metadata, templateVariables, templateName, mode) ⇒ Promise.<Array.<Array.<string>>> -helper for [buildTemplateForNested](buildTemplateForNested) / [buildDefinitionForNested](buildDefinitionForNested) +helper for [buildTemplateForNested](#MobileMessage.buildTemplateForNested) / [buildDefinitionForNested](#MobileMessage.buildDefinitionForNested) handles extracted code if any are found for complex types **Kind**: static method of [MobileMessage](#MobileMessage) @@ -4709,7 +4748,7 @@ prepares a Query for deployment ### Query.applyTemplateValues(code, templateVariables) ⇒ string -helper for [buildDefinitionForNested](buildDefinitionForNested) +helper for [buildDefinitionForNested](#Query.buildDefinitionForNested) searches extracted SQL file for template variables and applies the market values **Kind**: static method of [Query](#Query) @@ -5010,7 +5049,7 @@ Creates a single Script ### Script.\_mergeCode(metadata, deployDir, [templateName]) ⇒ Promise.<string> -helper for [preDeployTasks](preDeployTasks) that loads extracted code content back into JSON +helper for [preDeployTasks](#Script.preDeployTasks) that loads extracted code content back into JSON **Kind**: static method of [Script](#Script) **Returns**: Promise.<string> - content for metadata.script @@ -5075,7 +5114,7 @@ scripts are saved as 1 json and 1 ssjs file. both files need to be run through t ### Script.\_buildForNested(templateDir, targetDir, metadata, templateVariables, templateName, mode) ⇒ Promise.<Array.<Array.<string>>> -helper for [buildTemplateForNested](buildTemplateForNested) / [buildDefinitionForNested](buildDefinitionForNested) +helper for [buildTemplateForNested](#Script.buildTemplateForNested) / [buildDefinitionForNested](#Script.buildDefinitionForNested) handles extracted code if any are found for complex types **Kind**: static method of [Script](#Script) @@ -5105,7 +5144,7 @@ Splits the script metadata into two parts and parses in a standard manner ### Script.prepExtractedCode(metadataScript, metadataName) ⇒ Object -helper for [parseMetadata](parseMetadata) and [_buildForNested](_buildForNested) +helper for [parseMetadata](#Script.parseMetadata) and [_buildForNested](#Script._buildForNested) **Kind**: static method of [Script](#Script) **Returns**: Object - returns found extension and file content @@ -5389,7 +5428,7 @@ prepares for deployment ### TransactionalSMS.\_mergeCode(metadata, deployDir, [templateName]) ⇒ Promise.<string> -helper for [preDeployTasks](preDeployTasks) that loads extracted code content back into JSON +helper for [preDeployTasks](#TransactionalSMS.preDeployTasks) that loads extracted code content back into JSON **Kind**: static method of [TransactionalSMS](#TransactionalSMS) **Returns**: Promise.<string> - content for metadata.script @@ -5415,7 +5454,7 @@ manages post retrieve steps ### TransactionalSMS.prepExtractedCode(metadataScript) ⇒ Object -helper for [parseMetadata](parseMetadata) and [_buildForNested](_buildForNested) +helper for [postRetrieveTasks](#TransactionalSMS.postRetrieveTasks) and [_buildForNested](#TransactionalSMS._buildForNested) **Kind**: static method of [TransactionalSMS](#TransactionalSMS) **Returns**: Object - returns found extension and file content @@ -5427,7 +5466,7 @@ helper for [parseMetadata](parseMetadata) and [_buildForNested](_buildForNested) ### TransactionalSMS.buildDefinitionForNested(templateDir, targetDir, metadata, templateVariables, templateName) ⇒ Promise.<Array.<Array.<string>>> -helper for [buildDefinition](#MetadataType.buildDefinition) +helper for [TransactionalMessage.buildDefinition](TransactionalMessage.buildDefinition) handles extracted code if any are found for complex types **Kind**: static method of [TransactionalSMS](#TransactionalSMS) @@ -5444,7 +5483,7 @@ handles extracted code if any are found for complex types ### TransactionalSMS.buildTemplateForNested(templateDir, targetDir, metadata, templateVariables, templateName) ⇒ Promise.<Array.<Array.<string>>> -helper for [buildTemplate](#MetadataType.buildTemplate) +helper for [TransactionalMessage.buildTemplate](TransactionalMessage.buildTemplate) handles extracted code if any are found for complex types **Kind**: static method of [TransactionalSMS](#TransactionalSMS) @@ -5465,7 +5504,7 @@ scripts are saved as 1 json and 1 ssjs file. both files need to be run through t ### TransactionalSMS.\_buildForNested(templateDir, targetDir, metadata, templateVariables, templateName, mode) ⇒ Promise.<Array.<Array.<string>>> -helper for [buildTemplateForNested](buildTemplateForNested) / [buildDefinitionForNested](buildDefinitionForNested) +helper for [buildTemplateForNested](#TransactionalSMS.buildTemplateForNested) / [buildDefinitionForNested](#TransactionalSMS.buildDefinitionForNested) handles extracted code if any are found for complex types **Kind**: static method of [TransactionalSMS](#TransactionalSMS) @@ -5616,7 +5655,7 @@ TSD-specific refresh method that finds active TSDs and refreshes them ### TriggeredSend.getKeysForValidTSDs(metadata) ⇒ Promise.<Array.<string>> -helper for [refresh](refresh) that extracts the keys from the TSD item map and eli +helper for [refresh](#TriggeredSend.refresh) that extracts the keys from the TSD item map and eli **Kind**: static method of [TriggeredSend](#TriggeredSend) **Returns**: Promise.<Array.<string>> - keyArr @@ -5628,7 +5667,7 @@ helper for [refresh](refresh) that extracts the keys from the TSD item map and e ### TriggeredSend.findRefreshableItems([assetLoaded]) ⇒ Promise.<TYPE.MetadataTypeMapObj> -helper for [refresh](refresh) that finds active TSDs on the server and filters it by the same rules that [retrieve](retrieve) is using to avoid refreshing TSDs with broken dependencies +helper for [refresh](#TriggeredSend.refresh) that finds active TSDs on the server and filters it by the same rules that [retrieve](#TriggeredSend.retrieve) is using to avoid refreshing TSDs with broken dependencies **Kind**: static method of [TriggeredSend](#TriggeredSend) **Returns**: Promise.<TYPE.MetadataTypeMapObj> - Promise of TSD item map @@ -5640,7 +5679,7 @@ helper for [refresh](refresh) that finds active TSDs on the server and filters i ### TriggeredSend.\_refreshItem(key, checkKey) ⇒ Promise.<boolean> -helper for [refresh](refresh) that pauses, publishes and starts a triggered send +helper for [refresh](#TriggeredSend.refresh) that pauses, publishes and starts a triggered send **Kind**: static method of [TriggeredSend](#TriggeredSend) **Returns**: Promise.<boolean> - true if refresh was successful @@ -5860,7 +5899,7 @@ Retrieve metadata of specified types into local file system and Retriever.metada ### retriever.\_getTypeDependencies(metadataTypes) ⇒ Array.<TYPE.SupportedMetadataTypes> -helper for [retrieve](retrieve) to get all dependencies of the given types +helper for [Retriever.retrieve](Retriever.retrieve) to get all dependencies of the given types **Kind**: instance method of [Retriever](#Retriever) **Returns**: Array.<TYPE.SupportedMetadataTypes> - unique list dependent metadata types @@ -8220,6 +8259,98 @@ helper to convert CSVs into an array. if only one value was given, it's also ret | --- | --- | --- | | csv | string | potentially comma-separated value or null | + + +## Mcdev.(methodName, businessUnit, [selectedType], [keys]) ⇒ Promise.<boolean> +run a method across BUs + +**Kind**: global function +**Returns**: Promise.<boolean> - true if all started successfully, false if not + +| Param | Type | Description | +| --- | --- | --- | +| methodName | 'execute' \| 'pause' | what to run | +| businessUnit | string | name of BU | +| [selectedType] | TYPE.SupportedMetadataTypes | limit to given metadata types | +| [keys] | Array.<string> | customerkey of the metadata | + + + +## Mcdev.(methodName, cred, bu, [type], keyArr) ⇒ Promise.<boolean> +helper for [Mcdev.#runMethod](Mcdev.#runMethod) + +**Kind**: global function +**Returns**: Promise.<boolean> - true if all items were executed, false otherwise + +| Param | Type | Description | +| --- | --- | --- | +| methodName | 'execute' \| 'pause' | what to run | +| cred | string | name of Credential | +| bu | string | name of BU | +| [type] | TYPE.SupportedMetadataTypes | limit execution to given metadata type | +| keyArr | Array.<string> | customerkey of the metadata | + + + +## Mcdev.(selectedType, buObject) ⇒ Array.<string> +helper for [Mcdev.#runOnBU](Mcdev.#runOnBU) + +**Kind**: global function +**Returns**: Array.<string> - keyArr + +| Param | Type | Description | +| --- | --- | --- | +| selectedType | TYPE.SupportedMetadataTypes | limit execution to given metadata type | +| buObject | TYPE.BuObject | properties for auth | + + + +## Automation.(metadata) ⇒ boolean +helper for [postRetrieveTasks](#Automation.postRetrieveTasks) and [execute](#Automation.execute) + +**Kind**: global function +**Returns**: boolean - true if the automation schedule is valid + +| Param | Type | Description | +| --- | --- | --- | +| metadata | TYPE.AutomationItem | a single automation | + + + +## Automation.(metadataMap, key) ⇒ Promise.<object> +helper for [execute](#Automation.execute) + +**Kind**: global function +**Returns**: Promise.<object> - Returns the result of the API call + +| Param | Type | Description | +| --- | --- | --- | +| metadataMap | TYPE.AutomationMap | map of metadata | +| key | string | key of the metadata | + + + +## Automation.(metadata) ⇒ Promise.<object> +helper for [pause](#Automation.pause) + +**Kind**: global function +**Returns**: Promise.<object> - schedule reponse + +| Param | Type | Description | +| --- | --- | --- | +| metadata | TYPE.AutomationItem | automation metadata | + + + +## Automation.(metadata) +helper for [preDeployTasks](#Automation.preDeployTasks) and [execute](#Automation.execute) + +**Kind**: global function + +| Param | Type | Description | +| --- | --- | --- | +| metadata | TYPE.AutomationItem | metadata mapped by their keyField | + ## Automation.(metadataMap, key) ⇒ Promise.<void> @@ -8235,10 +8366,11 @@ helper for [postDeployTasks](#Automation.postDeployTasks) -## Automation.(metadataMap, originalMetadataMap, key) -helper for [postDeployTasks](postDeployTasks) +## Automation.(metadataMap, originalMetadataMap, key) ⇒ Promise.<object> +helper for [postDeployTasks](#Automation.postDeployTasks) **Kind**: global function +**Returns**: Promise.<object> - - | Param | Type | Description | | --- | --- | --- | diff --git a/lib/Deployer.js b/lib/Deployer.js index 28fd496e8..8df75ff6d 100644 --- a/lib/Deployer.js +++ b/lib/Deployer.js @@ -180,7 +180,7 @@ class Deployer { return buMultiMetadataTypeMap; } /** - * helper for {@link deploy} + * helper for {@link Deployer.deploy} * * @param {string} cred name of Credential * @param {string} bu name of BU diff --git a/lib/Retriever.js b/lib/Retriever.js index 86ecf781e..f32acd2e3 100644 --- a/lib/Retriever.js +++ b/lib/Retriever.js @@ -189,7 +189,7 @@ class Retriever { } /** - * helper for {@link retrieve} to get all dependencies of the given types + * helper for {@link Retriever.retrieve} to get all dependencies of the given types * * @param {TYPE.SupportedMetadataTypes[]} metadataTypes list of metadata types to retrieve; can include subtypes! * @returns {TYPE.SupportedMetadataTypes[]} unique list dependent metadata types diff --git a/lib/cli.js b/lib/cli.js index 7a7e5ea89..36476badb 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -438,6 +438,37 @@ yargs Mcdev.execute(argv.BU, argv.TYPE, csvToArray(argv.KEY)); }, }) + .command({ + command: 'pause [KEY]', + aliases: ['p', 'stop'], + desc: 'pauses the entity (automation etc.)', + builder: (yargs) => { + yargs + .positional('BU', { + type: 'string', + describe: 'the business unit where to start an item', + }) + .positional('TYPE', { + type: 'string', + describe: 'metadata type', + }) + .positional('KEY', { + type: 'string', + describe: 'key(s) of the metadata component(s)', + }) + .option('like', { + type: 'string', + group: 'Options for pause:', + describe: + 'filter metadata components (can include % as wildcard or _ for a single character)', + }); + }, + handler: (argv) => { + Mcdev.setOptions(argv); + // ! do not allow multiple types to be passed in here via csvToArray + Mcdev.pause(argv.BU, argv.TYPE, csvToArray(argv.KEY)); + }, + }) .command({ command: 'upgrade', aliases: ['up'], diff --git a/lib/index.js b/lib/index.js index 5928a854e..775364b0a 100644 --- a/lib/index.js +++ b/lib/index.js @@ -175,20 +175,20 @@ class Mcdev { } if (businessUnit === '*') { - Util.logger.info('\n :: Retrieving all BUs for all credentials'); + Util.logger.info(':: Retrieving all BUs for all credentials'); let counter_credTotal = 0; for (const cred in properties.credentials) { - Util.logger.info(`\n :: Retrieving all BUs for ${cred}`); + Util.logger.info(`:: Retrieving all BUs for ${cred}`); let counter_credBu = 0; for (const bu in properties.credentials[cred].businessUnits) { - await this._retrieveBU(cred, bu, selectedTypesArr, keys); + await this.#retrieveBU(cred, bu, selectedTypesArr, keys); counter_credBu++; Util.startLogger(true); } counter_credTotal += counter_credBu; - Util.logger.info(`\n :: ${counter_credBu} BUs for ${cred}\n`); + Util.logger.info(`:: ${counter_credBu} BUs for ${cred}\n`); } - Util.logger.info(`\n :: ${counter_credTotal} BUs in total\n`); + Util.logger.info(`:: ${counter_credTotal} BUs in total\n`); } else { let [cred, bu] = businessUnit ? businessUnit.split('/') : [null, null]; // to allow all-BU via user selection we need to run this here already @@ -212,17 +212,17 @@ class Mcdev { } if (bu === '*' && properties.credentials && properties.credentials[cred]) { - Util.logger.info(`\n :: Retrieving all BUs for ${cred}`); + Util.logger.info(`:: Retrieving all BUs for ${cred}`); let counter_credBu = 0; for (const bu in properties.credentials[cred].businessUnits) { - await this._retrieveBU(cred, bu, selectedTypesArr, keys); + await this.#retrieveBU(cred, bu, selectedTypesArr, keys); counter_credBu++; Util.startLogger(true); } - Util.logger.info(`\n :: ${counter_credBu} BUs for ${cred}\n`); + Util.logger.info(`:: ${counter_credBu} BUs for ${cred}\n`); } else { // retrieve a single BU; return - const retrieveChangelog = await this._retrieveBU( + const retrieveChangelog = await this.#retrieveBU( cred, bu, selectedTypesArr, @@ -247,7 +247,7 @@ class Mcdev { * @param {boolean} [changelogOnly] skip saving, only create json in memory * @returns {Promise.} ensure that BUs are worked on sequentially */ - static async _retrieveBU(cred, bu, selectedTypesArr, keys, changelogOnly) { + static async #retrieveBU(cred, bu, selectedTypesArr, keys, changelogOnly) { const properties = await config.getProperties(); if (!(await config.checkProperties(properties))) { return null; @@ -272,7 +272,7 @@ class Mcdev { triggeredSend: 'triggeredSendDefinition', user: 'accountUser', }; - Util.logger.info(`\n :: Retrieving ${cred}/${bu}\n`); + Util.logger.info(`:: Retrieving ${cred}/${bu}\n`); const retrieveTypesArr = []; if (selectedTypesArr) { for (const selectedType of Array.isArray(selectedTypesArr) @@ -703,7 +703,7 @@ class Mcdev { } } /** - * Start an item (query) + * Start/execute an item * * @param {string} businessUnit name of BU * @param {TYPE.SupportedMetadataTypes} [selectedType] limit to given metadata types @@ -711,8 +711,49 @@ class Mcdev { * @returns {Promise.} true if all started successfully, false if not */ static async execute(businessUnit, selectedType, keys) { + return this.#runMethod('execute', businessUnit, selectedType, keys); + } + /** + * pause an item + * + * @param {string} businessUnit name of BU + * @param {TYPE.SupportedMetadataTypes} [selectedType] limit to given metadata types + * @param {string[]} [keys] customerkey of the metadata + * @returns {Promise.} true if all started successfully, false if not + */ + static async pause(businessUnit, selectedType, keys) { + return this.#runMethod('pause', businessUnit, selectedType, keys); + } + /** + * run a method across BUs + * + * @param {'execute'|'pause'} methodName what to run + * @param {string} businessUnit name of BU + * @param {TYPE.SupportedMetadataTypes} [selectedType] limit to given metadata types + * @param {string[]} [keys] customerkey of the metadata + * @returns {Promise.} true if all started successfully, false if not + */ + static async #runMethod(methodName, businessUnit, selectedType, keys) { Util.startLogger(); - Util.logger.info('mcdev:: Executing ' + selectedType); + let lang_past; + let lang_present; + let requireKeyOrLike; + switch (methodName) { + case 'execute': { + lang_past = 'executed'; + lang_present = 'executing'; + requireKeyOrLike = true; + break; + } + case 'pause': { + lang_past = 'paused'; + lang_present = 'pausing'; + requireKeyOrLike = true; + break; + } + } + + Util.logger.info(`mcdev:: ${methodName} ${selectedType}`); const properties = await config.getProperties(); let counter_credBu = 0; let counter_failed = 0; @@ -723,13 +764,15 @@ class Mcdev { if (!Util._isValidType(selectedType)) { return false; } - if (!Object.prototype.hasOwnProperty.call(MetadataTypeInfo[selectedType], 'execute')) { + if (!Object.prototype.hasOwnProperty.call(MetadataTypeInfo[selectedType], methodName)) { Util.logger.error( - ` ☇ skipping ${selectedType}: execute is not supported yet for ${selectedType}` + ` ☇ skipping ${selectedType}: ${methodName} is not supported yet for ${selectedType}` ); return false; } + if ( + requireKeyOrLike && (!Array.isArray(keys) || !keys.length) && (!Util.OPTIONS.like || !Object.keys(Util.OPTIONS.like).length) ) { @@ -744,14 +787,17 @@ class Mcdev { Util.logger.error('You can either specify keys OR a --like filter.'); return false; } + if (businessUnit === '*') { - Util.logger.info(':: Executing the entity on all BUs for all credentials'); + Util.logger.info( + `:: ${lang_present} the ${selectedType} on all BUs for all credentials` + ); let counter_credTotal = 0; for (const cred in properties.credentials) { - Util.logger.info(`:: Executing the entity on all BUs for ${cred}`); + Util.logger.info(`:: ${lang_present} ${selectedType} on all BUs for ${cred}`); for (const bu in properties.credentials[cred].businessUnits) { - if (await this._executeBU(cred, bu, selectedType, keys)) { + if (await this.#runOnBU(methodName, cred, bu, selectedType, keys)) { counter_credBu++; } else { counter_failed++; @@ -759,9 +805,13 @@ class Mcdev { Util.startLogger(true); } counter_credTotal += counter_credBu; - Util.logger.info(`:: Executed the entity on ${counter_credBu} BUs for ${cred}\n`); + Util.logger.info( + `:: ${lang_past} ${selectedType} on ${counter_credBu} BUs for ${cred}` + ); } - Util.logger.info(`:: Executed the entity on ${counter_credTotal} BUs in total\n`); + Util.logger.info( + `:: ${lang_past} ${selectedType} on ${counter_credTotal} BUs in total\n` + ); } else { let [cred, bu] = businessUnit ? businessUnit.split('/') : [null, null]; // to allow all-BU via user selection we need to run this here already @@ -784,10 +834,10 @@ class Mcdev { } } if (bu === '*' && properties.credentials && properties.credentials[cred]) { - Util.logger.info(`\n :: Executing the entity on all BUs for ${cred}`); + Util.logger.info(`:: ${lang_present} ${selectedType} on all BUs for ${cred}`); let counter_credBu = 0; for (const bu in properties.credentials[cred].businessUnits) { - if (await this._executeBU(cred, bu, selectedType, keys)) { + if (await this.#runOnBU(methodName, cred, bu, selectedType, keys)) { counter_credBu++; } else { counter_failed++; @@ -795,33 +845,34 @@ class Mcdev { Util.startLogger(true); } Util.logger.info( - `\n :: Executed the entity on ${counter_credBu} BUs for ${cred}\n` + `:: ${lang_past} ${selectedType} on ${counter_credBu} BUs for ${cred}` ); } else { - // execute the entity on one BU only - if (await this._executeBU(cred, bu, selectedType, keys)) { + // execute runMethod for the entity on one BU only + if (await this.#runOnBU(methodName, cred, bu, selectedType, keys)) { counter_credBu++; } else { counter_failed++; } - Util.logger.info(`\n :: Done\n`); + Util.logger.info(`:: Done`); } } - if (counter_credBu !== 0) { - Util.logger.info(`\n :: Executed query on ${counter_credBu} BUs\n`); + if (counter_credBu > 1) { + Util.logger.info(`:: ${lang_past} ${selectedType} on ${counter_credBu} BUs`); } return counter_failed === 0 ? true : false; } /** - * helper for {@link Mcdev.execute} + * helper for {@link Mcdev.#runMethod} * + * @param {'execute'|'pause'} methodName what to run * @param {string} cred name of Credential * @param {string} bu name of BU * @param {TYPE.SupportedMetadataTypes} [type] limit execution to given metadata type * @param {string[]} keyArr customerkey of the metadata * @returns {Promise.} true if all items were executed, false otherwise */ - static async _executeBU(cred, bu, type, keyArr) { + static async #runOnBU(methodName, cred, bu, type, keyArr) { const properties = await config.getProperties(); let counter_failed = 0; const buObject = await Cli.getCredentialObject( @@ -839,34 +890,37 @@ class Mcdev { cred = buObject.credential; bu = buObject.businessUnit; } - Util.logger.info(`\n :: Executing ${type} on ${cred}/${bu}\n`); + Util.logger.info(`:: ${methodName} ${type} on ${cred}/${bu}`); MetadataTypeInfo[type].client = auth.getSDK(buObject); if (Util.OPTIONS.like && Object.keys(Util.OPTIONS.like).length) { - keyArr = await this._retrieveKeysWithLike(type, buObject); + keyArr = await this.#retrieveKeysWithLike(type, buObject); + } else { + MetadataTypeInfo[type].properties = properties; + MetadataTypeInfo[type].buObject = buObject; } if (!keyArr || (Array.isArray(keyArr) && !keyArr.length)) { throw new Error('No keys were provided'); } - // result will be undefined (false) if execute is not supported for the type - if (!(await MetadataTypeInfo[type].execute(keyArr))) { + // result will be undefined (false) if methodName is not supported for the type + if (!(await MetadataTypeInfo[type][methodName](keyArr))) { counter_failed++; } } catch (ex) { - Util.logger.errorStack(ex, 'mcdev.execute failed'); + Util.logger.errorStack(ex, 'mcdev.' + methodName + ' failed'); } return counter_failed === 0 ? true : false; } /** - * helper for {@link Mcdev._executeBU} + * helper for {@link Mcdev.#runOnBU} * * @param {TYPE.SupportedMetadataTypes} selectedType limit execution to given metadata type * @param {TYPE.BuObject} buObject properties for auth * @returns {string[]} keyArr */ - static async _retrieveKeysWithLike(selectedType, buObject) { + static async #retrieveKeysWithLike(selectedType, buObject) { const properties = await config.getProperties(); // cache depenencies diff --git a/lib/metadataTypes/Asset.js b/lib/metadataTypes/Asset.js index 2fd5c2c91..c44e337c3 100644 --- a/lib/metadataTypes/Asset.js +++ b/lib/metadataTypes/Asset.js @@ -86,7 +86,7 @@ class Asset extends MetadataType { return { metadata: Object.values(metadata)[0], type: this.definition.type }; } /** - * helper for {@link retrieve} + {@link retrieveAsTemplate} + * helper for {@link Asset.retrieve} + {@link Asset.retrieveAsTemplate} * * @private * @returns {TYPE.AssetSubType[]} subtype array @@ -448,7 +448,7 @@ class Asset extends MetadataType { ); } /** - * helper for {@link preDeployTasks} + * helper for {@link Asset.preDeployTasks} * Some metadata types store their actual content as a separate file, e.g. images * This method reads these from the local FS stores them in the metadata object allowing to deploy it * @@ -520,7 +520,7 @@ class Asset extends MetadataType { } /** - * helper for {@link postDeployTasks}. triggers a refresh of active triggerredSendDefinitions associated with the updated asset-message items. Gets executed if refresh option has been set. + * helper for {@link Asset.postDeployTasks}. triggers a refresh of active triggerredSendDefinitions associated with the updated asset-message items. Gets executed if refresh option has been set. * * @private * @param {TYPE.MetadataTypeMap} metadata metadata mapped by their keyField @@ -866,7 +866,7 @@ class Asset extends MetadataType { } /** - * helper for {@link preDeployTasks} that loads extracted code content back into JSON + * helper for {@link Asset.preDeployTasks} that loads extracted code content back into JSON * * @param {TYPE.AssetItem} metadata a single asset definition * @param {string} deployDir directory of deploy files @@ -1150,7 +1150,7 @@ class Asset extends MetadataType { return fileList; } /** - * helper for {@link preDeployTasks} that loads extracted code content back into JSON + * helper for {@link Asset.preDeployTasks} that loads extracted code content back into JSON * * @param {string} prefix usually the customerkey * @param {object} metadataSlots metadata.views.html.slots or deeper slots.<>.blocks.<>.slots @@ -1230,7 +1230,7 @@ class Asset extends MetadataType { } } /** - * helper for {@link postRetrieveTasks} that finds code content in JSON and extracts it + * helper for {@link Asset.postRetrieveTasks} that finds code content in JSON and extracts it * to allow saving that separately and formatted * * @param {TYPE.AssetItem} metadata a single asset definition diff --git a/lib/metadataTypes/Automation.js b/lib/metadataTypes/Automation.js index b2aaefa01..1f6dd6d19 100644 --- a/lib/metadataTypes/Automation.js +++ b/lib/metadataTypes/Automation.js @@ -318,6 +318,29 @@ class Automation extends MetadataType { throw new Error(JSON.stringify(results)); } } + /** + * helper for {@link Automation.postRetrieveTasks} and {@link Automation.execute} + * + * @param {TYPE.AutomationItem} metadata a single automation + * @returns {boolean} true if the automation schedule is valid + */ + static #isValidSchedule(metadata) { + if (metadata.type === 'scheduled' && metadata.schedule?.startDate) { + try { + if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) { + // if we found the id in our list, remove the redundant data + delete metadata.schedule.timezoneId; + } + } catch { + Util.logger.debug( + `- Schedule name '${metadata.schedule.timezoneName}' not found in definition.timeZoneMapping` + ); + } + return true; + } else { + return false; + } + } /** * manages post retrieve steps * @@ -332,27 +355,13 @@ class Automation extends MetadataType { if (metadata.type === 'scheduled' && metadata.schedule?.startDate) { // Starting Source == 'Schedule' - try { - if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) { - // if we found the id in our list, remove the redundant data - delete metadata.schedule.timezoneId; - } - } catch { - Util.logger.debug( - `- Schedule name '${metadata.schedule.timezoneName}' not found in definition.timeZoneMapping` - ); - } - try { - // type 'Running' is temporary status only, overwrite with Scheduled for storage. - if (metadata.type === 'scheduled' && metadata.status === 'Running') { - metadata.status = 'Scheduled'; - } - } catch { - Util.logger.error( - `- ${this.definition.type} ${metadata.name} does not have a valid schedule setting.` - ); + if (!this.#isValidSchedule(metadata)) { return; } + // type 'Running' is temporary status only, overwrite with Scheduled for storage. + if (metadata.type === 'scheduled' && metadata.status === 'Running') { + metadata.status = 'Scheduled'; + } } else if (metadata.type === 'triggered' && metadata.fileTrigger) { // Starting Source == 'File Drop' // Do nothing for now @@ -445,6 +454,147 @@ class Automation extends MetadataType { return null; } } + /** + * a function to start query execution via API + * + * @param {string[]} keyArr customerkey of the metadata + * @returns {Promise.} Returns true if all items were executed successfully, otherwise false + */ + static async execute(keyArr) { + const metadataMap = {}; + for (const key of keyArr) { + if (key) { + const results = await this.retrieve(undefined, undefined, undefined, key); + if (Object.keys(results.metadata).length) { + for (const key of Object.keys(results.metadata)) { + if (this.#isValidSchedule(results.metadata[key])) { + metadataMap[key] = results.metadata[key]; + } else { + Util.logger.error( + ` - skipping ${this.definition.type} ${results.metadata[key].name}: no valid schedule settings found.` + ); + } + } + } + } + } + + Util.logger.info( + `Starting automations according to schedule: ${Object.keys(metadataMap).length}` + ); + const promiseResults = []; + + for (const key of Object.keys(metadataMap)) { + if (metadataMap[key].status === 'Scheduled') { + Util.logger.info( + ` - skipping ${this.definition.type} ${metadataMap[key].name}: already scheduled.` + ); + } else { + promiseResults.push(this.#executeItem(metadataMap, key)); + } + } + const results = await Promise.all(promiseResults); + const successCounter = results + .filter(Boolean) + .filter((r) => r.OverallStatus === 'OK').length; + Util.logger.info(`Executed ${successCounter} of ${keyArr.length} items`); + return successCounter === keyArr.length; + } + /** + * helper for {@link Automation.execute} + * + * @param {TYPE.AutomationMap} metadataMap map of metadata + * @param {string} key key of the metadata + * @returns {Promise.} Returns the result of the API call + */ + static async #executeItem(metadataMap, key) { + this.#preDeploySchedule(metadataMap[key]); + metadataMap[key].status = 'Scheduled'; + return this.#scheduleAutomation(metadataMap, metadataMap, key); + } + /** + * a function to start query execution via API + * + * @param {string[]} keyArr customerkey of the metadata + * @returns {Promise.} Returns true if all items were executed successfully, otherwise false + */ + static async pause(keyArr) { + const metadataMap = {}; + for (const key of keyArr) { + if (key) { + const results = await this.retrieve(undefined, undefined, undefined, key); + if (Object.keys(results.metadata).length) { + for (const key of Object.keys(results.metadata)) { + if (this.#isValidSchedule(results.metadata[key])) { + metadataMap[key] = results.metadata[key]; + } else { + Util.logger.error( + ` - skipping ${this.definition.type} ${results.metadata[key].name}: no valid schedule settings found.` + ); + } + } + } + } + } + + Util.logger.info(`Pausing automations: ${Object.keys(metadataMap).length}`); + const promiseResults = []; + for (const key of Object.keys(metadataMap)) { + if (metadataMap[key].status === 'Scheduled') { + promiseResults.push(this.#pauseItem(metadataMap[key])); + } else if (metadataMap[key].status === 'PausedSchedule') { + Util.logger.info( + ` - skipping ${this.definition.type} ${metadataMap[key].name}: already paused.` + ); + } else { + Util.logger.error( + ` - skipping ${this.definition.type} ${ + metadataMap[key].name + }: currently ${metadataMap[ + key + ].status.toLowerCase()}. Please try again in a few minutes.` + ); + } + } + const successCounter = (await Promise.all(promiseResults)) + .filter(Boolean) + .filter((r) => r.OverallStatus === 'OK').length; + + Util.logger.info(`Paused ${successCounter} of ${keyArr.length} items`); + return successCounter === keyArr.length; + } + + /** + * helper for {@link Automation.pause} + * + * @param {TYPE.AutomationItem} metadata automation metadata + * @returns {Promise.} schedule reponse + */ + static async #pauseItem(metadata) { + const schedule = {}; + try { + const response = await this.client.soap.schedule( + 'Automation', + schedule, + { + Interaction: { + ObjectID: metadata[this.definition.idField], + }, + }, + 'pause', + {} + ); + Util.logger.info( + ` - paused ${this.definition.type}: ${metadata[this.definition.keyField]} / ${ + metadata[this.definition.nameField] + }` + ); + return response; + } catch (ex) { + this._handleSOAPErrors(ex, 'pausing', metadata, false); + return null; + } + } /** * Deploys automation - the saved file is the original one due to large differences required for deployment @@ -492,6 +642,33 @@ class Automation extends MetadataType { return super.updateREST(metadata, uri); } + /** + * helper for {@link Automation.preDeployTasks} and {@link Automation.execute} + * + * @param {TYPE.AutomationItem} metadata metadata mapped by their keyField + */ + static #preDeploySchedule(metadata) { + delete metadata.schedule.rangeTypeId; + delete metadata.schedule.pattern; + delete metadata.schedule.scheduledTime; + delete metadata.schedule.scheduledStatus; + if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) { + metadata.schedule.timezoneId = + this.definition.timeZoneMapping[metadata.schedule.timezoneName]; + } else { + Util.logger.error( + `Could not find timezone ${metadata.schedule.timezoneName} in definition.timeZoneMapping` + ); + } + + // the upsert API needs this to be named scheduleTypeId; the retrieve API returns it as typeId + metadata.schedule.scheduleTypeId = metadata.schedule.typeId; + delete metadata.schedule.typeId; + + // prep startSource + metadata.startSource = { schedule: metadata.schedule, typeId: 1 }; + } + /** * Gets executed before deploying metadata * @@ -515,25 +692,12 @@ class Automation extends MetadataType { if (metadata.type === 'scheduled' && metadata?.schedule?.startDate) { // Starting Source == 'Schedule' - delete metadata.schedule.rangeTypeId; - delete metadata.schedule.pattern; - delete metadata.schedule.scheduledTime; - delete metadata.schedule.scheduledStatus; - if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) { - metadata.schedule.timezoneId = - this.definition.timeZoneMapping[metadata.schedule.timezoneName]; - } else { - Util.logger.error( - `Could not find timezone ${metadata.schedule.timezoneName} in definition.timeZoneMapping` - ); - } - delete metadata.schedule.timezoneName; - // the upsert API needs this to be named scheduleTypeId; the retrieve API returns it as typeId - metadata.schedule.scheduleTypeId = metadata.schedule.typeId; - delete metadata.schedule.typeId; + this.#preDeploySchedule(metadata); + // * run _buildSchedule here but only to check if things look ok - do not use the returned schedule object for deploy + this._buildSchedule(metadata.schedule); - // prep startSource - metadata.startSource = { schedule: metadata.schedule, typeId: 1 }; + delete metadata.schedule.timezoneName; + delete metadata.startSource.schedule.timezoneName; } else if (metadata.type === 'triggered' && metadata.fileTrigger) { // Starting Source == 'File Drop' @@ -641,6 +805,30 @@ class Automation extends MetadataType { */ static async postDeployTasks(metadataMap, originalMetadataMap) { for (const key in metadataMap) { + if (!metadataMap[key].type) { + // create response does not return the type attribute + + // el.schedule.timezoneName + const scheduleHelper = + metadataMap[key].schedule || metadataMap[key].startSource.schedule; + scheduleHelper.timezoneName ||= Util.inverseGet( + this.definition.timeZoneMapping, + scheduleHelper.timezoneId + ); + + // el.type + metadataMap[key].type = scheduleHelper + ? 'scheduled' + : metadataMap[key].fileTrigger + ? 'triggered' + : undefined; + + // el.status + metadataMap[key].status ||= Util.inverseGet( + this.definition.statusMapping, + metadataMap[key].statusId + ); + } // need to put schedule on here if status is scheduled await Automation.#scheduleAutomation(metadataMap, originalMetadataMap, key); @@ -656,6 +844,10 @@ class Automation extends MetadataType { } } } + if (Util.OPTIONS.execute) { + Util.logger.info(`Executing: ${this.definition.type}`); + await this.execute(Object.keys(metadataMap)); + } } /** * helper for {@link Automation.postDeployTasks} @@ -705,13 +897,15 @@ class Automation extends MetadataType { } /** - * helper for {@link postDeployTasks} + * helper for {@link Automation.postDeployTasks} * * @param {TYPE.AutomationMap} metadataMap metadata mapped by their keyField * @param {TYPE.AutomationMap} originalMetadataMap metadata to be updated (contains additioanl fields) * @param {string} key current customer key + * @returns {Promise.} - */ static async #scheduleAutomation(metadataMap, originalMetadataMap, key) { + let response = null; if (originalMetadataMap[key]?.type === 'scheduled') { // Starting Source == 'Schedule': Try starting the automation if (originalMetadataMap[key].status === 'Scheduled') { @@ -733,7 +927,7 @@ class Automation extends MetadataType { const schedule_timezoneString = schedule._timezoneString; delete schedule._timezoneString; // start the automation - await this.client.soap.schedule( + response = await this.client.soap.schedule( 'Automation', schedule, { @@ -757,9 +951,10 @@ class Automation extends MetadataType { schedule_StartDateTime.split('T').join(' ').split('.')[0] } ${schedule_timezoneString}` ); - } catch (ex) { + } catch { + // API does not return anything usefull here. We have to know the rules instead Util.logger.error( - `- Could not start scheduled automation '${originalMetadataMap[key].name}': ${ex.message}` + ` ☇ error starting scheduled ${this.definition.type}${key}: Please check schedule settings` ); } } @@ -780,6 +975,7 @@ class Automation extends MetadataType { metadataMap[key].schedule.typeId = metadataMap[key].schedule.scheduleTypeId; delete metadataMap[key].schedule.scheduleTypeId; } + return response; } /** @@ -866,6 +1062,9 @@ class Automation extends MetadataType { const a = obj.split('='); recurHelper[a[0]] = a[1]; } + if (recurHelper.INTERVAL) { + recurHelper.INTERVAL = Number.parseInt(recurHelper.INTERVAL); + } // the ical schedule is all in caps but soap objects require Title Case. const keyStem = recurHelper.FREQ.charAt(0) + recurHelper.FREQ.slice(1, -2).toLowerCase(); @@ -894,13 +1093,18 @@ class Automation extends MetadataType { 'Scheduling automatically not supported for Weekly, Monthly and Yearly, please configure manually.' ); } + if (recurHelper.FREQ === 'MINUTELY' && recurHelper.INTERVAL && recurHelper.INTERVAL < 5) { + throw new Error( + 'The smallest interval you can configure is 5 minutes. Please adjust your schedule.' + ); + } if (this.definition.timeZoneMapping[scheduleObject.timezoneName]) { scheduleObject.timezoneId = this.definition.timeZoneMapping[scheduleObject.timezoneName]; } else { - Util.logger.error( - `- Could not find timezone ${scheduleObject.timezoneName} in definition.timeZoneMapping` + throw new Error( + `Could not find timezone ${scheduleObject.timezoneName} in definition.timeZoneMapping` ); } schedule.TimeZone.ID = scheduleObject.timezoneId; @@ -994,8 +1198,8 @@ class Automation extends MetadataType { // create new Date object reflecting SFMC's servertime const dateServer = new Date(utc + 3600000 * offsetServer); - // return time as a string without trailing "Z" - return dateServer.toISOString().slice(0, -1); + // return time as a string without trailing "Z" and without miliseconds (separated by .) + return dateServer.toISOString().slice(0, -1).split('.')[0]; } /** * Experimental: Only working for DataExtensions: @@ -1022,7 +1226,7 @@ class Automation extends MetadataType { const automationType = { scheduled: 'Schedule', triggered: 'File Drop' }; output += `**Started by:** ${automationType[json.type] || 'Not defined'}\n\n`; output += `**Status:** ${json.status}\n\n`; - if (json.type === 'scheduled') { + if (json.type === 'scheduled' || json.schedule) { const tz = this.definition.timeZoneDifference[ this.definition.timeZoneMapping[json?.schedule?.timezoneName] @@ -1032,7 +1236,7 @@ class Automation extends MetadataType { output += `**Schedule:**\n\n`; output += `* Start: ${json.schedule.startDate.split('T').join(' ')} ${tz}\n`; output += `* End: ${json.schedule.endDate.split('T').join(' ')} ${tz}\n`; - output += `* Timezone: ${json.schedule.timezoneName}\n`; + output += `* Timezone: ${json.schedule.timezoneName}\n`; const ical = {}; for (const item of json.schedule.icalRecur.split(';')) { @@ -1043,7 +1247,7 @@ class Automation extends MetadataType { output += `* Recurrance: every ${ical.INTERVAL > 1 ? ical.INTERVAL : ''} ${ frequency === 'dai' ? 'day' : frequency - }${ical.INTERVAL > 1 ? 's' : ''} ${ical.COUNT ? `for ${ical.COUNT} times` : ''}\n`; + }${ical.INTERVAL > 1 ? 's' : ''}${ical.COUNT ? ` for ${ical.COUNT} times` : ''}\n`; } else if (json.schedule) { output += `**Schedule:** Not defined\n`; } diff --git a/lib/metadataTypes/DataExtension.js b/lib/metadataTypes/DataExtension.js index 43e9dcfc8..7210095a6 100644 --- a/lib/metadataTypes/DataExtension.js +++ b/lib/metadataTypes/DataExtension.js @@ -203,7 +203,7 @@ class DataExtension extends MetadataType { } } /** - * helper for {@link upsert} + * helper for {@link DataExtension.upsert} * * @private * @param {object} res - @@ -607,7 +607,7 @@ class DataExtension extends MetadataType { metadata[customerKey].Fields = fieldArr; } /** - * helper for {@link super.updateREST} and {@link super.updateSOAP} that removes old files after the key was changed + * helper for {@link MetadataType.updateREST} and {@link MetadataType.updateSOAP} that removes old files after the key was changed * * @private * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry diff --git a/lib/metadataTypes/Journey.js b/lib/metadataTypes/Journey.js index 87e03e2b5..1719dfe47 100644 --- a/lib/metadataTypes/Journey.js +++ b/lib/metadataTypes/Journey.js @@ -264,7 +264,7 @@ class Journey extends MetadataType { } /** - * helper for Journey's {@link saveResults}. Gets executed after retreive of metadata type and + * helper for Journey's {@link Journey.saveResults}. Gets executed after retreive of metadata type and * * @param {TYPE.MetadataTypeMap} metadataMap key=customer key, value=metadata */ @@ -477,7 +477,7 @@ class Journey extends MetadataType { return metadata; } /** - * helper for {@link postRetrieveTasks} + * helper for {@link Journey.postRetrieveTasks} * * @private * @param {TYPE.MetadataTypeItem} metadata a single item @@ -799,7 +799,7 @@ class Journey extends MetadataType { } /** - * helper for {@link preDeployTasks} + * helper for {@link Journey.preDeployTasks} * * @private * @param {TYPE.MetadataTypeItem} metadata a single item diff --git a/lib/metadataTypes/MetadataType.js b/lib/metadataTypes/MetadataType.js index 09533b907..d18f75848 100644 --- a/lib/metadataTypes/MetadataType.js +++ b/lib/metadataTypes/MetadataType.js @@ -135,7 +135,7 @@ class MetadataType { static postDeployTasks(upsertResults, originalMetadata, createdUpdated) {} /** - * helper for {@link createREST} + * helper for {@link MetadataType.createREST} * * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry * @param {object} apiResponse varies depending on the API call @@ -144,7 +144,7 @@ class MetadataType { static postCreateTasks(metadataEntry, apiResponse) {} /** - * helper for {@link updateREST} + * helper for {@link MetadataType.updateREST} * * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry * @param {object} apiResponse varies depending on the API call @@ -153,7 +153,7 @@ class MetadataType { static postUpdateTasks(metadataEntry, apiResponse) {} /** - * helper for {@link createREST} when legacy API endpoints as these do not return the created item but only their new id + * helper for {@link MetadataType.createREST} when legacy API endpoints as these do not return the created item but only their new id * * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry * @param {object} apiResponse varies depending on the API call @@ -463,6 +463,17 @@ class MetadataType { ); return; } + /** + * Abstract pause method that needs to be implemented in child metadata type + * + * @returns {void} + */ + static pause() { + Util.logger.error( + ` ☇ skipping ${this.definition.type}: pause is not supported yet for ${this.definition.type}` + ); + return; + } /** * test if metadata was actually changed or not to potentially skip it during deployment @@ -899,7 +910,7 @@ class MetadataType { } /** - * helper for {@link updateREST} and {@link updateSOAP} that removes old files after the key was changed + * helper for {@link MetadataType.updateREST} and {@link MetadataType.updateSOAP} that removes old files after the key was changed * * @private * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry @@ -980,7 +991,7 @@ class MetadataType { } } /** - * helper for {@link _handleSOAPErrors} + * helper for {@link MetadataType._handleSOAPErrors} * * @param {Error} ex error that occured * @returns {string} error message @@ -1090,7 +1101,7 @@ class MetadataType { } /** - * helper for {@link retrieveREST} and {@link retrieveSOAP} + * helper for {@link MetadataType.retrieveREST} and {@link MetadataType.retrieveSOAP} * * @param {string|number} [singleRetrieve] key of single item to filter by * @param {TYPE.MetadataTypeMap} metadataMap saved metadata @@ -1620,7 +1631,7 @@ class MetadataType { return savedResults; } /** - * helper for {@link buildDefinitionForNested} + * helper for {@link MetadataType.buildDefinitionForNested} * searches extracted file for template variable names and applies the market values * * @param {string} code code from extracted code @@ -1632,7 +1643,7 @@ class MetadataType { return Mustache.render(code, templateVariables, {}, ['{{{', '}}}']); } /** - * helper for {@link buildTemplateForNested} + * helper for {@link MetadataType.buildTemplateForNested} * searches extracted file for template variable values and applies the market variable names * * @param {string} code code from extracted code @@ -1644,7 +1655,7 @@ class MetadataType { return Util.replaceByObject(code, templateVariables); } /** - * helper for {@link buildDefinition} + * helper for {@link MetadataType.buildDefinition} * handles extracted code if any are found for complex types (e.g script, asset, query) * * @param {string} templateDir Directory where metadata templates are stored @@ -1665,7 +1676,7 @@ class MetadataType { return null; } /** - * helper for {@link buildTemplate} + * helper for {@link MetadataType.buildTemplate} * handles extracted code if any are found for complex types * * @param {string} templateDir Directory where metadata templates are stored diff --git a/lib/metadataTypes/MobileKeyword.js b/lib/metadataTypes/MobileKeyword.js index 02ade31c3..12b796bc0 100644 --- a/lib/metadataTypes/MobileKeyword.js +++ b/lib/metadataTypes/MobileKeyword.js @@ -96,7 +96,7 @@ class MobileKeyword extends MetadataType { } /** - * helper for {@link parseResponseBody} that creates a custom key field for this type based on mobileCode and keyword + * helper for {@link MobileKeyword.parseResponseBody} that creates a custom key field for this type based on mobileCode and keyword * * @private * @param {TYPE.MetadataType} metadata single item @@ -106,7 +106,7 @@ class MobileKeyword extends MetadataType { } /** - * helper for {@link preDeployTasks} and {@link createOrUpdate} to ensure we have code & keyword properly set + * helper for {@link MobileKeyword.preDeployTasks} and {@link MobileKeyword.createOrUpdate} to ensure we have code & keyword properly set * * @private * @param {TYPE.MetadataType} metadata single item @@ -192,7 +192,7 @@ class MobileKeyword extends MetadataType { } /** - * helper for {@link retrieve} and {@link retrieveAsTemplate} + * helper for {@link MobileKeyword.retrieve} and {@link MobileKeyword.retrieveAsTemplate} * * @private * @param {string} key customer key of single item to retrieve / name of the metadata file @@ -281,7 +281,7 @@ class MobileKeyword extends MetadataType { } } /** - * helper for {@link parseMetadata} and {@link _buildForNested} + * helper for {@link MobileKeyword.postRetrieveTasks} and {@link MobileKeyword._buildForNested} * * @param {string} metadataScript the code of the file * @returns {{fileExt:string,code:string}} returns found extension and file content @@ -349,7 +349,7 @@ class MobileKeyword extends MetadataType { } /** - * helper for {@link buildTemplateForNested} / {@link buildDefinitionForNested} + * helper for {@link MobileKeyword.buildTemplateForNested} / {@link MobileKeyword.buildDefinitionForNested} * handles extracted code if any are found for complex types * * @param {string} templateDir Directory where metadata templates are stored @@ -445,7 +445,7 @@ class MobileKeyword extends MetadataType { return metadata; } /** - * helper for {@link createREST} + * helper for {@link MetadataType.createREST} * * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry * @param {object} apiResponse varies depending on the API call @@ -455,7 +455,7 @@ class MobileKeyword extends MetadataType { await super.postDeployTasks_legacyApi(metadataEntry, apiResponse); } /** - * helper for {@link updateREST} + * helper for {@link MetadataType.updateREST} * * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry * @param {object} apiResponse varies depending on the API call @@ -466,7 +466,7 @@ class MobileKeyword extends MetadataType { } /** - * helper for {@link preDeployTasks} that loads extracted code content back into JSON + * helper for {@link MobileKeyword.preDeployTasks} that loads extracted code content back into JSON * * @param {TYPE.MetadataTypeItem} metadata a single definition * @param {string} deployDir directory of deploy files diff --git a/lib/metadataTypes/MobileMessage.js b/lib/metadataTypes/MobileMessage.js index a9a1f8227..8514b5cd0 100644 --- a/lib/metadataTypes/MobileMessage.js +++ b/lib/metadataTypes/MobileMessage.js @@ -83,7 +83,7 @@ class MobileMessage extends MetadataType { return super.createREST(metadata, '/legacy/v1/beta/mobile/message/'); } /** - * helper for {@link preDeployTasks} that loads extracted code content back into JSON + * helper for {@link MobileMessage.preDeployTasks} that loads extracted code content back into JSON * * @param {TYPE.MetadataTypeItem} metadata a single definition * @param {string} deployDir directory of deploy files @@ -110,7 +110,7 @@ class MobileMessage extends MetadataType { } } /** - * helper for {@link parseMetadata} and {@link _buildForNested} + * helper for {@link MobileMessage.postRetrieveTasks} and {@link MobileMessage._buildForNested} * * @param {string} code the code of the file * @returns {{fileExt:string,code:string}} returns found extension and file content @@ -304,7 +304,7 @@ class MobileMessage extends MetadataType { return metadata; } /** - * helper for {@link createREST} + * helper for {@link MetadataType.createREST} * * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry * @param {object} apiResponse varies depending on the API call @@ -314,7 +314,7 @@ class MobileMessage extends MetadataType { await super.postDeployTasks_legacyApi(metadataEntry, apiResponse); } /** - * helper for {@link updateREST} + * helper for {@link MetadataType.updateREST} * * @param {TYPE.MetadataTypeItem} metadataEntry a single metadata Entry * @param {object} apiResponse varies depending on the API call @@ -380,7 +380,7 @@ class MobileMessage extends MetadataType { } /** - * helper for {@link buildTemplateForNested} / {@link buildDefinitionForNested} + * helper for {@link MobileMessage.buildTemplateForNested} / {@link MobileMessage.buildDefinitionForNested} * handles extracted code if any are found for complex types * * @param {string} templateDir Directory where metadata templates are stored diff --git a/lib/metadataTypes/Query.js b/lib/metadataTypes/Query.js index f33d9a9d4..4fe76d256 100644 --- a/lib/metadataTypes/Query.js +++ b/lib/metadataTypes/Query.js @@ -250,7 +250,7 @@ class Query extends MetadataType { return metadata; } /** - * helper for {@link buildDefinitionForNested} + * helper for {@link Query.buildDefinitionForNested} * searches extracted SQL file for template variables and applies the market values * * @param {string} code code from extracted code @@ -328,7 +328,7 @@ class Query extends MetadataType { ); } /** - * helper for {@link buildTemplateForNested} / {@link buildDefinitionForNested} + * helper for {@link Query.buildTemplateForNested} / {@link Query.buildDefinitionForNested} * handles extracted code if any are found for complex types * * @private diff --git a/lib/metadataTypes/Script.js b/lib/metadataTypes/Script.js index aec027bfb..1f33b1c1f 100644 --- a/lib/metadataTypes/Script.js +++ b/lib/metadataTypes/Script.js @@ -82,7 +82,7 @@ class Script extends MetadataType { } /** - * helper for {@link preDeployTasks} that loads extracted code content back into JSON + * helper for {@link Script.preDeployTasks} that loads extracted code content back into JSON * * @param {TYPE.ScriptItem} metadata a single asset definition * @param {string} deployDir directory of deploy files @@ -189,7 +189,7 @@ class Script extends MetadataType { } /** - * helper for {@link buildTemplateForNested} / {@link buildDefinitionForNested} + * helper for {@link Script.buildTemplateForNested} / {@link Script.buildDefinitionForNested} * handles extracted code if any are found for complex types * * @param {string} templateDir Directory where metadata templates are stored @@ -279,7 +279,7 @@ class Script extends MetadataType { return { json: metadata, codeArr: codeArr, subFolder: null }; } /** - * helper for {@link parseMetadata} and {@link _buildForNested} + * helper for {@link Script.parseMetadata} and {@link Script._buildForNested} * * @param {string} metadataScript the code of the file * @param {string} metadataName the name of the metadata diff --git a/lib/metadataTypes/TransactionalSMS.js b/lib/metadataTypes/TransactionalSMS.js index 4e9bfb3db..dd568c10c 100644 --- a/lib/metadataTypes/TransactionalSMS.js +++ b/lib/metadataTypes/TransactionalSMS.js @@ -66,7 +66,7 @@ class TransactionalSMS extends TransactionalMessage { return metadata; } /** - * helper for {@link preDeployTasks} that loads extracted code content back into JSON + * helper for {@link TransactionalSMS.preDeployTasks} that loads extracted code content back into JSON * * @param {TYPE.MetadataTypeItem} metadata a single definition * @param {string} deployDir directory of deploy files @@ -158,7 +158,7 @@ class TransactionalSMS extends TransactionalMessage { return { json: metadata, codeArr: codeArr, subFolder: null }; } /** - * helper for {@link parseMetadata} and {@link _buildForNested} + * helper for {@link TransactionalSMS.postRetrieveTasks} and {@link TransactionalSMS._buildForNested} * * @param {string} metadataScript the code of the file * @returns {{fileExt:string,code:string}} returns found extension and file content @@ -189,7 +189,7 @@ class TransactionalSMS extends TransactionalMessage { return { fileExt, code }; } /** - * helper for {@link MetadataType.buildDefinition} + * helper for {@link TransactionalMessage.buildDefinition} * handles extracted code if any are found for complex types * * @param {string} templateDir Directory where metadata templates are stored @@ -216,7 +216,7 @@ class TransactionalSMS extends TransactionalMessage { ); } /** - * helper for {@link MetadataType.buildTemplate} + * helper for {@link TransactionalMessage.buildTemplate} * handles extracted code if any are found for complex types * * @example scripts are saved as 1 json and 1 ssjs file. both files need to be run through templating @@ -245,7 +245,7 @@ class TransactionalSMS extends TransactionalMessage { } /** - * helper for {@link buildTemplateForNested} / {@link buildDefinitionForNested} + * helper for {@link TransactionalSMS.buildTemplateForNested} / {@link TransactionalSMS.buildDefinitionForNested} * handles extracted code if any are found for complex types * * @param {string} templateDir Directory where metadata templates are stored diff --git a/lib/metadataTypes/TriggeredSend.js b/lib/metadataTypes/TriggeredSend.js index 71d8bd35a..e0df317af 100644 --- a/lib/metadataTypes/TriggeredSend.js +++ b/lib/metadataTypes/TriggeredSend.js @@ -236,7 +236,7 @@ class TriggeredSend extends MetadataType { } /** - * helper for {@link refresh} that extracts the keys from the TSD item map and eli + * helper for {@link TriggeredSend.refresh} that extracts the keys from the TSD item map and eli * * @param {TYPE.MetadataTypeMapObj} metadata TSD item map * @returns {Promise.} keyArr @@ -250,7 +250,7 @@ class TriggeredSend extends MetadataType { return keyArr; } /** - * helper for {@link refresh} that finds active TSDs on the server and filters it by the same rules that {@link retrieve} is using to avoid refreshing TSDs with broken dependencies + * helper for {@link TriggeredSend.refresh} that finds active TSDs on the server and filters it by the same rules that {@link TriggeredSend.retrieve} is using to avoid refreshing TSDs with broken dependencies * * @param {boolean} [assetLoaded] if run after Asset.deploy via --refresh option this will skip caching assets * @returns {Promise.} Promise of TSD item map @@ -304,7 +304,7 @@ class TriggeredSend extends MetadataType { } /** - * helper for {@link refresh} that pauses, publishes and starts a triggered send + * helper for {@link TriggeredSend.refresh} that pauses, publishes and starts a triggered send * * @param {string} key external key of triggered send item * @param {boolean} checkKey whether to check if key exists on the server diff --git a/lib/metadataTypes/User.js b/lib/metadataTypes/User.js index 28e942215..5d86c32b4 100644 --- a/lib/metadataTypes/User.js +++ b/lib/metadataTypes/User.js @@ -740,7 +740,7 @@ class User extends MetadataType { } /** - * helper for {@link retrieveSOAP} + * helper for {@link User.retrieveSOAP} * * @private * @param {TYPE.SoapRequestParams} [requestParams] required for the specific request (filter for example) @@ -900,7 +900,7 @@ class User extends MetadataType { } } /** - * helper for {@link createOrUpdate} to generate a random initial password for new users + * helper for {@link User.createOrUpdate} to generate a random initial password for new users * note: possible minimum length values in SFMC are 6, 8, 10, 15 chars. Therefore we should default here to 15 chars. * * @private diff --git a/lib/metadataTypes/definitions/Automation.definition.js b/lib/metadataTypes/definitions/Automation.definition.js index c7ab19a19..e34b258ca 100644 --- a/lib/metadataTypes/definitions/Automation.definition.js +++ b/lib/metadataTypes/definitions/Automation.definition.js @@ -42,12 +42,19 @@ module.exports = { keyField: 'key', nameField: 'name', folderIdField: 'categoryId', - createdDateField: 'createdDate', - createdNameField: 'createdByName', - lastmodDateField: 'lastSavedDate', - lastmodNameField: 'lastSavedByName', + createdDateField: 'createdDate', // only returned by upsert + createdNameField: 'createdByName', // only returned by upsert + lastmodDateField: 'lastSavedDate', // only returned by upsert + lastmodNameField: 'lastSavedByName', // only returned by upsert restPagination: true, maxKeyLength: 200, // confirmed max length + scheduleTypeMapping: { + MINUTELY: 1, + HOURLY: 2, + DAILY: 3, + WEEKLY: 4, + MONTHLY: 5, + }, statusMapping: { AwaitingTrigger: 7, Building: 1, @@ -336,31 +343,31 @@ module.exports = { lastSavedDate: { isCreateable: false, isUpdateable: false, - retrieving: true, + retrieving: false, // only returned by upsert template: false, }, lastSavedByName: { isCreateable: false, isUpdateable: false, - retrieving: true, + retrieving: false, // only returned by upsert template: false, }, createdDate: { isCreateable: false, isUpdateable: false, - retrieving: true, + retrieving: false, // only returned by upsert template: false, }, createdByName: { isCreateable: false, isUpdateable: false, - retrieving: true, + retrieving: false, // only returned by upsertt template: false, }, updateInProgress: { isCreateable: false, isUpdateable: false, - retrieving: true, + retrieving: false, template: false, }, name: { @@ -474,8 +481,8 @@ module.exports = { 'schedule.timezoneId': { isCreateable: true, isUpdateable: true, - retrieving: true, - template: true, + retrieving: false, + template: false, }, 'schedule.timezoneName': { isCreateable: true, @@ -484,10 +491,10 @@ module.exports = { template: true, }, 'schedule.typeId': { - isCreateable: true, - isUpdateable: true, - retrieving: true, - template: true, + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, }, status: { isCreateable: true, diff --git a/test/mockRoot/deploy/testInstance/testBU/automation/testExisting_automation.automation-meta.json b/test/mockRoot/deploy/testInstance/testBU/automation/testExisting_automation.automation-meta.json index b901a271a..0110ca3e6 100644 --- a/test/mockRoot/deploy/testInstance/testBU/automation/testExisting_automation.automation-meta.json +++ b/test/mockRoot/deploy/testInstance/testBU/automation/testExisting_automation.automation-meta.json @@ -7,8 +7,7 @@ "endDate": "2022-07-30T00:00:00", "icalRecur": "FREQ=DAILY;COUNT=1;INTERVAL=1", "startDate": "2022-07-30T00:00:00", - "timezoneName": "W. Europe Standard Time", - "typeId": 3 + "timezoneName": "W. Europe Standard Time" }, "status": "PausedSchedule", "steps": [ diff --git a/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json b/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json index 2a1caee12..715b69f27 100644 --- a/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json +++ b/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json @@ -4,13 +4,12 @@ "name": "testNew_automation", "r__folder_Path": "my automations", "schedule": { - "endDate": "2022-07-30T00:00:00", - "icalRecur": "FREQ=DAILY;COUNT=1;INTERVAL=1", - "startDate": "2022-07-30T00:00:00", - "timezoneName": "W. Europe Standard Time", - "typeId": 3 + "startDate": "2020-05-14T02:30:32.11", + "endDate": "2079-06-06T21:00:00", + "icalRecur": "FREQ=MINUTELY;UNTIL=20790607T050000;INTERVAL=5", + "timezoneName": "W. Europe Standard Time" }, - "status": "PausedSchedule", + "status": "Scheduled", "steps": [ { "activities": [ diff --git a/test/resourceFactory.js b/test/resourceFactory.js index a8100c963..766eced42 100644 --- a/test/resourceFactory.js +++ b/test/resourceFactory.js @@ -10,13 +10,14 @@ const attributeParser = new XMLParser({ ignoreAttributes: false }); * @param {string} mcdevAction SOAP action * @param {string} type metadata Type * @param {string} mid of Business Unit - * @param {object} filter likely for customer key + * @param {object|string} filter likely for customer key * @returns {string} relevant metadata stringified */ exports.loadSOAPRecords = async (mcdevAction, type, mid, filter) => { type = type[0].toLowerCase() + type.slice(1); const testPath = path.join('test', 'resources', mid.toString(), type, mcdevAction); - const filterPath = this.filterToPath(filter); + const filterPath = + typeof filter === 'string' && filter ? '-' + filter : this.filterToPath(filter); if (await fs.pathExists(testPath + filterPath + '-response.xml')) { return fs.readFile(testPath + filterPath + '-response.xml', { encoding: 'utf8', @@ -42,8 +43,8 @@ exports.loadSOAPRecords = async (mcdevAction, type, mid, filter) => { /* eslint-disable no-console */ console.log( `${color.bgRed}${color.fgBlack}test-error${color.reset}: Please create file ${ - testPath + filterPath + '-response.xml' - } or ${testPath + '-response.xml'}` + filterPath ? testPath + filterPath + '-response.xml or ' : '' + }${testPath + '-response.xml'}` ); /* eslint-enable no-console */ @@ -138,8 +139,20 @@ exports.handleSOAPRequest = async (config) => { break; } + case 'Schedule': { + responseXML = await this.loadSOAPRecords( + config.headers.SOAPAction.toLocaleLowerCase(), + fullObj.Envelope.Body.ScheduleRequestMsg.Interactions.Interaction['@_xsi:type'], + jObj.Envelope.Header.fueloauth, + fullObj.Envelope.Body.ScheduleRequestMsg.Interactions.Interaction.ObjectID + ); + + break; + } default: { - throw new Error('This SOAP Action is not supported by test handler'); + throw new Error( + `The SOAP Action ${config.headers.SOAPAction} is not supported by test handler` + ); } } diff --git a/test/resources/9999999/automation/build-expected.json b/test/resources/9999999/automation/build-expected.json index ecd94cb33..87301aa55 100644 --- a/test/resources/9999999/automation/build-expected.json +++ b/test/resources/9999999/automation/build-expected.json @@ -7,8 +7,7 @@ "endDate": "2022-07-30T00:00:00", "icalRecur": "FREQ=DAILY;COUNT=1;INTERVAL=1", "startDate": "2022-07-30T00:00:00", - "timezoneName": "W. Europe Standard Time", - "typeId": 3 + "timezoneName": "W. Europe Standard Time" }, "status": "PausedSchedule", "steps": [ diff --git a/test/resources/9999999/automation/create-expected.json b/test/resources/9999999/automation/create-expected.json index 2a1caee12..92ea826e1 100644 --- a/test/resources/9999999/automation/create-expected.json +++ b/test/resources/9999999/automation/create-expected.json @@ -3,14 +3,14 @@ "key": "testNew_automation", "name": "testNew_automation", "r__folder_Path": "my automations", + "type": "scheduled", + "status": "PausedSchedule", "schedule": { - "endDate": "2022-07-30T00:00:00", - "icalRecur": "FREQ=DAILY;COUNT=1;INTERVAL=1", - "startDate": "2022-07-30T00:00:00", - "timezoneName": "W. Europe Standard Time", - "typeId": 3 + "endDate": "2079-06-06T21:00:00-06:00", + "icalRecur": "FREQ=MINUTELY;UNTIL=20790607T050000;INTERVAL=5", + "startDate": "2020-05-13T18:30:32.11-06:00", + "timezoneName": "W. Europe Standard Time" }, - "status": "PausedSchedule", "steps": [ { "activities": [ @@ -41,6 +41,5 @@ ], "name": "" } - ], - "type": "scheduled" + ] } diff --git a/test/resources/9999999/automation/create-testNew_automation-expected.md b/test/resources/9999999/automation/create-testNew_automation-expected.md index d596cc549..c607175cc 100644 --- a/test/resources/9999999/automation/create-testNew_automation-expected.md +++ b/test/resources/9999999/automation/create-testNew_automation-expected.md @@ -10,10 +10,10 @@ **Schedule:** -* Start: 2022-07-30 00:00:00 +01:00 -* End: 2022-07-30 00:00:00 +01:00 -* Timezone: W. Europe Standard Time -* Recurrance: every day for 1 times +* Start: 2020-05-13 18:30:32.11-06:00 +01:00 +* End: 2079-06-06 21:00:00-06:00 +01:00 +* Timezone: W. Europe Standard Time +* Recurrance: every 5 minutes **Notifications:** _none_ diff --git a/test/resources/9999999/automation/retrieve-expected.json b/test/resources/9999999/automation/retrieve-expected.json index fa80b0f99..f4771678a 100644 --- a/test/resources/9999999/automation/retrieve-expected.json +++ b/test/resources/9999999/automation/retrieve-expected.json @@ -7,8 +7,7 @@ "endDate": "2022-07-30T00:00:00", "icalRecur": "FREQ=DAILY;COUNT=1;INTERVAL=1", "startDate": "2022-07-30T00:00:00", - "timezoneName": "W. Europe Standard Time", - "typeId": 3 + "timezoneName": "W. Europe Standard Time" }, "status": "PausedSchedule", "steps": [ diff --git a/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md b/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md index 22bd4c500..ae8f4c10a 100644 --- a/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md +++ b/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md @@ -12,7 +12,7 @@ * Start: 2022-07-30 00:00:00 +01:00 * End: 2022-07-30 00:00:00 +01:00 -* Timezone: W. Europe Standard Time +* Timezone: W. Europe Standard Time * Recurrance: every day for 1 times **Notifications:** diff --git a/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml b/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml new file mode 100644 index 000000000..55576611b --- /dev/null +++ b/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml @@ -0,0 +1,52 @@ + + + + ScheduleResponse + urn:uuid:b58a82d9-a251-4be4-b50c-bd300d71601d + urn:uuid:9d3dbeb3-68b7-4f0a-b0af-04855dc0fd1e + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + + + 2023-07-05T10:29:58Z + 2023-07-05T10:34:58Z + + + + + + + + OK + Program scheduled. + 3a677ca8-5423-4d59-a1bb-c5fbe1c87197 + + + + + Interval + 1 + + Daily + EndAfter + 2023-07-05T16:00:57 + 1 + + + 5 + + + + + + + OK + + cfe97488-3c5e-49a9-b338-c0e9f6b6f684 + + + \ No newline at end of file diff --git a/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-pause-response.xml b/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-pause-response.xml new file mode 100644 index 000000000..072b427e5 --- /dev/null +++ b/test/resources/9999999/automation/schedule-08afb0e2-b00a-4c88-ad2e-pause-response.xml @@ -0,0 +1,38 @@ + + + + ScheduleResponse + urn:uuid:b58a82d9-a251-4be4-b50c-bd300d71601d + urn:uuid:9d3dbeb3-68b7-4f0a-b0af-04855dc0fd1e + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + + + 2023-07-05T10:29:58Z + 2023-07-05T10:34:58Z + + + + + + + + OK + Schedule Paused. To reactivate the schedule use proxy.Schedule with the action 'start'. + + + + + + + + OK + + cfe97488-3c5e-49a9-b338-c0e9f6b6f684 + + + diff --git a/test/resources/9999999/automation/schedule-a8afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml b/test/resources/9999999/automation/schedule-a8afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml new file mode 100644 index 000000000..c0876dbef --- /dev/null +++ b/test/resources/9999999/automation/schedule-a8afb0e2-b00a-4c88-ad2e-1f7f8788c560-response.xml @@ -0,0 +1,52 @@ + + + + ScheduleResponse + urn:uuid:6c743640-b80f-456a-abed-b542247f2414 + urn:uuid:29fc6536-3c75-4e07-a05d-e63f7ee520d3 + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + + + 2023-07-04T13:13:15Z + 2023-07-04T13:18:15Z + + + + + + + + OK + Program scheduled. + 89cd977a-3927-49fa-b8e5-47c7e6093bef + + + + + Interval + 5 + + Minutely + EndOn + 2023-07-04T06:21:14.408 + 2079-06-06T20:00:00 + + + 5 + + + + + + + OK + + 3b03417b-649e-4a1d-963d-05f42fc8f1b5 + + + diff --git a/test/resources/9999999/automation/template-expected.json b/test/resources/9999999/automation/template-expected.json index 038ecc7ff..32a6bcbc5 100644 --- a/test/resources/9999999/automation/template-expected.json +++ b/test/resources/9999999/automation/template-expected.json @@ -7,8 +7,7 @@ "endDate": "2022-07-30T00:00:00", "icalRecur": "FREQ=DAILY;COUNT=1;INTERVAL=1", "startDate": "2022-07-30T00:00:00", - "timezoneName": "W. Europe Standard Time", - "typeId": 3 + "timezoneName": "W. Europe Standard Time" }, "status": "PausedSchedule", "steps": [ diff --git a/test/resources/9999999/automation/update-expected.json b/test/resources/9999999/automation/update-expected.json index 47860e944..0705285b5 100644 --- a/test/resources/9999999/automation/update-expected.json +++ b/test/resources/9999999/automation/update-expected.json @@ -7,8 +7,7 @@ "endDate": "2022-07-30T00:00:00", "icalRecur": "FREQ=DAILY;COUNT=1;INTERVAL=1", "startDate": "2022-07-30T00:00:00", - "timezoneName": "W. Europe Standard Time", - "typeId": 3 + "timezoneName": "W. Europe Standard Time" }, "status": "PausedSchedule", "steps": [ diff --git a/test/resources/9999999/automation/update-testExisting_automation-expected.md b/test/resources/9999999/automation/update-testExisting_automation-expected.md index 681c2a694..fbee3558c 100644 --- a/test/resources/9999999/automation/update-testExisting_automation-expected.md +++ b/test/resources/9999999/automation/update-testExisting_automation-expected.md @@ -12,7 +12,7 @@ * Start: 2022-07-30 00:00:00 +01:00 * End: 2022-07-30 00:00:00 +01:00 -* Timezone: W. Europe Standard Time +* Timezone: W. Europe Standard Time * Recurrance: every day for 1 times **Notifications:** _none_ diff --git a/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-pause/get-response.json b/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-pause/get-response.json new file mode 100644 index 000000000..58ec6b51c --- /dev/null +++ b/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-pause/get-response.json @@ -0,0 +1,85 @@ +{ + "id": "08afb0e2-b00a-4c88-ad2e-pause", + "name": "testExisting_automation_pause", + "description": "bla bla", + "key": "testExisting_automation_pause", + "typeId": 1, + "type": "scheduled", + "statusId": 4, + "status": "Scheduled", + "categoryId": 290937, + "schedule": { + "id": "b393aa6c-a4a8-4c0f-a148-9250258a7339", + "typeId": 3, + "startDate": "2022-07-30T00:00:00", + "endDate": "2022-07-30T00:00:00", + "scheduledTime": "0001-01-01T07:00:00", + "rangeTypeId": 0, + "occurrences": 1, + "pattern": "01", + "icalRecur": "FREQ=DAILY;COUNT=1;INTERVAL=1", + "timezoneName": "W. Europe Standard Time", + "scheduleStatus": "scheduled", + "timezoneId": 5 + }, + "steps": [ + { + "id": "13fda077-0e82-4936-b936-a36b0997fc44", + "name": "", + "step": 1, + "activities": [ + { + "id": "8081a992-a27d-4a43-984a-d60114ea1025", + "name": "testExisting_dataExtract", + "activityObjectId": "56c5370a-f988-4f36-b0ee-0f876573f6d7", + "objectTypeId": 73, + "displayOrder": 1 + }, + { + "id": "d3774dc2-a271-4a44-8cbe-f630a6d6545e", + "name": "testExisting_emailSend", + "activityObjectId": "9b1c7bf9-4964-ed11-b849-48df37d1de8b", + "objectTypeId": 42, + "displayOrder": 2 + }, + { + "id": "2c77fc42-85eb-4611-98f9-223d29d89d72", + "name": "testExisting_fileTransfer", + "activityObjectId": "72c328ac-f5b0-4e37-91d3-a775666f15a6", + "objectTypeId": 53, + "displayOrder": 3 + }, + { + "id": "298b2794-28cb-4c70-b7ad-58b2c8cf48f7", + "name": "testExisting_importFile", + "activityObjectId": "9d16f42c-2260-ed11-b849-48df37d1de8b", + "objectTypeId": 43, + "displayOrder": 4, + "targetDataExtensions": [ + { + "id": "21711373-72c1-ec11-b83b-48df37d1deb7", + "name": "testExisting_dataExtension", + "key": "testExisting_dataExtension", + "description": "bla bla", + "rowCount": 0 + } + ] + }, + { + "id": "e3774dc2-a271-4a44-8cbe-f630a6d6545e", + "name": "testExisting_query_WRONG_NAME", + "activityObjectId": "549f0568-607c-4940-afef-437965094dat", + "objectTypeId": 300, + "displayOrder": 5 + }, + { + "id": "g3774dc2-a271-4a44-8cbe-f630a6d6545e", + "name": "testExisting_script", + "activityObjectId": "39f6a488-20eb-4ba0-b0b9-023725b574e4", + "objectTypeId": 423, + "displayOrder": 6 + } + ] + } + ] +} diff --git a/test/resources/9999999/automation/v1/automations/a8afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json b/test/resources/9999999/automation/v1/automations/a8afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json index b58d358fb..0f20856b9 100644 --- a/test/resources/9999999/automation/v1/automations/a8afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json +++ b/test/resources/9999999/automation/v1/automations/a8afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json @@ -6,7 +6,7 @@ "typeId": 1, "type": "scheduled", "statusId": 4, - "status": "PausedSchedule", + "status": "Scheduled", "categoryId": 290937, "schedule": { "id": "b393aa6c-a4a8-4c0f-a148-9250258a7339", diff --git a/test/resources/9999999/automation/v1/automations/post-response.json b/test/resources/9999999/automation/v1/automations/post-response.json index 6f82ef7c9..ba11ebf1e 100644 --- a/test/resources/9999999/automation/v1/automations/post-response.json +++ b/test/resources/9999999/automation/v1/automations/post-response.json @@ -1,25 +1,28 @@ { + "id": "a8afb0e2-b00a-4c88-ad2e-1f7f8788c560", "legacyId": "NewRkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow", "name": "testNew_automation", "description": "created on deploy", "key": "testNew_automation", - "typeId": 1, - "type": "scheduled", + "categoryId": 290937, "statusId": 4, - "status": "PausedSchedule", - "schedule": { - "id": "b393aa6c-a4a8-4c0f-a148-9250258a7339", - "typeId": 3, - "startDate": "2022-07-30T00:00:00", - "endDate": "2022-07-30T00:00:00", - "scheduledTime": "0001-01-01T07:00:00", - "rangeTypeId": 0, - "occurrences": 1, - "pattern": "01", - "icalRecur": "FREQ=DAILY;COUNT=1;INTERVAL=1", - "timezoneName": "W. Europe Standard Time", - "scheduleStatus": "paused", - "timezoneId": 5 + "lastSavedDate": "2023-07-04T07:49:08.577", + "lastSavedByName": "SFMC DevTools app user", + "createdDate": "2023-07-04T07:49:08.297", + "createdByName": "SFMC DevTools app user", + "updateInProgress": false, + "startSource": { + "typeId": 1, + "schedule": { + "scheduleTypeId": 1, + "startDate": "2020-05-13T18:30:32.11-06:00", + "endDate": "2079-06-06T21:00:00-06:00", + "rangeTypeId": 1, + "occurrences": 6213054, + "icalRecur": "FREQ=MINUTELY;UNTIL=20790607T050000;INTERVAL=5", + "timezoneId": 5, + "statusId": 0 + } }, "steps": [ { @@ -79,7 +82,5 @@ "annotation": "", "stepNumber": 0 } - ], - "categoryId": 290937, - "id": "a8afb0e2-b00a-4c88-ad2e-1f7f8788c560" + ] } diff --git a/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_pause-response.xml b/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_pause-response.xml new file mode 100644 index 000000000..6d33a7bfc --- /dev/null +++ b/test/resources/9999999/program/retrieve-CustomerKey=testExisting_automation_pause-response.xml @@ -0,0 +1,30 @@ + + + + RetrieveResponse + urn:uuid:60a72d4a-847e-4d9b-a4eb-a42951078298 + urn:uuid:0b59ed53-72ec-4481-ae06-4ee78912aef2 + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + + + 2023-06-01T12:04:20Z + 2023-06-01T12:09:20Z + + + + + + OK + 3b1c8cee-b270-49cb-b77b-e7b33934d1b6 + + + 08afb0e2-b00a-4c88-ad2e-pause + + + + diff --git a/test/resources/9999999/program/retrieve-response.xml b/test/resources/9999999/program/retrieve-response.xml index 67ede7785..bcb9045c8 100644 --- a/test/resources/9999999/program/retrieve-response.xml +++ b/test/resources/9999999/program/retrieve-response.xml @@ -24,9 +24,15 @@ 08afb0e2-b00a-4c88-ad2e-1f7f8788c560 - testExisting_automation - testExisting_automation + testExisting_automation + testExisting_automation + + + + 08afb0e2-b00a-4c88-ad2e-pause + testExisting_automation_pause + testExisting_automation_pause - \ No newline at end of file + diff --git a/test/type.automation.test.js b/test/type.automation.test.js index 5f9dd60bb..531b6b802 100644 --- a/test/type.automation.test.js +++ b/test/type.automation.test.js @@ -27,8 +27,8 @@ describe('type: automation', () => { const result = cache.getCache(); assert.equal( result.automation ? Object.keys(result.automation).length : 0, - 1, - 'only one automation expected' + 2, + 'only two automations expected' ); assert.deepEqual( await testUtils.getActualJson('testExisting_automation', 'automation'), @@ -50,7 +50,7 @@ describe('type: automation', () => { assert.equal( testUtils.getAPIHistoryLength(), - 14, + 15, 'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests' ); return; @@ -70,8 +70,8 @@ describe('type: automation', () => { const result = cache.getCache(); assert.equal( result.automation ? Object.keys(result.automation).length : 0, - 2, - 'two automations expected' + 3, + 'three automations expected' ); // insert assert.deepEqual( @@ -111,7 +111,69 @@ describe('type: automation', () => { assert.equal( testUtils.getAPIHistoryLength(), - 15, + 16, + 'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests' + ); + return; + }); + it('Should update & start an automation with --execute option', async () => { + // WHEN + handler.setOptions({ execute: true }); + const deployed = await handler.deploy( + 'testInstance/testBU', + ['automation'], + ['testExisting_automation'] + ); + // THEN + assert.equal( + process.exitCode, + false, + 'deploy with --execute should not have thrown an error' + ); + + // get results from cache + const cached = cache.getCache(); + assert.equal( + cached.automation ? Object.keys(cached.automation).length : 0, + 2, + 'two cached automation expected' + ); + assert.equal( + deployed['testInstance/testBU'].automation + ? Object.keys(deployed['testInstance/testBU'].automation).length + : 0, + 1, + 'one deployed automation expected' + ); + assert.equal( + deployed['testInstance/testBU'].automation + ? Object.keys(deployed['testInstance/testBU'].automation)[0] + : null, + 'testExisting_automation', + 'expected specific automation to have been deployed' + ); + + // update + assert.deepEqual( + await testUtils.getActualJson('testExisting_automation', 'automation'), + await testUtils.getExpectedJson('9999999', 'automation', 'update'), + 'returned metadata was not equal expected for update' + ); + // check if MD file was created and equals expectations + expect(file(testUtils.getActualDoc('testExisting_automation', 'automation'))).to.equal( + file( + testUtils.getExpectedFile( + '9999999', + 'automation', + 'update-testExisting_automation', + 'md' + ) + ) + ); + + assert.equal( + testUtils.getAPIHistoryLength(), + 19, 'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests' ); return; @@ -210,7 +272,7 @@ describe('type: automation', () => { ); assert.equal( testUtils.getAPIHistoryLength(), - 14, + 15, 'Unexpected number of requests made. Run testUtils.logAPIHistoryDebug() to see the requests' ); return; @@ -256,4 +318,56 @@ describe('type: automation', () => { return; }); }); + describe('Execute ================', () => { + it('Should start an automation by key', async () => { + const execute = await handler.execute('testInstance/testBU', 'automation', [ + 'testExisting_automation', + ]); + assert.equal(process.exitCode, false, 'execute should not have thrown an error'); + assert.equal(execute, true, 'automation was supposed to be executed'); + return; + }); + it('Should start an automation selected via --like', async () => { + handler.setOptions({ like: { key: 'testExist%automation' } }); + const execute = await handler.execute('testInstance/testBU', 'automation'); + assert.equal(process.exitCode, false, 'execute should not have thrown an error'); + assert.equal(execute, true, 'automation was supposed to be executed'); + return; + }); + it('Should not start executing an automation because key and --like was specified', async () => { + handler.setOptions({ like: { key: 'testExisting%' } }); + const execute = await handler.execute('testInstance/testBU', 'automation', [ + 'testExisting_automation', + ]); + assert.equal(process.exitCode, true, 'execute should not have thrown an error'); + assert.equal(execute, false, 'automation was not supposed to be executed'); + return; + }); + }); + describe('Pause ================', () => { + it('Should pause a automation by key', async () => { + const pause = await handler.pause('testInstance/testBU', 'automation', [ + 'testExisting_automation_pause', + ]); + assert.equal(process.exitCode, false, 'pause should not have thrown an error'); + assert.equal(pause, true, 'automation was supposed to be paused'); + return; + }); + it('Should pause a automation selected via --like', async () => { + handler.setOptions({ like: { key: 'testExisting_a%n_pause' } }); + const pause = await handler.pause('testInstance/testBU', 'automation'); + assert.equal(process.exitCode, false, 'pause should not have thrown an error'); + assert.equal(pause, true, 'automation was supposed to be paused'); + return; + }); + it('Should not pause executing a automation because key and --like was specified', async () => { + handler.setOptions({ like: { key: 'testExisting_a%n_pause' } }); + const pause = await handler.pause('testInstance/testBU', 'automation', [ + 'testExisting_automation_pause', + ]); + assert.equal(process.exitCode, true, 'pause should not have thrown an error'); + assert.equal(pause, false, 'automation was not supposed to be paused'); + return; + }); + }); });