mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-18 02:21:47 -05:00
🎨 Improved email failure handling and retrying (#15504)
fixes https://github.com/TryGhost/Team/issues/2009 - When an email is sent, but it failed there was no way to retry once you left the retry screen - There was no indication that the email failed to send in the post list and editor
This commit is contained in:
parent
0bfbee5523
commit
aaabf4b103
15 changed files with 109 additions and 35 deletions
ghost/admin/app
components
models
styles/layouts
templates
utils
|
@ -33,6 +33,7 @@
|
|||
<Editor::Modals::PublishFlow::CompleteWithEmailError
|
||||
@emailErrorMessage={{this.emailErrorMessage}}
|
||||
@publishOptions={{@data.publishOptions}}
|
||||
@setCompleted={{this.setCompleted}}
|
||||
@close={{@close}}
|
||||
/>
|
||||
{{else if this.isConfirming}}
|
||||
|
@ -59,4 +60,4 @@
|
|||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,7 @@ export default class PublishModalComponent extends Component {
|
|||
|
||||
@service store;
|
||||
|
||||
@tracked emailErrorMessage;
|
||||
@tracked emailErrorMessage = this.args.data.publishOptions.post.didEmailFail ? this.args.data.publishOptions.post.email.error : undefined;
|
||||
@tracked isConfirming = false;
|
||||
@tracked isComplete = false;
|
||||
@tracked postCount = null;
|
||||
|
@ -49,6 +49,13 @@ export default class PublishModalComponent extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
@action
|
||||
setCompleted() {
|
||||
this.emailErrorMessage = undefined;
|
||||
this.isConfirming = false;
|
||||
this.isComplete = true;
|
||||
}
|
||||
|
||||
@task
|
||||
*saveTask() {
|
||||
try {
|
||||
|
|
|
@ -42,6 +42,8 @@ export default class PublishFlowCompleteWithEmailError extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
this.args.setCompleted();
|
||||
|
||||
return email;
|
||||
} catch (e) {
|
||||
// update "failed" state if email fails again
|
||||
|
|
|
@ -107,7 +107,11 @@
|
|||
<div class="gh-publish-setting-title disabled" data-test-setting-title>
|
||||
{{svg-jar "member"}}
|
||||
<div class="gh-publish-setting-trigger">
|
||||
Already sent to
|
||||
{{#if (eq @publishOptions.post.email.status "failed") }}
|
||||
Retry sending to
|
||||
{{else}}
|
||||
Already sent to
|
||||
{{/if}}
|
||||
{{format-number @publishOptions.post.email.emailCount}}
|
||||
{{if (not-eq @recipientType "all") @recipientType}} {{!-- free/paid/specific --}}
|
||||
{{gh-pluralize @publishOptions.post.email.emailCount "subscriber" without-count=true}}
|
||||
|
@ -152,4 +156,4 @@
|
|||
<span>Continue, final review →</span>
|
||||
</button>
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{/unless}}
|
||||
|
|
|
@ -35,8 +35,7 @@
|
|||
{{if post.isScheduled "will be" "was"}}
|
||||
|
||||
{{#if
|
||||
(or post.isSent
|
||||
(and post.isPublished post.email)
|
||||
(or post.hasBeenEmailed
|
||||
post.willEmail
|
||||
)
|
||||
}}
|
||||
|
|
|
@ -69,4 +69,4 @@
|
|||
</button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
|
|
@ -3,8 +3,19 @@
|
|||
{{#if (and this.isSaving @post.isDraft)}}
|
||||
Saving...
|
||||
{{else if @post.isSent}}
|
||||
<button type="button" {{on "click" @openUpdateFlow}} class="gh-editor-post-status-btn">Sent</button>
|
||||
to {{gh-pluralize @post.email.emailCount "member"}}
|
||||
{{#if @post.didEmailFail }}
|
||||
Failed to send. <button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor midgrey gh-retry-trigger"
|
||||
{{on "click" @publishManagement.openPublishFlow}}
|
||||
disabled={{@publishManagement.publishOptions.isLoading}}
|
||||
>
|
||||
<span>Retry</span>
|
||||
</button>
|
||||
{{else}}
|
||||
<button type="button" {{on "click" @openUpdateFlow}} class="gh-editor-post-status-btn">Sent</button>
|
||||
to {{gh-pluralize @post.email.emailCount "member"}}
|
||||
{{/if}}
|
||||
{{else if (and @post.emailOnly @post.isScheduled)}}
|
||||
Scheduled
|
||||
{{#if this.isHovered}}
|
||||
|
@ -19,6 +30,15 @@
|
|||
and sending to {{gh-pluralize @post.email.emailCount "member"}}
|
||||
{{else if (eq @post.email.status "submitted")}}
|
||||
and sent to {{gh-pluralize @post.email.emailCount "member"}}
|
||||
{{else if (eq @post.email.status "failed")}}
|
||||
but failed to send. <button
|
||||
type="button"
|
||||
class="gh-btn gh-btn-editor midgrey gh-retry-trigger"
|
||||
{{on "click" @publishManagement.openPublishFlow}}
|
||||
disabled={{@publishManagement.publishOptions.isLoading}}
|
||||
>
|
||||
<span>Retry</span>
|
||||
</button>
|
||||
{{/if}}
|
||||
{{else if @post.isScheduled}}
|
||||
<time datetime="{{@post.publishedAtUTC}}" class="ml1 green-d1" data-test-schedule-countdown>
|
||||
|
|
|
@ -91,9 +91,11 @@
|
|||
{{/if}}
|
||||
|
||||
{{#if @post.isPublished}}
|
||||
<span class="published" {{on "mouseover" (fn (mut this.isStateHovered) true)}} {{on "mouseleave" (fn (mut this.isStateHovered) false)}}>
|
||||
<span class="published {{this.errorClass}}" {{on "mouseover" (fn (mut this.isStateHovered) true)}} {{on "mouseleave" (fn (mut this.isStateHovered) false)}}>
|
||||
Published
|
||||
{{#if @post.hasEmail}}
|
||||
{{#if @post.didEmailFail}}
|
||||
but failed to send
|
||||
{{else if @post.hasBeenEmailed}}
|
||||
and sent
|
||||
{{#if this.isHovered}}
|
||||
<span {{css-transition "anim-fade-in-scale"}}>to {{gh-pluralize @post.email.emailCount "member"}}</span>
|
||||
|
@ -103,10 +105,14 @@
|
|||
{{/if}}
|
||||
|
||||
{{#if @post.isSent}}
|
||||
<span class="sent" {{on "mouseover" (fn (mut this.isStateHovered) true)}} {{on "mouseleave" (fn (mut this.isStateHovered) false)}}>
|
||||
Sent
|
||||
{{#if this.isHovered}}
|
||||
<span {{css-transition "anim-fade-in-scale"}}>to {{gh-pluralize @post.email.emailCount "member"}}</span>
|
||||
<span class="sent {{this.errorClass}}" {{on "mouseover" (fn (mut this.isStateHovered) true)}} {{on "mouseleave" (fn (mut this.isStateHovered) false)}}>
|
||||
{{#if @post.didEmailFail}}
|
||||
Failed to send
|
||||
{{else}}
|
||||
Sent
|
||||
{{#if this.isHovered}}
|
||||
<span {{css-transition "anim-fade-in-scale"}}>to {{gh-pluralize @post.email.emailCount "member"}}</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</span>
|
||||
{{/if}}
|
||||
|
|
|
@ -15,6 +15,13 @@ export default class PostsListItemClicks extends Component {
|
|||
return this.args.post;
|
||||
}
|
||||
|
||||
get errorClass() {
|
||||
if (this.post.didEmailFail) {
|
||||
return 'error';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
get scheduledText() {
|
||||
let text = [];
|
||||
|
||||
|
|
|
@ -13,19 +13,19 @@
|
|||
</h2>
|
||||
<div class="gh-post-analytics-meta">
|
||||
<div class="gh-post-analytics-meta-text">
|
||||
{{#if
|
||||
(or this.post.isSent
|
||||
(and this.post.isPublished this.post.email)
|
||||
this.post.willEmail
|
||||
)
|
||||
}}
|
||||
{{#if this.post.hasBeenEmailed }}
|
||||
{{#if this.post.emailOnly}}
|
||||
Sent
|
||||
{{else}}
|
||||
Published and sent
|
||||
{{/if}}
|
||||
{{else}}
|
||||
Published on your site
|
||||
Published
|
||||
{{#if @post.didEmailFail}}
|
||||
but failed to send
|
||||
{{else}}
|
||||
on your site
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#let (moment-site-tz this.post.publishedAtUTC) as |publishedAt|}}
|
||||
|
@ -46,7 +46,7 @@
|
|||
Engagement
|
||||
</h4>
|
||||
<div class="gh-post-analytics-box">
|
||||
{{#if (or this.post.isSent (and this.post.isPublished this.post.email))}}
|
||||
{{#if this.post.hasBeenEmailed}}
|
||||
<div class="gh-post-analytics-item">
|
||||
<LinkTo @route="members" @query={{hash filterParam=(concat "emails.post_id:[" this.post.id "]") }}>
|
||||
<h3>{{format-number this.post.email.emailCount}}</h3>
|
||||
|
|
|
@ -183,24 +183,34 @@ export default Model.extend(Comparable, ValidationEngine, {
|
|||
return this.isScheduled && !!this.newsletter && !this.email;
|
||||
}),
|
||||
|
||||
showEmailOpenAnalytics: computed('isPost', 'isSent', 'isPublished', 'email', function () {
|
||||
hasBeenEmailed: computed('isPost', 'isSent', 'isPublished', 'email', function () {
|
||||
return this.isPost
|
||||
&& (this.isSent || this.isPublished)
|
||||
&& this.email && this.email.status !== 'failed';
|
||||
}),
|
||||
|
||||
didEmailFail: computed('isPost', 'isSent', 'isPublished', 'email.status', function () {
|
||||
return this.isPost
|
||||
&& (this.isSent || this.isPublished)
|
||||
&& this.email && this.email.status === 'failed';
|
||||
}),
|
||||
|
||||
showEmailOpenAnalytics: computed('hasBeenEmailed', 'isSent', 'isPublished', function () {
|
||||
return this.hasBeenEmailed
|
||||
&& !this.session.user.isContributor
|
||||
&& this.settings.get('membersSignupAccess') !== 'none'
|
||||
&& this.settings.get('editorDefaultEmailRecipients') !== 'disabled'
|
||||
&& (this.isSent || this.isPublished)
|
||||
&& this.email
|
||||
&& this.hasBeenEmailed
|
||||
&& this.email.trackOpens
|
||||
&& this.settings.get('emailTrackOpens');
|
||||
}),
|
||||
|
||||
showEmailClickAnalytics: computed('isPost', 'isSent', 'isPublished', 'email', function () {
|
||||
return this.isPost
|
||||
showEmailClickAnalytics: computed('hasBeenEmailed', 'isSent', 'isPublished', 'email', function () {
|
||||
return this.hasBeenEmailed
|
||||
&& !this.session.user.isContributor
|
||||
&& this.settings.get('membersSignupAccess') !== 'none'
|
||||
&& this.settings.get('editorDefaultEmailRecipients') !== 'disabled'
|
||||
&& (this.isSent || this.isPublished)
|
||||
&& this.email
|
||||
&& this.email.trackClicks
|
||||
&& this.settings.get('emailTrackClicks');
|
||||
}),
|
||||
|
|
|
@ -1165,6 +1165,11 @@ a.gh-post-list-signups.active:hover > span, a.gh-post-list-conversions.active:ho
|
|||
background: var(--pink);
|
||||
}
|
||||
|
||||
.gh-posts-list-item-labs .gh-content-entry-status .error {
|
||||
color: var(--red);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.gh-posts-list-item-labs .schedule-details {
|
||||
margin-left: 3px;
|
||||
color: var(--midlightgrey-d1);
|
||||
|
@ -1340,4 +1345,4 @@ a.gh-post-list-cta.stats.is-hovered:hover > * {
|
|||
top: -0.3em;
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -356,6 +356,10 @@
|
|||
color: var(--green-d1);
|
||||
}
|
||||
|
||||
.gh-btn-editor.midgrey span {
|
||||
color: var(--midgrey-l2);
|
||||
}
|
||||
|
||||
.gh-editor-wordcount-container {
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
|
|
|
@ -32,11 +32,12 @@
|
|||
</LinkTo>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if (or (not this.ui.isFullScreen) (not this.fromAnalytics)) }}
|
||||
{{#if (or (not this.ui.isFullScreen) (not this.fromAnalytics) this.post.didEmailFail) }}
|
||||
<div class="gh-editor-post-status">
|
||||
<span>
|
||||
<GhEditorPostStatus
|
||||
@post={{this.post}}
|
||||
@publishManagement={{publishManagement}}
|
||||
@hasDirtyAttributes={{this.hasDirtyAttributes}}
|
||||
@isSaving={{or this.autosaveTask.isRunning this.saveTasks.isRunning}}
|
||||
@openUpdateFlow={{publishManagement.openUpdateFlow}}
|
||||
|
|
|
@ -23,10 +23,14 @@ export default class PublishOptions {
|
|||
}
|
||||
|
||||
get willEmail() {
|
||||
return this.publishType !== 'publish'
|
||||
&& this.recipientFilter
|
||||
&& this.post.isDraft
|
||||
&& !this.post.email;
|
||||
return (
|
||||
(this.publishType !== 'publish'
|
||||
&& this.recipientFilter
|
||||
&& this.post.isDraft
|
||||
&& !this.post.email
|
||||
)
|
||||
|| (this.post.isDraft && this.post.email && this.post.email.status === 'failed')
|
||||
);
|
||||
}
|
||||
|
||||
get willPublish() {
|
||||
|
@ -255,6 +259,10 @@ export default class PublishOptions {
|
|||
) {
|
||||
this.publishType = 'publish';
|
||||
}
|
||||
|
||||
if (this.post.isSent) {
|
||||
this.publishType = 'send';
|
||||
}
|
||||
}
|
||||
|
||||
@task
|
||||
|
|
Loading…
Add table
Reference in a new issue