mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-19 08:31:43 +03:00
change default order of posts (#7096)
closes #6932 - new default order of posts: scheduled, draft, published - invent orderDefaultRaw fn for each model - each model is able to create a default raw order query - separate count and fetch query for fetchPage, because the count query where group/order statements attached
This commit is contained in:
parent
6439d60bc8
commit
416c215018
core
server/models
test
@ -348,9 +348,12 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
|||||||
|
|
||||||
if (options.order) {
|
if (options.order) {
|
||||||
options.order = self.parseOrderOption(options.order, options.include);
|
options.order = self.parseOrderOption(options.order, options.include);
|
||||||
|
} else if (self.orderDefaultRaw) {
|
||||||
|
options.orderRaw = self.orderDefaultRaw();
|
||||||
} else {
|
} else {
|
||||||
options.order = self.orderDefaultOptions();
|
options.order = self.orderDefaultOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
return itemCollection.fetchPage(options).then(function formatResponse(response) {
|
return itemCollection.fetchPage(options).then(function formatResponse(response) {
|
||||||
var data = {};
|
var data = {};
|
||||||
|
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
//
|
//
|
||||||
// Extends Bookshelf.Model with a `fetchPage` method. Handles everything to do with paginated requests.
|
// Extends Bookshelf.Model with a `fetchPage` method. Handles everything to do with paginated requests.
|
||||||
var _ = require('lodash'),
|
var _ = require('lodash'),
|
||||||
Promise = require('bluebird'),
|
|
||||||
|
|
||||||
defaults,
|
defaults,
|
||||||
paginationUtils,
|
paginationUtils,
|
||||||
pagination;
|
pagination;
|
||||||
@ -130,6 +128,11 @@ pagination = function pagination(bookshelf) {
|
|||||||
/**
|
/**
|
||||||
* ### Fetch page
|
* ### Fetch page
|
||||||
* A `fetch` extension to get a paginated set of items from a collection
|
* A `fetch` extension to get a paginated set of items from a collection
|
||||||
|
*
|
||||||
|
* We trigger two queries:
|
||||||
|
* 1. count query to know how many pages left (important: we don't attach any group/order statements!)
|
||||||
|
* 2. the actualy fetch query with limit and page property
|
||||||
|
*
|
||||||
* @param {options} options
|
* @param {options} options
|
||||||
* @returns {paginatedResult} set of results + pagination metadata
|
* @returns {paginatedResult} set of results + pagination metadata
|
||||||
*/
|
*/
|
||||||
@ -140,57 +143,58 @@ pagination = function pagination(bookshelf) {
|
|||||||
// Get the table name and idAttribute for this model
|
// Get the table name and idAttribute for this model
|
||||||
var tableName = _.result(this.constructor.prototype, 'tableName'),
|
var tableName = _.result(this.constructor.prototype, 'tableName'),
|
||||||
idAttribute = _.result(this.constructor.prototype, 'idAttribute'),
|
idAttribute = _.result(this.constructor.prototype, 'idAttribute'),
|
||||||
countPromise,
|
self = this,
|
||||||
collectionPromise,
|
countPromise = this.query().clone().select(
|
||||||
self = this;
|
bookshelf.knex.raw('count(distinct ' + tableName + '.' + idAttribute + ') as aggregate')
|
||||||
|
);
|
||||||
|
|
||||||
|
// the debug flag doesn't work for the raw knex count query!
|
||||||
|
if (this.debug) {
|
||||||
|
console.log('COUNT', countPromise.toQuery());
|
||||||
|
}
|
||||||
|
|
||||||
// #### Pre count clauses
|
// #### Pre count clauses
|
||||||
// Add any where or join clauses which need to be included with the aggregate query
|
// Add any where or join clauses which need to be included with the aggregate query
|
||||||
|
|
||||||
// Clone the base query & set up a promise to get the count of total items in the full set
|
// Clone the base query & set up a promise to get the count of total items in the full set
|
||||||
// Due to lack of support for count distinct, this is pretty complex.
|
// Due to lack of support for count distinct, this is pretty complex.
|
||||||
countPromise = this.query().clone().select(
|
return countPromise.then(function (countResult) {
|
||||||
bookshelf.knex.raw('count(distinct ' + tableName + '.' + idAttribute + ') as aggregate')
|
// #### Post count clauses
|
||||||
);
|
// Add any where or join clauses which need to NOT be included with the aggregate query
|
||||||
|
|
||||||
// #### Post count clauses
|
// Setup the pagination parameters so that we return the correct items from the set
|
||||||
// Add any where or join clauses which need to NOT be included with the aggregate query
|
paginationUtils.addLimitAndOffset(self, options);
|
||||||
|
|
||||||
// Setup the pagination parameters so that we return the correct items from the set
|
// Apply ordering options if they are present
|
||||||
paginationUtils.addLimitAndOffset(self, options);
|
if (options.order && !_.isEmpty(options.order)) {
|
||||||
|
_.forOwn(options.order, function (direction, property) {
|
||||||
|
if (property === 'count.posts') {
|
||||||
|
self.query('orderBy', 'count__posts', direction);
|
||||||
|
} else {
|
||||||
|
self.query('orderBy', tableName + '.' + property, direction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (options.orderRaw) {
|
||||||
|
self.query(function (qb) {
|
||||||
|
qb.orderByRaw(options.orderRaw);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Apply ordering options if they are present
|
if (options.groups && !_.isEmpty(options.groups)) {
|
||||||
if (options.order && !_.isEmpty(options.order)) {
|
_.each(options.groups, function (group) {
|
||||||
_.forOwn(options.order, function (direction, property) {
|
self.query('groupBy', group);
|
||||||
if (property === 'count.posts') {
|
});
|
||||||
self.query('orderBy', 'count__posts', direction);
|
}
|
||||||
} else {
|
|
||||||
self.query('orderBy', tableName + '.' + property, direction);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.groups && !_.isEmpty(options.groups)) {
|
// Setup the promise to do a fetch on our collection, running the specified query
|
||||||
_.each(options.groups, function (group) {
|
// @TODO: ensure option handling is done using an explicit pick elsewhere
|
||||||
self.query('groupBy', group);
|
return self.fetchAll(_.omit(options, ['page', 'limit']))
|
||||||
});
|
.then(function (fetchResult) {
|
||||||
}
|
return {
|
||||||
|
collection: fetchResult,
|
||||||
if (this.debug) {
|
pagination: paginationUtils.formatResponse(countResult[0] ? countResult[0].aggregate : 0, options)
|
||||||
console.log('COUNT', countPromise.toQuery());
|
};
|
||||||
}
|
});
|
||||||
|
|
||||||
// Setup the promise to do a fetch on our collection, running the specified query
|
|
||||||
// @TODO: ensure option handling is done using an explicit pick elsewhere
|
|
||||||
collectionPromise = self.fetchAll(_.omit(options, ['page', 'limit']));
|
|
||||||
|
|
||||||
// Resolve the two promises
|
|
||||||
return Promise.join(collectionPromise, countPromise).then(function formatResponse(results) {
|
|
||||||
// Format the collection & count result into `{collection: [], pagination: {}}`
|
|
||||||
return {
|
|
||||||
collection: results[0],
|
|
||||||
pagination: paginationUtils.formatResponse(results[1][0] ? results[1][0].aggregate : 0, options)
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -429,6 +429,16 @@ Post = ghostBookshelf.Model.extend({
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
orderDefaultRaw: function () {
|
||||||
|
return '' +
|
||||||
|
'CASE WHEN posts.status = \'scheduled\' THEN 1 ' +
|
||||||
|
'WHEN posts.status = \'draft\' THEN 2 ' +
|
||||||
|
'ELSE 3 END ASC,' +
|
||||||
|
'posts.published_at DESC,' +
|
||||||
|
'posts.updated_at DESC,' +
|
||||||
|
'posts.id DESC';
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated in favour of filter
|
* @deprecated in favour of filter
|
||||||
*/
|
*/
|
||||||
|
@ -21,6 +21,7 @@ describe('Post API', function () {
|
|||||||
done();
|
done();
|
||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
Promise.mapSeries(testUtils.DataGenerator.forKnex.tags, function (tag) {
|
Promise.mapSeries(testUtils.DataGenerator.forKnex.tags, function (tag) {
|
||||||
return models.Tag.add(tag, {context: {internal:true}});
|
return models.Tag.add(tag, {context: {internal:true}});
|
||||||
@ -28,6 +29,7 @@ describe('Post API', function () {
|
|||||||
done();
|
done();
|
||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
db.knex('posts_tags').insert(testUtils.DataGenerator.forKnex.posts_tags)
|
db.knex('posts_tags').insert(testUtils.DataGenerator.forKnex.posts_tags)
|
||||||
.then(function () {
|
.then(function () {
|
||||||
@ -43,17 +45,45 @@ describe('Post API', function () {
|
|||||||
should.exist(PostAPI);
|
should.exist(PostAPI);
|
||||||
|
|
||||||
describe('Browse', function () {
|
describe('Browse', function () {
|
||||||
it('can fetch featured posts', function (done) {
|
it('can fetch all posts with internal context in correct order', function (done) {
|
||||||
PostAPI.browse({context: {user: 1}, filter: 'featured:true'}).then(function (results) {
|
PostAPI.browse({context: {internal: true}}).then(function (results) {
|
||||||
should.exist(results.posts);
|
should.exist(results.posts);
|
||||||
results.posts.length.should.eql(4);
|
results.posts.length.should.eql(8);
|
||||||
results.posts[0].featured.should.eql(true);
|
|
||||||
|
results.posts[0].status.should.eql('scheduled');
|
||||||
|
|
||||||
|
results.posts[1].status.should.eql('draft');
|
||||||
|
results.posts[2].status.should.eql('draft');
|
||||||
|
|
||||||
|
results.posts[3].status.should.eql('published');
|
||||||
|
results.posts[4].status.should.eql('published');
|
||||||
|
results.posts[5].status.should.eql('published');
|
||||||
|
results.posts[6].status.should.eql('published');
|
||||||
|
results.posts[7].status.should.eql('published');
|
||||||
|
|
||||||
done();
|
done();
|
||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can exclude featured posts', function (done) {
|
it('can fetch featured posts for user 1', function (done) {
|
||||||
|
PostAPI.browse({context: {user: 1}, filter: 'featured:true'}).then(function (results) {
|
||||||
|
should.exist(results.posts);
|
||||||
|
results.posts.length.should.eql(4);
|
||||||
|
results.posts[0].featured.should.eql(true);
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can fetch featured posts for user 2', function (done) {
|
||||||
|
PostAPI.browse({context: {user: 2}, filter: 'featured:true'}).then(function (results) {
|
||||||
|
should.exist(results.posts);
|
||||||
|
results.posts.length.should.eql(4);
|
||||||
|
results.posts[0].featured.should.eql(true);
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can exclude featured posts for user 1', function (done) {
|
||||||
PostAPI.browse({context: {user: 1}, status: 'all', filter: 'featured:false'}).then(function (results) {
|
PostAPI.browse({context: {user: 1}, status: 'all', filter: 'featured:false'}).then(function (results) {
|
||||||
should.exist(results.posts);
|
should.exist(results.posts);
|
||||||
results.posts.length.should.eql(1);
|
results.posts.length.should.eql(1);
|
||||||
@ -135,7 +165,8 @@ describe('Post API', function () {
|
|||||||
PostAPI.browse({context: {user: 1}, page: 1, limit: 2, status: 'all'}).then(function (results) {
|
PostAPI.browse({context: {user: 1}, page: 1, limit: 2, status: 'all'}).then(function (results) {
|
||||||
should.exist(results.posts);
|
should.exist(results.posts);
|
||||||
results.posts.length.should.eql(2);
|
results.posts.length.should.eql(2);
|
||||||
results.posts[0].slug.should.eql('unfinished');
|
results.posts[0].slug.should.eql('scheduled-post');
|
||||||
|
results.posts[1].slug.should.eql('unfinished');
|
||||||
results.meta.pagination.page.should.eql(1);
|
results.meta.pagination.page.should.eql(1);
|
||||||
results.meta.pagination.next.should.eql(2);
|
results.meta.pagination.next.should.eql(2);
|
||||||
|
|
||||||
@ -147,7 +178,8 @@ describe('Post API', function () {
|
|||||||
PostAPI.browse({context: {user: 1}, page: 2, limit: 2, status: 'all'}).then(function (results) {
|
PostAPI.browse({context: {user: 1}, page: 2, limit: 2, status: 'all'}).then(function (results) {
|
||||||
should.exist(results.posts);
|
should.exist(results.posts);
|
||||||
results.posts.length.should.eql(2);
|
results.posts.length.should.eql(2);
|
||||||
results.posts[0].slug.should.eql('short-and-sweet');
|
results.posts[0].slug.should.eql('not-so-short-bit-complex');
|
||||||
|
results.posts[1].slug.should.eql('short-and-sweet');
|
||||||
results.meta.pagination.page.should.eql(2);
|
results.meta.pagination.page.should.eql(2);
|
||||||
results.meta.pagination.next.should.eql(3);
|
results.meta.pagination.next.should.eql(3);
|
||||||
results.meta.pagination.prev.should.eql(1);
|
results.meta.pagination.prev.should.eql(1);
|
||||||
@ -217,8 +249,10 @@ describe('Post API', function () {
|
|||||||
|
|
||||||
it('can include tags', function (done) {
|
it('can include tags', function (done) {
|
||||||
PostAPI.browse({context: {user: 1}, status: 'all', include: 'tags'}).then(function (results) {
|
PostAPI.browse({context: {user: 1}, status: 'all', include: 'tags'}).then(function (results) {
|
||||||
should.exist(results.posts[0].tags[0].name);
|
results.posts[0].tags.length.should.eql(0);
|
||||||
results.posts[0].tags[0].name.should.eql('pollo');
|
results.posts[1].tags.length.should.eql(1);
|
||||||
|
|
||||||
|
results.posts[1].tags[0].name.should.eql('pollo');
|
||||||
done();
|
done();
|
||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
@ -77,7 +77,6 @@ describe('Post Model', function () {
|
|||||||
PostModel.findAll().then(function (results) {
|
PostModel.findAll().then(function (results) {
|
||||||
should.exist(results);
|
should.exist(results);
|
||||||
results.length.should.be.above(1);
|
results.length.should.be.above(1);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
@ -192,7 +192,7 @@ describe('pagination', function () {
|
|||||||
toQuery: sandbox.stub()
|
toQuery: sandbox.stub()
|
||||||
};
|
};
|
||||||
mockQuery.clone.returns(mockQuery);
|
mockQuery.clone.returns(mockQuery);
|
||||||
mockQuery.select.returns([{aggregate: 1}]);
|
mockQuery.select.returns(Promise.resolve([{aggregate: 1}]));
|
||||||
|
|
||||||
model = function () {};
|
model = function () {};
|
||||||
|
|
||||||
@ -340,7 +340,7 @@ describe('pagination', function () {
|
|||||||
|
|
||||||
it('returns expected response even when aggregate is empty', function (done) {
|
it('returns expected response even when aggregate is empty', function (done) {
|
||||||
// override aggregate response
|
// override aggregate response
|
||||||
mockQuery.select.returns([]);
|
mockQuery.select.returns(Promise.resolve([]));
|
||||||
paginationUtils.parseOptions.returns({});
|
paginationUtils.parseOptions.returns({});
|
||||||
|
|
||||||
bookshelf.Model.prototype.fetchPage().then(function (result) {
|
bookshelf.Model.prototype.fetchPage().then(function (result) {
|
||||||
@ -352,18 +352,5 @@ describe('pagination', function () {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('will output sql statements in debug mode', function (done) {
|
|
||||||
model.prototype.debug = true;
|
|
||||||
mockQuery.select.returns({toQuery: function () {}});
|
|
||||||
paginationUtils.parseOptions.returns({});
|
|
||||||
|
|
||||||
var consoleSpy = sandbox.spy(console, 'log');
|
|
||||||
|
|
||||||
bookshelf.Model.prototype.fetchPage().then(function () {
|
|
||||||
consoleSpy.calledOnce.should.be.true();
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user