mcdev 3.1.3 → 4.0.1
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 +30 -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 +2807 -1730
- package/jsconfig.json +1 -1
- package/lib/Builder.js +171 -74
- 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 +117 -103
- package/lib/metadataTypes/Asset.js +705 -255
- package/lib/metadataTypes/AttributeGroup.js +23 -12
- package/lib/metadataTypes/Automation.js +489 -392
- 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 +664 -454
- package/lib/metadataTypes/MobileCode.js +46 -0
- package/lib/metadataTypes/MobileKeyword.js +114 -0
- package/lib/metadataTypes/Query.js +206 -105
- package/lib/metadataTypes/Role.js +76 -61
- package/lib/metadataTypes/Script.js +147 -83
- 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 +250 -50
- package/lib/util/file.js +141 -201
- package/lib/util/init.config.js +208 -75
- 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 +45 -34
- 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,73 +102,54 @@ 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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
115
|
+
if (Array.isArray(results?.Results)) {
|
|
116
|
+
for (const m of results.Results) {
|
|
117
|
+
resultsConverted[m.CustomerKey] = {
|
|
118
|
+
id: m.ObjectID,
|
|
119
|
+
key: m.CustomerKey,
|
|
120
|
+
name: m.Name,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
197
123
|
}
|
|
198
124
|
return { metadata: resultsConverted, type: this.definition.type };
|
|
199
125
|
}
|
|
200
126
|
|
|
201
127
|
/**
|
|
202
128
|
* Retrieve a specific Automation Definition by Name
|
|
129
|
+
*
|
|
203
130
|
* @param {string} templateDir Directory where retrieved metadata directory will be saved
|
|
204
131
|
* @param {string} name name of the metadata file
|
|
205
|
-
* @param {
|
|
206
|
-
* @returns {Promise
|
|
132
|
+
* @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
|
|
133
|
+
* @returns {Promise.<TYPE.AutomationItemObj>} Promise of metadata
|
|
207
134
|
*/
|
|
208
135
|
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
|
-
);
|
|
136
|
+
const results = await this.client.soap.retrieve('Program', ['ObjectID', 'Name'], {
|
|
137
|
+
filter: {
|
|
138
|
+
leftOperand: 'Name',
|
|
139
|
+
operator: 'equals',
|
|
140
|
+
rightOperand: name,
|
|
141
|
+
},
|
|
228
142
|
});
|
|
229
|
-
if (results
|
|
143
|
+
if (Array.isArray(results?.Results)) {
|
|
230
144
|
// eq-operator returns a similar, not exact match and hence might return more than 1 entry
|
|
231
|
-
const
|
|
145
|
+
const metadata = results.Results.find((item) => item.Name === name);
|
|
232
146
|
if (!metadata) {
|
|
233
147
|
Util.logger.error(`${this.definition.type} '${name}' not found on server.`);
|
|
234
148
|
return;
|
|
235
149
|
}
|
|
236
|
-
const details = (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
})
|
|
240
|
-
).body;
|
|
150
|
+
const details = await this.client.rest.get(
|
|
151
|
+
'/automation/v1/automations/' + metadata.ObjectID
|
|
152
|
+
);
|
|
241
153
|
let val = null;
|
|
242
154
|
let originalKey;
|
|
243
155
|
// if parsing fails, we should just save what we get
|
|
@@ -249,7 +161,7 @@ class Automation extends MetadataType {
|
|
|
249
161
|
Util.replaceByObject(JSON.stringify(parsedDetails), templateVariables)
|
|
250
162
|
);
|
|
251
163
|
}
|
|
252
|
-
} catch
|
|
164
|
+
} catch {
|
|
253
165
|
val = JSON.parse(JSON.stringify(details));
|
|
254
166
|
}
|
|
255
167
|
if (val === null) {
|
|
@@ -259,14 +171,12 @@ class Automation extends MetadataType {
|
|
|
259
171
|
}
|
|
260
172
|
// remove all fields not listed in Definition for templating
|
|
261
173
|
this.keepTemplateFields(val);
|
|
262
|
-
File.writeJSONToFile(
|
|
174
|
+
await File.writeJSONToFile(
|
|
263
175
|
[templateDir, this.definition.type].join('/'),
|
|
264
176
|
originalKey + '.' + this.definition.type + '-meta',
|
|
265
177
|
val
|
|
266
178
|
);
|
|
267
|
-
Util.logger.info(
|
|
268
|
-
`Automation.retrieveAsTemplate:: Written Metadata to filesystem (${name})`
|
|
269
|
-
);
|
|
179
|
+
Util.logger.info(`- templated ${this.definition.type}: ${name}`);
|
|
270
180
|
return { metadata: val, type: this.definition.type };
|
|
271
181
|
} else if (results) {
|
|
272
182
|
Util.logger.error(`${this.definition.type} '${name}' not found on server.`);
|
|
@@ -278,26 +188,21 @@ class Automation extends MetadataType {
|
|
|
278
188
|
}
|
|
279
189
|
/**
|
|
280
190
|
* manages post retrieve steps
|
|
281
|
-
*
|
|
282
|
-
* @param {
|
|
283
|
-
* @
|
|
284
|
-
* @returns {AutomationItem} metadata
|
|
191
|
+
*
|
|
192
|
+
* @param {TYPE.AutomationItem} metadata a single automation
|
|
193
|
+
* @returns {TYPE.AutomationItem} metadata
|
|
285
194
|
*/
|
|
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
|
-
}
|
|
195
|
+
static postRetrieveTasks(metadata) {
|
|
292
196
|
return this.parseMetadata(metadata);
|
|
293
197
|
}
|
|
294
198
|
|
|
295
199
|
/**
|
|
296
200
|
* Deploys automation - the saved file is the original one due to large differences required for deployment
|
|
297
|
-
*
|
|
201
|
+
*
|
|
202
|
+
* @param {TYPE.AutomationMap} metadata metadata mapped by their keyField
|
|
298
203
|
* @param {string} targetBU name/shorthand of target businessUnit for mapping
|
|
299
204
|
* @param {string} retrieveDir directory where metadata after deploy should be saved
|
|
300
|
-
* @returns {Promise
|
|
205
|
+
* @returns {Promise.<TYPE.AutomationMap>} Promise
|
|
301
206
|
*/
|
|
302
207
|
static async deploy(metadata, targetBU, retrieveDir) {
|
|
303
208
|
const orignalMetadata = JSON.parse(JSON.stringify(metadata));
|
|
@@ -309,7 +214,8 @@ class Automation extends MetadataType {
|
|
|
309
214
|
|
|
310
215
|
/**
|
|
311
216
|
* Creates a single automation
|
|
312
|
-
*
|
|
217
|
+
*
|
|
218
|
+
* @param {TYPE.AutomationItem} metadata single metadata entry
|
|
313
219
|
* @returns {Promise} Promise
|
|
314
220
|
*/
|
|
315
221
|
static create(metadata) {
|
|
@@ -319,8 +225,9 @@ class Automation extends MetadataType {
|
|
|
319
225
|
|
|
320
226
|
/**
|
|
321
227
|
* Updates a single automation
|
|
322
|
-
*
|
|
323
|
-
* @param {AutomationItem}
|
|
228
|
+
*
|
|
229
|
+
* @param {TYPE.AutomationItem} metadata single metadata entry
|
|
230
|
+
* @param {TYPE.AutomationItem} metadataBefore metadata mapped by their keyField
|
|
324
231
|
* @returns {Promise} Promise
|
|
325
232
|
*/
|
|
326
233
|
static update(metadata, metadataBefore) {
|
|
@@ -331,14 +238,14 @@ class Automation extends MetadataType {
|
|
|
331
238
|
|
|
332
239
|
/**
|
|
333
240
|
* Gets executed before deploying metadata
|
|
334
|
-
*
|
|
335
|
-
* @
|
|
241
|
+
*
|
|
242
|
+
* @param {TYPE.AutomationItem} metadata metadata mapped by their keyField
|
|
243
|
+
* @returns {Promise.<TYPE.AutomationItem>} Promise
|
|
336
244
|
*/
|
|
337
245
|
static async preDeployTasks(metadata) {
|
|
338
246
|
if (this.validateDeployMetadata(metadata)) {
|
|
339
247
|
try {
|
|
340
|
-
metadata.categoryId =
|
|
341
|
-
this.cache,
|
|
248
|
+
metadata.categoryId = cache.searchForField(
|
|
342
249
|
'folder',
|
|
343
250
|
metadata.r__folder_Path,
|
|
344
251
|
'Path',
|
|
@@ -346,7 +253,7 @@ class Automation extends MetadataType {
|
|
|
346
253
|
);
|
|
347
254
|
if (metadata.r__folder_Path !== 'my automations') {
|
|
348
255
|
Util.logger.warn(
|
|
349
|
-
`Automation '${
|
|
256
|
+
` - Automation '${
|
|
350
257
|
metadata[this.definition.nameField]
|
|
351
258
|
}' is located in subfolder ${
|
|
352
259
|
metadata.r__folder_Path
|
|
@@ -354,12 +261,12 @@ class Automation extends MetadataType {
|
|
|
354
261
|
);
|
|
355
262
|
}
|
|
356
263
|
delete metadata.r__folder_Path;
|
|
357
|
-
} catch
|
|
264
|
+
} catch {
|
|
358
265
|
throw new Error(
|
|
359
266
|
`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
267
|
);
|
|
361
268
|
}
|
|
362
|
-
if (metadata.type === 'scheduled' && metadata
|
|
269
|
+
if (metadata.type === 'scheduled' && metadata?.schedule?.startDate) {
|
|
363
270
|
// Starting Source == 'Schedule'
|
|
364
271
|
|
|
365
272
|
delete metadata.schedule.rangeTypeId;
|
|
@@ -399,26 +306,33 @@ class Automation extends MetadataType {
|
|
|
399
306
|
delete metadata.schedule;
|
|
400
307
|
delete metadata.type;
|
|
401
308
|
let i = 0;
|
|
402
|
-
|
|
403
|
-
for (const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
activity.
|
|
407
|
-
|
|
408
|
-
activity.
|
|
409
|
-
activity.
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
309
|
+
if (metadata.steps) {
|
|
310
|
+
for (const step of metadata.steps) {
|
|
311
|
+
let displayOrder = 0;
|
|
312
|
+
for (const activity of step.activities) {
|
|
313
|
+
activity.displayOrder = ++displayOrder;
|
|
314
|
+
if (
|
|
315
|
+
activity.name &&
|
|
316
|
+
this.definition.dependencies.includes(activity.r__type)
|
|
317
|
+
) {
|
|
318
|
+
// automations can have empty placeholder for activities with only their type defined
|
|
319
|
+
activity.activityObjectId = cache.searchForField(
|
|
320
|
+
activity.r__type,
|
|
321
|
+
activity.name,
|
|
322
|
+
Definitions[activity.r__type].nameField,
|
|
323
|
+
Definitions[activity.r__type].idField
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
activity.objectTypeId =
|
|
327
|
+
this.definition.activityTypeMapping[activity.r__type];
|
|
328
|
+
delete activity.r__type;
|
|
413
329
|
}
|
|
414
|
-
|
|
415
|
-
|
|
330
|
+
step.annotation = step.name;
|
|
331
|
+
step.stepNumber = i;
|
|
332
|
+
delete step.name;
|
|
333
|
+
delete step.step;
|
|
334
|
+
i++;
|
|
416
335
|
}
|
|
417
|
-
step.annotation = step.name;
|
|
418
|
-
step.stepNumber = i;
|
|
419
|
-
delete step.name;
|
|
420
|
-
delete step.step;
|
|
421
|
-
i++;
|
|
422
336
|
}
|
|
423
337
|
return metadata;
|
|
424
338
|
} else {
|
|
@@ -428,52 +342,62 @@ class Automation extends MetadataType {
|
|
|
428
342
|
/**
|
|
429
343
|
* Validates the automation to be sure it can be deployed.
|
|
430
344
|
* Whitelisted Activites are deployed but require configuration
|
|
431
|
-
*
|
|
432
|
-
* @
|
|
345
|
+
*
|
|
346
|
+
* @param {TYPE.AutomationItem} metadata single automation record
|
|
347
|
+
* @returns {boolean} result if automation can be deployed based on steps
|
|
433
348
|
*/
|
|
434
349
|
static validateDeployMetadata(metadata) {
|
|
435
350
|
let deployable = true;
|
|
351
|
+
const errors = [];
|
|
436
352
|
if (metadata.steps) {
|
|
353
|
+
let stepNumber = 0;
|
|
437
354
|
for (const step of metadata.steps) {
|
|
355
|
+
stepNumber++;
|
|
356
|
+
let displayOrder = 0;
|
|
357
|
+
|
|
438
358
|
for (const activity of step.activities) {
|
|
359
|
+
displayOrder++;
|
|
439
360
|
// check if manual deploy required. if so then log warning
|
|
440
361
|
if (this.definition.manualDeployTypes.includes(activity.r__type)) {
|
|
441
362
|
Util.logger.warn(
|
|
442
|
-
|
|
443
|
-
metadata.name
|
|
444
|
-
}' requires additional manual configuration: '${
|
|
445
|
-
activity.name
|
|
446
|
-
}' in step ${step.stepNumber || step.step}.${activity.displayOrder}`
|
|
363
|
+
`- ${this.definition.type} '${metadata.name}' requires additional manual configuration: '${activity.name}' in step ${stepNumber}.${displayOrder}`
|
|
447
364
|
);
|
|
448
365
|
}
|
|
449
366
|
// cannot deploy because it is not supported
|
|
450
367
|
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}`
|
|
368
|
+
errors.push(
|
|
369
|
+
` • not supported ${activity.r__type} activity '${activity.name}' in step ${stepNumber}.${displayOrder}`
|
|
457
370
|
);
|
|
458
371
|
deployable = false;
|
|
459
372
|
}
|
|
460
373
|
}
|
|
461
374
|
}
|
|
462
375
|
}
|
|
376
|
+
if (!deployable) {
|
|
377
|
+
Util.logger.error(
|
|
378
|
+
` ☇ skipping ${this.definition.type} ${metadata[this.definition.keyField]} / ${
|
|
379
|
+
metadata[this.definition.nameField]
|
|
380
|
+
}:`
|
|
381
|
+
);
|
|
382
|
+
for (const error of errors) {
|
|
383
|
+
Util.logger.error(error);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
463
386
|
return deployable;
|
|
464
387
|
}
|
|
465
388
|
|
|
466
389
|
/**
|
|
467
390
|
* Gets executed after deployment of metadata type
|
|
468
|
-
*
|
|
469
|
-
* @param {AutomationMap}
|
|
470
|
-
* @
|
|
391
|
+
*
|
|
392
|
+
* @param {TYPE.AutomationMap} metadata metadata mapped by their keyField
|
|
393
|
+
* @param {TYPE.AutomationMap} originalMetadata metadata to be updated (contains additioanl fields)
|
|
394
|
+
* @returns {Promise.<void>} -
|
|
471
395
|
*/
|
|
472
396
|
static async postDeployTasks(metadata, originalMetadata) {
|
|
473
397
|
for (const key in metadata) {
|
|
474
398
|
// need to put schedule on here if status is scheduled
|
|
475
399
|
|
|
476
|
-
if (originalMetadata[key]
|
|
400
|
+
if (originalMetadata[key]?.type === 'scheduled') {
|
|
477
401
|
// Starting Source == 'Schedule': Try starting the automation
|
|
478
402
|
if (originalMetadata[key].status === 'Scheduled') {
|
|
479
403
|
let schedule = null;
|
|
@@ -481,70 +405,45 @@ class Automation extends MetadataType {
|
|
|
481
405
|
schedule = this._buildSchedule(originalMetadata[key].schedule);
|
|
482
406
|
} catch (ex) {
|
|
483
407
|
Util.logger.error(
|
|
484
|
-
|
|
408
|
+
`- Could not create schedule for automation ${originalMetadata[key].name} to start it: ${ex.message}`
|
|
485
409
|
);
|
|
486
410
|
}
|
|
487
411
|
if (schedule !== null) {
|
|
488
412
|
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
|
-
}
|
|
413
|
+
await this.client.soap.schedule(
|
|
414
|
+
'Automation',
|
|
415
|
+
schedule,
|
|
416
|
+
{
|
|
417
|
+
Interaction: {
|
|
418
|
+
ObjectID: metadata[key].id,
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
'start',
|
|
422
|
+
{}
|
|
423
|
+
);
|
|
424
|
+
const intervalString =
|
|
425
|
+
(schedule._interval > 1 ? `${schedule._interval} ` : '') +
|
|
426
|
+
(schedule.RecurrenceType === 'Daily'
|
|
427
|
+
? 'Day'
|
|
428
|
+
: schedule.RecurrenceType.slice(0, -2) +
|
|
429
|
+
(schedule._interval > 1 ? 's' : ''));
|
|
430
|
+
Util.logger.warn(
|
|
431
|
+
` - scheduled automation '${
|
|
432
|
+
originalMetadata[key].name
|
|
433
|
+
}' deployed Active: runs every ${intervalString} starting ${
|
|
434
|
+
schedule._StartDateTime.split('T').join(' ').split('.')[0]
|
|
435
|
+
} ${schedule._timezoneString}`
|
|
539
436
|
);
|
|
540
437
|
} catch (ex) {
|
|
541
438
|
Util.logger.error(
|
|
542
|
-
|
|
439
|
+
`- Could not start scheduled automation '${originalMetadata[key].name}': ${ex.message}`
|
|
543
440
|
);
|
|
544
441
|
}
|
|
545
442
|
}
|
|
546
443
|
} else {
|
|
547
|
-
Util.logger.warn(
|
|
444
|
+
Util.logger.warn(
|
|
445
|
+
` - scheduled automation '${originalMetadata[key].name}' deployed Paused`
|
|
446
|
+
);
|
|
548
447
|
}
|
|
549
448
|
}
|
|
550
449
|
if (metadata[key].startSource) {
|
|
@@ -561,14 +460,14 @@ class Automation extends MetadataType {
|
|
|
561
460
|
|
|
562
461
|
/**
|
|
563
462
|
* parses retrieved Metadata before saving
|
|
564
|
-
*
|
|
565
|
-
* @
|
|
463
|
+
*
|
|
464
|
+
* @param {TYPE.AutomationItem} metadata a single automation definition
|
|
465
|
+
* @returns {TYPE.AutomationItem} parsed item
|
|
566
466
|
*/
|
|
567
467
|
static parseMetadata(metadata) {
|
|
568
468
|
// automations are often skipped due to lack of support.
|
|
569
469
|
try {
|
|
570
|
-
metadata.r__folder_Path =
|
|
571
|
-
this.cache,
|
|
470
|
+
metadata.r__folder_Path = cache.searchForField(
|
|
572
471
|
'folder',
|
|
573
472
|
metadata.categoryId,
|
|
574
473
|
'ID',
|
|
@@ -577,7 +476,9 @@ class Automation extends MetadataType {
|
|
|
577
476
|
delete metadata.categoryId;
|
|
578
477
|
if (metadata.r__folder_Path !== 'my automations') {
|
|
579
478
|
Util.logger.verbose(
|
|
580
|
-
|
|
479
|
+
`- automation '${
|
|
480
|
+
metadata[this.definition.nameField]
|
|
481
|
+
}' is located in subfolder ${
|
|
581
482
|
metadata.r__folder_Path
|
|
582
483
|
}. 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
484
|
);
|
|
@@ -585,13 +486,13 @@ class Automation extends MetadataType {
|
|
|
585
486
|
} catch (ex) {
|
|
586
487
|
// * don't exit on missing folder for automation
|
|
587
488
|
Util.logger.warn(
|
|
588
|
-
|
|
489
|
+
` - ${this.definition.typeName} '${metadata[this.definition.nameField]}': ${
|
|
589
490
|
ex.message
|
|
590
491
|
}`
|
|
591
492
|
);
|
|
592
493
|
}
|
|
593
494
|
try {
|
|
594
|
-
if (metadata.type === 'scheduled' && metadata.schedule
|
|
495
|
+
if (metadata.type === 'scheduled' && metadata.schedule?.startDate) {
|
|
595
496
|
// Starting Source == 'Schedule'
|
|
596
497
|
|
|
597
498
|
try {
|
|
@@ -599,24 +500,21 @@ class Automation extends MetadataType {
|
|
|
599
500
|
// if we found the id in our list, remove the redundant data
|
|
600
501
|
delete metadata.schedule.timezoneId;
|
|
601
502
|
}
|
|
602
|
-
} catch
|
|
503
|
+
} catch {
|
|
603
504
|
Util.logger.debug(
|
|
604
|
-
|
|
505
|
+
`- Schedule name '${metadata.schedule.timezoneName}' not found in definition.timeZoneMapping`
|
|
605
506
|
);
|
|
606
507
|
}
|
|
607
508
|
try {
|
|
608
509
|
// type 'Running' is temporary status only, overwrite with Scheduled for storage.
|
|
609
510
|
if (metadata.type === 'scheduled' && metadata.status === 'Running') {
|
|
610
|
-
metadata.status
|
|
511
|
+
metadata.status = 'Scheduled';
|
|
611
512
|
}
|
|
612
|
-
} catch
|
|
613
|
-
Util.
|
|
614
|
-
|
|
615
|
-
this.definition.type,
|
|
616
|
-
'parseMetadata',
|
|
617
|
-
`${metadata.name} does not have a valid schedule setting. `
|
|
513
|
+
} catch {
|
|
514
|
+
Util.logger.error(
|
|
515
|
+
`- ${this.definition.type} ${metadata.name} does not have a valid schedule setting.`
|
|
618
516
|
);
|
|
619
|
-
return
|
|
517
|
+
return;
|
|
620
518
|
}
|
|
621
519
|
} else if (metadata.type === 'triggered' && metadata.fileTrigger) {
|
|
622
520
|
// Starting Source == 'File Drop'
|
|
@@ -624,6 +522,10 @@ class Automation extends MetadataType {
|
|
|
624
522
|
}
|
|
625
523
|
if (metadata.steps) {
|
|
626
524
|
for (const step of metadata.steps) {
|
|
525
|
+
const stepNumber = step.stepNumber || step.step;
|
|
526
|
+
delete step.stepNumber;
|
|
527
|
+
delete step.step;
|
|
528
|
+
|
|
627
529
|
for (const activity of step.activities) {
|
|
628
530
|
try {
|
|
629
531
|
// get metadata type of activity
|
|
@@ -632,56 +534,50 @@ class Automation extends MetadataType {
|
|
|
632
534
|
activity.objectTypeId
|
|
633
535
|
);
|
|
634
536
|
delete activity.objectTypeId;
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
'
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
Util.logger.error(
|
|
672
|
-
`Missing ${activity.r__type} activity '${activity.name}'` +
|
|
673
|
-
` in step ${step.stepNumber || step.step}.${
|
|
674
|
-
activity.displayOrder
|
|
675
|
-
}` +
|
|
676
|
-
` of Automation '${metadata.name}' (Not Found in Cache)`
|
|
537
|
+
} catch {
|
|
538
|
+
Util.logger.warn(
|
|
539
|
+
` - Unknown activity type '${activity.objectTypeId}'` +
|
|
540
|
+
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
541
|
+
` of Automation '${metadata.name}'`
|
|
542
|
+
);
|
|
543
|
+
continue;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// if no activityObjectId then either serialized activity
|
|
547
|
+
// (config in Automation ) or unconfigured so no further action to be taken
|
|
548
|
+
if (
|
|
549
|
+
activity.activityObjectId === '00000000-0000-0000-0000-000000000000' ||
|
|
550
|
+
activity.activityObjectId == null ||
|
|
551
|
+
!this.definition.dependencies.includes(activity.r__type)
|
|
552
|
+
) {
|
|
553
|
+
// empty if block
|
|
554
|
+
}
|
|
555
|
+
// / if managed by cache we can update references to support deployment
|
|
556
|
+
else if (
|
|
557
|
+
Definitions[activity.r__type]?.['idField'] &&
|
|
558
|
+
cache.getCache(this.buObject.mid)[activity.r__type]
|
|
559
|
+
) {
|
|
560
|
+
try {
|
|
561
|
+
activity.activityObjectId = cache.searchForField(
|
|
562
|
+
activity.r__type,
|
|
563
|
+
activity.activityObjectId,
|
|
564
|
+
Definitions[activity.r__type].idField,
|
|
565
|
+
Definitions[activity.r__type].nameField
|
|
566
|
+
);
|
|
567
|
+
} catch (ex) {
|
|
568
|
+
// getFromCache throws error where the dependent metadata is not found
|
|
569
|
+
Util.logger.warn(
|
|
570
|
+
` - Missing ${activity.r__type} activity '${activity.name}'` +
|
|
571
|
+
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
572
|
+
` of Automation '${metadata.name}' (${ex.message})`
|
|
677
573
|
);
|
|
678
|
-
return null;
|
|
679
574
|
}
|
|
680
|
-
}
|
|
575
|
+
} else {
|
|
681
576
|
Util.logger.warn(
|
|
682
|
-
`
|
|
577
|
+
` - Missing ${activity.r__type} activity '${activity.name}'` +
|
|
578
|
+
` in step ${stepNumber}.${activity.displayOrder}` +
|
|
579
|
+
` of Automation '${metadata.name}' (Not Found in Cache)`
|
|
683
580
|
);
|
|
684
|
-
return null;
|
|
685
581
|
}
|
|
686
582
|
}
|
|
687
583
|
}
|
|
@@ -689,7 +585,7 @@ class Automation extends MetadataType {
|
|
|
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;
|
|
777
680
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
futureDate.
|
|
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;
|
|
784
689
|
}
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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;
|
|
699
|
+
}
|
|
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;
|