class MailingsModel extends EICModel { constructor(privileges) { super('/mailing', privileges) this.ffs = new FakeFileSystem() Object.assign(this, app.helpers.validators) } async search(query, status=[]) { if(!this.hasPrivilege('search')) return( new Promise((resolve, reject) => reject())) let endpoint = this.getApiEndpoint('search') let payload = { query: query, status: status, } return ( this.request(endpoint.uri, endpoint.method, payload) // Separate filesystem info from true mailings .then( async serverData => serverData.payload) .then( data => { // And feed ffs this.ffs.loadStructure( data.ffs, data.mailings.map(item=>( { fullPath: (item.path+'/'+item.name).replace(/\/+/g, '/'), object : item } )) ) return(data.mailings) }) ) } async get(mid) { if(!this.hasPrivilege('read')) return( new Promise((resolve, reject) => reject())) let endpoint = this.getApiEndpoint('read') endpoint.uri = endpoint.uri.replace('{mid}', mid) return ( this.request(endpoint.uri, endpoint.method) .then( async serverData => serverData.payload) ) } async getRecipients(mid){ if(!this.hasPrivilege('read')) return( new Promise((resolve, reject) => reject())) // if can read mailing => can read recipients let endpoint = this.getApiEndpoint('read', '/mailing/{mid}/recipients') endpoint.uri = endpoint.uri.replace('{mid}', mid) return ( this.request(endpoint.uri, endpoint.method) .then( async serverData => serverData.payload) ) } async getBounces(mid){ if(!this.hasPrivilege('read')) return( new Promise((resolve, reject) => reject())) // if can read mailing => can read bounces let endpoint = this.getApiEndpoint('read', '/mailing/{mid}/bounces') endpoint.uri = endpoint.uri.replace('{mid}', mid) return ( this.request(endpoint.uri, endpoint.method) .then( async serverData => serverData.payload) ) } async save(mailingInfo) { // Local copy where we'll remove display (non-save-able) mailingInfo stuff, and join-in ext stuff into the request. let localMmailingInfo = JSON.parse(JSON.stringify(mailingInfo)) // template struct => templateId if(localMmailingInfo.template && localMmailingInfo.template.id){ localMmailingInfo.templateId = localMmailingInfo.template.id delete(localMmailingInfo.template) } delete(localMmailingInfo.imports) delete(localMmailingInfo.sources) delete(localMmailingInfo.statusHistory) delete(localMmailingInfo.kpis) delete(localMmailingInfo.nbSources) delete(localMmailingInfo.nbRecipients) let endpoint = this.getApiEndpoint('save') return ( this.request(endpoint.uri, endpoint.method, localMmailingInfo) .then( async serverData => serverData.payload) ) } async delete(mid) { if(!this.hasPrivilege('delete')) return( new Promise((resolve, reject) => reject())) let endpoint = this.getApiEndpoint('delete') endpoint.uri = endpoint.uri.replace('{mid}', mid) return ( this.request(endpoint.uri, endpoint.method) .then( async serverData => serverData.payload) ) } async schedule(mid, scheduleDate) { if(!this.hasPrivilege('schedule')) return( new Promise((resolve, reject) => reject())) let endpoint = this.getApiEndpoint('schedule') endpoint.uri = endpoint.uri.replace('{mid}', mid) return ( this.request(endpoint.uri, endpoint.method, { scheduleDate: scheduleDate }) .then( async serverData => serverData.payload) ) } async test(mid, templateId, recipientEmail, mappings={}) { if((!this.hasPrivilege('edit')) && (!this.hasPrivilege('approve')) && (!this.hasPrivilege('reject'))) return( new Promise((resolve, reject) => reject())) let endpoint = this.getApiEndpoint('test') endpoint.uri = endpoint.uri.replace('{mid}', mid) return ( this.request(endpoint.uri, endpoint.method, { templateId: templateId, recipientEmail: recipientEmail, parameters: mappings }) .then( async serverData => serverData.payload) ) } async getImports(mid) { let endpoint = this.getApiEndpoint('list', '/mailing/{mid}/imports') endpoint.uri = endpoint.uri.replace('{mid}', mid) return ( this.request(endpoint.uri, endpoint.method) .then( async serverData => serverData.payload) ) } async saveImport(mid, data) { data.refresh = false let endpoint = this.getApiEndpoint('save', '/mailing/{mid}/imports') endpoint.uri = endpoint.uri.replace('{mid}', mid) return ( this.request(endpoint.uri, endpoint.method, data) .then( async serverData => serverData.payload) ) } async saveExclusion(mid, data) { data.refresh = false data.exclusionList = true data.availableColumns = data.availableColumns.filter(item=>item.isEmail) const emailIdx = data.availableColumns[0].value data.data = data.data.map(row=>row[emailIdx]) let endpoint = this.getApiEndpoint('save', '/mailing/{mid}/imports') endpoint.uri = endpoint.uri.replace('{mid}', mid) return ( this.request(endpoint.uri, endpoint.method, data) .then( async serverData => serverData.payload) ) } async refreshImport(mid, data, importId) { if(data.sourceType=='Excel') return({}) data.refresh = true data.importId = importId let endpoint = this.getApiEndpoint('save', '/mailing/{mid}/imports') endpoint.uri = endpoint.uri.replace('{mid}', mid) return ( this.request(endpoint.uri, endpoint.method, data) .then( async serverData => serverData.payload) ) } async readImport(mid, iid) { let endpoint = this.getApiEndpoint('read', '/mailing/{mid}/imports/{iid}') endpoint.uri = endpoint.uri.replace('{mid}', mid).replace('{iid}', iid) return ( this.request(endpoint.uri, endpoint.method) .then( async serverData => serverData.payload) ) } async deleteImport(mid, id) { let endpoint = this.getApiEndpoint('delete', '/mailing/{mid}/imports') endpoint.uri = endpoint.uri.replace('{mid}', mid) return ( this.request(endpoint.uri, endpoint.method, { id: id }) .then( async serverData => serverData.payload) ) } async getReadableFolders() { try { const endpoint = this.getApiEndpoint('list', '/mailing/folders') const payload = { path: '/', withFiles: true } const serverData = await this.request(endpoint.uri, endpoint.method, payload) return serverData.payload || {} } catch (err) { console.error("Error in getReadableFolders", err) return null } } async makeDir(path=null, dirName){ let ffs = this.ffs if(path && (!ffs.pathExists(path))) return(Promise.resolve(null)) if(!path) path=ffs.currentPath dirName = dirName.replace(/\//g,'').replace(/"/g,'').replace('\\','').replace(/\./g,'') let endpoint = this.getApiEndpoint('add', '/mailing/folders') let uri = endpoint.uri return ( this.request(uri, endpoint.method, { appId: 2, path: path, newFolder: dirName }) .then( async serverData => { return(serverData.payload) }) ) } async removeDir(appId, path = null) { let ffs = this.ffs if (!path) return Promise.resolve(null) path = ffs.normalizePath(path) let endpoint = this.getApiEndpoint('delete', '/mailing/folders') let uri = endpoint.uri return this.request(uri, endpoint.method, { appId: appId, path: path }).then(async serverData => { return serverData.payload }) } /********************************* Data helpers **************************************/ getStatusLabel() { return({ created : 'Created', draft : 'Draft', submitted : app.User.roles.includes('MAIL_Reviewer') ? 'To review' : 'Being reviewed', approved : 'Approved', rejected : 'Rejected', scheduled : 'Scheduled', sent : 'Sent', }) } findDupes(allImports){ let dupes = {} //Key email, value: array of where it is found let allEmails = [] for(let oneImport of allImports){ const emailCol = oneImport.availableColumns.find(item => item.isEmail) if(!emailCol) continue for(let[rownb, row] of oneImport.data.entries()){ let inAllEmails = allEmails.find(item => ( item.email == row[emailCol.value])) if(inAllEmails) { // that's a dupe if(!(row[emailCol.value] in dupes)) { // First time we come across this one, add the original entry dupes[row[emailCol.value]] = [{ sourceName: inAllEmails.sourceName, sourceType: inAllEmails.sourceType, rownb: inAllEmails.rownb, }] } dupes[row[emailCol.value]].push({ // Add the dupe sourceName: oneImport.sourceName, sourceType: oneImport.sourceType, rownb: rownb, }) } else { allEmails.push({ email: row[emailCol.value], sourceName: oneImport.sourceName, sourceType : oneImport.sourceType, rownb: rownb, }) } } } return(dupes) } getLatestStatus(mailingInfo, status=null){ if(!mailingInfo || !mailingInfo.statusHistory || (mailingInfo.statusHistory.length<1)) return('') const latest = mailingInfo.statusHistory.reduce((max, obj) => (obj.dateTime > max.dateTime) && ((!status) || (status==obj.value)) ? obj : max ) return( (!status) ? latest : (status == latest.value) ? latest : null ) } getStepActivations(mailing, templateTokens){ const stepActivations = { } stepActivations['start'] = { disabled: (!this.hasPrivilege('edit')) || ((mailing.status != 'created') && (mailing.status != 'draft')), severity: (mailing.status != 'created') ? 'success' : 'warning', done: (mailing.name && mailing.path) } stepActivations['template'] = { disabled: (!this.hasPrivilege('edit')) || (mailing.status != 'draft'), severity: (!mailing.template.id) ? (mailing.status != 'draft') ? 'secondary' : 'warning' : 'success', done: (mailing.template.id) } stepActivations['recipients'] = { disabled: (!this.hasPrivilege('edit')) || (mailing.status != 'draft'), // || (!stepActivations.template.done), severity: (mailing.nbRecipients == 0) ? ((mailing.status != 'draft') || (!mailing.template.id)) ? 'secondary' : 'warning' : 'success', done: (mailing.nbRecipients > 0) } stepActivations['mappings'] = { disabled: (!this.hasPrivilege('edit')) || (mailing.status != 'draft') || (!stepActivations.template.done) || (!stepActivations.recipients.done), severity: (stepActivations.template.done && stepActivations.recipients.done) ? (this.TotalNbMapMissing(mailing, templateTokens) > 0) ? 'warning': 'success' : 'secondary', done: (stepActivations['template'].done && stepActivations['recipients'].done && (this.TotalNbMapMissing(mailing, templateTokens) == 0)) } stepActivations['approval'] = { disabled: ((!this.hasPrivilege('edit')) && (!this.hasPrivilege('approve')) && (!this.hasPrivilege('reject'))) || (!(['draft','submitted'].includes(mailing.status))) || (!stepActivations.mappings.done) , severity: stepActivations.mappings.done ? ((mailing.status == 'approved') || (mailing.status == 'scheduled')) ? 'success' : 'warning' : 'secondary', done: (mailing.status == 'approved') } stepActivations['schedule'] = { disabled: (!this.hasPrivilege('schedule')) || ((mailing.status!='approved') && (mailing.status!='scheduled')), severity: (mailing.status!='scheduled') ? (mailing.status!='approved') ? 'secondary' : 'warning' : 'success', done: (mailing.status=='scheduled') } return(stepActivations) } nbMapMissingPerSrc(mailing, templateTokens){ let nbMissing = {} mailing.sources.forEach(src => { let srcMappings = mailing.mappings.find(mp => mp. sourceId==src.id) if(srcMappings) { nbMissing[src.id] = (templateTokens.length - Object.keys(srcMappings.mappings).length) } else { nbMissing[src.id] = templateTokens.length } }) return(nbMissing) } TotalNbMapMissing(mailing, templateTokens){ const missingPerSrc = this.nbMapMissingPerSrc(mailing, templateTokens) return(Object.keys(missingPerSrc).reduce((acc,key) => acc+=missingPerSrc[key],0)) } getReviewBlocks(mailing){ const latestSubmitted = this.getLatestStatus(mailing, 'submitted') const latestRejected = this.getLatestStatus(mailing, 'rejected') const latestApproved = this.getLatestStatus(mailing, 'approved') let blocks = [] let contents = {} if(!app.User.roles.includes('MAIL_Reviewer')){ // Only Editors & Revieers can sen test mails if(app.User.roles.includes('MAIL_Editor')) blocks.push('testPane') blocks.push('revieweePane') switch(mailing.status){ case 'submitted': blocks.push('revieweeOngoing'); break case 'approved': blocks.push('revieweeApproved') contents['approUser1'] = `${latestApproved.changedBy.firstname} ${latestApproved.changedBy.lastname}` contents['approDate1'] = (new Intl.DateTimeFormat("fr-FR", {timeZone: "Europe/Paris", dateStyle:'medium', timeStyle:'medium'})) .format(new Date(latestApproved.dateTime)) break case 'draft': if((latestRejected) && // no rejection ever // Rejection and no approval afterwards (possible because might be draft again after unscheduled) ((!latestApproved) || (latestApproved.dateTime