🐛 Fixed member count not showing in send email confirmation modal

closes https://github.com/TryGhost/Team/issues/738
refs https://github.com/TryGhost/Admin/pull/1972

- when we switched from the segment select back to checkboxes and label select we lost the automatic member counting which meant other parts of the publishing workflow had no counts
- fixed subscribed status counts shown in publish menu
- added the async count back to the confirm modal, taking full free/paid/specific query into account
- added total subscribed member count back to the draft publish menu so the email options can be disabled when no subscribed members exist
  - fixed missing disabled styling inside `<GhMembersRecipientSelect>`
This commit is contained in:
Kevin Ansfield 2021-06-08 13:07:16 +01:00
parent e44f18a412
commit 8f5e305721
7 changed files with 39 additions and 30 deletions

View File

@ -1,6 +1,6 @@
<div class="gh-publishmenu-send-to-option"> <div class="gh-publishmenu-send-to-option">
<p>Free members <span class="gh-publishmenu-emailcount">{{this.freeMemberCountLabel}}</span></p> <p>Free members <span class="gh-publishmenu-emailcount">{{this.freeMemberCountLabel}}</span></p>
<div class="for-switch x-small" {{on "click" (fn this.toggleFilter "status:free")}}> <div class="for-switch x-small {{if @disabled "disabled"}}" {{on "click" (fn this.toggleFilter "status:free")}}>
<label class="switch" for="send-email-to-free"> <label class="switch" for="send-email-to-free">
<input <input
id="send-email-to-free" id="send-email-to-free"
@ -17,7 +17,7 @@
{{#if this.isPaidAvailable}} {{#if this.isPaidAvailable}}
<div class="gh-publishmenu-send-to-option"> <div class="gh-publishmenu-send-to-option">
<p>Paid members <span class="gh-publishmenu-emailcount">{{this.paidMemberCountLabel}}</span></p> <p>Paid members <span class="gh-publishmenu-emailcount">{{this.paidMemberCountLabel}}</span></p>
<div class="for-switch x-small" {{on "click" (fn this.toggleFilter "status:-free")}}> <div class="for-switch x-small {{if @disabled "disabled"}}" {{on "click" (fn this.toggleFilter "status:-free")}}>
<label class="switch" for="send-email-to-paid"> <label class="switch" for="send-email-to-paid">
<input <input
id="send-email-to-paid" id="send-email-to-paid"
@ -35,7 +35,7 @@
{{#if this.specificOptions}} {{#if this.specificOptions}}
<div class="gh-publishmenu-send-to-option"> <div class="gh-publishmenu-send-to-option">
<p>Specific people</p> <p>Specific people</p>
<div class="for-switch x-small" {{on "click" this.toggleSpecificFilter}}> <div class="for-switch x-small {{if @disabled "disabled"}}" {{on "click" this.toggleSpecificFilter}}>
<label class="switch" for="send-email-to-paid"> <label class="switch" for="send-email-to-paid">
<input <input
id="send-email-to-paid" id="send-email-to-paid"

View File

@ -157,10 +157,10 @@ export default class GhMembersRecipientSelect extends Component {
} }
yield Promise.all([ yield Promise.all([
this.store.query('member', {filter: 'subscribed:true,status:free', limit: 1}).then((res) => { this.store.query('member', {filter: 'subscribed:true+status:free', limit: 1}).then((res) => {
this.freeMemberCount = res.meta.pagination.total; this.freeMemberCount = res.meta.pagination.total;
}), }),
this.store.query('member', {filter: 'subscribed:true,status:-free', limit: 1}).then((res) => { this.store.query('member', {filter: 'subscribed:true+status:-free', limit: 1}).then((res) => {
this.paidMemberCount = res.meta.pagination.total; this.paidMemberCount = res.meta.pagination.total;
}) })
]); ]);

View File

@ -30,7 +30,7 @@
</div> </div>
{{#if this.canSendEmail}} {{#if this.canSendEmail}}
<div class="gh-publishmenu-section"> <div class="gh-publishmenu-section" {{did-insert (perform this.countTotalMembersTask)}}>
<div class="gh-publishmenu-email"> <div class="gh-publishmenu-email">
{{#if this.isSendingEmailLimited}} {{#if this.isSendingEmailLimited}}
<p class="gh-box gh-box-alert">{{html-safe this.sendingEmailLimitError}}</p> <p class="gh-box gh-box-alert">{{html-safe this.sendingEmailLimitError}}</p>
@ -42,6 +42,7 @@
<GhMembersRecipientSelect <GhMembersRecipientSelect
@filter={{this.recipientsFilter}} @filter={{this.recipientsFilter}}
@onChange={{this.setSendEmailWhenPublished}} @onChange={{this.setSendEmailWhenPublished}}
@disabled={{this.disableEmailOption}}
/> />
</div> </div>
</div> </div>

View File

@ -3,12 +3,15 @@ import moment from 'moment';
import {computed} from '@ember/object'; import {computed} from '@ember/object';
import {isEmpty} from '@ember/utils'; import {isEmpty} from '@ember/utils';
import {inject as service} from '@ember/service'; import {inject as service} from '@ember/service';
import {task} from 'ember-concurrency';
export default Component.extend({ export default Component.extend({
feature: service(),
settings: service(),
config: service(), config: service(),
feature: service(),
session: service(), session: service(),
settings: service(),
store: service(),
post: null, post: null,
saveType: null, saveType: null,
@ -18,8 +21,9 @@ export default Component.extend({
'data-test-publishmenu-draft': true, 'data-test-publishmenu-draft': true,
disableEmailOption: computed('memberCount', function () { // TODO: remove owner or admin check when editors can count members
return (this.get('session.user.isOwnerOrAdmin') && this.memberCount === 0); disableEmailOption: computed('totalMemberCount', 'countTotalMembersTask.isRunning', function () {
return this.get('session.user.isOwnerOrAdmin') && (this.totalMemberCount === 0 || this.countTotalMembersTask.isRunning);
}), }),
didInsertElement() { didInsertElement() {
@ -75,6 +79,15 @@ export default Component.extend({
} }
}, },
countTotalMembersTask: task(function*() {
const user = yield this.session.user;
if (user.isOwnerOrAdmin) {
const result = yield this.store.query('member', {limit: 1, filter: 'subscribed:true'});
this.set('totalMemberCount', result.meta.pagination.total);
}
}),
// scheduled date 5 mins in the future to avoid immediate validation errors // scheduled date 5 mins in the future to avoid immediate validation errors
_getMinDate() { _getMinDate() {
return moment.utc().add(5, 'minutes'); return moment.utc().add(5, 'minutes');

View File

@ -62,9 +62,7 @@
@modal="confirm-email-send" @modal="confirm-email-send"
@model={{hash @model={{hash
sendEmailWhenPublished=this.sendEmailWhenPublished sendEmailWhenPublished=this.sendEmailWhenPublished
memberCount=this.memberCount
isScheduled=(eq this.saveType "schedule") isScheduled=(eq this.saveType "schedule")
paidOnly=(eq this.post.visibility "paid")
retryEmailSend=this.retryEmailSend retryEmailSend=this.retryEmailSend
}} }}
@confirm={{this.confirmEmailSend}} @confirm={{this.confirmEmailSend}}

View File

@ -4,8 +4,8 @@
</header> </header>
<button class="close" title="Close" {{on "click" this.closeModal}}>{{svg-jar "close"}}<span class="hidden">Close</span></button> <button class="close" title="Close" {{on "click" this.closeModal}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
<div class="modal-body" {{did-insert this.countPaidMembers}}> <div class="modal-body" {{did-insert this.countRecipients}}>
{{#if this.countPaidMembersTask.isRunning}} {{#if this.countRecipientsTask.isRunning}}
<div class="flex flex-column items-center"> <div class="flex flex-column items-center">
<div class="gh-loading-spinner"></div> <div class="gh-loading-spinner"></div>
</div> </div>
@ -15,16 +15,16 @@
<strong> <strong>
{{#if (eq this.model.sendEmailWhenPublished 'status:-free')}} {{#if (eq this.model.sendEmailWhenPublished 'status:-free')}}
{{!-- TODO: remove editor fallback once editors can query member counts --}} {{!-- TODO: remove editor fallback once editors can query member counts --}}
{{if this.session.user.isEditor "all paid members" (gh-pluralize this.model.memberCount "paid member")}} {{if this.session.user.isEditor "all paid members" (gh-pluralize this.memberCount "paid member")}}
{{else if (eq this.model.sendEmailWhenPublished 'status:free')}} {{else if (eq this.model.sendEmailWhenPublished 'status:free')}}
{{!-- TODO: remove editor fallback once editors can query member counts --}} {{!-- TODO: remove editor fallback once editors can query member counts --}}
{{if this.session.user.isEditor "all free members" (gh-pluralize this.model.memberCount "free member")}} {{if this.session.user.isEditor "all free members" (gh-pluralize this.memberCount "free member")}}
{{else if (eq this.model.sendEmailWhenPublished 'status:free,status:-free')}} {{else if (eq this.model.sendEmailWhenPublished 'status:free,status:-free')}}
{{!-- TODO: remove editor fallback once editors can query member counts --}} {{!-- TODO: remove editor fallback once editors can query member counts --}}
{{if this.session.user.isEditor "all members" (gh-pluralize this.model.memberCount "member")}} {{if this.session.user.isEditor "all members" (gh-pluralize this.memberCount "member")}}
{{else}} {{else}}
{{!-- TODO: remove editor fallback once editors can query member counts --}} {{!-- TODO: remove editor fallback once editors can query member counts --}}
{{if this.session.user.isEditor "a custom members segment" (gh-pluralize this.model.memberCount "member")}} {{if this.session.user.isEditor "a custom members segment" (gh-pluralize this.memberCount "member")}}
{{/if}} {{/if}}
</strong> </strong>
and will be published on your site{{#if this.model.isScheduled}} at the scheduled time{{/if}}. Sound good? and will be published on your site{{#if this.model.isScheduled}} at the scheduled time{{/if}}. Sound good?
@ -37,7 +37,7 @@
<span>Cancel</span> <span>Cancel</span>
</button> </button>
<GhTaskButton <GhTaskButton
@disabled={{this.countPaidMembersTask.isRunning}} @disabled={{this.countRecipientsTask.isRunning}}
@buttonText={{if this.model.isScheduled "Schedule" "Publish and send"}} @buttonText={{if this.model.isScheduled "Schedule" "Publish and send"}}
@runningText={{if this.model.isScheduled "Scheduling..." "Publishing..."}} @runningText={{if this.model.isScheduled "Scheduling..." "Publishing..."}}
@task={{this.confirmAndCheckErrorTask}} @task={{this.confirmAndCheckErrorTask}}

View File

@ -8,8 +8,7 @@ export default ModalComponent.extend({
store: service(), store: service(),
errorMessage: null, errorMessage: null,
paidMemberCount: null, memberCount: null,
freeMemberCount: null,
// Allowed actions // Allowed actions
confirm: () => {}, confirm: () => {},
@ -19,25 +18,23 @@ export default ModalComponent.extend({
if (this.errorMessage) { if (this.errorMessage) {
return this.retryEmailTask.perform(); return this.retryEmailTask.perform();
} else { } else {
if (!this.countPaidMembersTask.isRunning) { if (!this.countRecipientsTask.isRunning) {
return this.confirmAndCheckErrorTask.perform(); return this.confirmAndCheckErrorTask.perform();
} }
} }
} }
}, },
countPaidMembers: action(function () { countRecipients: action(function () {
// TODO: remove editor conditional once editors can query member counts // TODO: remove editor conditional once editors can query member counts
if (['free', 'paid'].includes(this.model.sendEmailWhenPublished) && !this.session.get('user.isEditor')) { if (this.model.sendEmailWhenPublished && !this.session.get('user.isEditor')) {
this.countPaidMembersTask.perform(); this.countRecipientsTask.perform();
} }
}), }),
countPaidMembersTask: task(function* () { countRecipientsTask: task(function* () {
const result = yield this.store.query('member', {filter: 'subscribed:true+status:-free', limit: 1, page: 1}); const result = yield this.store.query('member', {filter: `subscribed:true+(${this.model.sendEmailWhenPublished})`, limit: 1, page: 1});
this.set('paidMemberCount', result.meta.pagination.total); this.set('memberCount', result.meta.pagination.total);
const freeMemberCount = this.model.memberCount - result.meta.pagination.total;
this.set('freeMemberCount', freeMemberCount);
}), }),
confirmAndCheckErrorTask: task(function* () { confirmAndCheckErrorTask: task(function* () {