0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-24 23:48:13 -05:00

Added newsletter subscription filtering to members (#16006)

closes https://github.com/TryGhost/Team/issues/2012

- Members can now be filtered based on the newsletters they are
subscribed to.
- Defaults to the existing newsletter filtering if user does not have
more than 1 newsletter.
This commit is contained in:
Ronald Langeveld 2022-12-15 16:06:47 +07:00 committed by GitHub
parent 64e44444c1
commit ce53c76fdf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 79 additions and 11 deletions

View file

@ -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, 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 {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, NEWSLETTERS_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';
@ -144,9 +144,22 @@ export default class MembersFilter extends Component {
})
]);
newsletters;
get filterProperties() {
let availableFilters = FILTER_PROPERTIES;
// find list of newsletters from store and add them to filter list if there are more than one newsletter
// it also removes the 'subscribed' filter from the list as that would unsubscribe members from all newsletters, instead replace it with a filter for each newsletter
if (this.newsletters.length > 1) {
// remove the 'subscribed' filter from the list
availableFilters = availableFilters.filter(prop => prop.name !== 'subscribed');
// find the index of the 'basic' group and insert the 'multiple newsletters' filter after it
const indexes = availableFilters.map((obj, index) => (obj.group === 'Basic' ? index : null)).filter(i => i !== null);
const lastIndex = indexes.pop();
availableFilters.splice(lastIndex + 1, 0, ...NEWSLETTERS_FILTER(this.newsletters));
}
// 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]);
@ -184,9 +197,7 @@ export default class MembersFilter extends Component {
constructor(...args) {
super(...args);
this.parseDefaultFilters();
this.fetchTiers.perform();
}
/**
@ -198,7 +209,11 @@ export default class MembersFilter extends Component {
* -> better future proof solution: move the filter parsing logic elsewhere so it can be parsed in the members controller
*/
@action
parseDefaultFilters() {
async parseDefaultFilters() {
// we need to make sure all the filters are loaded before parsing the default filter
// otherwise the filter will be parsed with the wrong properties
await this.fetchTiers.perform();
await this.fetchNewsletters.perform();
if (this.args.defaultFilterParam) {
// check if it is different before parsing
const validFilters = this.validFilters;
@ -233,7 +248,6 @@ export default class MembersFilter extends Component {
let query = '';
filters.forEach((filter) => {
const filterProperty = this.filterProperties.find(prop => prop.name === filter.type);
if (filterProperty.buildNqlFilter) {
query += `${filterProperty.buildNqlFilter(filter)}+`;
return;
@ -276,7 +290,6 @@ export default class MembersFilter extends Component {
parseNqlFilterString(filterParam) {
let filters;
try {
filters = nql.parse(filterParam);
} catch (e) {
@ -289,8 +302,6 @@ export default class MembersFilter extends Component {
parseNqlFilter(filter) {
const parsedFilters = [];
// Check custom parsing
for (const filterProperties of this.filterProperties) {
if (filterProperties.parseNqlFilter) {
// This filter has a custom parsing function
@ -573,6 +584,13 @@ export default class MembersFilter extends Component {
this.tiersList = response;
}
@task({drop: true})
*fetchNewsletters() {
const response = yield this.store.query('newsletter', {filter: 'status:active'});
this.newsletters = response;
return response;
}
@task({restartable: true})
*fetchFilterResourcesTask() {
const ids = [];

View file

@ -1,9 +1,9 @@
import {MATCH_RELATION_OPTIONS} from './relation-options';
export const SUBSCRIBED_FILTER = {
label: 'Newsletter subscription',
label: 'Newsletter subscription',
name: 'subscribed',
columnLabel: 'Subscribed',
columnLabel: 'Subscribed',
relationOptions: MATCH_RELATION_OPTIONS,
valueType: 'options',
options: [
@ -12,7 +12,57 @@ export const SUBSCRIBED_FILTER = {
],
getColumnValue: (member) => {
return {
text: member.subscribed ? 'Yes' : 'No'
text: member.subscribed ? 'Subscribed' : 'Unsubscribed'
};
}
};
export const NEWSLETTERS_FILTER = (newsletterList) => {
let newsletters = [];
newsletterList.forEach((newsletter) => {
const filter = {
label: newsletter.name,
name: `newsletters.slug:${newsletter.slug}`,
relationOptions: MATCH_RELATION_OPTIONS,
group: 'Newsletters',
valueType: 'options',
buildNqlFilter: (flt) => {
const relation = flt.relation;
const value = flt.value;
return (relation === 'is' && value === 'true') || (relation === 'is-not' && value === 'false')
? `newsletters.slug:${newsletter.slug}`
: `newsletters.slug:-${newsletter.slug}`;
},
parseNqlFilter: (flt) => {
if (!flt['newsletters.slug']) {
return;
}
let value = flt['newsletters.slug'];
let invert = false;
if (typeof value === 'object') {
if (!value.$ne) {
// Unsupported relation type
return;
}
invert = true;
value = value.$ne;
}
if (value !== newsletter.slug) {
// This filter is for a different newsletter
return;
}
return {
value: invert ? 'false' : 'true',
relation: 'is'
};
},
options: [
{label: 'Subscribed', name: 'true'},
{label: 'Unsubscribed', name: 'false'}
]
};
newsletters.push(filter);
});
return newsletters;
};