mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-20 22:42:53 -05:00
🐛 Fixed empty error csv file for member imports (#15274)
closes https://github.com/TryGhost/Team/issues/1828 - members importer was sending empty error files in case of invalid rows in CSV, hiding both error and affected rows - fixes typo in `content` option(was `contents` before) passed to attachment (ref - https://nodemailer.com/message/attachments )
This commit is contained in:
parent
a3d4c4b3a9
commit
c16abbf085
2 changed files with 104 additions and 12 deletions
|
@ -258,6 +258,34 @@ module.exports = class MembersCSVImporter {
|
||||||
return membersCSV.unparse(errorsWithFormattedMessages);
|
return membersCSV.unparse(errorsWithFormattedMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send email with attached CSV containing error rows info
|
||||||
|
*
|
||||||
|
* @param {Object} config
|
||||||
|
* @param {String} config.emailRecipient - email recipient for error file
|
||||||
|
* @param {String} config.emailSubject - email subject
|
||||||
|
* @param {String} config.emailContent - html content of email
|
||||||
|
* @param {String} config.errorCSV - error CSV content
|
||||||
|
* @param {Object} config.emailSubject - email subject
|
||||||
|
* @param {Object} config.importLabel -
|
||||||
|
* @param {String} config.importLabel.name - label name
|
||||||
|
*/
|
||||||
|
async sendErrorEmail({emailRecipient, emailSubject, emailContent, errorCSV, importLabel}) {
|
||||||
|
await this._sendEmail({
|
||||||
|
to: emailRecipient,
|
||||||
|
subject: emailSubject,
|
||||||
|
html: emailContent,
|
||||||
|
forceTextContent: true,
|
||||||
|
attachments: [{
|
||||||
|
filename: `${importLabel.name} - Errors.csv`,
|
||||||
|
content: errorCSV,
|
||||||
|
contentType: 'text/csv',
|
||||||
|
contentDisposition: 'attachment'
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Processes CSV file and imports member&label records depending on the size of the imported set
|
* Processes CSV file and imports member&label records depending on the size of the imported set
|
||||||
*
|
*
|
||||||
|
@ -303,17 +331,12 @@ module.exports = class MembersCSVImporter {
|
||||||
const errorCSV = this.generateErrorCSV(result);
|
const errorCSV = this.generateErrorCSV(result);
|
||||||
const emailSubject = result.imported > 0 ? 'Your member import is complete' : 'Your member import was unsuccessful';
|
const emailSubject = result.imported > 0 ? 'Your member import is complete' : 'Your member import was unsuccessful';
|
||||||
|
|
||||||
await this._sendEmail({
|
await this.sendErrorEmail({
|
||||||
to: emailRecipient,
|
emailRecipient,
|
||||||
subject: emailSubject,
|
emailSubject,
|
||||||
html: emailContent,
|
emailContent,
|
||||||
forceTextContent: true,
|
errorCSV,
|
||||||
attachments: [{
|
importLabel
|
||||||
filename: `${importLabel.name} - Errors.csv`,
|
|
||||||
contents: errorCSV,
|
|
||||||
contentType: 'text/csv',
|
|
||||||
contentDisposition: 'attachment'
|
|
||||||
}]
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
offloaded: false
|
offloaded: false
|
||||||
|
|
|
@ -17,7 +17,7 @@ describe('Importer', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
const writtenFile = fsWriteSpy.args[0][0];
|
const writtenFile = fsWriteSpy.args?.[0]?.[0];
|
||||||
|
|
||||||
if (writtenFile) {
|
if (writtenFile) {
|
||||||
fs.removeSync(writtenFile);
|
fs.removeSync(writtenFile);
|
||||||
|
@ -103,6 +103,75 @@ describe('Importer', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('sendErrorEmail', function () {
|
||||||
|
it('should send email with errors for invalid CSV file', async function () {
|
||||||
|
const defaultProduct = {
|
||||||
|
id: 'default_product_id'
|
||||||
|
};
|
||||||
|
|
||||||
|
const memberCreateStub = sinon.stub().resolves(null);
|
||||||
|
const membersApi = {
|
||||||
|
productRepository: {
|
||||||
|
list: async () => {
|
||||||
|
return {
|
||||||
|
data: [defaultProduct]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
members: {
|
||||||
|
get: async () => {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
create: memberCreateStub
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const knexStub = {
|
||||||
|
transaction: sinon.stub().resolves({
|
||||||
|
rollback: () => {},
|
||||||
|
commit: () => {}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const sendEmailStub = sinon.stub();
|
||||||
|
|
||||||
|
const importer = new MembersCSVImporter({
|
||||||
|
storagePath: csvPath,
|
||||||
|
getTimezone: sinon.stub().returns('UTC'),
|
||||||
|
getMembersApi: () => membersApi,
|
||||||
|
sendEmail: sendEmailStub,
|
||||||
|
isSet: sinon.stub(),
|
||||||
|
addJob: sinon.stub(),
|
||||||
|
knex: knexStub,
|
||||||
|
urlFor: sinon.stub(),
|
||||||
|
context: {importer: true}
|
||||||
|
});
|
||||||
|
|
||||||
|
await importer.sendErrorEmail({
|
||||||
|
emailRecipient: 'test@example.com',
|
||||||
|
emailSubject: 'Your member import was unsuccessful',
|
||||||
|
emailContent: 'Import was unsuccessful',
|
||||||
|
errorCSV: 'id,email,invalid email',
|
||||||
|
importLabel: {name: 'Test import'}
|
||||||
|
});
|
||||||
|
|
||||||
|
sendEmailStub.calledWith({
|
||||||
|
to: 'test@example.com',
|
||||||
|
subject: 'Your member import was unsuccessful',
|
||||||
|
html: 'Import was unsuccessful',
|
||||||
|
forceTextContent: true,
|
||||||
|
attachments: [
|
||||||
|
{
|
||||||
|
filename: 'Test import - Errors.csv',
|
||||||
|
content: 'id,email,invalid email',
|
||||||
|
contentType: 'text/csv',
|
||||||
|
contentDisposition: 'attachment'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}).should.be.true();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('prepare', function () {
|
describe('prepare', function () {
|
||||||
it('processes a basic valid import file for members', async function () {
|
it('processes a basic valid import file for members', async function () {
|
||||||
const membersImporter = new MembersCSVImporter({
|
const membersImporter = new MembersCSVImporter({
|
||||||
|
|
Loading…
Add table
Reference in a new issue