Panels with campaign statistics and some fixes in computation of clicks.
This commit is contained in:
parent
ba996d845d
commit
d103a2cc79
18 changed files with 811 additions and 96 deletions
|
@ -10,13 +10,14 @@ const shares = require('./shares');
|
|||
const namespaceHelpers = require('../lib/namespace-helpers');
|
||||
const files = require('./files');
|
||||
const templates = require('./templates');
|
||||
const { CampaignStatus, CampaignSource, CampaignType, getSendConfigurationPermissionRequiredForSend} = require('../../shared/campaigns');
|
||||
const { CampaignStatus, CampaignSource, CampaignType, getSendConfigurationPermissionRequiredForSend } = require('../../shared/campaigns');
|
||||
const sendConfigurations = require('./send-configurations');
|
||||
const triggers = require('./triggers');
|
||||
const {SubscriptionStatus} = require('../../shared/lists');
|
||||
const subscriptions = require('./subscriptions');
|
||||
const segments = require('./segments');
|
||||
const senders = require('../lib/senders');
|
||||
const {LinkId} = require('./links');
|
||||
|
||||
const allowedKeysCommon = ['name', 'description', 'segment', 'namespace',
|
||||
'send_configuration', 'from_name_override', 'from_email_override', 'reply_to_override', 'subject_override', 'data', 'click_tracking_disabled', 'open_tracking_disabled', 'unsubscribe_url'];
|
||||
|
@ -158,7 +159,8 @@ async function listTestUsersDTAjax(context, campaignId, params) {
|
|||
subsQry = knex.raw(subsUnionSql, subsUnionBindings);
|
||||
}
|
||||
|
||||
return await dtHelpers.ajaxListWithPermissions(
|
||||
return await dtHelpers.ajaxListWithPermissionsTx(
|
||||
tx,
|
||||
context,
|
||||
[{ entityTypeId: 'list', requiredOperations: ['viewSubscriptions'], column: 'subs.list_id' }],
|
||||
params,
|
||||
|
@ -191,6 +193,115 @@ async function listTestUsersDTAjax(context, campaignId, params) {
|
|||
});
|
||||
}
|
||||
|
||||
async function _listSubscriberResultsDTAjax(context, campaignId, getSubsQrys, columns, params) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', campaignId, 'view');
|
||||
|
||||
const subsQrys = [];
|
||||
const cpgLists = await tx('campaign_lists').where('campaign', campaignId);
|
||||
|
||||
for (const cpgList of cpgLists) {
|
||||
const subsTable = subscriptions.getSubscriptionTableName(cpgList.list);
|
||||
subsQrys.push(getSubsQrys(subsTable, cpgList));
|
||||
}
|
||||
|
||||
if (subsQrys.length > 0) {
|
||||
let subsSql, subsBindings;
|
||||
|
||||
if (subsQrys.length === 1) {
|
||||
subsSql = '(' + subsQrys[0].sql + ') as `subs`'
|
||||
subsBindings = subsQrys[0].bindings;
|
||||
|
||||
} else {
|
||||
subsSql = '(' +
|
||||
subsQrys.map(qry => '(' + qry.sql + ')').join(' UNION ALL ') +
|
||||
') as `subs`';
|
||||
subsBindings = Array.prototype.concat(...subsQrys.map(qry => qry.bindings));
|
||||
}
|
||||
|
||||
return await dtHelpers.ajaxListWithPermissionsTx(
|
||||
tx,
|
||||
context,
|
||||
[{ entityTypeId: 'list', requiredOperations: ['viewSubscriptions'], column: 'lists.id' }],
|
||||
params,
|
||||
(builder, tx) => builder.from(knex.raw(subsSql, subsBindings))
|
||||
.innerJoin('lists', 'subs.list', 'lists.id')
|
||||
.innerJoin('namespaces', 'lists.namespace', 'namespaces.id')
|
||||
,
|
||||
columns
|
||||
);
|
||||
|
||||
} else {
|
||||
const result = {
|
||||
draw: params.draw,
|
||||
recordsTotal: 0,
|
||||
recordsFiltered: 0,
|
||||
data: []
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function listSentByStatusDTAjax(context, campaignId, status, params) {
|
||||
return await _listSubscriberResultsDTAjax(
|
||||
context,
|
||||
campaignId,
|
||||
(subsTable, cpgList) => knex.from(subsTable)
|
||||
.innerJoin(
|
||||
function () {
|
||||
return this.from('campaign_messages')
|
||||
.where('campaign_messages.campaign', campaignId)
|
||||
.where('campaign_messages.list', cpgList.list)
|
||||
.where('campaign_messages.status', status)
|
||||
.as('related_campaign_messages');
|
||||
},
|
||||
'related_campaign_messages.subscription', subsTable + '.id')
|
||||
.select([subsTable + '.email', subsTable + '.cid', knex.raw('? AS list', [cpgList.list])])
|
||||
.toSQL().toNative(),
|
||||
[ 'subs.email', 'subs.cid', 'lists.cid', 'lists.name', 'namespaces.name' ],
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
async function listOpensDTAjax(context, campaignId, params) {
|
||||
return await _listSubscriberResultsDTAjax(
|
||||
context,
|
||||
campaignId,
|
||||
(subsTable, cpgList) => knex.from(subsTable)
|
||||
.innerJoin(
|
||||
function () {
|
||||
return this.from('campaign_links')
|
||||
.where('campaign_links.campaign', campaignId)
|
||||
.where('campaign_links.list', cpgList.list)
|
||||
.where('campaign_links.link', LinkId.OPEN)
|
||||
.as('related_campaign_links');
|
||||
},
|
||||
'related_campaign_links.subscription', subsTable + '.id')
|
||||
.select([subsTable + '.email', subsTable + '.cid', knex.raw('? AS list', [cpgList.list]), 'related_campaign_links.count'])
|
||||
.toSQL().toNative(),
|
||||
[ 'subs.email', 'subs.cid', 'lists.cid', 'lists.name', 'namespaces.name', 'subs.count' ],
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
async function listLinkClicksDTAjax(context, campaignId, params) {
|
||||
return await knex.transaction(async (tx) => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', campaignId, 'viewStats');
|
||||
|
||||
return await dtHelpers.ajaxListTx(
|
||||
tx,
|
||||
params,
|
||||
builder => builder.from('links')
|
||||
.where('links.campaign', campaignId),
|
||||
[ 'links.url', 'links.visits', 'links.hits' ]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function getTrackingSettingsByCidTx(tx, cid) {
|
||||
const entity = await tx('campaigns').where('campaigns.cid', cid)
|
||||
.select([
|
||||
|
@ -511,6 +622,8 @@ async function _removeTx(tx, context, id, existing = null) {
|
|||
await tx('campaign_messages').where('campaign', id).del();
|
||||
await tx('campaign_links').where('campaign', id).del();
|
||||
|
||||
await tx('links').where('campaign', id).del();
|
||||
|
||||
await triggers.removeAllByCampaignIdTx(tx, context, id);
|
||||
|
||||
await tx('template_dep_campaigns')
|
||||
|
@ -779,11 +892,19 @@ async function reset(context, campaignId) {
|
|||
}
|
||||
|
||||
await tx('campaigns').where('id', campaignId).update({
|
||||
status: CampaignStatus.IDLE
|
||||
status: CampaignStatus.IDLE,
|
||||
delivered: 0,
|
||||
unsubscribed: 0,
|
||||
bounced: 0,
|
||||
complained: 0,
|
||||
blacklisted: 0,
|
||||
opened: 0,
|
||||
clicks: 0
|
||||
});
|
||||
|
||||
await tx('campaign_messages').where('campaign', campaignId).del();
|
||||
await tx('campaign_links').where('campaign', campaignId).del();
|
||||
await tx('links').where('campaign', campaignId).del();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -796,14 +917,51 @@ async function disable(context, campaignId) {
|
|||
}
|
||||
|
||||
|
||||
async function getStatisticsOverview(context, id) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'viewStats');
|
||||
|
||||
const stats = await tx('campaigns').where('id', id).select(['delivered', 'unsubscribed', 'bounced', 'complained', 'blacklisted', 'opened', 'clicks']).first();
|
||||
|
||||
const totalQryGen = await getSubscribersQueryGeneratorTx(tx, id, false);
|
||||
if (totalQryGen) {
|
||||
const res = await totalQryGen(tx).count('* AS subscriptionsTotal').first();
|
||||
stats.total = res.subscriptionsTotal;
|
||||
} else {
|
||||
stats.total = 0;
|
||||
}
|
||||
|
||||
return stats;
|
||||
});
|
||||
}
|
||||
|
||||
async function getStatisticsOpened(context, id) {
|
||||
return await knex.transaction(async tx => {
|
||||
await shares.enforceEntityPermissionTx(tx, context, 'campaign', id, 'viewStats');
|
||||
|
||||
const devices = await tx('campaign_links').where('campaign', id).groupBy('device_type').select('device_type AS key').count('* as count');
|
||||
const countries = await tx('campaign_links').where('campaign', id).groupBy('country').select('country AS key').count('* as count');
|
||||
|
||||
return {
|
||||
devices,
|
||||
countries
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.Content = Content;
|
||||
module.exports.hash = hash;
|
||||
|
||||
module.exports.listDTAjax = listDTAjax;
|
||||
module.exports.listChildrenDTAjax = listChildrenDTAjax;
|
||||
module.exports.listWithContentDTAjax = listWithContentDTAjax;
|
||||
module.exports.listOthersWhoseListsAreIncludedDTAjax = listOthersWhoseListsAreIncludedDTAjax;
|
||||
module.exports.listTestUsersDTAjax = listTestUsersDTAjax;
|
||||
module.exports.listSentByStatusDTAjax = listSentByStatusDTAjax;
|
||||
module.exports.listOpensDTAjax = listOpensDTAjax;
|
||||
module.exports.listLinkClicksDTAjax = listLinkClicksDTAjax;
|
||||
|
||||
|
||||
module.exports.getByIdTx = getByIdTx;
|
||||
module.exports.getById = getById;
|
||||
module.exports.create = create;
|
||||
|
@ -829,4 +987,6 @@ module.exports.enable = enable;
|
|||
module.exports.disable = disable;
|
||||
|
||||
module.exports.rawGetByTx = rawGetByTx;
|
||||
module.exports.getTrackingSettingsByCidTx = getTrackingSettingsByCidTx;
|
||||
module.exports.getTrackingSettingsByCidTx = getTrackingSettingsByCidTx;
|
||||
module.exports.getStatisticsOverview = getStatisticsOverview;
|
||||
module.exports.getStatisticsOpened = getStatisticsOpened;
|
||||
|
|
|
@ -14,6 +14,7 @@ const he = require('he');
|
|||
const { enforce } = require('../lib/helpers');
|
||||
const { getPublicUrl } = require('../lib/urls');
|
||||
const tools = require('../lib/tools');
|
||||
const shortid = require('shortid');
|
||||
|
||||
const LinkId = {
|
||||
OPEN: -1,
|
||||
|
@ -41,7 +42,7 @@ async function countLink(remoteIp, userAgent, campaignCid, listCid, subscription
|
|||
campaign: campaign.id,
|
||||
list: list.id,
|
||||
subscription: subscription.id,
|
||||
link: linkId,
|
||||
link: clickLinkId,
|
||||
ip: remoteIp,
|
||||
device_type: device.type,
|
||||
country
|
||||
|
@ -49,7 +50,7 @@ async function countLink(remoteIp, userAgent, campaignCid, listCid, subscription
|
|||
|
||||
const campaignLinksQryResult = await tx.raw(campaignLinksQry.sql + (incrementOnDup ? ' ON DUPLICATE KEY UPDATE `count`=`count`+1' : ''), campaignLinksQry.bindings);
|
||||
|
||||
if (campaignLinksQryResult.affectedRows > 1) { // When using DUPLICATE KEY UPDATE, this means that the entry was already there
|
||||
if (campaignLinksQryResult[0].affectedRows > 1) { // When using DUPLICATE KEY UPDATE, this means that the entry was already there
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -84,7 +85,10 @@ async function countLink(remoteIp, userAgent, campaignCid, listCid, subscription
|
|||
|
||||
// Update clicks
|
||||
if (linkId > LinkId.GENERAL_CLICK && !campaign.click_tracking_disabled) {
|
||||
await tx('links').increment('hits').where('id', linkId);
|
||||
if (await _countLink(linkId, true)) {
|
||||
await tx('links').increment('visits').where('id', linkId);
|
||||
|
||||
if (await _countLink(LinkId.GENERAL_CLICK, false)) {
|
||||
await tx('campaigns').increment('clicks').where('id', campaign.id);
|
||||
}
|
||||
|
@ -103,7 +107,7 @@ async function countLink(remoteIp, userAgent, campaignCid, listCid, subscription
|
|||
|
||||
async function addOrGet(campaignId, url) {
|
||||
return await knex.transaction(async tx => {
|
||||
const link = tx('links').select(['id', 'cid']).where({
|
||||
const link = await tx('links').select(['id', 'cid']).where({
|
||||
campaign: campaignId,
|
||||
url
|
||||
}).first();
|
||||
|
@ -111,7 +115,7 @@ async function addOrGet(campaignId, url) {
|
|||
if (!link) {
|
||||
let cid = shortid.generate();
|
||||
|
||||
const ids = tx('links').insert({
|
||||
const ids = await tx('links').insert({
|
||||
campaign: campaignId,
|
||||
cid,
|
||||
url
|
||||
|
|
|
@ -5,7 +5,7 @@ const { filterObject } = require('../lib/helpers');
|
|||
const hasher = require('node-object-hash')();
|
||||
const shares = require('./shares');
|
||||
|
||||
const allowedKeys = new Set(['adminEmail', 'uaCode', 'shoutout', 'pgpPassphrase', 'pgpPrivateKey', 'defaultHomepage']);
|
||||
const allowedKeys = new Set(['adminEmail', 'uaCode', 'mapsApiKey', 'shoutout', 'pgpPassphrase', 'pgpPrivateKey', 'defaultHomepage']);
|
||||
// defaultHomepage is used as a default to list.homepage - if the list.homepage is not filled in
|
||||
|
||||
function hash(entity) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue