Added separate "published but email failed" state to publish flow

refs https://github.com/TryGhost/Team/issues/1587

- if post creation succeeds but the email fails to send we want to show a separate state of the publish flow rather than adding an error to the confirm step
  - confirm _has_ completed so showing the error there doesn't make sense and causes confusing copy
- added check for email failure to the `<PublishFlow>` save task (which is called by the confirm step) to intercept any email failure errors and switch state
This commit is contained in:
Kevin Ansfield 2022-05-12 15:28:35 +01:00
parent d353f5dff1
commit dd3d84a8a5
7 changed files with 141 additions and 31 deletions

View File

@ -4,7 +4,7 @@
<span>{{svg-jar "arrow-left"}} Editor</span>
</button>
{{#unless this.isComplete}}
{{#if (and (not this.emailErrorMessage) (not this.isComplete))}}
<div class="flex">
<div>
<button
@ -20,11 +20,18 @@
</button>
<div class="settings-menu-toggle-spacer"></div>
</div>
{{/unless}}
{{/if}}
</header>
<div class="gh-publish-settings-container">
{{#if this.isConfirming}}
{{#if this.emailErrorMessage}}
<EditorLabs::Modals::PublishFlow::CompleteWithEmailError
@emailErrorMessage={{this.emailErrorMessage}}
@publishOptions={{@data.publishOptions}}
@posCount={{this.posCount}}
@close={{@close}}
/>
{{else if this.isConfirming}}
<EditorLabs::Modals::PublishFlow::Confirm
@publishOptions={{@data.publishOptions}}
@recipientType={{this.recipientType}}

View File

@ -13,6 +13,7 @@ export default class PublishModalComponent extends Component {
@service store;
@tracked emailErrorMessage;
@tracked isConfirming = false;
@tracked isComplete = false;
@tracked postCount = null;
@ -50,10 +51,18 @@ export default class PublishModalComponent extends Component {
@task
*saveTask() {
yield this.args.data.saveTask.perform();
try {
yield this.args.data.saveTask.perform();
this.isConfirming = false;
this.isComplete = true;
this.isConfirming = false;
this.isComplete = true;
} catch (e) {
if (e?.name === 'EmailFailedError') {
return this.emailErrorMessage = e.message;
}
throw e;
}
}
// we fetch the new post count in advance when reaching the confirm step

View File

@ -0,0 +1,46 @@
{{#let @publishOptions.post as |post|}}
<div class="gh-publish-title">
<span class="green">Boom. Its out there.</span>
{{#if post.emailOnly}}
Your email has been created.
{{else if (and post.isPost @postCount)}}
Thats {{@postCount}} posts published, keep going!
{{else}}
Your {{post.displayName}} has been published.
{{/if}}
</div>
<p class="error gh-box gh-box-error mt3 mb3">
{{svg-jar "warning"}}
{{#if @publishOptions.willOnlyEmail}}
Your post has been created but the email failed to send.
{{else}}
Your post has been published but the email failed to send.
{{/if}}
Please verify your email settings if the error persists.
<br><br>
Email error: {{this.emailErrorMessage}}
</p>
{{#if this.retryErrorMessage}}
<p class="error gh-box gh-box-error mt3 mb3">
{{svg-jar "warning"}}
{{this.retryErrorMessage}}
</p>
{{/if}}
<div class="gh-publish-cta">
<GhTaskButton
@task={{this.retryEmailTask}}
@buttonText="Retry sending email"
@runningText="Sending"
@successText="Sent"
@class="gh-btn gh-btn-large"
@showIcon={{false}}
/>
</div>
{{/let}}

View File

@ -0,0 +1,71 @@
import Component from '@glimmer/component';
import EmailFailedError from 'ghost-admin/errors/email-failed-error';
import {CONFIRM_EMAIL_MAX_POLL_LENGTH, CONFIRM_EMAIL_POLL_LENGTH} from '../../publish-management';
import {htmlSafe} from '@ember/template';
import {isServerUnreachableError} from 'ghost-admin/services/ajax';
import {task, timeout} from 'ember-concurrency';
import {tracked} from '@glimmer/tracking';
function isString(str) {
return toString.call(str) === '[object String]';
}
export default class PublishFlowCompleteWithEmailError extends Component {
@tracked newEmailErrorMessage;
@tracked retryErrorMessage;
get emailErrorMessage() {
return this.newEmailErrorMessage || this.args.emailErrorMessage;
}
@task({drop: true})
*retryEmailTask() {
this.retryErrorMessage = null;
try {
let email = yield this.args.publishOptions.post.email.retry();
let pollTimeout = 0;
if (email && email.status !== 'submitted') {
while (pollTimeout < CONFIRM_EMAIL_MAX_POLL_LENGTH) {
yield timeout(CONFIRM_EMAIL_POLL_LENGTH);
pollTimeout += CONFIRM_EMAIL_POLL_LENGTH;
email = yield email.reload();
if (email.status === 'submitted') {
break;
}
if (email.status === 'failed') {
throw new EmailFailedError(email.error);
}
}
}
return email;
} catch (e) {
// update "failed" state if email fails again
if (e && e.name === 'EmailFailedError') {
this.newEmailErrorMessage = e.message;
return false;
}
if (e) {
let errorMessage = '';
if (isServerUnreachableError(e)) {
errorMessage = 'Unable to connect, please check your internet connection and try again.';
} else if (e && isString(e)) {
errorMessage = e;
} else if (e?.payload?.errors?.[0].message) {
errorMessage = e.payload.errors[0].message;
} else {
errorMessage = 'Unknown Error occurred when attempting to resend';
}
this.retryErrorMessage = htmlSafe(errorMessage);
return false;
}
}
}
}

View File

@ -58,22 +58,6 @@
</p>
{{/if}}
{{#if this.emailErrorMessage}}
<p class="error gh-box gh-box-error mt3 mb3">
{{svg-jar "warning"}}
{{#if @publishOptions.willOnlyEmail}}
Your post has been created but the email failed to send.
{{else}}
Your post has been published but the email failed to send.
{{/if}}
Please verify your email settings if the error persists.
<br><br>
Email error: {{this.emailErrorMessage}}
</p>
{{/if}}
<div class="gh-publish-cta">
<GhTaskButton
@task={{this.confirmTask}}

View File

@ -15,7 +15,6 @@ export default class PublishFlowOptions extends Component {
@service settings;
@tracked errorMessage;
@tracked emailErrorMessage;
// store any derived state from PublishOptions on creation so the copy
// doesn't change whilst the post is saving
@ -48,7 +47,6 @@ export default class PublishFlowOptions extends Component {
@task({drop: true})
*confirmTask() {
this.errorMessage = null;
this.emailErrorMessage = null;
try {
yield this.args.saveTask.perform();
@ -60,11 +58,6 @@ export default class PublishFlowOptions extends Component {
return false;
}
if (e?.name === 'EmailFailedError') {
this.emailErrorMessage = e.message;
return false;
}
let errorMessage = '';
if (isServerUnreachableError(e)) {

View File

@ -12,8 +12,8 @@ import {task, taskGroup, timeout} from 'ember-concurrency';
import {use} from 'ember-could-get-used-to-this';
const SHOW_SAVE_STATUS_DURATION = 3000;
const CONFIRM_EMAIL_POLL_LENGTH = 1000;
const CONFIRM_EMAIL_MAX_POLL_LENGTH = 15 * 1000;
export const CONFIRM_EMAIL_POLL_LENGTH = 1000;
export const CONFIRM_EMAIL_MAX_POLL_LENGTH = 15 * 1000;
// This component exists for the duration of the editor screen being open.
// It's used to store the selected publish options, control the publishing flow