mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-24 19:33:02 +03:00
Updated the filter naming in Post Analytics (#15898)
closes TryGhost/Team#2329 - Replace 'Received' on 'Sent' in member's filter - Moved links for feedback analytics from chart to table
This commit is contained in:
parent
26d51687b1
commit
95c3a68c34
@ -1,7 +1,7 @@
|
||||
import Component from '@glimmer/component';
|
||||
import moment from 'moment-timezone';
|
||||
import nql from '@tryghost/nql-lang';
|
||||
import {AUDIENCE_FEEDBACK_FILTER, CREATED_AT_FILTER, EMAIL_CLICKED_FILTER, EMAIL_COUNT_FILTER, EMAIL_FILTER, EMAIL_OPENED_COUNT_FILTER, EMAIL_OPENED_FILTER, EMAIL_OPEN_RATE_FILTER, EMAIL_RECEIVED_FILTER, LABEL_FILTER, LAST_SEEN_FILTER, NAME_FILTER, NEXT_BILLING_DATE_FILTER, PLAN_INTERVAL_FILTER, SIGNUP_ATTRIBUTION_FILTER, STATUS_FILTER, SUBSCRIBED_FILTER, SUBSCRIPTION_ATTRIBUTION_FILTER, SUBSCRIPTION_START_DATE_FILTER, SUBSCRIPTION_STATUS_FILTER, TIER_FILTER} from './filters';
|
||||
import {AUDIENCE_FEEDBACK_FILTER, CREATED_AT_FILTER, EMAIL_CLICKED_FILTER, EMAIL_COUNT_FILTER, EMAIL_FILTER, EMAIL_OPENED_COUNT_FILTER, EMAIL_OPENED_FILTER, EMAIL_OPEN_RATE_FILTER, EMAIL_RECEIVED_FILTER, EMAIL_SENT_FILTER, LABEL_FILTER, LAST_SEEN_FILTER, NAME_FILTER, NEXT_BILLING_DATE_FILTER, PLAN_INTERVAL_FILTER, SIGNUP_ATTRIBUTION_FILTER, STATUS_FILTER, SUBSCRIBED_FILTER, SUBSCRIPTION_ATTRIBUTION_FILTER, SUBSCRIPTION_START_DATE_FILTER, SUBSCRIPTION_STATUS_FILTER, TIER_FILTER} from './filters';
|
||||
import {TrackedArray} from 'tracked-built-ins';
|
||||
import {action} from '@ember/object';
|
||||
import {inject as service} from '@ember/service';
|
||||
@ -44,6 +44,7 @@ const FILTER_GROUPS = [
|
||||
EMAIL_OPENED_COUNT_FILTER,
|
||||
EMAIL_OPEN_RATE_FILTER,
|
||||
EMAIL_RECEIVED_FILTER,
|
||||
EMAIL_SENT_FILTER,
|
||||
EMAIL_OPENED_FILTER,
|
||||
EMAIL_CLICKED_FILTER,
|
||||
AUDIENCE_FEEDBACK_FILTER
|
||||
@ -143,13 +144,20 @@ export default class MembersFilter extends Component {
|
||||
})
|
||||
]);
|
||||
|
||||
get availableFilterProperties() {
|
||||
get filterProperties() {
|
||||
let availableFilters = FILTER_PROPERTIES;
|
||||
const hasMultipleTiers = this.membersUtils.hasMultipleTiers;
|
||||
|
||||
// exclude any filters that are behind disabled feature flags
|
||||
availableFilters = availableFilters.filter(prop => !prop.feature || this.feature[prop.feature]);
|
||||
availableFilters = availableFilters.filter(prop => !prop.setting || this.settings[prop.setting]);
|
||||
availableFilters = availableFilters.filter(prop => !prop.excludeForFeature || !this.feature[prop.excludeForFeature]);
|
||||
|
||||
return availableFilters;
|
||||
}
|
||||
|
||||
get availableFilterProperties() {
|
||||
let availableFilters = this.filterProperties;
|
||||
const hasMultipleTiers = this.membersUtils.hasMultipleTiers;
|
||||
|
||||
// exclude tiers filter if site has only single tier
|
||||
availableFilters = availableFilters
|
||||
@ -224,7 +232,7 @@ export default class MembersFilter extends Component {
|
||||
|
||||
let query = '';
|
||||
filters.forEach((filter) => {
|
||||
const filterProperty = FILTER_PROPERTIES.find(prop => prop.name === filter.type);
|
||||
const filterProperty = this.filterProperties.find(prop => prop.name === filter.type);
|
||||
|
||||
if (filterProperty.buildNqlFilter) {
|
||||
query += `${filterProperty.buildNqlFilter(filter)}+`;
|
||||
@ -283,7 +291,7 @@ export default class MembersFilter extends Component {
|
||||
const parsedFilters = [];
|
||||
|
||||
// Check custom parsing
|
||||
for (const filterProperties of FILTER_PROPERTIES) {
|
||||
for (const filterProperties of this.filterProperties) {
|
||||
if (filterProperties.parseNqlFilter) {
|
||||
// This filter has a custom parsing function
|
||||
const parsedFilter = filterProperties.parseNqlFilter(filter);
|
||||
@ -305,7 +313,7 @@ export default class MembersFilter extends Component {
|
||||
parsedFilters.push(...this.parseNqlFilter(filter.yg));
|
||||
} else {
|
||||
const filterKeys = Object.keys(filter);
|
||||
const validKeys = FILTER_PROPERTIES.map(prop => prop.name);
|
||||
const validKeys = this.filterProperties.map(prop => prop.name);
|
||||
|
||||
for (const key of filterKeys) {
|
||||
if (validKeys.includes(key)) {
|
||||
@ -339,7 +347,7 @@ export default class MembersFilter extends Component {
|
||||
const key = keys[0];
|
||||
const nqlValue = nqlFilter[key];
|
||||
|
||||
const filterProperty = FILTER_PROPERTIES.find(prop => prop.name === key);
|
||||
const filterProperty = this.filterProperties.find(prop => prop.name === key);
|
||||
|
||||
let relation;
|
||||
let value;
|
||||
@ -415,8 +423,8 @@ export default class MembersFilter extends Component {
|
||||
}
|
||||
|
||||
if (relation && value) {
|
||||
const properties = FILTER_PROPERTIES.find(prop => key === prop.name);
|
||||
if (FILTER_PROPERTIES.find(prop => key === prop.name)) {
|
||||
const properties = this.filterProperties.find(prop => key === prop.name);
|
||||
if (this.filterProperties.find(prop => key === prop.name)) {
|
||||
return new Filter({
|
||||
properties,
|
||||
relation,
|
||||
@ -473,7 +481,7 @@ export default class MembersFilter extends Component {
|
||||
newType = newType.target.value;
|
||||
}
|
||||
|
||||
const newProp = FILTER_PROPERTIES.find(prop => prop.name === newType);
|
||||
const newProp = this.filterProperties.find(prop => prop.name === newType);
|
||||
|
||||
if (!newProp) {
|
||||
// eslint-disable-next-line no-console
|
||||
@ -534,7 +542,7 @@ export default class MembersFilter extends Component {
|
||||
this.fetchFilterResourcesTask.perform();
|
||||
}
|
||||
|
||||
@action
|
||||
@action
|
||||
applyFiltersPressed(dropdown) {
|
||||
dropdown?.actions.close();
|
||||
this.applyFilter();
|
||||
|
@ -2,9 +2,10 @@ import {MATCH_RELATION_OPTIONS} from './relation-options';
|
||||
|
||||
export const EMAIL_RECEIVED_FILTER = {
|
||||
label: 'Received email',
|
||||
name: 'emails.post_id',
|
||||
valueType: 'string',
|
||||
resource: 'email',
|
||||
name: 'emails.post_id',
|
||||
valueType: 'string',
|
||||
resource: 'email',
|
||||
excludeForFeature: 'suppressionList',
|
||||
relationOptions: MATCH_RELATION_OPTIONS,
|
||||
columnLabel: 'Received email',
|
||||
getColumnValue: (member, filter) => {
|
||||
|
16
ghost/admin/app/components/members/filters/email-sent.js
Normal file
16
ghost/admin/app/components/members/filters/email-sent.js
Normal file
@ -0,0 +1,16 @@
|
||||
import {MATCH_RELATION_OPTIONS} from './relation-options';
|
||||
|
||||
export const EMAIL_SENT_FILTER = {
|
||||
label: 'Sent email',
|
||||
name: 'emails.post_id',
|
||||
valueType: 'string',
|
||||
resource: 'email',
|
||||
feature: 'suppressionList',
|
||||
relationOptions: MATCH_RELATION_OPTIONS,
|
||||
columnLabel: 'Sent email',
|
||||
getColumnValue: (member, filter) => {
|
||||
return {
|
||||
text: filter.resource?.title ?? ''
|
||||
};
|
||||
}
|
||||
};
|
@ -19,4 +19,5 @@ export * from './email-opened-count';
|
||||
export * from './email-open-rate';
|
||||
export * from './email-clicked';
|
||||
export * from './email-received';
|
||||
export * from './email-sent';
|
||||
export * from './audience-feedback';
|
||||
|
@ -50,10 +50,9 @@
|
||||
|
||||
<tabs.tabPanel>
|
||||
<Posts::PostActivityFeed
|
||||
@postId={{this.post.id}}
|
||||
@post={{this.post}}
|
||||
@eventType="sent"
|
||||
@linkQuery={{hash filterParam=(concat "emails.post_id:" this.post.id) }}
|
||||
@linkText="Received"
|
||||
/>
|
||||
</tabs.tabPanel>
|
||||
|
||||
@ -65,10 +64,9 @@
|
||||
|
||||
<tabs.tabPanel>
|
||||
<Posts::PostActivityFeed
|
||||
@postId={{this.post.id}}
|
||||
@post={{this.post}}
|
||||
@eventType="opened"
|
||||
@linkQuery={{hash filterParam=(concat "opened_emails.post_id:" this.post.id) }}
|
||||
@linkText="Opened"
|
||||
/>
|
||||
</tabs.tabPanel>
|
||||
{{/if}}
|
||||
@ -81,10 +79,9 @@
|
||||
|
||||
<tabs.tabPanel>
|
||||
<Posts::PostActivityFeed
|
||||
@postId={{this.post.id}}
|
||||
@post={{this.post}}
|
||||
@eventType="clicked"
|
||||
@linkQuery={{hash filterParam=(concat "clicked_links.post_id:" this.post.id) }}
|
||||
@linkText="Clicked"
|
||||
/>
|
||||
</tabs.tabPanel>
|
||||
{{/if}}
|
||||
@ -97,7 +94,7 @@
|
||||
|
||||
<tabs.tabPanel>
|
||||
<Posts::PostActivityFeed
|
||||
@postId={{this.post.id}}
|
||||
@post={{this.post}}
|
||||
@eventType="feedback"
|
||||
@data={{this.feedbackChartData}}
|
||||
/>
|
||||
@ -112,7 +109,7 @@
|
||||
</tabs.tab>
|
||||
|
||||
<tabs.tabPanel>
|
||||
<Posts::PostActivityFeed @postId={{this.post.id}} @eventType="conversion" />
|
||||
<Posts::PostActivityFeed @post={{this.post}} @eventType="conversion" />
|
||||
</tabs.tabPanel>
|
||||
{{/if}}
|
||||
</Tabs::Tabs>
|
||||
|
@ -30,7 +30,7 @@
|
||||
<span class="gh-feedback-events-tooltip-metric">{{this.tooltipData.label}}</span>
|
||||
</div>
|
||||
|
||||
{{#if this.tooltipData.href}}
|
||||
{{#if (and this.tooltipData.href (not (feature "suppressionList")))}}
|
||||
<div class="gh-feedback-events-tooltip-footer">
|
||||
<LinkTo
|
||||
class="gh-post-activity-feed-pagination-link gh-post-activity-chart-link"
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div class="gh-post-activity-feed">
|
||||
{{#let (activity-feed-fetcher filter=(members-event-filter post=@postId includeEvents=this.getEventTypes) pageSize=this.pageSize) as |eventsFetcher|}}
|
||||
{{#let (activity-feed-fetcher filter=(members-event-filter post=@post.id includeEvents=this.getEventTypes) pageSize=this.pageSize) as |eventsFetcher|}}
|
||||
{{#if eventsFetcher.isError}}
|
||||
<div class="gh-dashboard-list-body">
|
||||
<div class="gh-dashboard-list-error">
|
||||
@ -84,29 +84,22 @@
|
||||
{{/if}}
|
||||
|
||||
<div class="gh-post-activity-feed-footer">
|
||||
{{#if @linkQuery}}
|
||||
{{#if (feature "suppressionList")}}
|
||||
<div class="gh-post-activity-feed-pagination-link-wrapper">
|
||||
{{svg-jar "filter"}}
|
||||
See members for
|
||||
<LinkTo
|
||||
class="gh-post-activity-feed-pagination-link"
|
||||
@route="members"
|
||||
@query={{@linkQuery}}
|
||||
>
|
||||
{{@linkText}}
|
||||
</LinkTo>
|
||||
</div>
|
||||
{{else}}
|
||||
<LinkTo
|
||||
class="gh-post-activity-feed-pagination-link"
|
||||
@route="members"
|
||||
@query={{@linkQuery}}
|
||||
>
|
||||
{{svg-jar "filter"}}
|
||||
See members
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
{{#if (feature "suppressionList")}}
|
||||
<Posts::PostActivityFeed::FooterLinks
|
||||
@eventType={{this.eventType}}
|
||||
@post={{@post}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if (and @linkQuery (not (feature "suppressionList")))}}
|
||||
<LinkTo
|
||||
class="gh-post-activity-feed-pagination-link"
|
||||
@route="members"
|
||||
@query={{@linkQuery}}
|
||||
>
|
||||
{{svg-jar "filter"}}
|
||||
See members
|
||||
</LinkTo>
|
||||
{{/if}}
|
||||
|
||||
<div class="gh-post-activity-feed-pagination">
|
||||
|
@ -0,0 +1,44 @@
|
||||
{{#if (eq @eventType "sent")}}
|
||||
<Posts::PostActivityFeed::Link>
|
||||
<LinkTo
|
||||
class="gh-post-activity-feed-pagination-link"
|
||||
@route="members"
|
||||
@query={{hash filterParam=(concat "emails.post_id:" @post.id) }}
|
||||
>
|
||||
Sent
|
||||
</LinkTo>
|
||||
</Posts::PostActivityFeed::Link>
|
||||
{{else if (eq @eventType "opened")}}
|
||||
<Posts::PostActivityFeed::Link>
|
||||
<LinkTo
|
||||
class="gh-post-activity-feed-pagination-link"
|
||||
@route="members"
|
||||
@query={{hash filterParam=(concat "opened_emails.post_id:" @post.id) }}
|
||||
>
|
||||
Opened
|
||||
</LinkTo>
|
||||
</Posts::PostActivityFeed::Link>
|
||||
{{else if (eq @eventType "clicked")}}
|
||||
<Posts::PostActivityFeed::Link>
|
||||
<LinkTo
|
||||
class="gh-post-activity-feed-pagination-link"
|
||||
@route="members"
|
||||
@query={{hash filterParam=(concat "clicked_links.post_id:" @post.id) }}
|
||||
>
|
||||
Clicked
|
||||
</LinkTo>
|
||||
</Posts::PostActivityFeed::Link>
|
||||
{{else if (eq @eventType "feedback")}}
|
||||
<Posts::PostActivityFeed::Link>
|
||||
{{#each this.feedbackLinks as |link|}}
|
||||
<LinkTo
|
||||
class="gh-post-activity-feed-pagination-link"
|
||||
@route="members"
|
||||
@query={{hash filterParam=link.filterParam}}
|
||||
>
|
||||
{{link.label}}
|
||||
</LinkTo>
|
||||
{{link.separator}}
|
||||
{{/each}}
|
||||
</Posts::PostActivityFeed::Link>
|
||||
{{/if}}
|
@ -0,0 +1,48 @@
|
||||
import Component from '@glimmer/component';
|
||||
|
||||
export default class FooterLinks extends Component {
|
||||
get feedbackLinks() {
|
||||
const post = this.args.post;
|
||||
const positiveLink = {filterParam: '(feedback.post_id:' + post.id + '+feedback.score:1)', label: 'More like this'};
|
||||
const negativeLink = {filterParam: '(feedback.post_id:' + post.id + '+feedback.score:0)', label: 'Less like this'};
|
||||
|
||||
const data = [
|
||||
{link: positiveLink, hidden: !post.count.positive_feedback},
|
||||
{link: negativeLink, hidden: !post.count.negative_feedback}
|
||||
];
|
||||
|
||||
const links = this.collectLinks(data);
|
||||
return this.addSeparator(links, 'and');
|
||||
}
|
||||
|
||||
collectLinks(list) {
|
||||
const data = [];
|
||||
list.forEach((item) => {
|
||||
if (item.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
data.push(item.link);
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
addSeparator(links, separator) {
|
||||
const data = [];
|
||||
links.forEach((item, index) => {
|
||||
const link = {...item};
|
||||
const isLastItem = links.length - 1 === index;
|
||||
|
||||
if (isLastItem) {
|
||||
data.push(link);
|
||||
return;
|
||||
}
|
||||
|
||||
link.separator = separator;
|
||||
data.push(link);
|
||||
});
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
<div class="gh-post-activity-feed-pagination-link-wrapper">
|
||||
{{svg-jar "filter"}}
|
||||
See members for
|
||||
{{yield}}
|
||||
</div>
|
@ -0,0 +1,54 @@
|
||||
import {describe, it} from 'mocha';
|
||||
import {expect} from 'chai';
|
||||
import {find, render} from '@ember/test-helpers';
|
||||
import {hbs} from 'ember-cli-htmlbars';
|
||||
import {setupRenderingTest} from 'ember-mocha';
|
||||
|
||||
describe('Integration: Component: posts/post-activity-feed/footer-links', function () {
|
||||
setupRenderingTest();
|
||||
|
||||
it('renders just one link if negative feedback > 0', async function () {
|
||||
this.set('post', {id: 'id', count: {positive_feedback: 0, negative_feedback: 1}});
|
||||
await render(hbs`
|
||||
<Posts::PostActivityFeed::FooterLinks
|
||||
@eventType="feedback"
|
||||
@post={{this.post}}
|
||||
/>`);
|
||||
|
||||
const link = find('.gh-post-activity-feed-pagination-link-wrapper');
|
||||
|
||||
expect(link).to.contain.text('Less like this');
|
||||
expect(link).not.to.contain.text('and');
|
||||
expect(link).not.to.contain.text('More like this');
|
||||
});
|
||||
|
||||
it('renders just one link if positive feedback > 0', async function () {
|
||||
this.set('post', {id: 'id', count: {positive_feedback: 1, negative_feedback: 0}});
|
||||
await render(hbs`
|
||||
<Posts::PostActivityFeed::FooterLinks
|
||||
@eventType="feedback"
|
||||
@post={{this.post}}
|
||||
/>`);
|
||||
|
||||
const link = find('.gh-post-activity-feed-pagination-link-wrapper');
|
||||
|
||||
expect(link).not.to.contain.text('Less like this');
|
||||
expect(link).not.to.contain.text('and');
|
||||
expect(link).to.contain.text('More like this');
|
||||
});
|
||||
|
||||
it('renders positive and negative links with separator', async function () {
|
||||
this.set('post', {id: 'id', count: {positive_feedback: 1, negative_feedback: 1}});
|
||||
await render(hbs`
|
||||
<Posts::PostActivityFeed::FooterLinks
|
||||
@eventType="feedback"
|
||||
@post={{this.post}}
|
||||
/>`);
|
||||
|
||||
const link = find('.gh-post-activity-feed-pagination-link-wrapper');
|
||||
|
||||
expect(link).to.contain.text('Less like this');
|
||||
expect(link).to.contain.text('and');
|
||||
expect(link).to.contain.text('More like this');
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue
Block a user