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,62 +1,48 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const jsonToTable = require('json-to-table');
|
|
4
|
+
const TYPE = require('../../types/mcdev.d');
|
|
4
5
|
const MetadataType = require('./MetadataType');
|
|
5
6
|
const DataExtensionField = require('./DataExtensionField');
|
|
6
7
|
const Folder = require('./Folder');
|
|
7
8
|
const Util = require('../util/util');
|
|
8
9
|
const File = require('../util/file');
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
* @typedef {Object} DataExtensionItem
|
|
12
|
-
* @property {string} CustomerKey key
|
|
13
|
-
* @property {string} Name name
|
|
14
|
-
* @property {string} Description -
|
|
15
|
-
* @property {'true'|'false'} IsSendable -
|
|
16
|
-
* @property {'true'|'false'} IsTestable -
|
|
17
|
-
* @property {Object} SendableDataExtensionField -
|
|
18
|
-
* @property {string} SendableDataExtensionField.Name -
|
|
19
|
-
* @property {Object} SendableSubscriberField -
|
|
20
|
-
* @property {string} SendableSubscriberField.Name -
|
|
21
|
-
* @property {DataExtensionField.DataExtensionFieldItem[]} Fields list of DE fields
|
|
22
|
-
* @property {'dataextension'|'salesforcedataextension'|'synchronizeddataextension'|'shared_dataextension'|'shared_salesforcedataextension'} r__folder_ContentType retrieved from associated folder
|
|
23
|
-
* @property {string} r__folder_Path folder path in which this DE is saved
|
|
24
|
-
* @property {string} [CategoryID] holds folder ID, replaced with r__folder_Path during retrieve
|
|
25
|
-
* @property {string} [r__dataExtensionTemplate_Name] name of optionally associated DE template
|
|
26
|
-
* @property {Object} [Template] -
|
|
27
|
-
* @property {string} [Template.CustomerKey] key of optionally associated DE teplate
|
|
28
|
-
*
|
|
29
|
-
* @typedef {Object.<string, DataExtensionItem>} DataExtensionMap
|
|
30
|
-
*/
|
|
10
|
+
const auth = require('../util/auth');
|
|
11
|
+
const cache = require('../util/cache');
|
|
31
12
|
|
|
32
13
|
/**
|
|
33
14
|
* DataExtension MetadataType
|
|
15
|
+
*
|
|
34
16
|
* @augments MetadataType
|
|
35
17
|
*/
|
|
36
18
|
class DataExtension extends MetadataType {
|
|
37
19
|
/**
|
|
38
20
|
* Upserts dataExtensions after retrieving them from source and target to compare
|
|
39
21
|
* if create or update operation is needed.
|
|
40
|
-
*
|
|
41
|
-
* @param {
|
|
42
|
-
* @param {
|
|
22
|
+
*
|
|
23
|
+
* @param {TYPE.DataExtensionMap} desToDeploy dataExtensions mapped by their customerKey
|
|
24
|
+
* @param {void} _ unused parameter
|
|
25
|
+
* @param {TYPE.BuObject} buObject properties for auth
|
|
43
26
|
* @returns {Promise} Promise
|
|
44
27
|
*/
|
|
45
28
|
static async upsert(desToDeploy, _, buObject) {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
const targetMetadata =
|
|
49
|
-
|
|
29
|
+
// get dataExtensions from target BU for add/update decision
|
|
30
|
+
/** @type {TYPE.DataExtensionMap} */
|
|
31
|
+
const targetMetadata = cache.getCache().dataExtension || {};
|
|
32
|
+
// get existing de-fields to properly handle add/update/delete of fields
|
|
33
|
+
await this._attachFields(targetMetadata);
|
|
34
|
+
|
|
50
35
|
/** @type {Promise[]} */
|
|
51
36
|
const deCreatePromises = [];
|
|
52
37
|
/** @type {Promise[]} */
|
|
53
38
|
const deUpdatePromises = [];
|
|
39
|
+
let filteredByPreDeploy = 0;
|
|
54
40
|
for (const dataExtension in desToDeploy) {
|
|
55
41
|
if (desToDeploy[dataExtension].Name.startsWith('_')) {
|
|
56
42
|
Util.logger.warn(
|
|
57
|
-
|
|
58
|
-
desToDeploy[dataExtension].Name
|
|
43
|
+
` ☇ skipping dataExtension ${desToDeploy[dataExtension].Name}: Cannot Upsert Strongly Typed Data Extensions`
|
|
59
44
|
);
|
|
45
|
+
filteredByPreDeploy++;
|
|
60
46
|
continue;
|
|
61
47
|
}
|
|
62
48
|
if (
|
|
@@ -65,8 +51,9 @@ class DataExtension extends MetadataType {
|
|
|
65
51
|
) {
|
|
66
52
|
// this needs to be run before executing preDeployTasks()
|
|
67
53
|
Util.logger.warn(
|
|
68
|
-
|
|
54
|
+
` ☇ skipping dataExtension ${desToDeploy[dataExtension].Name}: Cannot Create/Update a Shared Data Extension from the Child BU`
|
|
69
55
|
);
|
|
56
|
+
filteredByPreDeploy++;
|
|
70
57
|
continue;
|
|
71
58
|
}
|
|
72
59
|
if (
|
|
@@ -74,8 +61,9 @@ class DataExtension extends MetadataType {
|
|
|
74
61
|
) {
|
|
75
62
|
// this needs to be run before executing preDeployTasks()
|
|
76
63
|
Util.logger.warn(
|
|
77
|
-
|
|
64
|
+
` ☇ skipping dataExtension ${desToDeploy[dataExtension].Name}:Cannot Create/Update a Synchronized Data Extension. Please use Contact Builder to maintain these`
|
|
78
65
|
);
|
|
66
|
+
filteredByPreDeploy++;
|
|
79
67
|
continue;
|
|
80
68
|
}
|
|
81
69
|
try {
|
|
@@ -100,7 +88,7 @@ class DataExtension extends MetadataType {
|
|
|
100
88
|
}
|
|
101
89
|
if (deUpdatePromises.length) {
|
|
102
90
|
Util.logger.info(
|
|
103
|
-
'- Please note that Data Retention Policies can only be set during creation, not during update.'
|
|
91
|
+
' - Please note that Data Retention Policies can only be set during creation, not during update.'
|
|
104
92
|
);
|
|
105
93
|
}
|
|
106
94
|
|
|
@@ -112,27 +100,23 @@ class DataExtension extends MetadataType {
|
|
|
112
100
|
);
|
|
113
101
|
|
|
114
102
|
const successfulResults = [...createResults, ...updateResults];
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
this.definition.type,
|
|
119
|
-
'upsert',
|
|
120
|
-
`${createResults.length} of ${deCreatePromises.length} created / ${updateResults.length} of ${deUpdatePromises.length} updated`
|
|
103
|
+
Util.logger.info(
|
|
104
|
+
`${this.definition.type} upsert: ${createResults.length} of ${deCreatePromises.length} created / ${updateResults.length} of ${deUpdatePromises.length} updated` +
|
|
105
|
+
(filteredByPreDeploy > 0 ? ` / ${filteredByPreDeploy} filtered` : '')
|
|
121
106
|
);
|
|
122
107
|
if (successfulResults.length > 0) {
|
|
123
108
|
const metadataResults = successfulResults
|
|
124
|
-
.map((r) => r.value.
|
|
109
|
+
.map((r) => r.value.Results[0].Object)
|
|
125
110
|
.map((r) => {
|
|
126
111
|
// if only one fields added will return object otherwise array
|
|
127
|
-
if (
|
|
112
|
+
if (Array.isArray(r?.Fields?.Field)) {
|
|
128
113
|
r.Fields = r.Fields.Field;
|
|
129
|
-
} else if (r
|
|
114
|
+
} else if (r?.Fields?.Field) {
|
|
130
115
|
r.Fields = [r.Fields.Field];
|
|
131
116
|
}
|
|
132
117
|
return r;
|
|
133
118
|
});
|
|
134
|
-
|
|
135
|
-
return formattedResults;
|
|
119
|
+
return super.parseResponseBody({ Results: metadataResults });
|
|
136
120
|
} else {
|
|
137
121
|
return {};
|
|
138
122
|
}
|
|
@@ -140,8 +124,9 @@ class DataExtension extends MetadataType {
|
|
|
140
124
|
|
|
141
125
|
/**
|
|
142
126
|
* helper for upsert()
|
|
143
|
-
*
|
|
144
|
-
* @
|
|
127
|
+
*
|
|
128
|
+
* @param {object} res -
|
|
129
|
+
* @returns {boolean} true: keep, false: discard
|
|
145
130
|
*/
|
|
146
131
|
static _filterUpsertResults(res) {
|
|
147
132
|
if (res.status === 'rejected') {
|
|
@@ -155,12 +140,12 @@ class DataExtension extends MetadataType {
|
|
|
155
140
|
} else if (res.value.results) {
|
|
156
141
|
Util.logger.error(
|
|
157
142
|
'- error upserting dataExtension: ' +
|
|
158
|
-
(res.value.
|
|
143
|
+
(res.value.Results[0].Object ? res.value.Results[0].Object.Name : '') +
|
|
159
144
|
'. ' +
|
|
160
|
-
res.value.
|
|
145
|
+
res.value.Results[0].StatusMessage
|
|
161
146
|
);
|
|
162
147
|
return false;
|
|
163
|
-
} else if (res.status === 'fulfilled' && res
|
|
148
|
+
} else if (res.status === 'fulfilled' && res?.value?.faultstring) {
|
|
164
149
|
// can happen that the promise does not reject, but that it resolves an error
|
|
165
150
|
Util.logger.error('- error upserting dataExtension: ' + res.value.faultstring);
|
|
166
151
|
return false;
|
|
@@ -171,7 +156,8 @@ class DataExtension extends MetadataType {
|
|
|
171
156
|
|
|
172
157
|
/**
|
|
173
158
|
* Create a single dataExtension. Also creates their columns in 'dataExtension.columns'
|
|
174
|
-
*
|
|
159
|
+
*
|
|
160
|
+
* @param {TYPE.DataExtensionItem} metadata single metadata entry
|
|
175
161
|
* @returns {Promise} Promise
|
|
176
162
|
*/
|
|
177
163
|
static async create(metadata) {
|
|
@@ -193,8 +179,9 @@ class DataExtension extends MetadataType {
|
|
|
193
179
|
* SFMC saves a date in "RetainUntil" under certain circumstances even
|
|
194
180
|
* if that field duplicates whats in the period fields
|
|
195
181
|
* during deployment, that extra value is not accepted by the APIs which is why it needs to be removed
|
|
182
|
+
*
|
|
196
183
|
* @private
|
|
197
|
-
* @param {DataExtensionItem} metadata single metadata entry
|
|
184
|
+
* @param {TYPE.DataExtensionItem} metadata single metadata entry
|
|
198
185
|
* @returns {void}
|
|
199
186
|
*/
|
|
200
187
|
static _cleanupRetentionPolicyFields(metadata) {
|
|
@@ -205,23 +192,23 @@ class DataExtension extends MetadataType {
|
|
|
205
192
|
) {
|
|
206
193
|
metadata.RetainUntil = '';
|
|
207
194
|
Util.logger.warn(
|
|
208
|
-
`RetainUntil date was reset automatically because RetentionPeriod info was found in: ${metadata.CustomerKey}`
|
|
195
|
+
` - RetainUntil date was reset automatically because RetentionPeriod info was found in: ${metadata.CustomerKey}`
|
|
209
196
|
);
|
|
210
197
|
}
|
|
211
198
|
}
|
|
212
199
|
/**
|
|
213
200
|
* Updates a single dataExtension. Also updates their columns in 'dataExtension.columns'
|
|
214
|
-
*
|
|
201
|
+
*
|
|
202
|
+
* @param {TYPE.DataExtensionItem} metadata single metadata entry
|
|
215
203
|
* @returns {Promise} Promise
|
|
216
204
|
*/
|
|
217
205
|
static async update(metadata) {
|
|
218
206
|
// Update dataExtension + Columns if they already exist; Create them if not
|
|
219
207
|
// Modify columns for update call
|
|
220
|
-
DataExtensionField.cache = this.metadata;
|
|
221
208
|
DataExtensionField.client = this.client;
|
|
222
209
|
DataExtensionField.properties = this.properties;
|
|
223
210
|
DataExtension.oldFields = DataExtension.oldFields || {};
|
|
224
|
-
DataExtension.oldFields[metadata.
|
|
211
|
+
DataExtension.oldFields[metadata[this.definition.keyField]] =
|
|
225
212
|
await DataExtensionField.prepareDeployColumnsOnUpdate(
|
|
226
213
|
metadata.Fields,
|
|
227
214
|
metadata.CustomerKey
|
|
@@ -240,97 +227,129 @@ class DataExtension extends MetadataType {
|
|
|
240
227
|
}
|
|
241
228
|
/**
|
|
242
229
|
* Gets executed after deployment of metadata type
|
|
243
|
-
*
|
|
230
|
+
*
|
|
231
|
+
* @param {TYPE.DataExtensionMap} upsertedMetadata metadata mapped by their keyField
|
|
232
|
+
* @param {TYPE.DataExtensionMap} originalMetadata metadata to be updated (contains additioanl fields)
|
|
244
233
|
* @returns {void}
|
|
245
234
|
*/
|
|
246
|
-
static postDeployTasks(upsertedMetadata) {
|
|
247
|
-
if (!DataExtension.oldFields) {
|
|
248
|
-
// only run postDeploy if we are in update mode
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
// somewhat of a workardoun but it ensures we get the field list from the server rather than whatever we might have in cache got returned during update/add. This ensures a complete and correctly ordered field list
|
|
235
|
+
static postDeployTasks(upsertedMetadata, originalMetadata) {
|
|
252
236
|
for (const key in upsertedMetadata) {
|
|
253
237
|
const item = upsertedMetadata[key];
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
this.cache.dataExtension[item.CustomerKey];
|
|
258
|
-
if (isUpdate) {
|
|
259
|
-
const cachedVersion = this.cache.dataExtension[item.CustomerKey];
|
|
238
|
+
const cachedVersion = cache.getByKey('dataExtension', item.CustomerKey);
|
|
239
|
+
if (cachedVersion) {
|
|
240
|
+
// UPDATE
|
|
260
241
|
// restore retention values that are typically not returned by the update call
|
|
261
242
|
item.RowBasedRetention = cachedVersion.RowBasedRetention;
|
|
262
243
|
item.ResetRetentionPeriodOnImport = cachedVersion.ResetRetentionPeriodOnImport;
|
|
263
244
|
item.DeleteAtEndOfRetentionPeriod = cachedVersion.DeleteAtEndOfRetentionPeriod;
|
|
264
245
|
item.RetainUntil = cachedVersion.RetainUntil;
|
|
265
246
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
247
|
+
const existingFields = DataExtension.oldFields[item[this.definition.keyField]];
|
|
248
|
+
if (item.Fields === '') {
|
|
249
|
+
// if no fields were updated, we need to set Fields to "empty string" for the API to work
|
|
250
|
+
// reset here to get the correct field list
|
|
251
|
+
item.Fields = Object.keys(existingFields)
|
|
252
|
+
.map((key) => existingFields[key])
|
|
253
|
+
.sort((a, b) => a.Ordinal - b.Ordinal);
|
|
254
|
+
}
|
|
255
|
+
if (existingFields) {
|
|
256
|
+
// get list of updated fields
|
|
257
|
+
/** @type {TYPE.DataExtensionFieldItem[]} */
|
|
258
|
+
const updatedFieldsArr = originalMetadata[key].Fields.Field.filter(
|
|
259
|
+
(field) => field.ObjectID && field.ObjectID !== ''
|
|
260
|
+
);
|
|
261
|
+
// convert existing fields obj into array and sort
|
|
262
|
+
/** @type {TYPE.DataExtensionFieldItem[]} */
|
|
263
|
+
const finalFieldsArr = Object.keys(existingFields)
|
|
264
|
+
.map((key) => {
|
|
265
|
+
/** @type {TYPE.DataExtensionFieldItem} */
|
|
266
|
+
const existingField = existingFields[key];
|
|
267
|
+
// check if the current field was updated and then override with it. otherwise use existing value
|
|
268
|
+
const field =
|
|
269
|
+
updatedFieldsArr.find(
|
|
270
|
+
(field) => field.ObjectID === existingField.ObjectID
|
|
271
|
+
) || existingField;
|
|
272
|
+
// field does not have a ordinal value because we rely on array order
|
|
273
|
+
field.Ordinal = existingField.Ordinal;
|
|
274
|
+
// updating FieldType is not supported by API and hence removed
|
|
275
|
+
field.FieldType = existingField.FieldType;
|
|
276
|
+
return field;
|
|
277
|
+
})
|
|
278
|
+
.sort((a, b) => a.Ordinal - b.Ordinal);
|
|
279
|
+
|
|
280
|
+
// get list of new fields
|
|
281
|
+
/** @type {TYPE.DataExtensionFieldItem[]} */
|
|
282
|
+
const newFieldsArr = originalMetadata[key].Fields.Field.filter(
|
|
283
|
+
(field) => !field.ObjectID
|
|
284
|
+
);
|
|
285
|
+
// push new fields to end of list
|
|
286
|
+
if (newFieldsArr.length) {
|
|
287
|
+
finalFieldsArr.push(...newFieldsArr);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// sort Fields entry to the end of the object for saving in .json
|
|
291
|
+
delete item.Fields;
|
|
292
|
+
item.Fields = finalFieldsArr;
|
|
272
293
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
294
|
+
}
|
|
295
|
+
// UPDATE + CREATE
|
|
296
|
+
for (const field of item.Fields) {
|
|
297
|
+
DataExtensionField.postRetrieveTasks(field, true);
|
|
277
298
|
}
|
|
278
299
|
}
|
|
279
300
|
}
|
|
280
301
|
|
|
281
302
|
/**
|
|
282
303
|
* Retrieves dataExtension metadata. Afterwards starts retrieval of dataExtensionColumn metadata retrieval
|
|
304
|
+
*
|
|
283
305
|
* @param {string} retrieveDir Directory where retrieved metadata directory will be saved
|
|
284
306
|
* @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
|
|
285
|
-
* @param {
|
|
286
|
-
* @param {void} [_]
|
|
287
|
-
* @param {
|
|
288
|
-
* @returns {Promise
|
|
307
|
+
* @param {TYPE.BuObject} buObject properties for auth
|
|
308
|
+
* @param {void} [_] unused parameter
|
|
309
|
+
* @param {string} [key] customer key of single item to retrieve
|
|
310
|
+
* @returns {Promise.<{metadata: TYPE.DataExtensionMap, type: string}>} Promise of item map
|
|
289
311
|
*/
|
|
290
|
-
static async retrieve(retrieveDir, additionalFields, buObject, _,
|
|
291
|
-
|
|
312
|
+
static async retrieve(retrieveDir, additionalFields, buObject, _, key) {
|
|
313
|
+
/** @type {TYPE.SoapRequestParams} */
|
|
314
|
+
let requestParams = null;
|
|
315
|
+
/** @type {TYPE.SoapRequestParams} */
|
|
316
|
+
let fieldOptions = null;
|
|
317
|
+
if (key) {
|
|
318
|
+
requestParams = {
|
|
319
|
+
filter: {
|
|
320
|
+
leftOperand: 'CustomerKey',
|
|
321
|
+
operator: 'equals',
|
|
322
|
+
rightOperand: key,
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
fieldOptions = {
|
|
326
|
+
filter: {
|
|
327
|
+
leftOperand: 'DataExtension.CustomerKey',
|
|
328
|
+
operator: 'equals',
|
|
329
|
+
rightOperand: key,
|
|
330
|
+
},
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
let metadata = await this._retrieveAll(additionalFields, requestParams);
|
|
292
334
|
// in case of cache dont get fields
|
|
293
|
-
if (
|
|
335
|
+
if (metadata && retrieveDir) {
|
|
294
336
|
// get fields from API
|
|
295
|
-
|
|
296
|
-
const fieldKeys = Object.keys(fieldsObj);
|
|
297
|
-
// add fields to corresponding DE
|
|
298
|
-
fieldKeys.forEach((key) => {
|
|
299
|
-
const field = fieldsObj[key];
|
|
300
|
-
if (metadata[field?.DataExtension?.CustomerKey]) {
|
|
301
|
-
metadata[field.DataExtension.CustomerKey].Fields.push(field);
|
|
302
|
-
} else {
|
|
303
|
-
Util.logger.warn(`Issue retrieving data extension fields. key='${key}'`);
|
|
304
|
-
}
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// sort fields by Ordinal value (API returns field unsorted)
|
|
308
|
-
for (const metadataEntry in metadata) {
|
|
309
|
-
metadata[metadataEntry].Fields.sort(DataExtensionField.sortDeFields);
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
// remove attributes that we do not want to retrieve
|
|
313
|
-
// * do this after sorting on the DE's field list
|
|
314
|
-
fieldKeys.forEach((key) => {
|
|
315
|
-
DataExtensionField.postRetrieveTasks(fieldsObj[key], true);
|
|
316
|
-
});
|
|
337
|
+
await this._attachFields(metadata, fieldOptions, additionalFields);
|
|
317
338
|
}
|
|
318
339
|
if (!retrieveDir && buObject.eid !== buObject.mid) {
|
|
319
340
|
// for caching, we want to retrieve shared DEs as well from the instance parent BU
|
|
320
|
-
Util.logger.info(
|
|
321
|
-
|
|
341
|
+
Util.logger.info(
|
|
342
|
+
' - Caching dependent Metadata: dataExtension (shared via _ParentBU_)'
|
|
343
|
+
);
|
|
344
|
+
/** @type {TYPE.BuObject} */
|
|
322
345
|
const buObjectParentBu = {
|
|
323
|
-
clientId: this.properties.credentials[buObject.credential].clientId,
|
|
324
|
-
clientSecret: this.properties.credentials[buObject.credential].clientSecret,
|
|
325
|
-
tenant: this.properties.credentials[buObject.credential].tenant,
|
|
326
346
|
eid: this.properties.credentials[buObject.credential].eid,
|
|
327
347
|
mid: this.properties.credentials[buObject.credential].eid,
|
|
328
348
|
businessUnit: Util.parentBuName,
|
|
329
349
|
credential: buObject.credential,
|
|
330
350
|
};
|
|
331
|
-
const clientBackup = this.client;
|
|
332
351
|
try {
|
|
333
|
-
this.client =
|
|
352
|
+
this.client = auth.getSDK(buObjectParentBu);
|
|
334
353
|
} catch (ex) {
|
|
335
354
|
Util.logger.error(ex.message);
|
|
336
355
|
return;
|
|
@@ -338,14 +357,11 @@ class DataExtension extends MetadataType {
|
|
|
338
357
|
const metadataParentBu = await this._retrieveAll(additionalFields);
|
|
339
358
|
|
|
340
359
|
// get shared folders to match our shared / synched Data Extensions
|
|
341
|
-
Util.logger.info('- Caching dependent Metadata: folder (shared via _ParentBU_)');
|
|
342
|
-
Folder.cache = {};
|
|
360
|
+
Util.logger.info(' - Caching dependent Metadata: folder (shared via _ParentBU_)');
|
|
343
361
|
Folder.client = this.client;
|
|
344
362
|
Folder.properties = this.properties;
|
|
345
363
|
const result = await Folder.retrieveForCache(buObjectParentBu);
|
|
346
|
-
|
|
347
|
-
folder: result.metadata,
|
|
348
|
-
};
|
|
364
|
+
cache.mergeMetadata('folder', result.metadata, buObject.eid);
|
|
349
365
|
|
|
350
366
|
// get the types and clean out non-shared ones
|
|
351
367
|
const folderTypesFromParent = require('../MetadataTypeDefinitions').folder
|
|
@@ -353,12 +369,12 @@ class DataExtension extends MetadataType {
|
|
|
353
369
|
for (const metadataEntry in metadataParentBu) {
|
|
354
370
|
try {
|
|
355
371
|
// get the data extension type from the folder
|
|
356
|
-
const folderContentType =
|
|
357
|
-
parentCache,
|
|
372
|
+
const folderContentType = cache.searchForField(
|
|
358
373
|
'folder',
|
|
359
374
|
metadataParentBu[metadataEntry].CategoryID,
|
|
360
375
|
'ID',
|
|
361
|
-
'ContentType'
|
|
376
|
+
'ContentType',
|
|
377
|
+
buObject.eid
|
|
362
378
|
);
|
|
363
379
|
if (!folderTypesFromParent.includes(folderContentType)) {
|
|
364
380
|
Util.logger.verbose(
|
|
@@ -375,16 +391,14 @@ class DataExtension extends MetadataType {
|
|
|
375
391
|
}
|
|
376
392
|
|
|
377
393
|
// revert client to current default
|
|
378
|
-
this.client =
|
|
379
|
-
Folder.client =
|
|
380
|
-
Folder.cache = this.cache;
|
|
394
|
+
this.client = auth.getSDK(this.buObject);
|
|
395
|
+
Folder.client = auth.getSDK(this.buObject);
|
|
381
396
|
|
|
382
397
|
// make sure to overwrite parent bu DEs with local ones
|
|
383
398
|
metadata = { ...metadataParentBu, ...metadata };
|
|
384
399
|
}
|
|
385
|
-
|
|
386
400
|
if (retrieveDir) {
|
|
387
|
-
const savedMetadata = await
|
|
401
|
+
const savedMetadata = await super.saveResults(metadata, retrieveDir, null);
|
|
388
402
|
Util.logger.info(
|
|
389
403
|
`Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})`
|
|
390
404
|
);
|
|
@@ -394,41 +408,62 @@ class DataExtension extends MetadataType {
|
|
|
394
408
|
}
|
|
395
409
|
return { metadata: metadata, type: 'dataExtension' };
|
|
396
410
|
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* helper to retrieve all dataExtension fields and attach them to the dataExtension metadata
|
|
414
|
+
*
|
|
415
|
+
* @private
|
|
416
|
+
* @param {TYPE.DataExtensionMap} metadata already cached dataExtension metadata
|
|
417
|
+
* @param {TYPE.SoapRequestParams} [fieldOptions] optionally filter results
|
|
418
|
+
* @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
|
|
419
|
+
* @returns {Promise.<void>} -
|
|
420
|
+
*/
|
|
421
|
+
static async _attachFields(metadata, fieldOptions, additionalFields) {
|
|
422
|
+
const fieldsObj = await this._retrieveFields(fieldOptions, additionalFields);
|
|
423
|
+
const fieldKeys = Object.keys(fieldsObj);
|
|
424
|
+
// add fields to corresponding DE
|
|
425
|
+
for (const key of fieldKeys) {
|
|
426
|
+
const field = fieldsObj[key];
|
|
427
|
+
if (metadata[field?.DataExtension?.CustomerKey]) {
|
|
428
|
+
metadata[field.DataExtension.CustomerKey].Fields.push(field);
|
|
429
|
+
} else {
|
|
430
|
+
Util.logger.warn(` - Issue retrieving data extension fields. key='${key}'`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// sort fields by Ordinal value (API returns field unsorted)
|
|
435
|
+
for (const metadataEntry in metadata) {
|
|
436
|
+
metadata[metadataEntry].Fields.sort(DataExtensionField.sortDeFields);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// remove attributes that we do not want to retrieve
|
|
440
|
+
// * do this after sorting on the DE's field list
|
|
441
|
+
for (const key of fieldKeys) {
|
|
442
|
+
DataExtensionField.postRetrieveTasks(fieldsObj[key], true);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
397
446
|
/**
|
|
398
447
|
* Retrieves dataExtension metadata. Afterwards starts retrieval of dataExtensionColumn metadata retrieval
|
|
448
|
+
*
|
|
449
|
+
* @param {TYPE.BuObject} [buObject] properties for auth
|
|
399
450
|
* @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
|
|
400
|
-
* @returns {Promise
|
|
451
|
+
* @returns {Promise.<{metadata: TYPE.DataExtensionMap, type: string}>} Promise of item map
|
|
401
452
|
*/
|
|
402
|
-
static async retrieveChangelog(additionalFields) {
|
|
453
|
+
static async retrieveChangelog(buObject, additionalFields) {
|
|
403
454
|
const metadata = await this._retrieveAll(additionalFields);
|
|
404
455
|
return { metadata: metadata, type: 'dataExtension' };
|
|
405
456
|
}
|
|
406
457
|
/**
|
|
407
458
|
* manages post retrieve steps
|
|
408
|
-
*
|
|
409
|
-
* @param {
|
|
410
|
-
* @
|
|
411
|
-
* @returns {DataExtensionItem} metadata
|
|
459
|
+
*
|
|
460
|
+
* @param {TYPE.DataExtensionItem} metadata a single dataExtension
|
|
461
|
+
* @returns {TYPE.DataExtensionItem} metadata
|
|
412
462
|
*/
|
|
413
|
-
static async postRetrieveTasks(metadata
|
|
414
|
-
if (!metadata.Fields || !metadata.Fields.length) {
|
|
415
|
-
// assume we were in deploy mode. retrieve fields.
|
|
416
|
-
const tempList = {};
|
|
417
|
-
tempList[metadata[this.definition.keyField]] = metadata;
|
|
418
|
-
await this._retrieveFieldsForSingleDe(tempList, metadata[this.definition.keyField]);
|
|
419
|
-
}
|
|
420
|
-
// if retrieving template, replace the name with customer key if that wasn't already the case
|
|
421
|
-
if (isTemplating) {
|
|
422
|
-
const warningMsg =
|
|
423
|
-
'Ensure that Queries that write into this DE are updated with the new key before deployment.';
|
|
424
|
-
this.overrideKeyWithName(metadata, warningMsg);
|
|
425
|
-
}
|
|
463
|
+
static async postRetrieveTasks(metadata) {
|
|
426
464
|
// Error during deploy if SendableSubscriberField.Name = '_SubscriberKey' even though it is retrieved like that
|
|
427
465
|
// Therefore map it to 'Subscriber Key'. Retrieving afterward still results in '_SubscriberKey'
|
|
428
|
-
if (
|
|
429
|
-
metadata.SendableSubscriberField &&
|
|
430
|
-
metadata.SendableSubscriberField.Name === '_SubscriberKey'
|
|
431
|
-
) {
|
|
466
|
+
if (metadata.SendableSubscriberField?.Name === '_SubscriberKey') {
|
|
432
467
|
metadata.SendableSubscriberField.Name = 'Subscriber Key';
|
|
433
468
|
}
|
|
434
469
|
return this._parseMetadata(JSON.parse(JSON.stringify(metadata)));
|
|
@@ -436,17 +471,17 @@ class DataExtension extends MetadataType {
|
|
|
436
471
|
|
|
437
472
|
/**
|
|
438
473
|
* Helper to retrieve Data Extension Fields
|
|
474
|
+
*
|
|
439
475
|
* @private
|
|
440
|
-
* @param {
|
|
476
|
+
* @param {TYPE.SoapRequestParams} [options] options (e.g. continueRequest)
|
|
441
477
|
* @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
|
|
442
|
-
* @returns {Promise
|
|
478
|
+
* @returns {Promise.<TYPE.DataExtensionFieldMap>} Promise of items
|
|
443
479
|
*/
|
|
444
480
|
static async _retrieveFields(options, additionalFields) {
|
|
445
481
|
if (!options) {
|
|
446
482
|
// dont print this during updates or templating which retrieves fields DE-by-DE
|
|
447
|
-
Util.logger.info('- Caching dependent Metadata: dataExtensionField');
|
|
483
|
+
Util.logger.info(' - Caching dependent Metadata: dataExtensionField');
|
|
448
484
|
}
|
|
449
|
-
DataExtensionField.cache = this.metadata;
|
|
450
485
|
DataExtensionField.client = this.client;
|
|
451
486
|
DataExtensionField.properties = this.properties;
|
|
452
487
|
|
|
@@ -455,10 +490,11 @@ class DataExtension extends MetadataType {
|
|
|
455
490
|
}
|
|
456
491
|
/**
|
|
457
492
|
* helps retrieving fields during templating and deploy where we dont want the full list
|
|
493
|
+
*
|
|
458
494
|
* @private
|
|
459
|
-
* @param {DataExtensionMap} metadata list of DEs
|
|
495
|
+
* @param {TYPE.DataExtensionMap} metadata list of DEs
|
|
460
496
|
* @param {string} customerKey external key of single DE
|
|
461
|
-
* @returns {Promise
|
|
497
|
+
* @returns {Promise.<void>} -
|
|
462
498
|
*/
|
|
463
499
|
static async _retrieveFieldsForSingleDe(metadata, customerKey) {
|
|
464
500
|
const fieldOptions = {
|
|
@@ -470,34 +506,28 @@ class DataExtension extends MetadataType {
|
|
|
470
506
|
};
|
|
471
507
|
const fieldsObj = await this._retrieveFields(fieldOptions);
|
|
472
508
|
|
|
473
|
-
DataExtensionField.cache = this.metadata;
|
|
474
509
|
DataExtensionField.client = this.client;
|
|
475
510
|
DataExtensionField.properties = this.properties;
|
|
476
511
|
const fieldArr = DataExtensionField.convertToSortedArray(fieldsObj);
|
|
477
512
|
|
|
478
513
|
// remove attributes that we do not want to retrieve
|
|
479
514
|
// * do this after sorting on the DE's field list
|
|
480
|
-
|
|
515
|
+
for (const field of fieldArr) {
|
|
481
516
|
DataExtensionField.postRetrieveTasks(field, true);
|
|
482
|
-
}
|
|
517
|
+
}
|
|
483
518
|
|
|
484
519
|
metadata[customerKey].Fields = fieldArr;
|
|
485
520
|
}
|
|
486
521
|
|
|
487
522
|
/**
|
|
488
523
|
* prepares a DataExtension for deployment
|
|
489
|
-
*
|
|
490
|
-
* @
|
|
524
|
+
*
|
|
525
|
+
* @param {TYPE.DataExtensionItem} metadata a single data Extension
|
|
526
|
+
* @returns {Promise.<TYPE.DataExtensionItem>} Promise of updated single DE
|
|
491
527
|
*/
|
|
492
528
|
static async preDeployTasks(metadata) {
|
|
493
529
|
// folder
|
|
494
|
-
metadata.CategoryID =
|
|
495
|
-
this.cache,
|
|
496
|
-
'folder',
|
|
497
|
-
metadata.r__folder_Path,
|
|
498
|
-
'Path',
|
|
499
|
-
'ID'
|
|
500
|
-
);
|
|
530
|
+
metadata.CategoryID = cache.searchForField('folder', metadata.r__folder_Path, 'Path', 'ID');
|
|
501
531
|
delete metadata.r__folder_Path;
|
|
502
532
|
|
|
503
533
|
// DataExtensionTemplate
|
|
@@ -519,8 +549,7 @@ class DataExtension extends MetadataType {
|
|
|
519
549
|
// get template's CustomerKey
|
|
520
550
|
try {
|
|
521
551
|
metadata.Template = {
|
|
522
|
-
CustomerKey:
|
|
523
|
-
this.cache,
|
|
552
|
+
CustomerKey: cache.searchForField(
|
|
524
553
|
'dataExtensionTemplate',
|
|
525
554
|
metadata.r__dataExtensionTemplate_Name,
|
|
526
555
|
'Name',
|
|
@@ -561,9 +590,10 @@ class DataExtension extends MetadataType {
|
|
|
561
590
|
/**
|
|
562
591
|
* Saves json content to a html table in the local file system. Will create the parent directory if it does not exist.
|
|
563
592
|
* The json's first level of keys must represent the rows and the secend level the columns
|
|
593
|
+
*
|
|
564
594
|
* @private
|
|
565
|
-
* @param {DataExtensionItem} json single dataextension
|
|
566
|
-
* @param {
|
|
595
|
+
* @param {TYPE.DataExtensionItem} json single dataextension
|
|
596
|
+
* @param {object[][]} tabled prepped array for output in tabular format
|
|
567
597
|
* @returns {string} file content
|
|
568
598
|
*/
|
|
569
599
|
static _generateDocHtml(json, tabled) {
|
|
@@ -577,37 +607,34 @@ class DataExtension extends MetadataType {
|
|
|
577
607
|
|
|
578
608
|
output += `<p><b>Description:</b> ${json.Description || 'n/a'}</p>`;
|
|
579
609
|
output += `<p><b>Folder:</b> ${
|
|
580
|
-
json.r__folder_Path
|
|
581
|
-
|
|
582
|
-
: '<i>Hidden! Could not find folder with ID ' + json.CategoryID + '</i>'
|
|
610
|
+
json.r__folder_Path ||
|
|
611
|
+
'<i>Hidden! Could not find folder with ID ' + json.CategoryID + '</i>'
|
|
583
612
|
}</p>`;
|
|
584
613
|
output += `<p><b>Fields in table:</b> ${tabled.length - 1}</p>`;
|
|
585
614
|
output += '<p><b>Sendable:</b> ';
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
'Yes (<i>' +
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
output += `<p><b>Testable:</b> ${json.IsTestable === 'true' ? 'Yes' : 'No'}</p>\n\n`;
|
|
615
|
+
output +=
|
|
616
|
+
json.IsSendable === true
|
|
617
|
+
? 'Yes (<i>' +
|
|
618
|
+
json.SendableDataExtensionField.Name +
|
|
619
|
+
'</i> to <i>' +
|
|
620
|
+
json.SendableSubscriberField.Name +
|
|
621
|
+
'</i>)</p>\n\n'
|
|
622
|
+
: `No</p>\n\n`;
|
|
623
|
+
output += `<p><b>Testable:</b> ${json.IsTestable === true ? 'Yes' : 'No'}</p>\n\n`;
|
|
597
624
|
if (json.r__dataExtensionTemplate_Name) {
|
|
598
625
|
output += `<p><b>Template:</b> ${json.r__dataExtensionTemplate_Name}</p>`;
|
|
599
626
|
}
|
|
600
627
|
|
|
601
628
|
output += '<table><thead><tr>';
|
|
602
|
-
tabled[0]
|
|
629
|
+
for (const element of tabled[0]) {
|
|
603
630
|
output += '<th>' + element + '</th>';
|
|
604
|
-
}
|
|
631
|
+
}
|
|
605
632
|
output += '</tr><thead><tbody>';
|
|
606
633
|
for (let i = 1; i < tabled.length; i++) {
|
|
607
634
|
output += '<tr>';
|
|
608
|
-
tabled[i]
|
|
635
|
+
for (const field of tabled[i]) {
|
|
609
636
|
output += `<td>${field}</td>`;
|
|
610
|
-
}
|
|
637
|
+
}
|
|
611
638
|
output += '</tr>';
|
|
612
639
|
}
|
|
613
640
|
output += '</tbody></table>';
|
|
@@ -618,9 +645,10 @@ class DataExtension extends MetadataType {
|
|
|
618
645
|
* Experimental: Only working for DataExtensions:
|
|
619
646
|
* Saves json content to a html table in the local file system. Will create the parent directory if it does not exist.
|
|
620
647
|
* The json's first level of keys must represent the rows and the secend level the columns
|
|
648
|
+
*
|
|
621
649
|
* @private
|
|
622
|
-
* @param {DataExtensionItem} json dataextension
|
|
623
|
-
* @param {
|
|
650
|
+
* @param {TYPE.DataExtensionItem} json dataextension
|
|
651
|
+
* @param {object[][]} tabled prepped array for output in tabular format
|
|
624
652
|
* @returns {string} file content
|
|
625
653
|
*/
|
|
626
654
|
static _generateDocMd(json, tabled) {
|
|
@@ -632,37 +660,34 @@ class DataExtension extends MetadataType {
|
|
|
632
660
|
output +=
|
|
633
661
|
`**Description:** ${json.Description || 'n/a'}\n\n` +
|
|
634
662
|
`**Folder:** ${
|
|
635
|
-
json.r__folder_Path
|
|
636
|
-
|
|
637
|
-
: '_Hidden! Could not find folder with ID ' + json.CategoryID + '_'
|
|
663
|
+
json.r__folder_Path ||
|
|
664
|
+
'_Hidden! Could not find folder with ID ' + json.CategoryID + '_'
|
|
638
665
|
}/\n\n` +
|
|
639
666
|
`**Fields in table:** ${tabled.length - 1}\n\n`;
|
|
640
667
|
output += '**Sendable:** ';
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
'Yes (`' +
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
}
|
|
651
|
-
output += `**Testable:** ${json.IsTestable === 'true' ? 'Yes' : 'No'}\n\n`;
|
|
668
|
+
output +=
|
|
669
|
+
json.IsSendable === true
|
|
670
|
+
? 'Yes (`' +
|
|
671
|
+
json.SendableDataExtensionField.Name +
|
|
672
|
+
'` to `' +
|
|
673
|
+
json.SendableSubscriberField.Name +
|
|
674
|
+
'`)\n\n'
|
|
675
|
+
: `No\n\n`;
|
|
676
|
+
output += `**Testable:** ${json.IsTestable === true ? 'Yes' : 'No'}\n\n`;
|
|
652
677
|
if (json.r__dataExtensionTemplate_Name) {
|
|
653
678
|
output += `**Template:** ${json.r__dataExtensionTemplate_Name}\n\n`;
|
|
654
679
|
}
|
|
655
680
|
|
|
656
681
|
let tableSeparator = '';
|
|
657
|
-
tabled[0]
|
|
682
|
+
for (const column of tabled[0]) {
|
|
658
683
|
output += `| ${column} `;
|
|
659
684
|
tableSeparator += '| --- ';
|
|
660
|
-
}
|
|
685
|
+
}
|
|
661
686
|
output += `|\n${tableSeparator}|\n`;
|
|
662
687
|
for (let i = 1; i < tabled.length; i++) {
|
|
663
|
-
tabled[i]
|
|
688
|
+
for (const field of tabled[i]) {
|
|
664
689
|
output += `| ${field} `;
|
|
665
|
-
}
|
|
690
|
+
}
|
|
666
691
|
output += '|\n';
|
|
667
692
|
}
|
|
668
693
|
return output;
|
|
@@ -671,28 +696,26 @@ class DataExtension extends MetadataType {
|
|
|
671
696
|
/**
|
|
672
697
|
* Saves json content to a html table in the local file system. Will create the parent directory if it does not exist.
|
|
673
698
|
* The json's first level of keys must represent the rows and the secend level the columns
|
|
699
|
+
*
|
|
674
700
|
* @private
|
|
675
701
|
* @param {string} directory directory the file will be written to
|
|
676
702
|
* @param {string} filename name of the file without '.json' ending
|
|
677
|
-
* @param {DataExtensionItem} json dataextension.columns
|
|
703
|
+
* @param {TYPE.DataExtensionItem} json dataextension.columns
|
|
678
704
|
* @param {'html'|'md'} mode html or md
|
|
679
705
|
* @param {string[]} [fieldsToKeep] list of keys(columns) to show. This will also specify
|
|
680
|
-
* @returns {Promise
|
|
706
|
+
* @returns {Promise.<boolean>} Promise of success of saving the file
|
|
681
707
|
*/
|
|
682
708
|
static async _writeDoc(directory, filename, json, mode, fieldsToKeep) {
|
|
683
|
-
if (!File.existsSync(directory)) {
|
|
684
|
-
File.mkdirpSync(directory);
|
|
685
|
-
}
|
|
686
709
|
let fieldsJson = Object.values(json.Fields);
|
|
687
710
|
if (fieldsToKeep) {
|
|
688
711
|
const newJson = [];
|
|
689
|
-
|
|
712
|
+
for (const element of fieldsJson) {
|
|
690
713
|
const newJsonElement = {};
|
|
691
|
-
|
|
714
|
+
for (const field of fieldsToKeep) {
|
|
692
715
|
newJsonElement[field] = element[field];
|
|
693
|
-
}
|
|
716
|
+
}
|
|
694
717
|
newJson.push(newJsonElement);
|
|
695
|
-
}
|
|
718
|
+
}
|
|
696
719
|
fieldsJson = newJson;
|
|
697
720
|
}
|
|
698
721
|
const tabled = jsonToTable(fieldsJson);
|
|
@@ -704,41 +727,44 @@ class DataExtension extends MetadataType {
|
|
|
704
727
|
}
|
|
705
728
|
try {
|
|
706
729
|
// write to disk
|
|
707
|
-
await File.writeToFile(directory, filename + '.dataExtension', mode, output);
|
|
730
|
+
await File.writeToFile(directory, filename + '.dataExtension-doc', mode, output);
|
|
708
731
|
} catch (ex) {
|
|
709
732
|
Util.logger.error(`DataExtension.writeDeToX(${mode}):: error | ` + ex.message);
|
|
710
733
|
}
|
|
711
734
|
}
|
|
712
735
|
/**
|
|
713
736
|
* Parses metadata into a readable Markdown/HTML format then saves it
|
|
714
|
-
*
|
|
715
|
-
* @param {
|
|
716
|
-
* @param {
|
|
717
|
-
* @returns {Promise
|
|
737
|
+
*
|
|
738
|
+
* @param {TYPE.BuObject} buObject properties for auth
|
|
739
|
+
* @param {TYPE.DataExtensionMap} [metadata] a list of dataExtension definitions
|
|
740
|
+
* @returns {Promise.<void>} -
|
|
718
741
|
*/
|
|
719
|
-
static async document(buObject, metadata
|
|
720
|
-
|
|
721
|
-
metadata
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
742
|
+
static async document(buObject, metadata) {
|
|
743
|
+
try {
|
|
744
|
+
if (!metadata) {
|
|
745
|
+
metadata = this.readBUMetadataForType(
|
|
746
|
+
File.normalizePath([
|
|
747
|
+
this.properties.directories.retrieve,
|
|
748
|
+
buObject.credential,
|
|
749
|
+
buObject.businessUnit,
|
|
750
|
+
]),
|
|
751
|
+
true
|
|
752
|
+
).dataExtension;
|
|
753
|
+
}
|
|
754
|
+
} catch (ex) {
|
|
755
|
+
Util.logger.error(ex.message);
|
|
756
|
+
return;
|
|
729
757
|
}
|
|
730
758
|
const docPath = File.normalizePath([
|
|
731
|
-
this.properties.directories.
|
|
759
|
+
this.properties.directories.retrieve,
|
|
732
760
|
buObject.credential,
|
|
733
761
|
buObject.businessUnit,
|
|
762
|
+
this.definition.type,
|
|
734
763
|
]);
|
|
735
764
|
if (!metadata || !Object.keys(metadata).length) {
|
|
736
765
|
// as part of retrieve & manual execution we could face an empty folder
|
|
737
766
|
return;
|
|
738
767
|
}
|
|
739
|
-
if (!isDeploy) {
|
|
740
|
-
File.removeSync(docPath);
|
|
741
|
-
}
|
|
742
768
|
const columnsToIterateThrough = ['IsNullable', 'IsPrimaryKey'];
|
|
743
769
|
const columnsToPrint = [
|
|
744
770
|
'Name',
|
|
@@ -748,128 +774,99 @@ class DataExtension extends MetadataType {
|
|
|
748
774
|
'IsNullable',
|
|
749
775
|
'DefaultValue',
|
|
750
776
|
];
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
metadata
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
777
|
+
return Promise.all(
|
|
778
|
+
Object.keys(metadata).map((customerKey) => {
|
|
779
|
+
// for (const customerKey in metadata) {
|
|
780
|
+
if (metadata[customerKey]?.Fields?.length) {
|
|
781
|
+
for (const field of metadata[customerKey].Fields) {
|
|
782
|
+
field.IsNullable = !Util.isTrue(field.IsRequired);
|
|
783
|
+
for (const key of columnsToIterateThrough) {
|
|
784
|
+
if (Util.isTrue(field[key])) {
|
|
785
|
+
field[key] = '+';
|
|
786
|
+
} else if (Util.isFalse(field[key])) {
|
|
787
|
+
field[key] = '-';
|
|
788
|
+
}
|
|
760
789
|
}
|
|
761
|
-
}
|
|
762
|
-
});
|
|
790
|
+
}
|
|
763
791
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
792
|
+
if (['html', 'both'].includes(this.properties.options.documentType)) {
|
|
793
|
+
return this._writeDoc(
|
|
794
|
+
docPath + '/',
|
|
795
|
+
customerKey,
|
|
796
|
+
metadata[customerKey],
|
|
797
|
+
'html',
|
|
798
|
+
columnsToPrint
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
if (['md', 'both'].includes(this.properties.options.documentType)) {
|
|
802
|
+
return this._writeDoc(
|
|
803
|
+
docPath + '/',
|
|
804
|
+
customerKey,
|
|
805
|
+
metadata[customerKey],
|
|
806
|
+
'md',
|
|
807
|
+
columnsToPrint
|
|
808
|
+
);
|
|
809
|
+
}
|
|
781
810
|
}
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
if (['html', 'both'].includes(this.properties.options.documentType)) {
|
|
785
|
-
Util.logger.info(`Created ${docPath}/*.dataExtension.html`);
|
|
786
|
-
}
|
|
787
|
-
if (['md', 'both'].includes(this.properties.options.documentType)) {
|
|
788
|
-
Util.logger.info(`Created ${docPath}/*.dataExtension.md`);
|
|
789
|
-
}
|
|
811
|
+
})
|
|
812
|
+
);
|
|
790
813
|
}
|
|
791
814
|
|
|
792
815
|
/**
|
|
793
|
-
* Delete a
|
|
794
|
-
*
|
|
816
|
+
* Delete a metadata item from the specified business unit
|
|
817
|
+
*
|
|
818
|
+
* @param {TYPE.BuObject} buObject references credentials
|
|
795
819
|
* @param {string} customerKey Identifier of data extension
|
|
796
|
-
* @returns {Promise
|
|
820
|
+
* @returns {Promise.<boolean>} deletion success status
|
|
797
821
|
*/
|
|
798
|
-
static
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
client = await Util.getETClient(buObject);
|
|
802
|
-
} catch (ex) {
|
|
803
|
-
Util.logger.error(ex.message);
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
const config = {
|
|
807
|
-
props: { CustomerKey: customerKey },
|
|
808
|
-
};
|
|
822
|
+
static deleteByKey(buObject, customerKey) {
|
|
823
|
+
return super.deleteByKeySOAP(buObject, customerKey, false);
|
|
824
|
+
}
|
|
809
825
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
`${customerKey}.${this.definition.type}-meta.json`,
|
|
837
|
-
]);
|
|
838
|
-
if (File.existsSync(jsonFile)) {
|
|
839
|
-
File.unlinkSync(jsonFile);
|
|
840
|
-
}
|
|
841
|
-
// delete local copy: doc/dataExtension/cred/bu/...md
|
|
842
|
-
const mdFile = File.normalizePath([
|
|
843
|
-
this.properties.directories.dataExtension,
|
|
844
|
-
buObject.credential,
|
|
845
|
-
buObject.businessUnit,
|
|
846
|
-
`${customerKey}.${this.definition.type}.md`,
|
|
847
|
-
]);
|
|
848
|
-
if (File.existsSync(mdFile)) {
|
|
849
|
-
File.unlinkSync(mdFile);
|
|
850
|
-
}
|
|
851
|
-
} else {
|
|
852
|
-
Util.logger.info('mcdev.deleteDE:: Success: ' + JSON.stringify(response));
|
|
853
|
-
}
|
|
854
|
-
});
|
|
826
|
+
/**
|
|
827
|
+
* clean up after deleting a metadata item
|
|
828
|
+
*
|
|
829
|
+
* @param {TYPE.BuObject} buObject references credentials
|
|
830
|
+
* @param {string} customerKey Identifier of metadata item
|
|
831
|
+
* @returns {void}
|
|
832
|
+
*/
|
|
833
|
+
static async postDeleteTasks(buObject, customerKey) {
|
|
834
|
+
// delete local copy: retrieve/cred/bu/dataExtension/...json
|
|
835
|
+
const jsonFile = File.normalizePath([
|
|
836
|
+
this.properties.directories.retrieve,
|
|
837
|
+
buObject.credential,
|
|
838
|
+
buObject.businessUnit,
|
|
839
|
+
this.definition.type,
|
|
840
|
+
`${customerKey}.${this.definition.type}-meta.json`,
|
|
841
|
+
]);
|
|
842
|
+
await File.remove(jsonFile);
|
|
843
|
+
// delete local copy: doc/dataExtension/cred/bu/...md
|
|
844
|
+
const mdFile = File.normalizePath([
|
|
845
|
+
this.properties.directories.docs,
|
|
846
|
+
'dataExtension',
|
|
847
|
+
buObject.credential,
|
|
848
|
+
buObject.businessUnit,
|
|
849
|
+
`${customerKey}.${this.definition.type}.md`,
|
|
850
|
+
]);
|
|
851
|
+
await File.remove(mdFile);
|
|
855
852
|
}
|
|
856
853
|
|
|
857
854
|
/**
|
|
858
855
|
* Retrieves folder metadata into local filesystem. Also creates a uniquePath attribute for each folder.
|
|
859
|
-
*
|
|
860
|
-
* @param {
|
|
861
|
-
* @
|
|
862
|
-
* @returns {Promise} Promise
|
|
856
|
+
*
|
|
857
|
+
* @param {TYPE.BuObject} buObject properties for auth
|
|
858
|
+
* @returns {Promise.<{metadata: TYPE.DataExtensionMap, type: string}>} Promise
|
|
863
859
|
*/
|
|
864
|
-
static async retrieveForCache(buObject
|
|
865
|
-
return this.retrieve(null, ['ObjectID', 'CustomerKey', 'Name'], buObject, null,
|
|
860
|
+
static async retrieveForCache(buObject) {
|
|
861
|
+
return this.retrieve(null, ['ObjectID', 'CustomerKey', 'Name'], buObject, null, null);
|
|
866
862
|
}
|
|
867
863
|
/**
|
|
868
864
|
* Retrieves dataExtension metadata in template format.
|
|
865
|
+
*
|
|
869
866
|
* @param {string} templateDir Directory where retrieved metadata directory will be saved
|
|
870
867
|
* @param {string} name name of the metadata item
|
|
871
|
-
* @param {
|
|
872
|
-
* @returns {Promise
|
|
868
|
+
* @param {TYPE.TemplateMap} templateVariables variables to be replaced in the metadata
|
|
869
|
+
* @returns {Promise.<{metadata: TYPE.DataExtensionMap, type: string}>} Promise of items
|
|
873
870
|
*/
|
|
874
871
|
static async retrieveAsTemplate(templateDir, name, templateVariables) {
|
|
875
872
|
const options = {
|
|
@@ -879,8 +876,7 @@ class DataExtension extends MetadataType {
|
|
|
879
876
|
rightOperand: name,
|
|
880
877
|
},
|
|
881
878
|
};
|
|
882
|
-
|
|
883
|
-
// const metadata = super.parseResponseBody(response.body);
|
|
879
|
+
|
|
884
880
|
const metadata = await this._retrieveAll(null, options);
|
|
885
881
|
|
|
886
882
|
if (!Object.keys(metadata).length) {
|
|
@@ -898,16 +894,14 @@ class DataExtension extends MetadataType {
|
|
|
898
894
|
|
|
899
895
|
const originalKey = key;
|
|
900
896
|
const metadataCleaned = JSON.parse(
|
|
901
|
-
JSON.stringify(
|
|
902
|
-
await this.postRetrieveTasks(metadata[key], null, !!templateVariables)
|
|
903
|
-
)
|
|
897
|
+
JSON.stringify(await this.postRetrieveTasks(metadata[key]))
|
|
904
898
|
);
|
|
905
899
|
|
|
906
900
|
this.keepTemplateFields(metadataCleaned);
|
|
907
901
|
const metadataTemplated = JSON.parse(
|
|
908
902
|
Util.replaceByObject(JSON.stringify(metadataCleaned), templateVariables)
|
|
909
903
|
);
|
|
910
|
-
File.writeJSONToFile(
|
|
904
|
+
await File.writeJSONToFile(
|
|
911
905
|
[templateDir, this.definition.type].join('/'),
|
|
912
906
|
originalKey + '.' + this.definition.type + '-meta',
|
|
913
907
|
metadataTemplated
|
|
@@ -916,26 +910,24 @@ class DataExtension extends MetadataType {
|
|
|
916
910
|
Util.metadataLogger('error', this.definition.type, 'retrieve', ex, key);
|
|
917
911
|
}
|
|
918
912
|
}
|
|
913
|
+
Util.logger.info(`- templated ${this.definition.type}: ${customerKey}`);
|
|
919
914
|
|
|
920
|
-
|
|
921
|
-
`DataExtension.retrieveAsTemplate:: All records written to filesystem (${customerKey})`
|
|
922
|
-
);
|
|
923
|
-
return { metadata: metadata, type: 'dataExtension' };
|
|
915
|
+
return { metadata: metadata[customerKey], type: 'dataExtension' };
|
|
924
916
|
}
|
|
925
917
|
|
|
926
918
|
/**
|
|
927
919
|
* parses retrieved Metadata before saving
|
|
920
|
+
*
|
|
928
921
|
* @private
|
|
929
|
-
* @param {DataExtensionItem} metadata a single dataExtension definition
|
|
930
|
-
* @returns {DataExtensionItem} a single dataExtension definition
|
|
922
|
+
* @param {TYPE.DataExtensionItem} metadata a single dataExtension definition
|
|
923
|
+
* @returns {TYPE.DataExtensionItem} a single dataExtension definition
|
|
931
924
|
*/
|
|
932
925
|
static _parseMetadata(metadata) {
|
|
933
926
|
let error = false;
|
|
934
927
|
let verbose = false;
|
|
935
928
|
// data extension type (from folder)
|
|
936
929
|
try {
|
|
937
|
-
metadata.r__folder_ContentType =
|
|
938
|
-
this.cache,
|
|
930
|
+
metadata.r__folder_ContentType = cache.searchForField(
|
|
939
931
|
'folder',
|
|
940
932
|
metadata.CategoryID,
|
|
941
933
|
'ID',
|
|
@@ -947,13 +939,12 @@ class DataExtension extends MetadataType {
|
|
|
947
939
|
metadata.r__folder_ContentType = 'synchronizeddataextension';
|
|
948
940
|
} else {
|
|
949
941
|
error = true;
|
|
950
|
-
Util.logger.warn(`
|
|
942
|
+
Util.logger.warn(` - dataExtension '${metadata.Name}': ${ex.message}`);
|
|
951
943
|
}
|
|
952
944
|
}
|
|
953
945
|
// folder
|
|
954
946
|
try {
|
|
955
|
-
metadata.r__folder_Path =
|
|
956
|
-
this.cache,
|
|
947
|
+
metadata.r__folder_Path = cache.searchForField(
|
|
957
948
|
'folder',
|
|
958
949
|
metadata.CategoryID,
|
|
959
950
|
'ID',
|
|
@@ -975,10 +966,9 @@ class DataExtension extends MetadataType {
|
|
|
975
966
|
}
|
|
976
967
|
}
|
|
977
968
|
// DataExtensionTemplate
|
|
978
|
-
if (metadata.Template
|
|
969
|
+
if (metadata.Template?.CustomerKey) {
|
|
979
970
|
try {
|
|
980
|
-
metadata.r__dataExtensionTemplate_Name =
|
|
981
|
-
this.cache,
|
|
971
|
+
metadata.r__dataExtensionTemplate_Name = cache.searchForField(
|
|
982
972
|
'dataExtensionTemplate',
|
|
983
973
|
metadata.Template.CustomerKey,
|
|
984
974
|
'CustomerKey',
|
|
@@ -992,26 +982,29 @@ class DataExtension extends MetadataType {
|
|
|
992
982
|
// A workaround exists but it's likely not beneficial to explain to users:
|
|
993
983
|
// Create a DE based on the not-supported template on the target BU, retrieve it, copy the Template.CustomerKey into the to-be-deployed DE (or use mcdev-templating), done
|
|
994
984
|
Util.logger.warn(
|
|
995
|
-
`Issue with
|
|
985
|
+
` - Issue with dataExtension '${
|
|
996
986
|
metadata[this.definition.nameField]
|
|
997
987
|
}': Could not find specified DataExtension Template. Please note that DataExtensions based on SMSMessageTracking and SMSSubscriptionLog cannot be deployed automatically across BUs at this point.`
|
|
998
988
|
);
|
|
999
989
|
}
|
|
1000
990
|
}
|
|
991
|
+
// remove the date fields manually here because we need them in the changelog but not in the saved json
|
|
992
|
+
delete metadata.CreatedDate;
|
|
993
|
+
delete metadata.ModifiedDate;
|
|
1001
994
|
|
|
1002
995
|
return metadata;
|
|
1003
996
|
}
|
|
1004
997
|
|
|
1005
998
|
/**
|
|
1006
999
|
* Retrieves dataExtension metadata and cleans it
|
|
1000
|
+
*
|
|
1007
1001
|
* @private
|
|
1008
1002
|
* @param {string[]} [additionalFields] Returns specified fields even if their retrieve definition is not set to true
|
|
1009
|
-
* @param {
|
|
1010
|
-
* @returns {Promise
|
|
1003
|
+
* @param {TYPE.SoapRequestParams} [options] e.g. filter
|
|
1004
|
+
* @returns {Promise.<TYPE.DataExtensionMap>} keyField => metadata map
|
|
1011
1005
|
*/
|
|
1012
1006
|
static async _retrieveAll(additionalFields, options) {
|
|
1013
|
-
const metadata =
|
|
1014
|
-
.metadata;
|
|
1007
|
+
const { metadata } = await super.retrieveSOAP(null, null, options, additionalFields);
|
|
1015
1008
|
for (const key in metadata) {
|
|
1016
1009
|
// some system data extensions do not have CategoryID which throws errors in other places. These do not need to be parsed
|
|
1017
1010
|
if (!metadata[key].CategoryID) {
|
|
@@ -1022,14 +1015,36 @@ class DataExtension extends MetadataType {
|
|
|
1022
1015
|
}
|
|
1023
1016
|
return metadata;
|
|
1024
1017
|
}
|
|
1018
|
+
/**
|
|
1019
|
+
* should return only the json for all but asset, query and script that are saved as multiple files
|
|
1020
|
+
* additionally, the documentation for dataExtension and automation should be returned
|
|
1021
|
+
*
|
|
1022
|
+
* @param {string[]} keyArr customerkey of the metadata
|
|
1023
|
+
* @returns {string[]} list of all files that need to be committed in a flat array ['path/file1.ext', 'path/file2.ext']
|
|
1024
|
+
*/
|
|
1025
|
+
static getFilesToCommit(keyArr) {
|
|
1026
|
+
if (!this.properties.metaDataTypes.documentOnRetrieve.includes(this.definition.type)) {
|
|
1027
|
+
// document dataExtension is not active upon retrieve, run default method instead
|
|
1028
|
+
return super.getFilesToCommit(keyArr);
|
|
1029
|
+
} else {
|
|
1030
|
+
// document dataExtension is active. assume we want to commit the MD file as well
|
|
1031
|
+
const path = File.normalizePath([
|
|
1032
|
+
this.properties.directories.retrieve,
|
|
1033
|
+
this.buObject.credential,
|
|
1034
|
+
this.buObject.businessUnit,
|
|
1035
|
+
this.definition.type,
|
|
1036
|
+
]);
|
|
1037
|
+
|
|
1038
|
+
const fileList = keyArr.flatMap((key) => [
|
|
1039
|
+
File.normalizePath([path, `${key}.${this.definition.type}-meta.json`]),
|
|
1040
|
+
File.normalizePath([path, `${key}.${this.definition.type}-doc.md`]),
|
|
1041
|
+
]);
|
|
1042
|
+
return fileList;
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1025
1045
|
}
|
|
1026
1046
|
|
|
1027
1047
|
// Assign definition to static attributes
|
|
1028
1048
|
DataExtension.definition = require('../MetadataTypeDefinitions').dataExtension;
|
|
1029
|
-
/**
|
|
1030
|
-
* @type {Util.ET_Client}
|
|
1031
|
-
*/
|
|
1032
|
-
DataExtension.client = undefined;
|
|
1033
|
-
DataExtension.cache = {};
|
|
1034
1049
|
|
|
1035
1050
|
module.exports = DataExtension;
|