0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-03 23:00:14 -05:00

Run import run operations in order.

Closes #1977, Refs #3473
- Ensure that import operations are run in sequence.
  Previously the operations were started in order but subsequent
  ops were allowed to begin before the previous finished, which would
  result in out-of-order execution.
- Fix bug in attach() where a model property was being passed in
  instead of a transaction object.  If the call was made when a
  transaction was in process, it could cause bookshelf/knex to
  hang and never finish the transaction.
This commit is contained in:
Jason Williams 2014-08-26 20:41:11 +00:00
parent efddc98f53
commit 35e2387541
4 changed files with 89 additions and 42 deletions

View file

@ -93,10 +93,9 @@ Importer000.prototype.doUserImport = function (t, tableData, users, errors) {
Importer000.prototype.doImport = function (data) {
var self = this,
ops = [],
errors = [],
tableData = data.data,
imported = {},
errors = [],
users = {},
owner = {};
@ -108,6 +107,8 @@ Importer000.prototype.doImport = function (data) {
// Step 1: Attempt to handle adding new users
self.doUserImport(t, tableData, users, errors).then(function (result) {
var importResults = [];
imported.users = result;
_.each(imported.users, function (user) {
@ -126,33 +127,28 @@ Importer000.prototype.doImport = function (data) {
tableData = utils.preProcessPostTags(tableData);
}
// Import things in the right order:
if (tableData.tags && tableData.tags.length) {
utils.importTags(ops, tableData.tags, t);
}
// Import things in the right order
if (tableData.posts && tableData.posts.length) {
utils.importPosts(ops, tableData.posts, t);
}
return utils.importTags(tableData.tags, t).then(function (results) {
if (results) {
importResults = importResults.concat(results);
}
if (tableData.settings && tableData.settings.length) {
utils.importSettings(ops, tableData.settings, t);
}
return utils.importPosts(tableData.posts, t);
}).then(function (results) {
if (results) {
importResults = importResults.concat(results);
}
/** do nothing with these tables, the data shouldn't have changed from the fixtures
* permissions
* roles
* permissions_roles
* permissions_users
*/
// Write changes to DB, if successful commit, otherwise rollback
Promise.settle(ops).then(function (descriptors) {
var errors = [];
descriptors.forEach(function (d) {
if (d.isRejected()) {
errors = errors.concat(d.reason());
return utils.importSettings(tableData.settings, t);
}).then(function (results) {
if (results) {
importResults = importResults.concat(results);
}
}).then(function () {
importResults.forEach(function (p) {
if (p.isRejected()) {
errors = errors.concat(p.reason());
}
});
@ -162,6 +158,13 @@ Importer000.prototype.doImport = function (data) {
t.rollback(errors);
}
});
/** do nothing with these tables, the data shouldn't have changed from the fixtures
* permissions
* roles
* permissions_roles
* permissions_users
*/
});
}).then(function () {
//TODO: could return statistics of imported items

View file

@ -30,11 +30,13 @@ cleanError = function cleanError(error) {
offendingProperty = temp.length === 2 ? temp[1] : error.model;
temp = offendingProperty.split('.');
value = temp.length === 2 ? error.data[temp[1]] : 'unknown';
} else if (error.raw.detail) {
value = error.raw.detail;
offendingProperty = error.model;
}
message = 'Duplicate entry found. Multiple values of "' + value + '" found for ' + offendingProperty + '.';
}
offendingProperty = offendingProperty || error.model;
value = value || 'unknown';
message = message || error.raw.message;

View file

@ -149,7 +149,13 @@ utils = {
return tableData;
},
importTags: function importTags(ops, tableData, transaction) {
importTags: function importTags(tableData, transaction) {
if (!tableData) {
return Promise.resolve();
}
var ops = [];
tableData = stripProperties(['id'], tableData);
_.each(tableData, function (tag) {
// Validate minimum tag fields
@ -160,7 +166,6 @@ utils = {
ops.push(models.Tag.findOne({name: tag.name}, {transacting: transaction}).then(function (_tag) {
if (!_tag) {
return models.Tag.add(tag, _.extend(internal, {transacting: transaction}))
// add pass-through error handling so that bluebird doesn't think we've dropped it
.catch(function (error) {
return Promise.reject({raw: error, model: 'tag', data: tag});
});
@ -169,21 +174,46 @@ utils = {
return _tag;
}));
});
return Promise.settle(ops);
},
importPosts: function importPosts(ops, tableData, transaction) {
importPosts: function importPosts(tableData, transaction) {
if (!tableData) {
return Promise.resolve();
}
var ops = [];
tableData = stripProperties(['id'], tableData);
_.each(tableData, function (post) {
// Validate minimum post fields
if (areEmpty(post, 'title', 'slug', 'markdown')) {
return;
}
ops.push(models.Post.add(post, _.extend(internal, {transacting: transaction, importing: true}))
// add pass-through error handling so that bluebird doesn't think we've dropped it
.catch(function (error) {
return Promise.reject({raw: error, model: 'post', data: post});
}));
ops.push(function () {
return models.Post.add(post, _.extend(internal, {transacting: transaction, importing: true}))
.catch(function (error) {
return Promise.reject({raw: error, model: 'post', data: post});
});
});
});
return Promise.reduce(ops, function (results, op) {
return op().then(function (result) {
results.push(result);
return results;
}).catch(function (error) {
if (error) {
results.push(error);
}
return results;
});
}, []).settle();
},
importUsers: function importUsers(tableData, existingUsers, transaction) {
@ -206,7 +236,6 @@ utils = {
user.status = 'locked';
ops.push(models.User.add(user, _.extend(internal, {transacting: transaction}))
// add pass-through error handling so that bluebird doesn't think we've dropped it
.catch(function (error) {
return Promise.reject({raw: error, model: 'user', data: user});
}));
@ -215,11 +244,16 @@ utils = {
return ops;
},
importSettings: function importSettings(ops, tableData, transaction) {
importSettings: function importSettings(tableData, transaction) {
if (!tableData) {
return Promise.resolve();
}
// for settings we need to update individual settings, and insert any missing ones
// settings we MUST NOT update are 'core' and 'theme' settings
// as all of these will cause side effects which don't make sense for an import
var blackList = ['core', 'theme'];
var blackList = ['core', 'theme'],
ops = [];
tableData = stripProperties(['id'], tableData);
tableData = _.filter(tableData, function (data) {
@ -232,21 +266,27 @@ utils = {
});
ops.push(models.Settings.edit(tableData, _.extend(internal, {transacting: transaction}))
// add pass-through error handling so that bluebird doesn't think we've dropped it
.catch(function (error) {
return Promise.reject({raw: error, model: 'setting', data: tableData});
}));
return Promise.settle(ops);
},
/** For later **/
importApps: function importApps(ops, tableData, transaction) {
importApps: function importApps(tableData, transaction) {
if (!tableData) {
return Promise.resolve();
}
var ops = [];
tableData = stripProperties(['id'], tableData);
_.each(tableData, function (app) {
// Avoid duplicates
ops.push(models.App.findOne({name: app.name}, {transacting: transaction}).then(function (_app) {
if (!_app) {
return models.App.add(app, _.extend(internal, {transacting: transaction}))
// add pass-through error handling so that bluebird doesn't think we've dropped it
.catch(function (error) {
return Promise.reject({raw: error, model: 'app', data: app});
});
@ -255,6 +295,8 @@ utils = {
return _app;
}));
});
return Promise.settle(ops);
}
};

View file

@ -144,7 +144,7 @@ Post = ghostBookshelf.Model.extend({
createdTag = createdTag.toJSON();
// _.omit(options, 'query') is a fix for using bookshelf 0.6.8
// (https://github.com/tgriesser/bookshelf/issues/294)
return post.tags().attach(createdTag.id, createdTag.name, _.omit(options, 'query'));
return post.tags().attach(createdTag.id, _.omit(options, 'query'));
});
tagOps.push(createAndAttachOperation);