diff --git a/docs/dist/documentation.md b/docs/dist/documentation.md index 4b89a1cf6..0df8883e8 100644 --- a/docs/dist/documentation.md +++ b/docs/dist/documentation.md @@ -67,6 +67,12 @@ as this is a configuration in the EID

FilterMetadataType

Filter MetadataType

+
FilterDefinitionMetadataType
+

FilterDefinition MetadataType

+
+
FilterDefinitionHiddenFilterDefinitionHidden
+

FilterDefinitionHidden MetadataType

+
FolderMetadataType

Folder MetadataType

@@ -303,6 +309,16 @@ helper for DataExtension.#fixShared_item
skipInteraction : object

signals what to insert automatically for things usually asked via wizard

+
FilterMap : object
+
+
FilterDefinitionSOAPItemMap : object
+
+
AutomationFilterDefinitionItem : object
+

/automation/v1/filterdefinitions/ (not used)

+
+
FilterDefinitionMap : object
+

/email/v1/filters/filterdefinition/

+
AuthObject : object
SoapFilter : object
@@ -2868,16 +2884,22 @@ Filter MetadataType **Kind**: global class **Extends**: [MetadataType](#MetadataType) + +* [Filter](#Filter) ⇐ [MetadataType](#MetadataType) + * [.retrieve(retrieveDir, [_], [__], [key])](#Filter.retrieve) ⇒ Promise.<{metadata: TYPE.FilterMap, type: string}> + * [.postRetrieveTasks(metadata)](#Filter.postRetrieveTasks) ⇒ TYPE.FilterItem + * [.preDeployTasks(metadata)](#Filter.preDeployTasks) ⇒ Promise.<TYPE.FilterItem> + -### Filter.retrieve(retrieveDir, [_], [__], [key]) ⇒ Promise.<TYPE.MetadataTypeMapObj> +### Filter.retrieve(retrieveDir, [_], [__], [key]) ⇒ Promise.<{metadata: TYPE.FilterMap, type: string}> Retrieves Metadata of Filter. Endpoint /automation/v1/filters/ returns all Filters, but only with some of the fields. So it is needed to loop over Filters with the endpoint /automation/v1/filters/{id} **Kind**: static method of [Filter](#Filter) -**Returns**: Promise.<TYPE.MetadataTypeMapObj> - Promise +**Returns**: Promise.<{metadata: TYPE.FilterMap, type: string}> - Promise of items | Param | Type | Description | | --- | --- | --- | @@ -2886,6 +2908,204 @@ Filters with the endpoint /automation/v1/filters/{id} | [__] | void | unused parameter | | [key] | string | customer key of single item to retrieve | + + +### Filter.postRetrieveTasks(metadata) ⇒ TYPE.FilterItem +parses retrieved Metadata before saving + +**Kind**: static method of [Filter](#Filter) +**Returns**: TYPE.FilterItem - parsed metadata definition + +| Param | Type | Description | +| --- | --- | --- | +| metadata | TYPE.FilterItem | a single record | + + + +### Filter.preDeployTasks(metadata) ⇒ Promise.<TYPE.FilterItem> +prepares a record for deployment + +**Kind**: static method of [Filter](#Filter) +**Returns**: Promise.<TYPE.FilterItem> - Promise of updated single record + +| Param | Type | Description | +| --- | --- | --- | +| metadata | TYPE.FilterItem | a single record | + + + +## FilterDefinition ⇐ [MetadataType](#MetadataType) +FilterDefinition MetadataType + +**Kind**: global class +**Extends**: [MetadataType](#MetadataType) + +* [FilterDefinition](#FilterDefinition) ⇐ [MetadataType](#MetadataType) + * [.retrieve(retrieveDir, [_], [__], [key])](#FilterDefinition.retrieve) ⇒ Promise.<{metadata: TYPE.FilterDefinitionMap, type: string}> + * [.getFilterFolderIds([hidden])](#FilterDefinition.getFilterFolderIds) ⇒ Array.<number> + * [.getMeasureFolderIds()](#FilterDefinition.getMeasureFolderIds) ⇒ Array.<number> + * [.cacheDeFields(metadataTypeMapObj)](#FilterDefinition.cacheDeFields) + * [.cacheContactAttributes(metadataTypeMapObj)](#FilterDefinition.cacheContactAttributes) + * [.cacheMeasures(metadataTypeMapObj)](#FilterDefinition.cacheMeasures) + * [.retrieveForCache()](#FilterDefinition.retrieveForCache) ⇒ Promise.<{metadata: TYPE.FilterDefinitionMap, type: string}> + * [.postRetrieveTasks(metadata)](#FilterDefinition.postRetrieveTasks) ⇒ TYPE.FilterDefinitionItem + * [.resolveFieldIds(metadata, [fieldCache], [filter])](#FilterDefinition.resolveFieldIds) ⇒ void + * [.resolveAttributeIds(metadata, [filter])](#FilterDefinition.resolveAttributeIds) ⇒ void + * [.preDeployTasks(metadata)](#FilterDefinition.preDeployTasks) ⇒ Promise.<TYPE.FilterDefinitionItem> + * [.create(metadata)](#FilterDefinition.create) ⇒ Promise.<TYPE.FilterDefinitionItem> + * [.update(metadata)](#FilterDefinition.update) ⇒ Promise.<TYPE.FilterDefinitionItem> + + + +### FilterDefinition.retrieve(retrieveDir, [_], [__], [key]) ⇒ Promise.<{metadata: TYPE.FilterDefinitionMap, type: string}> +Retrieves all records and saves it to disk + +**Kind**: static method of [FilterDefinition](#FilterDefinition) +**Returns**: Promise.<{metadata: TYPE.FilterDefinitionMap, type: string}> - Promise of items + +| Param | Type | Description | +| --- | --- | --- | +| retrieveDir | string | Directory where retrieved metadata directory will be saved | +| [_] | void | unused parameter | +| [__] | void | unused parameter | +| [key] | string | customer key of single item to retrieve | + + + +### FilterDefinition.getFilterFolderIds([hidden]) ⇒ Array.<number> +helper for [retrieve](#FilterDefinition.retrieve) + +**Kind**: static method of [FilterDefinition](#FilterDefinition) +**Returns**: Array.<number> - Array of folder IDs + +| Param | Type | Default | Description | +| --- | --- | --- | --- | +| [hidden] | boolean | false | used to filter out hidden or non-hidden filterDefinitions | + + + +### FilterDefinition.getMeasureFolderIds() ⇒ Array.<number> +helper for [retrieve](#FilterDefinition.retrieve) + +**Kind**: static method of [FilterDefinition](#FilterDefinition) +**Returns**: Array.<number> - Array of folder IDs + + +### FilterDefinition.cacheDeFields(metadataTypeMapObj) +**Kind**: static method of [FilterDefinition](#FilterDefinition) + +| Param | Type | Description | +| --- | --- | --- | +| metadataTypeMapObj | TYPE.MultiMetadataTypeMap | - | + + + +### FilterDefinition.cacheContactAttributes(metadataTypeMapObj) +**Kind**: static method of [FilterDefinition](#FilterDefinition) + +| Param | Type | Description | +| --- | --- | --- | +| metadataTypeMapObj | TYPE.MultiMetadataTypeMap | - | + + + +### FilterDefinition.cacheMeasures(metadataTypeMapObj) +**Kind**: static method of [FilterDefinition](#FilterDefinition) + +| Param | Type | Description | +| --- | --- | --- | +| metadataTypeMapObj | TYPE.MultiMetadataTypeMap | - | + + + +### FilterDefinition.retrieveForCache() ⇒ Promise.<{metadata: TYPE.FilterDefinitionMap, type: string}> +Retrieves all records for caching + +**Kind**: static method of [FilterDefinition](#FilterDefinition) +**Returns**: Promise.<{metadata: TYPE.FilterDefinitionMap, type: string}> - Promise of items + + +### FilterDefinition.postRetrieveTasks(metadata) ⇒ TYPE.FilterDefinitionItem +parses retrieved Metadata before saving + +**Kind**: static method of [FilterDefinition](#FilterDefinition) +**Returns**: TYPE.FilterDefinitionItem - parsed metadata definition + +| Param | Type | Description | +| --- | --- | --- | +| metadata | TYPE.FilterDefinitionItem | a single record | + + + +### FilterDefinition.resolveFieldIds(metadata, [fieldCache], [filter]) ⇒ void +**Kind**: static method of [FilterDefinition](#FilterDefinition) + +| Param | Type | Description | +| --- | --- | --- | +| metadata | TYPE.FilterDefinitionItem | - | +| [fieldCache] | Array.<object> | - | +| [filter] | object | - | + + + +### FilterDefinition.resolveAttributeIds(metadata, [filter]) ⇒ void +**Kind**: static method of [FilterDefinition](#FilterDefinition) + +| Param | Type | Description | +| --- | --- | --- | +| metadata | TYPE.FilterDefinitionItem | - | +| [filter] | object | - | + + + +### FilterDefinition.preDeployTasks(metadata) ⇒ Promise.<TYPE.FilterDefinitionItem> +prepares a item for deployment + +**Kind**: static method of [FilterDefinition](#FilterDefinition) +**Returns**: Promise.<TYPE.FilterDefinitionItem> - Promise of updated single item + +| Param | Type | Description | +| --- | --- | --- | +| metadata | TYPE.FilterDefinitionItem | a single record | + + + +### FilterDefinition.create(metadata) ⇒ Promise.<TYPE.FilterDefinitionItem> +Creates a single item + +**Kind**: static method of [FilterDefinition](#FilterDefinition) +**Returns**: Promise.<TYPE.FilterDefinitionItem> - Promise + +| Param | Type | Description | +| --- | --- | --- | +| metadata | TYPE.FilterDefinitionItem | a single item | + + + +### FilterDefinition.update(metadata) ⇒ Promise.<TYPE.FilterDefinitionItem> +Updates a single item + +**Kind**: static method of [FilterDefinition](#FilterDefinition) +**Returns**: Promise.<TYPE.FilterDefinitionItem> - Promise + +| Param | Type | Description | +| --- | --- | --- | +| metadata | TYPE.FilterDefinitionItem | a single item | + + + +## FilterDefinitionHidden ⇐ [FilterDefinitionHidden](#FilterDefinitionHidden) +FilterDefinitionHidden MetadataType + +**Kind**: global class +**Extends**: [FilterDefinitionHidden](#FilterDefinitionHidden) + + +### FilterDefinitionHidden.getFilterFolderIds() ⇒ Array.<number> +helper for [retrieve](#FilterDefinition.retrieve) + +**Kind**: static method of [FilterDefinitionHidden](#FilterDefinitionHidden) +**Returns**: Array.<number> - Array of folder IDs ## Folder ⇐ [MetadataType](#MetadataType) @@ -9304,6 +9524,124 @@ signals what to insert automatically for things usually asked via wizard | credentialName | string | how you would like the credential to be named | | gitRemoteUrl | string | URL of Git remote server | + + +## FilterMap : object +**Kind**: global typedef +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| categoryId | number | folder id | +| [createdDate] | string | - | +| customerKey | string | key | +| destinationObjectId | string | DE/List ID | +| destinationTypeId | 1 \| 2 \| 3 \| 4 | 1:SubscriberList, 2:DataExtension, 3:GroupWizard, 4:BehavioralData | +| filterActivityId | string | ? | +| filterDefinitionId | string | ObjectID of filterDefinition | +| modifiedDate | string | - | +| name | string | name | +| sourceObjectId | string | DE/List ID | +| sourceTypeId | 1 \| 2 \| 3 \| 4 | 1:SubscriberList, 2:DataExtension, 3:GroupWizard, 4:BehavioralData | +| statusId | number | ? | + + + +## FilterDefinitionSOAPItemMap : object +**Kind**: global typedef +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| ObjectID | string | id | +| CustomerKey | string | key | +| [DataFilter] | object | most relevant part that defines the filter | +| DataFilter.LeftOperand | object | - | +| DataFilter.LeftOperand.Property | string | - | +| DataFilter.LeftOperand.SimpleOperator | string | - | +| DataFilter.LeftOperand.Value | string | - | +| DataFilter.LogicalOperator | string | - | +| [DataFilter.RightOperand] | object | - | +| DataFilter.RightOperand.Property | string | - | +| DataFilter.RightOperand.SimpleOperator | string | - | +| DataFilter.RightOperand.Value | string | - | +| Name | string | name | +| Description | string | - | +| [ObjectState] | string | returned from SOAP API; used to return error messages | + + + +## AutomationFilterDefinitionItem : object +/automation/v1/filterdefinitions/ (not used) + +**Kind**: global typedef +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| id | string | object id | +| key | string | external key | +| createdDate | string | - | +| createdBy | number | user id | +| createdName | string | - | +| [description] | string | (omitted by API if empty) | +| modifiedDate | string | - | +| modifiedBy | number | user id | +| modifiedName | string | - | +| name | string | name | +| categoryId | string | folder id | +| filterDefinitionXml | string | from REST API defines the filter in XML form | +| derivedFromType | 1 \| 2 | 1:list/profile attributes/measures, 2: dataExtension | +| isSendable | boolean | ? | +| [soap__DataFilter] | object | copied from SOAP API, defines the filter in readable form | +| soap__DataFilter.LeftOperand | object | - | +| soap__DataFilter.LeftOperand.Property | string | - | +| soap__DataFilter.LeftOperand.SimpleOperator | string | - | +| soap__DataFilter.LeftOperand.Value | string | - | +| soap__DataFilter.LogicalOperator | string | - | +| [soap__DataFilter.RightOperand] | object | - | +| soap__DataFilter.RightOperand.Property | string | - | +| soap__DataFilter.RightOperand.SimpleOperator | string | - | +| soap__DataFilter.RightOperand.Value | string | - | + + + +## FilterDefinitionMap : object +/email/v1/filters/filterdefinition/ + +**Kind**: global typedef +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| id | string | object id | +| key | string | external key | +| createdDate | string | date | +| createdBy | number | user id | +| createdName | string | name | +| [description] | string | (omitted by API if empty) | +| lastUpdated | string | date | +| lastUpdatedBy | number | user id | +| lastUpdatedName | string | name | +| name | string | name | +| categoryId | string | folder id | +| filterDefinitionXml | string | from REST API defines the filter in XML form | +| derivedFromType | 1 \| 2 | 1:list/profile attributes/measures, 2: dataExtension | +| derivedFromObjectId | string | Id of DataExtension - present if derivedFromType=2 | +| derivedFromObjectTypeName | 'DataExtension' \| 'SubscriberAttributes' | - | +| [derivedFromObjectName] | string | name of DataExtension | +| isSendable | boolean | ? | +| [soap__DataFilter] | object | copied from SOAP API, defines the filter in readable form | +| soap__DataFilter.LeftOperand | object | - | +| soap__DataFilter.LeftOperand.Property | string | - | +| soap__DataFilter.LeftOperand.SimpleOperator | string | - | +| soap__DataFilter.LeftOperand.Value | string | - | +| soap__DataFilter.LogicalOperator | string | - | +| [soap__DataFilter.RightOperand] | object | - | +| soap__DataFilter.RightOperand.Property | string | - | +| soap__DataFilter.RightOperand.SimpleOperator | string | - | +| soap__DataFilter.RightOperand.Value | string | - | + ## AuthObject : object diff --git a/lib/MetadataTypeDefinitions.js b/lib/MetadataTypeDefinitions.js index e6c0e9331..68da505fa 100644 --- a/lib/MetadataTypeDefinitions.js +++ b/lib/MetadataTypeDefinitions.js @@ -22,6 +22,8 @@ const MetadataTypeDefinitions = { fileLocation: require('./metadataTypes/definitions/FileLocation.definition'), fileTransfer: require('./metadataTypes/definitions/FileTransfer.definition'), filter: require('./metadataTypes/definitions/Filter.definition'), + filterDefinition: require('./metadataTypes/definitions/FilterDefinition.definition'), + filterDefinitionHidden: require('./metadataTypes/definitions/FilterDefinitionHidden.definition'), folder: require('./metadataTypes/definitions/Folder.definition'), importFile: require('./metadataTypes/definitions/ImportFile.definition'), journey: require('./metadataTypes/definitions/Journey.definition'), diff --git a/lib/MetadataTypeInfo.js b/lib/MetadataTypeInfo.js index 40d7bafce..6f4112bc4 100644 --- a/lib/MetadataTypeInfo.js +++ b/lib/MetadataTypeInfo.js @@ -22,6 +22,8 @@ const MetadataTypeInfo = { fileLocation: require('./metadataTypes/FileLocation'), fileTransfer: require('./metadataTypes/FileTransfer'), filter: require('./metadataTypes/Filter'), + filterDefinition: require('./metadataTypes/FilterDefinition'), + filterDefinitionHidden: require('./metadataTypes/FilterDefinitionHidden'), folder: require('./metadataTypes/Folder'), importFile: require('./metadataTypes/ImportFile'), journey: require('./metadataTypes/Journey'), diff --git a/lib/metadataTypes/DataExtension.js b/lib/metadataTypes/DataExtension.js index 07dfe3637..43c8be922 100644 --- a/lib/metadataTypes/DataExtension.js +++ b/lib/metadataTypes/DataExtension.js @@ -1341,7 +1341,7 @@ class DataExtension extends MetadataType { * @returns {Promise.<{metadata: TYPE.DataExtensionMap, type: string}>} Promise */ static async retrieveForCache() { - return this.retrieve(null, ['ObjectID', 'CustomerKey', 'Name'], this.buObject, null, null); + return this.retrieve(null, ['ObjectID', 'CustomerKey', 'Name']); } /** * Retrieves dataExtension metadata in template format. diff --git a/lib/metadataTypes/Filter.js b/lib/metadataTypes/Filter.js index dafa8691f..214a6c3c6 100644 --- a/lib/metadataTypes/Filter.js +++ b/lib/metadataTypes/Filter.js @@ -2,6 +2,15 @@ const TYPE = require('../../types/mcdev.d'); const MetadataType = require('./MetadataType'); +const Util = require('../util/util'); +const cache = require('../util/cache'); + +const dataTypes = { + 1: 'List', + 2: 'DataExtension', + 3: 'Group Wizard', + 4: 'Behavioral Data', +}; /** * Filter MetadataType @@ -19,11 +28,179 @@ class Filter extends MetadataType { * @param {void} [_] unused parameter * @param {void} [__] unused parameter * @param {string} [key] customer key of single item to retrieve - * @returns {Promise.} Promise + * @returns {Promise.<{metadata: TYPE.FilterMap, type: string}>} Promise of items */ static async retrieve(retrieveDir, _, __, key) { return super.retrieveREST(retrieveDir, '/automation/v1/filters/', null, key); } + /** + * parses retrieved Metadata before saving + * + * @param {TYPE.FilterItem} metadata a single record + * @returns {TYPE.FilterItem} parsed metadata definition + */ + static postRetrieveTasks(metadata) { + // folder + this.setFolderPath(metadata); + + try { + // filterDefinition + metadata.r__filterDefinition_CustomerKey = cache.searchForField( + 'filterDefinition', + metadata.filterDefinitionId, + 'id', + 'key' + ); + delete metadata.filterDefinitionId; + } catch { + try { + // filterDefinition + metadata.r__filterDefinition_CustomerKey = cache.searchForField( + 'filterDefinitionHidden', + metadata.filterDefinitionId, + 'id', + 'key' + ); + delete metadata.filterDefinitionId; + } catch { + // ignore + } + } + try { + // source + if (metadata.sourceTypeId === 1) { + // list + } else if (metadata.sourceTypeId === 2) { + // dataExtension + metadata.r__source_dataExtension_CustomerKey = cache.searchForField( + 'dataExtension', + metadata.sourceObjectId, + 'ObjectID', + 'CustomerKey' + ); + delete metadata.sourceObjectId; + delete metadata.sourceTypeId; + } else { + Util.logger.warn( + ` - Filter '${metadata.name}' (${ + metadata.customerKey + }): Unsupported source type ${metadata.sourceTypeId}=${ + dataTypes[metadata.sourceTypeId] + }` + ); + } + } catch (ex) { + Util.logger.warn( + ` - filter '${metadata.name}' (${metadata.customerKey}): Destination not found (${ex.message})` + ); + } + try { + // target + if (metadata.destinationTypeId === 1) { + // list + } else if (metadata.destinationTypeId === 2) { + // dataExtension + metadata.r__destination_dataExtension_CustomerKey = cache.searchForField( + 'dataExtension', + metadata.destinationObjectId, + 'ObjectID', + 'CustomerKey' + ); + delete metadata.destinationObjectId; + delete metadata.destinationTypeId; + } else { + Util.logger.warn( + ` - filter '${metadata.name}' (${ + metadata.customerKey + }): Unsupported destination type ${metadata.destinationTypeId}=${ + dataTypes[metadata.destinationTypeId] + }` + ); + } + } catch (ex) { + Util.logger.warn( + ` - filter '${metadata.name}' (${metadata.customerKey}): Source not found (${ex.message})` + ); + } + return metadata; + } + /** + * prepares a record for deployment + * + * @param {TYPE.FilterItem} metadata a single record + * @returns {Promise.} Promise of updated single record + */ + static async preDeployTasks(metadata) { + // folder + if (metadata.r__folder_Path) { + metadata.categoryId = cache.searchForField( + 'folder', + metadata.r__folder_Path, + 'Path', + 'ID' + ); + delete metadata.r__folder_Path; + } + + // filterDefinition + if (metadata.r__filterDefinition_CustomerKey) { + metadata.filterDefinitionId = cache.searchForField( + 'filterDefinition', + metadata.r__filterDefinition_CustomerKey, + 'CustomerKey', + 'ObjectID' + ); + delete metadata.r__filterDefinition_CustomerKey; + } + + // source + if (metadata.sourceTypeId === 1) { + // list + } else if (metadata.r__source_dataExtension_CustomerKey) { + // dataExtension + metadata.sourceObjectId = cache.searchForField( + 'dataExtension', + metadata.r__source_dataExtension_CustomerKey, + 'CustomerKey', + 'ObjectID' + ); + metadata.sourceTypeId = 2; + delete metadata.r__source_dataExtension_CustomerKey; + } else { + // assume the type id is still in the metadata + throw new Error( + ` - Filter '${metadata.name}' (${metadata.customerKey}): Unsupported source type ${ + metadata.sourceTypeId + }=${dataTypes[metadata.sourceTypeId]}` + ); + } + + // target + if (metadata.destinationTypeId === 1) { + // list + } else if (metadata.r__destination_dataExtension_CustomerKey) { + // dataExtension + metadata.destinationObjectId = cache.searchForField( + 'dataExtension', + metadata.r__destination_dataExtension_CustomerKey, + 'CustomerKey', + 'ObjectID' + ); + metadata.destinationTypeId = 2; + delete metadata.r__destination_dataExtension_CustomerKey; + } else { + // assume the type id is still in the metadata + throw new Error( + ` - Filter '${metadata.name}' (${ + metadata.customerKey + }): Unsupported destination type ${metadata.destinationTypeId}=${ + dataTypes[metadata.destinationTypeId] + }` + ); + } + + return metadata; + } } // Assign definition to static attributes diff --git a/lib/metadataTypes/FilterDefinition.js b/lib/metadataTypes/FilterDefinition.js new file mode 100644 index 000000000..6c480fed2 --- /dev/null +++ b/lib/metadataTypes/FilterDefinition.js @@ -0,0 +1,523 @@ +'use strict'; + +const TYPE = require('../../types/mcdev.d'); +const MetadataType = require('./MetadataType'); +const DataExtensionField = require('./DataExtensionField'); +const Folder = require('./Folder'); +const Util = require('../util/util'); +const cache = require('../util/cache'); +const { XMLBuilder, XMLParser } = require('fast-xml-parser'); + +/** + * FilterDefinition MetadataType + * + * @augments MetadataType + */ +class FilterDefinition extends MetadataType { + static cache = {}; // type internal cache for various things + /** + * Retrieves all records and saves it to disk + * + * @param {string} retrieveDir Directory where retrieved metadata directory will be saved + * @param {void} [_] unused parameter + * @param {void} [__] unused parameter + * @param {string} [key] customer key of single item to retrieve + * @returns {Promise.<{metadata: TYPE.FilterDefinitionMap, type: string}>} Promise of items + */ + static async retrieve(retrieveDir, _, __, key) { + const filterFolders = await this.getFilterFolderIds(); + + const metadataTypeMapObj = { metadata: {}, type: this.definition.type }; + for (const folderId of filterFolders) { + const metadataMapFolder = await super.retrieveREST( + null, + 'email/v1/filters/filterdefinition/category/' + + folderId + + '?derivedFromType=1,2,3,4&', + null, + key + ); + if (Object.keys(metadataMapFolder.metadata).length) { + metadataTypeMapObj.metadata = { + ...metadataTypeMapObj.metadata, + ...metadataMapFolder.metadata, + }; + if (key) { + // if key was found we can stop checking other folders + break; + } + } + } + // console.log('metadataMap', metadataMap); + + for (const item of Object.values(metadataTypeMapObj.metadata)) { + // description is not returned when emptyg + item.description ||= ''; + } + if (retrieveDir) { + // custom dataExtensionField caching + await this.cacheDeFields(metadataTypeMapObj); + await this.cacheContactAttributes(metadataTypeMapObj); + await this.cacheMeasures(metadataTypeMapObj); + + const savedMetadata = await this.saveResults(metadataTypeMapObj.metadata, retrieveDir); + Util.logger.info( + `Downloaded: ${this.definition.type} (${Object.keys(savedMetadata).length})` + + Util.getKeysString(key) + ); + } + + return metadataTypeMapObj; + } + /** + * helper for {@link FilterDefinition.retrieve} + * + * @param {boolean} [hidden] used to filter out hidden or non-hidden filterDefinitions + * @returns {number[]} Array of folder IDs + */ + static async getFilterFolderIds(hidden = false) { + const fromCache = + this.cache.folderFilter || cache.getCache().folder + ? Object.values(this.cache.folderFilter || cache.getCache().folder) + .filter((item) => item.ContentType === 'filterdefinition') + .filter( + (item) => + (!hidden && item.Path.startsWith('Data Filters')) || + (hidden && !item.Path.startsWith('Data Filters')) + ) // only retrieve from Data Filters folder + .map((item) => item.ID) + : []; + if (fromCache.length) { + return fromCache; + } + + const subTypeArr = ['hidden', 'filterdefinition']; + Util.logger.info(` - Caching dependent Metadata: folder`); + Util.logSubtypes(subTypeArr); + + Folder.client = this.client; + Folder.buObject = this.buObject; + Folder.properties = this.properties; + this.cache.folderFilter = (await Folder.retrieveForCache(null, subTypeArr)).metadata; + return this.getFilterFolderIds(hidden); + } + /** + * helper for {@link FilterDefinition.retrieve} + * + * @returns {number[]} Array of folder IDs + */ + static async getMeasureFolderIds() { + const fromCache = + this.cache.folderMeasure?.[this.buObject.mid] || cache.getCache().folder + ? Object.values( + this.cache.folderMeasure?.[this.buObject.mid] || cache.getCache().folder + ) + .filter((item) => item.ContentType === 'measure') + .map((item) => item.ID) + : []; + if (fromCache.length) { + return fromCache; + } + + const subTypeArr = ['measure']; + Util.logger.info(` - Caching dependent Metadata: folder`); + Util.logSubtypes(subTypeArr); + + Folder.client = this.client; + Folder.buObject = this.buObject; + Folder.properties = this.properties; + this.cache.folderMeasure ||= {}; + this.cache.folderMeasure[this.buObject.mid] = ( + await Folder.retrieveForCache(null, subTypeArr) + ).metadata; + return this.getMeasureFolderIds(); + } + + /** + * + * @param {TYPE.MultiMetadataTypeMap} metadataTypeMapObj - + */ + static async cacheDeFields(metadataTypeMapObj) { + const deKeys = Object.values(metadataTypeMapObj.metadata) + .filter((item) => item.derivedFromObjectTypeName === 'DataExtension') + .filter((item) => item.derivedFromObjectId) + .map((item) => { + try { + const deKey = cache.searchForField( + 'dataExtension', + item.derivedFromObjectId, + 'ObjectID', + 'CustomerKey' + ); + if (deKey) { + this.deIdKeyMap ||= {}; + this.deIdKeyMap[item.derivedFromObjectId] = deKey; + return deKey; + } + } catch { + return null; + } + }) + .filter(Boolean); + if (deKeys.length) { + Util.logger.info(' - Caching dependent Metadata: dataExtensionField'); + // only proceed with the download if we have dataExtension keys + const fieldOptions = {}; + for (const deKey of deKeys) { + fieldOptions.filter = fieldOptions.filter + ? { + leftOperand: { + leftOperand: 'DataExtension.CustomerKey', + operator: 'equals', + rightOperand: deKey, + }, + operator: 'OR', + rightOperand: fieldOptions.filter, + } + : { + leftOperand: 'DataExtension.CustomerKey', + operator: 'equals', + rightOperand: deKey, + }; + } + DataExtensionField.buObject = this.buObject; + DataExtensionField.client = this.client; + DataExtensionField.properties = this.properties; + this.dataExtensionFieldCache = ( + await DataExtensionField.retrieveForCache(fieldOptions, ['Name', 'ObjectID']) + ).metadata; + } + } + /** + * + * @param {TYPE.MultiMetadataTypeMap} metadataTypeMapObj - + */ + static async cacheContactAttributes(metadataTypeMapObj) { + if (this.cache.contactAttributes?.[this.buObject.mid]) { + return; + } + const subscriberFilters = Object.values(metadataTypeMapObj.metadata) + .filter((item) => item.derivedFromObjectTypeName === 'SubscriberAttributes') + .filter((item) => item.derivedFromObjectId); + if (subscriberFilters.length) { + Util.logger.info(' - Caching dependent Metadata: contactAttributes'); + const response = await this.client.rest.get('/email/v1/Contacts/Attributes/'); + const keyFieldBackup = this.definition.keyField; + this.definition.keyField = 'id'; + this.cache.contactAttributes ||= {}; + this.cache.contactAttributes[this.buObject.mid] = this.parseResponseBody(response); + this.definition.keyField = keyFieldBackup; + } + } + /** + * + * @param {TYPE.MultiMetadataTypeMap} metadataTypeMapObj - + */ + static async cacheMeasures(metadataTypeMapObj) { + if (this.cache.measures?.[this.buObject.mid]) { + return; + } + const subscriberFilters = Object.values(metadataTypeMapObj.metadata) + .filter((item) => item.derivedFromObjectTypeName === 'SubscriberAttributes') + .filter((item) => item.derivedFromObjectId); + const measureFolders = await this.getMeasureFolderIds(); + if (subscriberFilters.length) { + Util.logger.info(' - Caching dependent Metadata: measure'); + const response = { items: [] }; + for (const folderId of measureFolders) { + const metadataMapFolder = await this.client.rest.getBulk( + 'email/v1/Measures/category/' + folderId + '/', + 250 // 250 is what the GUI is using + ); + if (Object.keys(metadataMapFolder.items).length) { + response.items.push(...metadataMapFolder.items); + } + } + + const keyFieldBackup = this.definition.keyField; + this.definition.keyField = 'measureID'; + this.cache.measures ||= {}; + this.cache.measures[this.buObject.mid] = this.parseResponseBody(response); + this.definition.keyField = keyFieldBackup; + } + } + + /** + * Retrieves all records for caching + * + * @returns {Promise.<{metadata: TYPE.FilterDefinitionMap, type: string}>} Promise of items + */ + static async retrieveForCache() { + return this.retrieve(null); + } + + /** + * parses retrieved Metadata before saving + * + * @param {TYPE.FilterDefinitionItem} metadata a single record + * @returns {TYPE.FilterDefinitionItem} parsed metadata definition + */ + static async postRetrieveTasks(metadata) { + if (metadata.derivedFromType > 4) { + // GUI only shows types 1,2,3,4; lets mimic that here. + // type 6 seems to be journey related. Maybe we need to change that again in the future + return; + } + // folder + this.setFolderPath(metadata); + + // parse XML filter for further processing in JSON format + const xmlToJson = new XMLParser({ ignoreAttributes: false }); + metadata.c__filterDefinition = xmlToJson.parse( + metadata.filterDefinitionXml + )?.FilterDefinition; + delete metadata.filterDefinitionXml; + + switch (metadata.derivedFromType) { + case 1: { + if (metadata.c__filterDefinition['@_Source'] === 'SubscriberAttribute') { + if ( + metadata.derivedFromObjectId && + metadata.derivedFromObjectId !== '00000000-0000-0000-0000-000000000000' + ) { + // Lists + try { + metadata.r__source_list_PathName = cache.getListPathName( + metadata.derivedFromObjectId, + 'ObjectID' + ); + } catch { + Util.logger.warn( + ` - skipping ${this.definition.type} ${metadata.key}: list ${metadata.derivedFromObjectId} not found on current or Parent BU` + ); + // return; + } + } else { + // SubscriberAttributes + // - nothing to do + } + } + + break; + } + case 2: { + // DataExtension + XXX? + if ( + metadata.c__filterDefinition['@_Source'] === 'Meta' || + metadata.derivedFromObjectId === '00000000-0000-0000-0000-000000000000' + ) { + // TODO - weird so far not understood case of Source=Meta + // sample: + } else if (metadata.c__filterDefinition['@_Source'] === 'DataExtension') { + // DataExtension + try { + metadata.r__source_dataExtension_CustomerKey = + this.deIdKeyMap?.[metadata.derivedFromObjectId] || + cache.searchForField( + 'dataExtension', + metadata.derivedFromObjectId, + 'ObjectID', + 'CustomerKey' + ); + } catch { + Util.logger.debug( + ` - skipping ${this.definition.type} ${metadata.key}: dataExtension ${metadata.derivedFromObjectId} not found on BU` + ); + return; + } + } + break; + } + case 3: { + // TODO + break; + } + case 4: { + // TODO + break; + } + case 5: { + // TODO + break; + } + case 6: { + // TODO + break; + } + } + + // map Condition ID to fields ID + switch (metadata.derivedFromType) { + case 1: { + // SubscriberAttributes + this.resolveAttributeIds(metadata); + delete metadata.derivedFromObjectId; + delete metadata.derivedFromType; + delete metadata.c__filterDefinition['@_Source']; + break; + } + case 2: { + if (metadata.c__filterDefinition['@_Source'] === 'Meta') { + // TODO - weird so far not understood case of Source=Meta + // sample: + } else if (metadata.c__filterDefinition['@_Source'] === 'DataExtension') { + // DataExtension + this.resolveFieldIds(metadata); + delete metadata.derivedFromObjectId; + delete metadata.derivedFromType; + delete metadata.c__filterDefinition['@_Source']; + delete metadata.c__filterDefinition['@_SourceID']; + } + break; + } + case 3: { + // TODO + break; + } + case 4: { + // TODO + break; + } + case 5: { + // TODO + break; + } + case 6: { + // TODO + break; + } + } + return metadata; + } + + /** + * + * @param {TYPE.FilterDefinitionItem} metadata - + * @param {object[]} [fieldCache] - + * @param {object} [filter] - + * @returns {void} + */ + static resolveFieldIds(metadata, fieldCache, filter) { + if (!filter) { + return this.resolveFieldIds( + metadata, + Object.values(this.dataExtensionFieldCache), + metadata.c__filterDefinition?.ConditionSet + ); + } + const conditionsArr = Array.isArray(filter.Condition) + ? filter.Condition + : [filter.Condition]; + for (const condition of conditionsArr) { + condition.r__dataExtensionField = fieldCache.find( + (field) => field.ObjectID === condition['@_ID'] + )?.Name; + delete condition['@_ID']; + if (['IsEmpty', 'IsNotEmpty'].includes(condition['@_Operator'])) { + delete condition.Value; + } + } + if (filter.ConditionSet) { + this.resolveFieldIds(metadata, fieldCache, filter.ConditionSet); + } + } + /** + * + * @param {TYPE.FilterDefinitionItem} metadata - + * @param {object} [filter] - + * @returns {void} + */ + static resolveAttributeIds(metadata, filter) { + if (!filter) { + return this.resolveAttributeIds(metadata, metadata.c__filterDefinition?.ConditionSet); + } + const contactAttributes = this.cache.contactAttributes[this.buObject.mid]; + const measures = this.cache.measures[this.buObject.mid]; + const conditionsArr = Array.isArray(filter.Condition) + ? filter.Condition + : [filter.Condition]; + for (const condition of conditionsArr) { + condition['@_ID'] += ''; + if (condition['@_SourceType'] === 'Measure' && measures[condition['@_ID']]) { + condition.r__measure = measures[condition['@_ID']]?.name; + delete condition['@_ID']; + } else if ( + condition['@_SourceType'] !== 'Measure' && + contactAttributes[condition['@_ID']] + ) { + condition.r__contactAttribute = contactAttributes[condition['@_ID']]?.name; + delete condition['@_ID']; + } + if (['IsEmpty', 'IsNotEmpty'].includes(condition['@_Operator'])) { + delete condition.Value; + } + } + if (filter.ConditionSet) { + this.resolveAttributeIds(metadata, filter.ConditionSet); + } + } + + /** + * prepares a item for deployment + * + * @param {TYPE.FilterDefinitionItem} metadata a single record + * @returns {Promise.} Promise of updated single item + */ + static async preDeployTasks(metadata) { + // folder + super.setFolderId(metadata); + + if (metadata.derivedFromObjectTypeName === 'SubscriberAttributes') { + // SubscriberAttributes + metadata.derivedFromType = 1; + metadata.derivedFromObjectId = '00000000-0000-0000-0000-000000000000'; + } else { + // DataExtension + metadata.derivedFromType = 2; + + if (metadata.r__dataExtension_CustomerKey) { + metadata.derivedFromObjectId = cache.searchForField( + 'dataExtension', + metadata.r__dataExtension_CustomerKey, + 'CustomerKey', + 'ObjectID' + ); + delete metadata.r__dataExtension_CustomerKey; + } + } + + const jsonToXml = new XMLBuilder({ ignoreAttributes: false }); + metadata.filterDefinitionXml = jsonToXml.build(metadata.c__filterDefinition); + delete metadata.c__filterDefinition; + delete metadata.c__soap_DataFilter; + + return metadata; + } + /** + * Creates a single item + * + * @param {TYPE.FilterDefinitionItem} metadata a single item + * @returns {Promise.} Promise + */ + static create(metadata) { + // TODO test the create + return super.createREST(metadata, '/email/v1/filters/filterdefinition/'); + } + /** + * Updates a single item + * + * @param {TYPE.FilterDefinitionItem} metadata a single item + * @returns {Promise.} Promise + */ + static update(metadata) { + // TODO test the update + return super.updateREST( + metadata, + '/email/v1/filters/filterdefinition/' + metadata[this.definition.idField] + ); + } +} +// Assign definition to static attributes +FilterDefinition.definition = require('../MetadataTypeDefinitions').filterDefinition; + +module.exports = FilterDefinition; diff --git a/lib/metadataTypes/FilterDefinitionHidden.js b/lib/metadataTypes/FilterDefinitionHidden.js new file mode 100644 index 000000000..634fb9a44 --- /dev/null +++ b/lib/metadataTypes/FilterDefinitionHidden.js @@ -0,0 +1,24 @@ +'use strict'; + +// const TYPE = require('../../types/mcdev.d'); +const FilterDefinition = require('./FilterDefinition'); + +/** + * FilterDefinitionHidden MetadataType + * + * @augments FilterDefinitionHidden + */ +class FilterDefinitionHidden extends FilterDefinition { + /** + * helper for {@link FilterDefinition.retrieve} + * + * @returns {number[]} Array of folder IDs + */ + static async getFilterFolderIds() { + return super.getFilterFolderIds(true); + } +} +// Assign definition to static attributes +FilterDefinitionHidden.definition = require('../MetadataTypeDefinitions').filterDefinitionHidden; + +module.exports = FilterDefinitionHidden; diff --git a/lib/metadataTypes/Verification.js b/lib/metadataTypes/Verification.js index 93ce29fd0..6e967521b 100644 --- a/lib/metadataTypes/Verification.js +++ b/lib/metadataTypes/Verification.js @@ -189,6 +189,10 @@ class Verification extends MetadataType { 'ObjectID' ); delete metadata.r__dataExtension_CustomerKey; + metadata.notificationEmailAddress = Array.isArray(metadata.notificationEmailAddress) + ? metadata.notificationEmailAddress.map((item) => item.trim()).join(',') + : Array.isArray(metadata.notificationEmailAddress); + return metadata; } /** @@ -211,6 +215,9 @@ class Verification extends MetadataType { ` - ${this.definition.type} ${metadata[this.definition.keyField]}: ${ex.message}` ); } + metadata.notificationEmailAddress = metadata.notificationEmailAddress + .split(',') + .map((item) => item.trim()); return metadata; } /** diff --git a/lib/metadataTypes/definitions/Automation.definition.js b/lib/metadataTypes/definitions/Automation.definition.js index 656bcf659..d6054bb9f 100644 --- a/lib/metadataTypes/definitions/Automation.definition.js +++ b/lib/metadataTypes/definitions/Automation.definition.js @@ -27,6 +27,7 @@ module.exports = { 'dataExtract', 'emailSend', 'fileTransfer', + 'filter', 'folder-automations', 'importFile', 'query', diff --git a/lib/metadataTypes/definitions/Filter.definition.js b/lib/metadataTypes/definitions/Filter.definition.js index ef6499104..5fbea7be4 100644 --- a/lib/metadataTypes/definitions/Filter.definition.js +++ b/lib/metadataTypes/definitions/Filter.definition.js @@ -1,11 +1,20 @@ module.exports = { bodyIteratorField: 'items', - dependencies: [], + dependencies: [ + 'filterDefinition', + 'filterDefinitionHidden', + 'list', + 'dataExtension', + 'folder-filteractivity', + 'folder-hidden', + ], hasExtended: false, - idField: 'id', + idField: 'filterActivityId', keyIsFixed: null, keyField: 'customerKey', nameField: 'name', + folderType: 'filteractivity', + folderIdField: 'categoryId', createdDateField: 'createdDate', createdNameField: null, lastmodDateField: 'modifiedDate', @@ -14,56 +23,58 @@ module.exports = { maxKeyLength: 36, // confirmed max length type: 'filter', typeDescription: - 'BETA: Part of how filtered Data Extensions are created. Depends on type "FilterDefinitions".', - typeRetrieveByDefault: false, + 'Used in automations to filter lists and DEs. Depends on type "FilterDefinitions".', + typeRetrieveByDefault: true, typeName: 'Automation: Filter Activity', fields: { + // https://developer.salesforce.com/docs/atlas.en-us.noversion.mc-apis.meta/mc-apis/filteractivity.htm categoryId: { - isCreateable: false, - isUpdateable: false, + isCreateable: true, + isUpdateable: true, retrieving: true, - template: false, + template: true, }, createdDate: { isCreateable: false, isUpdateable: false, - retrieving: true, + retrieving: false, template: false, }, customerKey: { - isCreateable: null, - isUpdateable: false, + isCreateable: true, + isUpdateable: true, retrieving: true, - template: false, + template: true, }, description: { - isCreateable: false, - isUpdateable: false, + isCreateable: true, + isUpdateable: true, retrieving: true, - template: false, + template: true, }, destinationObjectId: { - isCreateable: false, - isUpdateable: false, + isCreateable: true, + isUpdateable: true, retrieving: true, - template: false, + template: true, }, destinationTypeId: { - isCreateable: false, - isUpdateable: false, + isCreateable: true, + isUpdateable: true, retrieving: true, - template: false, + template: true, }, filterActivityId: { - isCreateable: null, - isUpdateable: null, - retrieving: true, + isCreateable: false, + isUpdateable: true, + retrieving: false, + template: false, }, filterDefinitionId: { - isCreateable: false, - isUpdateable: false, + isCreateable: true, + isUpdateable: true, retrieving: true, - template: false, + template: true, }, modifiedDate: { isCreateable: false, @@ -72,28 +83,28 @@ module.exports = { template: false, }, name: { - isCreateable: null, - isUpdateable: false, + isCreateable: true, + isUpdateable: true, retrieving: true, - template: false, + template: true, }, sourceObjectId: { - isCreateable: false, - isUpdateable: false, + isCreateable: true, + isUpdateable: true, retrieving: true, - template: false, + template: true, }, sourceTypeId: { - isCreateable: false, - isUpdateable: false, + isCreateable: true, + isUpdateable: true, retrieving: true, - template: false, + template: true, }, statusId: { - isCreateable: false, - isUpdateable: false, + isCreateable: true, + isUpdateable: true, retrieving: true, - template: false, + template: true, }, }, }; diff --git a/lib/metadataTypes/definitions/FilterDefinition.definition.js b/lib/metadataTypes/definitions/FilterDefinition.definition.js new file mode 100644 index 000000000..b8299c046 --- /dev/null +++ b/lib/metadataTypes/definitions/FilterDefinition.definition.js @@ -0,0 +1,155 @@ +module.exports = { + bodyIteratorField: 'items', + dependencies: ['folder-filterdefinition', 'folder-hidden', 'dataExtension'], + filter: {}, + hasExtended: false, + idField: 'id', + keyField: 'key', + nameField: 'name', + folderType: 'filterdefinition', + folderIdField: 'categoryId', + createdDateField: 'createdDate', + createdNameField: 'createdBy', + lastmodDateField: 'lastUpdated', + lastmodNameField: 'lastUpdatedBy', + restPagination: true, + restPageSize: 100, + type: 'filterDefinition', + typeDescription: 'Defines an audience based on specified rules. Used by Filter Activities.', + typeRetrieveByDefault: true, + typeName: 'Filter Definition', + fields: { + // the GUI seems to ONLY send fields during update that are actually changed. It has yet to be tested if that also works with sending other fiedls as well + id: { + isCreateable: false, + isUpdateable: false, // included in URL + retrieving: false, + template: false, + }, + key: { + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + createdDate: { + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, + createdBy: { + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, + createdByName: { + // actual name of user indicated by id in createdBy + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, + lastUpdated: { + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, + lastUpdatedBy: { + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, + lastUpdatedByName: { + // actual name of user indicated by id in lastUpdatedBy + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, + name: { + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + categoryId: { + // returned by GET / CREATE / UPDATE; used in CREATE payload + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + description: { + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + filterDefinitionXml: { + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + // DerivedFromType: { + // // this upper-cased spelling is used by GUI when creating a dataExtension based filterDefintion + // isCreateable: true, + // isUpdateable: false, // cannot be updated + // retrieving: false, + // template: false, + // }, + derivedFromType: { + // 1: SubscriberAttributes, 2: DataExtension, 6: EntryCriteria; + isCreateable: true, + isUpdateable: false, // cannot be updated + retrieving: true, + template: true, + }, + derivedFromObjectId: { + // dataExtension ID or '00000000-0000-0000-0000-000000000000' for lists + isCreateable: true, + isUpdateable: false, // cannot be updated + retrieving: true, + template: true, + }, + derivedFromObjectTypeName: { + // "SubscriberAttributes" | "DataExtension" | "EntryCriteria" ...; only returned by GET API + isCreateable: false, + isUpdateable: false, + retrieving: true, + template: true, + }, + derivedFromObjectName: { + // dataExtension name; field only returned by GET-API + isCreateable: false, + isUpdateable: false, + retrieving: true, + template: true, + }, + isSendable: { + isCreateable: false, // automatically set during create + isUpdateable: false, + retrieving: true, + template: true, + }, + r__dataExtension_CustomerKey: { + isCreateable: false, + isUpdateable: false, + retrieving: true, + template: true, + }, + c__filterDefinition: { + skipValidation: true, + }, + r__folder_Path: { + isCreateable: false, + isUpdateable: false, + retrieving: true, + template: true, + }, + }, +}; diff --git a/lib/metadataTypes/definitions/FilterDefinitionHidden.definition.js b/lib/metadataTypes/definitions/FilterDefinitionHidden.definition.js new file mode 100644 index 000000000..60b8a7c70 --- /dev/null +++ b/lib/metadataTypes/definitions/FilterDefinitionHidden.definition.js @@ -0,0 +1,156 @@ +module.exports = { + bodyIteratorField: 'items', + dependencies: ['folder-filterdefinition', 'folder-hidden', 'dataExtension', 'list'], + filter: {}, + hasExtended: false, + idField: 'id', + keyField: 'key', + nameField: 'name', + folderType: 'filterdefinition', + folderIdField: 'categoryId', + createdDateField: 'createdDate', + createdNameField: 'createdBy', + lastmodDateField: 'lastUpdated', + lastmodNameField: 'lastUpdatedBy', + restPagination: true, + restPageSize: 100, + type: 'filterDefinitionHidden', + typeDescription: + 'Defines an audience based on specified rules. Used by filtered DEs and filtered Lists.', + typeRetrieveByDefault: false, + typeName: 'Filter Definition for filtered Lists && Data Extensions', + fields: { + // the GUI seems to ONLY send fields during update that are actually changed. It has yet to be tested if that also works with sending other fiedls as well + id: { + isCreateable: false, + isUpdateable: false, // included in URL + retrieving: false, + template: false, + }, + key: { + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + createdDate: { + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, + createdBy: { + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, + createdByName: { + // actual name of user indicated by id in createdBy + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, + lastUpdated: { + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, + lastUpdatedBy: { + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, + lastUpdatedByName: { + // actual name of user indicated by id in lastUpdatedBy + isCreateable: false, + isUpdateable: false, + retrieving: false, + template: false, + }, + name: { + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + categoryId: { + // returned by GET / CREATE / UPDATE; used in CREATE payload + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + description: { + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + filterDefinitionXml: { + isCreateable: true, + isUpdateable: true, + retrieving: true, + template: true, + }, + // DerivedFromType: { + // // this upper-cased spelling is used by GUI when creating a dataExtension based filterDefintion + // isCreateable: true, + // isUpdateable: false, // cannot be updated + // retrieving: false, + // template: false, + // }, + derivedFromType: { + // 1: SubscriberAttributes, 2: DataExtension, 6: EntryCriteria; + isCreateable: true, + isUpdateable: false, // cannot be updated + retrieving: true, + template: true, + }, + derivedFromObjectId: { + // dataExtension ID or '00000000-0000-0000-0000-000000000000' for lists + isCreateable: true, + isUpdateable: false, // cannot be updated + retrieving: true, + template: true, + }, + derivedFromObjectTypeName: { + // "SubscriberAttributes" | "DataExtension" | "EntryCriteria" ...; only returned by GET API + isCreateable: false, + isUpdateable: false, + retrieving: true, + template: true, + }, + derivedFromObjectName: { + // dataExtension name; field only returned by GET-API + isCreateable: false, + isUpdateable: false, + retrieving: true, + template: true, + }, + isSendable: { + isCreateable: false, // automatically set during create + isUpdateable: false, + retrieving: true, + template: true, + }, + r__dataExtension_CustomerKey: { + isCreateable: false, + isUpdateable: false, + retrieving: true, + template: true, + }, + c__filterDefinition: { + skipValidation: true, + }, + r__folder_Path: { + isCreateable: false, + isUpdateable: false, + retrieving: true, + template: true, + }, + }, +}; diff --git a/package-lock.json b/package-lock.json index 86e352906..011f186c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "conf": "10.2.0", "console.table": "0.10.0", "deep-equal": "2.2.2", + "fast-xml-parser": "4.2.7", "fs-extra": "11.1.0", "inquirer": "8.2.6", "json-to-table": "4.2.1", @@ -45,7 +46,6 @@ "eslint-plugin-mocha": "10.1.0", "eslint-plugin-prettier": "4.2.1", "eslint-plugin-unicorn": "48.0.0", - "fast-xml-parser": "4.2.7", "husky": "8.0.3", "jsdoc-to-markdown": "8.0.0", "lint-staged": "14.0.1", @@ -154,9 +154,9 @@ } }, "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -195,9 +195,9 @@ } }, "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -3628,7 +3628,6 @@ "version": "4.2.7", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.7.tgz", "integrity": "sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==", - "dev": true, "funding": [ { "type": "paypal", @@ -5370,9 +5369,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -6335,9 +6334,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -6887,9 +6886,9 @@ } }, "node_modules/normalize-package-data/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -7170,9 +7169,9 @@ } }, "node_modules/npm-run-all/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true, "bin": { "semver": "bin/semver" @@ -7672,9 +7671,9 @@ } }, "node_modules/package-json/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -8807,9 +8806,9 @@ } }, "node_modules/semver-diff/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -10157,9 +10156,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -10188,9 +10187,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -12780,7 +12779,6 @@ "version": "4.2.7", "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.7.tgz", "integrity": "sha512-J8r6BriSLO1uj2miOk1NW0YVm8AGOOu3Si2HQp/cSmo6EA4m3fcwu2WKjJ4RK9wMLBtg69y1kS8baDiQBR41Ig==", - "dev": true, "requires": { "strnum": "^1.0.5" } @@ -13990,9 +13988,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -14724,9 +14722,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, @@ -15144,9 +15142,9 @@ }, "dependencies": { "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true } } @@ -15363,9 +15361,9 @@ "dev": true }, "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", "dev": true }, "shebang-command": { @@ -15740,9 +15738,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, @@ -16568,9 +16566,9 @@ }, "dependencies": { "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" } } }, diff --git a/package.json b/package.json index 804530c47..6414c2c38 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "conf": "10.2.0", "console.table": "0.10.0", "deep-equal": "2.2.2", + "fast-xml-parser": "4.2.7", "fs-extra": "11.1.0", "inquirer": "8.2.6", "json-to-table": "4.2.1", @@ -91,7 +92,6 @@ "eslint-plugin-mocha": "10.1.0", "eslint-plugin-prettier": "4.2.1", "eslint-plugin-unicorn": "48.0.0", - "fast-xml-parser": "4.2.7", "husky": "8.0.3", "jsdoc-to-markdown": "8.0.0", "lint-staged": "14.0.1", @@ -106,8 +106,6 @@ "fsevents": "*" }, "lint-staged": { - "*.{js,jsx,ts,tsx}": [ - "eslint --fix" - ] + "*.{js,jsx,ts,tsx}": ["eslint --fix"] } } diff --git a/test/resources/9999999/verification/build-expected.json b/test/resources/9999999/verification/build-expected.json index f717cc626..98d32e01c 100644 --- a/test/resources/9999999/verification/build-expected.json +++ b/test/resources/9999999/verification/build-expected.json @@ -1,6 +1,6 @@ { "dataVerificationDefinitionId": "testTemplated_39f6a488-20eb-4ba0-b0b9", - "notificationEmailAddress": "", + "notificationEmailAddress": [""], "notificationEmailMessage": "", "r__dataExtension_CustomerKey": "testTemplated_dataExtension", "shouldEmailOnFailure": false, diff --git a/test/resources/9999999/verification/get-expected.json b/test/resources/9999999/verification/get-expected.json index 1f9c1825b..1be889a98 100644 --- a/test/resources/9999999/verification/get-expected.json +++ b/test/resources/9999999/verification/get-expected.json @@ -1,6 +1,6 @@ { "dataVerificationDefinitionId": "testExisting_39f6a488-20eb-4ba0-b0b9", - "notificationEmailAddress": "", + "notificationEmailAddress": [""], "notificationEmailMessage": "", "r__dataExtension_CustomerKey": "testExisting_dataExtension", "shouldEmailOnFailure": false, diff --git a/test/resources/9999999/verification/patch-expected.json b/test/resources/9999999/verification/patch-expected.json index dc8d975d2..dde339242 100644 --- a/test/resources/9999999/verification/patch-expected.json +++ b/test/resources/9999999/verification/patch-expected.json @@ -1,6 +1,6 @@ { "dataVerificationDefinitionId": "testExisting_39f6a488-20eb-4ba0-b0b9", - "notificationEmailAddress": "test@accenture.com", + "notificationEmailAddress": ["test@accenture.com"], "notificationEmailMessage": "", "r__dataExtension_CustomerKey": "testExisting_dataExtension", "shouldEmailOnFailure": true, diff --git a/test/resources/9999999/verification/post-expected.json b/test/resources/9999999/verification/post-expected.json index b06a77b76..41795da1f 100644 --- a/test/resources/9999999/verification/post-expected.json +++ b/test/resources/9999999/verification/post-expected.json @@ -6,6 +6,6 @@ "value2": 0, "shouldStopOnFailure": false, "shouldEmailOnFailure": false, - "notificationEmailAddress": "", + "notificationEmailAddress": [""], "notificationEmailMessage": "" } diff --git a/test/resources/9999999/verification/template-expected.json b/test/resources/9999999/verification/template-expected.json index 04296b7ea..f75056e69 100644 --- a/test/resources/9999999/verification/template-expected.json +++ b/test/resources/9999999/verification/template-expected.json @@ -1,6 +1,6 @@ { "dataVerificationDefinitionId": "{{{prefix}}}39f6a488-20eb-4ba0-b0b9", - "notificationEmailAddress": "", + "notificationEmailAddress": [""], "notificationEmailMessage": "", "r__dataExtension_CustomerKey": "{{{prefix}}}dataExtension", "shouldEmailOnFailure": false, diff --git a/types/mcdev.d.js b/types/mcdev.d.js index 0367a38f8..b167a342c 100644 --- a/types/mcdev.d.js +++ b/types/mcdev.d.js @@ -296,6 +296,105 @@ const SDK = require('sfmc-sdk'); * @property {string} gitRemoteUrl URL of Git remote server */ +/** + * @typedef {object} FilterItem + * @property {number} categoryId folder id + * @property {string} [createdDate] - + * @property {string} customerKey key + * @property {string} destinationObjectId DE/List ID + * @property {1|2|3|4} destinationTypeId 1:SubscriberList, 2:DataExtension, 3:GroupWizard, 4:BehavioralData + * @property {string} filterActivityId ? + * @property {string} filterDefinitionId ObjectID of filterDefinition + * @property {string} modifiedDate - + * @property {string} name name + * @property {string} sourceObjectId DE/List ID + * @property {1|2|3|4} sourceTypeId 1:SubscriberList, 2:DataExtension, 3:GroupWizard, 4:BehavioralData + * @property {number} statusId ? + * @typedef {Object.} FilterMap + */ + +/** + * @typedef {object} FilterDefinitionSOAPItem + * @property {string} ObjectID id + * @property {string} CustomerKey key + * @property {object} [DataFilter] most relevant part that defines the filter + * @property {object} DataFilter.LeftOperand - + * @property {string} DataFilter.LeftOperand.Property - + * @property {string} DataFilter.LeftOperand.SimpleOperator - + * @property {string} DataFilter.LeftOperand.Value - + * @property {string} DataFilter.LogicalOperator - + * @property {object} [DataFilter.RightOperand] - + * @property {string} DataFilter.RightOperand.Property - + * @property {string} DataFilter.RightOperand.SimpleOperator - + * @property {string} DataFilter.RightOperand.Value - + * @property {string} Name name + * @property {string} Description - + * @property {string} [ObjectState] returned from SOAP API; used to return error messages + * @typedef {Object.} FilterDefinitionSOAPItemMap + */ +/** + * /automation/v1/filterdefinitions/ (not used) + * + * @typedef {object} AutomationFilterDefinitionItem + * @property {string} id object id + * @property {string} key external key + * @property {string} createdDate - + * @property {number} createdBy user id + * @property {string} createdName - + * @property {string} [description] (omitted by API if empty) + * @property {string} modifiedDate - + * @property {number} modifiedBy user id + * @property {string} modifiedName - + * @property {string} name name + * @property {string} categoryId folder id + * @property {string} filterDefinitionXml from REST API defines the filter in XML form + * @property {1|2} derivedFromType 1:list/profile attributes/measures, 2: dataExtension + * @property {boolean} isSendable ? + * @property {object} [soap__DataFilter] copied from SOAP API, defines the filter in readable form + * @property {object} soap__DataFilter.LeftOperand - + * @property {string} soap__DataFilter.LeftOperand.Property - + * @property {string} soap__DataFilter.LeftOperand.SimpleOperator - + * @property {string} soap__DataFilter.LeftOperand.Value - + * @property {string} soap__DataFilter.LogicalOperator - + * @property {object} [soap__DataFilter.RightOperand] - + * @property {string} soap__DataFilter.RightOperand.Property - + * @property {string} soap__DataFilter.RightOperand.SimpleOperator - + * @property {string} soap__DataFilter.RightOperand.Value - + */ +/** + * /email/v1/filters/filterdefinition/ + * + * @typedef {object} FilterDefinitionItem + * @property {string} id object id + * @property {string} key external key + * @property {string} createdDate date + * @property {number} createdBy user id + * @property {string} createdName name + * @property {string} [description] (omitted by API if empty) + * @property {string} lastUpdated date + * @property {number} lastUpdatedBy user id + * @property {string} lastUpdatedName name + * @property {string} name name + * @property {string} categoryId folder id + * @property {string} filterDefinitionXml from REST API defines the filter in XML form + * @property {1|2} derivedFromType 1:list/profile attributes/measures, 2: dataExtension + * @property {string} derivedFromObjectId Id of DataExtension - present if derivedFromType=2 + * @property {'DataExtension'|'SubscriberAttributes'} derivedFromObjectTypeName - + * @property {string} [derivedFromObjectName] name of DataExtension + * @property {boolean} isSendable ? + * @property {object} [soap__DataFilter] copied from SOAP API, defines the filter in readable form + * @property {object} soap__DataFilter.LeftOperand - + * @property {string} soap__DataFilter.LeftOperand.Property - + * @property {string} soap__DataFilter.LeftOperand.SimpleOperator - + * @property {string} soap__DataFilter.LeftOperand.Value - + * @property {string} soap__DataFilter.LogicalOperator - + * @property {object} [soap__DataFilter.RightOperand] - + * @property {string} soap__DataFilter.RightOperand.Property - + * @property {string} soap__DataFilter.RightOperand.SimpleOperator - + * @property {string} soap__DataFilter.RightOperand.Value - + * @typedef {Object.} FilterDefinitionMap + */ + /** * @typedef {object} AuthObject * @property {string} client_id client_id client_id for sfmc-sdk auth