mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-27 18:52:14 +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 Component from '@ember/component';
|
||||||
|
import EmailFailedError from 'ghost-admin/errors/email-failed-error';
|
||||||
import {action} from '@ember/object';
|
import {action} from '@ember/object';
|
||||||
import {computed} from '@ember/object';
|
import {computed} from '@ember/object';
|
||||||
import {reads} from '@ember/object/computed';
|
import {reads} from '@ember/object/computed';
|
||||||
import {inject as service} from '@ember/service';
|
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({
|
export default Component.extend({
|
||||||
clock: service(),
|
clock: service(),
|
||||||
@ -184,8 +188,30 @@ export default Component.extend({
|
|||||||
|
|
||||||
_confirmEmailSend: task(function* () {
|
_confirmEmailSend: task(function* () {
|
||||||
this.sendEmailConfirmed = true;
|
this.sendEmailConfirmed = true;
|
||||||
yield this.save.perform();
|
let post = yield this.save.perform();
|
||||||
this.set('showEmailConfirmationModal', false);
|
|
||||||
|
// 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) {
|
openEmailConfirmationModal: action(function (dropdown) {
|
||||||
@ -214,6 +240,8 @@ export default Component.extend({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.sendEmailConfirmed = false;
|
||||||
|
|
||||||
// runningText needs to be declared before the other states change during the
|
// runningText needs to be declared before the other states change during the
|
||||||
// save action.
|
// save action.
|
||||||
this.set('runningText', this._runningText);
|
this.set('runningText', this._runningText);
|
||||||
|
@ -2,10 +2,29 @@ import ModalComponent from 'ghost-admin/components/modal-base';
|
|||||||
import {task} from 'ember-concurrency';
|
import {task} from 'ember-concurrency';
|
||||||
|
|
||||||
export default ModalComponent.extend({
|
export default ModalComponent.extend({
|
||||||
|
errorMessage: null,
|
||||||
|
|
||||||
// Allowed actions
|
// Allowed actions
|
||||||
confirm: () => {},
|
confirm: () => {},
|
||||||
|
|
||||||
confirmTask: task(function* () {
|
confirmAndCheckError: task(function* () {
|
||||||
|
try {
|
||||||
yield this.confirm();
|
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...
|
Saving...
|
||||||
{{else if (or this.post.isPublished this.post.pastScheduledTime)}}
|
{{else if (or this.post.isPublished this.post.pastScheduledTime)}}
|
||||||
Published
|
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"}}
|
and sent to {{pluralize this.post.email.emailCount "member"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else if this.post.isScheduled}}
|
{{else if this.post.isScheduled}}
|
||||||
|
@ -1,28 +1,28 @@
|
|||||||
{{#basic-dropdown verticalPosition="below" onOpen=(action "open") onClose=(action "close") as |dd|}}
|
{{#basic-dropdown verticalPosition="below" onOpen=(action "open") onClose=(action "close") as |dd|}}
|
||||||
{{#dd.trigger class="gh-btn gh-btn-outline gh-publishmenu-trigger"}}
|
{{#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.trigger}}
|
||||||
|
|
||||||
{{#dd.content class="gh-publishmenu-dropdown"}}
|
{{#dd.content class="gh-publishmenu-dropdown"}}
|
||||||
{{#if (eq displayState "published")}}
|
{{#if (eq displayState "published")}}
|
||||||
{{gh-publishmenu-published
|
{{gh-publishmenu-published
|
||||||
post=post
|
post=this.post
|
||||||
saveType=saveType
|
saveType=this.saveType
|
||||||
setSaveType=(action "setSaveType")
|
setSaveType=(action "setSaveType")
|
||||||
backgroundTask=this.backgroundTask}}
|
backgroundTask=this.backgroundTask}}
|
||||||
|
|
||||||
{{else if (eq displayState "scheduled")}}
|
{{else if (eq displayState "scheduled")}}
|
||||||
{{gh-publishmenu-scheduled
|
{{gh-publishmenu-scheduled
|
||||||
post=post
|
post=this.post
|
||||||
saveType=saveType
|
saveType=this.saveType
|
||||||
isClosing=isClosing
|
isClosing=this.isClosing
|
||||||
memberCount=this.memberCount
|
memberCount=this.memberCount
|
||||||
setSaveType=(action "setSaveType")}}
|
setSaveType=(action "setSaveType")}}
|
||||||
|
|
||||||
{{else}}
|
{{else}}
|
||||||
{{gh-publishmenu-draft
|
{{gh-publishmenu-draft
|
||||||
post=post
|
post=this.post
|
||||||
saveType=saveType
|
saveType=this.saveType
|
||||||
setSaveType=(action "setSaveType")
|
setSaveType=(action "setSaveType")
|
||||||
backgroundTask=this.backgroundTask
|
backgroundTask=this.backgroundTask
|
||||||
memberCount=this.memberCount
|
memberCount=this.memberCount
|
||||||
@ -34,21 +34,23 @@
|
|||||||
or cancel the task when the post status updates and switches components
|
or cancel the task when the post status updates and switches components
|
||||||
--}}
|
--}}
|
||||||
<footer class="gh-publishmenu-footer">
|
<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>
|
<span>Cancel</span>
|
||||||
</button>
|
</button>
|
||||||
{{gh-task-button buttonText
|
<GhTaskButton
|
||||||
task=save
|
@buttonText={{this.buttonText}}
|
||||||
taskArgs=(hash dropdown=dd)
|
@task={{this.save}}
|
||||||
successText=successText
|
@taskArgs={{hash dropdown=dd}}
|
||||||
runningText=runningText
|
@successText={{this.successText}}
|
||||||
class="gh-btn gh-btn-blue gh-publishmenu-button gh-btn-icon"
|
@runningText={{this.runningText}}
|
||||||
data-test-publishmenu-save=true}}
|
@class="gh-btn gh-btn-blue gh-publishmenu-button gh-btn-icon"
|
||||||
|
data-test-publishmenu-save=true
|
||||||
|
/>
|
||||||
</footer>
|
</footer>
|
||||||
{{/dd.content}}
|
{{/dd.content}}
|
||||||
{{/basic-dropdown}}
|
{{/basic-dropdown}}
|
||||||
|
|
||||||
{{#if showEmailConfirmationModal}}
|
{{#if this.showEmailConfirmationModal}}
|
||||||
<GhFullscreenModal
|
<GhFullscreenModal
|
||||||
@modal="confirm-email-send"
|
@modal="confirm-email-send"
|
||||||
@model={{hash
|
@model={{hash
|
||||||
|
@ -1,23 +1,45 @@
|
|||||||
|
{{#unless errorMessage}}
|
||||||
<header class="modal-header" data-test-modal="delete-user">
|
<header class="modal-header" data-test-modal="delete-user">
|
||||||
<h1>Ready to go? Here’s what happens next</h1>
|
<h1>Ready to go? Here’s what happens next</h1>
|
||||||
</header>
|
</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">
|
<div class="modal-body">
|
||||||
<p>
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<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>
|
<span>Cancel</span>
|
||||||
</button>
|
</button>
|
||||||
<GhTaskButton
|
<GhTaskButton
|
||||||
@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.confirmTask}}
|
@task={{this.confirmAndCheckError}}
|
||||||
@class="gh-btn gh-btn-green gh-btn-icon"
|
@class="gh-btn gh-btn-green gh-btn-icon"
|
||||||
data-test-button="confirm-publish-and-email"
|
data-test-button="confirm-publish-and-email"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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