mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-25 03:44:29 +03:00
Added click counts to posts admin API (#15435)
closes https://github.com/TryGhost/Team/issues/1928
This commit is contained in:
parent
e9f3d90147
commit
63103c2251
@ -122,12 +122,12 @@
|
||||
{{!-- Opens column --}}
|
||||
<LinkTo @route="editor.edit" @models={{array @post.displayName @post.id}} class="permalink gh-list-data gh-post-list-opens">
|
||||
{{#if (and @post.email.trackOpens (eq @post.email.status "submitted"))}}
|
||||
<div class="flex flex-column items-end">
|
||||
<span class="gh-content-email-stats" {{on "mouseover" (fn (mut this.isOpenStatHovered) true)}} {{on "mouseleave" (fn (mut this.isOpenStatHovered) false)}}>
|
||||
<div class="flex flex-column items-end" {{on "mouseover" (fn (mut this.isOpenStatHovered) true)}} {{on "mouseleave" (fn (mut this.isOpenStatHovered) false)}}>
|
||||
<span class="gh-content-email-stats">
|
||||
{{#if this.isOpenStatHovered}}
|
||||
<strong>1,283</strong> opens
|
||||
<strong>{{format-number @post.email.openedCount}}</strong> opens
|
||||
{{else}}
|
||||
<strong>52%</strong> opens
|
||||
<strong>{{@post.email.openRate}}%</strong> opens
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
@ -137,12 +137,12 @@
|
||||
{{!-- Clicks column --}}
|
||||
<LinkTo @route="editor.edit" @models={{array @post.displayName @post.id}} class="permalink gh-list-data gh-post-list-clicks">
|
||||
{{#if (and @post.email.trackOpens (eq @post.email.status "submitted"))}}
|
||||
<div class="flex flex-column items-end">
|
||||
<span class="gh-content-email-stats" {{on "mouseover" (fn (mut this.isClickStatHovered) true)}} {{on "mouseleave" (fn (mut this.isClickStatHovered) false)}}>
|
||||
<div class="flex flex-column items-end" {{on "mouseover" (fn (mut this.isClickStatHovered) true)}} {{on "mouseleave" (fn (mut this.isClickStatHovered) false)}}>
|
||||
<span class="gh-content-email-stats">
|
||||
{{#if this.isClickStatHovered}}
|
||||
<strong>419</strong> clicks
|
||||
<strong>{{format-number @post.count.clicks}}</strong> clicks
|
||||
{{else}}
|
||||
<strong>17%</strong> clicks
|
||||
<strong>{{@post.clickRate}}%</strong> clicks
|
||||
{{/if}}
|
||||
</span>
|
||||
</div>
|
||||
|
@ -253,6 +253,17 @@ export default Model.extend(Comparable, ValidationEngine, {
|
||||
}
|
||||
}),
|
||||
|
||||
clickRate: computed('email.emailCount', 'count.clicks', function () {
|
||||
if (!this.email || !this.email.emailCount) {
|
||||
return 0;
|
||||
}
|
||||
if (!this.count.clicks) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return Math.round(this.count.clicks / this.email.emailCount * 100);
|
||||
}),
|
||||
|
||||
_getPublishedAtBlogTZ() {
|
||||
let publishedAtUTC = this.publishedAtUTC;
|
||||
let publishedAtBlogDate = this.publishedAtBlogDate;
|
||||
|
@ -10,7 +10,8 @@ const allowedIncludes = [
|
||||
'tiers',
|
||||
'newsletter',
|
||||
'count.signups',
|
||||
'count.conversions'
|
||||
'count.conversions',
|
||||
'count.clicks'
|
||||
];
|
||||
const unsafeAttrs = ['status', 'authors', 'visibility'];
|
||||
|
||||
|
@ -6,6 +6,7 @@ const localUtils = require('../../index');
|
||||
const mobiledoc = require('../../../../../lib/mobiledoc');
|
||||
const postsMetaSchema = require('../../../../../data/schema').tables.posts_meta;
|
||||
const clean = require('./utils/clean');
|
||||
const {labs} = require('../../../../../../frontend/services/proxy');
|
||||
|
||||
function removeSourceFormats(frame) {
|
||||
if (frame.options.formats?.includes('mobiledoc') || frame.options.formats?.includes('lexical')) {
|
||||
@ -24,7 +25,11 @@ function defaultRelations(frame) {
|
||||
return false;
|
||||
}
|
||||
|
||||
frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.signups', 'count.conversions'];
|
||||
if (labs.isSet('emailClicks')) {
|
||||
frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.signups', 'count.conversions', 'count.clicks'];
|
||||
} else {
|
||||
frame.options.withRelated = ['tags', 'authors', 'authors.roles', 'email', 'tiers', 'newsletter', 'count.signups', 'count.conversions'];
|
||||
}
|
||||
}
|
||||
|
||||
function setDefaultOrder(frame) {
|
||||
|
@ -1345,6 +1345,15 @@ Post = ghostBookshelf.Model.extend({
|
||||
.whereRaw('posts.id = members_subscription_created_events.attribution_id')
|
||||
.as('count__conversions');
|
||||
});
|
||||
},
|
||||
clicks(modelOrCollection) {
|
||||
modelOrCollection.query('columns', 'posts.*', (qb) => {
|
||||
qb.countDistinct('members_link_click_events.member_id')
|
||||
.from('members_link_click_events')
|
||||
.join('link_redirects', 'members_link_click_events.link_id', 'link_redirects.id')
|
||||
.whereRaw('posts.id = link_redirects.post_id')
|
||||
.as('count__clicks');
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ Object {
|
||||
"codeinjection_head": null,
|
||||
"comment_id": Any<String>,
|
||||
"count": Object {
|
||||
"clicks": 0,
|
||||
"conversions": 0,
|
||||
"signups": 0,
|
||||
},
|
||||
@ -67,6 +68,7 @@ Object {
|
||||
"codeinjection_head": null,
|
||||
"comment_id": Any<String>,
|
||||
"count": Object {
|
||||
"clicks": 0,
|
||||
"conversions": 0,
|
||||
"signups": 0,
|
||||
},
|
||||
@ -117,7 +119,7 @@ exports[`Posts API Can browse 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": "9894",
|
||||
"content-length": "9916",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
@ -145,6 +147,7 @@ Object {
|
||||
"codeinjection_head": null,
|
||||
"comment_id": Any<String>,
|
||||
"count": Object {
|
||||
"clicks": 0,
|
||||
"conversions": 0,
|
||||
"signups": 0,
|
||||
},
|
||||
@ -196,6 +199,7 @@ Object {
|
||||
"codeinjection_head": null,
|
||||
"comment_id": Any<String>,
|
||||
"count": Object {
|
||||
"clicks": 0,
|
||||
"conversions": 0,
|
||||
"signups": 0,
|
||||
},
|
||||
@ -268,7 +272,7 @@ exports[`Posts API Can browse with formats 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": "12760",
|
||||
"content-length": "12782",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
@ -286,6 +290,7 @@ Object {
|
||||
"codeinjection_head": null,
|
||||
"comment_id": Any<String>,
|
||||
"count": Object {
|
||||
"clicks": 0,
|
||||
"conversions": 0,
|
||||
"signups": 0,
|
||||
},
|
||||
@ -337,7 +342,7 @@ exports[`Posts API Create Can create a post with lexical 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": "3743",
|
||||
"content-length": "3754",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/posts\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
|
||||
@ -356,6 +361,7 @@ Object {
|
||||
"codeinjection_head": null,
|
||||
"comment_id": Any<String>,
|
||||
"count": Object {
|
||||
"clicks": 0,
|
||||
"conversions": 0,
|
||||
"signups": 0,
|
||||
},
|
||||
@ -407,7 +413,7 @@ exports[`Posts API Create Can create a post with mobiledoc 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": "3559",
|
||||
"content-length": "3570",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/posts\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
|
||||
@ -527,6 +533,7 @@ Object {
|
||||
"codeinjection_head": null,
|
||||
"comment_id": Any<String>,
|
||||
"count": Object {
|
||||
"clicks": 0,
|
||||
"conversions": 0,
|
||||
"signups": 0,
|
||||
},
|
||||
@ -578,7 +585,7 @@ exports[`Posts API Update Can update a post with lexical 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": "3694",
|
||||
"content-length": "3705",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/posts\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
|
||||
@ -597,6 +604,7 @@ Object {
|
||||
"codeinjection_head": null,
|
||||
"comment_id": Any<String>,
|
||||
"count": Object {
|
||||
"clicks": 0,
|
||||
"conversions": 0,
|
||||
"signups": 0,
|
||||
},
|
||||
@ -648,7 +656,7 @@ exports[`Posts API Update Can update a post with lexical 4: [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": "3691",
|
||||
"content-length": "3702",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
@ -667,6 +675,7 @@ Object {
|
||||
"codeinjection_head": null,
|
||||
"comment_id": Any<String>,
|
||||
"count": Object {
|
||||
"clicks": 0,
|
||||
"conversions": 0,
|
||||
"signups": 0,
|
||||
},
|
||||
@ -718,7 +727,7 @@ exports[`Posts API Update Can update a post with mobiledoc 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": "3504",
|
||||
"content-length": "3515",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"location": StringMatching /https\\?:\\\\/\\\\/\\.\\*\\?\\\\/posts\\\\/\\[a-f0-9\\]\\{24\\}\\\\//,
|
||||
@ -737,6 +746,7 @@ Object {
|
||||
"codeinjection_head": null,
|
||||
"comment_id": Any<String>,
|
||||
"count": Object {
|
||||
"clicks": 0,
|
||||
"conversions": 0,
|
||||
"signups": 0,
|
||||
},
|
||||
@ -788,7 +798,7 @@ exports[`Posts API Update Can update a post with mobiledoc 4: [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": "3501",
|
||||
"content-length": "3512",
|
||||
"content-type": "application/json; charset=utf-8",
|
||||
"etag": StringMatching /\\(\\?:W\\\\/\\)\\?"\\(\\?:\\[ !#-\\\\x7E\\\\x80-\\\\xFF\\]\\*\\|\\\\r\\\\n\\[\\\\t \\]\\|\\\\\\\\\\.\\)\\*"/,
|
||||
"vary": "Origin, Accept-Encoding",
|
||||
|
@ -180,6 +180,7 @@ Object {
|
||||
"codeinjection_head": null,
|
||||
"comment_id": StringMatching /\\[a-f0-9\\]\\{24\\}/,
|
||||
"count": Object {
|
||||
"clicks": 0,
|
||||
"conversions": 0,
|
||||
"signups": 0,
|
||||
},
|
||||
|
Loading…
Reference in New Issue
Block a user