Merge pull request #723 from mailpoet/bounced_subscribers

Add 'Bounced' status to subscribers [MAILPOET-684]
This commit is contained in:
Tautvidas Sipavičius
2016-11-29 12:47:41 +02:00
committed by GitHub
13 changed files with 89 additions and 8 deletions

View File

@ -28,6 +28,10 @@ var columns = [
name: 'unsubscribed', name: 'unsubscribed',
label: MailPoet.I18n.t('unsubscribed') label: MailPoet.I18n.t('unsubscribed')
}, },
{
name: 'bounced',
label: MailPoet.I18n.t('bounced')
},
{ {
name: 'created_at', name: 'created_at',
label: MailPoet.I18n.t('createdOn'), label: MailPoet.I18n.t('createdOn'),
@ -192,6 +196,7 @@ const SegmentList = React.createClass({
const subscribed = ~~(segment.subscribers_count.subscribed || 0); const subscribed = ~~(segment.subscribers_count.subscribed || 0);
const unconfirmed = ~~(segment.subscribers_count.unconfirmed || 0); const unconfirmed = ~~(segment.subscribers_count.unconfirmed || 0);
const unsubscribed = ~~(segment.subscribers_count.unsubscribed || 0); const unsubscribed = ~~(segment.subscribers_count.unsubscribed || 0);
const bounced = ~~(segment.subscribers_count.bounced || 0);
let segment_name; let segment_name;
@ -229,6 +234,9 @@ const SegmentList = React.createClass({
<td className="column-date" data-colname={ MailPoet.I18n.t('unsubscribed') }> <td className="column-date" data-colname={ MailPoet.I18n.t('unsubscribed') }>
<abbr>{ unsubscribed.toLocaleString() }</abbr> <abbr>{ unsubscribed.toLocaleString() }</abbr>
</td> </td>
<td className="column-date" data-colname={ MailPoet.I18n.t('bounced') }>
<abbr>{ bounced.toLocaleString() }</abbr>
</td>
<td className="column-date" data-colname={ MailPoet.I18n.t('createdOn') }> <td className="column-date" data-colname={ MailPoet.I18n.t('createdOn') }>
<abbr>{ MailPoet.Date.format(segment.created_at) }</abbr> <abbr>{ MailPoet.Date.format(segment.created_at) }</abbr>
</td> </td>

View File

@ -45,7 +45,8 @@ define(
values: { values: {
'subscribed': MailPoet.I18n.t('subscribed'), 'subscribed': MailPoet.I18n.t('subscribed'),
'unconfirmed': MailPoet.I18n.t('unconfirmed'), 'unconfirmed': MailPoet.I18n.t('unconfirmed'),
'unsubscribed': MailPoet.I18n.t('unsubscribed') 'unsubscribed': MailPoet.I18n.t('unsubscribed'),
'bounced': MailPoet.I18n.t('bounced')
}, },
filter: function(subscriber, value) { filter: function(subscriber, value) {
if (~~(subscriber.wp_user_id) > 0 && value === 'unconfirmed') { if (~~(subscriber.wp_user_id) > 0 && value === 'unconfirmed') {

View File

@ -255,6 +255,10 @@ const SubscriberList = React.createClass({
case 'unsubscribed': case 'unsubscribed':
status = MailPoet.I18n.t('unsubscribed'); status = MailPoet.I18n.t('unsubscribed');
break; break;
case 'bounced':
status = MailPoet.I18n.t('bounced');
break;
} }
let segments = false; let segments = false;

View File

@ -87,6 +87,10 @@ class Segment extends Model {
'SUM(CASE subscribers.status WHEN "' . Subscriber::STATUS_UNCONFIRMED . '" THEN 1 ELSE 0 END)', 'SUM(CASE subscribers.status WHEN "' . Subscriber::STATUS_UNCONFIRMED . '" THEN 1 ELSE 0 END)',
Subscriber::STATUS_UNCONFIRMED Subscriber::STATUS_UNCONFIRMED
) )
->select_expr(
'SUM(CASE subscribers.status WHEN "' . Subscriber::STATUS_BOUNCED . '" THEN 1 ELSE 0 END)',
Subscriber::STATUS_BOUNCED
)
->findOne() ->findOne()
->asArray(); ->asArray();

View File

@ -13,6 +13,7 @@ class Subscriber extends Model {
const STATUS_SUBSCRIBED = 'subscribed'; const STATUS_SUBSCRIBED = 'subscribed';
const STATUS_UNSUBSCRIBED = 'unsubscribed'; const STATUS_UNSUBSCRIBED = 'unsubscribed';
const STATUS_UNCONFIRMED = 'unconfirmed'; const STATUS_UNCONFIRMED = 'unconfirmed';
const STATUS_BOUNCED = 'bounced';
const SUBSCRIPTION_LIMIT_COOLDOWN = 60; const SUBSCRIPTION_LIMIT_COOLDOWN = 60;
@ -342,6 +343,11 @@ class Subscriber extends Model {
'label' => __('Unsubscribed', 'mailpoet'), 'label' => __('Unsubscribed', 'mailpoet'),
'count' => self::filter(self::STATUS_UNSUBSCRIBED)->count() 'count' => self::filter(self::STATUS_UNSUBSCRIBED)->count()
), ),
array(
'name' => self::STATUS_BOUNCED,
'label' => __('Bounced', 'mailpoet'),
'count' => self::filter(self::STATUS_BOUNCED)->count()
),
array( array(
'name' => 'trash', 'name' => 'trash',
'label' => __('Trash', 'mailpoet'), 'label' => __('Trash', 'mailpoet'),
@ -727,6 +733,12 @@ class Subscriber extends Model {
->where('status', self::STATUS_UNCONFIRMED); ->where('status', self::STATUS_UNCONFIRMED);
} }
static function bounced($orm) {
return $orm
->whereNull('deleted_at')
->where('status', self::STATUS_BOUNCED);
}
static function withoutSegments($orm) { static function withoutSegments($orm) {
return $orm->select(MP_SUBSCRIBERS_TABLE.'.*') return $orm->select(MP_SUBSCRIBERS_TABLE.'.*')
->leftOuterJoin( ->leftOuterJoin(

View File

@ -304,18 +304,24 @@ class Import {
-1, -1,
'-1', '-1',
'false' 'false'
),
'bounced' => array(
'bounced'
) )
); );
$subscribers_data['status'] = array_map(function($state) use ($statuses) { $subscribers_data['status'] = array_map(function($state) use ($statuses) {
if(in_array(strtolower($state), $statuses['subscribed'])) { if(in_array(strtolower($state), $statuses['subscribed'], true)) {
return 'subscribed'; return 'subscribed';
} }
if(in_array(strtolower($state), $statuses['unsubscribed'])) { if(in_array(strtolower($state), $statuses['unsubscribed'], true)) {
return 'unsubscribed'; return 'unsubscribed';
} }
if(in_array(strtolower($state), $statuses['unconfirmed'])) { if(in_array(strtolower($state), $statuses['unconfirmed'], true)) {
return 'unconfirmed'; return 'unconfirmed';
} }
if(in_array(strtolower($state), $statuses['bounced'], true)) {
return 'bounced';
}
return 'subscribed'; // make "subscribed" a default status return 'subscribed'; // make "subscribed" a default status
}, $subscribers_data['status']); }, $subscribers_data['status']);
return array( return array(

View File

@ -290,6 +290,14 @@ class Pages {
'is_checked' => ( 'is_checked' => (
$subscriber->status === Subscriber::STATUS_UNSUBSCRIBED $subscriber->status === Subscriber::STATUS_UNSUBSCRIBED
) )
),
array(
'value' => array(
Subscriber::STATUS_BOUNCED => __('Bounced', 'mailpoet')
),
'is_checked' => (
$subscriber->status === Subscriber::STATUS_BOUNCED
)
) )
) )
) )

View File

@ -5,7 +5,7 @@ use \MailPoet\Models\Subscriber;
class BeaconTest extends MailPoetTest { class BeaconTest extends MailPoetTest {
function _before() { function _before() {
// create 3 users (1 confirmed, 1 subscribed, 1 unsubscribed) // create 4 users (1 confirmed, 1 subscribed, 1 unsubscribed, 1 bounced)
Subscriber::createOrUpdate(array( Subscriber::createOrUpdate(array(
'email' => 'user1@mailpoet.com', 'email' => 'user1@mailpoet.com',
'status' => Subscriber::STATUS_SUBSCRIBED 'status' => Subscriber::STATUS_SUBSCRIBED
@ -18,6 +18,10 @@ class BeaconTest extends MailPoetTest {
'email' => 'user3@mailpoet.com', 'email' => 'user3@mailpoet.com',
'status' => Subscriber::STATUS_UNSUBSCRIBED 'status' => Subscriber::STATUS_UNSUBSCRIBED
)); ));
Subscriber::createOrUpdate(array(
'email' => 'user4@mailpoet.com',
'status' => Subscriber::STATUS_BOUNCED
));
$this->beacon_data = Beacon::getData(); $this->beacon_data = Beacon::getData();
} }

View File

@ -115,16 +115,30 @@ class SubscriberTest extends MailPoetTest {
foreach($subscribers as $subscriber) { foreach($subscribers as $subscriber) {
expect($subscriber->status)->equals(Subscriber::STATUS_UNCONFIRMED); expect($subscriber->status)->equals(Subscriber::STATUS_UNCONFIRMED);
} }
$this->subscriber->status = Subscriber::STATUS_SUBSCRIBED;
$this->subscriber->save();
$subscribers = Subscriber::filter('groupBy', Subscriber::STATUS_SUBSCRIBED) $subscribers = Subscriber::filter('groupBy', Subscriber::STATUS_SUBSCRIBED)
->findMany(); ->findMany();
foreach($subscribers as $subscriber) { foreach($subscribers as $subscriber) {
expect($subscriber->status)->equals(Subscriber::STATUS_SUBSCRIBED); expect($subscriber->status)->equals(Subscriber::STATUS_SUBSCRIBED);
} }
$this->subscriber->status = Subscriber::STATUS_UNSUBSCRIBED;
$this->subscriber->save();
$subscribers = Subscriber::filter('groupBy', Subscriber::STATUS_UNSUBSCRIBED) $subscribers = Subscriber::filter('groupBy', Subscriber::STATUS_UNSUBSCRIBED)
->findMany(); ->findMany();
foreach($subscribers as $subscriber) { foreach($subscribers as $subscriber) {
expect($subscriber->status)->equals(Subscriber::STATUS_UNSUBSCRIBED); expect($subscriber->status)->equals(Subscriber::STATUS_UNSUBSCRIBED);
} }
$this->subscriber->status = Subscriber::STATUS_BOUNCED;
$this->subscriber->save();
$subscribers = Subscriber::filter('groupBy', Subscriber::STATUS_BOUNCED)
->findMany();
foreach($subscribers as $subscriber) {
expect($subscriber->status)->equals(Subscriber::STATUS_BOUNCED);
}
} }
function testItCanHaveSegment() { function testItCanHaveSegment() {
@ -525,6 +539,11 @@ class SubscriberTest extends MailPoetTest {
'deleted_at' => Carbon::now()->toDateTimeString() 'deleted_at' => Carbon::now()->toDateTimeString()
)); ));
$subscriber_5 = Subscriber::createOrUpdate(array(
'email' => 'subscriber_5@mailpoet.com',
'status' => Subscriber::STATUS_BOUNCED
));
// counts only subscribed & unconfirmed users // counts only subscribed & unconfirmed users
$total = Subscriber::getTotalSubscribers(); $total = Subscriber::getTotalSubscribers();
expect($total)->equals(2); expect($total)->equals(2);

View File

@ -153,7 +153,12 @@ class ShortcodesTest extends MailPoetTest {
$this->subscriber->save(); $this->subscriber->save();
$result = $result =
$shortcodes_object->process(array('[subscriber:count]')); $shortcodes_object->process(array('[subscriber:count]'));
expect($result[0])->equals(--$subscriber_count); expect($result[0])->equals($subscriber_count - 1);
$this->subscriber->status = 'bounced';
$this->subscriber->save();
$result =
$shortcodes_object->process(array('[subscriber:count]'));
expect($result[0])->equals($subscriber_count - 1);
} }
function testItCanProcessSubscriberCustomFieldShortcodes() { function testItCanProcessSubscriberCustomFieldShortcodes() {

View File

@ -186,7 +186,12 @@ class ImportTest extends MailPoetTest {
'unsubscribed', 'unsubscribed',
-1, -1,
'-1', '-1',
'false' 'false',
#bounced
'bounced',
#unexpected
'qwerty',
null
), ),
); );
list($subscribers_data, $subsciber_fields) = list($subscribers_data, $subsciber_fields) =
@ -205,7 +210,10 @@ class ImportTest extends MailPoetTest {
'unsubscribed', 'unsubscribed',
'unsubscribed', 'unsubscribed',
'unsubscribed', 'unsubscribed',
'unsubscribed' 'unsubscribed',
'bounced',
'subscribed',
'subscribed'
) )
) )
); );

View File

@ -30,6 +30,7 @@
'subscribed': __('Subscribed'), 'subscribed': __('Subscribed'),
'unconfirmed': __('Unconfirmed'), 'unconfirmed': __('Unconfirmed'),
'unsubscribed': __('Unsubscribed'), 'unsubscribed': __('Unsubscribed'),
'bounced': __('Bounced'),
'createdOn': __('Created on'), 'createdOn': __('Created on'),
'oneSegmentTrashed': __('1 list was moved to the trash'), 'oneSegmentTrashed': __('1 list was moved to the trash'),
'multipleSegmentsTrashed': __('%$1d lists were moved to the trash'), 'multipleSegmentsTrashed': __('%$1d lists were moved to the trash'),

View File

@ -50,6 +50,7 @@
'unconfirmed': __('Unconfirmed'), 'unconfirmed': __('Unconfirmed'),
'subscribed': __('Subscribed'), 'subscribed': __('Subscribed'),
'unsubscribed': __('Unsubscribed'), 'unsubscribed': __('Unsubscribed'),
'bounced': __('Bounced'),
'selectList': __('Select a list'), 'selectList': __('Select a list'),
'unsubscribedOn': __('Unsubscribed on %$1s'), 'unsubscribedOn': __('Unsubscribed on %$1s'),
'subscriberUpdated': __('Subscriber was updated successfully!'), 'subscriberUpdated': __('Subscriber was updated successfully!'),