mcdev 3.0.3 → 3.1.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.
Files changed (36) hide show
  1. package/.eslintrc.json +1 -1
  2. package/.github/ISSUE_TEMPLATE/bug.yml +72 -0
  3. package/.issuetracker +11 -3
  4. package/.vscode/settings.json +3 -3
  5. package/CHANGELOG.md +53 -0
  6. package/README.md +245 -141
  7. package/boilerplate/config.json +3 -2
  8. package/docs/dist/documentation.md +783 -322
  9. package/lib/Deployer.js +4 -1
  10. package/lib/MetadataTypeDefinitions.js +1 -0
  11. package/lib/MetadataTypeInfo.js +1 -0
  12. package/lib/Retriever.js +30 -14
  13. package/lib/cli.js +295 -0
  14. package/lib/index.js +774 -1019
  15. package/lib/metadataTypes/AccountUser.js +389 -0
  16. package/lib/metadataTypes/Asset.js +8 -7
  17. package/lib/metadataTypes/Automation.js +115 -52
  18. package/lib/metadataTypes/DataExtension.js +125 -89
  19. package/lib/metadataTypes/DataExtensionField.js +134 -4
  20. package/lib/metadataTypes/ImportFile.js +4 -6
  21. package/lib/metadataTypes/MetadataType.js +19 -5
  22. package/lib/metadataTypes/Query.js +2 -3
  23. package/lib/metadataTypes/Role.js +13 -8
  24. package/lib/metadataTypes/definitions/AccountUser.definition.js +227 -0
  25. package/lib/metadataTypes/definitions/Asset.definition.js +1 -0
  26. package/lib/metadataTypes/definitions/DataExtension.definition.js +1 -1
  27. package/lib/metadataTypes/definitions/ImportFile.definition.js +2 -1
  28. package/lib/metadataTypes/definitions/Script.definition.js +5 -5
  29. package/lib/retrieveChangelog.js +96 -0
  30. package/lib/util/cli.js +4 -6
  31. package/lib/util/init.git.js +2 -1
  32. package/lib/util/util.js +17 -0
  33. package/package.json +17 -21
  34. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
  35. package/img/README.md/troubleshoot-nodejs-postinstall.jpg +0 -0
  36. package/postinstall.js +0 -41
@@ -0,0 +1,389 @@
1
+ 'use strict';
2
+
3
+ const MetadataType = require('./MetadataType');
4
+ const Util = require('../util/util');
5
+ const File = require('../util/file');
6
+
7
+ /**
8
+ * MessageSendActivity MetadataType
9
+ * @augments MetadataType
10
+ */
11
+ class AccountUser extends MetadataType {
12
+ /**
13
+ * Retrieves SOAP based metadata of metadata type into local filesystem. executes callback with retrieved metadata
14
+ * @param {String} retrieveDir Directory where retrieved metadata directory will be saved
15
+ * @param {String[]} _ Returns specified fields even if their retrieve definition is not set to true
16
+ * @param {Object} buObject properties for auth
17
+ * @returns {Promise<Object>} Promise of metadata
18
+ */
19
+ static async retrieve(retrieveDir, _, buObject) {
20
+ if (buObject.eid !== buObject.mid) {
21
+ Util.logger.info('Skipping User retrieval on non-parent BU');
22
+ return;
23
+ }
24
+ Util.logger.info('- Caching dependent Metadata: AccountUserAccount');
25
+
26
+ // get BUs that each users have access to
27
+ const optionsBUs = {};
28
+ let resultsBatch;
29
+ await Util.retryOnError(`Retrying ${this.definition.type}`, async () => {
30
+ resultsBatch = await new Promise((resolve, reject) => {
31
+ this.client.SoapClient.retrieve(
32
+ 'AccountUserAccount',
33
+ [
34
+ 'AccountUser.AccountUserID',
35
+ 'AccountUser.UserID',
36
+ 'Account.ID',
37
+ 'Account.Name',
38
+ ],
39
+ optionsBUs,
40
+ (ex, response) => (ex ? reject(ex) : resolve(response.body.Results))
41
+ );
42
+ });
43
+ });
44
+ this.userIdBuMap = {};
45
+ resultsBatch.forEach((item) => {
46
+ this.userIdBuMap[item.AccountUser.AccountUserID] =
47
+ this.userIdBuMap[item.AccountUser.AccountUserID] || [];
48
+ this.userIdBuMap[item.AccountUser.AccountUserID].push({
49
+ ID: item.Account.ID,
50
+ Name: item.Account.Name,
51
+ });
52
+ });
53
+ // get actual user details
54
+ const options = {
55
+ queryAllAccounts: true,
56
+
57
+ filter: {
58
+ leftOperand: {
59
+ // normal users
60
+ leftOperand: 'Email',
61
+ operator: 'like',
62
+ rightOperand: '@',
63
+ },
64
+ operator: 'OR',
65
+ rightOperand: {
66
+ // installed packages
67
+ leftOperand: {
68
+ leftOperand: 'Name',
69
+ operator: 'like',
70
+ rightOperand: ' app user', // ! will not work if the name was too long as "app user" might be cut off
71
+ },
72
+ operator: 'AND',
73
+ rightOperand: {
74
+ // this is used to filter out system generated installed packages. in our testing, at least those installed packages created in the last few years have hat set this to false while additional (hidden) installed packages have it set to true.
75
+ leftOperand: 'MustChangePassword',
76
+ operator: 'equals',
77
+ rightOperand: 'false',
78
+ },
79
+ },
80
+ },
81
+ };
82
+
83
+ return super.retrieveSOAPgeneric(retrieveDir, buObject, options);
84
+ }
85
+ /**
86
+ *
87
+ * @param {string} date first date
88
+ * @param {string} date2 second date
89
+ * @returns {number} time difference
90
+ */
91
+ static timeSinceDate(date) {
92
+ const interval = 'days';
93
+ const second = 1000,
94
+ minute = second * 60,
95
+ hour = minute * 60,
96
+ day = hour * 24,
97
+ week = day * 7;
98
+ date = new Date(date);
99
+ const now = new Date();
100
+ const timediff = now - date;
101
+ if (isNaN(timediff)) {
102
+ return NaN;
103
+ }
104
+ let result;
105
+ switch (interval) {
106
+ case 'years':
107
+ result = now.getFullYear() - date.getFullYear();
108
+ break;
109
+ case 'months':
110
+ result =
111
+ now.getFullYear() * 12 +
112
+ now.getMonth() -
113
+ (date.getFullYear() * 12 + date.getMonth());
114
+ break;
115
+ case 'weeks':
116
+ result = Math.floor(timediff / week);
117
+ break;
118
+ case 'days':
119
+ result = Math.floor(timediff / day);
120
+ break;
121
+ case 'hours':
122
+ result = Math.floor(timediff / hour);
123
+ break;
124
+ case 'minutes':
125
+ result = Math.floor(timediff / minute);
126
+ break;
127
+ case 'seconds':
128
+ result = Math.floor(timediff / second);
129
+ break;
130
+ default:
131
+ return undefined;
132
+ }
133
+ return result + ' ' + interval;
134
+ }
135
+ /**
136
+ * helper to print bu names
137
+ * @param {Util.BuObject} buObject needed for eid
138
+ * @param {string} buObject.eid needed to check for parent bu
139
+ * @param {numeric} id bu id
140
+ * @returns {string} "bu name (bu id)""
141
+ */
142
+ static getBuName(buObject, id) {
143
+ let name;
144
+ if (buObject.eid == id) {
145
+ name = '_ParentBU_';
146
+ } else {
147
+ name = this.buIdName[id];
148
+ }
149
+ return `<nobr>${name} (${id})</nobr>`;
150
+ }
151
+ /**
152
+ * Creates markdown documentation of all roles
153
+ * @param {Util.BuObject} buObject properties for auth
154
+ * @param {Object} [metadata] user list
155
+ * @returns {Promise<void>} -
156
+ */
157
+ static async document(buObject, metadata) {
158
+ if (buObject.eid !== buObject.mid) {
159
+ Util.logger.error(
160
+ `Users can only be retrieved & documented for the ${Util.parentBuName}`
161
+ );
162
+ return;
163
+ }
164
+ if (!metadata) {
165
+ // load users from disk if document was called directly and not part of a retrieve
166
+ try {
167
+ metadata = this.readBUMetadataForType(
168
+ File.normalizePath([
169
+ this.properties.directories.retrieve,
170
+ buObject.credential,
171
+ Util.parentBuName,
172
+ ]),
173
+ true
174
+ ).accountUser;
175
+ } catch (ex) {
176
+ Util.logger.error(ex.message);
177
+ return;
178
+ }
179
+ }
180
+ // init map of BU Ids > BU Name
181
+ this.buIdName = {};
182
+
183
+ // initialize permission object
184
+ this.allPermissions = {};
185
+ const users = [];
186
+ // traverse all permissions recursively and write them into allPermissions object once it has reached the end
187
+ for (const id in metadata) {
188
+ const user = metadata[id];
189
+ // TODO resolve user permissions to something readable
190
+ let userPermissions = '';
191
+ if (user.UserPermissions) {
192
+ if (!user.UserPermissions.length) {
193
+ // 1 single user permission found, normalize it
194
+ user.UserPermissions = [user.UserPermissions];
195
+ }
196
+ userPermissions = user.UserPermissions.map((item) => item.ID * 1)
197
+ .sort(function (a, b) {
198
+ return a < b ? -1 : a > b ? 1 : 0;
199
+ })
200
+ .join(', ');
201
+ }
202
+ // user roles
203
+ // TODO think about what to do with "individual role" entries
204
+ let roles = '';
205
+ if (user.Roles) {
206
+ roles =
207
+ '<nobr>' +
208
+ user.Roles.map((item) => item.Name)
209
+ .sort(function (a, b) {
210
+ return a < b ? -1 : a > b ? 1 : 0;
211
+ })
212
+ .join(',</nobr><br> <nobr>') +
213
+ '</nobr>';
214
+ }
215
+ let associatedBus = '';
216
+ if (user.AssociatedBusinessUnits__c) {
217
+ associatedBus = user.AssociatedBusinessUnits__c.map((item) => {
218
+ this.buIdName[item.ID] = item.Name;
219
+ return this.getBuName(buObject, item.ID);
220
+ })
221
+ .sort(function (a, b) {
222
+ return a < b ? -1 : a > b ? 1 : 0;
223
+ })
224
+ .join(',<br> ');
225
+ }
226
+ const defaultBUName = this.getBuName(buObject, user.DefaultBusinessUnit);
227
+ users.push({
228
+ TYPE: user.type__c,
229
+ UserID: user.UserID,
230
+ AccountUserID: user.AccountUserID,
231
+ CustomerKey: user.CustomerKey,
232
+ Name: user.Name,
233
+ Email: user.Email,
234
+ NotificationEmailAddress: user.NotificationEmailAddress,
235
+ ActiveFlag: user.ActiveFlag === 'true' ? '✓' : '-',
236
+ IsAPIUser: user.IsAPIUser === 'true' ? '✓' : '-',
237
+ MustChangePassword: user.MustChangePassword === 'true' ? '✓' : '-',
238
+ DefaultBusinessUnit: defaultBUName,
239
+ AssociatedBusinessUnits__c: associatedBus,
240
+ Roles: roles,
241
+ UserPermissions: userPermissions,
242
+ LastSuccessfulLogin: this.timeSinceDate(user.LastSuccessfulLogin),
243
+ CreatedDate: user.CreatedDate.split('T').join(' '),
244
+ ModifiedDate: user.ModifiedDate.split('T').join(' '),
245
+ });
246
+ }
247
+ users.sort(function (a, b) {
248
+ return a.Name < b.Name ? -1 : a.Name > b.Name ? 1 : 0;
249
+ });
250
+ const columnsToPrint = [
251
+ ['Name', 'Name'],
252
+ ['Last successful Login', 'LastSuccessfulLogin'],
253
+ ['Active', 'ActiveFlag'],
254
+ ['API User', 'IsAPIUser'],
255
+ ['Must change PW', 'MustChangePassword'],
256
+ ['Default BU', 'DefaultBusinessUnit'],
257
+ ['BU Access', 'AssociatedBusinessUnits__c'],
258
+ ['Roles', 'Roles'],
259
+ ['User Permissions', 'UserPermissions'],
260
+ ['Login', 'UserID'],
261
+ ['ID', 'AccountUserID'],
262
+ ['Key', 'CustomerKey'],
263
+ ['E-Mail', 'Email'],
264
+ ['Notification E-Mail', 'NotificationEmailAddress'],
265
+ ['Modified Date', 'ModifiedDate'],
266
+ ['Created Date', 'CreatedDate'],
267
+ ];
268
+ let output = `# User Overview - ${buObject.credential}`;
269
+ output += this._generateDocMd(
270
+ users.filter((user) => user.TYPE === 'User' && user.ActiveFlag === '✓'),
271
+ 'User',
272
+ columnsToPrint
273
+ );
274
+ output += this._generateDocMd(
275
+ users.filter((user) => user.TYPE === 'User' && user.ActiveFlag === '-'),
276
+ 'Inactivated User',
277
+ columnsToPrint
278
+ );
279
+ output += this._generateDocMd(
280
+ users.filter((user) => user.TYPE === 'Installed Package'),
281
+ 'Installed Package',
282
+ columnsToPrint
283
+ );
284
+ const docPath = File.normalizePath([this.properties.directories.users]);
285
+
286
+ try {
287
+ const filename = buObject.credential;
288
+ // ensure docs/roles folder is existing (depends on setup in .mcdevrc.json)
289
+ if (!File.existsSync(docPath)) {
290
+ File.mkdirpSync(docPath);
291
+ }
292
+ // write to disk
293
+ await File.writeToFile(docPath, filename + '.accountUser', 'md', output);
294
+ Util.logger.info(`Created ${docPath}${filename}.accountUser.md`);
295
+ if (['html', 'both'].includes(this.properties.options.documentType)) {
296
+ Util.logger.warn(
297
+ 'HTML-based documentation of accountUser currently not supported.'
298
+ );
299
+ }
300
+ } catch (ex) {
301
+ Util.logger.error(`AccountUser.document():: error | `, ex.message);
302
+ }
303
+ }
304
+ /**
305
+ * Experimental: Only working for DataExtensions:
306
+ * Saves json content to a html table in the local file system. Will create the parent directory if it does not exist.
307
+ * The json's first level of keys must represent the rows and the secend level the columns
308
+ * @private
309
+ * @param {DataExtensionItem} json dataextension
310
+ * @param {Array} tabled prepped array for output in tabular format
311
+ * @returns {string} file content
312
+ */
313
+ /**
314
+ *
315
+ * @param {Object[]} users list of users and installed package
316
+ * @param {'Installed Package'|'User'} type choose what sub type to print
317
+ * @param {Array[]} columnsToPrint helper array
318
+ * @param {Object} buObject properties for auth
319
+ * @returns {string} markdown
320
+ */
321
+ static _generateDocMd(users, type, columnsToPrint) {
322
+ let output = `\n\n## ${type}s (${users.length})\n\n`;
323
+ let tableSeparator = '';
324
+ columnsToPrint.forEach((column) => {
325
+ output += `| ${column[0]} `;
326
+ tableSeparator += '| --- ';
327
+ });
328
+ output += `|\n${tableSeparator}|\n`;
329
+ users.forEach((user) => {
330
+ columnsToPrint.forEach((column) => {
331
+ output += `| ${user[column[1]]} `;
332
+ });
333
+ output += `|\n`;
334
+ });
335
+ return output;
336
+ }
337
+
338
+ /**
339
+ * manages post retrieve steps
340
+ * @param {Object} metadata a single query
341
+ * @returns {Object[]} Array with one metadata object and one query string
342
+ */
343
+ static postRetrieveTasks(metadata) {
344
+ return this.parseMetadata(metadata);
345
+ }
346
+ /**
347
+ * parses retrieved Metadata before saving
348
+ * @param {Object} metadata a single query activity definition
349
+ * @returns {Array} Array with one metadata object and one sql string
350
+ */
351
+ static parseMetadata(metadata) {
352
+ metadata.type__c = 'Installed Package';
353
+ if (metadata.Email.includes('@') && !metadata.Name.endsWith('app user')) {
354
+ metadata.type__c = 'User';
355
+ }
356
+
357
+ if (this.userIdBuMap[metadata.ID]) {
358
+ metadata.AssociatedBusinessUnits__c = this.userIdBuMap[metadata.ID];
359
+ } else {
360
+ metadata.AssociatedBusinessUnits__c = [];
361
+ }
362
+
363
+ let roles;
364
+ if (metadata.Roles.Role) {
365
+ // normalize to always use array
366
+ if (!metadata.Roles.Role.length) {
367
+ metadata.Roles.Role = [metadata.Roles.Role];
368
+ }
369
+ // convert complex object into basic set of info
370
+ roles = metadata.Roles.Role.map((item) => ({
371
+ Name: item.Name,
372
+ CustomerKey: item.CustomerKey,
373
+ })).sort(function (a, b) {
374
+ return a.Name < b.Name ? -1 : a.Name > b.Name ? 1 : 0;
375
+ });
376
+ } else {
377
+ // set to empty array
378
+ roles = [];
379
+ }
380
+ metadata.Roles = roles;
381
+
382
+ return metadata;
383
+ }
384
+ }
385
+
386
+ // Assign definition to static attributes
387
+ AccountUser.definition = require('../MetadataTypeDefinitions').accountUser;
388
+
389
+ module.exports = AccountUser;
@@ -162,6 +162,7 @@ class Asset extends MetadataType {
162
162
  } else {
163
163
  Util.logger.info(`- Caching Subtype: ${subType}`);
164
164
  }
165
+ const subtypeIds = subTypeArray?.map(subTypeItemName => Asset.definition.typeMapping[subTypeItemName]);
165
166
  const uri = 'asset/v1/content/assets/';
166
167
  const options = {
167
168
  uri: uri + 'query',
@@ -178,9 +179,9 @@ class Asset extends MetadataType {
178
179
  if (templateName) {
179
180
  options.json.query = {
180
181
  leftOperand: {
181
- property: 'assetType.name',
182
+ property: 'assetType.id',
182
183
  simpleOperator: 'in',
183
- value: subTypeArray,
184
+ value: subtypeIds,
184
185
  },
185
186
  logicalOperator: 'AND',
186
187
  rightOperand: {
@@ -191,9 +192,9 @@ class Asset extends MetadataType {
191
192
  };
192
193
  } else {
193
194
  options.json.query = {
194
- property: 'assetType.name',
195
+ property: 'assetType.id',
195
196
  simpleOperator: 'in',
196
- value: subTypeArray,
197
+ value: subtypeIds,
197
198
  };
198
199
  options.json.sort = [{ property: 'id', direction: 'ASC' }];
199
200
  }
@@ -232,9 +233,9 @@ class Asset extends MetadataType {
232
233
  // Since we sort by ID, we can get the last ID then run new requests from there
233
234
  options.json.query = {
234
235
  leftOperand: {
235
- property: 'assetType.name',
236
+ property: 'assetType.id',
236
237
  simpleOperator: 'in',
237
- value: subTypeArray,
238
+ value: subtypeIds,
238
239
  },
239
240
  logicalOperator: 'AND',
240
241
  rightOperand: {
@@ -441,7 +442,7 @@ class Asset extends MetadataType {
441
442
  await this._mergeCode(metadata, deployDir, subType);
442
443
 
443
444
  // #2 get file from local disk and insert as base64
444
- await this._readExtendedFileFromFS(metadata, deployDir, subType);
445
+ await this._readExtendedFileFromFS(metadata, subType, deployDir);
445
446
 
446
447
  return metadata;
447
448
  }
@@ -92,14 +92,10 @@ class Automation extends MetadataType {
92
92
  * @returns {Promise<{metadata:AutomationMap,type:string}>} Promise of metadata
93
93
  */
94
94
  static async retrieve(retrieveDir) {
95
- const results = await new Promise((resolve) => {
96
- this.client.SoapClient.retrieve('Program', ['ObjectID'], (error, response) => {
97
- if (error) {
98
- throw new Error(error);
99
- } else {
100
- resolve(response.body.Results);
101
- }
102
- });
95
+ const results = await new Promise((resolve, reject) => {
96
+ this.client.SoapClient.retrieve('Program', ['ObjectID'], (ex, response) =>
97
+ ex ? reject(ex) : resolve(response.body.Results)
98
+ );
103
99
  });
104
100
  const details = (
105
101
  await Promise.all(
@@ -116,6 +112,60 @@ class Automation extends MetadataType {
116
112
  Util.logger.info(
117
113
  `Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})`
118
114
  );
115
+ return { metadata: savedMetadata, type: this.definition.type };
116
+ }
117
+ /**
118
+ * Retrieves Metadata of Automation
119
+ * @returns {Promise<{metadata:AutomationMap,type:string}>} Promise of metadata
120
+ */
121
+ static async retrieveChangelog() {
122
+ const results = await new Promise((resolve, reject) => {
123
+ this.client.SoapClient.retrieve(
124
+ 'Program',
125
+ ['ObjectID'],
126
+
127
+ (ex, response) => (ex ? reject(ex) : resolve(response.body.Results))
128
+ );
129
+ });
130
+ const details = [];
131
+ (
132
+ await Promise.all(
133
+ results.map(async (a) => {
134
+ const options = {
135
+ filter: {
136
+ leftOperand: 'ProgramID',
137
+ operator: 'equals',
138
+ rightOperand: a.ObjectID,
139
+ },
140
+ };
141
+
142
+ return new Promise((resolve, reject) => {
143
+ this.client.SoapClient.retrieve(
144
+ 'Automation',
145
+ [
146
+ 'ProgramID',
147
+ 'Name',
148
+ 'CustomerKey',
149
+ 'LastSaveDate',
150
+ 'LastSavedBy',
151
+ 'CreatedBy',
152
+ 'CreatedDate',
153
+ ],
154
+ options,
155
+ (ex, response) => (ex ? reject(ex) : resolve(response.body.Results))
156
+ );
157
+ });
158
+ })
159
+ )
160
+ ).forEach((item) => {
161
+ details.push(...item);
162
+ });
163
+ details.map((item) => {
164
+ item.key = item.CustomerKey;
165
+ });
166
+
167
+ const parsed = this.parseResponseBody({ items: details });
168
+
119
169
  return { metadata: parsed, type: this.definition.type };
120
170
  }
121
171
 
@@ -315,9 +365,8 @@ class Automation extends MetadataType {
315
365
  delete metadata.schedule.scheduledTime;
316
366
  delete metadata.schedule.scheduledStatus;
317
367
  if (this.definition.timeZoneMapping[metadata.schedule.timezoneName]) {
318
- metadata.schedule.timezoneId = this.definition.timeZoneMapping[
319
- metadata.schedule.timezoneName
320
- ];
368
+ metadata.schedule.timezoneId =
369
+ this.definition.timeZoneMapping[metadata.schedule.timezoneName];
321
370
  } else {
322
371
  Util.logger.error(
323
372
  `Could not find timezone ${metadata.schedule.timezoneName} in definition.timeZoneMapping`
@@ -435,44 +484,56 @@ class Automation extends MetadataType {
435
484
  }
436
485
  if (schedule !== null) {
437
486
  try {
438
- await new Promise((resolve, reject) => {
439
- this.client.SoapClient.schedule(
440
- 'Automation',
441
- schedule,
442
- {
443
- Interaction: {
444
- ObjectID: metadata[key].id,
445
- },
446
- },
447
- 'start',
448
- null,
449
- (error, response) => {
450
- if (
451
- error ||
452
- (response.body.Results &&
453
- response.body.Results[0] &&
454
- response.body.Results[0].StatusCode &&
455
- response.body.Results[0].StatusCode === 'Error')
456
- ) {
457
- reject(error || response.body.Results[0].StatusMessage);
458
- } else {
459
- resolve(response.body.Results);
460
- }
461
- }
462
- );
463
- });
464
- const intervalString =
465
- (schedule._interval > 1 ? `${schedule._interval} ` : '') +
466
- (schedule.RecurrenceType === 'Daily'
467
- ? 'Day'
468
- : schedule.RecurrenceType.slice(0, -2) +
469
- (schedule._interval > 1 ? 's' : ''));
470
- Util.logger.warn(
471
- `Automation '${
472
- originalMetadata[key].name
473
- }' deployed Active: runs every ${intervalString} starting ${
474
- schedule._StartDateTime.split('T').join(' ').split('.')[0]
475
- } ${schedule._timezoneString}`
487
+ await Util.retryOnError(
488
+ `Retrying ${this.definition.type}`,
489
+ async () => {
490
+ await new Promise((resolve, reject) => {
491
+ this.client.SoapClient.schedule(
492
+ 'Automation',
493
+ schedule,
494
+ {
495
+ Interaction: {
496
+ ObjectID: metadata[key].id,
497
+ },
498
+ },
499
+ 'start',
500
+ null,
501
+ (error, response) => {
502
+ if (
503
+ error ||
504
+ (response.body.Results &&
505
+ response.body.Results[0] &&
506
+ response.body.Results[0].StatusCode &&
507
+ response.body.Results[0].StatusCode ===
508
+ 'Error')
509
+ ) {
510
+ reject(
511
+ error ||
512
+ response.body.Results[0].StatusMessage
513
+ );
514
+ } else {
515
+ resolve(response.body.Results);
516
+ }
517
+ }
518
+ );
519
+ });
520
+ const intervalString =
521
+ (schedule._interval > 1 ? `${schedule._interval} ` : '') +
522
+ (schedule.RecurrenceType === 'Daily'
523
+ ? 'Day'
524
+ : schedule.RecurrenceType.slice(0, -2) +
525
+ (schedule._interval > 1 ? 's' : ''));
526
+ Util.logger.warn(
527
+ `Automation '${
528
+ originalMetadata[key].name
529
+ }' deployed Active: runs every ${intervalString} starting ${
530
+ schedule._StartDateTime
531
+ .split('T')
532
+ .join(' ')
533
+ .split('.')[0]
534
+ } ${schedule._timezoneString}`
535
+ );
536
+ }
476
537
  );
477
538
  } catch (ex) {
478
539
  Util.logger.error(
@@ -684,9 +745,8 @@ class Automation extends MetadataType {
684
745
  }
685
746
 
686
747
  if (this.definition.timeZoneMapping[scheduleObject.timezoneName]) {
687
- scheduleObject.timezoneId = this.definition.timeZoneMapping[
688
- scheduleObject.timezoneName
689
- ];
748
+ scheduleObject.timezoneId =
749
+ this.definition.timeZoneMapping[scheduleObject.timezoneName];
690
750
  } else {
691
751
  Util.logger.error(
692
752
  `Could not find timezone ${scheduleObject.timezoneName} in definition.timeZoneMapping`
@@ -780,6 +840,9 @@ class Automation extends MetadataType {
780
840
  // Assign definition to static attributes
781
841
  Automation.definition = Definitions.automation;
782
842
  Automation.cache = {};
843
+ /**
844
+ * @type {Util.ET_Client}
845
+ */
783
846
  Automation.client = undefined;
784
847
 
785
848
  module.exports = Automation;