Ghost/ghost/data-generator/lib/importers/EmailRecipientsImporter.js
Sam Lord 4ff467794f Entirely rewrote data generator to simplify codebase
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
2023-08-04 13:36:09 +01:00

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;