mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 03:44:29 +03:00
Added email track clicks column and cleaned up frontend checks (#15501)
fixes https://github.com/TryGhost/Team/issues/2008 - New column that stores email click tracking at the time it was created - Improved frontend side checks for when to show analytics
This commit is contained in:
parent
22a75ba144
commit
0cd0fc838d
@ -117,7 +117,7 @@
|
||||
|
||||
{{!-- Opened / Signups column --}}
|
||||
<LinkTo @route={{this.routeForLink}} @models={{this.modelsForLink}} class="permalink gh-list-data gh-post-list-opens">
|
||||
{{#if (and @post.email (not-eq this.settings.membersSignupAccess "none") (not-eq this.settings.editorDefaultEmailRecipients "disabled") (not this.session.user.isContributor) (or @post.isSent @post.isPublished) this.settings.emailTrackOpens @post.email.trackOpens this.feature.emailAnalytics (eq @post.displayName "post"))}}
|
||||
{{#if @post.showEmailOpenAnalytics }}
|
||||
<LinkTo @route="members" @query={{hash filterParam=(concat "opened_emails.post_id:[" @post.id "]") }} class="flex flex-column gh-post-row-event" {{on "mouseover" (fn (mut this.isOpenStatHovered) true)}} {{on "mouseleave" (fn (mut this.isOpenStatHovered) false)}}>
|
||||
<span class="gh-content-email-stats-value">
|
||||
{{#if this.isHovered}}
|
||||
@ -130,9 +130,7 @@
|
||||
opened
|
||||
</span>
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
|
||||
{{#if (and this.feature.memberAttribution (not-eq this.settings.membersSignupAccess "none") (and @post.isPage) (not this.session.user.isContributor)) }}
|
||||
{{else if (and @post.isPage @post.showAttributionAnalytics) }}
|
||||
<LinkTo @route="members" @query={{hash filterParam=(concat "signup:[" @post.id "]") }} class="flex flex-column gh-post-row-event" {{on "mouseover" (fn (mut this.isOpenStatHovered) true)}} {{on "mouseleave" (fn (mut this.isOpenStatHovered) false)}}>
|
||||
<span class="gh-content-email-stats-value">
|
||||
{{@post.count.signups}}
|
||||
@ -146,7 +144,7 @@
|
||||
|
||||
{{!-- Clicked / Conversions column --}}
|
||||
<LinkTo @route={{this.routeForLink}} @models={{this.modelsForLink}} class="permalink gh-list-data gh-post-list-clicks">
|
||||
{{#if (and @post.email (not-eq this.settings.membersSignupAccess "none") (not-eq this.settings.editorDefaultEmailRecipients "disabled") (not this.session.user.isContributor) (or @post.isSent @post.isPublished) this.settings.emailTrackClicks this.feature.emailAnalytics)}}
|
||||
{{#if @post.showEmailClickAnalytics }}
|
||||
<LinkTo @route="members" @query={{hash filterParam=(concat "clicked_links.post_id:[" @post.id "]") }} class="flex flex-column gh-post-row-event" {{on "mouseover" (fn (mut this.isClickStatHovered) true)}} {{on "mouseleave" (fn (mut this.isClickStatHovered) false)}}>
|
||||
<span class="gh-content-email-stats-value">
|
||||
{{#if this.isHovered}}
|
||||
@ -159,9 +157,7 @@
|
||||
clicked
|
||||
</span>
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
|
||||
{{#if (and this.feature.memberAttribution (not-eq this.settings.membersSignupAccess "none") (and @post.isPage) (not this.session.user.isContributor)) }}
|
||||
{{else if (and @post.isPage @post.showPaidAttributionAnalytics) }}
|
||||
<LinkTo @route="members" @query={{hash filterParam=(concat "conversion:[" @post.id "]") }} class="flex flex-column gh-post-row-event" {{on "mouseover" (fn (mut this.isClickStatHovered) true)}} {{on "mouseleave" (fn (mut this.isClickStatHovered) false)}}>
|
||||
<span class="gh-content-email-stats-value">
|
||||
{{@post.count.paid_conversions}}
|
||||
@ -176,7 +172,7 @@
|
||||
{{!-- Button column --}}
|
||||
<LinkTo @route={{this.routeForLink}} @models={{this.modelsForLink}} class="permalink gh-list-data gh-post-list-button">
|
||||
<div class="gh-list-data-inner">
|
||||
{{#if this.isAnalytics}}
|
||||
{{#if @post.hasAnalyticsPage }}
|
||||
<LinkTo @route={{this.routeForLink}} @models={{this.modelsForLink}} class="gh-post-list-cta stats {{if this.isHovered "is-hovered"}}" title="">
|
||||
{{svg-jar "stats" title=""}}
|
||||
</LinkTo>
|
||||
|
@ -24,10 +24,6 @@ export default class PostsListItemClicks extends Component {
|
||||
return text.join(' ');
|
||||
}
|
||||
|
||||
get isAnalytics() {
|
||||
return this.args.post.hasAnalytics;
|
||||
}
|
||||
|
||||
get routeForLink() {
|
||||
if (this.isAnalytics) {
|
||||
return 'posts.analytics';
|
||||
|
@ -52,7 +52,7 @@
|
||||
</LinkTo>
|
||||
</div>
|
||||
|
||||
{{#if (and this.post.email.trackOpens this.settings.emailTrackOpens) }}
|
||||
{{#if this.post.showEmailOpenAnalytics }}
|
||||
<div class="gh-post-analytics-item">
|
||||
<LinkTo @route="members" @query={{hash filterParam=(concat "opened_emails.post_id:[" this.post.id "]") }}>
|
||||
<h3>{{this.post.email.openRate}}<sup>%</sup></h3>
|
||||
@ -61,7 +61,7 @@
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.settings.emailTrackClicks}}
|
||||
{{#if this.post.showEmailClickAnalytics }}
|
||||
<div class="gh-post-analytics-item">
|
||||
<LinkTo @route="members" @query={{hash filterParam=(concat "clicked_links.post_id:[" this.post.id "]") }}>
|
||||
<h3>{{this.post.clickRate}}<sup>%</sup></h3>
|
||||
@ -71,7 +71,7 @@
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#if (and (feature 'memberAttribution') (not this.post.emailOnly)) }}
|
||||
{{#if this.post.showAttributionAnalytics }}
|
||||
<div class="gh-post-analytics-item">
|
||||
<LinkTo @route="members" @query={{hash filterParam=(concat "signup:[" this.post.id "]") }}>
|
||||
<h3>{{format-number this.post.count.signups}}</h3>
|
||||
@ -79,7 +79,7 @@
|
||||
</LinkTo>
|
||||
</div>
|
||||
|
||||
{{#if this.membersUtils.paidMembersEnabled}}
|
||||
{{#if this.post.showPaidAttributionAnalytics }}
|
||||
<div class="gh-post-analytics-item">
|
||||
<LinkTo @route="members" @query={{hash filterParam=(concat "conversion:[" this.post.id "]") }}>
|
||||
<h3>{{format-number this.post.count.paid_conversions}}</h3>
|
||||
|
@ -119,11 +119,11 @@ export default class Analytics extends Component {
|
||||
}
|
||||
|
||||
get showLinks() {
|
||||
return this.settings.get('emailTrackClicks') && (this.post.isSent || (this.post.isPublished && this.post.email));
|
||||
return this.post.showEmailClickAnalytics;
|
||||
}
|
||||
|
||||
get showSources() {
|
||||
return this.feature.get('sourceAttribution') && !this.membersUtils.isMembersInviteOnly && !this.post.emailOnly;
|
||||
return this.feature.get('sourceAttribution') && this.post.showAttributionAnalytics;
|
||||
}
|
||||
|
||||
get isLoaded() {
|
||||
|
@ -19,6 +19,7 @@ export default Model.extend({
|
||||
failedCount: attr('number', {defaultValue: 0}),
|
||||
|
||||
trackOpens: attr('boolean'),
|
||||
trackClicks: attr('boolean'),
|
||||
|
||||
createdAtUTC: attr('moment-utc'),
|
||||
createdBy: attr('string'),
|
||||
|
@ -73,6 +73,7 @@ export default Model.extend(Comparable, ValidationEngine, {
|
||||
ghostPaths: service(),
|
||||
clock: service(),
|
||||
settings: service(),
|
||||
membersUtils: service(),
|
||||
|
||||
displayName: 'post',
|
||||
validationType: 'post',
|
||||
@ -182,20 +183,44 @@ export default Model.extend(Comparable, ValidationEngine, {
|
||||
return this.isScheduled && !!this.newsletter && !this.email;
|
||||
}),
|
||||
|
||||
hasAnalytics: computed('isPost', 'isSent', 'isPublished', 'email', function () {
|
||||
showEmailOpenAnalytics: computed('isPost', 'isSent', 'isPublished', 'email', function () {
|
||||
return this.isPost
|
||||
&& !this.session.user.isContributor
|
||||
&& this.settings.get('membersSignupAccess') !== 'none'
|
||||
&& this.settings.get('editorDefaultEmailRecipients') !== 'disabled'
|
||||
&& (this.isSent || this.isPublished)
|
||||
&& this.email
|
||||
&& this.email.trackOpens
|
||||
&& this.settings.get('emailTrackOpens');
|
||||
}),
|
||||
|
||||
showEmailClickAnalytics: computed('isPost', 'isSent', 'isPublished', 'email', function () {
|
||||
return this.isPost
|
||||
&& !this.session.user.isContributor
|
||||
&& this.settings.get('membersSignupAccess') !== 'none'
|
||||
&& this.settings.get('editorDefaultEmailRecipients') !== 'disabled'
|
||||
&& (this.isSent || this.isPublished)
|
||||
&& this.email
|
||||
&& this.email.trackClicks
|
||||
&& this.settings.get('emailTrackClicks');
|
||||
}),
|
||||
|
||||
showAttributionAnalytics: computed('isPage', 'emailOnly', 'isPublished', 'membersUtils.isMembersInviteOnly', function () {
|
||||
return (this.isPage || !this.emailOnly)
|
||||
&& this.isPublished
|
||||
&& this.feature.get('memberAttribution')
|
||||
&& !this.membersUtils.isMembersInviteOnly
|
||||
&& !this.session.user.isContributor;
|
||||
}),
|
||||
|
||||
showPaidAttributionAnalytics: computed.and('showAttributionAnalytics', 'membersUtils.paidMembersEnabled'),
|
||||
|
||||
hasAnalyticsPage: computed('isPost', 'showEmailOpenAnalytics', 'showEmailClickAnalytics', 'showAttributionAnalytics', function () {
|
||||
return this.isPost
|
||||
&& (
|
||||
(
|
||||
// We have clicks or opens data
|
||||
(this.isSent || (this.isPublished && this.email))
|
||||
&& (this.settings.get('emailTrackClicks') || this.settings.get('emailTrackOpens'))
|
||||
)
|
||||
|| (
|
||||
// We have attribution data for pubished posts
|
||||
this.isPublished && this.feature.get('memberAttribution')
|
||||
)
|
||||
this.showEmailOpenAnalytics
|
||||
|| this.showEmailClickAnalytics
|
||||
|| this.showAttributionAnalytics
|
||||
);
|
||||
}),
|
||||
|
||||
|
@ -0,0 +1,7 @@
|
||||
const {createAddColumnMigration} = require('../../utils');
|
||||
|
||||
module.exports = createAddColumnMigration('emails', 'track_clicks', {
|
||||
type: 'bool',
|
||||
nullable: false,
|
||||
defaultTo: false
|
||||
});
|
@ -723,6 +723,7 @@ module.exports = {
|
||||
html: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
|
||||
plaintext: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
|
||||
track_opens: {type: 'bool', nullable: false, defaultTo: false},
|
||||
track_clicks: {type: 'bool', nullable: false, defaultTo: false},
|
||||
submitted_at: {type: 'dateTime', nullable: false},
|
||||
newsletter_id: {type: 'string', maxlength: 24, nullable: true, references: 'newsletters.id'},
|
||||
created_at: {type: 'dateTime', nullable: false},
|
||||
|
@ -10,6 +10,7 @@ const Email = ghostBookshelf.Model.extend({
|
||||
status: 'pending',
|
||||
recipient_filter: 'status:-free',
|
||||
track_opens: false,
|
||||
track_clicks: false,
|
||||
delivered_count: 0,
|
||||
opened_count: 0,
|
||||
failed_count: 0
|
||||
|
@ -239,6 +239,7 @@ const addEmail = async (postModel, options) => {
|
||||
plaintext: emailData.plaintext,
|
||||
submitted_at: moment().toDate(),
|
||||
track_opens: !!settingsCache.get('email_track_opens'),
|
||||
track_clicks: !!settingsCache.get('email_track_clicks'),
|
||||
recipient_filter: emailRecipientFilter,
|
||||
newsletter_id: newsletter.id
|
||||
}, knexOptions);
|
||||
|
@ -22,6 +22,7 @@ Object {
|
||||
"status": "submitted",
|
||||
"subject": "You got mailed!",
|
||||
"submitted_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"track_clicks": false,
|
||||
"track_opens": false,
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
@ -45,6 +46,7 @@ Object {
|
||||
"status": "failed",
|
||||
"subject": "You got mailed! Again!",
|
||||
"submitted_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"track_clicks": false,
|
||||
"track_opens": false,
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
@ -67,7 +69,7 @@ exports[`Emails API Can browse emails 2: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "1243",
|
||||
"content-length": "1285",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Accept-Version, Origin, Accept-Encoding",
|
||||
@ -97,6 +99,7 @@ Object {
|
||||
"status": "submitted",
|
||||
"subject": "You got mailed!",
|
||||
"submitted_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"track_clicks": false,
|
||||
"track_opens": false,
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
@ -109,7 +112,7 @@ exports[`Emails API Can read an email 2: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "561",
|
||||
"content-length": "582",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Accept-Version, Origin, Accept-Encoding",
|
||||
@ -139,6 +142,7 @@ Object {
|
||||
"status": "pending",
|
||||
"subject": "You got mailed! Again!",
|
||||
"submitted_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"track_clicks": false,
|
||||
"track_opens": false,
|
||||
"updated_at": StringMatching /\\\\d\\{4\\}-\\\\d\\{2\\}-\\\\d\\{2\\}T\\\\d\\{2\\}:\\\\d\\{2\\}:\\\\d\\{2\\}\\\\\\.000Z/,
|
||||
"uuid": StringMatching /\\[a-f0-9\\]\\{8\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{4\\}-\\[a-f0-9\\]\\{12\\}/,
|
||||
@ -151,7 +155,7 @@ exports[`Emails API Can retry a failed email 2: [headers] 1`] = `
|
||||
Object {
|
||||
"access-control-allow-origin": "http://127.0.0.1:2369",
|
||||
"cache-control": "no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0",
|
||||
"content-length": "607",
|
||||
"content-length": "628",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Accept-Version, Origin, Accept-Encoding",
|
||||
|
@ -35,7 +35,7 @@ const validateRouteSettings = require('../../../../../core/server/services/route
|
||||
*/
|
||||
describe('DB version integrity', function () {
|
||||
// Only these variables should need updating
|
||||
const currentSchemaHash = '999d625400e6d87efe0fd66e6bda4059';
|
||||
const currentSchemaHash = '2a59debcacc1e3dc0b15e2f729ca4bdb';
|
||||
const currentFixturesHash = '8cf221f0ed930ac1fe8030a58e60d64b';
|
||||
const currentSettingsHash = '2978a5684a2d5fcf089f61f5d368a0c0';
|
||||
const currentRoutesHash = '3d180d52c663d173a6be791ef411ed01';
|
||||
|
Loading…
Reference in New Issue
Block a user