UNPKG

mcdev

Version:

Accenture Salesforce Marketing Cloud DevTools

291 lines (274 loc) 11.5 kB
'use strict'; import MetadataType from './MetadataType.js'; import { Util } from '../util/util.js'; /** * @typedef {import('../../types/mcdev.d.js').BuObject} BuObject * @typedef {import('../../types/mcdev.d.js').CodeExtract} CodeExtract * @typedef {import('../../types/mcdev.d.js').CodeExtractItem} CodeExtractItem * @typedef {import('../../types/mcdev.d.js').MetadataTypeItem} MetadataTypeItem * @typedef {import('../../types/mcdev.d.js').MetadataTypeItemDiff} MetadataTypeItemDiff * @typedef {import('../../types/mcdev.d.js').MetadataTypeItemObj} MetadataTypeItemObj * @typedef {import('../../types/mcdev.d.js').MetadataTypeMap} MetadataTypeMap * @typedef {import('../../types/mcdev.d.js').MetadataTypeMapObj} MetadataTypeMapObj * @typedef {import('../../types/mcdev.d.js').SoapRequestParams} SoapRequestParams * @typedef {import('../../types/mcdev.d.js').TemplateMap} TemplateMap */ /** * ImportFile MetadataType * * @augments MetadataType */ class FileLocation extends MetadataType { static cache = {}; /** * Retrieves Metadata of FileLocation * Endpoint /automation/v1/ftplocations/ return all FileLocations * * @param {string} retrieveDir Directory where retrieved metadata directory will be saved * @param {void | string[]} [_] unused parameter * @param {void | string[]} [__] unused parameter * @param {string} [key] customer key of single item to retrieve * @returns {Promise.<MetadataTypeMapObj>} Promise */ static async retrieve(retrieveDir, _, __, key) { try { const dataItems = await super.retrieveREST( null, '/data/v1/filetransferlocation' + (key ? '/' + encodeURIComponent(key) : 's'), null, key ); this.cache[this.buObject.mid] ||= {}; this.cache[this.buObject.mid].dataItems = dataItems.metadata; } catch (ex) { if (ex.code === 'ERR_BAD_REQUEST') { // if retrieve-by-key comes up empty, the data-endpoint returns a code 400 Util.logger.debug(ex.message); } else { Util.logger.warn(ex.message); } } const items = await super.retrieveREST( retrieveDir, '/automation/v1/ftplocations/', null, key ); return items; } /** * Retrieves folder metadata into local filesystem. Also creates a uniquePath attribute for each folder. * * @returns {Promise.<MetadataTypeMapObj>} Promise */ static async retrieveForCache() { return this.retrieve(null); } /** * Creates a single item * * @param {MetadataTypeItem} metadata a single item * @returns {Promise.<MetadataTypeItem>} Promise */ static create(metadata) { return this.createREST(metadata, '/data/v1/filetransferlocation'); } /** * Updates a single item * * @param {MetadataTypeItem} metadata a single item * @returns {Promise.<MetadataTypeItem>} Promise */ static update(metadata) { return this.updateREST( metadata, '/data/v1/filetransferlocation/' + encodeURIComponent(metadata[this.definition.keyField]) ); } /** * helper for {@link MetadataType.parseResponseBody} that creates a custom key field for this type based on mobileCode and keyword * * @param {MetadataTypeItem} metadata single item */ static createCustomKeyField(metadata) { if (metadata.fileTransferLocation) { const fileTransferLocation = metadata.fileTransferLocation; for (const key of Object.keys(metadata)) { delete metadata[key]; } Object.assign(metadata, fileTransferLocation); } else { if (!metadata.customerKey && this.cache[this.buObject.mid]?.dataItems) { const nameMatch = Object.values(this.cache[this.buObject.mid].dataItems).find( (item) => item.name === metadata.name ); if (nameMatch) { Object.assign(metadata, nameMatch); } } if (!this.definition.locationTypeIdMappingDeployable[metadata.locationTypeId]) { // old file location types are only returned by the automation-endpoint which does not return customerKey field - but also these are not updatable and hence we can improvise here metadata.customerKey ||= metadata.name; } } } /** * Creates a single metadata entry via REST * * @param {MetadataTypeItem} metadataEntry a single metadata Entry * @param {string} uri rest endpoint for POST * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method * @returns {Promise.<object> | null} Promise of API response or null in case of an error */ static async createREST(metadataEntry, uri, handleOutside) { this.removeNotCreateableFields(metadataEntry); const createPayload = { fileTransferLocation: metadataEntry }; try { // set to empty object in case API returned nothing to be able to update it in helper classes let response = (await this.client.rest.post(uri, createPayload)) || {}; response = await this.postCreateTasks(metadataEntry, response); if (!handleOutside) { Util.logger.info( ` - created ${Util.getTypeKeyName(this.definition, metadataEntry)}` ); } return response; } catch (ex) { const parsedErrors = this.getErrorsREST(ex); Util.logger.error( ` ☇ error creating ${Util.getTypeKeyName(this.definition, metadataEntry)}:` ); if (parsedErrors.length) { for (const msg of parsedErrors) { Util.logger.error(' • ' + msg); } } else if (ex?.message) { Util.logger.debug(ex.message); } return null; } } /** * helper for {@link MetadataType.createREST} * * @param {MetadataTypeItem} _ a single metadata Entry * @param {object} apiResponse varies depending on the API call * @returns {Promise.<object>} apiResponse, potentially modified */ static async postCreateTasks(_, apiResponse) { return apiResponse?.fileTransferLocation || apiResponse; } /** * Updates a single metadata entry via REST * * @param {MetadataTypeItem} metadataEntry a single metadata Entry * @param {string} uri rest endpoint for PATCH * @param {'patch'|'post'|'put'} [httpMethod] defaults to 'patch'; some update requests require PUT instead of PATCH * @param {boolean} [handleOutside] if the API reponse is irregular this allows you to handle it outside of this generic method * @returns {Promise.<object> | null} Promise of API response or null in case of an error */ static async updateREST(metadataEntry, uri, httpMethod = 'patch', handleOutside) { this.removeNotUpdateableFields(metadataEntry); const updatePayload = { fileTransferLocation: metadataEntry }; try { // set to empty object in case API returned nothing to be able to update it in helper classes let response = (await this.client.rest[httpMethod](uri, updatePayload)) || {}; await this._postChangeKeyTasks(metadataEntry); this.getErrorsREST(response); response = await this.postUpdateTasks(metadataEntry, response); // some times, e.g. automation dont return a key in their update response and hence we need to fall back to name if (!handleOutside) { Util.logger.info( ` - updated ${Util.getTypeKeyName(this.definition, metadataEntry)}` ); } return response; } catch (ex) { const parsedErrors = this.getErrorsREST(ex); Util.logger.error( ` ☇ error updating ${Util.getTypeKeyName(this.definition, metadataEntry)}:` ); for (const msg of parsedErrors) { Util.logger.error(' • ' + msg); } return null; } } /** * helper for {@link MetadataType.updateREST} and {@link MetadataType.updateSOAP} * * @param {MetadataTypeItem} _ a single metadata Entry * @param {object} apiResponse varies depending on the API call * @returns {Promise.<object>} apiResponse, potentially modified */ static postUpdateTasks(_, apiResponse) { return apiResponse?.fileTransferLocation || apiResponse; } /** * prepares a import definition for deployment * * @param {MetadataTypeItem} metadata a single importDef * @returns {Promise.<MetadataTypeItem>} Promise */ static async preDeployTasks(metadata) { if (metadata.c__locationType) { metadata.locationTypeId = this.definition.locationTypeMapping[metadata.c__locationType]; if (this.definition.locationTypeMappingDeployable[metadata.c__locationType]) { metadata.locationType = this.definition.locationTypeMappingDeployable[metadata.c__locationType]; } else { throw new Error( `Only FileLocations of types ${Object.keys(this.definition.locationTypeMappingDeployable).join(', ')} can be deployed via mcdev.` ); } } return metadata; } /** * manages post retrieve steps * * @param {MetadataTypeItem} metadata a single item * @returns {MetadataTypeItem} parsed metadata */ static postRetrieveTasks(metadata) { if (metadata.locationTypeId !== undefined) { try { metadata.c__locationType = Util.inverseGet( this.definition.locationTypeMapping, metadata.locationTypeId ); delete metadata.locationTypeId; } catch { Util.logger.info( 'Please report this new & unknown locationTypeId to the mcdev developer team via github: ' + metadata.locationTypeId ); } } else if (metadata.locationType) { // assuming create/update of new types metadata.c__locationType = Util.inverseGet( this.definition.locationTypeMappingDeployable, metadata.locationType ); } return metadata; } /** * Delete a metadata item from the specified business unit * * @param {string} key Identifier of item * @returns {Promise.<boolean>} deletion success flag */ static async deleteByKey(key) { return super.deleteByKeyREST( '/data/v1/filetransferlocation/' + encodeURIComponent(key), key, 400 ); } } // Assign definition to static attributes import MetadataTypeDefinitions from '../MetadataTypeDefinitions.js'; FileLocation.definition = MetadataTypeDefinitions.fileLocation; export default FileLocation;