mcdev 8.2.0 → 8.3.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 (129) hide show
  1. package/.github/ISSUE_TEMPLATE/bug.yml +2 -0
  2. package/.github/workflows/code-test.yml +1 -1
  3. package/.github/workflows/coverage-base-update.yml +1 -1
  4. package/.github/workflows/coverage-develop-branch.yml +1 -1
  5. package/.github/workflows/coverage-main-branch.yml +1 -1
  6. package/.github/workflows/coverage.yml +1 -1
  7. package/.github/workflows/npm-publish.yml +9 -5
  8. package/@types/lib/MetadataTypeDefinitions.d.ts +4 -0
  9. package/@types/lib/MetadataTypeDefinitions.d.ts.map +1 -1
  10. package/@types/lib/MetadataTypeInfo.d.ts +4 -0
  11. package/@types/lib/MetadataTypeInfo.d.ts.map +1 -1
  12. package/@types/lib/Retriever.d.ts.map +1 -1
  13. package/@types/lib/index.d.ts +20 -9
  14. package/@types/lib/index.d.ts.map +1 -1
  15. package/@types/lib/metadataTypes/Asset.d.ts +3 -3
  16. package/@types/lib/metadataTypes/Asset.d.ts.map +1 -1
  17. package/@types/lib/metadataTypes/Automation.d.ts +7 -7
  18. package/@types/lib/metadataTypes/Automation.d.ts.map +1 -1
  19. package/@types/lib/metadataTypes/DataExtension.d.ts +9 -9
  20. package/@types/lib/metadataTypes/DataExtension.d.ts.map +1 -1
  21. package/@types/lib/metadataTypes/DataExtensionField.d.ts +7 -0
  22. package/@types/lib/metadataTypes/DataExtensionField.d.ts.map +1 -1
  23. package/@types/lib/metadataTypes/DataFilter.d.ts +326 -0
  24. package/@types/lib/metadataTypes/DataFilter.d.ts.map +1 -0
  25. package/@types/lib/metadataTypes/DataFilterHidden.d.ts +164 -0
  26. package/@types/lib/metadataTypes/DataFilterHidden.d.ts.map +1 -0
  27. package/@types/lib/metadataTypes/Filter.d.ts +181 -6
  28. package/@types/lib/metadataTypes/Filter.d.ts.map +1 -1
  29. package/@types/lib/metadataTypes/FilterDefinition.d.ts +318 -0
  30. package/@types/lib/metadataTypes/FilterDefinition.d.ts.map +1 -0
  31. package/@types/lib/metadataTypes/FilterDefinitionHidden.d.ts +156 -0
  32. package/@types/lib/metadataTypes/FilterDefinitionHidden.d.ts.map +1 -0
  33. package/@types/lib/metadataTypes/ImportFile.d.ts.map +1 -1
  34. package/@types/lib/metadataTypes/Journey.d.ts +18 -0
  35. package/@types/lib/metadataTypes/Journey.d.ts.map +1 -1
  36. package/@types/lib/metadataTypes/MobileKeyword.d.ts +2 -2
  37. package/@types/lib/metadataTypes/MobileKeyword.d.ts.map +1 -1
  38. package/@types/lib/metadataTypes/User.d.ts +1 -1
  39. package/@types/lib/metadataTypes/User.d.ts.map +1 -1
  40. package/@types/lib/metadataTypes/definitions/DataFilter.definition.d.ts +230 -0
  41. package/@types/lib/metadataTypes/definitions/DataFilter.definition.d.ts.map +1 -0
  42. package/@types/lib/metadataTypes/definitions/DataFilterHidden.definition.d.ts +230 -0
  43. package/@types/lib/metadataTypes/definitions/DataFilterHidden.definition.d.ts.map +1 -0
  44. package/@types/lib/metadataTypes/definitions/Filter.definition.d.ts +203 -31
  45. package/@types/lib/metadataTypes/definitions/FilterDefinition.definition.d.ts +218 -0
  46. package/@types/lib/metadataTypes/definitions/FilterDefinition.definition.d.ts.map +1 -0
  47. package/@types/lib/metadataTypes/definitions/FilterDefinitionHidden.definition.d.ts +218 -0
  48. package/@types/lib/metadataTypes/definitions/FilterDefinitionHidden.definition.d.ts.map +1 -0
  49. package/@types/lib/metadataTypes/definitions/Journey.definition.d.ts +18 -0
  50. package/@types/lib/util/devops.d.ts +1 -0
  51. package/@types/lib/util/devops.d.ts.map +1 -1
  52. package/@types/lib/util/replaceContentBlockReference.d.ts +3 -3
  53. package/@types/lib/util/replaceContentBlockReference.d.ts.map +1 -1
  54. package/@types/lib/util/util.d.ts.map +1 -1
  55. package/@types/lib/util/validations.d.ts.map +1 -1
  56. package/@types/types/mcdev.d.d.ts +296 -0
  57. package/@types/types/mcdev.d.d.ts.map +1 -1
  58. package/lib/MetadataTypeDefinitions.js +4 -0
  59. package/lib/MetadataTypeInfo.js +4 -0
  60. package/lib/Retriever.js +3 -0
  61. package/lib/index.js +87 -3
  62. package/lib/metadataTypes/Automation.js +3 -1
  63. package/lib/metadataTypes/DataExtension.js +19 -12
  64. package/lib/metadataTypes/DataExtensionField.js +25 -3
  65. package/lib/metadataTypes/DataFilter.js +653 -0
  66. package/lib/metadataTypes/DataFilterHidden.js +18 -0
  67. package/lib/metadataTypes/Filter.js +315 -1
  68. package/lib/metadataTypes/ImportFile.js +11 -1
  69. package/lib/metadataTypes/Journey.js +118 -7
  70. package/lib/metadataTypes/definitions/Automation.definition.js +1 -0
  71. package/lib/metadataTypes/definitions/DataFilter.definition.js +168 -0
  72. package/lib/metadataTypes/definitions/DataFilterHidden.definition.js +169 -0
  73. package/lib/metadataTypes/definitions/Filter.definition.js +136 -22
  74. package/lib/metadataTypes/definitions/Journey.definition.js +20 -1
  75. package/lib/util/devops.js +18 -5
  76. package/lib/util/util.js +7 -2
  77. package/lib/util/validations.js +7 -1
  78. package/package.json +10 -10
  79. package/test/general.test.js +17 -19
  80. package/test/mockRoot/.mcdevrc.json +4 -1
  81. package/test/mockRoot/deploy/testInstance/testBU/dataFilter/testExisting_dataFilter.dataFilter-meta.json +19 -0
  82. package/test/mockRoot/deploy/testInstance/testBU/dataFilter/testNew_dataFilter.dataFilter-meta.json +19 -0
  83. package/test/mockRoot/deploy/testInstance/testBU/filter/testExisting_filter.filter-meta.json +10 -0
  84. package/test/mockRoot/deploy/testInstance/testBU/filter/testNew_filter.filter-meta.json +11 -0
  85. package/test/mockRoot/deploy/testInstance/testBU/journey/testExisting_journey_updatecontact.journey-meta.json +108 -0
  86. package/test/resources/9999999/automation/v1/filters/a0f1a1bc-4ea1-44b3-8fe1-ce40ef35c1c0/patch-response.json +18 -0
  87. package/test/resources/9999999/automation/v1/filters/f018f237-f7ef-40b0-afc8-39ea2e5dcca4/delete-response.txt +0 -0
  88. package/test/resources/9999999/automation/v1/filters/f018f237-f7ef-40b0-afc8-39ea2e5dcca4/get-response.json +15 -0
  89. package/test/resources/9999999/automation/v1/filters/f018f237-f7ef-40b0-afc8-39ea2e5dcca4/patch-response.json +15 -0
  90. package/test/resources/9999999/automation/v1/filters/get-response.json +22 -0
  91. package/test/resources/9999999/dataFilter/build-expected.json +19 -0
  92. package/test/resources/9999999/dataFilter/get-expected.json +23 -0
  93. package/test/resources/9999999/dataFilter/patch-expected.json +20 -0
  94. package/test/resources/9999999/dataFilter/post-expected.json +20 -0
  95. package/test/resources/9999999/dataFilter/template-expected.json +19 -0
  96. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINasset,asset-sha,automatio,cloudpage,dataexten,filteract,filterdef,hidden,journey,list,mysubs,publicati,queryacti,salesforc,shared_da,shared_da,shared_sa,ssjsactiv,synchroni,useriniti-response.xml +765 -0
  97. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINfilteractivity,hidden-response.xml +139 -0
  98. package/test/resources/9999999/dataFolder/retrieve-ContentTypeINfilterdefinition,hidden-response.xml +116 -0
  99. package/test/resources/9999999/email/v1/filters/filterdefinition/10ef27dd-4be8-4bf6-970a-8acf8e281e55/delete-response.txt +1 -0
  100. package/test/resources/9999999/email/v1/filters/filterdefinition/10ef27dd-4be8-4bf6-970a-8acf8e281e55/get-response.json +19 -0
  101. package/test/resources/9999999/email/v1/filters/filterdefinition/10ef27dd-4be8-4bf6-970a-8acf8e281e55/patch-response.json +15 -0
  102. package/test/resources/9999999/email/v1/filters/filterdefinition/category/5318/get-response.json +27 -0
  103. package/test/resources/9999999/email/v1/filters/filterdefinition/category/8502/get-response.json +7 -0
  104. package/test/resources/9999999/email/v1/filters/filterdefinition/category/8503/get-response.json +7 -0
  105. package/test/resources/9999999/email/v1/filters/filterdefinition/post-response.json +15 -0
  106. package/test/resources/9999999/filter/build-expected.json +10 -0
  107. package/test/resources/9999999/filter/get-expected.json +11 -0
  108. package/test/resources/9999999/filter/patch-expected.json +11 -0
  109. package/test/resources/9999999/filter/post-expected.json +11 -0
  110. package/test/resources/9999999/filter/template-expected.json +10 -0
  111. package/test/resources/9999999/filterActivity/create-response.xml +46 -0
  112. package/test/resources/9999999/filterActivity/retrieve-CustomerKey=testExisting_filter-response.xml +30 -0
  113. package/test/resources/9999999/filterDefinition/retrieve-CustomerKey=testExisting_dataFilter-response.xml +30 -0
  114. package/test/resources/9999999/interaction/v1/interactions/get-response.json +45 -3
  115. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_Multistep/put-response.json +1 -1
  116. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_updatecontact/get-response.json +119 -0
  117. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_journey_updatecontact/put-response.json +106 -0
  118. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail/put-response-paused.json +1 -1
  119. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail/put-response.json +1 -1
  120. package/test/resources/9999999/interaction/v1/interactions/key_testExisting_temail_notPublished/put-response.json +1 -1
  121. package/test/resources/9999999/journey/get-updatecontact-expected.json +108 -0
  122. package/test/resources/9999999/journey/put-updatecontact-expected.json +108 -0
  123. package/test/type.automation.test.js +14 -14
  124. package/test/type.dataFilter.test.js +174 -0
  125. package/test/type.filter.test.js +170 -0
  126. package/test/type.journey.test.js +59 -7
  127. package/test/type.query.test.js +2 -2
  128. package/test/utils.js +8 -0
  129. package/types/mcdev.d.js +101 -0
@@ -0,0 +1,653 @@
1
+ 'use strict';
2
+
3
+ import MetadataType from './MetadataType.js';
4
+ import { Util } from '../util/util.js';
5
+ import cache from '../util/cache.js';
6
+ import DataExtensionField from './DataExtensionField.js';
7
+ import Folder from './Folder.js';
8
+ import { XMLBuilder, XMLParser } from 'fast-xml-parser';
9
+ import File from '../util/file.js';
10
+
11
+ /**
12
+ * @typedef {import('../../types/mcdev.d.js').BuObject} BuObject
13
+ * @typedef {import('../../types/mcdev.d.js').CodeExtract} CodeExtract
14
+ * @typedef {import('../../types/mcdev.d.js').CodeExtractItem} CodeExtractItem
15
+ * @typedef {import('../../types/mcdev.d.js').MetadataTypeItem} MetadataTypeItem
16
+ * @typedef {import('../../types/mcdev.d.js').MetadataTypeItemDiff} MetadataTypeItemDiff
17
+ * @typedef {import('../../types/mcdev.d.js').MetadataTypeItemObj} MetadataTypeItemObj
18
+ * @typedef {import('../../types/mcdev.d.js').MetadataTypeMap} MetadataTypeMap
19
+ * @typedef {import('../../types/mcdev.d.js').MetadataTypeMapObj} MetadataTypeMapObj
20
+ * @typedef {import('../../types/mcdev.d.js').SoapRequestParams} SoapRequestParams
21
+ * @typedef {import('../../types/mcdev.d.js').TemplateMap} TemplateMap
22
+ * @typedef {import('../../types/mcdev.d.js').DataFilterItem} DataFilterItem
23
+ * @typedef {import('../../types/mcdev.d.js').DataFilterMap} DataFilterMap
24
+ * @typedef {import('../../types/mcdev.d.js').MultiMetadataTypeMap} MultiMetadataTypeMap
25
+ * @typedef {import('../../types/mcdev.d.js').FilterConditionSet} FilterConditionSet
26
+ * @typedef {import('../../types/mcdev.d.js').FilterCondition} FilterCondition
27
+ */
28
+
29
+ /**
30
+ * DataFilter (FilterDefinition) MetadataType
31
+ *
32
+ * @augments MetadataType
33
+ */
34
+ class DataFilter extends MetadataType {
35
+ static cache = {}; // type internal cache for various things
36
+ static deIdKeyMap;
37
+ static hidden = false;
38
+ /**
39
+ * Retrieves all records and saves it to disk
40
+ *
41
+ * @param {string} retrieveDir Directory where retrieved metadata directory will be saved
42
+ * @param {string[]} [_] unused parameter
43
+ * @param {string[]} [__] unused parameter
44
+ * @param {string} [key] customer key of single item to retrieve
45
+ * @returns {Promise.<{metadata: DataFilterMap, type: string}>} Promise of items
46
+ */
47
+ static async retrieve(retrieveDir, _, __, key) {
48
+ Util.logBeta(this.definition.type);
49
+ /** @type {DataFilterMap} */
50
+ const filterDefinitionMap = {};
51
+
52
+ const objectId = key ? await this._getObjectIdForSingleRetrieve(key) : null;
53
+ if (objectId) {
54
+ const metadataMapSingle = await super.retrieveREST(
55
+ null,
56
+ '/email/v1/filters/filterdefinition/' + objectId,
57
+ null,
58
+ key
59
+ );
60
+ Object.assign(filterDefinitionMap, metadataMapSingle.metadata);
61
+ } else {
62
+ const filterFolders = await this._getFilterFolderIds();
63
+
64
+ for (const folderId of filterFolders) {
65
+ const metadataMapFolder = await super.retrieveREST(
66
+ null,
67
+ 'email/v1/filters/filterdefinition/category/' +
68
+ folderId +
69
+ '?derivedFromType=1,2,3,4&',
70
+ null,
71
+ key
72
+ );
73
+ if (Object.keys(metadataMapFolder.metadata).length) {
74
+ Object.assign(filterDefinitionMap, metadataMapFolder.metadata);
75
+ if (filterDefinitionMap[key]) {
76
+ // if key was found we can stop checking other folders
77
+ break;
78
+ }
79
+ }
80
+ }
81
+ }
82
+ // /** @type {DataFilterMap} */
83
+ // const filterDefinitionMap = metadataTypeMapObj.metadata;
84
+ for (const item of Object.values(filterDefinitionMap)) {
85
+ // description is not returned when empty
86
+ item.description ||= '';
87
+ }
88
+ if (retrieveDir) {
89
+ // custom dataExtensionField caching
90
+ await this._cacheDeFields(filterDefinitionMap, 'retrieve');
91
+ // await this._cacheContactAttributes(filterDefinitionMap);
92
+ // await this._cacheMeasures(filterDefinitionMap);
93
+
94
+ const savedMetadata = await this.saveResults(filterDefinitionMap, retrieveDir);
95
+ Util.logger.info(
96
+ `Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})` +
97
+ Util.getKeysString(key)
98
+ );
99
+ }
100
+
101
+ return { metadata: filterDefinitionMap, type: this.definition.type };
102
+ }
103
+ /**
104
+ * helper for {@link DataFilter.retrieve}
105
+ *
106
+ * @param {boolean} [recached] indicates if this is a recursive call after cache refresh
107
+ * @returns {Promise.<number[]>} Array of folder IDs
108
+ */
109
+ static async _getFilterFolderIds(recached = false) {
110
+ const fromCache =
111
+ this.cache.folderFilter || cache.getCache().folder
112
+ ? Object.values(this.cache.folderFilter || cache.getCache().folder)
113
+ .filter((item) => item.ContentType === 'filterdefinition')
114
+ .filter(
115
+ (item) =>
116
+ (!this.hidden && item.Path.startsWith('Data Filters')) ||
117
+ (this.hidden && !item.Path.startsWith('Data Filters'))
118
+ ) // only retrieve from Data Filters folder
119
+ .map((item) => item.ID)
120
+ : [];
121
+ if (fromCache.length) {
122
+ return fromCache;
123
+ }
124
+ if (recached) {
125
+ Util.logger.debug('_getFilterFolderIds: could not find filterdefinition folders');
126
+ return [];
127
+ }
128
+
129
+ const subTypeArr = ['hidden', 'filterdefinition'];
130
+ Util.logger.info(` - Caching dependent Metadata: folder`);
131
+ Util.logSubtypes(subTypeArr);
132
+
133
+ Folder.client = this.client;
134
+ Folder.buObject = this.buObject;
135
+ Folder.properties = this.properties;
136
+ this.cache.folderFilter = (await Folder.retrieveForCache(null, subTypeArr)).metadata;
137
+ return this._getFilterFolderIds(true);
138
+ }
139
+ /**
140
+ * helper for {@link DataFilter._cacheMeasures}
141
+ *
142
+ * @returns {Promise.<number[]>} Array of folder IDs
143
+ */
144
+ static async _getMeasureFolderIds() {
145
+ const fromCache =
146
+ this.cache.folderMeasure?.[this.buObject.mid] || cache.getCache().folder
147
+ ? Object.values(
148
+ this.cache.folderMeasure?.[this.buObject.mid] || cache.getCache().folder
149
+ )
150
+ .filter((item) => item.ContentType === 'measure')
151
+ .map((item) => item.ID)
152
+ : [];
153
+ if (fromCache.length) {
154
+ return fromCache;
155
+ }
156
+
157
+ const subTypeArr = ['measure'];
158
+ Util.logger.info(` - Caching dependent Metadata: folder`);
159
+ Util.logSubtypes(subTypeArr);
160
+
161
+ Folder.client = this.client;
162
+ Folder.buObject = this.buObject;
163
+ Folder.properties = this.properties;
164
+ this.cache.folderMeasure ||= {};
165
+ this.cache.folderMeasure[this.buObject.mid] = (
166
+ await Folder.retrieveForCache(null, subTypeArr)
167
+ ).metadata;
168
+ return this._getMeasureFolderIds();
169
+ }
170
+
171
+ /**
172
+ * helper for {@link DataFilter.retrieve}. uses cached dataExtensions to resolve dataExtensionFields
173
+ *
174
+ * @param {DataFilterMap} metadataTypeMap -
175
+ * @param {'retrieve'|'deploy'} [mode] -
176
+ */
177
+ static async _cacheDeFields(metadataTypeMap, mode) {
178
+ const deKeys =
179
+ mode === 'retrieve'
180
+ ? Object.values(metadataTypeMap)
181
+ .filter((item) => item.derivedFromObjectTypeName === 'DataExtension')
182
+ .filter((item) => item.derivedFromObjectId)
183
+ .map((item) => {
184
+ try {
185
+ const deKey = cache.searchForField(
186
+ 'dataExtension',
187
+ item.derivedFromObjectId,
188
+ 'ObjectID',
189
+ 'CustomerKey'
190
+ );
191
+ if (deKey) {
192
+ this.deIdKeyMap ||= {};
193
+ this.deIdKeyMap[item.derivedFromObjectId] = deKey;
194
+ return deKey;
195
+ }
196
+ } catch {
197
+ return null;
198
+ }
199
+ })
200
+ .filter(Boolean)
201
+ : Object.values(metadataTypeMap)
202
+ .filter((item) => item.r__source_dataExtension_key)
203
+ .map((item) => item.r__source_dataExtension_key)
204
+ .filter(Boolean);
205
+ if (deKeys.length) {
206
+ Util.logger.info(' - Caching dependent Metadata: dataExtensionField');
207
+ // only proceed with the download if we have dataExtension keys
208
+ const fieldOptions = {};
209
+ for (const deKey of deKeys) {
210
+ fieldOptions.filter = fieldOptions.filter
211
+ ? {
212
+ leftOperand: {
213
+ leftOperand: 'DataExtension.CustomerKey',
214
+ operator: 'equals',
215
+ rightOperand: deKey,
216
+ },
217
+ operator: 'OR',
218
+ rightOperand: fieldOptions.filter,
219
+ }
220
+ : {
221
+ leftOperand: 'DataExtension.CustomerKey',
222
+ operator: 'equals',
223
+ rightOperand: deKey,
224
+ };
225
+ }
226
+ DataExtensionField.buObject = this.buObject;
227
+ DataExtensionField.client = this.client;
228
+ DataExtensionField.properties = this.properties;
229
+ this.dataExtensionFieldCache = (
230
+ await DataExtensionField.retrieveForCacheDE(fieldOptions, ['Name', 'ObjectID'])
231
+ ).metadata;
232
+ }
233
+ }
234
+ /**
235
+ * helper for {@link DataFilter.retrieve}
236
+ *
237
+ * @param {DataFilterMap} metadataTypeMap -
238
+ */
239
+ static async _cacheContactAttributes(metadataTypeMap) {
240
+ if (this.cache.contactAttributes?.[this.buObject.mid]) {
241
+ return;
242
+ }
243
+ const subscriberFilters = Object.values(metadataTypeMap)
244
+ .filter((item) => item.derivedFromObjectTypeName === 'SubscriberAttributes')
245
+ .filter((item) => item.derivedFromObjectId);
246
+ if (subscriberFilters.length) {
247
+ Util.logger.info(' - Caching dependent Metadata: contactAttributes');
248
+ const response = await this.client.rest.get('/email/v1/Contacts/Attributes/');
249
+ const keyFieldBackup = this.definition.keyField;
250
+ this.definition.keyField = 'id';
251
+ this.cache.contactAttributes ||= {};
252
+ this.cache.contactAttributes[this.buObject.mid] = this.parseResponseBody(response);
253
+ this.definition.keyField = keyFieldBackup;
254
+ }
255
+ }
256
+ /**
257
+ * helper for {@link DataFilter.retrieve}
258
+ *
259
+ * @param {DataFilterMap} metadataTypeMap -
260
+ */
261
+ static async _cacheMeasures(metadataTypeMap) {
262
+ if (this.cache.measures?.[this.buObject.mid]) {
263
+ return;
264
+ }
265
+ const subscriberFilters = Object.values(metadataTypeMap)
266
+ .filter((item) => item.derivedFromObjectTypeName === 'SubscriberAttributes')
267
+ .filter((item) => item.derivedFromObjectId);
268
+ const measureFolders = await this._getMeasureFolderIds();
269
+ if (subscriberFilters.length) {
270
+ Util.logger.info(' - Caching dependent Metadata: measure');
271
+ const response = { items: [] };
272
+ for (const folderId of measureFolders) {
273
+ const metadataMapFolder = await this.client.rest.getBulk(
274
+ 'email/v1/Measures/category/' + folderId + '/',
275
+ 250 // 250 is what the GUI is using
276
+ );
277
+ if (Object.keys(metadataMapFolder.items).length) {
278
+ response.items.push(...metadataMapFolder.items);
279
+ }
280
+ }
281
+
282
+ const keyFieldBackup = this.definition.keyField;
283
+ this.definition.keyField = 'measureID';
284
+ this.cache.measures ||= {};
285
+ this.cache.measures[this.buObject.mid] = this.parseResponseBody(response);
286
+ this.definition.keyField = keyFieldBackup;
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Retrieves all records for caching
292
+ *
293
+ * @returns {Promise.<{metadata: DataFilterMap, type: string}>} Promise of items
294
+ */
295
+ static async retrieveForCache() {
296
+ return this.retrieve(null);
297
+ }
298
+
299
+ /**
300
+ * parses retrieved Metadata before saving
301
+ *
302
+ * @param {DataFilterItem} metadata a single record
303
+ * @returns {Promise.<DataFilterItem>} parsed metadata definition
304
+ */
305
+ static async postRetrieveTasks(metadata) {
306
+ // if (metadata.derivedFromType > 4) {
307
+ // // GUI only shows types 1,2,3,4; lets mimic that here.
308
+ // // type 6 seems to be journey related. Maybe we need to change that again in the future
309
+ // return;
310
+ // }
311
+ // folder
312
+ super.setFolderPath(metadata);
313
+
314
+ // parse XML filter for further processing in JSON format
315
+ const xmlToJson = new XMLParser({
316
+ preserveOrder: false, // XML parser returns undefined if this is true
317
+ ignoreAttributes: false,
318
+ allowBooleanAttributes: true,
319
+ });
320
+ metadata.c__filterDefinition = xmlToJson.parse(
321
+ metadata.filterDefinitionXml
322
+ )?.FilterDefinition;
323
+ delete metadata.filterDefinitionXml;
324
+ switch (metadata.derivedFromType) {
325
+ case 1: {
326
+ // if (metadata.c__filterDefinition['@_Source'] === 'SubscriberAttribute') {
327
+ // if (
328
+ // metadata.derivedFromObjectId &&
329
+ // metadata.derivedFromObjectId !== '00000000-0000-0000-0000-000000000000'
330
+ // ) {
331
+ // // Lists
332
+ // try {
333
+ // metadata.r__source_list_PathName = cache.getListPathName(
334
+ // metadata.derivedFromObjectId,
335
+ // 'ObjectID'
336
+ // );
337
+ // } catch {
338
+ // Util.logger.warn(
339
+ // ` - skipping ${this.definition.type} ${metadata.key}: list ${metadata.derivedFromObjectId} not found on current or Parent BU`
340
+ // );
341
+ // }
342
+ // } else {
343
+ // // SubscriberAttributes
344
+ // // - nothing to do
345
+ // }
346
+ // }
347
+ // // SubscriberAttributes
348
+ // this._postRetrieve_resolveAttributeIds(metadata);
349
+ // delete metadata.derivedFromObjectId;
350
+ // delete metadata.derivedFromObjectName;
351
+ // delete metadata.derivedFromObjectTypeName;
352
+ // delete metadata.derivedFromType;
353
+ // delete metadata.c__filterDefinition['@_Source'];
354
+ break;
355
+ }
356
+ case 2: {
357
+ // DataExtension + XXX?
358
+ if (
359
+ metadata.c__filterDefinition['@_Source'] === 'Meta' ||
360
+ metadata.derivedFromObjectId === '00000000-0000-0000-0000-000000000000'
361
+ ) {
362
+ // TODO - weird so far not understood case of Source=Meta
363
+ // sample: <FilterDefinition Source=\"Meta\"><Include><ConditionSet Operator=\"OR\" ConditionSetName=\"Individual Filter Grouping\"><Condition ID=\"55530cec-1df4-e611-80cc-1402ec7222b4\" isParam=\"false\" isPathed=\"true\" pathAttrGroupID=\"75530cec-1df4-e611-80cc-1402ec7222b4\" Operator=\"Equal\"><Value><![CDATA[994607]]></Value></Condition><Condition ID=\"55530cec-1df4-e611-80cc-1402ec7222b4\" isParam=\"false\" isPathed=\"true\" pathAttrGroupID=\"75530cec-1df4-e611-80cc-1402ec7222b4\" Operator=\"Equal\"><Value><![CDATA[3624804]]></Value></Condition></ConditionSet></Include><Exclude></Exclude></FilterDefinition>
364
+ } else if (metadata.c__filterDefinition['@_Source'] === 'DataExtension') {
365
+ // DataExtension
366
+ try {
367
+ metadata.r__source_dataExtension_key =
368
+ this.deIdKeyMap?.[metadata.derivedFromObjectId] ||
369
+ cache.searchForField(
370
+ 'dataExtension',
371
+ metadata.derivedFromObjectId,
372
+ 'ObjectID',
373
+ 'CustomerKey'
374
+ );
375
+ delete metadata.derivedFromObjectName;
376
+ delete metadata.derivedFromObjectTypeName;
377
+ } catch {
378
+ Util.logger.debug(
379
+ ` - skipping ${this.definition.type} ${metadata.key}: dataExtension ${metadata.derivedFromObjectId} not found on BU`
380
+ );
381
+ return;
382
+ }
383
+ this._resolveFields(metadata, 'postRetrieve');
384
+
385
+ delete metadata.derivedFromObjectId;
386
+ delete metadata.derivedFromType;
387
+ delete metadata.c__filterDefinition['@_Source'];
388
+ delete metadata.c__filterDefinition['@_SourceID'];
389
+ }
390
+ break;
391
+ }
392
+ }
393
+
394
+ return metadata;
395
+ }
396
+
397
+ /**
398
+ * helper for {@link postRetrieveTasks}
399
+ *
400
+ * @param {DataFilterItem} metadata -
401
+ * @param {'postRetrieve'|'preDeploy'} mode -
402
+ * @param {object[]} [fieldCache] -
403
+ * @param {FilterConditionSet} [filter] -
404
+ * @returns {void}
405
+ */
406
+ static _resolveFields(metadata, mode, fieldCache, filter) {
407
+ if (!filter) {
408
+ return this._resolveFields(
409
+ metadata,
410
+ mode,
411
+ Object.values(this.dataExtensionFieldCache),
412
+ metadata.c__filterDefinition?.ConditionSet
413
+ );
414
+ }
415
+ if (filter.Condition) {
416
+ const conditionsArr = Array.isArray(filter.Condition)
417
+ ? filter.Condition
418
+ : [filter.Condition];
419
+ if (mode === 'postRetrieve') {
420
+ for (const condition of conditionsArr) {
421
+ this._postRetrieve_resolveFieldIdsCondition(condition, fieldCache);
422
+ }
423
+ } else if (mode === 'preDeploy') {
424
+ for (const condition of conditionsArr) {
425
+ this._preDeploy_resolveFieldNamesCondition(condition, fieldCache);
426
+ }
427
+ }
428
+ }
429
+ if (filter.ConditionSet) {
430
+ const conditionSet = Array.isArray(filter.ConditionSet)
431
+ ? filter.ConditionSet
432
+ : [filter.ConditionSet];
433
+ for (const cs of conditionSet) {
434
+ this._resolveFields(metadata, mode, fieldCache, cs);
435
+ }
436
+ }
437
+ }
438
+ /**
439
+ * helper for {@link _resolveFields}
440
+ *
441
+ * @param {FilterCondition} condition -
442
+ * @param {object[]} fieldCache -
443
+ * @returns {void}
444
+ */
445
+ static _postRetrieve_resolveFieldIdsCondition(condition, fieldCache) {
446
+ condition.r__dataExtensionField_name = fieldCache.find(
447
+ (field) => field.ObjectID === condition['@_ID']
448
+ )?.Name;
449
+ if (condition.r__dataExtensionField_name) {
450
+ delete condition['@_ID'];
451
+ }
452
+ if (['IsEmpty', 'IsNotEmpty'].includes(condition['@_Operator'])) {
453
+ delete condition.Value;
454
+ }
455
+ }
456
+ /**
457
+ * helper for {@link _resolveFields}
458
+ *
459
+ * @param {FilterCondition} condition -
460
+ * @param {object[]} fieldCache -
461
+ * @returns {void}
462
+ */
463
+ static _preDeploy_resolveFieldNamesCondition(condition, fieldCache) {
464
+ condition['@_ID'] = fieldCache.find(
465
+ (field) => field.Name === condition.r__dataExtensionField_name
466
+ )?.ObjectID;
467
+ if (condition['@_ID']) {
468
+ delete condition.r__dataExtensionField_name;
469
+ }
470
+ if (['IsEmpty', 'IsNotEmpty'].includes(condition['@_Operator'])) {
471
+ condition.Value ||= '';
472
+ } else if (condition?.Value && typeof condition.Value !== 'object') {
473
+ // allow adding cdata
474
+ // @ts-ignore
475
+ condition.Value = { cdata: condition.Value };
476
+ }
477
+ }
478
+ /**
479
+ * helper for {@link postRetrieveTasks}
480
+ *
481
+ * @param {DataFilterItem} metadata -
482
+ * @param {object} [filter] -
483
+ * @returns {void}
484
+ */
485
+ static _postRetrieve_resolveAttributeIds(metadata, filter) {
486
+ if (!filter) {
487
+ return this._postRetrieve_resolveAttributeIds(
488
+ metadata,
489
+ metadata.c__filterDefinition?.ConditionSet
490
+ );
491
+ }
492
+ const contactAttributes = this.cache.contactAttributes[this.buObject.mid];
493
+ const measures = this.cache.measures[this.buObject.mid];
494
+ const conditionsArr = Array.isArray(filter.Condition)
495
+ ? filter.Condition
496
+ : [filter.Condition];
497
+ for (const condition of conditionsArr) {
498
+ condition['@_ID'] += '';
499
+ if (condition['@_SourceType'] === 'Measure' && measures[condition['@_ID']]) {
500
+ condition.r__measure = measures[condition['@_ID']]?.name;
501
+ delete condition['@_ID'];
502
+ } else if (
503
+ condition['@_SourceType'] !== 'Measure' &&
504
+ contactAttributes[condition['@_ID']]
505
+ ) {
506
+ condition.r__contactAttribute = contactAttributes[condition['@_ID']]?.name;
507
+ delete condition['@_ID'];
508
+ }
509
+ if (['IsEmpty', 'IsNotEmpty'].includes(condition['@_Operator'])) {
510
+ delete condition.Value;
511
+ }
512
+ }
513
+ if (filter.ConditionSet) {
514
+ this._postRetrieve_resolveAttributeIds(metadata, filter.ConditionSet);
515
+ }
516
+ }
517
+
518
+ /**
519
+ * prepares a item for deployment
520
+ *
521
+ * @param {DataFilterItem} metadata a single record
522
+ * @returns {Promise.<DataFilterItem>} Promise of updated single item
523
+ */
524
+ static async preDeployTasks(metadata) {
525
+ // folder
526
+ super.setFolderId(metadata);
527
+
528
+ // disable updates to r__source_dataExtension_key
529
+ // the API and GUI prevent changes to the source data extensions.
530
+ // to avoid confusion and accidental changes, we will reset the values from cache before deployment
531
+ const normalizedKey = File.reverseFilterIllegalFilenames(
532
+ metadata[this.definition.keyField]
533
+ );
534
+ const cachedVersion = cache.getByKey(this.definition.type, normalizedKey);
535
+
536
+ // DataExtension
537
+ if (metadata.r__source_dataExtension_key) {
538
+ metadata.derivedFromObjectId = cache.searchForField(
539
+ 'dataExtension',
540
+ metadata.r__source_dataExtension_key,
541
+ 'CustomerKey',
542
+ 'ObjectID'
543
+ );
544
+ if (
545
+ cachedVersion &&
546
+ cachedVersion.derivedFromObjectId !== metadata.derivedFromObjectId
547
+ ) {
548
+ throw new Error(
549
+ `Updating r__source_dataExtension_key is not allowed. You need to delete and re-create the dataFilter to change this.`
550
+ );
551
+ }
552
+ metadata.derivedFromObjectName = cache.searchForField(
553
+ 'dataExtension',
554
+ metadata.r__source_dataExtension_key,
555
+ 'CustomerKey',
556
+ 'Name'
557
+ );
558
+ metadata.derivedFromObjectTypeName = 'DataExtension';
559
+ metadata.derivedFromType = 2;
560
+ metadata.c__filterDefinition['@_Source'] = 'DataExtension';
561
+ metadata.c__filterDefinition['@_SourceID'] = metadata.derivedFromObjectId;
562
+ this._resolveFields(metadata, 'preDeploy');
563
+
564
+ delete metadata.r__source_dataExtension_key;
565
+ }
566
+
567
+ const jsonToXml = new XMLBuilder({
568
+ preserveOrder: false, // XML Builder returns undefined if this is true
569
+ ignoreAttributes: false,
570
+ cdataPropName: 'cdata',
571
+ });
572
+ metadata.filterDefinitionXml = jsonToXml.build({
573
+ FilterDefinition: metadata.c__filterDefinition,
574
+ });
575
+ delete metadata.c__filterDefinition;
576
+ return metadata;
577
+ }
578
+
579
+ /**
580
+ * MetadataType upsert, after retrieving from target and comparing to check if create or update operation is needed.
581
+ *
582
+ * @param {MetadataTypeMap} metadataMap metadata mapped by their keyField
583
+ * @param {string} deployDir directory where deploy metadata are saved
584
+ * @param {boolean} [runUpsertSequentially] when a type has self-dependencies creates need to run one at a time and created/changed keys need to be cached to ensure following creates/updates have thoses keys available
585
+ * @returns {Promise.<MetadataTypeMap>} keyField => metadata map
586
+ */
587
+ static async upsert(metadataMap, deployDir, runUpsertSequentially = false) {
588
+ Util.logBeta(this.definition.type);
589
+ await this._cacheDeFields(metadataMap, 'deploy');
590
+ return super.upsert(metadataMap, deployDir, runUpsertSequentially);
591
+ }
592
+ /**
593
+ * Creates a single item
594
+ *
595
+ * @param {DataFilterItem} metadata a single item
596
+ * @returns {Promise.<DataFilterItem>} Promise
597
+ */
598
+ static create(metadata) {
599
+ return super.createREST(metadata, '/email/v1/filters/filterdefinition/');
600
+ }
601
+ /**
602
+ * Updates a single item
603
+ *
604
+ * @param {DataFilterItem} metadata a single item
605
+ * @returns {Promise.<DataFilterItem>} Promise
606
+ */
607
+ static update(metadata) {
608
+ return super.updateREST(
609
+ metadata,
610
+ '/email/v1/filters/filterdefinition/' + metadata[this.definition.idField]
611
+ );
612
+ }
613
+
614
+ /**
615
+ * helper to allow us to select single metadata entries via REST
616
+ *
617
+ * @private
618
+ * @param {string} key customer key
619
+ * @returns {Promise.<string>} objectId or empty string
620
+ */
621
+ static async _getObjectIdForSingleRetrieve(key) {
622
+ const name = key.startsWith('name:') ? key.slice(5) : null;
623
+ const response = await this.client.soap.retrieve(this.definition.soapType, ['ObjectID'], {
624
+ filter: {
625
+ leftOperand: name ? 'Name' : 'CustomerKey',
626
+ operator: 'equals',
627
+ rightOperand: name || key,
628
+ },
629
+ });
630
+ return response?.Results?.length ? response.Results[0].ObjectID : null;
631
+ }
632
+
633
+ /**
634
+ * Delete a metadata item from the specified business unit
635
+ *
636
+ * @param {string} key Identifier of data extension
637
+ * @returns {Promise.<boolean>} deletion success flag
638
+ */
639
+ static async deleteByKey(key) {
640
+ // delete only works with the query's object id
641
+ const objectId = key ? await this._getObjectIdForSingleRetrieve(key) : null;
642
+ if (!objectId) {
643
+ await this.deleteNotFound(key);
644
+ return false;
645
+ }
646
+ return super.deleteByKeyREST('/email/v1/filters/filterdefinition/' + objectId, key);
647
+ }
648
+ }
649
+ // Assign definition to static attributes
650
+ import MetadataTypeDefinitions from '../MetadataTypeDefinitions.js';
651
+ DataFilter.definition = MetadataTypeDefinitions.dataFilter;
652
+
653
+ export default DataFilter;
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ // const TYPE = require('../../types/mcdev.d');
4
+ import DataFilter from './DataFilter.js';
5
+
6
+ /**
7
+ * DataFilterHidden (FilterDefinitionHidden) MetadataType
8
+ *
9
+ * @augments DataFilter
10
+ */
11
+ class DataFilterHidden extends DataFilter {
12
+ static hidden = true;
13
+ }
14
+ // Assign definition to static attributes
15
+ import MetadataTypeDefinitions from '../MetadataTypeDefinitions.js';
16
+ DataFilterHidden.definition = MetadataTypeDefinitions.dataFilterHidden;
17
+
18
+ export default DataFilterHidden;