-
-
Notifications
You must be signed in to change notification settings - Fork 10.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
🐛 Fixed members import unsubscribing members when subscribe_to_emails
is blank
#19658
Changes from all commits
0dd1432
ce272da
4d206f3
3ec72aa
4d6c796
0270228
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,53 +14,53 @@ const DEFAULT_COLUMNS = [ | |
'tiers' | ||
]; | ||
|
||
const unparse = (members, columns = DEFAULT_COLUMNS.slice()) => { | ||
const unparse = (rows, columns = DEFAULT_COLUMNS.slice()) => { | ||
columns = columns.map((column) => { | ||
if (column === 'subscribed') { | ||
return 'subscribed_to_emails'; | ||
} | ||
return column; | ||
}); | ||
const mappedMembers = members.map((member) => { | ||
if (member.error && !columns.includes('error')) { | ||
const mappedRows = rows.map((row) => { | ||
if (row.error && !columns.includes('error')) { | ||
columns.push('error'); | ||
} | ||
|
||
let labels = ''; | ||
if (typeof member.labels === 'string') { | ||
labels = member.labels; | ||
} else if (Array.isArray(member.labels)) { | ||
labels = member.labels.map((l) => { | ||
if (typeof row.labels === 'string') { | ||
labels = row.labels; | ||
} else if (Array.isArray(row.labels)) { | ||
labels = row.labels.map((l) => { | ||
return typeof l === 'string' ? l : l.name; | ||
}).join(','); | ||
} | ||
|
||
let tiers = ''; | ||
|
||
if (Array.isArray(member.tiers)) { | ||
tiers = member.tiers.map((tier) => { | ||
if (Array.isArray(row.tiers)) { | ||
tiers = row.tiers.map((tier) => { | ||
return tier.name; | ||
}).join(','); | ||
} | ||
|
||
return { | ||
id: member.id, | ||
email: member.email, | ||
name: member.name, | ||
note: member.note, | ||
subscribed_to_emails: member.subscribed || member.subscribed_to_emails ? true : false, | ||
complimentary_plan: member.comped || member.complimentary_plan, | ||
stripe_customer_id: _.get(member, 'subscriptions[0].customer.id') || member.stripe_customer_id, | ||
created_at: member.created_at, | ||
deleted_at: member.deleted_at, | ||
id: row.id, | ||
email: row.email, | ||
name: row.name, | ||
note: row.note, | ||
subscribed_to_emails: 'subscribed' in row ? row.subscribed : row.subscribed_to_emails, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm 99% sure there's no way currently to get a value in This allows the expected value ( |
||
complimentary_plan: row.comped || row.complimentary_plan, | ||
stripe_customer_id: _.get(row, 'subscriptions[0].customer.id') || row.stripe_customer_id, | ||
created_at: row.created_at, | ||
deleted_at: row.deleted_at, | ||
labels: labels, | ||
tiers: tiers, | ||
import_tier: member.import_tier || null, | ||
error: member.error || null | ||
import_tier: row.import_tier || null, | ||
error: row.error || null | ||
}; | ||
}); | ||
|
||
return papaparse.unparse(mappedMembers, { | ||
return papaparse.unparse(mappedRows, { | ||
columns | ||
}); | ||
}; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,7 @@ describe('MembersCSVImporter', function () { | |
email: 'email', | ||
name: 'name', | ||
note: 'note', | ||
subscribed_to_emails: 'subscribed', | ||
subscribed_to_emails: 'subscribed_to_emails', | ||
created_at: 'created_at', | ||
complimentary_plan: 'complimentary_plan', | ||
stripe_customer_id: 'stripe_customer_id', | ||
|
@@ -221,6 +221,222 @@ describe('MembersCSVImporter', function () { | |
}]); | ||
should.deepEqual(membersRepositoryStub.update.args[0][1].id, 'test_member_id'); | ||
}); | ||
|
||
it('should subscribe or unsubscribe members as per the `subscribe_to_emails` column', async function () { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A bit verbose and cumbersome, but this one has come up a couple times so I want to just squash it dead. |
||
/** | ||
* @NOTE This tests all the different scenarios for the `subscribed_to_emails` column for existing and new members | ||
* For existing members with at least one newsletter subscription: | ||
* CASE 1: If `subscribe_to_emails` is `true`, the member should remain subscribed (but not added to any additional newsletters) | ||
* CASE 2: If `subscribe_to_emails` is `false`, the member should be unsubscribed from all newsletters | ||
* CASE 3: If `subscribe_to_emails` is NULL, the member should remain subscribed (but not added to any additional newsletters) | ||
* CASE 4: If `subscribe_to_emails` is empty, the member should remain subscribed (but not added to any additional newsletters) | ||
* CASE 5: If `subscribe_to_emails` is invalid, the member should remain subscribed (but not added to any additional newsletters) | ||
* | ||
* | ||
* For existing members with no newsletter subscriptions: | ||
* CASE 6: If `subscribe_to_emails` is `true`, the member should remain unsubscribed (as they likely have previously unsubscribed) | ||
* CASE 7: If `subscribe_to_emails` is `false`, the member should remain unsubscribed | ||
* CASE 8: If `subscribe_to_emails` is NULL, the member should remain unsubscribed | ||
* CASE 9: If `subscribe_to_emails` is empty, the member should remain unsubscribed | ||
* CASE 10: If `subscribe_to_emails` is invalid, the member should remain unsubscribed | ||
* | ||
* - In summary, an existing member with no pre-existing newsletter subscriptions should _never_ be subscribed to newsletters upon import | ||
* | ||
* For new members: | ||
* CASE 11: If `subscribe_to_emails` is `true`, the member should be created and subscribed | ||
* CASE 12: If `subscribe_to_emails` is `false`, the member should be created but not subscribed | ||
* CASE 13: If `subscribe_to_emails` is NULL, the member should be created and subscribed | ||
* CASE 14: If `subscribe_to_emails` is empty, the member should be created and subscribed | ||
* CASE 15: If `subscribe_to_emails` is invalid, the member should be created and subscribed | ||
*/ | ||
|
||
const internalLabel = { | ||
name: 'Test Subscription Import' | ||
}; | ||
const LabelModelStub = { | ||
findOne: sinon.stub() | ||
.withArgs({ | ||
name: 'Test Subscription Import' | ||
}) | ||
.resolves({ | ||
name: 'Test Subscription Import' | ||
}) | ||
}; | ||
|
||
const importer = buildMockImporterInstance(); | ||
|
||
const defaultNewsletters = [ | ||
{id: 'newsletter_1'}, | ||
{id: 'newsletter_2'} | ||
]; | ||
|
||
const existingMembers = [ | ||
{email: 'member_subscribed_true@example.com', newsletters: defaultNewsletters}, | ||
{email: 'member_subscribed_null@example.com', newsletters: defaultNewsletters}, | ||
{email: 'member_subscribed_false@example.com', newsletters: defaultNewsletters}, | ||
{email: 'member_subscribed_empty@example.com', newsletters: defaultNewsletters}, | ||
{email: 'member_subscribed_invalid@example.com', newsletters: defaultNewsletters}, | ||
{email: 'member_not_subscribed_true@example.com', newsletters: []}, | ||
{email: 'member_not_subscribed_null@example.com', newsletters: []}, | ||
{email: 'member_not_subscribed_false@example.com', newsletters: []}, | ||
{email: 'member_not_subscribed_empty@example.com', newsletters: []}, | ||
{email: 'member_not_subscribed_invalid@example.com', newsletters: []} | ||
]; | ||
|
||
membersRepositoryStub.get = sinon.stub(); | ||
|
||
for (const existingMember of existingMembers) { | ||
const newslettersCollection = { | ||
length: existingMember.newsletters.length, | ||
toJSON: sinon.stub().returns(existingMember.newsletters) | ||
}; | ||
const memberRecord = { | ||
related: sinon.stub() | ||
}; | ||
memberRecord.related.withArgs('labels').returns(null); | ||
memberRecord.related.withArgs('newsletters').returns(newslettersCollection); | ||
membersRepositoryStub.get.withArgs({email: existingMember.email}).resolves(memberRecord); | ||
} | ||
|
||
const result = await importer.process({ | ||
pathToCSV: `${csvPath}/subscribed-to-emails-cases.csv`, | ||
headerMapping: defaultAllowedFields, | ||
importLabel: { | ||
name: 'Test Subscription Import' | ||
}, | ||
user: { | ||
email: 'test@example.com' | ||
}, | ||
LabelModel: LabelModelStub, | ||
forceInline: true, | ||
verificationTrigger: { | ||
testImportThreshold: () => {} | ||
} | ||
}); | ||
|
||
should.exist(result.meta); | ||
should.exist(result.meta.stats); | ||
should.exist(result.meta.stats.imported); | ||
result.meta.stats.imported.should.equal(5); | ||
|
||
should.exist(result.meta.stats.invalid); | ||
should.deepEqual(result.meta.import_label, internalLabel); | ||
|
||
should.exist(result.meta.originalImportSize); | ||
result.meta.originalImportSize.should.equal(15); | ||
|
||
fsWriteSpy.calledOnce.should.be.true(); | ||
|
||
// member records get inserted | ||
should.equal(membersRepositoryStub.create.callCount, 5); | ||
|
||
should.equal(membersRepositoryStub.create.args[0][1].context.import, true, 'inserts are done in the "import" context'); | ||
|
||
// CASE 1: Existing member with at least one newsletter subscription, `subscribed_to_emails` column is true | ||
// Member's newsletter subscriptions should not change | ||
should.deepEqual(Object.keys(membersRepositoryStub.update.args[0][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels', 'newsletters']); | ||
should.equal(membersRepositoryStub.update.args[0][0].email, 'member_subscribed_true@example.com'); | ||
should.equal(membersRepositoryStub.update.args[0][0].subscribed, true); | ||
should.deepEqual(membersRepositoryStub.update.args[0][0].newsletters, defaultNewsletters); | ||
|
||
// CASE 2: Existing member with at least one newsletter subscription, `subscribed_to_emails` column is false | ||
// Member's newsletter subscriptions should be removed | ||
should.deepEqual(Object.keys(membersRepositoryStub.update.args[1][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels']); | ||
should.equal(membersRepositoryStub.update.args[1][0].email, 'member_subscribed_false@example.com'); | ||
should.equal(membersRepositoryStub.update.args[1][0].subscribed, false); | ||
should.equal(membersRepositoryStub.update.args[1][0].newsletters, undefined); | ||
|
||
// CASE 3: Existing member with at least one newsletter subscription, `subscribed_to_emails` column is NULL | ||
// Member's newsletter subscriptions should not change | ||
should.deepEqual(Object.keys(membersRepositoryStub.update.args[2][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels', 'newsletters']); | ||
should.equal(membersRepositoryStub.update.args[2][0].email, 'member_subscribed_null@example.com'); | ||
should.equal(membersRepositoryStub.update.args[2][0].subscribed, true); | ||
should.deepEqual(membersRepositoryStub.update.args[2][0].newsletters, defaultNewsletters); | ||
|
||
// CASE 4: Existing member with at least one newsletter subscription, `subscribed_to_emails` column is empty | ||
// Member's newsletter subscriptions should not change | ||
should.deepEqual(Object.keys(membersRepositoryStub.update.args[3][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels', 'newsletters']); | ||
should.equal(membersRepositoryStub.update.args[3][0].email, 'member_subscribed_empty@example.com'); | ||
should.equal(membersRepositoryStub.update.args[3][0].subscribed, true); | ||
should.deepEqual(membersRepositoryStub.update.args[3][0].newsletters, defaultNewsletters); | ||
|
||
// CASE 5: Existing member with at least one newsletter subscription, `subscribed_to_emails` column is invalid | ||
// Member's newsletter subscriptions should not change | ||
should.deepEqual(Object.keys(membersRepositoryStub.update.args[4][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels', 'newsletters']); | ||
should.equal(membersRepositoryStub.update.args[4][0].email, 'member_subscribed_invalid@example.com'); | ||
should.equal(membersRepositoryStub.update.args[4][0].subscribed, true); | ||
should.deepEqual(membersRepositoryStub.update.args[4][0].newsletters, defaultNewsletters); | ||
|
||
// CASE 6: Existing member with no newsletter subscriptions, `subscribed_to_emails` column is true | ||
// Member should remain unsubscribed and not added to any newsletters | ||
should.deepEqual(Object.keys(membersRepositoryStub.update.args[5][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels']); | ||
should.equal(membersRepositoryStub.update.args[5][0].email, 'member_not_subscribed_true@example.com'); | ||
should.equal(membersRepositoryStub.update.args[5][0].subscribed, false); | ||
should.equal(membersRepositoryStub.update.args[5][0].newsletters, undefined); | ||
|
||
// CASE 7: Existing member with no newsletter subscriptions, `subscribed_to_emails` column is false | ||
// Member should remain unsubscribed and not added to any newsletters | ||
should.deepEqual(Object.keys(membersRepositoryStub.update.args[6][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels']); | ||
should.equal(membersRepositoryStub.update.args[6][0].email, 'member_not_subscribed_false@example.com'); | ||
should.equal(membersRepositoryStub.update.args[6][0].subscribed, false); | ||
should.equal(membersRepositoryStub.update.args[6][0].newsletters, undefined); | ||
|
||
// CASE 8: Existing member with no newsletter subscriptions, `subscribed_to_emails` column is NULL | ||
// Member should remain unsubscribed and not added to any newsletters | ||
should.deepEqual(Object.keys(membersRepositoryStub.update.args[7][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels']); | ||
should.equal(membersRepositoryStub.update.args[7][0].email, 'member_not_subscribed_null@example.com'); | ||
should.equal(membersRepositoryStub.update.args[7][0].subscribed, false); | ||
should.equal(membersRepositoryStub.update.args[7][0].newsletters, undefined); | ||
|
||
// CASE 9: Existing member with no newsletter subscriptions, `subscribed_to_emails` column is empty | ||
// Member should remain unsubscribed and not added to any newsletters | ||
should.deepEqual(Object.keys(membersRepositoryStub.update.args[8][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels']); | ||
should.equal(membersRepositoryStub.update.args[8][0].email, 'member_not_subscribed_empty@example.com'); | ||
should.equal(membersRepositoryStub.update.args[8][0].subscribed, false); | ||
should.equal(membersRepositoryStub.update.args[8][0].newsletters, undefined); | ||
|
||
// CASE 10: Existing member with no newsletter subscriptions, `subscribed_to_emails` column is invalid | ||
// Member should remain unsubscribed and not added to any newsletters | ||
should.deepEqual(Object.keys(membersRepositoryStub.update.args[9][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels']); | ||
should.equal(membersRepositoryStub.update.args[9][0].email, 'member_not_subscribed_invalid@example.com'); | ||
should.equal(membersRepositoryStub.update.args[9][0].subscribed, false); | ||
should.equal(membersRepositoryStub.update.args[9][0].newsletters, undefined); | ||
|
||
// CASE 11: New member, `subscribed_to_emails` column is true | ||
// Member should be created and subscribed | ||
should.deepEqual(Object.keys(membersRepositoryStub.create.args[0][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels']); | ||
should.equal(membersRepositoryStub.create.args[0][0].email, 'new_member_true@example.com'); | ||
should.equal(membersRepositoryStub.create.args[0][0].subscribed, true); | ||
should.equal(membersRepositoryStub.create.args[0][0].newsletters, undefined); | ||
|
||
// CASE 12: New member, `subscribed_to_emails` column is false | ||
// Member should be created but not subscribed | ||
should.deepEqual(Object.keys(membersRepositoryStub.create.args[1][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels']); | ||
should.equal(membersRepositoryStub.create.args[1][0].email, 'new_member_false@example.com'); | ||
should.equal(membersRepositoryStub.create.args[1][0].subscribed, false); | ||
should.equal(membersRepositoryStub.create.args[1][0].newsletters, undefined); | ||
|
||
// CASE 13: New member, `subscribed_to_emails` column is NULL | ||
// Member should be created but not subscribed | ||
should.deepEqual(Object.keys(membersRepositoryStub.create.args[2][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels']); | ||
should.equal(membersRepositoryStub.create.args[2][0].email, 'new_member_null@example.com'); | ||
should.equal(membersRepositoryStub.create.args[2][0].subscribed, true); | ||
should.equal(membersRepositoryStub.create.args[2][0].newsletters, undefined); | ||
|
||
// CASE 14: New member, `subscribed_to_emails` column is empty | ||
// Member should be created but not subscribed | ||
should.deepEqual(Object.keys(membersRepositoryStub.create.args[3][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels']); | ||
should.equal(membersRepositoryStub.create.args[3][0].email, 'new_member_empty@example.com'); | ||
should.equal(membersRepositoryStub.create.args[3][0].subscribed, true); | ||
should.equal(membersRepositoryStub.create.args[3][0].newsletters, undefined); | ||
|
||
// CASE 15: New member, `subscribed_to_emails` column is invalid | ||
// Member should be created but not subscribed | ||
should.deepEqual(Object.keys(membersRepositoryStub.create.args[4][0]), ['email', 'name', 'note', 'subscribed', 'created_at', 'labels']); | ||
should.equal(membersRepositoryStub.create.args[4][0].email, 'new_member_invalid@example.com'); | ||
should.equal(membersRepositoryStub.create.args[4][0].subscribed, true); | ||
should.equal(membersRepositoryStub.create.args[4][0].newsletters, undefined); | ||
}); | ||
}); | ||
|
||
describe('sendErrorEmail', function () { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick but at this point these are just rows in a CSV, not members