Added option to schedule sending
This commit is contained in:
parent
2196503cae
commit
e396219c03
6 changed files with 95 additions and 18 deletions
|
@ -297,7 +297,7 @@ module.exports.delete = (id, callback) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports.send = (id, callback) => {
|
module.exports.send = (id, scheduled, callback) => {
|
||||||
module.exports.get(id, false, (err, campaign) => {
|
module.exports.get(id, false, (err, campaign) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
@ -312,8 +312,18 @@ module.exports.send = (id, callback) => {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let query;
|
||||||
|
let values;
|
||||||
|
if (scheduled) {
|
||||||
|
query = 'UPDATE campaigns SET `status`=2, `scheduled`=?, `status_change`=NOW() WHERE id=? LIMIT 1';
|
||||||
|
values = [scheduled, id];
|
||||||
|
} else {
|
||||||
|
query = 'UPDATE campaigns SET `status`=2, `status_change`=NOW() WHERE id=? LIMIT 1';
|
||||||
|
values = [id];
|
||||||
|
}
|
||||||
|
|
||||||
// campaigns marked as status=2 should be picked up by the sending processes
|
// campaigns marked as status=2 should be picked up by the sending processes
|
||||||
connection.query('UPDATE campaigns SET `status`=2, `status_change`=NOW() WHERE id=? LIMIT 1', [id], err => {
|
connection.query(query, values, err => {
|
||||||
connection.release();
|
connection.release();
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
|
@ -357,7 +367,7 @@ module.exports.reset = (id, callback) => {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (campaign.status !== 3) {
|
if (campaign.status !== 3 && !(campaign.status === 2 && campaign.scheduled > new Date())) {
|
||||||
return callback(null, false);
|
return callback(null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"schemaVersion": 2
|
"schemaVersion": 3
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,11 @@ router.get('/', (req, res) => {
|
||||||
row.statusText = 'Idling';
|
row.statusText = 'Idling';
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
row.statusText = 'Sending';
|
if (row.scheduled && row.scheduled > new Date()) {
|
||||||
|
row.statusText = 'Scheduled';
|
||||||
|
} else {
|
||||||
|
row.statusText = 'Sending';
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
row.statusText = 'Finished';
|
row.statusText = 'Finished';
|
||||||
|
@ -228,6 +232,8 @@ router.get('/view/:id', passport.csrfProtection, (req, res) => {
|
||||||
campaign.isFinished = campaign.status === 3;
|
campaign.isFinished = campaign.status === 3;
|
||||||
campaign.isPaused = campaign.status === 4;
|
campaign.isPaused = campaign.status === 4;
|
||||||
|
|
||||||
|
campaign.isScheduled = campaign.scheduled && campaign.scheduled > new Date();
|
||||||
|
|
||||||
campaign.openRate = campaign.delivered ? Math.round((campaign.opened / campaign.delivered) * 100) : 0;
|
campaign.openRate = campaign.delivered ? Math.round((campaign.opened / campaign.delivered) * 100) : 0;
|
||||||
campaign.clicksRate = campaign.delivered ? Math.round((campaign.clicks / campaign.delivered) * 100) : 0;
|
campaign.clicksRate = campaign.delivered ? Math.round((campaign.clicks / campaign.delivered) * 100) : 0;
|
||||||
|
|
||||||
|
@ -251,7 +257,11 @@ router.post('/delete', passport.parseForm, passport.csrfProtection, (req, res) =
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('/send', passport.parseForm, passport.csrfProtection, (req, res) => {
|
router.post('/send', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||||
campaigns.send(req.body.id, (err, scheduled) => {
|
let delayHours = Math.max(Number(req.body['delay-hours']) || 0, 0);
|
||||||
|
let delayMinutes = Math.max(Number(req.body['delay-minutes']) || 0, 0);
|
||||||
|
let scheduled = new Date(Date.now() + delayHours * 3600 * 1000 + delayMinutes * 60 * 1000);
|
||||||
|
|
||||||
|
campaigns.send(req.body.id, scheduled, (err, scheduled) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
req.flash('danger', err && err.message || err);
|
req.flash('danger', err && err.message || err);
|
||||||
} else if (scheduled) {
|
} else if (scheduled) {
|
||||||
|
@ -264,6 +274,20 @@ router.post('/send', passport.parseForm, passport.csrfProtection, (req, res) =>
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
router.post('/resume', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||||
|
campaigns.send(req.body.id, false, (err, scheduled) => {
|
||||||
|
if (err) {
|
||||||
|
req.flash('danger', err && err.message || err);
|
||||||
|
} else if (scheduled) {
|
||||||
|
req.flash('success', 'Sending resumed');
|
||||||
|
} else {
|
||||||
|
req.flash('info', 'Could not resume sending');
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.redirect('/campaigns/view/' + encodeURIComponent(req.body.id));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
router.post('/reset', passport.parseForm, passport.csrfProtection, (req, res) => {
|
router.post('/reset', passport.parseForm, passport.csrfProtection, (req, res) => {
|
||||||
campaigns.reset(req.body.id, (err, reset) => {
|
campaigns.reset(req.body.id, (err, reset) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|
|
@ -20,7 +20,7 @@ function findUnsent(callback) {
|
||||||
return callback(err);
|
return callback(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
let query = 'SELECT id, list, segment FROM campaigns WHERE status=? LIMIT 1';
|
let query = 'SELECT `id`, `list`, `segment` FROM `campaigns` WHERE `status`=? AND (`scheduled` IS NULL OR `scheduled` <= NOW()) LIMIT 1';
|
||||||
connection.query(query, [2], (err, rows) => {
|
connection.query(query, [2], (err, rows) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
connection.release();
|
connection.release();
|
||||||
|
|
12
setup/sql/upgrade-00003.sql
Normal file
12
setup/sql/upgrade-00003.sql
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Header section
|
||||||
|
# Define incrementing schema version number
|
||||||
|
SET @schema_version = '3';
|
||||||
|
|
||||||
|
# Adds new column 'scheduled' to campaigns table. Indicates when the sending should actually start
|
||||||
|
ALTER TABLE `campaigns` ADD COLUMN `scheduled` timestamp NULL DEFAULT NULL AFTER `status`;
|
||||||
|
CREATE INDEX schedule_index ON `campaigns` (`scheduled`);
|
||||||
|
|
||||||
|
# Footer section
|
||||||
|
LOCK TABLES `settings` WRITE;
|
||||||
|
INSERT INTO `settings` (`key`, `value`) VALUES('db_schema_version', @schema_version) ON DUPLICATE KEY UPDATE `value`=@schema_version;
|
||||||
|
UNLOCK TABLES;
|
|
@ -85,10 +85,28 @@
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
{{#if isIdling}}
|
{{#if isIdling}}
|
||||||
<form class="form-horizontal confirm-submit" data-confirm-message="Are you sure? This action would start sending messages to the selected list" method="post" action="/campaigns/send">
|
<form class="form-inline confirm-submit" data-confirm-message="Are you sure? This action would start sending messages to the selected list" method="post" action="/campaigns/send">
|
||||||
<input type="hidden" name="_csrf" value="{{csrfToken}}">
|
<input type="hidden" name="_csrf" value="{{csrfToken}}">
|
||||||
<input type="hidden" name="id" value="{{id}}" />
|
<input type="hidden" name="id" value="{{id}}" />
|
||||||
|
|
||||||
|
<div class="pull-right">
|
||||||
|
<div class="form-group">
|
||||||
|
<p class="form-control-static">Delay sending</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="number" class="form-control" name="delay-hours" id="delay-hours" placeholder="0">
|
||||||
|
<div class="input-group-addon"> hours</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="number" class="form-control" name="delay-minutes" id="delay-minutes" placeholder="0">
|
||||||
|
<div class="input-group-addon"> minutes</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-send" aria-hidden="true"></span> Send to
|
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-send" aria-hidden="true"></span> Send to
|
||||||
{{#if segment}}
|
{{#if segment}}
|
||||||
{{segment.subscribers}}
|
{{segment.subscribers}}
|
||||||
|
@ -101,21 +119,34 @@
|
||||||
{{#if isSending}}
|
{{#if isSending}}
|
||||||
<!-- Indicate that this page needs refreshing after 20s. -->
|
<!-- Indicate that this page needs refreshing after 20s. -->
|
||||||
<div class="page-refresh" data-interval="20"></div>
|
<div class="page-refresh" data-interval="20"></div>
|
||||||
<div class="pull-right">
|
{{#if isScheduled}}
|
||||||
<form class="form-horizontal" method="post" action="/campaigns/pause">
|
<div class="pull-right">
|
||||||
<input type="hidden" name="_csrf" value="{{csrfToken}}">
|
<form class="form-horizontal confirm-submit" data-confirm-message="Are you sure? This action would reset scheduling" method="post" action="/campaigns/reset">
|
||||||
<input type="hidden" name="id" value="{{id}}" />
|
<input type="hidden" name="_csrf" value="{{csrfToken}}">
|
||||||
|
<input type="hidden" name="id" value="{{id}}" />
|
||||||
|
|
||||||
<button type="submit" class="btn btn-info"><span class="glyphicon glyphicon-pause" aria-hidden="true"></span> Pause</a>
|
<button type="submit" class="btn btn-danger"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Cancel</a>
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<h4>Sending…</h4>
|
<h4>Sending scheduled <span class="datestring text-info" data-date="{{scheduled}}" title="{{scheduled}}">{{scheduled}}</span></h4>
|
||||||
|
{{else}}
|
||||||
|
<div class="pull-right">
|
||||||
|
<form class="form-horizontal" method="post" action="/campaigns/pause">
|
||||||
|
<input type="hidden" name="_csrf" value="{{csrfToken}}">
|
||||||
|
<input type="hidden" name="id" value="{{id}}" />
|
||||||
|
|
||||||
|
<button type="submit" class="btn btn-info"><span class="glyphicon glyphicon-pause" aria-hidden="true"></span> Pause</a>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<h4>Sending…</h4>
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if isPaused}}
|
{{#if isPaused}}
|
||||||
<div class="pull-right">
|
<div class="pull-right">
|
||||||
<form class="form-horizontal confirm-submit" data-confirm-message="Are you sure? This action would resume sending messages to the selected list" method="post" action="/campaigns/send">
|
<form class="form-horizontal confirm-submit" data-confirm-message="Are you sure? This action would resume sending messages to the selected list" method="post" action="/campaigns/resume">
|
||||||
<input type="hidden" name="_csrf" value="{{csrfToken}}">
|
<input type="hidden" name="_csrf" value="{{csrfToken}}">
|
||||||
<input type="hidden" name="id" value="{{id}}" />
|
<input type="hidden" name="id" value="{{id}}" />
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue