Skip to content

Commit

Permalink
feat: Merge pull request #45 from pelias/add-support-for-scoring-queries
Browse files Browse the repository at this point in the history
added scoring and filtering support to FallbackQuery and GeodisambiguationQuery
  • Loading branch information
trescube authored Aug 17, 2016
2 parents 3615ac1 + 2886853 commit d4e53c9
Show file tree
Hide file tree
Showing 4 changed files with 454 additions and 0 deletions.
33 changes: 33 additions & 0 deletions layout/FallbackQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@

function Layout(){
this._score = [];
this._filter = [];
}

Layout.prototype.score = function( view, operator ){
this._score.push([ view, operator === 'must' ? 'must': 'should' ]);
return this;
};

Layout.prototype.filter = function( view ){
this._filter.push( view );
return this;
};

function addPrimary(value, layer, fields, likely_to_have_abbreviation) {
// base primary query should match on layer and one of the admin fields via
// multi_match
Expand Down Expand Up @@ -371,6 +377,33 @@ Layout.prototype.render = function( vs ){
q.query.bool.should.push(addCountry(vs));
}

// handle scoring views under 'query' section (both 'must' & 'should')
if( this._score.length ){
this._score.forEach( function( condition ){
var view = condition[0], operator = condition[1];
var rendered = view( vs );
if( rendered ){
if( !q.query.bool.hasOwnProperty( operator ) ){
q.query.bool[ operator ] = [];
}
q.query.bool[ operator ].push( rendered );
}
});
}

// handle filter views under 'filter' section (only 'must' is allowed here)
if( this._filter.length ){
this._filter.forEach( function( view ){
var rendered = view( vs );
if( rendered ){
if( !q.query.bool.hasOwnProperty( 'filter' ) ){
q.query.bool.filter = [];
}
q.query.bool.filter.push( rendered );
}
});
}

return q;
};

Expand Down
33 changes: 33 additions & 0 deletions layout/GeodisambiguationQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,19 @@ var _ = require('lodash');

function Layout(){
this._score = [];
this._filter = [];
}

Layout.prototype.score = function( view, operator ){
this._score.push([ view, operator === 'must' ? 'must': 'should' ]);
return this;
};

Layout.prototype.filter = function( view ){
this._filter.push( view );
return this;
};

function addCoarseLayer(layer, coarse_value, fields) {
var o = {
bool: {
Expand Down Expand Up @@ -101,6 +107,33 @@ Layout.prototype.render = function( vs ){
q.query.bool.should.push(addCoarseLayer('dependency', coarse_value));
q.query.bool.should.push(addCoarseLayer('country', coarse_value));

// handle scoring views under 'query' section (both 'must' & 'should')
if( this._score.length ){
this._score.forEach( function( condition ){
var view = condition[0], operator = condition[1];
var rendered = view( vs );
if( rendered ){
if( !q.query.bool.hasOwnProperty( operator ) ){
q.query.bool[ operator ] = [];
}
q.query.bool[ operator ].push( rendered );
}
});
}

// handle filter views under 'filter' section (only 'must' is allowed here)
if( this._filter.length ){
this._filter.forEach( function( view ){
var rendered = view( vs );
if( rendered ){
if( !q.query.bool.hasOwnProperty( 'filter' ) ){
q.query.bool.filter = [];
}
q.query.bool.filter.push( rendered );
}
});
}

return q;
};

Expand Down
206 changes: 206 additions & 0 deletions test/layout/FallbackQuery.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,212 @@ module.exports.tests.base_render = function(test, common) {

};

module.exports.tests.scores = function(test, common) {
test('score with operator specified should be honored and in order', function(t) {
var score_view1 = function(vs) {
console.assert(vs !== null);
return { 'score field 1': 'score value 1' };
};

var score_view2 = function(vs) {
console.assert(vs !== null);
return { 'score field 2': 'score value 2' };
};

var score_view3 = function(vs) {
console.assert(vs !== null);
return { 'score field 3': 'score value 3' };
};

var score_view4 = function(vs) {
console.assert(vs !== null);
return { 'score field 4': 'score value 4' };
};

var query = new FallbackQuery();
query.score(score_view1, 'must');
query.score(score_view2, 'should');
query.score(score_view3, 'must');
query.score(score_view4, 'should');

var vs = new VariableStore();
vs.var('size', 'size value');
vs.var('track_scores', 'track_scores value');

var actual = query.render(vs);

var expected = {
query: {
bool: {
should: [
{ 'score field 2': 'score value 2'},
{ 'score field 4': 'score value 4'}
],
must: [
{ 'score field 1': 'score value 1'},
{ 'score field 3': 'score value 3'}
]
}
},
size: { $: 'size value' },
track_scores: { $: 'track_scores value' }
};

t.deepEquals(actual, expected);
t.end();

});

test('score without operator specified should be added as \'should\'', function(t) {
var score_view = function(vs) {
console.assert(vs !== null);
return { 'score field': 'score value' };
};

var query = new FallbackQuery();
query.score(score_view);

var vs = new VariableStore();
vs.var('size', 'size value');
vs.var('track_scores', 'track_scores value');

var actual = query.render(vs);

var expected = {
query: {
bool: {
should: [
{ 'score field': 'score value'}
]
}
},
size: { $: 'size value' },
track_scores: { $: 'track_scores value' }
};

t.deepEquals(actual, expected);
t.end();

});

test('score with non-must or -should operator specified should be added as \'should\'', function(t) {
var score_view = function(vs) {
console.assert(vs !== null);
return { 'score field': 'score value' };
};

var query = new FallbackQuery();
query.score(score_view, 'non must or should value');

var vs = new VariableStore();
vs.var('size', 'size value');
vs.var('track_scores', 'track_scores value');

var actual = query.render(vs);

var expected = {
query: {
bool: {
should: [
{ 'score field': 'score value'}
]
}
},
size: { $: 'size value' },
track_scores: { $: 'track_scores value' }
};

t.deepEquals(actual, expected);
t.end();

});

test('scores rendering to falsy values should not be added', function(t) {
var score_views_called = 0;

var query = new FallbackQuery();

[
{ 'score field 1': 'score value 1' },
false, '', 0, null, undefined, NaN,
{ 'score field 2': 'score value 2' },
].forEach(function(value) {
query.score(function(vs) {
// assert that `vs` was actually passed
console.assert(vs !== null);
// make a note that the view was actually called
score_views_called++;
return value;
});
});

var vs = new VariableStore();
vs.var('size', 'size value');
vs.var('track_scores', 'track_scores value');

var actual = query.render(vs);

var expected = {
query: {
bool: {
should: [
{ 'score field 1': 'score value 1'},
{ 'score field 2': 'score value 2'}
]
}
},
size: { $: 'size value' },
track_scores: { $: 'track_scores value' }
};

t.deepEquals(actual, expected);
t.equals(score_views_called, 8);
t.end();

});

};

module.exports.tests.filter = function(test, common) {
test('all filter views returning truthy values should be added in order to sort', function(t) {
// the views assert that the VariableStore was passed, otherwise there's no
// guarantee that it was actually passed
var filter_views_called = 0;

var query = new FallbackQuery();

[
{ 'filter field 1': 'filter value 1' },
false, '', 0, null, undefined, NaN,
{ 'filter field 2': 'filter value 2' },
].forEach(function(value) {
query.filter(function(vs) {
// assert that `vs` was actually passed
console.assert(vs !== null);
// make a note that the view was actually called
filter_views_called++;
return value;
});
});

var vs = new VariableStore();
vs.var('size', 'size value');
vs.var('track_scores', 'track_scores value');

var actual = query.render(vs);

var expected_filter = [
{ 'filter field 1': 'filter value 1'},
{ 'filter field 2': 'filter value 2'}
];

t.equals(filter_views_called, 8);
t.deepEquals(actual.query.bool.filter, expected_filter);
t.end();

});
};

module.exports.all = function (tape, common) {
function test(name, testFunction) {
return tape('address ' + name, testFunction);
Expand Down
Loading

0 comments on commit d4e53c9

Please sign in to comment.