From d19a641627498ac27521c5e99e33aac35a22e61f Mon Sep 17 00:00:00 2001 From: Julian Simioni Date: Sat, 12 May 2018 16:00:57 -0400 Subject: [PATCH 01/23] Disable semantic-release success actions The default is to comment on every issue which made it into the release. Possibly convenient, definitely spammy. See https://github.com/semantic-release/semantic-release/blob/caribou/docs/usage/plugins.md#success-plugin and https://github.com/semantic-release/github --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 34af521a..8b64cf94 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "test" ], "release": { - "branch": "production" + "branch": "production", + "success": [] } } From 03d873260600fb6426470199effc8c93d073413e Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 17 Apr 2018 19:16:47 +0000 Subject: [PATCH 02/23] fix(package): update joi to version 13.1.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8b64cf94..a27f6001 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "geolib": "^2.0.18", "iso-639-3": "^1.0.0", "iso3166-1": "^0.3.0", - "joi": "^12.0.0", + "joi": "^13.1.3", "locale": "^0.1.0", "lodash": "^4.17.4", "markdown": "0.5.0", From 63c962503c0ff7c4fa4d486e46455bef42112d3c Mon Sep 17 00:00:00 2001 From: missinglink Date: Mon, 7 May 2018 15:44:44 +0200 Subject: [PATCH 03/23] test: improved type_mapping tests --- test/unit/helper/type_mapping.js | 91 ++++++++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 9 deletions(-) diff --git a/test/unit/helper/type_mapping.js b/test/unit/helper/type_mapping.js index ec8cd379..25b06d0f 100644 --- a/test/unit/helper/type_mapping.js +++ b/test/unit/helper/type_mapping.js @@ -4,18 +4,31 @@ var type_mapping = require('../../../helper/type_mapping'); module.exports.tests = {}; module.exports.tests.interfaces = function(test, common) { - test('basic layer mapping', function(t) { - t.deepEquals(type_mapping.layer_mapping.venue, ['venue']); - t.deepEquals(type_mapping.layer_mapping.address, ['address']); + + test('complete sources', function(t) { + t.deepEquals(type_mapping.sources, [ 'openstreetmap', 'openaddresses', 'geonames', 'whosonfirst' ]); t.end(); }); - test('alias layer mapping', function(t) { - t.deepEquals(type_mapping.layer_mapping.coarse, - [ 'continent', 'empire', 'country', 'dependency', 'macroregion', - 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', - 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode', - 'continent', 'ocean', 'marinearea']); + test('complete layers', function(t) { + t.deepEquals(type_mapping.layers, [ 'address', 'venue', 'street', 'country', 'macroregion', + 'region', 'county', 'localadmin', 'locality', 'borough', 'neighbourhood', 'continent', + 'empire', 'dependency', 'macrocounty', 'macrohood', 'microhood', 'disputed', + 'postalcode', 'ocean', 'marinearea' ]); + t.end(); + }); + + test('complete source mapping', function(t) { + t.deepEquals(type_mapping.source_mapping, { + osm: [ 'openstreetmap' ], + oa: [ 'openaddresses' ], + gn: [ 'geonames' ], + wof: [ 'whosonfirst' ], + openstreetmap: [ 'openstreetmap' ], + openaddresses: [ 'openaddresses' ], + geonames: [ 'geonames' ], + whosonfirst: [ 'whosonfirst' ] + }); t.end(); }); @@ -30,6 +43,66 @@ module.exports.tests.interfaces = function(test, common) { t.deepEquals(type_mapping.source_mapping.wof, ['whosonfirst']); t.end(); }); + + test('complete layer mapping', function(t) { + t.deepEquals(type_mapping.layer_mapping, { + coarse: [ 'continent', 'empire', 'country', 'dependency', 'macroregion', 'region', + 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough', + 'neighbourhood', 'microhood', 'disputed', 'postalcode', 'continent', 'ocean', 'marinearea' + ], + address: [ 'address' ], + venue: [ 'venue' ], + street: [ 'street' ], + country: [ 'country' ], + macroregion: [ 'macroregion' ], + region: [ 'region' ], + county: [ 'county' ], + localadmin: [ 'localadmin' ], + locality: [ 'locality' ], + borough: [ 'borough' ], + neighbourhood: [ 'neighbourhood' ], + continent: [ 'continent' ], + empire: [ 'empire' ], + dependency: [ 'dependency' ], + macrocounty: [ 'macrocounty' ], + macrohood: [ 'macrohood' ], + microhood: [ 'microhood' ], + disputed: [ 'disputed' ], + postalcode: [ 'postalcode' ], + ocean: [ 'ocean' ], + marinearea: [ 'marinearea' ] + }); + t.end(); + }); + + test('basic layer mapping', function(t) { + t.deepEquals(type_mapping.layer_mapping.venue, ['venue']); + t.deepEquals(type_mapping.layer_mapping.address, ['address']); + t.end(); + }); + + test('alias layer mapping', function(t) { + t.deepEquals(type_mapping.layer_mapping.coarse, + [ 'continent', 'empire', 'country', 'dependency', 'macroregion', + 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', + 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode', + 'continent', 'ocean', 'marinearea']); + t.end(); + }); + + test('complete layers by source', function(t) { + t.deepEquals(type_mapping.layers_by_source, { + openstreetmap: [ 'address', 'venue', 'street' ], + openaddresses: [ 'address' ], + geonames: [ 'country', 'macroregion', 'region', 'county', 'localadmin', + 'locality', 'borough', 'neighbourhood', 'venue' ], + whosonfirst: [ 'continent', 'empire', 'country', 'dependency', 'macroregion', + 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', + 'borough', 'neighbourhood', 'microhood', 'disputed', 'venue', 'postalcode', + 'continent', 'ocean', 'marinearea' ] + }); + t.end(); + }); }; module.exports.all = function (tape, common) { From e2835dcd1cf3bf110cea4689936de2329045beb6 Mon Sep 17 00:00:00 2001 From: missinglink Date: Mon, 7 May 2018 16:14:32 +0200 Subject: [PATCH 04/23] feat(type_mapping): refactor --- helper/type_mapping.js | 113 ++++++++++++++++++++++------------------- 1 file changed, 62 insertions(+), 51 deletions(-) diff --git a/helper/type_mapping.js b/helper/type_mapping.js index 41303583..bd212cc5 100644 --- a/helper/type_mapping.js +++ b/helper/type_mapping.js @@ -1,6 +1,17 @@ const _ = require('lodash'); -function addStandardTargetsToAliases(standard, aliases) { +var TypeMapping = function(){ + this.sources = []; + this.source_aliases = {}; + this.layers = []; + this.layers_by_source = {}; + this.layer_aliases = {}; + this.source_mapping = {}; + this.layer_mapping = {}; + this.layers_by_source = {}; +}; + +TypeMapping.addStandardTargetsToAliases = function(standard, aliases) { var combined = _.extend({}, aliases); standard.forEach(function(target) { if (combined[target] === undefined) { @@ -9,78 +20,78 @@ function addStandardTargetsToAliases(standard, aliases) { }); return combined; -} +}; -/* - * Sources - */ +TypeMapping.prototype.setSources = function( sources ){ this.sources = sources; }; +TypeMapping.prototype.setSourceAliases = function( aliases ){ this.source_aliases = aliases; }; +TypeMapping.prototype.setLayersBySource = function( lbs ){ this.layers_by_source = lbs; }; +TypeMapping.prototype.setLayerAliases = function( aliases ){ this.layer_aliases = aliases; }; +TypeMapping.prototype.generateDynamicMappings = function(){ -// a list of all sources -var SOURCES = ['openstreetmap', 'openaddresses', 'geonames', 'whosonfirst']; + /* + * Create an object that contains all sources or aliases. The key is the source or alias, + * the value is either that source, or the canonical name for that alias if it's an alias. + */ + this.source_mapping = TypeMapping.addStandardTargetsToAliases(this.sources, this.source_aliases); -/* - * A list of alternate names for sources, mostly used to save typing - */ -var SOURCE_ALIASES = { - 'osm': ['openstreetmap'], - 'oa': ['openaddresses'], - 'gn': ['geonames'], - 'wof': ['whosonfirst'] + // create a list of all layers by combining each entry from this.layers_by_source + this.layers = _.uniq(Object.keys(this.layers_by_source).reduce(function(acc, key) { + return acc.concat(this.layers_by_source[key]); + }.bind(this), [])); + + /* + * Create the an object that has a key for each possible layer or alias, + * and returns either that layer, or all the layers in the alias + */ + this.layer_mapping = TypeMapping.addStandardTargetsToAliases(this.layers, this.layer_aliases); }; -/* - * Create an object that contains all sources or aliases. The key is the source or alias, - * the value is either that source, or the canonical name for that alias if it's an alias. - */ -var SOURCE_MAPPING = addStandardTargetsToAliases(SOURCES, SOURCE_ALIASES); +// instantiate a new type mapping +var tm = new TypeMapping(); + +// a list of all sources +tm.setSources([ 'openstreetmap', 'openaddresses', 'geonames', 'whosonfirst' ]); /* - * Layers + * A list of alternate names for sources, mostly used to save typing */ +tm.setSourceAliases({ + 'osm': [ 'openstreetmap' ], + 'oa': [ 'openaddresses' ], + 'gn': [ 'geonames' ], + 'wof': [ 'whosonfirst' ] +}); /* * A list of all layers in each source. This is used for convenience elswhere * and to determine when a combination of source and layer parameters is * not going to match any records and will return no results. */ -var LAYERS_BY_SOURCE = { - openstreetmap: [ 'address', 'venue', 'street' ], - openaddresses: [ 'address' ], - geonames: [ 'country','macroregion', 'region', 'county','localadmin', - 'locality','borough', 'neighbourhood', 'venue' ], - whosonfirst: [ 'continent', 'empire', 'country', 'dependency', 'macroregion', 'region', - 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough', - 'neighbourhood', 'microhood', 'disputed', 'venue', 'postalcode', - 'continent', 'ocean', 'marinearea'] -}; +tm.setLayersBySource({ + openstreetmap: [ 'address', 'venue', 'street' ], + openaddresses: [ 'address' ], + geonames: [ 'country','macroregion', 'region', 'county','localadmin', + 'locality','borough', 'neighbourhood', 'venue' ], + whosonfirst: [ 'continent', 'empire', 'country', 'dependency', 'macroregion', + 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', + 'borough', 'neighbourhood', 'microhood', 'disputed', 'venue', 'postalcode', + 'continent', 'ocean', 'marinearea' ] +}); /* * A list of layer aliases that can be used to support specific use cases * (like coarse geocoding) * or work around the fact that different sources * may have layers that mean the same thing but have a different name */ -var LAYER_ALIASES = { +tm.setLayerAliases({ 'coarse': [ 'continent', 'empire', 'country', 'dependency', 'macroregion', 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode', - 'continent', 'ocean', 'marinearea'] -}; + 'continent', 'ocean', 'marinearea' ] +}); -// create a list of all layers by combining each entry from LAYERS_BY_SOURCE -var LAYERS = _.uniq(Object.keys(LAYERS_BY_SOURCE).reduce(function(acc, key) { - return acc.concat(LAYERS_BY_SOURCE[key]); -}, [])); +// generate the dynamic mappings +tm.generateDynamicMappings(); -/* - * Create the an object that has a key for each possible layer or alias, - * and returns either that layer, or all the layers in the alias - */ -var LAYER_MAPPING = addStandardTargetsToAliases(LAYERS, LAYER_ALIASES); - -module.exports = { - sources: SOURCES, - layers: LAYERS, - source_mapping: SOURCE_MAPPING, - layer_mapping: LAYER_MAPPING, - layers_by_source: LAYERS_BY_SOURCE -}; +// export singleton +module.exports = tm; \ No newline at end of file From 2dd2061f8e3e0621c3e86617c2139118993f33c4 Mon Sep 17 00:00:00 2001 From: missinglink Date: Mon, 7 May 2018 17:14:14 +0200 Subject: [PATCH 05/23] feat(targets): configuration-based filter targets --- helper/type_mapping.js | 168 ++++++++++++++++++++++++++--------------- index.js | 14 ++-- package.json | 2 +- 3 files changed, 117 insertions(+), 67 deletions(-) diff --git a/helper/type_mapping.js b/helper/type_mapping.js index bd212cc5..72e86554 100644 --- a/helper/type_mapping.js +++ b/helper/type_mapping.js @@ -1,14 +1,42 @@ const _ = require('lodash'); +const elasticsearch = require('elasticsearch'); var TypeMapping = function(){ + + // A list of all sources this.sources = []; + + // A list of alternate names for sources, mostly used to save typing this.source_aliases = {}; + + // A list of all layers this.layers = []; + + /* + * A list of all layers in each source. This is used for convenience elswhere + * and to determine when a combination of source and layer parameters is + * not going to match any records and will return no results. + */ this.layers_by_source = {}; + + /* + * A list of layer aliases that can be used to support specific use cases + * (like coarse geocoding) * or work around the fact that different sources + * may have layers that mean the same thing but have a different name + */ this.layer_aliases = {}; + + /* + * An object that contains all sources or aliases. The key is the source or alias, + * the value is either that source, or the canonical name for that alias if it's an alias. + */ this.source_mapping = {}; + + /* + * An object that has a key for each possible layer or alias, + * and returns either that layer, or gall the layers in the alias + */ this.layer_mapping = {}; - this.layers_by_source = {}; }; TypeMapping.addStandardTargetsToAliases = function(standard, aliases) { @@ -22,76 +50,94 @@ TypeMapping.addStandardTargetsToAliases = function(standard, aliases) { return combined; }; -TypeMapping.prototype.setSources = function( sources ){ this.sources = sources; }; -TypeMapping.prototype.setSourceAliases = function( aliases ){ this.source_aliases = aliases; }; -TypeMapping.prototype.setLayersBySource = function( lbs ){ this.layers_by_source = lbs; }; -TypeMapping.prototype.setLayerAliases = function( aliases ){ this.layer_aliases = aliases; }; -TypeMapping.prototype.generateDynamicMappings = function(){ +// source alias setter +TypeMapping.prototype.setSourceAliases = function( aliases ){ + this.source_aliases = aliases; +}; - /* - * Create an object that contains all sources or aliases. The key is the source or alias, - * the value is either that source, or the canonical name for that alias if it's an alias. - */ - this.source_mapping = TypeMapping.addStandardTargetsToAliases(this.sources, this.source_aliases); +// layers-by-source alias setter +TypeMapping.prototype.setLayersBySource = function( lbs ){ + this.layers_by_source = lbs; +}; + +// layer alias setter +TypeMapping.prototype.setLayerAliases = function( aliases ){ + this.layer_aliases = aliases; +}; - // create a list of all layers by combining each entry from this.layers_by_source +// generate dynamic mappings after setters have been run +TypeMapping.prototype.generateDynamicMappings = function(){ + this.sources = Object.keys( this.layers_by_source ); + this.source_mapping = TypeMapping.addStandardTargetsToAliases(this.sources, this.source_aliases); this.layers = _.uniq(Object.keys(this.layers_by_source).reduce(function(acc, key) { return acc.concat(this.layers_by_source[key]); }.bind(this), [])); - - /* - * Create the an object that has a key for each possible layer or alias, - * and returns either that layer, or all the layers in the alias - */ this.layer_mapping = TypeMapping.addStandardTargetsToAliases(this.layers, this.layer_aliases); }; +// load values from targets block +TypeMapping.prototype.loadTargets = function( targetsBlock ){ + + // set values from targets block + this.setSourceAliases( targetsBlock.source_aliases || {} ); + this.setLayersBySource( targetsBlock.layers_by_source || {} ); + this.setLayerAliases( targetsBlock.layer_aliases || {} ); + + // generate the dynamic mappings + this.generateDynamicMappings(); +}; + +// load values from either pelias config file or from elasticsearch +TypeMapping.prototype.load = function( done ){ + + // load pelias config + const peliasConfig = require('pelias-config').generate(require('../schema')); + + // load targets from config file + this.loadTargets( peliasConfig.api.targets ); + + // do not load values from elasticsearch + if( true !== peliasConfig.api.targets.auto_discover ){ + if( 'function' === typeof done ){ done(); } + return; + } + + // load values from elasticsearch + + // create connection to elasticsearch + // const esclient = elasticsearch.Client(peliasConfig.esclient); + + // const query = { + // requestCache: true, + // preference: '_replica_first', + // timeout: '10s', + // body: { + // aggs: { + // sources: { + // terms: { + // field: 'source', + // size: 100 + // } + // }, + // layers: { + // terms: { + // field: 'layer', + // size: 100 + // } + // } + // }, + // size: 0 + // } + // }; + + // esclient.search( query, ( err, res ) => { + // console.error( err, res ); + // }); +}; + // instantiate a new type mapping var tm = new TypeMapping(); - -// a list of all sources -tm.setSources([ 'openstreetmap', 'openaddresses', 'geonames', 'whosonfirst' ]); - -/* - * A list of alternate names for sources, mostly used to save typing - */ -tm.setSourceAliases({ - 'osm': [ 'openstreetmap' ], - 'oa': [ 'openaddresses' ], - 'gn': [ 'geonames' ], - 'wof': [ 'whosonfirst' ] -}); - -/* - * A list of all layers in each source. This is used for convenience elswhere - * and to determine when a combination of source and layer parameters is - * not going to match any records and will return no results. - */ -tm.setLayersBySource({ - openstreetmap: [ 'address', 'venue', 'street' ], - openaddresses: [ 'address' ], - geonames: [ 'country','macroregion', 'region', 'county','localadmin', - 'locality','borough', 'neighbourhood', 'venue' ], - whosonfirst: [ 'continent', 'empire', 'country', 'dependency', 'macroregion', - 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', - 'borough', 'neighbourhood', 'microhood', 'disputed', 'venue', 'postalcode', - 'continent', 'ocean', 'marinearea' ] -}); - -/* - * A list of layer aliases that can be used to support specific use cases - * (like coarse geocoding) * or work around the fact that different sources - * may have layers that mean the same thing but have a different name - */ -tm.setLayerAliases({ - 'coarse': [ 'continent', 'empire', 'country', 'dependency', 'macroregion', - 'region', 'locality', 'localadmin', 'macrocounty', 'county', 'macrohood', - 'borough', 'neighbourhood', 'microhood', 'disputed', 'postalcode', - 'continent', 'ocean', 'marinearea' ] -}); - -// generate the dynamic mappings -tm.generateDynamicMappings(); +tm.load(); // export singleton module.exports = tm; \ No newline at end of file diff --git a/index.js b/index.js index f541c0cc..a50dc518 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,14 @@ const app = require('./app'), + typeMapping = require('./helper/type_mapping'), port = ( process.env.PORT || 3100 ), host = ( process.env.HOST || undefined ); -const server = app.listen( port, host, () => { - // ask server for the actual address and port its listening on - const listenAddress = server.address(); - console.log( `pelias is now running on ${listenAddress.address}:${listenAddress.port}` ); -}); +// ensure the dynamic type mappings are loaded before starting the server. +typeMapping.load(() => { + const server = app.listen( port, host, () => { + // ask server for the actual address and port its listening on + const listenAddress = server.address(); + console.log( `pelias is now running on ${listenAddress.address}:${listenAddress.port}` ); + }); +}); \ No newline at end of file diff --git a/package.json b/package.json index a27f6001..913b75bf 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "markdown": "0.5.0", "morgan": "^1.8.2", "pelias-categories": "1.2.0", - "pelias-config": "2.14.0", + "pelias-config": "git://github.com/pelias/config.git#config_source_layers", "pelias-labels": "1.8.0", "pelias-logger": "0.3.1", "pelias-microservice-wrapper": "1.3.0", From 20e7db3091b96f5a55de7b4145d27c59952e664a Mon Sep 17 00:00:00 2001 From: missinglink Date: Fri, 18 May 2018 14:28:38 +0200 Subject: [PATCH 06/23] package: bump config version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 913b75bf..b8e2f279 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "node": ">=6.0.0" }, "dependencies": { + "@mapbox/geojson-extent": "^0.3.1", "addressit": "1.5.0", "async": "^2.0.0", "check-types": "^7.0.0", @@ -43,7 +44,6 @@ "elasticsearch-exceptions": "0.0.4", "express": "^4.8.8", "geojson": "^0.5.0", - "@mapbox/geojson-extent": "^0.3.1", "geolib": "^2.0.18", "iso-639-3": "^1.0.0", "iso3166-1": "^0.3.0", @@ -53,7 +53,7 @@ "markdown": "0.5.0", "morgan": "^1.8.2", "pelias-categories": "1.2.0", - "pelias-config": "git://github.com/pelias/config.git#config_source_layers", + "pelias-config": "2.15.0", "pelias-labels": "1.8.0", "pelias-logger": "0.3.1", "pelias-microservice-wrapper": "1.3.0", From 4e3552f0733a972be7d89e3598ba111de8064d01 Mon Sep 17 00:00:00 2001 From: missinglink Date: Fri, 18 May 2018 14:28:59 +0200 Subject: [PATCH 07/23] index: revert changes --- index.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index a50dc518..f541c0cc 100644 --- a/index.js +++ b/index.js @@ -1,14 +1,10 @@ const app = require('./app'), - typeMapping = require('./helper/type_mapping'), port = ( process.env.PORT || 3100 ), host = ( process.env.HOST || undefined ); -// ensure the dynamic type mappings are loaded before starting the server. -typeMapping.load(() => { - const server = app.listen( port, host, () => { - // ask server for the actual address and port its listening on - const listenAddress = server.address(); - console.log( `pelias is now running on ${listenAddress.address}:${listenAddress.port}` ); - }); -}); \ No newline at end of file +const server = app.listen( port, host, () => { + // ask server for the actual address and port its listening on + const listenAddress = server.address(); + console.log( `pelias is now running on ${listenAddress.address}:${listenAddress.port}` ); +}); From b1cfd091ede416f40946545094df68c73dde91cc Mon Sep 17 00:00:00 2001 From: missinglink Date: Fri, 18 May 2018 14:29:36 +0200 Subject: [PATCH 08/23] type_mapping: add unit tests --- helper/TypeMapping.js | 146 ++++++++++++++++++++++ helper/type_mapping.js | 137 +-------------------- test/unit/helper/TypeMapping.js | 208 ++++++++++++++++++++++++++++++++ test/unit/run.js | 1 + 4 files changed, 356 insertions(+), 136 deletions(-) create mode 100644 helper/TypeMapping.js create mode 100644 test/unit/helper/TypeMapping.js diff --git a/helper/TypeMapping.js b/helper/TypeMapping.js new file mode 100644 index 00000000..3c504eaf --- /dev/null +++ b/helper/TypeMapping.js @@ -0,0 +1,146 @@ +const _ = require('lodash'); +const elasticsearch = require('elasticsearch'); + +var TypeMapping = function(){ + + // A list of all sources + this.sources = []; + + // A list of alternate names for sources, mostly used to save typing + this.source_aliases = {}; + + // A list of all layers + this.layers = []; + + /* + * A list of all layers in each source. This is used for convenience elswhere + * and to determine when a combination of source and layer parameters is + * not going to match any records and will return no results. + */ + this.layers_by_source = {}; + + /* + * A list of layer aliases that can be used to support specific use cases + * (like coarse geocoding) * or work around the fact that different sources + * may have layers that mean the same thing but have a different name + */ + this.layer_aliases = {}; + + /* + * An object that contains all sources or aliases. The key is the source or alias, + * the value is either that source, or the canonical name for that alias if it's an alias. + */ + this.source_mapping = {}; + + /* + * An object that has a key for each possible layer or alias, + * and returns either that layer, or gall the layers in the alias + */ + this.layer_mapping = {}; +}; + +TypeMapping.addStandardTargetsToAliases = function(standard, aliases) { + var combined = _.extend({}, aliases); + standard.forEach(function(target) { + if (combined[target] === undefined) { + combined[target] = [target]; + } + }); + + return combined; +}; + +// source alias setter +TypeMapping.prototype.setSourceAliases = function( aliases ){ + this.source_aliases = aliases; +}; + +// layers-by-source alias setter +TypeMapping.prototype.setLayersBySource = function( lbs ){ + this.layers_by_source = lbs; +}; + +// layer alias setter +TypeMapping.prototype.setLayerAliases = function( aliases ){ + this.layer_aliases = aliases; +}; + +// generate dynamic mappings after setters have been run +TypeMapping.prototype.generateDynamicMappings = function(){ + this.sources = Object.keys( this.layers_by_source ); + this.source_mapping = TypeMapping.addStandardTargetsToAliases(this.sources, this.source_aliases); + this.layers = _.uniq(Object.keys(this.layers_by_source).reduce(function(acc, key) { + return acc.concat(this.layers_by_source[key]); + }.bind(this), [])); + this.layer_mapping = TypeMapping.addStandardTargetsToAliases(this.layers, this.layer_aliases); +}; + +// load values from targets block +TypeMapping.prototype.loadTargets = function( targetsBlock ){ + + if( !_.isObject(targetsBlock) ){ return; } + + // set values from targets block + this.setSourceAliases( targetsBlock.source_aliases || {} ); + this.setLayersBySource( targetsBlock.layers_by_source || {} ); + this.setLayerAliases( targetsBlock.layer_aliases || {} ); + + // generate the dynamic mappings + this.generateDynamicMappings(); +}; + +// load values from either pelias config file or from elasticsearch +TypeMapping.prototype.load = function( done ){ + + // load pelias config + const peliasConfigTargets = _.get( + require('pelias-config').generate(require('../schema')), + 'api.targets', {} + ); + + // load targets from config file + this.loadTargets( peliasConfigTargets ); + + // do not load values from elasticsearch + if( true !== peliasConfigTargets.auto_discover ){ + if( 'function' === typeof done ){ done(); } + return; + } + + if( 'function' === typeof done ){ done(); } + return; + + // load values from elasticsearch + + // create connection to elasticsearch + // const esclient = elasticsearch.Client(peliasConfig.esclient); + + // const query = { + // requestCache: true, + // preference: '_replica_first', + // timeout: '10s', + // body: { + // aggs: { + // sources: { + // terms: { + // field: 'source', + // size: 100 + // } + // }, + // layers: { + // terms: { + // field: 'layer', + // size: 100 + // } + // } + // }, + // size: 0 + // } + // }; + + // esclient.search( query, ( err, res ) => { + // console.error( err, res ); + // }); +}; + +module.exports = TypeMapping; \ No newline at end of file diff --git a/helper/type_mapping.js b/helper/type_mapping.js index 72e86554..987aa26a 100644 --- a/helper/type_mapping.js +++ b/helper/type_mapping.js @@ -1,139 +1,4 @@ -const _ = require('lodash'); -const elasticsearch = require('elasticsearch'); - -var TypeMapping = function(){ - - // A list of all sources - this.sources = []; - - // A list of alternate names for sources, mostly used to save typing - this.source_aliases = {}; - - // A list of all layers - this.layers = []; - - /* - * A list of all layers in each source. This is used for convenience elswhere - * and to determine when a combination of source and layer parameters is - * not going to match any records and will return no results. - */ - this.layers_by_source = {}; - - /* - * A list of layer aliases that can be used to support specific use cases - * (like coarse geocoding) * or work around the fact that different sources - * may have layers that mean the same thing but have a different name - */ - this.layer_aliases = {}; - - /* - * An object that contains all sources or aliases. The key is the source or alias, - * the value is either that source, or the canonical name for that alias if it's an alias. - */ - this.source_mapping = {}; - - /* - * An object that has a key for each possible layer or alias, - * and returns either that layer, or gall the layers in the alias - */ - this.layer_mapping = {}; -}; - -TypeMapping.addStandardTargetsToAliases = function(standard, aliases) { - var combined = _.extend({}, aliases); - standard.forEach(function(target) { - if (combined[target] === undefined) { - combined[target] = [target]; - } - }); - - return combined; -}; - -// source alias setter -TypeMapping.prototype.setSourceAliases = function( aliases ){ - this.source_aliases = aliases; -}; - -// layers-by-source alias setter -TypeMapping.prototype.setLayersBySource = function( lbs ){ - this.layers_by_source = lbs; -}; - -// layer alias setter -TypeMapping.prototype.setLayerAliases = function( aliases ){ - this.layer_aliases = aliases; -}; - -// generate dynamic mappings after setters have been run -TypeMapping.prototype.generateDynamicMappings = function(){ - this.sources = Object.keys( this.layers_by_source ); - this.source_mapping = TypeMapping.addStandardTargetsToAliases(this.sources, this.source_aliases); - this.layers = _.uniq(Object.keys(this.layers_by_source).reduce(function(acc, key) { - return acc.concat(this.layers_by_source[key]); - }.bind(this), [])); - this.layer_mapping = TypeMapping.addStandardTargetsToAliases(this.layers, this.layer_aliases); -}; - -// load values from targets block -TypeMapping.prototype.loadTargets = function( targetsBlock ){ - - // set values from targets block - this.setSourceAliases( targetsBlock.source_aliases || {} ); - this.setLayersBySource( targetsBlock.layers_by_source || {} ); - this.setLayerAliases( targetsBlock.layer_aliases || {} ); - - // generate the dynamic mappings - this.generateDynamicMappings(); -}; - -// load values from either pelias config file or from elasticsearch -TypeMapping.prototype.load = function( done ){ - - // load pelias config - const peliasConfig = require('pelias-config').generate(require('../schema')); - - // load targets from config file - this.loadTargets( peliasConfig.api.targets ); - - // do not load values from elasticsearch - if( true !== peliasConfig.api.targets.auto_discover ){ - if( 'function' === typeof done ){ done(); } - return; - } - - // load values from elasticsearch - - // create connection to elasticsearch - // const esclient = elasticsearch.Client(peliasConfig.esclient); - - // const query = { - // requestCache: true, - // preference: '_replica_first', - // timeout: '10s', - // body: { - // aggs: { - // sources: { - // terms: { - // field: 'source', - // size: 100 - // } - // }, - // layers: { - // terms: { - // field: 'layer', - // size: 100 - // } - // } - // }, - // size: 0 - // } - // }; - - // esclient.search( query, ( err, res ) => { - // console.error( err, res ); - // }); -}; +const TypeMapping = require('./TypeMapping'); // instantiate a new type mapping var tm = new TypeMapping(); diff --git a/test/unit/helper/TypeMapping.js b/test/unit/helper/TypeMapping.js new file mode 100644 index 00000000..3e9b9c41 --- /dev/null +++ b/test/unit/helper/TypeMapping.js @@ -0,0 +1,208 @@ +const _ = require('lodash'); +const TypeMapping = require('../../../helper/TypeMapping'); + +module.exports.tests = {}; + +module.exports.tests.interface = function(test) { + test('valid interface', function(t) { + t.equal(typeof TypeMapping, 'function', 'TypeMapping is a function'); + + t.equal(typeof TypeMapping.addStandardTargetsToAliases, 'function', 'addStandardTargetsToAliases() is a function'); + t.equal(typeof TypeMapping.prototype.setSourceAliases, 'function', 'setSourceAliases() is a function'); + + t.equal(typeof TypeMapping.prototype.setLayersBySource, 'function', 'setLayersBySource() is a function'); + t.equal(typeof TypeMapping.prototype.setLayerAliases, 'function', 'setLayerAliases() is a function'); + + t.equal(typeof TypeMapping.prototype.generateDynamicMappings, 'function', 'generateDynamicMappings() is a function'); + + t.equal(typeof TypeMapping.prototype.loadTargets, 'function', 'loadTargets() is a function'); + t.equal(typeof TypeMapping.prototype.load, 'function', 'load() is a function'); + + t.end(); + }); +}; + +module.exports.tests.constructor = function(test) { + test('constructor', function(t) { + + var doc = new TypeMapping(); + + // initial values + t.deepEqual(doc.sources, [], 'initial value'); + t.deepEqual(doc.source_aliases, {}, 'initial value'); + t.deepEqual(doc.layers, [], 'initial value'); + t.deepEqual(doc.layers_by_source, {}, 'initial value'); + t.deepEqual(doc.layer_aliases, {}, 'initial value'); + t.deepEqual(doc.source_mapping, {}, 'initial value'); + t.deepEqual(doc.layer_mapping, {}, 'initial value'); + + t.end(); + }); +}; + +module.exports.tests.addStandardTargetsToAliases = function(test) { + test('static method addStandardTargetsToAliases', function(t) { + + var aliases = { test: ['test2'] }; + + t.deepEqual( + TypeMapping.addStandardTargetsToAliases([], aliases), + { test: ['test2'] } + ); + t.deepEqual(aliases, aliases, 'aliases object not mutated'); + + t.deepEqual( + TypeMapping.addStandardTargetsToAliases(['test'], aliases), + { test: ['test2'] }, + 'not modified' + ); + t.deepEqual(aliases, aliases, 'aliases object not mutated'); + + t.deepEqual( + TypeMapping.addStandardTargetsToAliases(['baz'], aliases), + { test: ['test2'], baz: ['baz'] } + ); + t.deepEqual(aliases, aliases, 'aliases object not mutated'); + + t.deepEqual( + TypeMapping.addStandardTargetsToAliases(['baz','boo'], aliases), + { test: ['test2'], baz: ['baz'], boo: ['boo'] } + ); + t.deepEqual(aliases, aliases, 'aliases object not mutated'); + + t.end(); + + }); +}; + +module.exports.tests.setSourceAliases = function(test) { + test('setter setSourceAliases', function(t) { + var tm = new TypeMapping(); + t.deepEqual(tm.source_aliases, {}); + tm.setSourceAliases({ foo: ['foo', 'bar'] }); + t.deepEqual(tm.source_aliases, { foo: ['foo', 'bar'] }); + t.end(); + }); +}; + +module.exports.tests.setLayersBySource = function(test) { + test('setter setLayersBySource', function(t) { + var tm = new TypeMapping(); + t.deepEqual(tm.layers_by_source, {}); + tm.setLayersBySource({ foo: ['foo', 'bar'] }); + t.deepEqual(tm.layers_by_source, { foo: ['foo', 'bar'] }); + t.end(); + }); +}; + +module.exports.tests.setLayerAliases = function(test) { + test('setter setLayerAliases', function(t) { + var tm = new TypeMapping(); + t.deepEqual(tm.layer_aliases, {}); + tm.setLayerAliases({ foo: ['foo', 'bar'] }); + t.deepEqual(tm.layer_aliases, { foo: ['foo', 'bar'] }); + t.end(); + }); +}; + +module.exports.tests.generateDynamicMappings = function(test) { + test('generateDynamicMappings - no-op', function(t) { + var tm = new TypeMapping(); + t.deepEqual(tm.sources, []); + t.deepEqual(tm.source_mapping, {}); + t.deepEqual(tm.layers, []); + t.deepEqual(tm.layer_mapping, {}); + tm.generateDynamicMappings(); + t.deepEqual(tm.sources, []); + t.deepEqual(tm.source_mapping, {}); + t.deepEqual(tm.layers, []); + t.deepEqual(tm.layer_mapping, {}); + t.end(); + }); + test('generateDynamicMappings - sources', function(t) { + var tm = new TypeMapping(); + tm.layers_by_source = { foo: ['foo'], faz: ['faz'] }; + tm.generateDynamicMappings(); + t.deepEqual(tm.sources, ['foo', 'faz']); + t.end(); + }); + test('generateDynamicMappings - source_mapping', function(t) { + var tm = new TypeMapping(); + tm.layers_by_source = { foo: ['foo'], faz: ['faz'] }; + tm.source_aliases = { foo: ['foo','f'], bar: ['bar', 'b'], baz: ['baz'] }; + tm.generateDynamicMappings(); + t.deepEqual(tm.source_mapping, { foo: ['foo', 'f'], bar: ['bar', 'b'], baz: ['baz'], faz: ['faz'] }); + t.end(); + }); + test('generateDynamicMappings - layers', function(t) { + var tm = new TypeMapping(); + tm.layers_by_source = { foo: ['foo'], faz: ['faz'] }; + tm.generateDynamicMappings(); + t.deepEqual(tm.layers, ['foo','faz']); + t.end(); + }); + test('generateDynamicMappings - layer_mapping', function(t) { + var tm = new TypeMapping(); + tm.layers_by_source = { foo: ['foo'], faz: ['faz'] }; + tm.layer_aliases = { foo: ['foo','f'], bar: ['bar', 'b'], baz: ['baz'] }; + tm.generateDynamicMappings(); + t.deepEqual(tm.layer_mapping, { foo: ['foo', 'f'], bar: ['bar', 'b'], baz: ['baz'], faz: ['faz'] }); + t.end(); + }); +}; + +module.exports.tests.loadTargets = function(test) { + test('loadTargets - undefined', function(t) { + var tm = new TypeMapping(); + tm.loadTargets(); + t.deepEqual(tm.sources, []); + t.deepEqual(tm.source_mapping, {}); + t.deepEqual(tm.layers, []); + t.deepEqual(tm.layer_mapping, {}); + t.end(); + }); + test('loadTargets', function(t) { + var tm = new TypeMapping(); + tm.loadTargets({ + source_aliases: { source1: ['s1', 's2'], source2: ['s3', 's4'] }, + layers_by_source: { source1: ['layer1', 'layer3'], source2: ['layer2'] }, + layer_aliases: { layer1: ['l1', 'l2'], layer2: ['l3', 'l4'] }, + }); + t.deepEqual(tm.sources, [ 'source1', 'source2' ]); + t.deepEqual(tm.source_mapping, { source1: [ 's1', 's2' ], source2: [ 's3', 's4' ] }); + t.deepEqual(tm.layers, [ 'layer1', 'layer3', 'layer2' ]); + t.deepEqual(tm.layer_mapping, { layer1: [ 'l1', 'l2' ], layer2: [ 'l3', 'l4' ], layer3: [ 'layer3' ] }); + t.end(); + }); +}; + +module.exports.tests.load = function(test) { + test('load from pelias config', function(t) { + var tm = new TypeMapping(); + tm.load(() => { + + // load pelias config + const expected = _.get( + require('pelias-config').generate(require('../../../schema')), + 'api.targets', {} + ); + + // values copied from config + t.deepEqual(tm.layers_by_source, expected.layers_by_source || {}); + t.deepEqual(tm.source_aliases, expected.source_aliases || {}); + t.deepEqual(tm.layer_aliases, expected.layer_aliases || {}); + t.end(); + }); + }); +}; + +module.exports.all = function (tape, common) { + + function test(name, testFunction) { + return tape('TypeMapping: ' + name, testFunction); + } + + for( var testCase in module.exports.tests ){ + module.exports.tests[testCase](test, common); + } +}; diff --git a/test/unit/run.js b/test/unit/run.js index 52b6e519..404cd86b 100644 --- a/test/unit/run.js +++ b/test/unit/run.js @@ -36,6 +36,7 @@ var tests = [ require('./helper/geojsonify_place_details'), require('./helper/geojsonify'), require('./helper/logging'), + require('./helper/TypeMapping'), require('./helper/type_mapping'), require('./helper/stackTraceLine'), require('./middleware/access_log'), From 83ab1636e1c4ab73c1b0f9d76bbb8b83080f6098 Mon Sep 17 00:00:00 2001 From: missinglink Date: Fri, 18 May 2018 14:58:21 +0200 Subject: [PATCH 09/23] readme: documentation for api.targets config --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0991c143..c3287227 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ The API recognizes the following properties under the top-level `api` key in you |`accessLog`|*no*||name of the format to use for access logs; may be any one of the [predefined values](https://github.com/expressjs/morgan#predefined-formats) in the `morgan` package. Defaults to `"common"`; if set to `false`, or an otherwise falsy value, disables access-logging entirely.| |`services`|*no*||service definitions for [point-in-polygon](https://github.com/pelias/pip-service), [libpostal](https://github.com/whosonfirst/go-whosonfirst-libpostal), [placeholder](https://github.com/pelias/placeholder), and [interpolation](https://github.com/pelias/interpolation) services. If missing (which is not recommended), the services will not be called.| |`defaultParameters.focus.point.lon`
`defaultParameters.focus.point.lat`|no | |default coordinates for focus point +|`targets.layers_by_source`
`targets.source_aliases`
`targets.layer_aliases`|no | |custom values for which `sources` and `layers` the API accepts ([more info](https://github.com/pelias/api/pull/1131)). A good starting configuration file includes this section (fill in the service and Elasticsearch hosts as needed): From e15aa52f63c596f0e053e65caef943eeaca8947d Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Mon, 28 May 2018 10:44:00 +0200 Subject: [PATCH 10/23] feat(type_mapping): rename function to avoid confusion with elasticsearch API --- helper/TypeMapping.js | 10 +++++----- test/unit/helper/TypeMapping.js | 26 +++++++++++++------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/helper/TypeMapping.js b/helper/TypeMapping.js index 3c504eaf..97d87a26 100644 --- a/helper/TypeMapping.js +++ b/helper/TypeMapping.js @@ -65,8 +65,8 @@ TypeMapping.prototype.setLayerAliases = function( aliases ){ this.layer_aliases = aliases; }; -// generate dynamic mappings after setters have been run -TypeMapping.prototype.generateDynamicMappings = function(){ +// generate mappings after setters have been run +TypeMapping.prototype.generateMappings = function(){ this.sources = Object.keys( this.layers_by_source ); this.source_mapping = TypeMapping.addStandardTargetsToAliases(this.sources, this.source_aliases); this.layers = _.uniq(Object.keys(this.layers_by_source).reduce(function(acc, key) { @@ -85,8 +85,8 @@ TypeMapping.prototype.loadTargets = function( targetsBlock ){ this.setLayersBySource( targetsBlock.layers_by_source || {} ); this.setLayerAliases( targetsBlock.layer_aliases || {} ); - // generate the dynamic mappings - this.generateDynamicMappings(); + // generate the mappings + this.generateMappings(); }; // load values from either pelias config file or from elasticsearch @@ -143,4 +143,4 @@ TypeMapping.prototype.load = function( done ){ // }); }; -module.exports = TypeMapping; \ No newline at end of file +module.exports = TypeMapping; diff --git a/test/unit/helper/TypeMapping.js b/test/unit/helper/TypeMapping.js index 3e9b9c41..b4e45475 100644 --- a/test/unit/helper/TypeMapping.js +++ b/test/unit/helper/TypeMapping.js @@ -13,8 +13,8 @@ module.exports.tests.interface = function(test) { t.equal(typeof TypeMapping.prototype.setLayersBySource, 'function', 'setLayersBySource() is a function'); t.equal(typeof TypeMapping.prototype.setLayerAliases, 'function', 'setLayerAliases() is a function'); - t.equal(typeof TypeMapping.prototype.generateDynamicMappings, 'function', 'generateDynamicMappings() is a function'); - + t.equal(typeof TypeMapping.prototype.generateMappings, 'function', 'generateMappings() is a function'); + t.equal(typeof TypeMapping.prototype.loadTargets, 'function', 'loadTargets() is a function'); t.equal(typeof TypeMapping.prototype.load, 'function', 'load() is a function'); @@ -105,47 +105,47 @@ module.exports.tests.setLayerAliases = function(test) { }); }; -module.exports.tests.generateDynamicMappings = function(test) { - test('generateDynamicMappings - no-op', function(t) { +module.exports.tests.generateMappings = function(test) { + test('generateMappings - no-op', function(t) { var tm = new TypeMapping(); t.deepEqual(tm.sources, []); t.deepEqual(tm.source_mapping, {}); t.deepEqual(tm.layers, []); t.deepEqual(tm.layer_mapping, {}); - tm.generateDynamicMappings(); + tm.generateMappings(); t.deepEqual(tm.sources, []); t.deepEqual(tm.source_mapping, {}); t.deepEqual(tm.layers, []); t.deepEqual(tm.layer_mapping, {}); t.end(); }); - test('generateDynamicMappings - sources', function(t) { + test('generateMappings - sources', function(t) { var tm = new TypeMapping(); tm.layers_by_source = { foo: ['foo'], faz: ['faz'] }; - tm.generateDynamicMappings(); + tm.generateMappings(); t.deepEqual(tm.sources, ['foo', 'faz']); t.end(); }); - test('generateDynamicMappings - source_mapping', function(t) { + test('generateMappings - source_mapping', function(t) { var tm = new TypeMapping(); tm.layers_by_source = { foo: ['foo'], faz: ['faz'] }; tm.source_aliases = { foo: ['foo','f'], bar: ['bar', 'b'], baz: ['baz'] }; - tm.generateDynamicMappings(); + tm.generateMappings(); t.deepEqual(tm.source_mapping, { foo: ['foo', 'f'], bar: ['bar', 'b'], baz: ['baz'], faz: ['faz'] }); t.end(); }); - test('generateDynamicMappings - layers', function(t) { + test('generateMappings - layers', function(t) { var tm = new TypeMapping(); tm.layers_by_source = { foo: ['foo'], faz: ['faz'] }; - tm.generateDynamicMappings(); + tm.generateMappings(); t.deepEqual(tm.layers, ['foo','faz']); t.end(); }); - test('generateDynamicMappings - layer_mapping', function(t) { + test('generateMappings - layer_mapping', function(t) { var tm = new TypeMapping(); tm.layers_by_source = { foo: ['foo'], faz: ['faz'] }; tm.layer_aliases = { foo: ['foo','f'], bar: ['bar', 'b'], baz: ['baz'] }; - tm.generateDynamicMappings(); + tm.generateMappings(); t.deepEqual(tm.layer_mapping, { foo: ['foo', 'f'], bar: ['bar', 'b'], baz: ['baz'], faz: ['faz'] }); t.end(); }); From 7122317f2a823298b9c65a565ad45cab7ee00f16 Mon Sep 17 00:00:00 2001 From: Julian Simioni Date: Fri, 25 May 2018 14:21:59 -0400 Subject: [PATCH 11/23] feat(attribution): allow overriding attribution URL This url is used to show attribution information for Pelias data sources It was often not detected correctly if, for example, the API is proxied behind a load balancer. --- middleware/geocodeJSON.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/geocodeJSON.js b/middleware/geocodeJSON.js index 0ff2e303..6c78d4a3 100644 --- a/middleware/geocodeJSON.js +++ b/middleware/geocodeJSON.js @@ -47,7 +47,7 @@ function convertToGeocodeJSON(req, res, next, opts) { // OPTIONAL. Default: null. The attribution of the data. In case of multiple sources, // and then multiple attributions, can be an object with one key by source. // Can be a URI on the server, which outlines attribution details. - res.body.geocoding.attribution = url.format({ + res.body.geocoding.attribution = opts.config.attributionURL || url.format({ protocol: req.protocol, host: req.get('host'), pathname: opts.basePath + 'attribution' From bb860b815df49c07f366c2bde0a46bc0baf95ab0 Mon Sep 17 00:00:00 2001 From: Julian Simioni Date: Fri, 25 May 2018 14:43:27 -0400 Subject: [PATCH 12/23] fix(attribution): Use correct path for attribution endpoint --- middleware/geocodeJSON.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/geocodeJSON.js b/middleware/geocodeJSON.js index 6c78d4a3..60c74f68 100644 --- a/middleware/geocodeJSON.js +++ b/middleware/geocodeJSON.js @@ -50,7 +50,7 @@ function convertToGeocodeJSON(req, res, next, opts) { res.body.geocoding.attribution = opts.config.attributionURL || url.format({ protocol: req.protocol, host: req.get('host'), - pathname: opts.basePath + 'attribution' + pathname: 'attribution' }); // OPTIONAL. Default: null. The query that has been issued to trigger the From 1fbb81a1a417a131708b30fa18f8541937d642c6 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 23 May 2018 01:09:52 +0000 Subject: [PATCH 13/23] fix(package): update pelias-config to version 3.0.1 Closes #1136 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b8e2f279..ff049e23 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "markdown": "0.5.0", "morgan": "^1.8.2", "pelias-categories": "1.2.0", - "pelias-config": "2.15.0", + "pelias-config": "3.0.1", "pelias-labels": "1.8.0", "pelias-logger": "0.3.1", "pelias-microservice-wrapper": "1.3.0", From 152897714e78dbbf95bb8aca6b330806e67deaed Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Sat, 2 Jun 2018 03:14:20 +0000 Subject: [PATCH 14/23] fix(package): update pelias-logger to version 0.4.1 Closes #1144 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ff049e23..5657307b 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "pelias-categories": "1.2.0", "pelias-config": "3.0.1", "pelias-labels": "1.8.0", - "pelias-logger": "0.3.1", + "pelias-logger": "0.4.1", "pelias-microservice-wrapper": "1.3.0", "pelias-model": "5.3.2", "pelias-query": "9.1.1", From 5f565583f0987a0866b84867c41bb94036a404fe Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Wed, 23 May 2018 22:28:39 +0000 Subject: [PATCH 15/23] fix(package): update pelias-microservice-wrapper to version 1.4.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5657307b..e6680795 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "pelias-config": "3.0.1", "pelias-labels": "1.8.0", "pelias-logger": "0.4.1", - "pelias-microservice-wrapper": "1.3.0", + "pelias-microservice-wrapper": "1.4.0", "pelias-model": "5.3.2", "pelias-query": "9.1.1", "pelias-sorting": "1.1.0", From 84cac12fa93f862c34088a25a4def4a2fd65334c Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Thu, 24 May 2018 14:03:43 +0000 Subject: [PATCH 16/23] chore(package): update pelias-mock-logger to version 1.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e6680795..3c5759da 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,7 @@ "istanbul": "^0.4.2", "jshint": "^2.5.6", "nsp": "^3.0.0", - "pelias-mock-logger": "1.2.0", + "pelias-mock-logger": "1.3.0", "precommit-hook": "^3.0.0", "proxyquire": "^2.0.0", "semantic-release": "^15.1.4", From 10241047a67bfff1e487d0fa4737c4b20d130b2d Mon Sep 17 00:00:00 2001 From: Julian Simioni Date: Fri, 1 Jun 2018 16:43:12 -0400 Subject: [PATCH 17/23] fix(interpolation): Ensure proper sorting of streets with interpolated addresses In cases where several conditions are met, it is possible for results to be returned from the API that are not sorted as they were intended. These conditions are: * over 10 results total were returned from Elasticsearch * the interpolation middleware was called * not all street results end up with possible interpolated address matches, and some of those streets come before other interpolated address records, necessitating a re-sorting of the results in the interpolation middleware In these cases, the ordering of streets as defined by Elasticsearch, such as by linguistic match or distance from a focus point, will no longer be respected in the results. This is because Node.js's `Array.prototype.sort` uses an [*un*stable QuickSort for arrays of size 11 or greater](https://github.com/nodejs/node/blob/master/deps/v8/src/js/array.js#L670). The solution is to switch to a sorting algorithm that is always stable. This ensures that whatever ordering was specified in the Elasticsearch queries is observed, without any of that logic having to be duplicated, and then possibly conflict. Stable sorting is provided by the [stable](http://npmjs.com/stable) NPM package. --- middleware/interpolate.js | 3 +- package.json | 1 + test/unit/middleware/interpolate.js | 75 +++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/middleware/interpolate.js b/middleware/interpolate.js index 10c3f7e8..8507709a 100644 --- a/middleware/interpolate.js +++ b/middleware/interpolate.js @@ -2,6 +2,7 @@ const async = require('async'); const logger = require( 'pelias-logger' ).get( 'api' ); const source_mapping = require('../helper/type_mapping').source_mapping; const _ = require('lodash'); +const stable = require('stable'); /** example response from interpolation web service: @@ -116,7 +117,7 @@ function setup(service, should_execute) { // sort the results to ensure that addresses show up higher than street centroids if (_.has(res, 'data')) { - res.data.sort((a, b) => { + res.data = stable(res.data, (a, b) => { if (a.layer === 'address' && b.layer !== 'address') { return -1; } if (a.layer !== 'address' && b.layer === 'address') { return 1; } return 0; diff --git a/package.json b/package.json index 3c5759da..196c8a78 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "pelias-sorting": "1.1.0", "predicates": "^2.0.0", "retry": "^0.10.1", + "stable": "^0.1.8", "stats-lite": "^2.0.4", "through2": "^2.0.3" }, diff --git a/test/unit/middleware/interpolate.js b/test/unit/middleware/interpolate.js index 3602592c..537c7444 100644 --- a/test/unit/middleware/interpolate.js +++ b/test/unit/middleware/interpolate.js @@ -277,6 +277,81 @@ module.exports.tests.success_conditions = (test, common) => { }); + test('results should be sorted first by address/non-address. previous ordering should otherwise be maintained via a stable sort', t => { + const service = (req, res, callback) => { + // results 5 and 7 will have interpolated results returned + // this is to ensure results are re-sorted to move the addresses first + if (res.id === 5 || res.id === 7) { + callback(null, { + properties: { + number: 17, + source: 'Source Abbr 1', + source_id: 'source 1 source id', + lat: 12.121212, + lon: 21.212121 + } + }); + } else { + // return empty results in most cases + callback(null, {}); + } + }; + + const logger = require('pelias-mock-logger')(); + + const controller = proxyquire('../../../middleware/interpolate', { + 'pelias-logger': logger + })(service, () => true); + + const req = { + clean: { + parsed_text: 'this is req.clean.parsed_text' + } + }; + const res = {}; + + // helper method to generate test results which default to streets + function generateTestStreets(id) { + return { + id: id+1, + layer: 'street', + name: { default: `name ${id+1}` }, + address_parts: {}, + source_id: 'original source_id' + }; + } + + // generate a set of street results of desired size + // NOTE: this set must be of 11 elements or greater + // Node.js uses stable insertion sort for arrays of 10 or fewer elements, + // but _unstable_ QuickSort for larger arrays + const resultCount = 11; + const sequence_array = Array.from(new Array(resultCount),(val,index)=>index); + res.data = sequence_array.map(generateTestStreets); + + controller(req, res, () => { + t.notOk(logger.hasErrorMessages(), 'there shouldn\'t be any error messages'); + + const results = res.data; + + t.equals(results.length, results.length, 'correct number of results should be returned'); + t.equals(results[0].layer, 'address', 'first result should be interpolated address'); + t.equals(results[1].layer, 'address', 'second result should be interpolated address'); + + // iterate through all remaining records, ensuring their ids are increasing, + // as was the case when the set of streets was originally generated + let previous_id; + for (let i = 2; i < results.length; i++) { + if (previous_id) { + t.ok(results[i].id > previous_id, `id ${results[i].id} should be higher than ${previous_id}, to ensure sort is stable`); + } + previous_id = results[i].id; + } + + t.end(); + }); + }); + test('service call returning error should not map in interpolated results for non-errors', t => { const service = (req, res, callback) => { if (res.id === 1) { From be29fd9d27a14acfa233d95f3cedad0254e4aac0 Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 5 Jun 2018 13:20:48 +0000 Subject: [PATCH 18/23] fix(package): update pelias-config to version 3.0.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 196c8a78..71f20ccb 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "markdown": "0.5.0", "morgan": "^1.8.2", "pelias-categories": "1.2.0", - "pelias-config": "3.0.1", + "pelias-config": "3.0.2", "pelias-labels": "1.8.0", "pelias-logger": "0.4.1", "pelias-microservice-wrapper": "1.4.0", From bc61c1fe50fa928bdbc5d4ec46429b633eb739fd Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 22 May 2018 22:40:01 +0000 Subject: [PATCH 19/23] fix(package): update pelias-sorting to version 1.2.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 71f20ccb..c9f3856e 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "pelias-microservice-wrapper": "1.4.0", "pelias-model": "5.3.2", "pelias-query": "9.1.1", - "pelias-sorting": "1.1.0", + "pelias-sorting": "1.2.0", "predicates": "^2.0.0", "retry": "^0.10.1", "stable": "^0.1.8", From 03f5b65e33fded07b33d9b751db85175845f982f Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 5 Jun 2018 16:01:38 +0000 Subject: [PATCH 20/23] fix(package): update pelias-model to version 5.5.2 Closes #1116 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c9f3856e..90288601 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "pelias-labels": "1.8.0", "pelias-logger": "0.4.1", "pelias-microservice-wrapper": "1.4.0", - "pelias-model": "5.3.2", + "pelias-model": "5.5.2", "pelias-query": "9.1.1", "pelias-sorting": "1.2.0", "predicates": "^2.0.0", From f4cf1411abeb7c934d0520ee65cfcc09e75b4c0b Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Tue, 5 Jun 2018 14:04:59 +0000 Subject: [PATCH 21/23] fix(package): update pelias-logger to version 0.4.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 90288601..83cfca41 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "pelias-categories": "1.2.0", "pelias-config": "3.0.2", "pelias-labels": "1.8.0", - "pelias-logger": "0.4.1", + "pelias-logger": "0.4.2", "pelias-microservice-wrapper": "1.4.0", "pelias-model": "5.5.2", "pelias-query": "9.1.1", From 13d0a5b1793540a8d8f001c959bbc2de7faae29d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 14 May 2018 19:30:05 +0000 Subject: [PATCH 22/23] fix(package): update elasticsearch to version 15.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 83cfca41..e05fe830 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "addressit": "1.5.0", "async": "^2.0.0", "check-types": "^7.0.0", - "elasticsearch": "^14.2.1", + "elasticsearch": "^15.0.0", "elasticsearch-exceptions": "0.0.4", "express": "^4.8.8", "geojson": "^0.5.0", From 370dda42cc8ec22c2abd761bf235d5c48d21aa7d Mon Sep 17 00:00:00 2001 From: "greenkeeper[bot]" Date: Mon, 9 Apr 2018 09:35:47 +0000 Subject: [PATCH 23/23] fix(package): update retry to version 0.12.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e05fe830..2f361801 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "pelias-query": "9.1.1", "pelias-sorting": "1.2.0", "predicates": "^2.0.0", - "retry": "^0.10.1", + "retry": "^0.12.0", "stable": "^0.1.8", "stats-lite": "^2.0.4", "through2": "^2.0.3"