This repository has been archived by the owner on Nov 28, 2022. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🐛 Fixed confusing member count shown in save notification and editor …
…header closes TryGhost/Product#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
- Loading branch information
1 parent
097c775
commit 27363df
Showing
8 changed files
with
124 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{{this.recipientCount}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import Component from '@glimmer/component'; | ||
import {inject as service} from '@ember/service'; | ||
import {task} from 'ember-concurrency-decorators'; | ||
import {tracked} from '@glimmer/tracking'; | ||
|
||
export default class GhRecipientFilterCountComponent extends Component { | ||
@service membersCountCache; | ||
|
||
@tracked recipientCount; | ||
|
||
constructor() { | ||
super(...arguments); | ||
this.getRecipientCountTask.perform(); | ||
} | ||
|
||
@task | ||
*getRecipientCountTask() { | ||
if (!this.args.filter) { | ||
this.recipientCount = 'no members'; | ||
return; | ||
} | ||
|
||
this.recipientCount = yield this.membersCountCache.countString( | ||
`subscribed:true+(${this.args.filter})`, | ||
{knownCount: this.args.knownCount} | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
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; | ||
} | ||
} | ||
} |