diff --git a/.coverage-comment-template.md b/.coverage-comment-template.md deleted file mode 100644 index 4aceff38b..000000000 --- a/.coverage-comment-template.md +++ /dev/null @@ -1,20 +0,0 @@ -## Coverage Report - -Commit: [{{short_commit_sha}}]({{commit_link}}) -Base: [{{base_ref}}@{{base_short_commit_sha}}]({{base_commit_link}}) - -| Type | Base | This PR | -| ------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------- | -| Total Statements Coverage | {{base_total_statements_coverage_percent}} | {{total_statements_coverage_percent}} ({{total_statements_coverage_percent_diff}}) | -| Total Branches Coverage | {{base_total_branches_coverage_percent}} | {{total_branches_coverage_percent}} ({{total_branches_coverage_percent_diff}}) | -| Total Functions Coverage | {{base_total_functions_coverage_percent}} | {{total_functions_coverage_percent}} ({{total_functions_coverage_percent_diff}}) | -| Total Lines Coverage | {{base_total_lines_coverage_percent}} | {{total_lines_coverage_percent}} ({{total_lines_coverage_percent_diff}}) | - -
-Details (changed files) -{{changed_files_coverage_table}} -
-
-Details (all files) -{{files_coverage_table}} -
diff --git a/.coverage-comment-template.svelte b/.coverage-comment-template.svelte index 8455cd02f..ae2267b27 100644 --- a/.coverage-comment-template.svelte +++ b/.coverage-comment-template.svelte @@ -1,178 +1,194 @@

Coverage Report

-Commit: {short_commit_sha}
-Base: {base_ref}@{base_short_commit_sha}

+Commit:{short_commit_sha}
+Base: {base_ref}@{base_short_commit_sha}

- - - {#if has_base_data} - - {/if} - - - - {#each summary_list as {type, percent}} - - + + {#if has_base_data} - + {/if} - - - {/each} - -
TypeBaseThis PR
{type}
Type - {#if Number.isFinite(percent.base)} -  {percent.base}% - {:else} - - - {/if} - Base - {#if Number.isFinite(percent.total)} -  {percent.total}% - {#if has_base_data} -  ({formatPercentDiff(percent.diff)}) - {/if} - {:else} - - - {/if} -
- -
- Details (changed testable files):
- - - - - - - + - {#each changed_files_coverage_data as [file, data]} - {@const percents = [ - data.statements.pct, - data.branches.pct, - data.functions.pct, - data.lines.pct - ]} - - - {#each percents as percent} - - {/each} - - {/each} + {#each summary_list as { type, percent }} + + + {#if has_base_data} + + {/if} + + + {/each} -
FileStatementsBranchesFunctionsLinesThis PR
- {file} - - {#if Number.isFinite(percent)} -  {percent}% - {:else} - - - {/if} -
{type} + {#if Number.isFinite(percent.base)} +  {percent.base}% + {:else} + - + {/if} + + {#if Number.isFinite(percent.total)} +  {percent.total}% + {#if has_base_data} +  ({formatPercentDiff(percent.diff)}) + {/if} + {:else} + - + {/if} +
-
\ No newline at end of file + + +
+ Details (changed files):
+ + + + + + + + + + {#each changed_files_coverage_data as [file, data]} + {@const percents = [ + data.statements.pct, + data.branches.pct, + data.functions.pct, + data.lines.pct, + ]} + + + {#each percents as percent} + + {/each} + + {/each} + +
FileStatementsBranchesFunctionsLines
+ {file} + + {#if Number.isFinite(percent)} +  {percent}% + {:else} + - + {/if} +
+
diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index bca928014..c133ed59c 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -39,6 +39,7 @@ body: label: Version description: What version of our software are you running? (mcdev --version) options: + - 5.1.0 - 5.0.2 - 5.0.1 - 5.0.0 diff --git a/.github/workflows/coverage-base-update.yml b/.github/workflows/coverage-base-update.yml index 87dd0f1b4..a84e002d2 100644 --- a/.github/workflows/coverage-base-update.yml +++ b/.github/workflows/coverage-base-update.yml @@ -1,8 +1,12 @@ -name: Update coverage comment +name: Update coverage comment # base-update.yml on: pull_request: types: [edited] +permissions: + # allow dependabot to execute this workflow + pull-requests: write + jobs: hello_world_job: runs-on: ubuntu-latest @@ -48,7 +52,7 @@ jobs: files: 'base-artifacts/coverage-summary.json, artifacts/coverage-summary.json' - name: Update Coverage comment - uses: sidx1024/report-nyc-coverage-github-action@v1.2.6 + uses: sidx1024/report-nyc-coverage-github-action@v1.2.7 if: steps.check_files.outputs.files_exists == 'true' # Only runs if all of the files exists with: diff --git a/.github/workflows/coverage-develop-branch.yml b/.github/workflows/coverage-develop-branch.yml index 9ace0197d..d14c5fbe3 100644 --- a/.github/workflows/coverage-develop-branch.yml +++ b/.github/workflows/coverage-develop-branch.yml @@ -1,24 +1,25 @@ -name: Test coverage for develop branch +name: Test coverage for develop branch # default-branch.yml on: push: branches: - develop +permissions: + # allow dependabot to execute this workflow + pull-requests: write + jobs: hello_world_job: runs-on: ubuntu-latest - name: Test and report + name: Test and upload coverage steps: - name: Checkout uses: actions/checkout@v3 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.ref }} fetch-depth: 1000 - - name: Fetch base - run: git fetch origin ${{ github.event.pull_request.base.ref }} --depth=1000 - - uses: actions/setup-node@v3 with: node-version: 16 diff --git a/.github/workflows/coverage-main-branch.yml b/.github/workflows/coverage-main-branch.yml index ce40479d9..9aa2b1ff3 100644 --- a/.github/workflows/coverage-main-branch.yml +++ b/.github/workflows/coverage-main-branch.yml @@ -1,24 +1,25 @@ -name: Test coverage for main branch +name: Test coverage for main branch # default-branch.yml on: push: branches: - main +permissions: + # allow dependabot to execute this workflow + pull-requests: write + jobs: hello_world_job: runs-on: ubuntu-latest - name: Test and report + name: Test and upload coverage steps: - name: Checkout uses: actions/checkout@v3 with: - ref: ${{ github.event.pull_request.head.sha }} + ref: ${{ github.event.ref }} fetch-depth: 1000 - - name: Fetch base - run: git fetch origin ${{ github.event.pull_request.base.ref }} --depth=1000 - - uses: actions/setup-node@v3 with: node-version: 16 diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 9ea664a6b..82d75fb38 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -1,6 +1,11 @@ on: [pull_request] -name: Test coverage report +name: Test coverage report # main.yml + +permissions: + # allow dependabot to execute this workflow + pull-requests: write + jobs: hello_world_job: runs-on: ubuntu-latest @@ -69,7 +74,7 @@ jobs: files: 'base-artifacts/coverage-summary.json' - name: Report coverage - uses: sidx1024/report-nyc-coverage-github-action@v1.2.6 + uses: sidx1024/report-nyc-coverage-github-action@v1.2.7 id: report with: coverage_file: 'artifacts/test-coverage-output/coverage-summary.json' diff --git a/.husky/post-checkout b/.husky/post-checkout index e2201380c..2607e40f5 100644 --- a/.husky/post-checkout +++ b/.husky/post-checkout @@ -10,7 +10,8 @@ then fi TEMPLATE="#$TICKETID: " echo "[POST_CHECKOUT] Setting template commit to '$TEMPLATE'" -echo $TEMPLATE > ".git/templatemessage" +# wrap $TEMPLATE in quotes or else it is trimmed automatically +echo "$TEMPLATE" > ".git/templatemessage" # ### run npm install ### @@ -21,7 +22,7 @@ IFS=$'\n' NEWHEAD=$1 # $2 is the previous HEAD pointer OLDHEAD=$2 -# extract all paths to package-lock.json files +# extract all paths to package-lock.json files PACKAGE_LOCK_REGEX="(^package-lock\.json)" PACKAGES=$(git diff --name-only $NEWHEAD $OLDHEAD | grep -E $PACKAGE_LOCK_REGEX || true) diff --git a/docs/dist/documentation.md b/docs/dist/documentation.md index d9363cd3c..8027e7538 100644 --- a/docs/dist/documentation.md +++ b/docs/dist/documentation.md @@ -186,17 +186,17 @@ 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

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

helper for postDeployTasks

+
+
Automation.(metadataMap, originalMetadataMap, key)
+

helper for postDeployTasks

+
getUserName(userList, item, fieldname)string
setupSDK(sessionKey, authObject)SDK

Returns an SDK instance to be used for API calls

-
createNewLoggerTransport()object
-

wrapper around our standard winston logging to console and logfile

-
-
startLogger()void
-

initiate winston logger

-
## Typedefs @@ -496,6 +496,8 @@ main class * [.buildDefinition(businessUnit, selectedType, name, market)](#Mcdev.buildDefinition) ⇒ Promise.<void> * [.buildDefinitionBulk(listName, type, name)](#Mcdev.buildDefinitionBulk) ⇒ Promise.<void> * [.getFilesToCommit(businessUnit, selectedType, keyArr)](#Mcdev.getFilesToCommit) ⇒ Promise.<Array.<string>> + * [.execute(businessUnit, [selectedTypesArr], keys)](#Mcdev.execute) ⇒ Promise.<boolean> + * [._executeBU(cred, bu, [selectedTypesArr], keyArr)](#Mcdev._executeBU) ⇒ Promise.<boolean> @@ -748,6 +750,35 @@ Build a specific metadata file based on a template using a list of bu-market com | selectedType | string | supported metadata type | | keyArr | Array.<string> | customerkey of the metadata | + + +### Mcdev.execute(businessUnit, [selectedTypesArr], keys) ⇒ Promise.<boolean> +Start an item (query) + +**Kind**: static method of [Mcdev](#Mcdev) +**Returns**: Promise.<boolean> - true if all started successfully, false if not + +| Param | Type | Description | +| --- | --- | --- | +| businessUnit | string | name of BU | +| [selectedTypesArr] | Array.<TYPE.SupportedMetadataTypes> | limit to given metadata types | +| keys | Array.<string> | customerkey of the metadata | + + + +### Mcdev.\_executeBU(cred, bu, [selectedTypesArr], keyArr) ⇒ Promise.<boolean> +helper for [execute](execute) + +**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 | +| [selectedTypesArr] | Array.<TYPE.SupportedMetadataTypes> | limit execution to given metadata type | +| keyArr | Array.<string> | customerkey of the metadata | + ## Asset ⇐ [MetadataType](#MetadataType) @@ -1206,7 +1237,7 @@ Automation MetadataType * [.retrieveChangelog()](#Automation.retrieveChangelog) ⇒ Promise.<TYPE.AutomationMapObj> * [.retrieveForCache()](#Automation.retrieveForCache) ⇒ Promise.<TYPE.AutomationMapObj> * [.retrieveAsTemplate(templateDir, name, templateVariables)](#Automation.retrieveAsTemplate) ⇒ Promise.<TYPE.AutomationItemObj> - * [.postRetrieveTasks(metadata)](#Automation.postRetrieveTasks) ⇒ TYPE.AutomationItem + * [.postRetrieveTasks(metadata)](#Automation.postRetrieveTasks) ⇒ TYPE.AutomationItem \| void * [.deploy(metadata, targetBU, retrieveDir, [isRefresh])](#Automation.deploy) ⇒ Promise.<TYPE.AutomationMap> * [.create(metadata)](#Automation.create) ⇒ Promise * [.update(metadata, metadataBefore)](#Automation.update) ⇒ Promise @@ -1215,7 +1246,6 @@ Automation MetadataType * [.postDeployTasks(metadataMap, originalMetadataMap)](#Automation.postDeployTasks) ⇒ Promise.<void> * [.setFolderPath(metadata)](#Automation.setFolderPath) * [.setFolderId(metadata)](#Automation.setFolderId) - * [.parseMetadata(metadata)](#Automation.parseMetadata) ⇒ TYPE.AutomationItem \| void * [._buildSchedule(scheduleObject)](#Automation._buildSchedule) ⇒ TYPE.AutomationScheduleSoap * [._calcTime(offsetServer, dateInput, [offsetInput])](#Automation._calcTime) ⇒ string * [.document([metadata])](#Automation.document) ⇒ Promise.<void> @@ -1268,11 +1298,11 @@ Retrieve a specific Automation Definition by Name -### Automation.postRetrieveTasks(metadata) ⇒ TYPE.AutomationItem +### Automation.postRetrieveTasks(metadata) ⇒ TYPE.AutomationItem \| void manages post retrieve steps **Kind**: static method of [Automation](#Automation) -**Returns**: TYPE.AutomationItem - metadata +**Returns**: TYPE.AutomationItem \| void - parsed item | Param | Type | Description | | --- | --- | --- | @@ -1378,18 +1408,6 @@ automation-specific script that retrieves the folder ID from cache and updates t | --- | --- | --- | | metadata | TYPE.MetadataTypeItem | a single item | - - -### Automation.parseMetadata(metadata) ⇒ TYPE.AutomationItem \| void -parses retrieved Metadata before saving - -**Kind**: static method of [Automation](#Automation) -**Returns**: TYPE.AutomationItem \| void - parsed item - -| Param | Type | Description | -| --- | --- | --- | -| metadata | TYPE.AutomationItem | a single automation definition | - ### Automation.\_buildSchedule(scheduleObject) ⇒ TYPE.AutomationScheduleSoap @@ -3155,6 +3173,7 @@ Provides default functionality that can be overwritten by child metadata type cl * [.create(metadata, deployDir)](#MetadataType.create) ⇒ void * [.update(metadata, [metadataBefore])](#MetadataType.update) ⇒ void * [.refresh()](#MetadataType.refresh) ⇒ void + * [.execute()](#MetadataType.execute) ⇒ void * [.hasChanged(cachedVersion, metadata, [fieldName])](#MetadataType.hasChanged) ⇒ boolean * [.hasChangedGeneric(cachedVersion, metadata, [fieldName], [silent])](#MetadataType.hasChangedGeneric) ⇒ boolean * [.upsert(metadataMap, deployDir, [isRefresh])](#MetadataType.upsert) ⇒ Promise.<TYPE.MetadataTypeMap> @@ -3167,6 +3186,7 @@ Provides default functionality that can be overwritten by child metadata type cl * [.getSOAPErrorMsg(ex)](#MetadataType.getSOAPErrorMsg) ⇒ string * [.retrieveSOAP(retrieveDir, [requestParams], [singleRetrieve], [additionalFields])](#MetadataType.retrieveSOAP) ⇒ Promise.<TYPE.MetadataTypeMapObj> * [.retrieveREST(retrieveDir, uri, [templateVariables], [singleRetrieve])](#MetadataType.retrieveREST) ⇒ Promise.<{metadata: (TYPE.MetadataTypeMap\|TYPE.MetadataTypeItem), type: string}> + * [.executeREST(uri, key)](#MetadataType.executeREST) ⇒ Promise.<string> * [.runDocumentOnRetrieve([singleRetrieve], metadataMap)](#MetadataType.runDocumentOnRetrieve) ⇒ Promise.<void> * [.parseResponseBody(body, [singleRetrieve])](#MetadataType.parseResponseBody) ⇒ TYPE.MetadataTypeMap * [.deleteFieldByDefinition(metadataEntry, fieldPath, definitionProperty, origin)](#MetadataType.deleteFieldByDefinition) ⇒ void @@ -3466,6 +3486,12 @@ Abstract update method that needs to be implemented in child metadata type ### MetadataType.refresh() ⇒ void Abstract refresh method that needs to be implemented in child metadata type +**Kind**: static method of [MetadataType](#MetadataType) + + +### MetadataType.execute() ⇒ void +Abstract execute method that needs to be implemented in child metadata type + **Kind**: static method of [MetadataType](#MetadataType) @@ -3633,6 +3659,19 @@ Retrieves Metadata for Rest Types | [templateVariables] | TYPE.TemplateMap | variables to be replaced in the metadata | | [singleRetrieve] | string \| number | key of single item to filter by | + + +### MetadataType.executeREST(uri, key) ⇒ Promise.<string> +Used to execute a query/automation etc. + +**Kind**: static method of [MetadataType](#MetadataType) +**Returns**: Promise.<string> - 'OK' if started execution successfully, otherwise - 'Error' + +| Param | Type | Description | +| --- | --- | --- | +| uri | string | REST endpoint where the POST request should be sent | +| key | string | item key | + ### MetadataType.runDocumentOnRetrieve([singleRetrieve], metadataMap) ⇒ Promise.<void> @@ -4548,6 +4587,7 @@ Query MetadataType * [Query](#Query) ⇐ [MetadataType](#MetadataType) * [.retrieve(retrieveDir, [_], [__], [key])](#Query.retrieve) ⇒ Promise.<{metadata: TYPE.QueryMap, type: string}> + * [.execute(keyArr)](#Query.execute) ⇒ Promise.<boolean> * [.retrieveForCache()](#Query.retrieveForCache) ⇒ Promise.<{metadata: TYPE.QueryMap, type: string}> * [.retrieveAsTemplate(templateDir, name, templateVariables)](#Query.retrieveAsTemplate) ⇒ Promise.<{metadata: Query, type: string}> * [.postRetrieveTasks(metadata)](#Query.postRetrieveTasks) ⇒ TYPE.CodeExtractItem @@ -4577,6 +4617,18 @@ Retrieves Metadata of queries | [__] | void | unused parameter | | [key] | string | customer key of single item to retrieve | + + +### Query.execute(keyArr) ⇒ Promise.<boolean> +a function to start query execution via API + +**Kind**: static method of [Query](#Query) +**Returns**: Promise.<boolean> - Returns true if all items were executed successfully, otherwise false + +| Param | Type | Description | +| --- | --- | --- | +| keyArr | Array.<string> | customerkey of the metadata | + ### Query.retrieveForCache() ⇒ Promise.<{metadata: TYPE.QueryMap, type: string}> @@ -5833,19 +5885,21 @@ CLI entry for SFMC DevTools * [.includesStartsWith(arr, search)](#Util.includesStartsWith) ⇒ boolean * [.includesStartsWithIndex(arr, search)](#Util.includesStartsWithIndex) ⇒ number * [.checkMarket(market, properties)](#Util.checkMarket) ⇒ boolean - * [.verifyMarketList(mlName, properties)](#Util.verifyMarketList) ⇒ void + * [.verifyMarketList(mlName, properties)](#Util.verifyMarketList) * [.signalFatalError()](#Util.signalFatalError) ⇒ void * [.isTrue(attrValue)](#Util.isTrue) ⇒ boolean * [.isFalse(attrValue)](#Util.isFalse) ⇒ boolean * [._isValidType(selectedType, [handleOutside])](#Util._isValidType) ⇒ boolean * [.getTypeAndSubType(selectedType)](#Util.getTypeAndSubType) ⇒ Array.<string> * [.getRetrieveTypeChoices()](#Util.getRetrieveTypeChoices) ⇒ Array.<TYPE.SupportedMetadataTypes> + * [._createNewLoggerTransport([noLogFile])](#Util._createNewLoggerTransport) ⇒ object + * [.startLogger([restart], [noLogFile])](#Util.startLogger) ⇒ void * [.metadataLogger(level, type, method, payload, [source])](#Util.metadataLogger) ⇒ void * [.replaceByObject(str, obj)](#Util.replaceByObject) ⇒ string \| object * [.inverseGet(objs, val)](#Util.inverseGet) ⇒ string * [.getMetadataHierachy(metadataTypes)](#Util.getMetadataHierachy) ⇒ Object.<string, Array.<string>> * [.resolveObjPath(path, obj)](#Util.resolveObjPath) ⇒ any - * [.execSync(cmd, [args], [hideOutput])](#Util.execSync) ⇒ string + * [.execSync(cmd, [args], [hideOutput])](#Util.execSync) ⇒ string \| void * [.templateSearchResult(results, keyToSearch, searchValue)](#Util.templateSearchResult) ⇒ TYPE.MetadataTypeItem * [.setLoggingLevel(argv)](#Util.setLoggingLevel) ⇒ void * [.logBeta(type)](#Util.logBeta) @@ -5919,11 +5973,10 @@ check if a market name exists in current mcdev config -### Util.verifyMarketList(mlName, properties) ⇒ void +### Util.verifyMarketList(mlName, properties) ensure provided MarketList exists and it's content including markets and BUs checks out **Kind**: static method of [Util](#Util) -**Returns**: void - throws errors if problems were found | Param | Type | Description | | --- | --- | --- | @@ -5992,6 +6045,30 @@ helper for getDefaultProperties() **Kind**: static method of [Util](#Util) **Returns**: Array.<TYPE.SupportedMetadataTypes> - type choices + + +### Util.\_createNewLoggerTransport([noLogFile]) ⇒ object +wrapper around our standard winston logging to console and logfile + +**Kind**: static method of [Util](#Util) +**Returns**: object - initiated logger for console and file + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| [noLogFile] | boolean | false | optional flag to indicate if we should log to file; CLI logs are always on | + + + +### Util.startLogger([restart], [noLogFile]) ⇒ void +initiate winston logger + +**Kind**: static method of [Util](#Util) + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| [restart] | boolean | false | if true, logger will be restarted; otherwise, an existing logger will be used | +| [noLogFile] | boolean | false | if false, logger will log to file; otherwise, only to console | + ### Util.metadataLogger(level, type, method, payload, [source]) ⇒ void @@ -6061,11 +6138,11 @@ let's you dynamically walk down an object and get a value -### Util.execSync(cmd, [args], [hideOutput]) ⇒ string +### Util.execSync(cmd, [args], [hideOutput]) ⇒ string \| void helper to run other commands as if run manually by user **Kind**: static method of [Util](#Util) -**Returns**: string - output of command if hideOutput is true +**Returns**: string \| void - output of command if hideOutput is true | Param | Type | Description | | --- | --- | --- | @@ -7698,19 +7775,21 @@ Util that contains logger and simple util methods * [.includesStartsWith(arr, search)](#Util.includesStartsWith) ⇒ boolean * [.includesStartsWithIndex(arr, search)](#Util.includesStartsWithIndex) ⇒ number * [.checkMarket(market, properties)](#Util.checkMarket) ⇒ boolean - * [.verifyMarketList(mlName, properties)](#Util.verifyMarketList) ⇒ void + * [.verifyMarketList(mlName, properties)](#Util.verifyMarketList) * [.signalFatalError()](#Util.signalFatalError) ⇒ void * [.isTrue(attrValue)](#Util.isTrue) ⇒ boolean * [.isFalse(attrValue)](#Util.isFalse) ⇒ boolean * [._isValidType(selectedType, [handleOutside])](#Util._isValidType) ⇒ boolean * [.getTypeAndSubType(selectedType)](#Util.getTypeAndSubType) ⇒ Array.<string> * [.getRetrieveTypeChoices()](#Util.getRetrieveTypeChoices) ⇒ Array.<TYPE.SupportedMetadataTypes> + * [._createNewLoggerTransport([noLogFile])](#Util._createNewLoggerTransport) ⇒ object + * [.startLogger([restart], [noLogFile])](#Util.startLogger) ⇒ void * [.metadataLogger(level, type, method, payload, [source])](#Util.metadataLogger) ⇒ void * [.replaceByObject(str, obj)](#Util.replaceByObject) ⇒ string \| object * [.inverseGet(objs, val)](#Util.inverseGet) ⇒ string * [.getMetadataHierachy(metadataTypes)](#Util.getMetadataHierachy) ⇒ Object.<string, Array.<string>> * [.resolveObjPath(path, obj)](#Util.resolveObjPath) ⇒ any - * [.execSync(cmd, [args], [hideOutput])](#Util.execSync) ⇒ string + * [.execSync(cmd, [args], [hideOutput])](#Util.execSync) ⇒ string \| void * [.templateSearchResult(results, keyToSearch, searchValue)](#Util.templateSearchResult) ⇒ TYPE.MetadataTypeItem * [.setLoggingLevel(argv)](#Util.setLoggingLevel) ⇒ void * [.logBeta(type)](#Util.logBeta) @@ -7784,11 +7863,10 @@ check if a market name exists in current mcdev config -### Util.verifyMarketList(mlName, properties) ⇒ void +### Util.verifyMarketList(mlName, properties) ensure provided MarketList exists and it's content including markets and BUs checks out **Kind**: static method of [Util](#Util) -**Returns**: void - throws errors if problems were found | Param | Type | Description | | --- | --- | --- | @@ -7857,6 +7935,30 @@ helper for getDefaultProperties() **Kind**: static method of [Util](#Util) **Returns**: Array.<TYPE.SupportedMetadataTypes> - type choices + + +### Util.\_createNewLoggerTransport([noLogFile]) ⇒ object +wrapper around our standard winston logging to console and logfile + +**Kind**: static method of [Util](#Util) +**Returns**: object - initiated logger for console and file + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| [noLogFile] | boolean | false | optional flag to indicate if we should log to file; CLI logs are always on | + + + +### Util.startLogger([restart], [noLogFile]) ⇒ void +initiate winston logger + +**Kind**: static method of [Util](#Util) + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| [restart] | boolean | false | if true, logger will be restarted; otherwise, an existing logger will be used | +| [noLogFile] | boolean | false | if false, logger will log to file; otherwise, only to console | + ### Util.metadataLogger(level, type, method, payload, [source]) ⇒ void @@ -7926,11 +8028,11 @@ let's you dynamically walk down an object and get a value -### Util.execSync(cmd, [args], [hideOutput]) ⇒ string +### Util.execSync(cmd, [args], [hideOutput]) ⇒ string \| void helper to run other commands as if run manually by user **Kind**: static method of [Util](#Util) -**Returns**: string - output of command if hideOutput is true +**Returns**: string \| void - output of command if hideOutput is true | Param | Type | Description | | --- | --- | --- | @@ -8064,6 +8166,32 @@ 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 | + + +## Automation.(metadataMap, key) ⇒ Promise.<void> +helper for [postDeployTasks](#Automation.postDeployTasks) + +**Kind**: global function +**Returns**: Promise.<void> - - + +| Param | Type | Description | +| --- | --- | --- | +| metadataMap | TYPE.AutomationMap | metadata mapped by their keyField | +| key | string | current customer key | + + + +## Automation.(metadataMap, originalMetadataMap, key) +helper for [postDeployTasks](postDeployTasks) + +**Kind**: global function + +| Param | Type | Description | +| --- | --- | --- | +| metadataMap | TYPE.AutomationMap | metadata mapped by their keyField | +| originalMetadataMap | TYPE.AutomationMap | metadata to be updated (contains additioanl fields) | +| key | string | current customer key | + ## getUserName(userList, item, fieldname) ⇒ string @@ -8089,19 +8217,6 @@ Returns an SDK instance to be used for API calls | sessionKey | string | key for specific BU | | authObject | TYPE.AuthObject | credentials for specific BU | - - -## createNewLoggerTransport() ⇒ object -wrapper around our standard winston logging to console and logfile - -**Kind**: global function -**Returns**: object - initiated logger for console and file - - -## startLogger() ⇒ void -initiate winston logger - -**Kind**: global function ## TypeKeyCombo : Object.<string, string> diff --git a/lib/Deployer.js b/lib/Deployer.js index 29e267c2d..3d064049a 100644 --- a/lib/Deployer.js +++ b/lib/Deployer.js @@ -103,7 +103,7 @@ class Deployer { buMultiMetadataTypeMap[cred + '/' + bu] = multiMetadataTypeMap; counter_credBu++; Util.logger.info(''); - Util.restartLogger(); + Util.startLogger(true); } } else { // anything but "*" passed in @@ -157,7 +157,7 @@ class Deployer { buMultiMetadataTypeMap[cred + '/' + buPath] = multiMetadataTypeMap; counter_credBu++; Util.logger.info(''); - Util.restartLogger(); + Util.startLogger(true); } Util.logger.info(`\n :: ${counter_credBu} BUs for ${cred}\n`); } else { @@ -387,7 +387,7 @@ class Deployer { const allFolders = Object.keys(metadata[metadataType]) .filter( // filter out root folders (which would not have a slash in their path) - (key) => metadata[metadataType][key].r__folder_Path.includes('/') + (key) => metadata[metadataType][key].r__folder_Path?.includes('/') ) .filter( // filter out dataExtension folders other than standard & shared (--> synchronized / salesforce are not allowed) diff --git a/lib/cli.js b/lib/cli.js index 992d6f9f9..931c78b19 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -389,6 +389,30 @@ yargs Mcdev.refresh(argv.BU, argv.TYPE, csvToArray(argv.KEY)); }, }) + .command({ + command: 'execute ', + aliases: ['exec'], + desc: 'executes the entity (query/journey/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)', + }); + }, + handler: (argv) => { + Mcdev.setOptions(argv); + Mcdev.execute(argv.BU, csvToArray(argv.TYPE), csvToArray(argv.KEY)); + }, + }) .command({ command: 'upgrade', aliases: ['up'], @@ -410,6 +434,10 @@ yargs type: 'boolean', description: 'Only output errors to CLI', }) + .option('noLogFile', { + type: 'boolean', + description: 'Only output log to CLI but not to files', + }) .option('skipInteraction', { alias: ['yes', 'y'], description: 'Interactive questions where possible and go with defaults instead', diff --git a/lib/index.js b/lib/index.js index ecd28d298..3589f02b6 100644 --- a/lib/index.js +++ b/lib/index.js @@ -59,6 +59,7 @@ class Mcdev { 'json', 'refresh', 'skipInteraction', + 'noLogFile', ]; for (const option of knownOptions) { if (argv[option] !== undefined) { @@ -89,6 +90,7 @@ class Mcdev { * @returns {Promise.} list of changed items */ static async createDeltaPkg(argv) { + Util.startLogger(); Util.logger.info('Create Delta Package ::'); const properties = await config.getProperties(); if (!(await config.checkProperties(properties))) { @@ -111,6 +113,7 @@ class Mcdev { * @returns {Promise} . */ static async selectTypes() { + Util.startLogger(); const properties = await config.getProperties(); if (!(await config.checkProperties(properties))) { return null; @@ -127,6 +130,7 @@ class Mcdev { * @returns {Promise.} success flag */ static async upgrade() { + Util.startLogger(); const properties = await config.getProperties(); if (!properties) { Util.logger.error('No config found. Please run mcdev init'); @@ -149,6 +153,7 @@ class Mcdev { * @returns {Promise.} - */ static async retrieve(businessUnit, selectedTypesArr, keys, changelogOnly) { + Util.startLogger(); Util.logger.info('mcdev:: Retrieve'); const properties = await config.getProperties(); if (!(await config.checkProperties(properties))) { @@ -176,7 +181,7 @@ class Mcdev { for (const bu in properties.credentials[cred].businessUnits) { await this._retrieveBU(cred, bu, selectedTypesArr, keys); counter_credBu++; - Util.restartLogger(); + Util.startLogger(true); } counter_credTotal += counter_credBu; Util.logger.info(`\n :: ${counter_credBu} BUs for ${cred}\n`); @@ -210,7 +215,7 @@ class Mcdev { for (const bu in properties.credentials[cred].businessUnits) { await this._retrieveBU(cred, bu, selectedTypesArr, keys); counter_credBu++; - Util.restartLogger(); + Util.startLogger(true); } Util.logger.info(`\n :: ${counter_credBu} BUs for ${cred}\n`); } else { @@ -351,6 +356,7 @@ class Mcdev { * @returns {Promise.>} deployed metadata per BU (first key: bu name, second key: metadata type) */ static async deploy(businessUnit, selectedTypesArr, keyArr, fromRetrieve = false) { + Util.startLogger(); return Deployer.deploy(businessUnit, selectedTypesArr, keyArr, fromRetrieve); } @@ -361,6 +367,7 @@ class Mcdev { * @returns {Promise.} - */ static async initProject(credentialsName) { + Util.startLogger(); Util.logger.info('mcdev:: Setting up project'); const properties = await config.getProperties(!!credentialsName, true); await Init.initProject(properties, credentialsName); @@ -371,6 +378,7 @@ class Mcdev { * @returns {Promise.} - */ static async joinProject() { + Util.startLogger(); Util.logger.info('mcdev:: Joining an existing project'); await Init.joinProject(); } @@ -382,6 +390,7 @@ class Mcdev { * @returns {Promise.} - */ static async findBUs(credentialsName) { + Util.startLogger(); Util.logger.info('mcdev:: Load BUs'); const properties = await config.getProperties(); if (!(await config.checkProperties(properties))) { @@ -401,6 +410,7 @@ class Mcdev { * @returns {Promise.} - */ static async document(businessUnit, type) { + Util.startLogger(); Util.logger.info('mcdev:: Document'); const properties = await config.getProperties(); if (!(await config.checkProperties(properties))) { @@ -440,6 +450,7 @@ class Mcdev { * @returns {Promise.} true if successful, false otherwise */ static async deleteByKey(businessUnit, type, customerKey) { + Util.startLogger(); Util.logger.info('mcdev:: delete'); if (!Util._isValidType(type)) { return; @@ -477,6 +488,7 @@ class Mcdev { * @returns {Promise.} - */ static async refresh(businessUnit, type, keyArr) { + Util.startLogger(); Util.logger.info('mcdev:: refresh'); if (!type || !Util._isValidType(type, true)) { type = 'triggeredSend'; @@ -512,6 +524,7 @@ class Mcdev { * @returns {Promise.} - */ static async badKeys(businessUnit) { + Util.startLogger(); const properties = await config.getProperties(); if (!(await config.checkProperties(properties))) { return null; @@ -585,6 +598,7 @@ class Mcdev { * @returns {Promise.} - */ static async retrieveAsTemplate(businessUnit, selectedType, name, market) { + Util.startLogger(); Util.logger.info('mcdev:: Retrieve as Template'); const properties = await config.getProperties(); if (!(await config.checkProperties(properties))) { @@ -626,6 +640,7 @@ class Mcdev { * @returns {Promise.} - */ static async buildTemplate(businessUnit, selectedType, keyArr, market) { + Util.startLogger(); Util.logger.info('mcdev:: Build Template from retrieved files'); return Builder.buildTemplate(businessUnit, selectedType, keyArr, market); } @@ -639,6 +654,7 @@ class Mcdev { * @returns {Promise.} - */ static async buildDefinition(businessUnit, selectedType, name, market) { + Util.startLogger(); Util.logger.info('mcdev:: Build Definition from Template'); return Builder.buildDefinition(businessUnit, selectedType, name, market); } @@ -652,6 +668,7 @@ class Mcdev { * @returns {Promise.} - */ static async buildDefinitionBulk(listName, type, name) { + Util.startLogger(); Util.logger.info('mcdev:: Build Definition from Template Bulk'); return Builder.buildDefinitionBulk(listName, type, name); } @@ -663,6 +680,7 @@ class Mcdev { * @returns {Promise.} list of all files that need to be committed in a flat array ['path/file1.ext', 'path/file2.ext'] */ static async getFilesToCommit(businessUnit, selectedType, keyArr) { + Util.startLogger(); Util.logger.info('mcdev:: getFilesToCommit'); const properties = await config.getProperties(); if (!(await config.checkProperties(properties))) { @@ -682,6 +700,159 @@ class Mcdev { return DevOps.getFilesToCommit(properties, buObject, selectedType, keyArr); } } + /** + * Start an item (query) + * + * @param {string} businessUnit name of BU + * @param {TYPE.SupportedMetadataTypes[]} [selectedTypesArr] limit to given metadata types + * @param {string[]} keys customerkey of the metadata + * @returns {Promise.} true if all started successfully, false if not + */ + static async execute(businessUnit, selectedTypesArr, keys) { + Util.startLogger(); + Util.logger.info('mcdev:: Execute'); + const properties = await config.getProperties(); + let counter_credBu = 0; + let counter_failed = 0; + if (!(await config.checkProperties(properties))) { + // return null here to avoid seeing 2 error messages for the same issue + return null; + } + if (Array.isArray(selectedTypesArr)) { + // types and keys can be provided but for each type all provided keys are applied as filter + for (const selectedType of Array.isArray(selectedTypesArr) + ? selectedTypesArr + : Object.keys(selectedTypesArr)) { + if (!Util._isValidType(selectedType)) { + return; + } + } + } + if (businessUnit === '*') { + Util.logger.info( + '\n :: Executing the entity on all BUs for all credentials' + ); + let counter_credTotal = 0; + for (const cred in properties.credentials) { + Util.logger.info(`\n :: Executing the entity on all BUs for ${cred}`); + + for (const bu in properties.credentials[cred].businessUnits) { + if (await this._executeBU(cred, bu, selectedTypesArr, keys)) { + counter_credBu++; + } else { + counter_failed++; + } + Util.startLogger(true); + } + counter_credTotal += counter_credBu; + Util.logger.info( + `\n :: Executed the entity on ${counter_credBu} BUs for ${cred}\n` + ); + } + Util.logger.info( + `\n :: Executed the entity 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 + if ( + properties.credentials && + (!properties.credentials[cred] || + (bu !== '*' && !properties.credentials[cred].businessUnits[bu])) + ) { + const buObject = await Cli.getCredentialObject( + properties, + cred === null ? null : cred + '/' + bu, + null, + true + ); + if (buObject === null) { + return; + } else { + cred = buObject.credential; + bu = buObject.businessUnit; + } + } + if (bu === '*' && properties.credentials && properties.credentials[cred]) { + Util.logger.info(`\n :: Executing the entity on all BUs for ${cred}`); + let counter_credBu = 0; + for (const bu in properties.credentials[cred].businessUnits) { + if (await this._executeBU(cred, bu, selectedTypesArr, keys)) { + counter_credBu++; + } else { + counter_failed++; + } + Util.startLogger(true); + } + Util.logger.info( + `\n :: Executed the entity on ${counter_credBu} BUs for ${cred}\n` + ); + } else { + // execute the entity on one BU only + if (await this._executeBU(cred, bu, selectedTypesArr, keys)) { + counter_credBu++; + } else { + counter_failed++; + } + Util.logger.info(`\n :: Done\n`); + } + } + if (counter_credBu !== 0) { + Util.logger.info(`\n :: Executed query on ${counter_credBu} BUs\n`); + } + return counter_failed === 0 ? true : false; + } + /** + * helper for {@link execute} + * + * @param {string} cred name of Credential + * @param {string} bu name of BU + * @param {TYPE.SupportedMetadataTypes[]} [selectedTypesArr] 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, selectedTypesArr, keyArr) { + const properties = await config.getProperties(); + let counter_failed = 0; + const buObject = await Cli.getCredentialObject( + properties, + cred === null ? null : cred + '/' + bu, + null, + true + ); + if (!keyArr || (Array.isArray(keyArr) && !keyArr.length)) { + throw new Error('No keys were provided'); + } + if (!selectedTypesArr || (Array.isArray(selectedTypesArr) && !selectedTypesArr.length)) { + throw new Error('No type was provided'); + } + if (buObject !== null) { + cache.initCache(buObject); + cred = buObject.credential; + bu = buObject.businessUnit; + } + Util.logger.info( + `\n :: Executing ${selectedTypesArr.join(', ')} on ${cred}/${bu}\n` + ); + try { + // more than one type was provided, iterate types and execute items + for (const type of selectedTypesArr) { + try { + MetadataTypeInfo[type].client = auth.getSDK(buObject); + } catch (ex) { + Util.logger.error(ex.message); + return; + } + // result will be undefined (false) if execute is not supported for the type + if (!(await MetadataTypeInfo[type].execute(keyArr))) { + counter_failed++; + } + } + } catch (ex) { + Util.logger.errorStack(ex, 'mcdev.execute failed'); + } + return counter_failed === 0 ? true : false; + } } module.exports = Mcdev; diff --git a/lib/metadataTypes/Automation.js b/lib/metadataTypes/Automation.js index 8232a2e11..560ed94e3 100644 --- a/lib/metadataTypes/Automation.js +++ b/lib/metadataTypes/Automation.js @@ -6,6 +6,7 @@ const Util = require('../util/util'); const File = require('../util/file'); const Definitions = require('../MetadataTypeDefinitions'); const cache = require('../util/cache'); +const pLimit = require('p-limit'); /** * Automation MetadataType @@ -13,6 +14,7 @@ const cache = require('../util/cache'); * @augments MetadataType */ class Automation extends MetadataType { + static notificationUpdates = {}; /** * Retrieves Metadata of Automation * @@ -35,11 +37,13 @@ class Automation extends MetadataType { }; } const results = await this.client.soap.retrieveBulk('Program', ['ObjectID'], requestParams); - if (results.Results?.length) { + if (results.Results?.length && !key) { // empty results will come back without "Results" defined Util.logger.info( Util.getGrayMsg( - ` - ${results.Results?.length} Automations found. Retrieving details...` + ` - ${results.Results?.length} automation${ + results.Results?.length === 1 ? '' : 's' + } found. Retrieving details...` ) ); } @@ -71,6 +75,12 @@ class Automation extends MetadataType { ) : []; let metadataMap = this.parseResponseBody({ items: details }); + + if (Object.keys(metadataMap).length) { + // attach notification information to each automation that has any + await this.#getAutomationNotificationsREST(metadataMap); + } + // * retrieveDir can be empty when we use it in the context of postDeployTasks if (retrieveDir) { metadataMap = await this.saveResults(metadataMap, retrieveDir, null, null); @@ -83,6 +93,77 @@ class Automation extends MetadataType { } return { metadata: metadataMap, type: this.definition.type }; } + + /** + * helper for {@link Automation.retrieve} to get Automation Notifications + * + * @private + * @param {TYPE.MetadataTypeMap} metadataMap keyField => metadata map + * @returns {Promise.} Promise of nothing + */ + static async #getAutomationNotificationsREST(metadataMap) { + Util.logger.info(Util.getGrayMsg(` Retrieving Automation Notification information...`)); + + // get list of keys that we retrieved so far + const foundKeys = Object.keys(metadataMap); + + // get encodedAutomationID to retrieve notification information + const iteratorBackup = this.definition.bodyIteratorField; + this.definition.bodyIteratorField = 'entry'; + const automationLegacyMapObj = await super.retrieveREST( + undefined, + `/legacy/v1/beta/bulk/automations/automation/definition/` + ); + this.definition.bodyIteratorField = iteratorBackup; + const automationLegacyMap = Object.keys(automationLegacyMapObj.metadata) + .filter((key) => foundKeys.includes(key)) + // ! using the `id` field to retrieve notifications does not work. instead one needs to use the URL in the `notifications` field + .map((key) => ({ + id: automationLegacyMapObj.metadata[key].id, + key, + })); + + // get notifications for each automation + const rateLimit = pLimit(5); + let found = 0; + let skipped = 0; + const promiseMap = await Promise.all( + automationLegacyMap.map((automationLegacy) => + rateLimit(async () => { + // this is a file so extended is at another endpoint + try { + const notificationsResult = await this.client.rest.get( + '/legacy/v1/beta/automations/notifications/' + automationLegacy.id + ); + if (Array.isArray(notificationsResult?.workers)) { + metadataMap[automationLegacy.key].notifications = + notificationsResult.workers.map((n) => ({ + email: n.definition.split(',').map((item) => item.trim()), + message: n.body, + type: n.notificationType, + })); + found++; + } else { + throw new TypeError(JSON.stringify(notificationsResult)); + } + } catch (ex) { + Util.logger.debug( + ` ☇ skipping Notifications for Automation ${automationLegacy.key}: ${ex.message} ${ex.code}` + ); + skipped++; + } + }) + ) + ); + Util.logger.info( + Util.getGrayMsg(` Notifications found for ${found} automation${found === 1 ? '' : 's'}`) + ); + Util.logger.debug( + `Notifications not found for ${skipped} automation${skipped === 1 ? '' : 's'}` + ); + return promiseMap; + } + /** * Retrieves Metadata of Automation * @@ -134,6 +215,7 @@ class Automation extends MetadataType { * @returns {Promise.} Promise of metadata */ static async retrieveForCache() { + // get automations for cache const results = await this.client.soap.retrieveBulk('Program', [ 'ObjectID', 'CustomerKey', @@ -141,14 +223,29 @@ class Automation extends MetadataType { ]); const resultsConverted = {}; if (Array.isArray(results?.Results)) { + // get encodedAutomationID to retrieve notification information + const keyBackup = this.definition.keyField; + const iteratorBackup = this.definition.bodyIteratorField; + this.definition.keyField = 'key'; + this.definition.bodyIteratorField = 'entry'; + const automationsLegacy = await super.retrieveREST( + undefined, + `/legacy/v1/beta/bulk/automations/automation/definition/` + ); + this.definition.keyField = keyBackup; + this.definition.bodyIteratorField = iteratorBackup; + + // merge encodedAutomationID into results for (const m of results.Results) { resultsConverted[m.CustomerKey] = { id: m.ObjectID, key: m.CustomerKey, name: m.Name, + programId: automationsLegacy.metadata[m.CustomerKey]?.id, }; } } + return { metadata: resultsConverted, type: this.definition.type }; } @@ -175,14 +272,21 @@ class Automation extends MetadataType { Util.logger.error(`${this.definition.type} '${name}' not found on server.`); return; } - const details = await this.client.rest.get( + let details = await this.client.rest.get( '/automation/v1/automations/' + metadata.ObjectID ); + const metadataMap = this.parseResponseBody({ items: [details] }); + if (Object.keys(metadataMap).length) { + // attach notification information to each automation that has any + await this.#getAutomationNotificationsREST(metadataMap); + details = Object.values(metadataMap)[0]; + } + let val = null; let originalKey; // if parsing fails, we should just save what we get try { - const parsedDetails = this.parseMetadata(details); + const parsedDetails = this.postRetrieveTasks(details); originalKey = parsedDetails[this.definition.keyField]; if (parsedDetails !== null) { val = JSON.parse( @@ -218,10 +322,128 @@ class Automation extends MetadataType { * manages post retrieve steps * * @param {TYPE.AutomationItem} metadata a single automation - * @returns {TYPE.AutomationItem} metadata + * @returns {TYPE.AutomationItem | void} parsed item */ static postRetrieveTasks(metadata) { - return this.parseMetadata(metadata); + // folder + this.setFolderPath(metadata); + // automations are often skipped due to lack of support. + try { + 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.` + ); + return; + } + } else if (metadata.type === 'triggered' && metadata.fileTrigger) { + // Starting Source == 'File Drop' + // Do nothing for now + } + if (metadata.steps) { + for (const step of metadata.steps) { + const stepNumber = step.stepNumber || step.step; + delete step.stepNumber; + delete step.step; + + for (const activity of step.activities) { + try { + // get metadata type of activity + activity.r__type = Util.inverseGet( + this.definition.activityTypeMapping, + activity.objectTypeId + ); + delete activity.objectTypeId; + } catch { + Util.logger.warn( + ` - Unknown activity type '${activity.objectTypeId}'` + + ` in step ${stepNumber}.${activity.displayOrder}` + + ` of Automation '${metadata.name}'` + ); + continue; + } + + // if no activityObjectId then either serialized activity + // (config in Automation ) or unconfigured so no further action to be taken + if ( + activity.activityObjectId === '00000000-0000-0000-0000-000000000000' || + activity.activityObjectId == null + ) { + Util.logger.debug( + ` - skip parsing of activity due to missing activityObjectId: ${JSON.stringify( + activity + )}` + ); + // empty if block + } else if (!this.definition.dependencies.includes(activity.r__type)) { + Util.logger.debug( + ` - skip parsing because the type is not set up as a dependecy for ${this.definition.type}` + ); + } + // / if managed by cache we can update references to support deployment + else if ( + Definitions[activity.r__type]?.['idField'] && + cache.getCache(this.buObject.mid)[activity.r__type] + ) { + try { + // this will override the name returned by the API in case this activity's name was changed since the automation was last updated, keeping things nicely in sync for mcdev + const name = cache.searchForField( + activity.r__type, + activity.activityObjectId, + Definitions[activity.r__type].idField, + Definitions[activity.r__type].nameField + ); + if (name !== activity.name) { + Util.logger.debug( + ` - updated name of step ${stepNumber}.${activity.displayOrder}` + + ` in Automation '${metadata.name}' from ${activity.name} to ${name}` + ); + activity.name = name; + } + } catch (ex) { + // getFromCache throws error where the dependent metadata is not found + Util.logger.warn( + ` - Missing ${activity.r__type} activity '${activity.name}'` + + ` in step ${stepNumber}.${activity.displayOrder}` + + ` of Automation '${metadata.name}' (${ex.message})` + ); + } + } else { + Util.logger.warn( + ` - Missing ${activity.r__type} activity '${activity.name}'` + + ` in step ${stepNumber}.${activity.displayOrder}` + + ` of Automation '${metadata.name}' (Not Found in Cache)` + ); + } + } + } + } + return JSON.parse(JSON.stringify(metadata)); + } catch (ex) { + Util.logger.warn( + ` - ${this.definition.typeName} '${metadata[this.definition.nameField]}': ${ + ex.message + }` + ); + return null; + } } /** @@ -235,7 +457,15 @@ class Automation extends MetadataType { */ static async deploy(metadata, targetBU, retrieveDir, isRefresh) { const upsertResults = await this.upsert(metadata, targetBU, isRefresh); - await this.saveResults(upsertResults, retrieveDir, null); + const savedMetadata = await this.saveResults(upsertResults, retrieveDir, null); + if ( + this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type) && + !this.definition.documentInOneFile + ) { + const count = Object.keys(savedMetadata).length; + Util.logger.debug(` - Running document for ${count} record${count === 1 ? '' : 's'}`); + await this.document(savedMetadata); + } return upsertResults; } @@ -270,6 +500,15 @@ class Automation extends MetadataType { * @returns {Promise.} Promise */ static async preDeployTasks(metadata) { + if (metadata.notifications) { + this.notificationUpdates[metadata.key] = metadata.notifications; + } else { + const cached = cache.getByKey(metadata.key); + if (cached?.notifications) { + // if notifications existed but are no longer present in the deployment package, we need to run an empty update call to remove them + this.notificationUpdates[metadata.key] = []; + } + } if (this.validateDeployMetadata(metadata)) { // folder this.setFolderId(metadata); @@ -404,86 +643,146 @@ class Automation extends MetadataType { static async postDeployTasks(metadataMap, originalMetadataMap) { for (const key in metadataMap) { // need to put schedule on here if status is scheduled + await Automation.#scheduleAutomation(metadataMap, originalMetadataMap, key); + + // need to update notifications separately if there are any + await Automation.#updateNotificationInfoREST(metadataMap, key); + + // rewrite upsert to retrieve fields + const metadata = metadataMap[key]; + if (metadata.steps) { + for (const step of metadata.steps) { + step.name = step.annotation; + delete step.annotation; + } + } + } + } + /** + * helper for {@link Automation.postDeployTasks} + * + * @param {TYPE.AutomationMap} metadataMap metadata mapped by their keyField + * @param {string} key current customer key + * @returns {Promise.} - + */ + static async #updateNotificationInfoREST(metadataMap, key) { + if (this.notificationUpdates[key]) { + // create & update automation calls return programId as 'legacyId'; retrieve does not return it + const programId = metadataMap[key]?.legacyId; + if (programId) { + const notificationBody = { + programId, + workers: this.notificationUpdates[key].map((notification) => ({ + programId, + notificationType: notification.type, + definition: Array.isArray(notification.email) + ? notification.email.join(',') + : notification.email, + body: notification.message, + channelType: 'Account', + })), + }; + try { + const result = await this.client.rest.post( + '/legacy/v1/beta/automations/notifications/' + programId, + notificationBody + ); + if (result) { + // should be empty if all OK + throw new Error(result); + } + } catch (ex) { + Util.logger.error( + `Error updating notifications for automation '${metadataMap[key].name}': ${ex.message} (${ex.code}))` + ); + } + Util.logger.info( + Util.getGrayMsg( + ` - updated notifications for automation '${metadataMap[key].name}'` + ) + ); + } + } + } - if (originalMetadataMap[key]?.type === 'scheduled') { - // Starting Source == 'Schedule': Try starting the automation - if (originalMetadataMap[key].status === 'Scheduled') { - let schedule = null; + /** + * helper for {@link 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 + */ + static async #scheduleAutomation(metadataMap, originalMetadataMap, key) { + if (originalMetadataMap[key]?.type === 'scheduled') { + // Starting Source == 'Schedule': Try starting the automation + if (originalMetadataMap[key].status === 'Scheduled') { + let schedule = null; + try { + schedule = this._buildSchedule(originalMetadataMap[key].schedule); + } catch (ex) { + Util.logger.error( + `- Could not create schedule for automation '${originalMetadataMap[key].name}' to start it: ${ex.message}` + ); + } + if (schedule !== null) { try { - schedule = this._buildSchedule(originalMetadataMap[key].schedule); + // remove the fields that are not needed for the schedule but only for CLI output + const schedule_StartDateTime = schedule._StartDateTime; + delete schedule._StartDateTime; + const schedule_interval = schedule._interval; + delete schedule._interval; + const schedule_timezoneString = schedule._timezoneString; + delete schedule._timezoneString; + // start the automation + await this.client.soap.schedule( + 'Automation', + schedule, + { + Interaction: { + ObjectID: metadataMap[key].id, + }, + }, + 'start', + {} + ); + const intervalString = + (schedule_interval > 1 ? `${schedule_interval} ` : '') + + (schedule.RecurrenceType === 'Daily' + ? 'Day' + : schedule.RecurrenceType.slice(0, -2) + + (schedule_interval > 1 ? 's' : '')); + Util.logger.warn( + ` - scheduled automation '${ + originalMetadataMap[key].name + }' deployed as Active: runs every ${intervalString} starting ${ + schedule_StartDateTime.split('T').join(' ').split('.')[0] + } ${schedule_timezoneString}` + ); } catch (ex) { Util.logger.error( - `- Could not create schedule for automation ${originalMetadataMap[key].name} to start it: ${ex.message}` + `- Could not start scheduled automation '${originalMetadataMap[key].name}': ${ex.message}` ); } - if (schedule !== null) { - try { - // remove the fields that are not needed for the schedule but only for CLI output - const schedule_StartDateTime = schedule._StartDateTime; - delete schedule._StartDateTime; - const schedule_interval = schedule._interval; - delete schedule._interval; - const schedule_timezoneString = schedule._timezoneString; - delete schedule._timezoneString; - // start the automation - await this.client.soap.schedule( - 'Automation', - schedule, - { - Interaction: { - ObjectID: metadataMap[key].id, - }, - }, - 'start', - {} - ); - const intervalString = - (schedule_interval > 1 ? `${schedule_interval} ` : '') + - (schedule.RecurrenceType === 'Daily' - ? 'Day' - : schedule.RecurrenceType.slice(0, -2) + - (schedule_interval > 1 ? 's' : '')); - Util.logger.warn( - ` - scheduled automation '${ - originalMetadataMap[key].name - }' deployed Active: runs every ${intervalString} starting ${ - schedule_StartDateTime.split('T').join(' ').split('.')[0] - } ${schedule_timezoneString}` - ); - } catch (ex) { - Util.logger.error( - `- Could not start scheduled automation '${originalMetadataMap[key].name}': ${ex.message}` - ); - } - } - } else { - Util.logger.warn( - ` - scheduled automation '${originalMetadataMap[key].name}' deployed Paused` - ); } - } - if (metadataMap[key].startSource) { - metadataMap[key].schedule = metadataMap[key].startSource.schedule; - - delete metadataMap[key].startSource; - } - if (metadataMap[key].schedule) { - metadataMap[key].schedule.typeId = metadataMap[key].schedule.scheduleTypeId; - delete metadataMap[key].schedule.scheduleTypeId; - } - - // re-retrieve deployed items because the API does not return any info for them except the new id (api key) - try { - const { metadata } = await this.retrieve(null, null, null, key); - metadataMap[key] = Object.values(metadata)[0]; - // postRetrieveTasks will be run automatically on this via super.saveResult - } catch (ex) { - throw new Error( - `Could not get details for new ${this.definition.type} ${key} from server (${ex.message})` + } else { + Util.logger.info( + Util.getGrayMsg( + ` - scheduled automation '${originalMetadataMap[key].name}' deployed as Paused` + ) ); } } + if (metadataMap[key].startSource) { + metadataMap[key].schedule = metadataMap[key].startSource.schedule; + + delete metadataMap[key].startSource; + } + if (metadataMap[key].schedule?.scheduleTypeId) { + metadataMap[key].schedule.typeId = metadataMap[key].schedule.scheduleTypeId; + delete metadataMap[key].schedule.scheduleTypeId; + } } + /** * generic script that retrieves the folder path from cache and updates the given metadata with it after retrieve * @@ -549,118 +848,6 @@ class Automation extends MetadataType { } } - /** - * parses retrieved Metadata before saving - * - * @param {TYPE.AutomationItem} metadata a single automation definition - * @returns {TYPE.AutomationItem | void} parsed item - */ - static parseMetadata(metadata) { - // folder - this.setFolderPath(metadata); - // automations are often skipped due to lack of support. - try { - 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.` - ); - return; - } - } else if (metadata.type === 'triggered' && metadata.fileTrigger) { - // Starting Source == 'File Drop' - // Do nothing for now - } - if (metadata.steps) { - for (const step of metadata.steps) { - const stepNumber = step.stepNumber || step.step; - delete step.stepNumber; - delete step.step; - - for (const activity of step.activities) { - try { - // get metadata type of activity - activity.r__type = Util.inverseGet( - this.definition.activityTypeMapping, - activity.objectTypeId - ); - delete activity.objectTypeId; - } catch { - Util.logger.warn( - ` - Unknown activity type '${activity.objectTypeId}'` + - ` in step ${stepNumber}.${activity.displayOrder}` + - ` of Automation '${metadata.name}'` - ); - continue; - } - - // if no activityObjectId then either serialized activity - // (config in Automation ) or unconfigured so no further action to be taken - if ( - activity.activityObjectId === '00000000-0000-0000-0000-000000000000' || - activity.activityObjectId == null || - !this.definition.dependencies.includes(activity.r__type) - ) { - // empty if block - } - // / if managed by cache we can update references to support deployment - else if ( - Definitions[activity.r__type]?.['idField'] && - cache.getCache(this.buObject.mid)[activity.r__type] - ) { - try { - activity.activityObjectId = cache.searchForField( - activity.r__type, - activity.activityObjectId, - Definitions[activity.r__type].idField, - Definitions[activity.r__type].nameField - ); - } catch (ex) { - // getFromCache throws error where the dependent metadata is not found - Util.logger.warn( - ` - Missing ${activity.r__type} activity '${activity.name}'` + - ` in step ${stepNumber}.${activity.displayOrder}` + - ` of Automation '${metadata.name}' (${ex.message})` - ); - } - } else { - Util.logger.warn( - ` - Missing ${activity.r__type} activity '${activity.name}'` + - ` in step ${stepNumber}.${activity.displayOrder}` + - ` of Automation '${metadata.name}' (Not Found in Cache)` - ); - } - } - } - } - return JSON.parse(JSON.stringify(metadata)); - } catch (ex) { - Util.logger.warn( - ` - ${this.definition.typeName} '${metadata[this.definition.nameField]}': ${ - ex.message - }` - ); - return null; - } - } - /** * Builds a schedule object to be used for scheduling an automation * based on combination of ical string and start/end dates. @@ -868,6 +1055,27 @@ class Automation extends MetadataType { output += `* Pattern: ${json.fileTrigger.fileNamingPattern}\n`; output += `* Folder: ${json.fileTrigger.folderLocationText}\n`; } + // add empty line to ensure the following notifications are rendered properly + output += '\n'; + if (json.notifications?.length) { + output += `**Notifications:**\n\n`; + // ensure notifications are sorted by type regardless of how the API returns it + const notifications = {}; + for (const n of json.notifications) { + notifications[n.type] = + (Array.isArray(n.email) ? n.email.join(',') : n.email) + + (n.message ? ` ("${n.message}")` : ''); + } + if (notifications.Complete) { + output += `* Complete: ${notifications.Complete}\n`; + } + if (notifications.Error) { + output += `* Error: ${notifications.Error}\n`; + } + } else { + output += `**Notifications:** _none_\n\n`; + } + // show table with automation steps if (tabled && tabled.length) { // add empty line to ensure the following table is rendered properly @@ -975,11 +1183,10 @@ class Automation extends MetadataType { // as part of retrieve & manual execution we could face an empty folder return; } - await Promise.all( - Object.keys(metadata).map((key) => { - this._writeDoc(docPath + '/', key, metadata[key], 'md'); - return metadata[key]; - }) + return await Promise.all( + Object.keys(metadata).map((key) => + this._writeDoc(docPath + '/', key, metadata[key], 'md') + ) ); } } diff --git a/lib/metadataTypes/DataExtension.js b/lib/metadataTypes/DataExtension.js index cd50d099e..43e9dcfc8 100644 --- a/lib/metadataTypes/DataExtension.js +++ b/lib/metadataTypes/DataExtension.js @@ -59,8 +59,8 @@ class DataExtension extends MetadataType { // output error & remove from deploy list Util.logger.error( ` ☇ skipping ${this.definition.type} ${ - metadataMap[this.definition.keyField] - } / ${metadataMap[this.definition.nameField]}: ${ex.message}` + metadataMap[metadataKey][this.definition.keyField] + } / ${metadataMap[metadataKey][this.definition.nameField]}: ${ex.message}` ); delete metadataMap[metadataKey]; // skip rest of handling for this DE @@ -624,16 +624,16 @@ class DataExtension extends MetadataType { * @returns {Promise.} Promise of updated single DE */ static async preDeployTasks(metadata) { - if (metadata.Name.startsWith('_')) { + if (metadata.Name?.startsWith('_')) { throw new Error(`Cannot Upsert Strongly Typed Data Extensions`); } if ( this.buObject.eid !== this.buObject.mid && - metadata.r__folder_Path.startsWith('Shared Items') + metadata.r__folder_Path?.startsWith('Shared Items') ) { throw new Error(`Cannot Create/Update a Shared Data Extension from the Child BU`); } - if (metadata.r__folder_Path.startsWith('Synchronized Data Extensions')) { + if (metadata.r__folder_Path?.startsWith('Synchronized Data Extensions')) { throw new Error( `Cannot Create/Update a Synchronized Data Extension. Please use Contact Builder to maintain these` ); diff --git a/lib/metadataTypes/MetadataType.js b/lib/metadataTypes/MetadataType.js index 69b582593..92b1686de 100644 --- a/lib/metadataTypes/MetadataType.js +++ b/lib/metadataTypes/MetadataType.js @@ -58,15 +58,9 @@ class MetadataType { if (key === fileNameWithoutEnding || listBadKeys) { fileName2FileContent[fileNameWithoutEnding] = fileContent; } else { - Util.metadataLogger( - 'error', - this.definition.type, - 'getJsonFromFS', - 'Name of the Metadata and the External Identifier must match', - JSON.stringify({ - Filename: fileNameWithoutEnding, - ExternalIdentifier: key, - }) + Util.logger.error( + ` ${this.definition.type} ${key}: Name of the metadata file and the JSON-key (${this.definition.keyField}) must match` + + Util.getGrayMsg(` - ${dir}/${fileName}`) ); } } @@ -460,6 +454,18 @@ class MetadataType { return; } + /** + * Abstract execute method that needs to be implemented in child metadata type + * + * @returns {void} + */ + static execute() { + Util.logger.error( + ` ☇ skipping ${this.definition.type}: execute is not supported yet for ${this.definition.type}` + ); + return; + } + /** * test if metadata was actually changed or not to potentially skip it during deployment * @@ -650,6 +656,7 @@ class MetadataType { metadataMap[metadataKey][this.definition.keyField] ); // Update if it already exists; Create it if not + const maxKeyLength = this.definition.maxKeyLength || 36; if ( Util.logger.level === 'debug' && metadataMap[metadataKey][this.definition.idField] && @@ -703,11 +710,11 @@ class MetadataType { // NOTE: trim twice while getting the newKey value to remove leading spaces before limiting the length const newKey = (metadataMap[metadataKey][Util.OPTIONS.changeKeyField] + '') .trim() - .slice(0, 36) + .slice(0, maxKeyLength) .trim(); - if (metadataMap[metadataKey][Util.OPTIONS.changeKeyField] + '' > 36) { + if (metadataMap[metadataKey][Util.OPTIONS.changeKeyField] + '' > maxKeyLength) { Util.logger.warn( - `Customer Keys may not exceed 36 characters. Truncated the value in field ${Util.OPTIONS.changeKeyField} to ${newKey}` + `${this.definition.type} ${this.definition.keyField} may not exceed ${maxKeyLength} characters. Truncated the value in field ${Util.OPTIONS.changeKeyField} to ${newKey}` ); } if (metadataKey == newKey) { @@ -727,10 +734,10 @@ class MetadataType { } } else if (Util.OPTIONS.changeKeyValue) { // NOTE: trim twice while getting the newKey value to remove leading spaces before limiting the length - const newKey = Util.OPTIONS.changeKeyValue.trim().slice(0, 36).trim(); - if (Util.OPTIONS.changeKeyValue.trim().length > 36) { + const newKey = Util.OPTIONS.changeKeyValue.trim().slice(0, maxKeyLength).trim(); + if (Util.OPTIONS.changeKeyValue.trim().length > maxKeyLength) { Util.logger.warn( - `Customer Keys may not exceed 36 characters. Truncated your value to ${newKey}` + `${this.definition.type} ${this.definition.keyField} may not exceed ${maxKeyLength} characters. Truncated your value to ${newKey}` ); } if (this.definition.keyField == this.definition.idField) { @@ -1066,6 +1073,26 @@ class MetadataType { type: this.definition.type, }; } + /** + * Used to execute a query/automation etc. + * + * @param {string} uri REST endpoint where the POST request should be sent + * @param {string} key item key + * @returns {Promise.} 'OK' if started execution successfully, otherwise - 'Error' + */ + static async executeREST(uri, key) { + try { + const response = await this.client.rest.post(uri, {}); // payload is empty for this request + if (response === 'OK') { + Util.logger.info(`Executed ${this.definition.type}: ${key}`); + } else { + throw new Error(response); + } + return response; + } catch (ex) { + Util.logger.error(`Failed to execute ${this.definition.type} ${key}: ${ex.message}`); + } + } /** * helper for {@link retrieveREST} and {@link retrieveSOAP} diff --git a/lib/metadataTypes/Query.js b/lib/metadataTypes/Query.js index 1b3d331d3..e91496131 100644 --- a/lib/metadataTypes/Query.js +++ b/lib/metadataTypes/Query.js @@ -50,6 +50,32 @@ class Query extends MetadataType { 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 execute(keyArr) { + const results = []; + // works only with objectId + let objectId; + for (const key of keyArr) { + if (key) { + objectId = await this._getObjectIdForSingleRetrieve(key); + if (!objectId) { + Util.logger.info(`Skipping ${key} - did not find an item with such key`); + break; + } + } + results.push( + super.executeREST(`/automation/v1/queries/${objectId}/actions/start/`, key) + ); + } + const successCounter = (await Promise.all(results)).filter((r) => r === 'OK').length; + Util.logger.info(`Executed ${successCounter} of ${keyArr.length} items`); + return successCounter === keyArr.length; + } /** * helper to allow us to select single metadata entries via REST * diff --git a/lib/metadataTypes/definitions/Asset.definition.js b/lib/metadataTypes/definitions/Asset.definition.js index ad6007658..96d427258 100644 --- a/lib/metadataTypes/definitions/Asset.definition.js +++ b/lib/metadataTypes/definitions/Asset.definition.js @@ -13,6 +13,7 @@ module.exports = { lastmodDateField: 'modifiedDate', lastmodNameField: 'modifiedBy.name', restPagination: true, + maxKeyLength: 36, // confirmed max length type: 'asset', typeDescription: 'Assets from Content Builder grouped into subtypes.', typeRetrieveByDefault: ['asset', 'code', 'textfile', 'block', 'message', 'template', 'other'], diff --git a/lib/metadataTypes/definitions/Automation.definition.js b/lib/metadataTypes/definitions/Automation.definition.js index 5be143e55..c7ab19a19 100644 --- a/lib/metadataTypes/definitions/Automation.definition.js +++ b/lib/metadataTypes/definitions/Automation.definition.js @@ -46,7 +46,8 @@ module.exports = { createdNameField: 'createdByName', lastmodDateField: 'lastSavedDate', lastmodNameField: 'lastSavedByName', - restPagination: false, + restPagination: true, + maxKeyLength: 200, // confirmed max length statusMapping: { AwaitingTrigger: 7, Building: 1, @@ -329,7 +330,7 @@ module.exports = { legacyId: { isCreateable: false, isUpdateable: false, - retrieving: true, + retrieving: false, template: false, }, lastSavedDate: { @@ -368,6 +369,45 @@ module.exports = { retrieving: true, template: true, }, + notifications: { + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + 'notifications[].email': { + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + 'notifications[].message': { + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + 'notifications[].channelType': { + // always 'Account' + isCreateable: true, + isUpdateable: true, + retrieving: false, + template: false, + }, + 'notifications[].type': { + // custom shorthand for channelType + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + 'notifications[].notificationType': { + // Error, Complete + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, startSource: { skipValidation: true, }, @@ -411,7 +451,7 @@ module.exports = { isCreateable: true, isUpdateable: true, retrieving: false, - template: true, + template: false, }, 'schedule.scheduleStatus': { isCreateable: false, @@ -425,6 +465,12 @@ module.exports = { retrieving: true, template: true, }, + 'schedule.statusId': { + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, 'schedule.timezoneId': { isCreateable: true, isUpdateable: true, @@ -527,8 +573,8 @@ module.exports = { 'steps[].annotation': { isCreateable: true, isUpdateable: true, - retrieving: true, - template: true, + retrieving: false, + template: false, }, 'steps[].id': { isCreateable: true, @@ -564,7 +610,7 @@ module.exports = { isCreateable: true, isUpdateable: true, retrieving: false, - template: true, + template: false, }, r__folder_Path: { skipValidation: true }, }, diff --git a/lib/metadataTypes/definitions/DataExtension.definition.js b/lib/metadataTypes/definitions/DataExtension.definition.js index ff68df684..67a224d45 100644 --- a/lib/metadataTypes/definitions/DataExtension.definition.js +++ b/lib/metadataTypes/definitions/DataExtension.definition.js @@ -96,6 +96,7 @@ module.exports = { lastmodDateField: 'ModifiedDate', lastmodNameField: null, restPagination: false, + maxKeyLength: 200, // confirmed max length type: 'dataExtension', typeDescription: 'Database table schemas.', typeRetrieveByDefault: true, diff --git a/lib/metadataTypes/definitions/DataExtract.definition.js b/lib/metadataTypes/definitions/DataExtract.definition.js index 4736cc7d8..0b0f009e8 100644 --- a/lib/metadataTypes/definitions/DataExtract.definition.js +++ b/lib/metadataTypes/definitions/DataExtract.definition.js @@ -11,6 +11,7 @@ module.exports = { lastmodNameField: 'modifiedBy', nameField: 'name', restPagination: true, + maxKeyLength: 36, // confirmed max length type: 'dataExtract', typeDescription: 'Creates zipped files in your FTP directory or convert XML into CSV.', typeRetrieveByDefault: true, diff --git a/lib/metadataTypes/definitions/EmailSend.definition.js b/lib/metadataTypes/definitions/EmailSend.definition.js index d8a8241fe..9be557b4e 100644 --- a/lib/metadataTypes/definitions/EmailSend.definition.js +++ b/lib/metadataTypes/definitions/EmailSend.definition.js @@ -14,6 +14,7 @@ module.exports = { lastmodDateField: 'ModifiedDate', lastmodNameField: null, restPagination: null, + maxKeyLength: 36, // confirmed max length type: 'emailSend', soapType: 'emailSendDefinition', typeDescription: 'Mainly used in Automations as "Send Email Activity".', diff --git a/lib/metadataTypes/definitions/Event.definition.js b/lib/metadataTypes/definitions/Event.definition.js index 9a923af67..b2217d881 100644 --- a/lib/metadataTypes/definitions/Event.definition.js +++ b/lib/metadataTypes/definitions/Event.definition.js @@ -11,6 +11,7 @@ module.exports = { lastmodDateField: 'modifiedDate', lastmodNameField: 'modifiedBy', restPagination: true, + maxKeyLength: 200, // confirmed max length type: 'event', typeDescription: 'Used in Journeys (Interactions) to define Entry Events.', typeRetrieveByDefault: true, diff --git a/lib/metadataTypes/definitions/Filter.definition.js b/lib/metadataTypes/definitions/Filter.definition.js index e93ccfb01..ef6499104 100644 --- a/lib/metadataTypes/definitions/Filter.definition.js +++ b/lib/metadataTypes/definitions/Filter.definition.js @@ -11,6 +11,7 @@ module.exports = { lastmodDateField: 'modifiedDate', lastmodNameField: null, restPagination: true, + maxKeyLength: 36, // confirmed max length type: 'filter', typeDescription: 'BETA: Part of how filtered Data Extensions are created. Depends on type "FilterDefinitions".', diff --git a/lib/metadataTypes/definitions/ImportFile.definition.js b/lib/metadataTypes/definitions/ImportFile.definition.js index 27bf5c20c..b1938c785 100644 --- a/lib/metadataTypes/definitions/ImportFile.definition.js +++ b/lib/metadataTypes/definitions/ImportFile.definition.js @@ -22,6 +22,7 @@ module.exports = { DataExtension: 255, Email: 0, }, + maxKeyLength: 36, // confirmed max length type: 'importFile', typeDescription: 'Reads files in FTP directory for further processing.', typeRetrieveByDefault: true, diff --git a/lib/metadataTypes/definitions/MobileKeyword.definition.js b/lib/metadataTypes/definitions/MobileKeyword.definition.js index 72ac918af..5108ae965 100644 --- a/lib/metadataTypes/definitions/MobileKeyword.definition.js +++ b/lib/metadataTypes/definitions/MobileKeyword.definition.js @@ -12,6 +12,7 @@ module.exports = { lastmodNameField: null, restPagination: true, restPageSize: 50, + maxKeyLength: 50, // assumed max length type: 'mobileKeyword', typeDescription: 'Used for managing subscriptions for Mobile numbers in Mobile Connect', typeRetrieveByDefault: true, diff --git a/lib/metadataTypes/definitions/Query.definition.js b/lib/metadataTypes/definitions/Query.definition.js index adfbff659..98d047421 100644 --- a/lib/metadataTypes/definitions/Query.definition.js +++ b/lib/metadataTypes/definitions/Query.definition.js @@ -21,6 +21,7 @@ module.exports = { Overwrite: 0, Update: 1, }, + maxKeyLength: 36, // confirmed max length type: 'query', typeDescription: 'Select & transform data using SQL.', typeRetrieveByDefault: true, diff --git a/lib/metadataTypes/definitions/Role.definition.js b/lib/metadataTypes/definitions/Role.definition.js index d6de32622..0f5a6c0b6 100644 --- a/lib/metadataTypes/definitions/Role.definition.js +++ b/lib/metadataTypes/definitions/Role.definition.js @@ -23,6 +23,7 @@ module.exports = { createdNameField: null, lastmodDateField: 'ModifiedDate', lastmodNameField: null, + maxKeyLength: 36, // confirmed max length type: 'role', typeDescription: 'User Roles define groups that are used to grant users access to SFMC systems.', diff --git a/lib/metadataTypes/definitions/TriggeredSend.definition.js b/lib/metadataTypes/definitions/TriggeredSend.definition.js index 789afad7f..e5afad57e 100644 --- a/lib/metadataTypes/definitions/TriggeredSend.definition.js +++ b/lib/metadataTypes/definitions/TriggeredSend.definition.js @@ -21,6 +21,7 @@ module.exports = { lastmodDateField: 'ModifiedDate', lastmodNameField: null, restPagination: null, + maxKeyLength: 36, // confirmed max length type: 'triggeredSend', soapType: 'triggeredSendDefinition', typeDescription: 'DEPRECATED: Sends emails via API or DataExtension Event.', diff --git a/lib/metadataTypes/definitions/User.definition.js b/lib/metadataTypes/definitions/User.definition.js index 34d3b5e0b..34fea1006 100644 --- a/lib/metadataTypes/definitions/User.definition.js +++ b/lib/metadataTypes/definitions/User.definition.js @@ -12,6 +12,7 @@ module.exports = { createdNameField: null, lastmodDateField: 'ModifiedDate', lastmodNameField: 'Client.ModifiedBy', + maxKeyLength: 50, // confirmed max length type: 'user', soapType: 'AccountUser', typeDescription: 'Marketing Cloud users', diff --git a/lib/util/devops.js b/lib/util/devops.js index 208e838a0..d08f51fff 100644 --- a/lib/util/devops.js +++ b/lib/util/devops.js @@ -293,17 +293,19 @@ const DevOps = { } /** @type {TYPE.DeltaPkgItem[]} */ - const copied = delta.map((file) => - File.copyFile( - file.file, - path - .normalize(file.file) - .replace( - path.normalize(properties.directories.retrieve), - path.normalize(properties.directories.deploy) - ) - ) - ); + const copied = delta + .filter((file) => !file.file.endsWith('.md')) // filter documentation files + .map((file) => + File.copyFile( + file.file, + path + .normalize(file.file) + .replace( + path.normalize(properties.directories.retrieve), + path.normalize(properties.directories.deploy) + ) + ) + ); const results = await Promise.all(copied); const failed = results.filter((result) => result.status === 'failed'); const skipped = results.filter((result) => result.status === 'skipped'); diff --git a/lib/util/util.js b/lib/util/util.js index 482cbd0c5..a5bfe7cf3 100644 --- a/lib/util/util.js +++ b/lib/util/util.js @@ -85,7 +85,6 @@ const Util = { * * @param {string} mlName name of marketList * @param {TYPE.Mcdevrc} properties General configuration to be used in retrieve - * @returns {void} throws errors if problems were found */ verifyMarketList(mlName, properties) { if (properties.marketList[mlName]) { @@ -227,6 +226,65 @@ const Util = { return typeChoices; }, + /** + * wrapper around our standard winston logging to console and logfile + * + * @param {boolean} [noLogFile=false] optional flag to indicate if we should log to file; CLI logs are always on + * @returns {object} initiated logger for console and file + */ + _createNewLoggerTransport: function (noLogFile = false) { + // { + // error: 0, + // warn: 1, + // info: 2, + // http: 3, + // verbose: 4, + // debug: 5, + // silly: 6 + // } + const logFileName = new Date().toISOString().split(':').join('.'); + const transports = { + console: new winston.transports.Console({ + // Write logs to Console + level: Util.OPTIONS.loggerLevel || 'info', + format: winston.format.combine( + winston.format.colorize(), + winston.format.timestamp({ format: 'HH:mm:ss' }), + winston.format.simple(), + winston.format.printf( + (info) => `${info.timestamp} ${info.level}: ${info.message}` + ) + ), + }), + }; + if (!noLogFile) { + transports.file = new winston.transports.File({ + // Write logs to logfile + filename: 'logs/' + logFileName + '.log', + level: 'debug', // log everything + format: winston.format.combine( + winston.format.timestamp({ format: 'HH:mm:ss.SSS' }), + winston.format.simple(), + winston.format.printf( + (info) => `${info.timestamp} ${info.level}: ${info.message}` + ) + ), + }); + transports.fileError = new winston.transports.File({ + // Write logs to additional error-logfile for better visibility of errors + filename: 'logs/' + logFileName + '-errors.log', + level: 'error', // only log errors + format: winston.format.combine( + winston.format.timestamp({ format: 'HH:mm:ss.SSS' }), + winston.format.simple(), + winston.format.printf( + (info) => `${info.timestamp} ${info.level}: ${info.message}` + ) + ), + }); + } + return transports; + }, loggerTransports: null, /** @@ -235,7 +293,91 @@ const Util = { * @type {TYPE.Logger} */ logger: null, - restartLogger: startLogger, + /** + * initiate winston logger + * + * @param {boolean} [restart=false] if true, logger will be restarted; otherwise, an existing logger will be used + * @param {boolean} [noLogFile=false] if false, logger will log to file; otherwise, only to console + * @returns {void} + */ + startLogger: function (restart = false, noLogFile = false) { + if ( + !( + Util.loggerTransports === null || + restart || + (!Util.loggerTransports?.file && !noLogFile && !Util.OPTIONS?.noLogFile) + ) + ) { + // logger already started + return; + } + Util.loggerTransports = this._createNewLoggerTransport( + noLogFile || Util.OPTIONS?.noLogFile + ); + const myWinston = winston.createLogger({ + level: Util.OPTIONS.loggerLevel, + levels: winston.config.npm.levels, + transports: Object.values(Util.loggerTransports), + }); + const winstonError = myWinston.error; + const winstonExtension = { + /** + * helper that prints better stack trace for errors + * + * @param {Error} ex the error + * @param {string} [message] optional custom message to be printed as error together with the exception's message + * @returns {void} + */ + errorStack: function (ex, message) { + if (message) { + // ! this method only sets exitCode=1 if message-param was set + // if not, then this method purely outputs debug information and should not change the exitCode + winstonError(message + ':'); + winstonError(` ${ex.message} (${ex.code})`); + if (ex.endpoint) { + // ex.endpoint is only available if 'ex' is of type RestError + winstonError(' endpoint: ' + ex.endpoint); + } + Util.signalFatalError(); + } + let stack; + /* eslint-disable unicorn/prefer-ternary */ + if ( + [ + 'ETIMEDOUT', + 'EHOSTUNREACH', + 'ENOTFOUND', + 'ECONNRESET', + 'ECONNABORTED', + ].includes(ex.code) + ) { + // the stack would just return a one-liner that does not help + stack = new Error().stack; // eslint-disable-line unicorn/error-message + } else { + stack = ex.stack; + } + /* eslint-enable unicorn/prefer-ternary */ + myWinston.debug(stack); + }, + /** + * errors should cause surrounding applications to take notice + * hence we overwrite the default error function here + * + * @param {string} msg - the message to log + * @returns {void} + */ + error: function (msg) { + winstonError(msg); + Util.signalFatalError(); + }, + }; + Util.logger = Object.assign(myWinston, winstonExtension); + + const processArgv = process.argv.slice(2); + Util.logger.debug( + `:: mcdev ${packageJsonMcdev.version} :: ⚡ mcdev ${processArgv.join(' ')}` + ); + }, /** * Logger helper for Metadata functions * @@ -416,7 +558,7 @@ const Util = { * @param {string} cmd to be executed command * @param {string[]} [args] list of arguments * @param {boolean} [hideOutput] if true, output of command will be hidden from CLI - * @returns {string} output of command if hideOutput is true + * @returns {string|void} output of command if hideOutput is true */ execSync(cmd, args, hideOutput) { args ||= []; @@ -471,34 +613,30 @@ const Util = { * @returns {void} */ setLoggingLevel(argv) { - Util.loggerTransports.console.file = 'debug'; if (argv.silent) { // only errors printed to CLI - Util.logger.level = 'error'; Util.OPTIONS.loggerLevel = 'error'; - Util.loggerTransports.console.level = 'error'; Util.logger.debug('CLI logger set to: silent'); } else if (argv.verbose) { // chatty user cli logs - Util.logger.level = 'verbose'; Util.OPTIONS.loggerLevel = 'verbose'; - Util.loggerTransports.console.level = 'verbose'; Util.logger.debug('CLI logger set to: verbose'); } else { // default user cli logs - // TODO to be switched to "warn" when cli-process is integrated - Util.logger.level = 'info'; Util.OPTIONS.loggerLevel = 'info'; - Util.loggerTransports.console.level = 'info'; Util.logger.debug('CLI logger set to: info / default'); } if (argv.debug) { // enables developer output & features. no change to actual logs - Util.logger.level = 'debug'; Util.OPTIONS.loggerLevel = 'debug'; - Util.loggerTransports.console.level = 'debug'; Util.logger.debug('CLI logger set to: debug'); } + if (Util.loggerTransports?.console) { + Util.loggerTransports.console.level = Util.OPTIONS.loggerLevel; + } + if (Util.logger) { + Util.logger.level = Util.OPTIONS.loggerLevel; + } }, /** * outputs a warning that the given type is still in beta @@ -643,122 +781,7 @@ const Util = { return null; }, }; -/** - * wrapper around our standard winston logging to console and logfile - * - * @returns {object} initiated logger for console and file - */ -function createNewLoggerTransport() { - // { - // error: 0, - // warn: 1, - // info: 2, - // http: 3, - // verbose: 4, - // debug: 5, - // silly: 6 - // } - const logFileName = new Date().toISOString().split(':').join('.'); - return { - console: new winston.transports.Console({ - // Write logs to Console - level: 'info', // log error, warn, info - format: winston.format.combine( - winston.format.colorize(), - winston.format.timestamp({ format: 'HH:mm:ss' }), - winston.format.simple(), - winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`) - ), - }), - file: new winston.transports.File({ - // Write logs to logfile - filename: 'logs/' + logFileName + '.log', - level: 'debug', // log everything - format: winston.format.combine( - winston.format.timestamp({ format: 'HH:mm:ss.SSS' }), - winston.format.simple(), - winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`) - ), - }), - fileError: new winston.transports.File({ - // Write logs to additional error-logfile for better visibility of errors - filename: 'logs/' + logFileName + '-errors.log', - level: 'error', // only log errors - format: winston.format.combine( - winston.format.timestamp({ format: 'HH:mm:ss.SSS' }), - winston.format.simple(), - winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`) - ), - }), - }; -} -/** - * initiate winston logger - * - * @returns {void} - */ -function startLogger() { - Util.loggerTransports = createNewLoggerTransport(); - const myWinston = winston.createLogger({ - levels: winston.config.npm.levels, - transports: [ - Util.loggerTransports.console, - Util.loggerTransports.file, - Util.loggerTransports.fileError, - ], - }); - const winstonError = myWinston.error; - const winstonExtension = { - /** - * helper that prints better stack trace for errors - * - * @param {Error} ex the error - * @param {string} [message] optional custom message to be printed as error together with the exception's message - * @returns {void} - */ - errorStack: function (ex, message) { - if (message) { - // ! this method only sets exitCode=1 if message-param was set - // if not, then this method purely outputs debug information and should not change the exitCode - winstonError(message + ':'); - winstonError(` ${ex.message} (${ex.code})`); - if (ex.endpoint) { - // ex.endpoint is only available if 'ex' is of type RestError - winstonError(' endpoint: ' + ex.endpoint); - } - Util.signalFatalError(); - } - let stack; - /* eslint-disable unicorn/prefer-ternary */ - if ( - ['ETIMEDOUT', 'EHOSTUNREACH', 'ENOTFOUND', 'ECONNRESET', 'ECONNABORTED'].includes( - ex.code - ) - ) { - // the stack would just return a one-liner that does not help - stack = new Error().stack; // eslint-disable-line unicorn/error-message - } else { - stack = ex.stack; - } - /* eslint-enable unicorn/prefer-ternary */ - myWinston.debug(stack); - }, - /** - * errors should cause surrounding applications to take notice - * hence we overwrite the default error function here - * - * @param {string} msg - the message to log - * @returns {void} - */ - error: function (msg) { - winstonError(msg); - Util.signalFatalError(); - }, - }; - Util.logger = Object.assign(myWinston, winstonExtension); - Util.logger.debug(`:: mcdev ${packageJsonMcdev.version} ::`); -} -startLogger(); +Util.startLogger(false, true); module.exports = Util; diff --git a/package-lock.json b/package-lock.json index cdc2e50d5..f9ca5851b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mcdev", - "version": "5.0.2", + "version": "5.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "mcdev", - "version": "5.0.2", + "version": "5.1.0", "license": "MIT", "dependencies": { "beauty-amp-core": "0.3.7", @@ -27,7 +27,7 @@ "simple-git": "3.18.0", "toposort": "2.0.2", "update-notifier": "5.1.0", - "winston": "3.8.2", + "winston": "3.9.0", "yargs": "17.7.2" }, "bin": { @@ -38,14 +38,14 @@ "axios-mock-adapter": "1.21.3", "chai": "4.3.7", "chai-files": "1.4.0", - "eslint": "8.41.0", + "eslint": "8.42.0", "eslint-config-prettier": "8.7.0", "eslint-config-ssjs": "1.1.11", - "eslint-plugin-jsdoc": "45.0.0", + "eslint-plugin-jsdoc": "46.2.5", "eslint-plugin-mocha": "10.1.0", "eslint-plugin-prettier": "4.2.1", "eslint-plugin-unicorn": "47.0.0", - "fast-xml-parser": "4.2.2", + "fast-xml-parser": "4.2.4", "husky": "8.0.3", "jsdoc-to-markdown": "8.0.0", "lint-staged": "13.2.2", @@ -590,18 +590,18 @@ "dev": true }, "node_modules/@eslint/js": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz", - "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", + "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -3138,16 +3138,16 @@ } }, "node_modules/eslint": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", - "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", + "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.41.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint/js": "8.42.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -3221,9 +3221,9 @@ } }, "node_modules/eslint-plugin-jsdoc": { - "version": "45.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-45.0.0.tgz", - "integrity": "sha512-l2+Jcs/Ps7oFA+SWY+0sweU/e5LgricnEl6EsDlyRTF5y0+NWL1y9Qwz9PHwHAxtdJq6lxPjEQWmYLMkvhzD4g==", + "version": "46.2.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.2.5.tgz", + "integrity": "sha512-Rmd0pb6S5fv9/lGbJMiVUZn56XvjKTGQoq9H5yfNjj6jcJHkTaq+Pqj2KHK/8EO01f8auFFy2kNL64cFisMEDw==", "dev": true, "dependencies": { "@es-joy/jsdoccomment": "~0.39.4", @@ -3232,6 +3232,7 @@ "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "esquery": "^1.5.0", + "is-builtin-module": "^3.2.1", "semver": "^7.5.1", "spdx-expression-parse": "^3.0.1" }, @@ -3636,9 +3637,9 @@ "dev": true }, "node_modules/fast-xml-parser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.2.tgz", - "integrity": "sha512-DLzIPtQqmvmdq3VUKR7T6omPK/VCRNqgFlGtbESfyhcH2R4I8EzK1/K6E8PkRCK2EabWrUHK32NjYRbEFnnz0Q==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.4.tgz", + "integrity": "sha512-fbfMDvgBNIdDJLdLOwacjFAPYt67tr31H9ZhWSm45CDAxvd0I6WTlSOUo7K2P/K5sA5JgMKG64PI3DMcaFdWpQ==", "dev": true, "funding": [ { @@ -9757,9 +9758,9 @@ } }, "node_modules/winston": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", - "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.9.0.tgz", + "integrity": "sha512-jW51iW/X95BCW6MMtZWr2jKQBP4hV5bIDq9QrIjfDk6Q9QuxvTKEAlpUNAzP+HYHFFCeENhph16s0zEunu4uuQ==", "dependencies": { "@colors/colors": "1.5.0", "@dabh/diagnostics": "^2.0.2", @@ -10395,15 +10396,15 @@ } }, "@eslint/js": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.41.0.tgz", - "integrity": "sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.42.0.tgz", + "integrity": "sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==", "dev": true }, "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", "dev": true, "requires": { "@humanwhocodes/object-schema": "^1.2.1", @@ -12334,16 +12335,16 @@ "dev": true }, "eslint": { - "version": "8.41.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.41.0.tgz", - "integrity": "sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==", + "version": "8.42.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.42.0.tgz", + "integrity": "sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.4.0", "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.41.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint/js": "8.42.0", + "@humanwhocodes/config-array": "^0.11.10", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -12415,9 +12416,9 @@ "requires": {} }, "eslint-plugin-jsdoc": { - "version": "45.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-45.0.0.tgz", - "integrity": "sha512-l2+Jcs/Ps7oFA+SWY+0sweU/e5LgricnEl6EsDlyRTF5y0+NWL1y9Qwz9PHwHAxtdJq6lxPjEQWmYLMkvhzD4g==", + "version": "46.2.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-46.2.5.tgz", + "integrity": "sha512-Rmd0pb6S5fv9/lGbJMiVUZn56XvjKTGQoq9H5yfNjj6jcJHkTaq+Pqj2KHK/8EO01f8auFFy2kNL64cFisMEDw==", "dev": true, "requires": { "@es-joy/jsdoccomment": "~0.39.4", @@ -12426,6 +12427,7 @@ "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "esquery": "^1.5.0", + "is-builtin-module": "^3.2.1", "semver": "^7.5.1", "spdx-expression-parse": "^3.0.1" }, @@ -12682,9 +12684,9 @@ "dev": true }, "fast-xml-parser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.2.tgz", - "integrity": "sha512-DLzIPtQqmvmdq3VUKR7T6omPK/VCRNqgFlGtbESfyhcH2R4I8EzK1/K6E8PkRCK2EabWrUHK32NjYRbEFnnz0Q==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.4.tgz", + "integrity": "sha512-fbfMDvgBNIdDJLdLOwacjFAPYt67tr31H9ZhWSm45CDAxvd0I6WTlSOUo7K2P/K5sA5JgMKG64PI3DMcaFdWpQ==", "dev": true, "requires": { "strnum": "^1.0.5" @@ -17223,9 +17225,9 @@ } }, "winston": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", - "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.9.0.tgz", + "integrity": "sha512-jW51iW/X95BCW6MMtZWr2jKQBP4hV5bIDq9QrIjfDk6Q9QuxvTKEAlpUNAzP+HYHFFCeENhph16s0zEunu4uuQ==", "requires": { "@colors/colors": "1.5.0", "@dabh/diagnostics": "^2.0.2", diff --git a/package.json b/package.json index 255dd3212..e9975d472 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcdev", - "version": "5.0.2", + "version": "5.1.0", "description": "Accenture Salesforce Marketing Cloud DevTools", "author": "Accenture: joern.berkefeld, douglas.midgley, robert.zimmermann, maciej.barnas", "license": "MIT", @@ -76,7 +76,7 @@ "simple-git": "3.18.0", "toposort": "2.0.2", "update-notifier": "5.1.0", - "winston": "3.8.2", + "winston": "3.9.0", "yargs": "17.7.2" }, "devDependencies": { @@ -84,14 +84,14 @@ "axios-mock-adapter": "1.21.3", "chai": "4.3.7", "chai-files": "1.4.0", - "eslint": "8.41.0", + "eslint": "8.42.0", "eslint-config-prettier": "8.7.0", "eslint-config-ssjs": "1.1.11", - "eslint-plugin-jsdoc": "45.0.0", + "eslint-plugin-jsdoc": "46.2.5", "eslint-plugin-mocha": "10.1.0", "eslint-plugin-prettier": "4.2.1", "eslint-plugin-unicorn": "47.0.0", - "fast-xml-parser": "4.2.2", + "fast-xml-parser": "4.2.4", "husky": "8.0.3", "jsdoc-to-markdown": "8.0.0", "lint-staged": "13.2.2", diff --git a/test/general.test.js b/test/general.test.js index 1a3036d66..b92831f7a 100644 --- a/test/general.test.js +++ b/test/general.test.js @@ -14,6 +14,22 @@ describe('GENERAL', () => { testUtils.mockReset(); }); + describe('init ================', () => { + it('should init a local project without downloading BUs'); + it('should init a local project and download all BUs'); + }); + describe('join ================', () => { + it('should clone a project from git'); + }); + describe('upgrade ================', () => { + it('should upgrade a project to the latest version'); + }); + describe('reloadBUs ================', () => { + it('should load all BUs from the server and refresh the config'); + }); + describe('selectTypes ================', () => { + it('should change which types are selected for default retrieval'); + }); describe('explainTypes ================', () => { it('without options', () => { handler.explainTypes(); @@ -59,4 +75,14 @@ describe('GENERAL', () => { return; }); }); + describe('createDeltaPkg ================', () => { + it('should show diff to master branch'); + // mcdev createDeltaPkg master # resolves to master..HEAD + it('should show diff between master and develop branch'); + // mcdev createDeltaPkg master..develop + it( + 'should show diff between master and develop branch and filter the results to only show MyProject/BU1' + ); + // mcdev createDeltaPkg master..develop --filter 'MyProject/BU1' + }); }); diff --git a/test/mockRoot/.mcdevrc.json b/test/mockRoot/.mcdevrc.json index f8e5c389c..29f14ebd1 100644 --- a/test/mockRoot/.mcdevrc.json +++ b/test/mockRoot/.mcdevrc.json @@ -78,5 +78,5 @@ "triggeredSend" ] }, - "version": "5.0.2" + "version": "5.1.0" } 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 new file mode 100644 index 000000000..b901a271a --- /dev/null +++ b/test/mockRoot/deploy/testInstance/testBU/automation/testExisting_automation.automation-meta.json @@ -0,0 +1,53 @@ +{ + "description": "updated on deploy", + "key": "testExisting_automation", + "name": "testExisting_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 + }, + "status": "PausedSchedule", + "steps": [ + { + "activities": [ + { + "name": "testExisting_dataExtract", + "r__type": "dataExtract" + }, + { + "name": "testExisting_emailSend", + "r__type": "emailSend" + }, + { + "name": "testExisting_fileTransfer", + "r__type": "fileTransfer" + }, + { + "name": "testExisting_importFile", + "r__type": "importFile" + }, + { + "name": "testExisting_query", + "r__type": "query" + }, + { + "name": "testExisting_script", + "r__type": "script" + } + ], + "name": "" + } + ], + "notifications": [ + { + "email": ["error-updated@test.accenture.com"], + "message": "test updated", + "type": "Error" + } + ], + "type": "scheduled" +} 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 new file mode 100644 index 000000000..2a1caee12 --- /dev/null +++ b/test/mockRoot/deploy/testInstance/testBU/automation/testNew_automation.automation-meta.json @@ -0,0 +1,46 @@ +{ + "description": "created on deploy", + "key": "testNew_automation", + "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 + }, + "status": "PausedSchedule", + "steps": [ + { + "activities": [ + { + "name": "testExisting_dataExtract", + "r__type": "dataExtract" + }, + { + "name": "testExisting_emailSend", + "r__type": "emailSend" + }, + { + "name": "testExisting_fileTransfer", + "r__type": "fileTransfer" + }, + { + "name": "testExisting_importFile", + "r__type": "importFile" + }, + { + "name": "testExisting_query", + "r__type": "query" + }, + { + "name": "testExisting_script", + "r__type": "script" + } + ], + "name": "" + } + ], + "type": "scheduled" +} diff --git a/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json b/test/mockRoot/deploy/testInstance/testBU/query/testExisting_query.query-meta.json similarity index 80% rename from test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json rename to test/mockRoot/deploy/testInstance/testBU/query/testExisting_query.query-meta.json index 72ba5f157..421e918e5 100644 --- a/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +++ b/test/mockRoot/deploy/testInstance/testBU/query/testExisting_query.query-meta.json @@ -1,6 +1,6 @@ { - "name": "testExistingQuery", - "key": "testExistingQuery", + "name": "testExisting_query", + "key": "testExisting_query", "description": "updated on deploy", "targetKey": "testExisting_dataExtension", "createdDate": "2022-04-26T15:21:16.453", diff --git a/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.sql b/test/mockRoot/deploy/testInstance/testBU/query/testExisting_query.query-meta.sql similarity index 100% rename from test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.sql rename to test/mockRoot/deploy/testInstance/testBU/query/testExisting_query.query-meta.sql diff --git a/test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.json b/test/mockRoot/deploy/testInstance/testBU/query/testNew_query.query-meta.json similarity index 82% rename from test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.json rename to test/mockRoot/deploy/testInstance/testBU/query/testNew_query.query-meta.json index b2fc695a7..7e4bdc099 100644 --- a/test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.json +++ b/test/mockRoot/deploy/testInstance/testBU/query/testNew_query.query-meta.json @@ -1,6 +1,6 @@ { - "name": "testNewQuery", - "key": "testNewQuery", + "name": "testNew_query", + "key": "testNew_query", "description": "created on deploy", "targetKey": "testExisting_dataExtension", "createdDate": "2022-04-26T15:21:16.453", diff --git a/test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.sql b/test/mockRoot/deploy/testInstance/testBU/query/testNew_query.query-meta.sql similarity index 100% rename from test/mockRoot/deploy/testInstance/testBU/query/testNewQuery.query-meta.sql rename to test/mockRoot/deploy/testInstance/testBU/query/testNew_query.query-meta.sql diff --git a/test/resourceFactory.js b/test/resourceFactory.js index 13b9a6645..5cdd55508 100644 --- a/test/resourceFactory.js +++ b/test/resourceFactory.js @@ -10,33 +10,55 @@ 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 * @returns {string} relevant metadata stringified */ -exports.loadSOAPRecords = async (mcdevAction, type, mid) => { +exports.loadSOAPRecords = async (mcdevAction, type, mid, filter) => { type = type[0].toLowerCase() + type.slice(1); - const testPath = path.join( - 'test', - 'resources', - mid.toString(), - type, - mcdevAction + '-response.xml' - ); - if (await fs.pathExists(testPath)) { - return fs.readFile(testPath, { + const testPath = path.join('test', 'resources', mid.toString(), type, mcdevAction); + const filterPath = this.filterToPath(filter); + if (await fs.pathExists(testPath + filterPath + '-response.xml')) { + return fs.readFile(testPath + filterPath + '-response.xml', { + encoding: 'utf8', + }); + } else if (await fs.pathExists(testPath + '-response.xml')) { + if (filterPath) { + /* eslint-disable no-console */ + console.log( + `${color.bgYellow}${color.fgBlack}test-warning${ + color.reset + }: You are loading your reponse from ${ + testPath + '-response.xml' + } instead of the more specific ${ + testPath + filterPath + '-response.xml' + }. Make sure this is intended` + ); + /* eslint-enable no-console */ + } + return fs.readFile(testPath + '-response.xml', { encoding: 'utf8', }); } /* eslint-disable no-console */ console.log( - `${color.bgRed}${color.fgBlack}test-error${color.reset}: Please create file ${testPath}` + `${color.bgRed}${color.fgBlack}test-error${color.reset}: Please create file ${ + testPath + filterPath + '-response.xml' + } or ${testPath + '-response.xml'}` ); /* eslint-enable no-console */ - process.exitCode = 404; + // return error + process.exitCode = 404; return fs.readFile(path.join('test', 'resources', mcdevAction + '-response.xml'), { encoding: 'utf8', }); }; +exports.filterToPath = (filter) => { + if (filter && filter.Property && filter.SimpleOperator && filter.Value) { + return `-${filter.Property}${filter.SimpleOperator.replace('equals', '=')}${filter.Value}`; + } + return ''; +}; /** * based on request, respond with different soap data * @@ -53,7 +75,8 @@ exports.handleSOAPRequest = async (config) => { responseXML = await this.loadSOAPRecords( config.headers.SOAPAction.toLocaleLowerCase(), jObj.Envelope.Body.RetrieveRequestMsg.RetrieveRequest.ObjectType, - jObj.Envelope.Header.fueloauth + jObj.Envelope.Header.fueloauth, + jObj.Envelope.Body.RetrieveRequestMsg.RetrieveRequest.Filter ); break; @@ -62,7 +85,8 @@ exports.handleSOAPRequest = async (config) => { responseXML = await this.loadSOAPRecords( config.headers.SOAPAction.toLocaleLowerCase(), fullObj.Envelope.Body.CreateRequest.Objects['@_xsi:type'], - jObj.Envelope.Header.fueloauth + jObj.Envelope.Header.fueloauth, + null ); break; @@ -71,7 +95,8 @@ exports.handleSOAPRequest = async (config) => { responseXML = await this.loadSOAPRecords( config.headers.SOAPAction.toLocaleLowerCase(), fullObj.Envelope.Body.UpdateRequest.Objects['@_xsi:type'], - jObj.Envelope.Header.fueloauth + jObj.Envelope.Header.fueloauth, + null ); break; @@ -82,7 +107,18 @@ exports.handleSOAPRequest = async (config) => { fullObj.Envelope.Body.ConfigureRequestMsg.Configurations.Configuration[0][ '@_xsi:type' ], - jObj.Envelope.Header.fueloauth + jObj.Envelope.Header.fueloauth, + null + ); + + break; + } + case 'Delete': { + responseXML = await this.loadSOAPRecords( + config.headers.SOAPAction.toLocaleLowerCase(), + fullObj.Envelope.Body.DeleteRequest.Objects['@_xsi:type'], + jObj.Envelope.Header.fueloauth, + null ); break; @@ -121,15 +157,15 @@ exports.handleRESTRequest = async (config) => { 'resources', config.headers.Authorization.replace('Bearer ', ''), urlObj.pathname, - config.method + '-response.json' + config.method + '-response' ) .replace(':', '_'); // replace : with _ for Windows - if (await fs.pathExists(testPath)) { + if (await fs.pathExists(testPath + '.json')) { // build filter logic to ensure templating works if (filterName) { const response = JSON.parse( - await fs.readFile(testPath, { + await fs.readFile(testPath + '.json', { encoding: 'utf8', }) ); @@ -139,15 +175,22 @@ exports.handleRESTRequest = async (config) => { } else { return [ 200, - await fs.readFile(testPath, { + await fs.readFile(testPath + '.json', { encoding: 'utf8', }), ]; } + } else if (await fs.pathExists(testPath + '.txt')) { + return [ + 200, + await fs.readFile(testPath + '.txt', { + encoding: 'utf8', + }), + ]; } else { /* eslint-disable no-console */ console.log( - `${color.bgRed}${color.fgBlack}test-error${color.reset}: Please create file ${testPath}` + `${color.bgRed}${color.fgBlack}test-error${color.reset}: Please create file ${testPath}.json/.txt` ); /* eslint-enable no-console */ process.exitCode = 404; diff --git a/test/resources/9999999/automation/build-expected.json b/test/resources/9999999/automation/build-expected.json new file mode 100644 index 000000000..ecd94cb33 --- /dev/null +++ b/test/resources/9999999/automation/build-expected.json @@ -0,0 +1,58 @@ +{ + "description": "foobar", + "key": "testTemplated_automation", + "name": "testTemplated_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 + }, + "status": "PausedSchedule", + "steps": [ + { + "activities": [ + { + "name": "testTemplated_dataExtract", + "r__type": "dataExtract" + }, + { + "name": "testTemplated_emailSend", + "r__type": "emailSend" + }, + { + "name": "testTemplated_fileTransfer", + "r__type": "fileTransfer" + }, + { + "name": "testTemplated_importFile", + "r__type": "importFile" + }, + { + "name": "testTemplated_query", + "r__type": "query" + }, + { + "name": "testTemplated_script", + "r__type": "script" + } + ], + "name": "" + } + ], + "type": "scheduled", + "notifications": [ + { + "email": ["complete@test.accenture.com"], + "message": "", + "type": "Complete" + }, + { + "email": ["error@test.accenture.com"], + "message": "test", + "type": "Error" + } + ] +} diff --git a/test/resources/9999999/automation/create-expected.json b/test/resources/9999999/automation/create-expected.json new file mode 100644 index 000000000..2a1caee12 --- /dev/null +++ b/test/resources/9999999/automation/create-expected.json @@ -0,0 +1,46 @@ +{ + "description": "created on deploy", + "key": "testNew_automation", + "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 + }, + "status": "PausedSchedule", + "steps": [ + { + "activities": [ + { + "name": "testExisting_dataExtract", + "r__type": "dataExtract" + }, + { + "name": "testExisting_emailSend", + "r__type": "emailSend" + }, + { + "name": "testExisting_fileTransfer", + "r__type": "fileTransfer" + }, + { + "name": "testExisting_importFile", + "r__type": "importFile" + }, + { + "name": "testExisting_query", + "r__type": "query" + }, + { + "name": "testExisting_script", + "r__type": "script" + } + ], + "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 new file mode 100644 index 000000000..d596cc549 --- /dev/null +++ b/test/resources/9999999/automation/create-testNew_automation-expected.md @@ -0,0 +1,28 @@ +## testNew_automation + +**Description:** created on deploy + +**Folder:** my automations/ + +**Started by:** Schedule + +**Status:** PausedSchedule + +**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 + +**Notifications:** _none_ + + +| Step 1
_-_ | +| --- | +| _1.1: dataExtract_
testExisting_dataExtract | +| _1.2: emailSend_
testExisting_emailSend | +| _1.3: fileTransfer_
testExisting_fileTransfer | +| _1.4: importFile_
testExisting_importFile | +| _1.5: query_
testExisting_query | +| _1.6: script_
testExisting_script | diff --git a/test/resources/9999999/automation/delete-response.xml b/test/resources/9999999/automation/delete-response.xml new file mode 100644 index 000000000..54cce0ebc --- /dev/null +++ b/test/resources/9999999/automation/delete-response.xml @@ -0,0 +1,40 @@ + + + + DeleteResponse + urn:uuid:cc45bb83-15a3-4a82-ba48-47ac4fb13000 + urn:uuid:29986848-a0c3-4f3b-bf5a-91151f784449 + http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous + + + 2023-06-02T13:41:30Z + 2023-06-02T13:46:30Z + + + + + + + OK + Program deleted + 0 + + + 08afb0e2-b00a-4c88-ad2e-1f7f8788c560 + testExisting_automation + false + + + true + + + 898702fc-a432-4176-8130-5d6dd5ad9ca6 + OK + + + \ No newline at end of file diff --git a/test/resources/9999999/automation/retrieve-expected.json b/test/resources/9999999/automation/retrieve-expected.json new file mode 100644 index 000000000..fa80b0f99 --- /dev/null +++ b/test/resources/9999999/automation/retrieve-expected.json @@ -0,0 +1,58 @@ +{ + "description": "bla bla", + "key": "testExisting_automation", + "name": "testExisting_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 + }, + "status": "PausedSchedule", + "steps": [ + { + "activities": [ + { + "name": "testExisting_dataExtract", + "r__type": "dataExtract" + }, + { + "name": "testExisting_emailSend", + "r__type": "emailSend" + }, + { + "name": "testExisting_fileTransfer", + "r__type": "fileTransfer" + }, + { + "name": "testExisting_importFile", + "r__type": "importFile" + }, + { + "name": "testExisting_query", + "r__type": "query" + }, + { + "name": "testExisting_script", + "r__type": "script" + } + ], + "name": "" + } + ], + "type": "scheduled", + "notifications": [ + { + "email": ["complete@test.accenture.com"], + "message": "", + "type": "Complete" + }, + { + "email": ["error@test.accenture.com"], + "message": "test", + "type": "Error" + } + ] +} diff --git a/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md b/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md new file mode 100644 index 000000000..22bd4c500 --- /dev/null +++ b/test/resources/9999999/automation/retrieve-testExisting_automation-expected.md @@ -0,0 +1,30 @@ +## testExisting_automation + +**Description:** bla bla + +**Folder:** my automations/ + +**Started by:** Schedule + +**Status:** PausedSchedule + +**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 + +**Notifications:** + +* Complete: complete@test.accenture.com +* Error: error@test.accenture.com ("test") + +| Step 1
_-_ | +| --- | +| _1.1: dataExtract_
testExisting_dataExtract | +| _1.2: emailSend_
testExisting_emailSend | +| _1.3: fileTransfer_
testExisting_fileTransfer | +| _1.4: importFile_
testExisting_importFile | +| _1.5: query_
testExisting_query | +| _1.6: script_
testExisting_script | diff --git a/test/resources/9999999/automation/template-expected.json b/test/resources/9999999/automation/template-expected.json new file mode 100644 index 000000000..038ecc7ff --- /dev/null +++ b/test/resources/9999999/automation/template-expected.json @@ -0,0 +1,58 @@ +{ + "description": "{{{description}}}", + "key": "{{{prefix}}}automation", + "name": "{{{prefix}}}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 + }, + "status": "PausedSchedule", + "steps": [ + { + "activities": [ + { + "name": "{{{prefix}}}dataExtract", + "r__type": "dataExtract" + }, + { + "name": "{{{prefix}}}emailSend", + "r__type": "emailSend" + }, + { + "name": "{{{prefix}}}fileTransfer", + "r__type": "fileTransfer" + }, + { + "name": "{{{prefix}}}importFile", + "r__type": "importFile" + }, + { + "name": "{{{prefix}}}query", + "r__type": "query" + }, + { + "name": "{{{prefix}}}script", + "r__type": "script" + } + ], + "name": "" + } + ], + "type": "scheduled", + "notifications": [ + { + "email": ["complete@test.accenture.com"], + "message": "", + "type": "Complete" + }, + { + "email": ["error@test.accenture.com"], + "message": "test", + "type": "Error" + } + ] +} diff --git a/test/resources/9999999/automation/update-expected.json b/test/resources/9999999/automation/update-expected.json new file mode 100644 index 000000000..47860e944 --- /dev/null +++ b/test/resources/9999999/automation/update-expected.json @@ -0,0 +1,46 @@ +{ + "description": "updated on deploy", + "key": "testExisting_automation", + "name": "testExisting_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 + }, + "status": "PausedSchedule", + "steps": [ + { + "activities": [ + { + "name": "testExisting_dataExtract", + "r__type": "dataExtract" + }, + { + "name": "testExisting_emailSend", + "r__type": "emailSend" + }, + { + "name": "testExisting_fileTransfer", + "r__type": "fileTransfer" + }, + { + "name": "testExisting_importFile", + "r__type": "importFile" + }, + { + "name": "testExisting_query", + "r__type": "query" + }, + { + "name": "testExisting_script", + "r__type": "script" + } + ], + "name": "" + } + ], + "type": "scheduled" +} diff --git a/test/resources/9999999/automation/update-testExisting_automation-expected.md b/test/resources/9999999/automation/update-testExisting_automation-expected.md new file mode 100644 index 000000000..681c2a694 --- /dev/null +++ b/test/resources/9999999/automation/update-testExisting_automation-expected.md @@ -0,0 +1,28 @@ +## testExisting_automation + +**Description:** updated on deploy + +**Folder:** my automations/ + +**Started by:** Schedule + +**Status:** PausedSchedule + +**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 + +**Notifications:** _none_ + + +| Step 1
_-_ | +| --- | +| _1.1: dataExtract_
testExisting_dataExtract | +| _1.2: emailSend_
testExisting_emailSend | +| _1.3: fileTransfer_
testExisting_fileTransfer | +| _1.4: importFile_
testExisting_importFile | +| _1.5: query_
testExisting_query | +| _1.6: script_
testExisting_script | diff --git a/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json b/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json new file mode 100644 index 000000000..5d86c87b2 --- /dev/null +++ b/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json @@ -0,0 +1,85 @@ +{ + "id": "08afb0e2-b00a-4c88-ad2e-1f7f8788c560", + "name": "testExisting_automation", + "description": "bla bla", + "key": "testExisting_automation", + "typeId": 1, + "type": "scheduled", + "statusId": 4, + "status": "PausedSchedule", + "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": "paused", + "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/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/patch-response.json b/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/patch-response.json new file mode 100644 index 000000000..1c0dc1b08 --- /dev/null +++ b/test/resources/9999999/automation/v1/automations/08afb0e2-b00a-4c88-ad2e-1f7f8788c560/patch-response.json @@ -0,0 +1,85 @@ +{ + "legacyId": "RkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow", + "name": "testExisting_automation", + "description": "updated on deploy", + "key": "testExisting_automation", + "typeId": 1, + "type": "scheduled", + "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 + }, + "steps": [ + { + "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 + } + ], + "annotation": "", + "stepNumber": 0 + } + ], + "categoryId": 290937, + "id": "08afb0e2-b00a-4c88-ad2e-1f7f8788c560" +} 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 new file mode 100644 index 000000000..b58d358fb --- /dev/null +++ b/test/resources/9999999/automation/v1/automations/a8afb0e2-b00a-4c88-ad2e-1f7f8788c560/get-response.json @@ -0,0 +1,85 @@ +{ + "id": "a8afb0e2-b00a-4c88-ad2e-1f7f8788c560", + "name": "testNew_automation", + "description": "created on deploy", + "key": "testNew_automation", + "typeId": 1, + "type": "scheduled", + "statusId": 4, + "status": "PausedSchedule", + "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": "paused", + "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/post-response.json b/test/resources/9999999/automation/v1/automations/post-response.json new file mode 100644 index 000000000..6f82ef7c9 --- /dev/null +++ b/test/resources/9999999/automation/v1/automations/post-response.json @@ -0,0 +1,85 @@ +{ + "legacyId": "NewRkpOcE9qSVh2VUdnYTVJbWFfWW14dzoyNTow", + "name": "testNew_automation", + "description": "created on deploy", + "key": "testNew_automation", + "typeId": 1, + "type": "scheduled", + "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 + }, + "steps": [ + { + "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 + } + ], + "annotation": "", + "stepNumber": 0 + } + ], + "categoryId": 290937, + "id": "a8afb0e2-b00a-4c88-ad2e-1f7f8788c560" +} diff --git a/test/resources/9999999/automation/v1/dataextracts/56c5370a-f988-4f36-b0ee-0f876573f6d7/get-response.json b/test/resources/9999999/automation/v1/dataextracts/56c5370a-f988-4f36-b0ee-0f876573f6d7/get-response.json new file mode 100644 index 000000000..70564f615 --- /dev/null +++ b/test/resources/9999999/automation/v1/dataextracts/56c5370a-f988-4f36-b0ee-0f876573f6d7/get-response.json @@ -0,0 +1,38 @@ +{ + "dataExtractDefinitionId": "56c5370a-f988-4f36-b0ee-0f876573f6d7", + "name": "testExisting_dataExtract", + "key": "testExisting_dataExtract", + "description": "blabla", + "dataExtractTypeId": "bb94a04d-9632-4623-be47-daabc3f588a6", + "fileSpec": "testExisting-%%Year%%-%%Month%%-%%Day%%", + "createdBy": 700301950, + "modifiedBy": 700301950, + "intervalType": 0, + "dataFields": [ + { + "name": "ColumnDelimiter", + "type": "string", + "value": "," + }, + { + "name": "DECustomerKey", + "type": "string", + "value": "testExisting_dataExtension" + }, + { + "name": "HasColumnHeaders", + "type": "bool", + "value": "False" + }, + { + "name": "TextQualified", + "type": "bool", + "value": "False" + }, + { + "name": "UsesLineFeed", + "type": "bool", + "value": "False" + } + ] +} diff --git a/test/resources/9999999/automation/v1/dataextracts/get-response.json b/test/resources/9999999/automation/v1/dataextracts/get-response.json new file mode 100644 index 000000000..462a69793 --- /dev/null +++ b/test/resources/9999999/automation/v1/dataextracts/get-response.json @@ -0,0 +1,20 @@ +{ + "count": 1, + "page": 1, + "pageSize": 500, + "items": [ + { + "dataExtractDefinitionId": "56c5370a-f988-4f36-b0ee-0f876573f6d7", + "name": "testExisting_dataExtract", + "key": "testExisting_dataExtract", + "description": "bla bla", + "dataExtractTypeId": "bb94a04d-9632-4623-be47-daabc3f588a6", + "fileSpec": "testExisting-%%Year%%-%%Month%%-%%Day%%", + "createdDate": "2022-11-09T05:31:21.667", + "modifiedDate": "2022-11-17T07:13:36.9", + "createdBy": 700301950, + "modifiedBy": 700301950, + "intervalType": 0 + } + ] +} diff --git a/test/resources/9999999/automation/v1/filetransfers/72c328ac-f5b0-4e37-91d3-a775666f15a6/get-response.json b/test/resources/9999999/automation/v1/filetransfers/72c328ac-f5b0-4e37-91d3-a775666f15a6/get-response.json new file mode 100644 index 000000000..4775efdce --- /dev/null +++ b/test/resources/9999999/automation/v1/filetransfers/72c328ac-f5b0-4e37-91d3-a775666f15a6/get-response.json @@ -0,0 +1,18 @@ +{ + "id": "72c328ac-f5b0-4e37-91d3-a775666f15a6", + "name": "testExisting_fileTransfer", + "description": "17.11.2022", + "customerKey": "testExisting_fileTransfer", + "fileSpec": "%%Year%%", + "isEncrypted": false, + "isCompressed": false, + "maxFileAge": 0, + "maxFileAgeScheduleOffset": 0, + "maxImportFrequency": 0, + "fileTransferLocationId": "41a5ded7-0d98-4910-a15f-d09e7ab0af24", + "isUpload": true, + "isPgp": false, + "isFileSpecLocalized": false, + "createdDate": "2022-11-09T05:31:56.477", + "modifiedDate": "2022-11-17T07:13:20.05" +} diff --git a/test/resources/9999999/automation/v1/filetransfers/get-response.json b/test/resources/9999999/automation/v1/filetransfers/get-response.json new file mode 100644 index 000000000..1a925b687 --- /dev/null +++ b/test/resources/9999999/automation/v1/filetransfers/get-response.json @@ -0,0 +1,15 @@ +{ + "page": 1, + "pageSize": 500, + "count": 1, + "items": [ + { + "id": "72c328ac-f5b0-4e37-91d3-a775666f15a6", + "name": "testExisting_fileTransfer", + "description": "17.11.2022", + "customerKey": "testExisting_fileTransfer", + "createdDate": "2022-11-09T05:31:56.477", + "modifiedDate": "2022-11-17T07:13:20.05" + } + ] +} diff --git a/test/resources/9999999/automation/v1/imports/get-response.json b/test/resources/9999999/automation/v1/imports/get-response.json new file mode 100644 index 000000000..d58fc438c --- /dev/null +++ b/test/resources/9999999/automation/v1/imports/get-response.json @@ -0,0 +1,38 @@ +{ + "page": 1, + "pageSize": 500, + "count": 1, + "items": [ + { + "allowErrors": true, + "customerKey": "testExisting_importFile", + "createdDate": "2022-11-09T05:32:30.533", + "modifiedDate": "2022-11-17T07:13:03.95", + "dateFormatLocale": "en-US", + "deleteFile": false, + "description": "17.11.2022", + "destinationObjectId": "bf457cfa-ca04-ec11-b82d-48df37d1da95", + "destinationId": 0, + "destinationObjectTypeId": 310, + "fieldMappings": [], + "fieldMappingType": "InferFromColumnHeadings", + "fileNamingPattern": "blabla", + "fileTransferLocationId": "41a5ded7-0d98-4910-a15f-d09e7ab0af24", + "fileType": "CSV", + "filter": "", + "hasColumnHeader": true, + "importDefinitionId": "9d16f42c-2260-ed11-b849-48df37d1de8b", + "isOrderedImport": true, + "isSequential": true, + "maxFileAgeHours": 0, + "maxImportFrequencyHours": 0, + "maxFileAgeScheduleOffsetHours": 0, + "name": "testExisting_importFile", + "sendEmailNotification": false, + "standardQuotedStrings": true, + "subscriberImportTypeId": 255, + "updateTypeId": 0, + "fileTransferLocationTypeId": 0 + } + ] +} diff --git a/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/actions/start/post-response.txt b/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/actions/start/post-response.txt new file mode 100644 index 000000000..a0aba9318 --- /dev/null +++ b/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/actions/start/post-response.txt @@ -0,0 +1 @@ +OK \ No newline at end of file diff --git a/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/get-response.json b/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/get-response.json index bc8651c28..9144d0e0b 100644 --- a/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/get-response.json +++ b/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/get-response.json @@ -1,7 +1,7 @@ { "queryDefinitionId": "549f0568-607c-4940-afef-437965094dat", - "name": "testExistingQuery", - "key": "testExistingQuery", + "name": "testExisting_query", + "key": "testExisting_query", "description": "bla bla", "queryText": "SELECT\n SubscriberKey as testField\nFROM\n _Subscribers\nWHERE\n country IN ('test')\n", "targetName": "testExisting_dataExtension", diff --git a/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json b/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json index a647f711a..6414f11dc 100644 --- a/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +++ b/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json @@ -1,7 +1,7 @@ { "queryDefinitionId": "549f0568-607c-4940-afef-437965094dat", - "name": "testExistingQuery", - "key": "testExistingQuery", + "name": "testExisting_query", + "key": "testExisting_query", "description": "updated on deploy", "queryText": "SELECT\n SubscriberKey as testField\nFROM\n _Subscribers\nWHERE\n country IN ('test')\n", "targetName": "testExisting_dataExtension", diff --git a/test/resources/9999999/automation/v1/queries/get-response.json b/test/resources/9999999/automation/v1/queries/get-response.json index 13d3ba1fa..4a7b047ec 100644 --- a/test/resources/9999999/automation/v1/queries/get-response.json +++ b/test/resources/9999999/automation/v1/queries/get-response.json @@ -5,8 +5,8 @@ "items": [ { "queryDefinitionId": "549f0568-607c-4940-afef-437965094dat", - "name": "testExistingQuery", - "key": "testExistingQuery", + "name": "testExisting_query", + "key": "testExisting_query", "description": "bla bla", "queryText": "SELECT\n SubscriberKey as testField\nFROM\n _Subscribers\nWHERE\n country IN ('test')\n", "targetName": "testExisting_dataExtension", @@ -22,8 +22,8 @@ }, { "queryDefinitionId": "abcde-607c-4940-afef-437965094dat", - "name": "testExistingQuery2", - "key": "testExistingQuery2", + "name": "testExisting_query2", + "key": "testExisting_query2", "description": "bla bla", "queryText": "SELECT\n SubscriberKey as testField\nFROM\n _Subscribers\nWHERE\n country IN ('test')\n", "targetName": "testExisting_dataExtension", diff --git a/test/resources/9999999/automation/v1/queries/post-response.json b/test/resources/9999999/automation/v1/queries/post-response.json index f858e8e38..e809ffc3a 100644 --- a/test/resources/9999999/automation/v1/queries/post-response.json +++ b/test/resources/9999999/automation/v1/queries/post-response.json @@ -1,7 +1,7 @@ { "queryDefinitionId": "549f0568-607c-4940-afef-437965094dae", - "name": "testNewQuery", - "key": "testNewQuery", + "name": "testNew_query", + "key": "testNew_query", "description": "created on deploy", "queryText": "SELECT\n SubscriberKey as testField\nFROM\n _Subscribers\n", "targetName": "testExisting_dataExtension", diff --git a/test/resources/9999999/automation/v1/scripts/get-response.json b/test/resources/9999999/automation/v1/scripts/get-response.json new file mode 100644 index 000000000..d5830148f --- /dev/null +++ b/test/resources/9999999/automation/v1/scripts/get-response.json @@ -0,0 +1,17 @@ +{ + "count": 1, + "page": 1, + "pageSize": 500, + "items": [ + { + "ssjsActivityId": "39f6a488-20eb-4ba0-b0b9-023725b574e4", + "name": "testExisting_script", + "key": "testExisting_script", + "description": "", + "script": "