mcdev 3.1.3 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +67 -7
- package/.github/ISSUE_TEMPLATE/bug.yml +2 -1
- package/.github/PULL_REQUEST_TEMPLATE.md +5 -3
- package/.github/dependabot.yml +14 -0
- package/.github/workflows/code-analysis.yml +57 -0
- package/.husky/commit-msg +10 -0
- package/.husky/post-checkout +5 -0
- package/.husky/pre-commit +2 -1
- package/.prettierrc +8 -0
- package/.vscode/settings.json +1 -1
- package/LICENSE +2 -2
- package/README.md +134 -45
- package/boilerplate/config.json +5 -11
- package/boilerplate/files/.prettierrc +8 -0
- package/boilerplate/files/.vscode/extensions.json +0 -1
- package/boilerplate/files/.vscode/settings.json +28 -2
- package/boilerplate/files/README.md +2 -2
- package/boilerplate/forcedUpdates.json +10 -0
- package/boilerplate/npm-dependencies.json +5 -5
- package/docs/dist/documentation.md +2795 -1724
- package/jsconfig.json +1 -1
- package/lib/Builder.js +166 -75
- package/lib/Deployer.js +244 -96
- package/lib/MetadataTypeDefinitions.js +2 -0
- package/lib/MetadataTypeInfo.js +2 -0
- package/lib/Retriever.js +61 -84
- package/lib/cli.js +116 -11
- package/lib/index.js +241 -561
- package/lib/metadataTypes/AccountUser.js +101 -95
- package/lib/metadataTypes/Asset.js +677 -248
- package/lib/metadataTypes/AttributeGroup.js +23 -12
- package/lib/metadataTypes/Automation.js +451 -354
- package/lib/metadataTypes/Campaign.js +33 -93
- package/lib/metadataTypes/ContentArea.js +31 -11
- package/lib/metadataTypes/DataExtension.js +387 -372
- package/lib/metadataTypes/DataExtensionField.js +131 -54
- package/lib/metadataTypes/DataExtensionTemplate.js +22 -4
- package/lib/metadataTypes/DataExtract.js +61 -48
- package/lib/metadataTypes/DataExtractType.js +14 -8
- package/lib/metadataTypes/Discovery.js +21 -16
- package/lib/metadataTypes/Email.js +32 -12
- package/lib/metadataTypes/EmailSendDefinition.js +85 -80
- package/lib/metadataTypes/EventDefinition.js +61 -43
- package/lib/metadataTypes/FileTransfer.js +72 -52
- package/lib/metadataTypes/Filter.js +11 -4
- package/lib/metadataTypes/Folder.js +149 -117
- package/lib/metadataTypes/FtpLocation.js +14 -8
- package/lib/metadataTypes/ImportFile.js +61 -64
- package/lib/metadataTypes/Interaction.js +19 -4
- package/lib/metadataTypes/List.js +54 -13
- package/lib/metadataTypes/MetadataType.js +668 -454
- package/lib/metadataTypes/MobileCode.js +46 -0
- package/lib/metadataTypes/MobileKeyword.js +114 -0
- package/lib/metadataTypes/Query.js +204 -103
- package/lib/metadataTypes/Role.js +76 -61
- package/lib/metadataTypes/Script.js +145 -81
- package/lib/metadataTypes/SetDefinition.js +20 -8
- package/lib/metadataTypes/TriggeredSendDefinition.js +78 -58
- package/lib/metadataTypes/definitions/Asset.definition.js +21 -10
- package/lib/metadataTypes/definitions/AttributeGroup.definition.js +12 -0
- package/lib/metadataTypes/definitions/Automation.definition.js +10 -5
- package/lib/metadataTypes/definitions/Campaign.definition.js +44 -1
- package/lib/metadataTypes/definitions/DataExtension.definition.js +4 -0
- package/lib/metadataTypes/definitions/DataExtensionTemplate.definition.js +6 -0
- package/lib/metadataTypes/definitions/DataExtract.definition.js +18 -14
- package/lib/metadataTypes/definitions/Discovery.definition.js +12 -0
- package/lib/metadataTypes/definitions/EmailSendDefinition.definition.js +4 -0
- package/lib/metadataTypes/definitions/EventDefinition.definition.js +22 -0
- package/lib/metadataTypes/definitions/FileTransfer.definition.js +4 -0
- package/lib/metadataTypes/definitions/Filter.definition.js +4 -0
- package/lib/metadataTypes/definitions/Folder.definition.js +6 -0
- package/lib/metadataTypes/definitions/FtpLocation.definition.js +4 -0
- package/lib/metadataTypes/definitions/ImportFile.definition.js +10 -5
- package/lib/metadataTypes/definitions/Interaction.definition.js +4 -0
- package/lib/metadataTypes/definitions/MobileCode.definition.js +163 -0
- package/lib/metadataTypes/definitions/MobileKeyword.definition.js +253 -0
- package/lib/metadataTypes/definitions/Query.definition.js +4 -0
- package/lib/metadataTypes/definitions/Role.definition.js +5 -0
- package/lib/metadataTypes/definitions/Script.definition.js +4 -0
- package/lib/metadataTypes/definitions/SetDefinition.definition.js +28 -0
- package/lib/metadataTypes/definitions/TriggeredSendDefinition.definition.js +4 -0
- package/lib/retrieveChangelog.js +7 -6
- package/lib/util/auth.js +117 -0
- package/lib/util/businessUnit.js +55 -66
- package/lib/util/cache.js +194 -0
- package/lib/util/cli.js +90 -116
- package/lib/util/config.js +302 -0
- package/lib/util/devops.js +240 -50
- package/lib/util/file.js +120 -191
- package/lib/util/init.config.js +195 -69
- package/lib/util/init.git.js +45 -50
- package/lib/util/init.js +72 -59
- package/lib/util/init.npm.js +48 -39
- package/lib/util/util.js +280 -564
- package/package.json +44 -33
- package/test/dataExtension.test.js +152 -0
- package/test/mockRoot/.mcdev-auth.json +8 -0
- package/test/mockRoot/.mcdevrc.json +67 -0
- package/test/mockRoot/deploy/testInstance/testBU/dataExtension/childBU_dataextension_test.dataExtension-meta.json +39 -0
- package/test/mockRoot/deploy/testInstance/testBU/dataExtension/testDataExtension.dataExtension-meta.json +23 -0
- package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.json +11 -0
- package/test/mockRoot/deploy/testInstance/testBU/query/testExistingQuery.query-meta.sql +4 -0
- package/test/mockRoot/deploy/testInstance/testBU/query/testQuery.query-meta.json +11 -0
- package/test/mockRoot/deploy/testInstance/testBU/query/testQuery.query-meta.sql +4 -0
- package/test/query.test.js +149 -0
- package/test/resourceFactory.js +142 -0
- package/test/resources/1111111/dataFolder/retrieve-response.xml +43 -0
- package/test/resources/9999999/automation/v1/queries/549f0568-607c-4940-afef-437965094dat/patch-response.json +18 -0
- package/test/resources/9999999/automation/v1/queries/get-response.json +24 -0
- package/test/resources/9999999/automation/v1/queries/post-response.json +18 -0
- package/test/resources/9999999/dataExtension/build-expected.json +51 -0
- package/test/resources/9999999/dataExtension/create-expected.json +23 -0
- package/test/resources/9999999/dataExtension/create-response.xml +54 -0
- package/test/resources/9999999/dataExtension/retrieve-expected.json +51 -0
- package/test/resources/9999999/dataExtension/retrieve-response.xml +47 -0
- package/test/resources/9999999/dataExtension/template-expected.json +51 -0
- package/test/resources/9999999/dataExtension/update-expected.json +55 -0
- package/test/resources/9999999/dataExtension/update-response.xml +52 -0
- package/test/resources/9999999/dataExtensionField/retrieve-response.xml +93 -0
- package/test/resources/9999999/dataExtensionTemplate/retrieve-response.xml +303 -0
- package/test/resources/9999999/dataFolder/retrieve-response.xml +65 -0
- package/test/resources/9999999/query/build-expected.json +8 -0
- package/test/resources/9999999/query/get-expected.json +11 -0
- package/test/resources/9999999/query/patch-expected.json +11 -0
- package/test/resources/9999999/query/post-expected.json +11 -0
- package/test/resources/9999999/query/template-expected.json +8 -0
- package/test/resources/auth.json +32 -0
- package/test/resources/rest404-response.json +5 -0
- package/test/resources/retrieve-response.xml +21 -0
- package/test/utils.js +107 -0
- package/types/mcdev.d.js +301 -0
- package/CHANGELOG.md +0 -126
- package/PULL_REQUEST_TEMPLATE.md +0 -19
- package/test/util/file.js +0 -51
|
@@ -1,165 +1,96 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const MetadataType = require('./MetadataType');
|
|
4
|
+
const TYPE = require('../../types/mcdev.d');
|
|
4
5
|
const Util = require('../util/util');
|
|
5
6
|
const File = require('../util/file');
|
|
6
7
|
const Definitions = require('../MetadataTypeDefinitions');
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* @typedef {Object} AutomationActivity
|
|
10
|
-
* @property {string} name name (not key) of activity
|
|
11
|
-
* @property {string} [objectTypeId] Id of assoicated activity type; see this.definition.activityTypeMapping
|
|
12
|
-
* @property {string} [activityObjectId] Object Id of assoicated metadata item
|
|
13
|
-
* @property {number} displayOrder order within step; starts with 1 or higher number
|
|
14
|
-
* @property {string} r__type see this.definition.activityTypeMapping
|
|
15
|
-
*
|
|
16
|
-
* @typedef {Object} AutomationStep
|
|
17
|
-
* @property {string} name description
|
|
18
|
-
* @property {string} [annotation] equals AutomationStep.name
|
|
19
|
-
* @property {number} step step iterator
|
|
20
|
-
* @property {number} [stepNumber] step iterator, automatically set during deployment
|
|
21
|
-
* @property {AutomationActivity[]} activities -
|
|
22
|
-
*
|
|
23
|
-
* @typedef {Object} AutomationSchedule REST format
|
|
24
|
-
* @property {number} typeId ?
|
|
25
|
-
* @property {string} startDate example: '2021-05-07T09:00:00'
|
|
26
|
-
* @property {string} endDate example: '2021-05-07T09:00:00'
|
|
27
|
-
* @property {string} icalRecur example: 'FREQ=DAILY;UNTIL=20790606T160000;INTERVAL=1'
|
|
28
|
-
* @property {string} timezoneName example: 'W. Europe Standard Time'; see this.definition.timeZoneMapping
|
|
29
|
-
* @property {number} [timezoneId] see this.definition.timeZoneMapping
|
|
30
|
-
*
|
|
31
|
-
* @typedef {Object} AutomationScheduleSoap SOAP format
|
|
32
|
-
* @property {Object} Recurrence -
|
|
33
|
-
* @property {Object} Recurrence.$ {'xsi:type': keyStem + 'lyRecurrence'}
|
|
34
|
-
* @property {'ByYear'} [Recurrence.YearlyRecurrencePatternType] * currently not supported by tool *
|
|
35
|
-
* @property {'ByMonth'} [Recurrence.MonthlyRecurrencePatternType] * currently not supported by tool *
|
|
36
|
-
* @property {'ByWeek'} [Recurrence.WeeklyRecurrencePatternType] * currently not supported by tool *
|
|
37
|
-
* @property {'ByDay'} [Recurrence.DailyRecurrencePatternType] -
|
|
38
|
-
* @property {'Interval'} [Recurrence.MinutelyRecurrencePatternType] -
|
|
39
|
-
* @property {'Interval'} [Recurrence.HourlyRecurrencePatternType] -
|
|
40
|
-
* @property {number} [Recurrence.YearInterval] 1..n * currently not supported by tool *
|
|
41
|
-
* @property {number} [Recurrence.MonthInterval] 1..n * currently not supported by tool *
|
|
42
|
-
* @property {number} [Recurrence.WeekInterval] 1..n * currently not supported by tool *
|
|
43
|
-
* @property {number} [Recurrence.DayInterval] 1..n
|
|
44
|
-
* @property {number} [Recurrence.HourInterval] 1..n
|
|
45
|
-
* @property {number} [Recurrence.MinuteInterval] 1..n
|
|
46
|
-
* @property {number} _interval internal variable for CLI output only
|
|
47
|
-
* @property {Object} TimeZone -
|
|
48
|
-
* @property {number} TimeZone.ID AutomationSchedule.timezoneId
|
|
49
|
-
* @property {string} _timezoneString internal variable for CLI output only
|
|
50
|
-
* @property {string} StartDateTime AutomationSchedule.startDate
|
|
51
|
-
* @property {string} EndDateTime AutomationSchedule.endDate
|
|
52
|
-
* @property {string} _StartDateTime AutomationSchedule.startDate; internal variable for CLI output only
|
|
53
|
-
* @property {'EndOn'|'EndAfter'} RecurrenceRangeType set to 'EndOn' if AutomationSchedule.icalRecur contains 'UNTIL'; otherwise to 'EndAfter'
|
|
54
|
-
* @property {number} Occurrences only exists if RecurrenceRangeType=='EndAfter'
|
|
55
|
-
*
|
|
56
|
-
* @typedef {Object} AutomationItem
|
|
57
|
-
* @property {string} [id] Object Id
|
|
58
|
-
* @property {string} key key
|
|
59
|
-
* @property {string} name name
|
|
60
|
-
* @property {string} description -
|
|
61
|
-
* @property {'scheduled'|'triggered'} type Starting Source = Schedule / File Drop
|
|
62
|
-
* @property {'Scheduled'|'Running'} status -
|
|
63
|
-
* @property {AutomationSchedule} [schedule] only existing if type=scheduled
|
|
64
|
-
* @property {Object} [fileTrigger] only existing if type=triggered
|
|
65
|
-
* @property {string} fileTrigger.fileNamingPattern -
|
|
66
|
-
* @property {string} fileTrigger.fileNamePatternTypeId -
|
|
67
|
-
* @property {string} fileTrigger.folderLocationText -
|
|
68
|
-
* @property {string} fileTrigger.queueFiles -
|
|
69
|
-
* @property {Object} [startSource] -
|
|
70
|
-
* @property {AutomationSchedule} [startSource.schedule] rewritten to AutomationItem.schedule
|
|
71
|
-
* @property {Object} [startSource.fileDrop] rewritten to AutomationItem.fileTrigger
|
|
72
|
-
* @property {string} startSource.fileDrop.fileNamingPattern -
|
|
73
|
-
* @property {string} startSource.fileDrop.fileNamePatternTypeId -
|
|
74
|
-
* @property {string} startSource.fileDrop.folderLocation -
|
|
75
|
-
* @property {string} startSource.fileDrop.queueFiles -
|
|
76
|
-
* @property {number} startSource.typeId -
|
|
77
|
-
* @property {AutomationStep[]} steps -
|
|
78
|
-
* @property {string} r__folder_Path folder path
|
|
79
|
-
* @property {string} [categoryId] holds folder ID, replaced with r__folder_Path during retrieve
|
|
80
|
-
*
|
|
81
|
-
* @typedef {Object.<string, AutomationItem>} AutomationMap
|
|
82
|
-
*/
|
|
8
|
+
const cache = require('../util/cache');
|
|
83
9
|
|
|
84
10
|
/**
|
|
85
11
|
* Automation MetadataType
|
|
12
|
+
*
|
|
86
13
|
* @augments MetadataType
|
|
87
14
|
*/
|
|
88
15
|
class Automation extends MetadataType {
|
|
89
16
|
/**
|
|
90
17
|
* Retrieves Metadata of Automation
|
|
18
|
+
*
|
|
91
19
|
* @param {string} retrieveDir Directory where retrieved metadata directory will be saved
|
|
92
|
-
* @
|
|
20
|
+
* @param {void} [_] unused parameter
|
|
21
|
+
* @param {void} [__] unused parameter
|
|
22
|
+
* @param {void} [___] unused parameter
|
|
23
|
+
* @param {string} [key] customer key of single item to retrieve
|
|
24
|
+
* @returns {Promise.<TYPE.AutomationMapObj>} Promise of metadata
|
|
93
25
|
*/
|
|
94
|
-
static async retrieve(retrieveDir) {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
26
|
+
static async retrieve(retrieveDir, _, __, ___, key) {
|
|
27
|
+
/** @type {TYPE.SoapRequestParams} */
|
|
28
|
+
let requestParams = null;
|
|
29
|
+
if (key) {
|
|
30
|
+
requestParams = {
|
|
31
|
+
filter: {
|
|
32
|
+
leftOperand: 'CustomerKey',
|
|
33
|
+
operator: 'equals',
|
|
34
|
+
rightOperand: key,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const results = await this.client.soap.retrieveBulk('Program', ['ObjectID'], requestParams);
|
|
39
|
+
|
|
40
|
+
const details = results.Results
|
|
41
|
+
? await Promise.all(
|
|
42
|
+
results.Results.map((a) =>
|
|
43
|
+
this.client.rest.get('/automation/v1/automations/' + a.ObjectID)
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
: [];
|
|
109
47
|
const parsed = this.parseResponseBody({ items: details });
|
|
110
48
|
|
|
49
|
+
// * retrieveDir is mandatory in this method as it is not used for caching (there is a seperate method for that)
|
|
111
50
|
const savedMetadata = await this.saveResults(parsed, retrieveDir, null, null);
|
|
112
51
|
Util.logger.info(
|
|
113
52
|
`Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})`
|
|
114
53
|
);
|
|
54
|
+
if (this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type)) {
|
|
55
|
+
await this.document(this.buObject, savedMetadata);
|
|
56
|
+
}
|
|
115
57
|
return { metadata: savedMetadata, type: this.definition.type };
|
|
116
58
|
}
|
|
117
59
|
/**
|
|
118
60
|
* Retrieves Metadata of Automation
|
|
119
|
-
*
|
|
61
|
+
*
|
|
62
|
+
* @returns {Promise.<TYPE.AutomationMapObj>} Promise of metadata
|
|
120
63
|
*/
|
|
121
64
|
static async retrieveChangelog() {
|
|
122
|
-
const results = await
|
|
123
|
-
this.client.SoapClient.retrieve(
|
|
124
|
-
'Program',
|
|
125
|
-
['ObjectID'],
|
|
126
|
-
|
|
127
|
-
(ex, response) => (ex ? reject(ex) : resolve(response.body.Results))
|
|
128
|
-
);
|
|
129
|
-
});
|
|
65
|
+
const results = await this.client.soap.retrieveBulk('Program', ['ObjectID']);
|
|
130
66
|
const details = [];
|
|
131
|
-
(
|
|
132
|
-
await Promise.all(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
})
|
|
159
|
-
)
|
|
160
|
-
).forEach((item) => {
|
|
161
|
-
details.push(...item);
|
|
162
|
-
});
|
|
67
|
+
for (const item of results.Results
|
|
68
|
+
? await Promise.all(
|
|
69
|
+
results.Results.map((a) =>
|
|
70
|
+
this.client.soap.retrieveBulk(
|
|
71
|
+
'Automation',
|
|
72
|
+
[
|
|
73
|
+
'ProgramID',
|
|
74
|
+
'Name',
|
|
75
|
+
'CustomerKey',
|
|
76
|
+
'LastSaveDate',
|
|
77
|
+
'LastSavedBy',
|
|
78
|
+
'CreatedBy',
|
|
79
|
+
'CreatedDate',
|
|
80
|
+
],
|
|
81
|
+
{
|
|
82
|
+
filter: {
|
|
83
|
+
leftOperand: 'ProgramID',
|
|
84
|
+
operator: 'equals',
|
|
85
|
+
rightOperand: a.ObjectID,
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
: []) {
|
|
92
|
+
details.push(...item.Results);
|
|
93
|
+
}
|
|
163
94
|
details.map((item) => {
|
|
164
95
|
item.key = item.CustomerKey;
|
|
165
96
|
});
|
|
@@ -171,24 +102,17 @@ class Automation extends MetadataType {
|
|
|
171
102
|
|
|
172
103
|
/**
|
|
173
104
|
* Retrieves automation metadata for caching
|
|
174
|
-
*
|
|
105
|
+
*
|
|
106
|
+
* @returns {Promise.<TYPE.AutomationMapObj>} Promise of metadata
|
|
175
107
|
*/
|
|
176
108
|
static async retrieveForCache() {
|
|
177
|
-
const results = await
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (error) {
|
|
183
|
-
throw new Error(error);
|
|
184
|
-
} else {
|
|
185
|
-
resolve(response.body.Results);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
);
|
|
189
|
-
});
|
|
109
|
+
const results = await this.client.soap.retrieveBulk('Program', [
|
|
110
|
+
'ObjectID',
|
|
111
|
+
'CustomerKey',
|
|
112
|
+
'Name',
|
|
113
|
+
]);
|
|
190
114
|
const resultsConverted = {};
|
|
191
|
-
for (const m of results) {
|
|
115
|
+
for (const m of results.Results) {
|
|
192
116
|
resultsConverted[m.CustomerKey] = {
|
|
193
117
|
id: m.ObjectID,
|
|
194
118
|
key: m.CustomerKey,
|
|
@@ -200,44 +124,30 @@ class Automation extends MetadataType {
|
|
|
200
124
|
|
|
201
125
|
/**
|
|
202
126
|
* Retrieve a specific Automation Definition by Name
|
|
127
|
+
*
|
|
203
128
|
* @param {string} templateDir Directory where retrieved metadata directory will be saved
|
|
204
129
|
* @param {string} name name of the metadata file
|
|
205
|
-
* @param {
|
|
206
|
-
* @returns {Promise
|
|
130
|
+
* @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
|
|
131
|
+
* @returns {Promise.<TYPE.AutomationItemObj>} Promise of metadata
|
|
207
132
|
*/
|
|
208
133
|
static async retrieveAsTemplate(templateDir, name, templateVariables) {
|
|
209
|
-
const results = await
|
|
210
|
-
|
|
211
|
-
'
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
leftOperand: 'Name',
|
|
216
|
-
operator: 'equals',
|
|
217
|
-
rightOperand: name,
|
|
218
|
-
},
|
|
219
|
-
},
|
|
220
|
-
(error, response) => {
|
|
221
|
-
if (error) {
|
|
222
|
-
throw new Error(error);
|
|
223
|
-
} else {
|
|
224
|
-
resolve(response.body.Results);
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
);
|
|
134
|
+
const results = await this.client.soap.retrieve('Program', ['ObjectID', 'Name'], {
|
|
135
|
+
filter: {
|
|
136
|
+
leftOperand: 'Name',
|
|
137
|
+
operator: 'equals',
|
|
138
|
+
rightOperand: name,
|
|
139
|
+
},
|
|
228
140
|
});
|
|
229
|
-
if (results
|
|
141
|
+
if (Array.isArray(results?.Results)) {
|
|
230
142
|
// eq-operator returns a similar, not exact match and hence might return more than 1 entry
|
|
231
|
-
const
|
|
143
|
+
const metadata = results.Results.find((item) => item.Name === name);
|
|
232
144
|
if (!metadata) {
|
|
233
145
|
Util.logger.error(`${this.definition.type} '${name}' not found on server.`);
|
|
234
146
|
return;
|
|
235
147
|
}
|
|
236
|
-
const details = (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
})
|
|
240
|
-
).body;
|
|
148
|
+
const details = await this.client.rest.get(
|
|
149
|
+
'/automation/v1/automations/' + metadata.ObjectID
|
|
150
|
+
);
|
|
241
151
|
let val = null;
|
|
242
152
|
let originalKey;
|
|
243
153
|
// if parsing fails, we should just save what we get
|
|
@@ -249,7 +159,7 @@ class Automation extends MetadataType {
|
|
|
249
159
|
Util.replaceByObject(JSON.stringify(parsedDetails), templateVariables)
|
|
250
160
|
);
|
|
251
161
|
}
|
|
252
|
-
} catch
|
|
162
|
+
} catch {
|
|
253
163
|
val = JSON.parse(JSON.stringify(details));
|
|
254
164
|
}
|
|
255
165
|
if (val === null) {
|
|
@@ -259,14 +169,12 @@ class Automation extends MetadataType {
|
|
|
259
169
|
}
|
|
260
170
|
// remove all fields not listed in Definition for templating
|
|
261
171
|
this.keepTemplateFields(val);
|
|
262
|
-
File.writeJSONToFile(
|
|
172
|
+
await File.writeJSONToFile(
|
|
263
173
|
[templateDir, this.definition.type].join('/'),
|
|
264
174
|
originalKey + '.' + this.definition.type + '-meta',
|
|
265
175
|
val
|
|
266
176
|
);
|
|
267
|
-
Util.logger.info(
|
|
268
|
-
`Automation.retrieveAsTemplate:: Written Metadata to filesystem (${name})`
|
|
269
|
-
);
|
|
177
|
+
Util.logger.info(`- templated ${this.definition.type}: ${name}`);
|
|
270
178
|
return { metadata: val, type: this.definition.type };
|
|
271
179
|
} else if (results) {
|
|
272
180
|
Util.logger.error(`${this.definition.type} '${name}' not found on server.`);
|
|
@@ -278,26 +186,21 @@ class Automation extends MetadataType {
|
|
|
278
186
|
}
|
|
279
187
|
/**
|
|
280
188
|
* manages post retrieve steps
|
|
281
|
-
*
|
|
282
|
-
* @param {
|
|
283
|
-
* @
|
|
284
|
-
* @returns {AutomationItem} metadata
|
|
189
|
+
*
|
|
190
|
+
* @param {TYPE.AutomationItem} metadata a single automation
|
|
191
|
+
* @returns {TYPE.AutomationItem} metadata
|
|
285
192
|
*/
|
|
286
|
-
static postRetrieveTasks(metadata
|
|
287
|
-
// if retrieving template, replace the name with customer key if that wasn't already the case
|
|
288
|
-
if (isTemplating) {
|
|
289
|
-
const warningMsg = null;
|
|
290
|
-
this.overrideKeyWithName(metadata, warningMsg);
|
|
291
|
-
}
|
|
193
|
+
static postRetrieveTasks(metadata) {
|
|
292
194
|
return this.parseMetadata(metadata);
|
|
293
195
|
}
|
|
294
196
|
|
|
295
197
|
/**
|
|
296
198
|
* Deploys automation - the saved file is the original one due to large differences required for deployment
|
|
297
|
-
*
|
|
199
|
+
*
|
|
200
|
+
* @param {TYPE.AutomationMap} metadata metadata mapped by their keyField
|
|
298
201
|
* @param {string} targetBU name/shorthand of target businessUnit for mapping
|
|
299
202
|
* @param {string} retrieveDir directory where metadata after deploy should be saved
|
|
300
|
-
* @returns {Promise
|
|
203
|
+
* @returns {Promise.<TYPE.AutomationMap>} Promise
|
|
301
204
|
*/
|
|
302
205
|
static async deploy(metadata, targetBU, retrieveDir) {
|
|
303
206
|
const orignalMetadata = JSON.parse(JSON.stringify(metadata));
|
|
@@ -309,7 +212,8 @@ class Automation extends MetadataType {
|
|
|
309
212
|
|
|
310
213
|
/**
|
|
311
214
|
* Creates a single automation
|
|
312
|
-
*
|
|
215
|
+
*
|
|
216
|
+
* @param {TYPE.AutomationItem} metadata single metadata entry
|
|
313
217
|
* @returns {Promise} Promise
|
|
314
218
|
*/
|
|
315
219
|
static create(metadata) {
|
|
@@ -319,8 +223,9 @@ class Automation extends MetadataType {
|
|
|
319
223
|
|
|
320
224
|
/**
|
|
321
225
|
* Updates a single automation
|
|
322
|
-
*
|
|
323
|
-
* @param {AutomationItem}
|
|
226
|
+
*
|
|
227
|
+
* @param {TYPE.AutomationItem} metadata single metadata entry
|
|
228
|
+
* @param {TYPE.AutomationItem} metadataBefore metadata mapped by their keyField
|
|
324
229
|
* @returns {Promise} Promise
|
|
325
230
|
*/
|
|
326
231
|
static update(metadata, metadataBefore) {
|
|
@@ -331,14 +236,14 @@ class Automation extends MetadataType {
|
|
|
331
236
|
|
|
332
237
|
/**
|
|
333
238
|
* Gets executed before deploying metadata
|
|
334
|
-
*
|
|
335
|
-
* @
|
|
239
|
+
*
|
|
240
|
+
* @param {TYPE.AutomationItem} metadata metadata mapped by their keyField
|
|
241
|
+
* @returns {Promise.<TYPE.AutomationItem>} Promise
|
|
336
242
|
*/
|
|
337
243
|
static async preDeployTasks(metadata) {
|
|
338
244
|
if (this.validateDeployMetadata(metadata)) {
|
|
339
245
|
try {
|
|
340
|
-
metadata.categoryId =
|
|
341
|
-
this.cache,
|
|
246
|
+
metadata.categoryId = cache.searchForField(
|
|
342
247
|
'folder',
|
|
343
248
|
metadata.r__folder_Path,
|
|
344
249
|
'Path',
|
|
@@ -346,7 +251,7 @@ class Automation extends MetadataType {
|
|
|
346
251
|
);
|
|
347
252
|
if (metadata.r__folder_Path !== 'my automations') {
|
|
348
253
|
Util.logger.warn(
|
|
349
|
-
`Automation '${
|
|
254
|
+
` - Automation '${
|
|
350
255
|
metadata[this.definition.nameField]
|
|
351
256
|
}' is located in subfolder ${
|
|
352
257
|
metadata.r__folder_Path
|
|
@@ -354,12 +259,12 @@ class Automation extends MetadataType {
|
|
|
354
259
|
);
|
|
355
260
|
}
|
|
356
261
|
delete metadata.r__folder_Path;
|
|
357
|
-
} catch
|
|
262
|
+
} catch {
|
|
358
263
|
throw new Error(
|
|
359
264
|
`Folder '${metadata.r__folder_Path}' was not found on the server. Please create this manually in the GUI. Automation-folders cannot be deployed automatically.`
|
|
360
265
|
);
|
|
361
266
|
}
|
|
362
|
-
if (metadata.type === 'scheduled' && metadata
|
|
267
|
+
if (metadata.type === 'scheduled' && metadata?.schedule?.startDate) {
|
|
363
268
|
// Starting Source == 'Schedule'
|
|
364
269
|
|
|
365
270
|
delete metadata.schedule.rangeTypeId;
|
|
@@ -399,26 +304,33 @@ class Automation extends MetadataType {
|
|
|
399
304
|
delete metadata.schedule;
|
|
400
305
|
delete metadata.type;
|
|
401
306
|
let i = 0;
|
|
402
|
-
|
|
403
|
-
for (const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
activity.
|
|
407
|
-
|
|
408
|
-
activity.
|
|
409
|
-
activity.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
307
|
+
if (metadata.steps) {
|
|
308
|
+
for (const step of metadata.steps) {
|
|
309
|
+
let displayOrder = 0;
|
|
310
|
+
for (const activity of step.activities) {
|
|
311
|
+
activity.displayOrder = ++displayOrder;
|
|
312
|
+
if (
|
|
313
|
+
activity.name &&
|
|
314
|
+
this.definition.dependencies.includes(activity.r__type)
|
|
315
|
+
) {
|
|
316
|
+
// automations can have empty placeholder for activities with only their type defined
|
|
317
|
+
activity.activityObjectId = cache.searchForField(
|
|
318
|
+
activity.r__type,
|
|
319
|
+
activity.name,
|
|
320
|
+
Definitions[activity.r__type].nameField,
|
|
321
|
+
Definitions[activity.r__type].idField
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
activity.objectTypeId =
|
|
325
|
+
this.definition.activityTypeMapping[activity.r__type];
|
|
326
|
+
delete activity.r__type;
|
|
413
327
|
}
|
|
414
|
-
|
|
415
|
-
|
|
328
|
+
step.annotation = step.name;
|
|
329
|
+
step.stepNumber = i;
|
|
330
|
+
delete step.name;
|
|
331
|
+
delete step.step;
|
|
332
|
+
i++;
|
|
416
333
|
}
|
|
417
|
-
step.annotation = step.name;
|
|
418
|
-
step.stepNumber = i;
|
|
419
|
-
delete step.name;
|
|
420
|
-
delete step.step;
|
|
421
|
-
i++;
|
|
422
334
|
}
|
|
423
335
|
return metadata;
|
|
424
336
|
} else {
|
|
@@ -428,52 +340,62 @@ class Automation extends MetadataType {
|
|
|
428
340
|
/**
|
|
429
341
|
* Validates the automation to be sure it can be deployed.
|
|
430
342
|
* Whitelisted Activites are deployed but require configuration
|
|
431
|
-
*
|
|
432
|
-
* @
|
|
343
|
+
*
|
|
344
|
+
* @param {TYPE.AutomationItem} metadata single automation record
|
|
345
|
+
* @returns {boolean} result if automation can be deployed based on steps
|
|
433
346
|
*/
|
|
434
347
|
static validateDeployMetadata(metadata) {
|
|
435
348
|
let deployable = true;
|
|
349
|
+
const errors = [];
|
|
436
350
|
if (metadata.steps) {
|
|
351
|
+
let stepNumber = 0;
|
|
437
352
|
for (const step of metadata.steps) {
|
|
353
|
+
stepNumber++;
|
|
354
|
+
let displayOrder = 0;
|
|
355
|
+
|
|
438
356
|
for (const activity of step.activities) {
|
|
357
|
+
displayOrder++;
|
|
439
358
|
// check if manual deploy required. if so then log warning
|
|
440
359
|
if (this.definition.manualDeployTypes.includes(activity.r__type)) {
|
|
441
360
|
Util.logger.warn(
|
|
442
|
-
|
|
443
|
-
metadata.name
|
|
444
|
-
}' requires additional manual configuration: '${
|
|
445
|
-
activity.name
|
|
446
|
-
}' in step ${step.stepNumber || step.step}.${activity.displayOrder}`
|
|
361
|
+
`- ${this.definition.type} '${metadata.name}' requires additional manual configuration: '${activity.name}' in step ${stepNumber}.${displayOrder}`
|
|
447
362
|
);
|
|
448
363
|
}
|
|
449
364
|
// cannot deploy because it is not supported
|
|
450
365
|
else if (!this.definition.dependencies.includes(activity.r__type)) {
|
|
451
|
-
|
|
452
|
-
`
|
|
453
|
-
metadata.name
|
|
454
|
-
}' cannot be deployed as the following activity is not supported: '${
|
|
455
|
-
activity.name
|
|
456
|
-
}' in step ${step.stepNumber || step.step}.${activity.displayOrder}`
|
|
366
|
+
errors.push(
|
|
367
|
+
` • not supported ${activity.r__type} activity '${activity.name}' in step ${stepNumber}.${displayOrder}`
|
|
457
368
|
);
|
|
458
369
|
deployable = false;
|
|
459
370
|
}
|
|
460
371
|
}
|
|
461
372
|
}
|
|
462
373
|
}
|
|
374
|
+
if (!deployable) {
|
|
375
|
+
Util.logger.error(
|
|
376
|
+
` ☇ skipping ${this.definition.type} ${metadata[this.definition.keyField]} / ${
|
|
377
|
+
metadata[this.definition.nameField]
|
|
378
|
+
}:`
|
|
379
|
+
);
|
|
380
|
+
for (const error of errors) {
|
|
381
|
+
Util.logger.error(error);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
463
384
|
return deployable;
|
|
464
385
|
}
|
|
465
386
|
|
|
466
387
|
/**
|
|
467
388
|
* Gets executed after deployment of metadata type
|
|
468
|
-
*
|
|
469
|
-
* @param {AutomationMap}
|
|
470
|
-
* @
|
|
389
|
+
*
|
|
390
|
+
* @param {TYPE.AutomationMap} metadata metadata mapped by their keyField
|
|
391
|
+
* @param {TYPE.AutomationMap} originalMetadata metadata to be updated (contains additioanl fields)
|
|
392
|
+
* @returns {Promise.<void>} -
|
|
471
393
|
*/
|
|
472
394
|
static async postDeployTasks(metadata, originalMetadata) {
|
|
473
395
|
for (const key in metadata) {
|
|
474
396
|
// need to put schedule on here if status is scheduled
|
|
475
397
|
|
|
476
|
-
if (originalMetadata[key]
|
|
398
|
+
if (originalMetadata[key]?.type === 'scheduled') {
|
|
477
399
|
// Starting Source == 'Schedule': Try starting the automation
|
|
478
400
|
if (originalMetadata[key].status === 'Scheduled') {
|
|
479
401
|
let schedule = null;
|
|
@@ -481,70 +403,45 @@ class Automation extends MetadataType {
|
|
|
481
403
|
schedule = this._buildSchedule(originalMetadata[key].schedule);
|
|
482
404
|
} catch (ex) {
|
|
483
405
|
Util.logger.error(
|
|
484
|
-
|
|
406
|
+
`- Could not create schedule for automation ${originalMetadata[key].name} to start it: ${ex.message}`
|
|
485
407
|
);
|
|
486
408
|
}
|
|
487
409
|
if (schedule !== null) {
|
|
488
410
|
try {
|
|
489
|
-
await
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
reject(
|
|
513
|
-
error ||
|
|
514
|
-
response.body.Results[0].StatusMessage
|
|
515
|
-
);
|
|
516
|
-
} else {
|
|
517
|
-
resolve(response.body.Results);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
);
|
|
521
|
-
});
|
|
522
|
-
const intervalString =
|
|
523
|
-
(schedule._interval > 1 ? `${schedule._interval} ` : '') +
|
|
524
|
-
(schedule.RecurrenceType === 'Daily'
|
|
525
|
-
? 'Day'
|
|
526
|
-
: schedule.RecurrenceType.slice(0, -2) +
|
|
527
|
-
(schedule._interval > 1 ? 's' : ''));
|
|
528
|
-
Util.logger.warn(
|
|
529
|
-
`Automation '${
|
|
530
|
-
originalMetadata[key].name
|
|
531
|
-
}' deployed Active: runs every ${intervalString} starting ${
|
|
532
|
-
schedule._StartDateTime
|
|
533
|
-
.split('T')
|
|
534
|
-
.join(' ')
|
|
535
|
-
.split('.')[0]
|
|
536
|
-
} ${schedule._timezoneString}`
|
|
537
|
-
);
|
|
538
|
-
}
|
|
411
|
+
await this.client.soap.schedule(
|
|
412
|
+
'Automation',
|
|
413
|
+
schedule,
|
|
414
|
+
{
|
|
415
|
+
Interaction: {
|
|
416
|
+
ObjectID: metadata[key].id,
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
'start',
|
|
420
|
+
{}
|
|
421
|
+
);
|
|
422
|
+
const intervalString =
|
|
423
|
+
(schedule._interval > 1 ? `${schedule._interval} ` : '') +
|
|
424
|
+
(schedule.RecurrenceType === 'Daily'
|
|
425
|
+
? 'Day'
|
|
426
|
+
: schedule.RecurrenceType.slice(0, -2) +
|
|
427
|
+
(schedule._interval > 1 ? 's' : ''));
|
|
428
|
+
Util.logger.warn(
|
|
429
|
+
` - scheduled automation '${
|
|
430
|
+
originalMetadata[key].name
|
|
431
|
+
}' deployed Active: runs every ${intervalString} starting ${
|
|
432
|
+
schedule._StartDateTime.split('T').join(' ').split('.')[0]
|
|
433
|
+
} ${schedule._timezoneString}`
|
|
539
434
|
);
|
|
540
435
|
} catch (ex) {
|
|
541
436
|
Util.logger.error(
|
|
542
|
-
|
|
437
|
+
`- Could not start scheduled automation '${originalMetadata[key].name}': ${ex.message}`
|
|
543
438
|
);
|
|
544
439
|
}
|
|
545
440
|
}
|
|
546
441
|
} else {
|
|
547
|
-
Util.logger.warn(
|
|
442
|
+
Util.logger.warn(
|
|
443
|
+
` - scheduled automation '${originalMetadata[key].name}' deployed Paused`
|
|
444
|
+
);
|
|
548
445
|
}
|
|
549
446
|
}
|
|
550
447
|
if (metadata[key].startSource) {
|
|
@@ -561,14 +458,14 @@ class Automation extends MetadataType {
|
|
|
561
458
|
|
|
562
459
|
/**
|
|
563
460
|
* parses retrieved Metadata before saving
|
|
564
|
-
*
|
|
565
|
-
* @
|
|
461
|
+
*
|
|
462
|
+
* @param {TYPE.AutomationItem} metadata a single automation definition
|
|
463
|
+
* @returns {TYPE.AutomationItem} parsed item
|
|
566
464
|
*/
|
|
567
465
|
static parseMetadata(metadata) {
|
|
568
466
|
// automations are often skipped due to lack of support.
|
|
569
467
|
try {
|
|
570
|
-
metadata.r__folder_Path =
|
|
571
|
-
this.cache,
|
|
468
|
+
metadata.r__folder_Path = cache.searchForField(
|
|
572
469
|
'folder',
|
|
573
470
|
metadata.categoryId,
|
|
574
471
|
'ID',
|
|
@@ -577,7 +474,9 @@ class Automation extends MetadataType {
|
|
|
577
474
|
delete metadata.categoryId;
|
|
578
475
|
if (metadata.r__folder_Path !== 'my automations') {
|
|
579
476
|
Util.logger.verbose(
|
|
580
|
-
|
|
477
|
+
`- automation '${
|
|
478
|
+
metadata[this.definition.nameField]
|
|
479
|
+
}' is located in subfolder ${
|
|
581
480
|
metadata.r__folder_Path
|
|
582
481
|
}. Please note that creating automation folders is not supported via API and hence you will have to create it manually in the GUI if you choose to deploy this automation.`
|
|
583
482
|
);
|
|
@@ -585,13 +484,13 @@ class Automation extends MetadataType {
|
|
|
585
484
|
} catch (ex) {
|
|
586
485
|
// * don't exit on missing folder for automation
|
|
587
486
|
Util.logger.warn(
|
|
588
|
-
|
|
487
|
+
` - ${this.definition.typeName} '${metadata[this.definition.nameField]}': ${
|
|
589
488
|
ex.message
|
|
590
489
|
}`
|
|
591
490
|
);
|
|
592
491
|
}
|
|
593
492
|
try {
|
|
594
|
-
if (metadata.type === 'scheduled' && metadata.schedule
|
|
493
|
+
if (metadata.type === 'scheduled' && metadata.schedule?.startDate) {
|
|
595
494
|
// Starting Source == 'Schedule'
|
|
596
495
|
|
|
597
496
|
try {
|
|
@@ -599,24 +498,21 @@ class Automation extends MetadataType {
|
|
|
599
498
|
// if we found the id in our list, remove the redundant data
|
|
600
499
|
delete metadata.schedule.timezoneId;
|
|
601
500
|
}
|
|
602
|
-
} catch
|
|
501
|
+
} catch {
|
|
603
502
|
Util.logger.debug(
|
|
604
|
-
|
|
503
|
+
`- Schedule name '${metadata.schedule.timezoneName}' not found in definition.timeZoneMapping`
|
|
605
504
|
);
|
|
606
505
|
}
|
|
607
506
|
try {
|
|
608
507
|
// type 'Running' is temporary status only, overwrite with Scheduled for storage.
|
|
609
508
|
if (metadata.type === 'scheduled' && metadata.status === 'Running') {
|
|
610
|
-
metadata.status
|
|
509
|
+
metadata.status = 'Scheduled';
|
|
611
510
|
}
|
|
612
|
-
} catch
|
|
613
|
-
Util.
|
|
614
|
-
|
|
615
|
-
this.definition.type,
|
|
616
|
-
'parseMetadata',
|
|
617
|
-
`${metadata.name} does not have a valid schedule setting. `
|
|
511
|
+
} catch {
|
|
512
|
+
Util.logger.error(
|
|
513
|
+
`- ${this.definition.type} ${metadata.name} does not have a valid schedule setting.`
|
|
618
514
|
);
|
|
619
|
-
return
|
|
515
|
+
return;
|
|
620
516
|
}
|
|
621
517
|
} else if (metadata.type === 'triggered' && metadata.fileTrigger) {
|
|
622
518
|
// Starting Source == 'File Drop'
|
|
@@ -644,32 +540,30 @@ class Automation extends MetadataType {
|
|
|
644
540
|
}
|
|
645
541
|
// / if managed by cache we can update references to support deployment
|
|
646
542
|
else if (
|
|
647
|
-
Definitions[activity.r__type] &&
|
|
648
|
-
|
|
649
|
-
this.cache[activity.r__type]
|
|
543
|
+
Definitions[activity.r__type]?.['idField'] &&
|
|
544
|
+
cache.getCache(this.buObject.mid)[activity.r__type]
|
|
650
545
|
) {
|
|
651
546
|
try {
|
|
652
|
-
activity.activityObjectId =
|
|
653
|
-
this.cache,
|
|
547
|
+
activity.activityObjectId = cache.searchForField(
|
|
654
548
|
activity.r__type,
|
|
655
549
|
activity.activityObjectId,
|
|
656
550
|
Definitions[activity.r__type].idField,
|
|
657
551
|
Definitions[activity.r__type].nameField
|
|
658
552
|
);
|
|
659
|
-
} catch (
|
|
553
|
+
} catch (ex) {
|
|
660
554
|
// getFromCache throws error where the dependent metadata is not found
|
|
661
|
-
Util.logger.
|
|
662
|
-
`Missing ${activity.r__type} activity '${activity.name}'` +
|
|
555
|
+
Util.logger.warn(
|
|
556
|
+
` - Missing ${activity.r__type} activity '${activity.name}'` +
|
|
663
557
|
` in step ${step.stepNumber || step.step}.${
|
|
664
558
|
activity.displayOrder
|
|
665
559
|
}` +
|
|
666
|
-
` of Automation '${metadata.name}' (${
|
|
560
|
+
` of Automation '${metadata.name}' (${ex.message})`
|
|
667
561
|
);
|
|
668
562
|
return null;
|
|
669
563
|
}
|
|
670
564
|
} else {
|
|
671
|
-
Util.logger.
|
|
672
|
-
`Missing ${activity.r__type} activity '${activity.name}'` +
|
|
565
|
+
Util.logger.warn(
|
|
566
|
+
` - Missing ${activity.r__type} activity '${activity.name}'` +
|
|
673
567
|
` in step ${step.stepNumber || step.step}.${
|
|
674
568
|
activity.displayOrder
|
|
675
569
|
}` +
|
|
@@ -677,19 +571,21 @@ class Automation extends MetadataType {
|
|
|
677
571
|
);
|
|
678
572
|
return null;
|
|
679
573
|
}
|
|
680
|
-
} catch
|
|
574
|
+
} catch {
|
|
681
575
|
Util.logger.warn(
|
|
682
|
-
`Excluding automation '${metadata.name}' from retrieve (ObjectType ${activity.objectTypeId} is unknown)`
|
|
576
|
+
` - Excluding automation '${metadata.name}' from retrieve (ObjectType ${activity.objectTypeId} is unknown)`
|
|
683
577
|
);
|
|
684
578
|
return null;
|
|
685
579
|
}
|
|
686
580
|
}
|
|
581
|
+
delete step.stepNumber;
|
|
582
|
+
delete step.step;
|
|
687
583
|
}
|
|
688
584
|
}
|
|
689
585
|
return JSON.parse(JSON.stringify(metadata));
|
|
690
586
|
} catch (ex) {
|
|
691
587
|
Util.logger.warn(
|
|
692
|
-
|
|
588
|
+
` - ${this.definition.typeName} '${metadata[this.definition.nameField]}': ${
|
|
693
589
|
ex.message
|
|
694
590
|
}`
|
|
695
591
|
);
|
|
@@ -700,12 +596,13 @@ class Automation extends MetadataType {
|
|
|
700
596
|
/**
|
|
701
597
|
* Builds a schedule object to be used for scheduling an automation
|
|
702
598
|
* based on combination of ical string and start/end dates.
|
|
703
|
-
*
|
|
704
|
-
* @
|
|
599
|
+
*
|
|
600
|
+
* @param {TYPE.AutomationSchedule} scheduleObject child of automation metadata used for scheduling
|
|
601
|
+
* @returns {TYPE.AutomationScheduleSoap} Schedulable object for soap API (currently not rest supported)
|
|
705
602
|
*/
|
|
706
603
|
static _buildSchedule(scheduleObject) {
|
|
707
604
|
/**
|
|
708
|
-
* @type {AutomationScheduleSoap}
|
|
605
|
+
* @type {TYPE.AutomationScheduleSoap}
|
|
709
606
|
*/
|
|
710
607
|
const schedule = { Recurrence: {}, TimeZone: { IDSpecified: true } };
|
|
711
608
|
// build recurrence
|
|
@@ -751,7 +648,7 @@ class Automation extends MetadataType {
|
|
|
751
648
|
this.definition.timeZoneMapping[scheduleObject.timezoneName];
|
|
752
649
|
} else {
|
|
753
650
|
Util.logger.error(
|
|
754
|
-
|
|
651
|
+
`- Could not find timezone ${scheduleObject.timezoneName} in definition.timeZoneMapping`
|
|
755
652
|
);
|
|
756
653
|
}
|
|
757
654
|
schedule.TimeZone.ID = scheduleObject.timezoneId;
|
|
@@ -769,25 +666,38 @@ class Automation extends MetadataType {
|
|
|
769
666
|
const scheduledDate = new Date(inputStartDateString);
|
|
770
667
|
const futureDate = new Date();
|
|
771
668
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
669
|
+
switch (keyStem) {
|
|
670
|
+
case 'Dai': {
|
|
671
|
+
// keep time from template and start today if possible
|
|
672
|
+
if (scheduledDate.getHours() <= futureDate.getHours()) {
|
|
673
|
+
// hour on template has already passed today, start tomorrow
|
|
674
|
+
futureDate.setDate(futureDate.getDate() + 1);
|
|
675
|
+
}
|
|
676
|
+
futureDate.setHours(scheduledDate.getHours());
|
|
677
|
+
futureDate.setMinutes(scheduledDate.getMinutes());
|
|
678
|
+
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
case 'Hour': {
|
|
682
|
+
// keep minute and start next possible hour
|
|
683
|
+
if (scheduledDate.getMinutes() <= futureDate.getMinutes()) {
|
|
684
|
+
futureDate.setHours(futureDate.getHours() + 1);
|
|
685
|
+
}
|
|
686
|
+
futureDate.setMinutes(scheduledDate.getMinutes());
|
|
687
|
+
|
|
688
|
+
break;
|
|
777
689
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
690
|
+
case 'Minute': {
|
|
691
|
+
// schedule in next 15 minutes randomly to avoid that all automations run at exactly
|
|
692
|
+
// earliest start 1 minute from now
|
|
693
|
+
// the same time which would slow performance
|
|
694
|
+
futureDate.setMinutes(
|
|
695
|
+
futureDate.getMinutes() + 1 + Math.ceil(Math.random() * 15)
|
|
696
|
+
);
|
|
697
|
+
|
|
698
|
+
break;
|
|
784
699
|
}
|
|
785
|
-
|
|
786
|
-
} else if (keyStem === 'Minute') {
|
|
787
|
-
// schedule in next 15 minutes randomly to avoid that all automations run at exactly
|
|
788
|
-
// earliest start 1 minute from now
|
|
789
|
-
// the same time which would slow performance
|
|
790
|
-
futureDate.setMinutes(futureDate.getMinutes() + 1 + Math.ceil(Math.random() * 15));
|
|
700
|
+
// No default
|
|
791
701
|
}
|
|
792
702
|
// return time as Dateobject
|
|
793
703
|
schedule.StartDateTime = futureDate;
|
|
@@ -824,12 +734,10 @@ class Automation extends MetadataType {
|
|
|
824
734
|
*/
|
|
825
735
|
static _calcTime(offsetServer, dateInput, offsetInput) {
|
|
826
736
|
// get UTC time in msec
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
utc = dateInput.getTime();
|
|
832
|
-
}
|
|
737
|
+
const utc =
|
|
738
|
+
'string' === typeof dateInput
|
|
739
|
+
? new Date(dateInput + offsetInput).getTime()
|
|
740
|
+
: dateInput.getTime();
|
|
833
741
|
|
|
834
742
|
// create new Date object reflecting SFMC's servertime
|
|
835
743
|
const dateServer = new Date(utc + 3600000 * offsetServer);
|
|
@@ -837,14 +745,203 @@ class Automation extends MetadataType {
|
|
|
837
745
|
// return time as a string without trailing "Z"
|
|
838
746
|
return dateServer.toISOString().slice(0, -1);
|
|
839
747
|
}
|
|
748
|
+
/**
|
|
749
|
+
* Experimental: Only working for DataExtensions:
|
|
750
|
+
* Saves json content to a html table in the local file system. Will create the parent directory if it does not exist.
|
|
751
|
+
* The json's first level of keys must represent the rows and the secend level the columns
|
|
752
|
+
*
|
|
753
|
+
* @private
|
|
754
|
+
* @param {TYPE.AutomationItem} json dataextension
|
|
755
|
+
* @param {object[][]} tabled prepped array for output in tabular format
|
|
756
|
+
* @returns {string} file content
|
|
757
|
+
*/
|
|
758
|
+
static _generateDocMd(json, tabled) {
|
|
759
|
+
let output = `## ${json.key}\n\n`;
|
|
760
|
+
if (json.key !== json.name) {
|
|
761
|
+
output += `**Name** (not equal to External Key)**:** ${json.name}\n\n`;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
output +=
|
|
765
|
+
`**Description:** ${json.description || 'n/a'}\n\n` +
|
|
766
|
+
`**Folder:** ${
|
|
767
|
+
json.r__folder_Path ||
|
|
768
|
+
'_Hidden! Could not find folder with ID ' + json.categoryId + '_'
|
|
769
|
+
}/\n\n`;
|
|
770
|
+
const automationType = { scheduled: 'Schedule', triggered: 'File Drop' };
|
|
771
|
+
output += `**Started by:** ${automationType[json.type] || 'Not defined'}\n\n`;
|
|
772
|
+
output += `**Status:** ${json.status}\n\n`;
|
|
773
|
+
if (json.type === 'scheduled') {
|
|
774
|
+
const tz =
|
|
775
|
+
this.definition.timeZoneDifference[
|
|
776
|
+
this.definition.timeZoneMapping[json?.schedule?.timezoneName]
|
|
777
|
+
];
|
|
778
|
+
|
|
779
|
+
if (json.schedule?.icalRecur) {
|
|
780
|
+
output += `**Schedule:**\n\n`;
|
|
781
|
+
output += `* Start: ${json.schedule.startDate.split('T').join(' ')} ${tz}\n`;
|
|
782
|
+
output += `* End: ${json.schedule.endDate.split('T').join(' ')} ${tz}\n`;
|
|
783
|
+
output += `* Timezone: ${json.schedule.timezoneName}\n`;
|
|
784
|
+
|
|
785
|
+
const ical = {};
|
|
786
|
+
for (const item of json.schedule.icalRecur.split(';')) {
|
|
787
|
+
const temp = item.split('=');
|
|
788
|
+
ical[temp[0]] = temp[1];
|
|
789
|
+
}
|
|
790
|
+
const frequency = ical.FREQ.slice(0, -2).toLowerCase();
|
|
791
|
+
|
|
792
|
+
output += `* Recurrance: every ${ical.INTERVAL > 1 ? ical.INTERVAL : ''} ${
|
|
793
|
+
frequency === 'dai' ? 'day' : frequency
|
|
794
|
+
}${ical.INTERVAL > 1 ? 's' : ''} ${ical.COUNT ? `for ${ical.COUNT} times` : ''}\n`;
|
|
795
|
+
output += '\n';
|
|
796
|
+
} else if (json.schedule) {
|
|
797
|
+
output += `**Schedule:** Not defined\n\n`;
|
|
798
|
+
}
|
|
799
|
+
} else if (json.type === 'triggered' && json.fileTrigger) {
|
|
800
|
+
output += `**File Trigger:**\n\n`;
|
|
801
|
+
output += `* Queue Files: ${json.fileTrigger.queueFiles}\n`;
|
|
802
|
+
output += `* Published: ${json.fileTrigger.isPublished}\n`;
|
|
803
|
+
output += `* Pattern: ${json.fileTrigger.fileNamingPattern}\n`;
|
|
804
|
+
output += `* Folder: ${json.fileTrigger.folderLocationText}\n`;
|
|
805
|
+
}
|
|
806
|
+
if (tabled && tabled.length) {
|
|
807
|
+
let tableSeparator = '';
|
|
808
|
+
const row1 = [];
|
|
809
|
+
for (const column of tabled[0]) {
|
|
810
|
+
row1.push(
|
|
811
|
+
`| ${column.title}${column.description ? `<br>_${column.description}_` : ''} `
|
|
812
|
+
);
|
|
813
|
+
tableSeparator += '| --- ';
|
|
814
|
+
}
|
|
815
|
+
output += row1.join('') + `|\n${tableSeparator}|\n`;
|
|
816
|
+
for (let i = 1; i < tabled.length; i++) {
|
|
817
|
+
for (const field of tabled[i]) {
|
|
818
|
+
output += field ? `| _${field.i}: ${field.type}_<br>${field.name} ` : '| - ';
|
|
819
|
+
}
|
|
820
|
+
output += '|\n';
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
return output;
|
|
824
|
+
}
|
|
825
|
+
/**
|
|
826
|
+
* Saves json content to a html table in the local file system. Will create the parent directory if it does not exist.
|
|
827
|
+
* The json's first level of keys must represent the rows and the secend level the columns
|
|
828
|
+
*
|
|
829
|
+
* @private
|
|
830
|
+
* @param {string} directory directory the file will be written to
|
|
831
|
+
* @param {string} filename name of the file without '.json' ending
|
|
832
|
+
* @param {TYPE.AutomationItem} json dataextension.columns
|
|
833
|
+
* @param {'html'|'md'} mode html or md
|
|
834
|
+
* @returns {Promise.<boolean>} Promise of success of saving the file
|
|
835
|
+
*/
|
|
836
|
+
static async _writeDoc(directory, filename, json, mode) {
|
|
837
|
+
await File.ensureDir(directory);
|
|
838
|
+
|
|
839
|
+
const tabled = [];
|
|
840
|
+
if (json.steps && json.steps.length) {
|
|
841
|
+
tabled.push(
|
|
842
|
+
json.steps.map((step, index) => ({
|
|
843
|
+
title: `Step ${index + 1}`,
|
|
844
|
+
description: step.name || '-',
|
|
845
|
+
}))
|
|
846
|
+
);
|
|
847
|
+
let maxActivities = 0;
|
|
848
|
+
for (const step of json.steps) {
|
|
849
|
+
if (step.activities.length > maxActivities) {
|
|
850
|
+
maxActivities = step.activities.length;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
for (let activityIndex = 0; activityIndex < maxActivities; activityIndex++) {
|
|
854
|
+
tabled.push(
|
|
855
|
+
json.steps.map((step, stepIndex) =>
|
|
856
|
+
step.activities[activityIndex]
|
|
857
|
+
? {
|
|
858
|
+
i: stepIndex + 1 + '.' + (activityIndex + 1),
|
|
859
|
+
name: step.activities[activityIndex].name,
|
|
860
|
+
type: step.activities[activityIndex].r__type,
|
|
861
|
+
}
|
|
862
|
+
: null
|
|
863
|
+
)
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
let output;
|
|
868
|
+
if (mode === 'md') {
|
|
869
|
+
output = this._generateDocMd(json, tabled);
|
|
870
|
+
try {
|
|
871
|
+
// write to disk
|
|
872
|
+
await File.writeToFile(directory, filename + '.automation-doc', mode, output);
|
|
873
|
+
} catch (ex) {
|
|
874
|
+
Util.logger.error(`Automation.writeDeToX(${mode}):: error | ` + ex.message);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Parses metadata into a readable Markdown/HTML format then saves it
|
|
880
|
+
*
|
|
881
|
+
* @param {TYPE.BuObject} buObject properties for auth
|
|
882
|
+
* @param {TYPE.AutomationMap} [metadata] a list of dataExtension definitions
|
|
883
|
+
* @returns {Promise.<void>} -
|
|
884
|
+
*/
|
|
885
|
+
static async document(buObject, metadata) {
|
|
886
|
+
if (['md', 'both'].includes(this.properties.options.documentType)) {
|
|
887
|
+
if (!metadata) {
|
|
888
|
+
metadata = this.readBUMetadataForType(
|
|
889
|
+
File.normalizePath([
|
|
890
|
+
this.properties.directories.retrieve,
|
|
891
|
+
buObject.credential,
|
|
892
|
+
buObject.businessUnit,
|
|
893
|
+
]),
|
|
894
|
+
true
|
|
895
|
+
).automation;
|
|
896
|
+
}
|
|
897
|
+
const docPath = File.normalizePath([
|
|
898
|
+
this.properties.directories.retrieve,
|
|
899
|
+
buObject.credential,
|
|
900
|
+
buObject.businessUnit,
|
|
901
|
+
this.definition.type,
|
|
902
|
+
]);
|
|
903
|
+
if (!metadata || !Object.keys(metadata).length) {
|
|
904
|
+
// as part of retrieve & manual execution we could face an empty folder
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
await Promise.all(
|
|
908
|
+
Object.keys(metadata).map((key) => {
|
|
909
|
+
this._writeDoc(docPath + '/', key, metadata[key], 'md');
|
|
910
|
+
return metadata[key];
|
|
911
|
+
})
|
|
912
|
+
);
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* should return only the json for all but asset, query and script that are saved as multiple files
|
|
917
|
+
* additionally, the documentation for dataExtension and automation should be returned
|
|
918
|
+
*
|
|
919
|
+
* @param {string[]} keyArr customerkey of the metadata
|
|
920
|
+
* @returns {string[]} list of all files that need to be committed in a flat array ['path/file1.ext', 'path/file2.ext']
|
|
921
|
+
*/
|
|
922
|
+
static getFilesToCommit(keyArr) {
|
|
923
|
+
if (!this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type)) {
|
|
924
|
+
// document automation is not active upon retrieve, run default method instead
|
|
925
|
+
return super.getFilesToCommit(keyArr);
|
|
926
|
+
} else {
|
|
927
|
+
// document automation is active. assume we want to commit the MD file as well
|
|
928
|
+
const path = File.normalizePath([
|
|
929
|
+
this.properties.directories.retrieve,
|
|
930
|
+
this.buObject.credential,
|
|
931
|
+
this.buObject.businessUnit,
|
|
932
|
+
this.definition.type,
|
|
933
|
+
]);
|
|
934
|
+
|
|
935
|
+
const fileList = keyArr.flatMap((key) => [
|
|
936
|
+
File.normalizePath([path, `${key}.${this.definition.type}-meta.json`]),
|
|
937
|
+
File.normalizePath([path, `${key}.${this.definition.type}-doc.md`]),
|
|
938
|
+
]);
|
|
939
|
+
return fileList;
|
|
940
|
+
}
|
|
941
|
+
}
|
|
840
942
|
}
|
|
841
943
|
|
|
842
944
|
// Assign definition to static attributes
|
|
843
945
|
Automation.definition = Definitions.automation;
|
|
844
|
-
Automation.cache = {};
|
|
845
|
-
/**
|
|
846
|
-
* @type {Util.ET_Client}
|
|
847
|
-
*/
|
|
848
|
-
Automation.client = undefined;
|
|
849
946
|
|
|
850
947
|
module.exports = Automation;
|