mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 06:35:49 +03:00
Added polling when confirming email to show immediate error
no issue - when confirming email send, after initial save in, poll every second for a maximum of 10 seconds and check the status of the email - if it's `'success'` close the modal immediately - if it's `'failure'` switch the confirm modal to an error state - if the save fails for some other reason (validation, server error) close the modal immediately and let the normal editor error handling do it's thing - fixed confirm modal not appearing when retrying a save after a post validation failed - show email status in post status area - `"and sending to x members"` when email is pending or submitting - `"and sent to x members"` once email is fully submitted
This commit is contained in:
parent
31b5b9319d
commit
30b23f2a7c
@ -1,9 +1,13 @@
|
||||
import Component from '@ember/component';
|
||||
import EmailFailedError from 'ghost-admin/errors/email-failed-error';
|
||||
import {action} from '@ember/object';
|
||||
import {computed} from '@ember/object';
|
||||
import {reads} from '@ember/object/computed';
|
||||
import {inject as service} from '@ember/service';
|
||||
import {task} from 'ember-concurrency';
|
||||
import {task, timeout} from 'ember-concurrency';
|
||||
|
||||
const CONFIRM_EMAIL_POLL_LENGTH = 1000;
|
||||
const CONFIRM_EMAIL_MAX_POLL_LENGTH = 10 * 1000;
|
||||
|
||||
export default Component.extend({
|
||||
clock: service(),
|
||||
@ -184,8 +188,30 @@ export default Component.extend({
|
||||
|
||||
_confirmEmailSend: task(function* () {
|
||||
this.sendEmailConfirmed = true;
|
||||
yield this.save.perform();
|
||||
this.set('showEmailConfirmationModal', false);
|
||||
let post = yield this.save.perform();
|
||||
|
||||
// simulate a validation error if saving failed so that the confirm
|
||||
// modal can react accordingly
|
||||
if (!post || post.errors.length) {
|
||||
throw null;
|
||||
}
|
||||
|
||||
let pollTimeout = 0;
|
||||
if (post.email && post.email.status !== 'submitted') {
|
||||
while (pollTimeout < CONFIRM_EMAIL_MAX_POLL_LENGTH) {
|
||||
yield timeout(CONFIRM_EMAIL_POLL_LENGTH);
|
||||
post = yield post.reload();
|
||||
|
||||
if (post.email.status === 'submitted') {
|
||||
break;
|
||||
}
|
||||
if (post.email.status === 'failed') {
|
||||
throw new EmailFailedError(post.email.error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return post;
|
||||
}),
|
||||
|
||||
openEmailConfirmationModal: action(function (dropdown) {
|
||||
@ -214,6 +240,8 @@ export default Component.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendEmailConfirmed = false;
|
||||
|
||||
// runningText needs to be declared before the other states change during the
|
||||
// save action.
|
||||
this.set('runningText', this._runningText);
|
||||
|
@ -2,10 +2,29 @@ import ModalComponent from 'ghost-admin/components/modal-base';
|
||||
import {task} from 'ember-concurrency';
|
||||
|
||||
export default ModalComponent.extend({
|
||||
errorMessage: null,
|
||||
|
||||
// Allowed actions
|
||||
confirm: () => {},
|
||||
|
||||
confirmTask: task(function* () {
|
||||
confirmAndCheckError: task(function* () {
|
||||
try {
|
||||
yield this.confirm();
|
||||
this.closeModal();
|
||||
return true;
|
||||
} catch (e) {
|
||||
// switch to "failed" state if email fails
|
||||
if (e && e.name === 'EmailFailedError') {
|
||||
this.set('errorMessage', e.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// close modal and continue with normal error handling if it was
|
||||
// a non-email-related error
|
||||
this.closeModal();
|
||||
if (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
6
ghost/admin/app/errors/email-failed-error.js
Normal file
6
ghost/admin/app/errors/email-failed-error.js
Normal file
@ -0,0 +1,6 @@
|
||||
export default class EmailFailedError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = 'EmailFailedError';
|
||||
}
|
||||
}
|
@ -2,7 +2,9 @@
|
||||
Saving...
|
||||
{{else if (or this.post.isPublished this.post.pastScheduledTime)}}
|
||||
Published
|
||||
{{#if this.post.email}}
|
||||
{{#if (or (eq this.post.email.status "submitting") (eq this.post.email.status "submitting"))}}
|
||||
and sending to {{pluralize this.post.email.emailCount "member"}}
|
||||
{{else if (eq this.post.email.status "submitted")}}
|
||||
and sent to {{pluralize this.post.email.emailCount "member"}}
|
||||
{{/if}}
|
||||
{{else if this.post.isScheduled}}
|
||||
|
@ -1,28 +1,28 @@
|
||||
{{#basic-dropdown verticalPosition="below" onOpen=(action "open") onClose=(action "close") as |dd|}}
|
||||
{{#dd.trigger class="gh-btn gh-btn-outline gh-publishmenu-trigger"}}
|
||||
<span data-test-publishmenu-trigger>{{triggerText}} {{svg-jar "arrow-down"}}</span>
|
||||
<span data-test-publishmenu-trigger>{{this.triggerText}} {{svg-jar "arrow-down"}}</span>
|
||||
{{/dd.trigger}}
|
||||
|
||||
{{#dd.content class="gh-publishmenu-dropdown"}}
|
||||
{{#if (eq displayState "published")}}
|
||||
{{gh-publishmenu-published
|
||||
post=post
|
||||
saveType=saveType
|
||||
post=this.post
|
||||
saveType=this.saveType
|
||||
setSaveType=(action "setSaveType")
|
||||
backgroundTask=this.backgroundTask}}
|
||||
|
||||
{{else if (eq displayState "scheduled")}}
|
||||
{{gh-publishmenu-scheduled
|
||||
post=post
|
||||
saveType=saveType
|
||||
isClosing=isClosing
|
||||
post=this.post
|
||||
saveType=this.saveType
|
||||
isClosing=this.isClosing
|
||||
memberCount=this.memberCount
|
||||
setSaveType=(action "setSaveType")}}
|
||||
|
||||
{{else}}
|
||||
{{gh-publishmenu-draft
|
||||
post=post
|
||||
saveType=saveType
|
||||
post=this.post
|
||||
saveType=this.saveType
|
||||
setSaveType=(action "setSaveType")
|
||||
backgroundTask=this.backgroundTask
|
||||
memberCount=this.memberCount
|
||||
@ -34,21 +34,23 @@
|
||||
or cancel the task when the post status updates and switches components
|
||||
--}}
|
||||
<footer class="gh-publishmenu-footer">
|
||||
<button class="gh-btn gh-btn-outline gh-btn-link" {{action dd.actions.close}} data-test-publishmenu-cancel>
|
||||
<button class="gh-btn gh-btn-outline gh-btn-link" {{on "click" (action dd.actions.close)}} data-test-publishmenu-cancel>
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
{{gh-task-button buttonText
|
||||
task=save
|
||||
taskArgs=(hash dropdown=dd)
|
||||
successText=successText
|
||||
runningText=runningText
|
||||
class="gh-btn gh-btn-blue gh-publishmenu-button gh-btn-icon"
|
||||
data-test-publishmenu-save=true}}
|
||||
<GhTaskButton
|
||||
@buttonText={{this.buttonText}}
|
||||
@task={{this.save}}
|
||||
@taskArgs={{hash dropdown=dd}}
|
||||
@successText={{this.successText}}
|
||||
@runningText={{this.runningText}}
|
||||
@class="gh-btn gh-btn-blue gh-publishmenu-button gh-btn-icon"
|
||||
data-test-publishmenu-save=true
|
||||
/>
|
||||
</footer>
|
||||
{{/dd.content}}
|
||||
{{/basic-dropdown}}
|
||||
|
||||
{{#if showEmailConfirmationModal}}
|
||||
{{#if this.showEmailConfirmationModal}}
|
||||
<GhFullscreenModal
|
||||
@modal="confirm-email-send"
|
||||
@model={{hash
|
||||
|
@ -1,23 +1,45 @@
|
||||
{{#unless errorMessage}}
|
||||
<header class="modal-header" data-test-modal="delete-user">
|
||||
<h1>Ready to go? Here’s what happens next</h1>
|
||||
</header>
|
||||
<a class="close" href="" title="Close" {{action "closeModal"}}>{{svg-jar "close"}}<span class="hidden">Close</span></a>
|
||||
<button class="close" title="Close" {{on "click" this.closeModal}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
Your post will be delivered to <strong>{{if this.model.paidOnly "all paid members" (pluralize this.model.memberCount "member")}}</strong> and will be published on your site{{#if this.model.isScheduled}} at the scheduled time{{/if}}. Sounds good?
|
||||
Your post will be delivered to
|
||||
<strong>{{if this.model.paidOnly "all paid members" (pluralize this.model.memberCount "member")}}</strong>
|
||||
and will be published on your site{{#if this.model.isScheduled}} at the scheduled time{{/if}}. Sounds good?
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button {{action "closeModal"}} class="gh-btn" data-test-button="cancel-publish-and-email">
|
||||
<button {{on "click" this.closeModal}} class="gh-btn" data-test-button="cancel-publish-and-email">
|
||||
<span>Cancel</span>
|
||||
</button>
|
||||
<GhTaskButton
|
||||
@buttonText={{if this.model.isScheduled "Schedule" "Publish and send"}}
|
||||
@runningText={{if this.model.isScheduled "Scheduling..." "Publishing..."}}
|
||||
@task={{this.confirmTask}}
|
||||
@task={{this.confirmAndCheckError}}
|
||||
@class="gh-btn gh-btn-green gh-btn-icon"
|
||||
data-test-button="confirm-publish-and-email"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{else}}
|
||||
<header class="modal-header" data-test-modal="delete-user">
|
||||
<h1>Failed to send email</h1>
|
||||
</header>
|
||||
<button class="close" title="Close" {{on "click" this.closeModal}}>{{svg-jar "close"}}<span class="hidden">Close</span></button>
|
||||
|
||||
<div class="modal-body">
|
||||
<p>Your post has been published but the email failed to send.</p>
|
||||
<p class="error">Error: {{this.errorMessage}}</p>
|
||||
<p>Check your <LinkTo @route="settings.labs">mailgun settings</LinkTo> if it persists.</p>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button {{on "click" this.closeModal}} class="gh-btn" data-test-button="cancel-publish-and-email">
|
||||
<span>Close</span>
|
||||
</button>
|
||||
</div>
|
||||
{{/unless}}
|
Loading…
Reference in New Issue
Block a user