Ghost/ghost/admin/app/services/members-count-cache.js
Kevin Ansfield bd60c8089b 🐛 Fixed confusing member count shown in save notification and editor header
closes https://github.com/TryGhost/Team/issues/776

Since switching to using a real NQL filter in the `posts.email_recipient_filter` field where we used to show `free members`, `paid members`, or `all members` we were showing `status:free`, `status:-free`, and `status:free,status:-free` respectively. If labels are used in a filter the text became even longer.

- added a `membersCountCache` service
  - `.count(filter)` fetches a numeric count from the members API, if the filter has been counted in the last minute it returns the count directly from a cache instead to avoid hammering the members API when we show counts in multiple places across the UI
  - `.countString(filter)` fetches a count but returns a humanized string with the logic extracted from what we displayed in the confirm email sending modal
- added a `<GhRecipientFilterCount @filter="" />` component that acts as a wrapper around the async count from `membersCountCache`
- updated confirm email send modal, plus save notification and editor status displays for scheduled posts to use the new service and component
2021-06-11 11:44:50 +01:00

80 lines
2.4 KiB
JavaScript

import Service from '@ember/service';
import moment from 'moment';
import {ghPluralize} from 'ghost-admin/helpers/gh-pluralize';
import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency-decorators';
export default class MembersCountCacheService extends Service {
@service session;
@service store;
cache = {};
async count(filter) {
const cachedValue = this.cache[filter];
if (cachedValue && moment().diff(cachedValue.time, 'seconds') > 60) {
return cachedValue.count;
}
const count = this._countMembersTask.perform(filter);
this.cache[filter] = {count, time: moment()};
return count;
}
async countString(filter, {knownCount} = {}) {
const user = await this.session.user;
const basicFilter = filter.replace(/^subscribed:true\+\((.*)\)$/, '$1');
const filterParts = basicFilter.split(',');
const isFree = filterParts.length === 1 && filterParts[0] === 'status:free';
const isPaid = filterParts.length === 1 && filterParts[0] === 'status:-free';
const isAll = filterParts.includes('status:free') && filterParts.includes('status:-free');
// editors don't have permission to browse members so can't retrieve a count
// TODO: remove when editors have relevant permissions or we have a different way of fetching counts
if (user.isEditor && knownCount === undefined) {
if (isFree) {
return 'all free members';
}
if (isPaid) {
return 'all paid members';
}
if (isAll) {
return 'all members';
}
return 'a custom members segment';
}
const recipientCount = knownCount !== undefined ? knownCount : await this.count(filter);
if (isFree) {
return ghPluralize(recipientCount, 'free member');
}
if (isPaid) {
return ghPluralize(recipientCount, 'paid member');
}
return ghPluralize(recipientCount, 'member');
}
@task
*_countMembersTask(filter) {
if (!filter) {
return 0;
}
try {
const result = yield this.store.query('member', {filter, limit: 1, page: 1});
return result.meta.pagination.total;
} catch (e) {
console.error(e); // eslint-disable-line
return 0;
}
}
}