mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-11-24 06:35:49 +03:00
✨ Support filtering based on primary_tag (#9124)
closes #8668, refs #8920 - Updated tests to include internal tags - Tests had no example of an internal tag - Need this to show that the new filtering works as expected - primary_tag is a calculated field - This ensures that we can alias the field to equivalent logic in API filters - By replacing primary_tag by a lookup based on a tag which has order 0 - bump ghost-gql to 0.0.8 **NOTE:** Until GQL is refactored, there are limitations on what else can be filtered when using primary_tag in a filter e.g. it wont be possible to do a filter based on primary_tag AND/OR other tag filters.
This commit is contained in:
parent
2941932ebe
commit
7999c38fa2
@ -1,7 +1,7 @@
|
||||
var _ = require('lodash'),
|
||||
var _ = require('lodash'),
|
||||
errors = require('../../errors'),
|
||||
gql = require('ghost-gql'),
|
||||
i18n = require('../../i18n'),
|
||||
gql = require('ghost-gql'),
|
||||
i18n = require('../../i18n'),
|
||||
filter,
|
||||
filterUtils;
|
||||
|
||||
@ -83,6 +83,17 @@ filter = function filter(Bookshelf) {
|
||||
enforcedFilters: function enforcedFilters() {},
|
||||
defaultFilters: function defaultFilters() {},
|
||||
|
||||
preProcessFilters: function preProcessFilters() {
|
||||
this._filters.statements = gql.json.replaceStatements(this._filters.statements, {prop: /primary_tag/}, function (statement) {
|
||||
statement.prop = 'tags.slug';
|
||||
return {group: [
|
||||
statement,
|
||||
{prop: 'posts_tags.sort_order', op: '=', value: 0},
|
||||
{prop: 'tags.visibility', op: '=', value: 'public'}
|
||||
]};
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* ## Post process Filters
|
||||
* Post Process filters looking for joins etc
|
||||
@ -160,6 +171,8 @@ filter = function filter(Bookshelf) {
|
||||
gql.json.printStatements(this._filters.statements);
|
||||
}
|
||||
|
||||
this.preProcessFilters(options);
|
||||
|
||||
this.query(function (qb) {
|
||||
gql.knexify(qb, self._filters);
|
||||
});
|
||||
|
@ -154,11 +154,11 @@ describe('Advanced Browse', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('4. Posts - filter="author:[leslie,pat]+(tag:audio,image:-null)"', function () {
|
||||
describe('4. Posts - filter="author:[leslie,pat]+(tag:hash-audio,image:-null)"', function () {
|
||||
// Note that `pat` doesn't exist (it's `pat-smith`)
|
||||
it('Will fetch posts by the author `leslie` or `pat` which are either have tag `audio` or an image.', function (done) {
|
||||
it('Will fetch posts by the author `leslie` or `pat` which are either have tag `hash-audio` or an image.', function (done) {
|
||||
PostAPI.browse({
|
||||
filter: 'author:[leslie,pat]+(tag:audio,feature_image:-null)',
|
||||
filter: 'author:[leslie,pat]+(tag:hash-audio,feature_image:-null)',
|
||||
include: 'author,tags'
|
||||
}).then(function (result) {
|
||||
var ids, authors;
|
||||
@ -177,7 +177,7 @@ describe('Advanced Browse', function () {
|
||||
});
|
||||
authors.should.matchAny(/leslie|pat/);
|
||||
|
||||
// Each post must either be featured or have the tag 'audio'
|
||||
// Each post must either be featured or have the tag 'hash-audio'
|
||||
_.each(result.posts, function (post) {
|
||||
var tags = _.map(post.tags, 'slug');
|
||||
// This construct ensures we get an assertion or a failure
|
||||
@ -185,7 +185,7 @@ describe('Advanced Browse', function () {
|
||||
post.feature_image.should.not.be.empty();
|
||||
} else {
|
||||
tags = _.map(post.tags, 'slug');
|
||||
tags.should.containEql('audio');
|
||||
tags.should.containEql('hash-audio');
|
||||
}
|
||||
});
|
||||
|
||||
@ -365,6 +365,107 @@ describe('Advanced Browse', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Primary Tags', function () {
|
||||
it('Will fetch posts which have a primary tag of photo', function (done) {
|
||||
PostAPI.browse({
|
||||
filter: 'primary_tag:photo',
|
||||
include: 'tags'
|
||||
}).then(function (result) {
|
||||
var ids;
|
||||
|
||||
// 1. Result should have the correct base structure
|
||||
should.exist(result);
|
||||
result.should.have.property('posts');
|
||||
result.should.have.property('meta');
|
||||
|
||||
// 2. The data part of the response should be correct
|
||||
// We should have 5 matching items
|
||||
result.posts.should.be.an.Array().with.lengthOf(4);
|
||||
|
||||
ids = _.map(result.posts, 'id');
|
||||
ids.should.eql([
|
||||
testUtils.filterData.data.posts[10].id,
|
||||
testUtils.filterData.data.posts[8].id,
|
||||
testUtils.filterData.data.posts[2].id,
|
||||
testUtils.filterData.data.posts[1].id
|
||||
]);
|
||||
|
||||
_.each(result.posts, function (post) {
|
||||
post.page.should.be.false();
|
||||
post.status.should.eql('published');
|
||||
});
|
||||
|
||||
// 3. The meta object should contain the right details
|
||||
result.meta.should.have.property('pagination');
|
||||
result.meta.pagination.should.be.an.Object().with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||
result.meta.pagination.page.should.eql(1);
|
||||
result.meta.pagination.limit.should.eql(15);
|
||||
result.meta.pagination.pages.should.eql(1);
|
||||
result.meta.pagination.total.should.eql(4);
|
||||
should.equal(result.meta.pagination.next, null);
|
||||
should.equal(result.meta.pagination.prev, null);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('Will fetch empty list if no post has matching primary-tag', function (done) {
|
||||
PostAPI.browse({
|
||||
filter: 'primary_tag:no-posts',
|
||||
include: 'tags'
|
||||
}).then(function (result) {
|
||||
// 1. Result should have the correct base structure
|
||||
should.exist(result);
|
||||
result.should.have.property('posts');
|
||||
result.should.have.property('meta');
|
||||
|
||||
// 2. The data part of the response should be correct
|
||||
// We should have 5 matching items
|
||||
result.posts.should.be.an.Array().with.lengthOf(0);
|
||||
|
||||
// 3. The meta object should contain the right details
|
||||
result.meta.should.have.property('pagination');
|
||||
result.meta.pagination.should.be.an.Object().with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||
result.meta.pagination.page.should.eql(1);
|
||||
result.meta.pagination.limit.should.eql(15);
|
||||
result.meta.pagination.pages.should.eql(1);
|
||||
result.meta.pagination.total.should.eql(0);
|
||||
should.equal(result.meta.pagination.next, null);
|
||||
should.equal(result.meta.pagination.prev, null);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('Will fetch empty list if primary_tag is internal', function (done) {
|
||||
PostAPI.browse({
|
||||
filter: 'primary_tag:no-posts',
|
||||
include: 'tags'
|
||||
}).then(function (result) {
|
||||
// 1. Result should have the correct base structure
|
||||
should.exist(result);
|
||||
result.should.have.property('posts');
|
||||
result.should.have.property('meta');
|
||||
|
||||
// 2. The data part of the response should be correct
|
||||
// We should have 5 matching items
|
||||
result.posts.should.be.an.Array().with.lengthOf(0);
|
||||
|
||||
// 3. The meta object should contain the right details
|
||||
result.meta.should.have.property('pagination');
|
||||
result.meta.pagination.should.be.an.Object().with.properties(['page', 'limit', 'pages', 'total', 'next', 'prev']);
|
||||
result.meta.pagination.page.should.eql(1);
|
||||
result.meta.pagination.limit.should.eql(15);
|
||||
result.meta.pagination.pages.should.eql(1);
|
||||
result.meta.pagination.total.should.eql(0);
|
||||
should.equal(result.meta.pagination.next, null);
|
||||
should.equal(result.meta.pagination.prev, null);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Count capabilities', function () {
|
||||
it('can fetch `count.posts` for tags (public data only)', function (done) {
|
||||
TagAPI.browse({include: 'count.posts'}).then(function (result) {
|
||||
@ -391,7 +492,7 @@ describe('Advanced Browse', function () {
|
||||
}).count.posts.should.eql(5);
|
||||
|
||||
_.find(result.tags, function (tag) {
|
||||
return tag.name === 'Audio';
|
||||
return tag.name === '#Audio';
|
||||
}).count.posts.should.eql(6);
|
||||
|
||||
_.find(result.tags, function (tag) {
|
||||
@ -443,7 +544,7 @@ describe('Advanced Browse', function () {
|
||||
}).count.posts.should.eql(5);
|
||||
|
||||
_.find(result.tags, function (tag) {
|
||||
return tag.name === 'Audio';
|
||||
return tag.name === '#Audio';
|
||||
}).count.posts.should.eql(6);
|
||||
|
||||
_.find(result.tags, function (tag) {
|
||||
|
@ -167,7 +167,8 @@ describe('Filter', function () {
|
||||
fetchSpy = sandbox.stub(ghostBookshelf.Model.prototype, 'fetchAndCombineFilters');
|
||||
filterGQL.knexify = sandbox.stub();
|
||||
filterGQL.json = {
|
||||
printStatements: sandbox.stub()
|
||||
printStatements: sandbox.stub(),
|
||||
replaceStatements: sandbox.stub().returnsArg(0)
|
||||
};
|
||||
|
||||
restoreGQL = filter.__set__('gql', filterGQL);
|
||||
@ -226,8 +227,12 @@ describe('Filter', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Pre Process Filters', function () {
|
||||
it('should not have tests yet, as this needs to be removed');
|
||||
});
|
||||
|
||||
describe('Post Process Filters', function () {
|
||||
it('should not have tests yet, as this is about to be removed');
|
||||
it('should not have tests yet, as this needs to be removed');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -358,6 +358,19 @@ DataGenerator.forKnex = (function () {
|
||||
});
|
||||
}
|
||||
|
||||
function createTag(overrides) {
|
||||
var newObj = _.cloneDeep(overrides);
|
||||
|
||||
return _.defaults(newObj, {
|
||||
id: ObjectId.generate(),
|
||||
visibility: 'public',
|
||||
created_by: DataGenerator.Content.users[0].id,
|
||||
created_at: new Date(),
|
||||
updated_by: DataGenerator.Content.users[0].id,
|
||||
updated_at: new Date()
|
||||
});
|
||||
}
|
||||
|
||||
function createPost(overrides) {
|
||||
var newObj = _.cloneDeep(overrides),
|
||||
mobiledoc = JSON.parse(overrides.mobiledoc || '{}');
|
||||
@ -521,11 +534,11 @@ DataGenerator.forKnex = (function () {
|
||||
];
|
||||
|
||||
tags = [
|
||||
createBasic(DataGenerator.Content.tags[0]),
|
||||
createBasic(DataGenerator.Content.tags[1]),
|
||||
createBasic(DataGenerator.Content.tags[2]),
|
||||
createBasic(DataGenerator.Content.tags[3]),
|
||||
createBasic(DataGenerator.Content.tags[4])
|
||||
createTag(DataGenerator.Content.tags[0]),
|
||||
createTag(DataGenerator.Content.tags[1]),
|
||||
createTag(DataGenerator.Content.tags[2]),
|
||||
createTag(DataGenerator.Content.tags[3]),
|
||||
createTag(DataGenerator.Content.tags[4])
|
||||
];
|
||||
|
||||
roles = [
|
||||
@ -616,7 +629,7 @@ DataGenerator.forKnex = (function () {
|
||||
return {
|
||||
createPost: createPost,
|
||||
createGenericPost: createGenericPost,
|
||||
createTag: createBasic,
|
||||
createTag: createTag,
|
||||
createUser: createUser,
|
||||
createClient: createClient,
|
||||
createGenericUser: createGenericUser,
|
||||
|
@ -59,10 +59,11 @@ data.tags = [
|
||||
},
|
||||
{
|
||||
id: ObjectId.generate(),
|
||||
name: 'Audio',
|
||||
slug: 'audio',
|
||||
name: '#Audio',
|
||||
slug: 'hash-audio',
|
||||
feature_image: 'some/image/path.jpg',
|
||||
description: 'Audio posts',
|
||||
visibility: 'internal',
|
||||
created_by: data.users[0].id
|
||||
},
|
||||
{
|
||||
@ -308,7 +309,7 @@ function createUsers(knex, DataGenerator) {
|
||||
|
||||
function createTags(knex, DataGenerator) {
|
||||
data.tags = _.map(data.tags, function (tag) {
|
||||
return DataGenerator.forKnex.createBasic(tag);
|
||||
return DataGenerator.forKnex.createTag(tag);
|
||||
});
|
||||
|
||||
// Next, insert it into the database & return the correctly indexed data
|
||||
|
@ -49,7 +49,7 @@
|
||||
"express-hbs": "1.0.4",
|
||||
"extract-zip-fork": "1.5.1",
|
||||
"fs-extra": "3.0.1",
|
||||
"ghost-gql": "0.0.6",
|
||||
"ghost-gql": "0.0.8",
|
||||
"ghost-ignition": "2.8.14",
|
||||
"ghost-storage-base": "0.0.1",
|
||||
"glob": "5.0.15",
|
||||
|
@ -1745,9 +1745,9 @@ getsetdeep@~2.0.0:
|
||||
dependencies:
|
||||
typechecker "~2.0.1"
|
||||
|
||||
ghost-gql@0.0.6:
|
||||
version "0.0.6"
|
||||
resolved "https://registry.yarnpkg.com/ghost-gql/-/ghost-gql-0.0.6.tgz#be811bc95f8f72671009c33100fc85d2d02758ee"
|
||||
ghost-gql@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/ghost-gql/-/ghost-gql-0.0.8.tgz#630410cf1f71ccffbdab3d9d01419981c794b0ce"
|
||||
dependencies:
|
||||
lodash "^4.17.4"
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user