4ff467794f
refs: https://github.com/TryGhost/DevOps/issues/11 This is a pretty huge commit, but the relevant points are: * Each importer no longer needs to be passed a set of data, it just gets the data it needs * Each importer specifies its dependencies, so that the order of import can be determined at runtime using a topological sort * The main data generator function can just tell each importer to import the data it has This makes working on the data generator much easier. Some other benefits are: * Batched importing, massively speeding up the whole process * `--tables` to set the exact tables you want to import, and specify the quantity of each
120 lines
4.7 KiB
JavaScript
120 lines
4.7 KiB
JavaScript
const TableImporter = require('./TableImporter');
|
|
const {faker} = require('@faker-js/faker');
|
|
const generateEvents = require('../utils/event-generator');
|
|
const dateToDatabaseString = require('../utils/database-date');
|
|
|
|
const emailStatus = {
|
|
delivered: Symbol(),
|
|
opened: Symbol(),
|
|
failed: Symbol(),
|
|
none: Symbol()
|
|
};
|
|
|
|
class EmailRecipientsImporter extends TableImporter {
|
|
static table = 'email_recipients';
|
|
static dependencies = ['emails', 'email_batches', 'members', 'members_subscribe_events'];
|
|
|
|
constructor(knex, transaction) {
|
|
super(EmailRecipientsImporter.table, knex, transaction);
|
|
}
|
|
|
|
async import(quantity) {
|
|
const emails = await this.transaction
|
|
.select(
|
|
'id',
|
|
'newsletter_id',
|
|
'email_count',
|
|
'delivered_count',
|
|
'opened_count',
|
|
'failed_count')
|
|
.from('emails');
|
|
this.emailBatches = await this.transaction.select('id', 'email_id', 'updated_at').from('email_batches');
|
|
this.members = await this.transaction.select('id', 'uuid', 'email', 'name').from('members');
|
|
this.membersSubscribeEvents = await this.transaction.select('id', 'newsletter_id', 'created_at', 'member_id').from('members_subscribe_events');
|
|
|
|
await this.importForEach(emails, quantity ? quantity / emails.length : this.members.length);
|
|
}
|
|
|
|
setReferencedModel(model) {
|
|
this.model = model;
|
|
this.batch = this.emailBatches.find(batch => batch.email_id === model.id);
|
|
// Shallow clone members list so we can shuffle and modify it
|
|
const earliestOpenTime = new Date(this.batch.updated_at);
|
|
const latestOpenTime = new Date(this.batch.updated_at);
|
|
latestOpenTime.setDate(latestOpenTime.getDate() + 14);
|
|
const currentTime = new Date();
|
|
this.membersList = this.membersSubscribeEvents
|
|
.filter(entry => entry.newsletter_id === this.model.newsletter_id)
|
|
.filter(entry => new Date(entry.created_at) < earliestOpenTime)
|
|
.map(memberSubscribeEvent => memberSubscribeEvent.member_id);
|
|
this.events = this.membersList.length > 0 ? generateEvents({
|
|
shape: 'ease-out',
|
|
trend: 'negative',
|
|
total: this.membersList.length,
|
|
startTime: earliestOpenTime,
|
|
endTime: currentTime < latestOpenTime ? currentTime : latestOpenTime
|
|
}) : [];
|
|
this.emailMeta = {
|
|
emailCount: this.model.email_count,
|
|
// delievered and not opened
|
|
deliveredCount: this.model.delivered_count - this.model.opened_count,
|
|
openedCount: this.model.opened_count,
|
|
failedCount: this.model.failed_count
|
|
};
|
|
}
|
|
|
|
generate() {
|
|
if (this.emailMeta.emailCount <= 0) {
|
|
return;
|
|
}
|
|
this.emailMeta.emailCount -= 1;
|
|
|
|
const timestamp = this.events.shift();
|
|
if (!timestamp) {
|
|
return;
|
|
}
|
|
|
|
const memberIdIndex = faker.datatype.number({
|
|
min: 0,
|
|
max: this.membersList.length - 1
|
|
});
|
|
const [memberId] = this.membersList.splice(memberIdIndex, 1);
|
|
const member = this.members.find(m => m.id === memberId);
|
|
|
|
let status = emailStatus.none;
|
|
if (this.emailMeta.failedCount > 0) {
|
|
status = emailStatus.failed;
|
|
this.emailMeta.failedCount -= 1;
|
|
} else if (this.emailMeta.openedCount > 0) {
|
|
status = emailStatus.opened;
|
|
this.emailMeta.openedCount -= 1;
|
|
} else if (this.emailMeta.deliveredCount > 0) {
|
|
status = emailStatus.delivered;
|
|
this.emailMeta.deliveredCount -= 1;
|
|
}
|
|
|
|
let deliveredTime;
|
|
if (status === emailStatus.opened) {
|
|
const startDate = new Date(this.batch.updated_at).valueOf();
|
|
const endDate = timestamp.valueOf();
|
|
deliveredTime = new Date(startDate + (Math.random() * (endDate - startDate)));
|
|
}
|
|
|
|
return {
|
|
id: faker.database.mongodbObjectId(),
|
|
email_id: this.model.id,
|
|
batch_id: this.batch.id,
|
|
member_id: member.id,
|
|
processed_at: this.batch.updated_at,
|
|
delivered_at: status === emailStatus.opened ? dateToDatabaseString(deliveredTime) : status === emailStatus.delivered ? dateToDatabaseString(timestamp) : null,
|
|
opened_at: status === emailStatus.opened ? dateToDatabaseString(timestamp) : null,
|
|
failed_at: status === emailStatus.failed ? dateToDatabaseString(timestamp) : null,
|
|
member_uuid: member.uuid,
|
|
member_email: member.email,
|
|
member_name: member.name
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = EmailRecipientsImporter;
|