2022-12-02 12:08:40 +03:00
|
|
|
import Component from '@glimmer/component';
|
|
|
|
import moment from 'moment-timezone';
|
|
|
|
import {action} from '@ember/object';
|
2023-02-20 18:44:13 +03:00
|
|
|
import {didCancel, task, timeout} from 'ember-concurrency';
|
2022-12-02 18:58:12 +03:00
|
|
|
import {formatNumber} from 'ghost-admin/helpers/format-number';
|
|
|
|
import {ghPluralize} from 'ghost-admin/helpers/gh-pluralize';
|
2022-12-02 12:08:40 +03:00
|
|
|
import {inject as service} from '@ember/service';
|
|
|
|
import {tracked} from '@glimmer/tracking';
|
|
|
|
export default class Debug extends Component {
|
|
|
|
@service ajax;
|
|
|
|
@service ghostPaths;
|
|
|
|
@service settings;
|
|
|
|
@service membersUtils;
|
|
|
|
@service utils;
|
|
|
|
@service feature;
|
2023-02-20 18:44:13 +03:00
|
|
|
@service store;
|
2022-12-02 12:08:40 +03:00
|
|
|
|
|
|
|
@tracked emailBatches = null;
|
|
|
|
@tracked recipientFailures = null;
|
2022-12-02 18:58:12 +03:00
|
|
|
@tracked loading = true;
|
2023-02-20 18:44:13 +03:00
|
|
|
@tracked analyticsStatus = null;
|
|
|
|
@tracked latestEmail = null;
|
2022-12-02 12:08:40 +03:00
|
|
|
|
|
|
|
get post() {
|
|
|
|
return this.args.post;
|
|
|
|
}
|
|
|
|
|
2023-02-20 18:44:13 +03:00
|
|
|
get email() {
|
|
|
|
return this.latestEmail ?? this.post.email;
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateEmail() {
|
|
|
|
try {
|
|
|
|
this.latestEmail = await this.store.findRecord('email', this.post.email.id, {reload: true});
|
|
|
|
} catch (e) {
|
|
|
|
// Skip
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-02 13:42:24 +03:00
|
|
|
get emailError() {
|
2022-12-02 18:58:12 +03:00
|
|
|
// get failed batches count
|
|
|
|
let failedBatches = this.emailBatchesData?.filter((batch) => {
|
|
|
|
return batch.statusClass === 'failed';
|
|
|
|
}).length || 0;
|
|
|
|
// get total batch count
|
|
|
|
let totalBatches = this.emailBatchesData?.length || 0;
|
|
|
|
|
|
|
|
let details = (this.loading || !totalBatches) ? '' : `${failedBatches} of ${ghPluralize(totalBatches, 'batch')} failed to send, check below for more details.`;
|
2022-12-02 13:52:59 +03:00
|
|
|
return {
|
2022-12-02 18:58:12 +03:00
|
|
|
message: this.post.email?.error || 'Failed to send email.',
|
|
|
|
details
|
2022-12-02 13:52:59 +03:00
|
|
|
};
|
2022-12-02 13:42:24 +03:00
|
|
|
}
|
|
|
|
|
2022-12-02 12:08:40 +03:00
|
|
|
get emailSettings() {
|
|
|
|
return {
|
2023-02-20 18:44:13 +03:00
|
|
|
statusClass: this.email?.status,
|
|
|
|
status: this.getStatusLabel(this.email?.status),
|
|
|
|
recipientFilter: this.email?.recipientFilter,
|
|
|
|
createdAt: this.email?.createdAtUTC ? moment(this.email.createdAtUTC).format('DD MMM, YYYY, HH:mm:ss') : '',
|
|
|
|
submittedAt: this.email?.submittedAtUTC ? moment(this.email.submittedAtUTC).format('DD MMM, YYYY, HH:mm:ss') : '',
|
|
|
|
emailsSent: this.email?.emailCount,
|
|
|
|
emailsDelivered: this.email?.deliveredCount,
|
|
|
|
emailsOpened: this.email?.openedCount,
|
|
|
|
emailsFailed: this.email?.failedCount,
|
|
|
|
trackOpens: this.email?.trackOpens,
|
|
|
|
trackClicks: this.email?.trackClicks,
|
|
|
|
feedbackEnabled: this.email?.feedbackEnabled
|
2022-12-02 12:08:40 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
get tabTotals() {
|
|
|
|
return {
|
2022-12-02 18:58:12 +03:00
|
|
|
temporaryFailures: formatNumber(this.temporaryFailureData?.length || 0),
|
|
|
|
permanentFailures: formatNumber(this.permanentFailureData?.length || 0),
|
|
|
|
erroredBatches: formatNumber(this.emailBatchesData?.filter((batch) => {
|
2022-12-02 12:08:40 +03:00
|
|
|
return batch.statusClass === 'failed';
|
2022-12-02 18:58:12 +03:00
|
|
|
}).length || 0)
|
2022-12-02 12:08:40 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
get emailBatchesData() {
|
|
|
|
return this.emailBatches?.map((batch) => {
|
|
|
|
return {
|
|
|
|
id: batch.id,
|
|
|
|
status: this.getStatusLabel(batch.status),
|
|
|
|
statusClass: batch.status,
|
|
|
|
createdAt: batch.created_at ? moment(batch.created_at).format('DD MMM, YYYY, HH:mm:ss') : '',
|
|
|
|
segment: batch.member_segment || '',
|
|
|
|
providerId: batch.provider_id || null,
|
|
|
|
errorMessage: batch.error_message || '',
|
|
|
|
errorStatusCode: batch.error_status_code || '',
|
|
|
|
recipientCount: batch.count?.recipients || 0
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
get temporaryFailureData() {
|
|
|
|
return this.recipientFailures?.filter((failure) => {
|
|
|
|
return failure.severity === 'temporary';
|
|
|
|
}).map((failure) => {
|
|
|
|
return {
|
|
|
|
id: failure.id,
|
|
|
|
code: failure.code,
|
2022-12-02 13:42:24 +03:00
|
|
|
failedAt: failure.failed_at ? moment(failure.failed_at).format('DD MMM, YYYY, HH:mm:ss') : '',
|
|
|
|
processedAt: failure.email_recipient.processed_at ? moment(failure.email_recipient.processed_at).format('DD MMM, YYYY, HH:mm:ss') : '',
|
|
|
|
batchId: failure.email_recipient.batch_id,
|
2022-12-02 12:08:40 +03:00
|
|
|
enhancedCode: failure.enhanced_code,
|
|
|
|
message: failure.message,
|
|
|
|
recipient: {
|
|
|
|
name: failure.email_recipient.member_name || '',
|
|
|
|
email: failure.email_recipient.member_email || '',
|
|
|
|
initials: this.getInitials(failure.email_recipient?.member_name || failure.email_recipient?.member_email)
|
|
|
|
},
|
|
|
|
member: {
|
2023-01-26 17:12:26 +03:00
|
|
|
record: failure.member,
|
|
|
|
id: failure.member?.id,
|
2022-12-02 12:08:40 +03:00
|
|
|
name: failure.member?.name || '',
|
|
|
|
email: failure.member?.email || '',
|
|
|
|
initials: this.getInitials(failure.member?.name)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
get permanentFailureData() {
|
|
|
|
return this.recipientFailures?.filter((failure) => {
|
|
|
|
return failure.severity === 'permanent';
|
|
|
|
}).map((failure) => {
|
|
|
|
return {
|
|
|
|
id: failure.id,
|
|
|
|
code: failure.code,
|
|
|
|
enhancedCode: failure.enhanced_code,
|
|
|
|
message: failure.message,
|
|
|
|
recipient: {
|
|
|
|
name: failure.email_recipient.member_name || '',
|
|
|
|
email: failure.email_recipient.member_email || '',
|
|
|
|
initials: this.getInitials(failure.email_recipient?.member_name || failure.email_recipient?.member_email)
|
|
|
|
},
|
|
|
|
member: {
|
2023-01-26 17:12:26 +03:00
|
|
|
record: failure.member,
|
|
|
|
id: failure.member?.id,
|
2022-12-02 12:08:40 +03:00
|
|
|
name: failure.member?.name || '',
|
|
|
|
email: failure.member?.email || '',
|
|
|
|
initials: this.getInitials(failure.member?.name)
|
|
|
|
}
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
getInitials(name) {
|
|
|
|
if (!name) {
|
|
|
|
return 'U';
|
|
|
|
}
|
|
|
|
let names = name.split(' ');
|
|
|
|
let intials = names.length > 1 ? [names[0][0], names[names.length - 1][0]] : [names[0][0]];
|
|
|
|
return intials.join('').toUpperCase();
|
|
|
|
}
|
|
|
|
|
|
|
|
getStatusLabel(status) {
|
|
|
|
if (status === 'submitted') {
|
|
|
|
return 'Submitted';
|
|
|
|
} else if (status === 'submitting') {
|
|
|
|
return 'Submitting';
|
|
|
|
} else if (status === 'pending') {
|
|
|
|
return 'Pending';
|
|
|
|
} else if (status === 'failed') {
|
|
|
|
return 'Failed';
|
|
|
|
}
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
loadData() {
|
2022-12-02 18:10:25 +03:00
|
|
|
if (this.post.email) {
|
|
|
|
this.fetchEmailBatches();
|
|
|
|
this.fetchRecipientFailures();
|
2023-02-20 18:44:13 +03:00
|
|
|
this.pollAnalyticsStatus.perform();
|
|
|
|
this.pollEmail.perform();
|
2022-12-02 18:10:25 +03:00
|
|
|
}
|
2022-12-02 12:08:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async fetchEmailBatches() {
|
|
|
|
try {
|
|
|
|
if (this._fetchEmailBatches.isRunning) {
|
|
|
|
return this._fetchEmailBatches.last;
|
|
|
|
}
|
|
|
|
return this._fetchEmailBatches.perform();
|
|
|
|
} catch (e) {
|
|
|
|
if (!didCancel(e)) {
|
|
|
|
// re-throw the non-cancelation error
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@task
|
|
|
|
*_fetchEmailBatches() {
|
|
|
|
const data = {
|
2022-12-02 16:49:21 +03:00
|
|
|
include: 'count.recipients',
|
|
|
|
limit: 'all',
|
|
|
|
order: 'status asc, created_at desc'
|
2022-12-02 12:08:40 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
let statsUrl = this.ghostPaths.url.api(`emails/${this.post.email.id}/batches`);
|
|
|
|
let result = yield this.ajax.request(statsUrl, {data});
|
|
|
|
this.emailBatches = result.batches;
|
2022-12-02 18:58:12 +03:00
|
|
|
this.loading = false;
|
2022-12-02 12:08:40 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
async fetchRecipientFailures() {
|
|
|
|
try {
|
|
|
|
if (this._fetchRecipientFailures.isRunning) {
|
|
|
|
return this._fetchRecipientFailures.last;
|
|
|
|
}
|
|
|
|
return this._fetchRecipientFailures.perform();
|
|
|
|
} catch (e) {
|
|
|
|
if (!didCancel(e)) {
|
|
|
|
// re-throw the non-cancelation error
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-20 18:44:13 +03:00
|
|
|
@task
|
|
|
|
*pollAnalyticsStatus() {
|
|
|
|
while (true) {
|
|
|
|
yield this.fetchAnalyticsStatus();
|
|
|
|
yield timeout(5 * 1000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@task
|
|
|
|
*pollEmail() {
|
|
|
|
while (true) {
|
|
|
|
yield timeout(10 * 1000);
|
|
|
|
yield this.updateEmail();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async fetchAnalyticsStatus() {
|
|
|
|
try {
|
|
|
|
if (this._fetchAnalyticsStatus.isRunning) {
|
|
|
|
return this._fetchAnalyticsStatus.last;
|
|
|
|
}
|
|
|
|
return this._fetchAnalyticsStatus.perform();
|
|
|
|
} catch (e) {
|
|
|
|
// Skip
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-02 12:08:40 +03:00
|
|
|
@task
|
|
|
|
*_fetchRecipientFailures() {
|
|
|
|
const data = {
|
2022-12-02 16:49:21 +03:00
|
|
|
include: 'member,email_recipient',
|
|
|
|
limit: 'all'
|
2022-12-02 12:08:40 +03:00
|
|
|
};
|
|
|
|
let statsUrl = this.ghostPaths.url.api(`/emails/${this.post.email.id}/recipient-failures`);
|
|
|
|
let result = yield this.ajax.request(statsUrl, {data});
|
|
|
|
this.recipientFailures = result.failures;
|
|
|
|
}
|
2023-02-20 18:44:13 +03:00
|
|
|
|
|
|
|
@task
|
|
|
|
*_fetchAnalyticsStatus() {
|
|
|
|
let statsUrl = this.ghostPaths.url.api(`/emails/${this.post.email.id}/analytics`);
|
|
|
|
let result = yield this.ajax.request(statsUrl);
|
|
|
|
this.analyticsStatus = result;
|
|
|
|
|
|
|
|
// Parse dates
|
|
|
|
for (const type of Object.keys(result)) {
|
|
|
|
if (!result[type]) {
|
|
|
|
result[type] = {};
|
|
|
|
}
|
|
|
|
let object = result[type];
|
|
|
|
for (const key of ['lastStarted', 'lastBegin', 'lastEventTimestamp']) {
|
|
|
|
if (object[key]) {
|
|
|
|
object[key] = moment(object[key]).format('DD MMM, YYYY, HH:mm:ss.SSS');
|
|
|
|
} else {
|
|
|
|
object[key] = 'N/A';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (object.schedule) {
|
|
|
|
object = object.schedule;
|
|
|
|
for (const key of ['begin', 'end']) {
|
|
|
|
if (object[key]) {
|
|
|
|
object[key] = moment(object[key]).format('DD MMM, YYYY, HH:mm:ss.SSS');
|
|
|
|
} else {
|
|
|
|
object[key] = 'N/A';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
scheduleAnalytics() {
|
|
|
|
try {
|
|
|
|
if (this._scheduleAnalytics.isRunning) {
|
|
|
|
return this._scheduleAnalytics.last;
|
|
|
|
}
|
|
|
|
return this._scheduleAnalytics.perform();
|
|
|
|
} catch (e) {
|
|
|
|
if (!didCancel(e)) {
|
|
|
|
// re-throw the non-cancelation error
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@task
|
|
|
|
*_scheduleAnalytics() {
|
|
|
|
let statsUrl = this.ghostPaths.url.api(`/emails/${this.post.email.id}/analytics`);
|
|
|
|
yield this.ajax.put(statsUrl, {});
|
|
|
|
yield this.fetchAnalyticsStatus();
|
|
|
|
}
|
|
|
|
|
|
|
|
@action
|
|
|
|
cancelScheduleAnalytics() {
|
|
|
|
try {
|
|
|
|
if (this._cancelScheduleAnalytics.isRunning) {
|
|
|
|
return this._cancelScheduleAnalytics.last;
|
|
|
|
}
|
|
|
|
return this._cancelScheduleAnalytics.perform();
|
|
|
|
} catch (e) {
|
|
|
|
if (!didCancel(e)) {
|
|
|
|
// re-throw the non-cancelation error
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@task
|
|
|
|
*_cancelScheduleAnalytics() {
|
|
|
|
let statsUrl = this.ghostPaths.url.api(`/emails/analytics`);
|
|
|
|
yield this.ajax.delete(statsUrl, {});
|
|
|
|
yield this.fetchAnalyticsStatus();
|
|
|
|
}
|
2022-12-02 12:08:40 +03:00
|
|
|
}
|