diff --git a/lib/cartodb/models/aggregation/aggregation-map-config.js b/lib/cartodb/models/aggregation/aggregation-map-config.js new file mode 100644 index 00000000..dfc52f60 --- /dev/null +++ b/lib/cartodb/models/aggregation/aggregation-map-config.js @@ -0,0 +1,89 @@ +const MapConfig = require('windshaft').model.MapConfig; +const MISSING_AGGREGATION_COLUMNS = 'Missing columns in the aggregation. The map-config defines cartocss expressions,'+ +' interactivity fields or attributes that are not present in the aggregation'; + + +module.exports = class AggregationMapConfig extends MapConfig { + constructor (config, datasource) { + super(config, datasource); + + if (this._hasAggregationMissingColumns()) { + throw new Error(MISSING_AGGREGATION_COLUMNS); + } + } + + isAggregationMapConfig () { + return this.isVectorOnlyMapConfig() || this.hasAnyLayerAggregation(); + } + + isAggregationLayer (index) { + return this.isVectorOnlyMapConfig() || this.hasLayerAggregation(index); + } + + hasAnyLayerAggregation () { + const layers = this.getLayers(); + + for (let index = 0; index < layers.length; index++) { + if (this.hasLayerAggregation(index)) { + return true; + } + } + + return false; + } + + hasLayerAggregation (index) { + const layer = this.getLayer(index); + const { aggregation } = layer.options; + + return aggregation !== undefined && (typeof aggregation === 'object' || typeof aggregation === 'boolean'); + } + + validateAggregation () { + if (this._hasAggregationMissingColumns()) { + throw new Error(MISSING_AGGREGATION_COLUMNS); + } + } + + _hasAggregationMissingColumns () { + const layers = this.getLayers(); + + if (!this.isAggregationMapConfig()) { + return false; + } + + for (let index = 0; index < layers.length; index++) { + const aggregationColumns = this._getAggregationColumnsByLayer(index); + const layerColumns = this.getColumnsByLayer(index); + + if (layerColumns.length === 0) { + continue; + } + + if (aggregationColumns.length !== layerColumns.length) { + return true; + } + + const missingColumns = this._getMissingColumns(aggregationColumns, layerColumns); + + if (missingColumns.length > 0) { + return true; + } + } + + return false; + } + + _getMissingColumns (aggregationColumns, layerColumns) { + return aggregationColumns.filter(column => !layerColumns.includes(column)); + } + + _getAggregationColumnsByLayer (index) { + const { aggregation } = this.getLayer(index).options; + const hasAggregationColumns = aggregation !== undefined && + typeof aggregation !== 'boolean' && + typeof aggregation.columns === 'object'; + + return hasAggregationColumns ? Object.keys(aggregation.columns) : []; + } +}; diff --git a/lib/cartodb/models/mapconfig/adapter/aggregation-mapconfig-adapter.js b/lib/cartodb/models/mapconfig/adapter/aggregation-mapconfig-adapter.js index fdd70bcf..2f738842 100644 --- a/lib/cartodb/models/mapconfig/adapter/aggregation-mapconfig-adapter.js +++ b/lib/cartodb/models/mapconfig/adapter/aggregation-mapconfig-adapter.js @@ -1,5 +1,5 @@ const AggregationProxy = require('../../aggregation/aggregation-proxy'); -const { MapConfig } = require('windshaft').model; +const AggregationMapConfig = require('../../aggregation/aggregation-map-config'); const queryUtils = require('../../../utils/query-utils'); const unsupportedGeometryTypeErrorMessage = ctx => @@ -18,10 +18,10 @@ module.exports = class AggregationMapConfigAdapter { return callback(new Error(invalidAggregationParamValueErrorMessage({ value: params.aggregation }))); } - const mapConfig = new MapConfig(requestMapConfig); + let mapConfig; try { - mapConfig.validateAggregation(); + mapConfig = new AggregationMapConfig(requestMapConfig); } catch (error) { error.http_status = 400; error.type = 'mapconfig'; diff --git a/test/acceptance/ported/multilayer_error_cases.js b/test/acceptance/ported/multilayer_error_cases.js index 6e7f5964..936446f2 100644 --- a/test/acceptance/ported/multilayer_error_cases.js +++ b/test/acceptance/ported/multilayer_error_cases.js @@ -57,7 +57,7 @@ describe('multilayer error cases', function() { res.body, '/**/ typeof test === \'function\' && ' + 'test({"errors":["Missing layers array from layergroup config"],' + - '"errors_with_context":[{"type":"unknown",' + + '"errors_with_context":[{"type":"mapconfig",' + '"message":"Missing layers array from layergroup config"}]});' ); done();