mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-25 02:31:59 -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
|
@ -33,6 +33,7 @@
|
||||||
<Editor::Modals::PublishFlow::CompleteWithEmailError
|
<Editor::Modals::PublishFlow::CompleteWithEmailError
|
||||||
@emailErrorMessage={{this.emailErrorMessage}}
|
@emailErrorMessage={{this.emailErrorMessage}}
|
||||||
@publishOptions={{@data.publishOptions}}
|
@publishOptions={{@data.publishOptions}}
|
||||||
|
@setCompleted={{this.setCompleted}}
|
||||||
@close={{@close}}
|
@close={{@close}}
|
||||||
/>
|
/>
|
||||||
{{else if this.isConfirming}}
|
{{else if this.isConfirming}}
|
||||||
|
@ -59,4 +60,4 @@
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,7 +13,7 @@ export default class PublishModalComponent extends Component {
|
||||||
|
|
||||||
@service store;
|
@service store;
|
||||||
|
|
||||||
@tracked emailErrorMessage;
|
@tracked emailErrorMessage = this.args.data.publishOptions.post.didEmailFail ? this.args.data.publishOptions.post.email.error : undefined;
|
||||||
@tracked isConfirming = false;
|
@tracked isConfirming = false;
|
||||||
@tracked isComplete = false;
|
@tracked isComplete = false;
|
||||||
@tracked postCount = null;
|
@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
|
@task
|
||||||
*saveTask() {
|
*saveTask() {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -42,6 +42,8 @@ export default class PublishFlowCompleteWithEmailError extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.args.setCompleted();
|
||||||
|
|
||||||
return email;
|
return email;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// update "failed" state if email fails again
|
// update "failed" state if email fails again
|
||||||
|
|
|
@ -107,7 +107,11 @@
|
||||||
<div class="gh-publish-setting-title disabled" data-test-setting-title>
|
<div class="gh-publish-setting-title disabled" data-test-setting-title>
|
||||||
{{svg-jar "member"}}
|
{{svg-jar "member"}}
|
||||||
<div class="gh-publish-setting-trigger">
|
<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}}
|
{{format-number @publishOptions.post.email.emailCount}}
|
||||||
{{if (not-eq @recipientType "all") @recipientType}} {{!-- free/paid/specific --}}
|
{{if (not-eq @recipientType "all") @recipientType}} {{!-- free/paid/specific --}}
|
||||||
{{gh-pluralize @publishOptions.post.email.emailCount "subscriber" without-count=true}}
|
{{gh-pluralize @publishOptions.post.email.emailCount "subscriber" without-count=true}}
|
||||||
|
@ -152,4 +156,4 @@
|
||||||
<span>Continue, final review →</span>
|
<span>Continue, final review →</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
|
|
|
@ -35,8 +35,7 @@
|
||||||
{{if post.isScheduled "will be" "was"}}
|
{{if post.isScheduled "will be" "was"}}
|
||||||
|
|
||||||
{{#if
|
{{#if
|
||||||
(or post.isSent
|
(or post.hasBeenEmailed
|
||||||
(and post.isPublished post.email)
|
|
||||||
post.willEmail
|
post.willEmail
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -69,4 +69,4 @@
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -3,8 +3,19 @@
|
||||||
{{#if (and this.isSaving @post.isDraft)}}
|
{{#if (and this.isSaving @post.isDraft)}}
|
||||||
Saving...
|
Saving...
|
||||||
{{else if @post.isSent}}
|
{{else if @post.isSent}}
|
||||||
<button type="button" {{on "click" @openUpdateFlow}} class="gh-editor-post-status-btn">Sent</button>
|
{{#if @post.didEmailFail }}
|
||||||
to {{gh-pluralize @post.email.emailCount "member"}}
|
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)}}
|
{{else if (and @post.emailOnly @post.isScheduled)}}
|
||||||
Scheduled
|
Scheduled
|
||||||
{{#if this.isHovered}}
|
{{#if this.isHovered}}
|
||||||
|
@ -19,6 +30,15 @@
|
||||||
and sending to {{gh-pluralize @post.email.emailCount "member"}}
|
and sending to {{gh-pluralize @post.email.emailCount "member"}}
|
||||||
{{else if (eq @post.email.status "submitted")}}
|
{{else if (eq @post.email.status "submitted")}}
|
||||||
and sent to {{gh-pluralize @post.email.emailCount "member"}}
|
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}}
|
{{/if}}
|
||||||
{{else if @post.isScheduled}}
|
{{else if @post.isScheduled}}
|
||||||
<time datetime="{{@post.publishedAtUTC}}" class="ml1 green-d1" data-test-schedule-countdown>
|
<time datetime="{{@post.publishedAtUTC}}" class="ml1 green-d1" data-test-schedule-countdown>
|
||||||
|
|
|
@ -91,9 +91,11 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if @post.isPublished}}
|
{{#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
|
Published
|
||||||
{{#if @post.hasEmail}}
|
{{#if @post.didEmailFail}}
|
||||||
|
but failed to send
|
||||||
|
{{else if @post.hasBeenEmailed}}
|
||||||
and sent
|
and sent
|
||||||
{{#if this.isHovered}}
|
{{#if this.isHovered}}
|
||||||
<span {{css-transition "anim-fade-in-scale"}}>to {{gh-pluralize @post.email.emailCount "member"}}</span>
|
<span {{css-transition "anim-fade-in-scale"}}>to {{gh-pluralize @post.email.emailCount "member"}}</span>
|
||||||
|
@ -103,10 +105,14 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if @post.isSent}}
|
{{#if @post.isSent}}
|
||||||
<span class="sent" {{on "mouseover" (fn (mut this.isStateHovered) true)}} {{on "mouseleave" (fn (mut this.isStateHovered) false)}}>
|
<span class="sent {{this.errorClass}}" {{on "mouseover" (fn (mut this.isStateHovered) true)}} {{on "mouseleave" (fn (mut this.isStateHovered) false)}}>
|
||||||
Sent
|
{{#if @post.didEmailFail}}
|
||||||
{{#if this.isHovered}}
|
Failed to send
|
||||||
<span {{css-transition "anim-fade-in-scale"}}>to {{gh-pluralize @post.email.emailCount "member"}}</span>
|
{{else}}
|
||||||
|
Sent
|
||||||
|
{{#if this.isHovered}}
|
||||||
|
<span {{css-transition "anim-fade-in-scale"}}>to {{gh-pluralize @post.email.emailCount "member"}}</span>
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</span>
|
</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -15,6 +15,13 @@ export default class PostsListItemClicks extends Component {
|
||||||
return this.args.post;
|
return this.args.post;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get errorClass() {
|
||||||
|
if (this.post.didEmailFail) {
|
||||||
|
return 'error';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
get scheduledText() {
|
get scheduledText() {
|
||||||
let text = [];
|
let text = [];
|
||||||
|
|
||||||
|
|
|
@ -13,19 +13,19 @@
|
||||||
</h2>
|
</h2>
|
||||||
<div class="gh-post-analytics-meta">
|
<div class="gh-post-analytics-meta">
|
||||||
<div class="gh-post-analytics-meta-text">
|
<div class="gh-post-analytics-meta-text">
|
||||||
{{#if
|
{{#if this.post.hasBeenEmailed }}
|
||||||
(or this.post.isSent
|
|
||||||
(and this.post.isPublished this.post.email)
|
|
||||||
this.post.willEmail
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
{{#if this.post.emailOnly}}
|
{{#if this.post.emailOnly}}
|
||||||
Sent
|
Sent
|
||||||
{{else}}
|
{{else}}
|
||||||
Published and sent
|
Published and sent
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{else}}
|
{{else}}
|
||||||
Published on your site
|
Published
|
||||||
|
{{#if @post.didEmailFail}}
|
||||||
|
but failed to send
|
||||||
|
{{else}}
|
||||||
|
on your site
|
||||||
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#let (moment-site-tz this.post.publishedAtUTC) as |publishedAt|}}
|
{{#let (moment-site-tz this.post.publishedAtUTC) as |publishedAt|}}
|
||||||
|
@ -46,7 +46,7 @@
|
||||||
Engagement
|
Engagement
|
||||||
</h4>
|
</h4>
|
||||||
<div class="gh-post-analytics-box">
|
<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">
|
<div class="gh-post-analytics-item">
|
||||||
<LinkTo @route="members" @query={{hash filterParam=(concat "emails.post_id:[" this.post.id "]") }}>
|
<LinkTo @route="members" @query={{hash filterParam=(concat "emails.post_id:[" this.post.id "]") }}>
|
||||||
<h3>{{format-number this.post.email.emailCount}}</h3>
|
<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;
|
return this.isScheduled && !!this.newsletter && !this.email;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
showEmailOpenAnalytics: computed('isPost', 'isSent', 'isPublished', 'email', function () {
|
hasBeenEmailed: computed('isPost', 'isSent', 'isPublished', 'email', function () {
|
||||||
return this.isPost
|
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.session.user.isContributor
|
||||||
&& this.settings.get('membersSignupAccess') !== 'none'
|
&& this.settings.get('membersSignupAccess') !== 'none'
|
||||||
&& this.settings.get('editorDefaultEmailRecipients') !== 'disabled'
|
&& this.settings.get('editorDefaultEmailRecipients') !== 'disabled'
|
||||||
&& (this.isSent || this.isPublished)
|
&& this.hasBeenEmailed
|
||||||
&& this.email
|
|
||||||
&& this.email.trackOpens
|
&& this.email.trackOpens
|
||||||
&& this.settings.get('emailTrackOpens');
|
&& this.settings.get('emailTrackOpens');
|
||||||
}),
|
}),
|
||||||
|
|
||||||
showEmailClickAnalytics: computed('isPost', 'isSent', 'isPublished', 'email', function () {
|
showEmailClickAnalytics: computed('hasBeenEmailed', 'isSent', 'isPublished', 'email', function () {
|
||||||
return this.isPost
|
return this.hasBeenEmailed
|
||||||
&& !this.session.user.isContributor
|
&& !this.session.user.isContributor
|
||||||
&& this.settings.get('membersSignupAccess') !== 'none'
|
&& this.settings.get('membersSignupAccess') !== 'none'
|
||||||
&& this.settings.get('editorDefaultEmailRecipients') !== 'disabled'
|
&& this.settings.get('editorDefaultEmailRecipients') !== 'disabled'
|
||||||
&& (this.isSent || this.isPublished)
|
&& (this.isSent || this.isPublished)
|
||||||
&& this.email
|
|
||||||
&& this.email.trackClicks
|
&& this.email.trackClicks
|
||||||
&& this.settings.get('emailTrackClicks');
|
&& 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);
|
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 {
|
.gh-posts-list-item-labs .schedule-details {
|
||||||
margin-left: 3px;
|
margin-left: 3px;
|
||||||
color: var(--midlightgrey-d1);
|
color: var(--midlightgrey-d1);
|
||||||
|
@ -1340,4 +1345,4 @@ a.gh-post-list-cta.stats.is-hovered:hover > * {
|
||||||
top: -0.3em;
|
top: -0.3em;
|
||||||
font-size: 1.8rem;
|
font-size: 1.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -356,6 +356,10 @@
|
||||||
color: var(--green-d1);
|
color: var(--green-d1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gh-btn-editor.midgrey span {
|
||||||
|
color: var(--midgrey-l2);
|
||||||
|
}
|
||||||
|
|
||||||
.gh-editor-wordcount-container {
|
.gh-editor-wordcount-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 30px;
|
right: 30px;
|
||||||
|
|
|
@ -32,11 +32,12 @@
|
||||||
</LinkTo>
|
</LinkTo>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/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">
|
<div class="gh-editor-post-status">
|
||||||
<span>
|
<span>
|
||||||
<GhEditorPostStatus
|
<GhEditorPostStatus
|
||||||
@post={{this.post}}
|
@post={{this.post}}
|
||||||
|
@publishManagement={{publishManagement}}
|
||||||
@hasDirtyAttributes={{this.hasDirtyAttributes}}
|
@hasDirtyAttributes={{this.hasDirtyAttributes}}
|
||||||
@isSaving={{or this.autosaveTask.isRunning this.saveTasks.isRunning}}
|
@isSaving={{or this.autosaveTask.isRunning this.saveTasks.isRunning}}
|
||||||
@openUpdateFlow={{publishManagement.openUpdateFlow}}
|
@openUpdateFlow={{publishManagement.openUpdateFlow}}
|
||||||
|
|
|
@ -23,10 +23,14 @@ export default class PublishOptions {
|
||||||
}
|
}
|
||||||
|
|
||||||
get willEmail() {
|
get willEmail() {
|
||||||
return this.publishType !== 'publish'
|
return (
|
||||||
&& this.recipientFilter
|
(this.publishType !== 'publish'
|
||||||
&& this.post.isDraft
|
&& this.recipientFilter
|
||||||
&& !this.post.email;
|
&& this.post.isDraft
|
||||||
|
&& !this.post.email
|
||||||
|
)
|
||||||
|
|| (this.post.isDraft && this.post.email && this.post.email.status === 'failed')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get willPublish() {
|
get willPublish() {
|
||||||
|
@ -255,6 +259,10 @@ export default class PublishOptions {
|
||||||
) {
|
) {
|
||||||
this.publishType = 'publish';
|
this.publishType = 'publish';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.post.isSent) {
|
||||||
|
this.publishType = 'send';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@task
|
@task
|
||||||
|
|
Loading…
Add table
Reference in a new issue