Skip to content

Commit

Permalink
Added /emails/:id/retry/ endpoint for retrying failed emails (#11410)
Browse files Browse the repository at this point in the history
We want to allow admin users to trigger a retry of failed emails without having to go through the unpublish/republish dance.

- fixed resource identifier in email permissions migration so email permissions are added correctly
- added new email permissions migration so that beta releases can be upgraded without rollback (will be a no-op for any non-beta upgrades)
- added `/emails/:id/retry/` canary Admin API endpoint
  - follows same URL pattern as theme activation
  - only triggers mega service retry endpoint if the email has a `'failed'` status
  • Loading branch information
kevinansfield committed Nov 22, 2019
1 parent d7d5d9a commit 6a057fa
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 5 deletions.
29 changes: 28 additions & 1 deletion core/server/api/canary/email.js
@@ -1,5 +1,6 @@
const models = require('../../models');
const common = require('../../lib/common');
const megaService = require('../../services/mega');

module.exports = {
docName: 'emails',
Expand All @@ -22,12 +23,38 @@ module.exports = {
.then((model) => {
if (!model) {
throw new common.errors.NotFoundError({
message: common.i18n.t('errors.api.email.emailNotFound')
message: common.i18n.t('errors.models.email.emailNotFound')
});
}

return model.toJSON(frame.options);
});
}
},

retry: {
data: [
'id'
],
permissions: true,
query(frame) {
return models.Email.findOne(frame.data, frame.options)
.then(async (model) => {
if (!model) {
throw new common.errors.NotFoundError({
message: common.i18n.t('errors.models.email.emailNotFound')
});
}

if (model.get('status') !== 'failed') {
throw new common.errors.IncorrectUsageError({
message: common.i18n.t('errors.models.email.retryNotAllowed')
});
}

const result = await megaService.mega.retryFailedEmail(model);
return result.toJSON(frame.options);
});
}
}
};
4 changes: 4 additions & 0 deletions core/server/api/canary/utils/serializers/output/emails.js
Expand Up @@ -3,5 +3,9 @@ module.exports = {
frame.response = {
emails: [email]
};
},

get retry() {
return this.read;
}
};
Expand Up @@ -3,7 +3,7 @@ const utils = require('../../../schema/fixtures/utils');
const permissions = require('../../../../services/permissions');
const logging = require('../../../../lib/common/logging');

const resources = ['emails'];
const resources = ['email'];
const _private = {};

_private.getPermissions = function getPermissions(resource) {
Expand Down
@@ -0,0 +1,51 @@
const _ = require('lodash');
const utils = require('../../../schema/fixtures/utils');
const permissions = require('../../../../services/permissions');
const logging = require('../../../../lib/common/logging');

const resources = ['email'];
const _private = {};

_private.getPermissions = function getPermissions(resource) {
return utils.findModelFixtures('Permission', {object_type: resource});
};

_private.printResult = function printResult(result, message) {
if (result.done === result.expected) {
logging.info(message);
} else {
logging.warn(`(${result.done}/${result.expected}) ${message}`);
}
};

module.exports.config = {
transaction: true
};

module.exports.up = (options) => {
const localOptions = _.merge({
context: {internal: true}
}, options);

return Promise.map(resources, (resource) => {
const modelToAdd = _private.getPermissions(resource);

return utils.addFixturesForModel(modelToAdd, localOptions)
.then(result => _private.printResult(result, `Adding permissions fixtures for ${resource}`))
.then(() => permissions.init(localOptions));
});
};

module.exports.down = (options) => {
const localOptions = _.merge({
context: {internal: true}
}, options);

return Promise.map(resources, (resource) => {
const modelToRemove = _private.getPermissions(resource);

// permission model automatically cleans up permissions_roles on .destroy()
return utils.removeFixturesForModel(modelToRemove, localOptions)
.then(result => _private.printResult(result, `Removing permissions fixtures for ${resource}s`));
});
};
12 changes: 11 additions & 1 deletion core/server/data/schema/fixtures/fixtures.json
Expand Up @@ -374,9 +374,19 @@
"object_type": "email_preview"
},
{
"name": "Email",
"name": "Browse emails",
"action_type": "browse",
"object_type": "email"
},
{
"name": "Read emails",
"action_type": "read",
"object_type": "email"
},
{
"name": "Retry emails",
"action_type": "retry",
"object_type": "email"
}
]
},
Expand Down
3 changes: 2 additions & 1 deletion core/server/translations/en.json
Expand Up @@ -256,7 +256,8 @@
"apiKeyNotFound": "API Key not found"
},
"email": {
"emailNotFound": "Email not found."
"emailNotFound": "Email not found.",
"retryNotAllowed": "Only failed emails can be retried"
},
"base": {
"index": {
Expand Down
1 change: 1 addition & 0 deletions core/server/web/api/canary/admin/routes.js
Expand Up @@ -221,6 +221,7 @@ module.exports = function apiRoutes() {

// ## Emails
router.get('/emails/:id', mw.authAdminApi, http(apiCanary.emails.read));
router.put('/emails/:id/retry', mw.authAdminApi, http(apiCanary.emails.retry));

return router;
};
2 changes: 1 addition & 1 deletion core/test/unit/data/schema/integrity_spec.js
Expand Up @@ -20,7 +20,7 @@ var should = require('should'),
describe('DB version integrity', function () {
// Only these variables should need updating
const currentSchemaHash = '773f8f6cd4267f50aec6af8c8b1edbd2';
const currentFixturesHash = 'b1787330f042f3954d73c43aa8bfa915';
const currentFixturesHash = '1a0f96fa1d8b976d663eb06719be031c';

// If this test is failing, then it is likely a change has been made that requires a DB version bump,
// and the values above will need updating as confirmation
Expand Down

0 comments on commit 6a057fa

Please sign in to comment.