Initial commit
This commit is contained in:
Executable
+210
@@ -0,0 +1,210 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var ScrollView = require('builder/components/scroll/scroll-view');
|
||||
var ViewFactory = require('builder/components/view-factory');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
var IconView = require('builder/components/icon/icon-view');
|
||||
var analysisPlaceholderTemplate = require('builder/editor/layers/layer-content-views/analyses/analyses-placeholder.tpl');
|
||||
var analysisSQLErrorTemplate = require('builder/editor/layers/layer-content-views/analyses/analyses-sql-error.tpl');
|
||||
var AnalysisFormView = require('builder/editor/layers/layer-content-views/analyses/analysis-form-view');
|
||||
var AnalysesWorkflowView = require('builder/editor/layers/layer-content-views/analyses/analyses-workflow-view');
|
||||
var AnalysisControlsView = require('builder/editor/layers/layer-content-views/analyses/analysis-controls-view');
|
||||
var actionErrorTemplate = require('builder/editor/layers/sql-error-action.tpl');
|
||||
var AnalysesQuotaInfo = require('./analyses-quota/analyses-quota-info');
|
||||
var AnalysesService = require('builder/editor/layers/layer-content-views/analyses/analyses-service');
|
||||
var Router = require('builder/routes/router');
|
||||
var utils = require('builder/helpers/utils');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'userActions',
|
||||
'analysisFormsCollection',
|
||||
'layerDefinitionModel',
|
||||
'analysisDefinitionNodesCollection',
|
||||
'configModel',
|
||||
'userModel',
|
||||
'stackLayoutModel',
|
||||
'overlayModel',
|
||||
'layerContentModel',
|
||||
'deleteAnalysisModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* AnalysesContentView:
|
||||
* ├── ScrollView
|
||||
* | └── ListView:
|
||||
* | ├── WorkflowView
|
||||
* | └── FormView
|
||||
* └── ControlsView
|
||||
*/
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
module: 'editor:layers:layer-content-views:analyses:analyses-content-view',
|
||||
|
||||
options: {
|
||||
selectedNodeId: false // or an id, e.g. 'a1'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._selectedNodeId = this.options.selectedNodeId;
|
||||
this._querySchemaModel = this._layerDefinitionModel.getAnalysisDefinitionNodeModel().querySchemaModel;
|
||||
this._quotaInfo = AnalysesQuotaInfo.get(opts.configModel);
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
|
||||
if (this._isErrored()) {
|
||||
this._renderError();
|
||||
} else {
|
||||
this._initViews();
|
||||
}
|
||||
|
||||
this._toggleOverlay();
|
||||
return this;
|
||||
},
|
||||
|
||||
_isErrored: function () {
|
||||
return this._layerContentModel.isErrored();
|
||||
},
|
||||
|
||||
_renderError: function () {
|
||||
this.$el.html(
|
||||
analysisSQLErrorTemplate({
|
||||
body: _t('editor.error-query.body', {
|
||||
action: actionErrorTemplate({
|
||||
label: _t('editor.error-query.label')
|
||||
})
|
||||
})
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
if (this._analysisFormsCollection.isEmpty()) {
|
||||
this.$el.html(analysisPlaceholderTemplate());
|
||||
this._renderTooltip();
|
||||
} else {
|
||||
var formModel = this._getFormModel();
|
||||
|
||||
this._renderScrollableListView(formModel);
|
||||
this._renderControlsView(formModel);
|
||||
}
|
||||
|
||||
var plusIcon = new IconView({
|
||||
placeholder: this.$el.find('.js-plus-icon'),
|
||||
icon: 'plus'
|
||||
});
|
||||
plusIcon.render();
|
||||
this.addView(plusIcon);
|
||||
},
|
||||
|
||||
_toggleOverlay: function () {
|
||||
var isDisabled = this._overlayModel.get('visible');
|
||||
this.$el.toggleClass('is-disabled', isDisabled);
|
||||
},
|
||||
|
||||
_renderTooltip: function () {
|
||||
var tooltip = new TipsyTooltipView({
|
||||
el: this.$('.js-add-analysis'),
|
||||
gravity: 'w',
|
||||
title: function () {
|
||||
return utils.replaceLastSpaceWithNbsp(_t('editor.layers.analysis-form.add-analysis.tooltip'));
|
||||
},
|
||||
offset: 8
|
||||
});
|
||||
this.addView(tooltip);
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._layerDefinitionModel, 'change:source', this._onLayerDefSourceChanged);
|
||||
this.listenTo(this._analysisFormsCollection, 'change:persisted', this._onAnalysisFormsCollectionSync);
|
||||
this.listenTo(this._analysisFormsCollection, 'destroyedModels remove', this._setDefaultSelectedNodeId);
|
||||
this.listenTo(this._overlayModel, 'change:visible', this._toggleOverlay);
|
||||
},
|
||||
|
||||
_renderScrollableListView: function (formModel) {
|
||||
var self = this;
|
||||
|
||||
var view = new ScrollView({
|
||||
createContentView: function () {
|
||||
return ViewFactory.createListView([
|
||||
function () {
|
||||
return new AnalysesWorkflowView({
|
||||
analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
|
||||
layerDefinitionModel: self._layerDefinitionModel,
|
||||
analysisFormsCollection: self._analysisFormsCollection,
|
||||
selectedNodeId: self._selectedNodeId
|
||||
});
|
||||
},
|
||||
function () {
|
||||
return new AnalysisFormView({
|
||||
formModel: formModel,
|
||||
configModel: self._configModel,
|
||||
userModel: self._userModel
|
||||
});
|
||||
}
|
||||
]);
|
||||
}
|
||||
});
|
||||
this.$el.append(view.render().el);
|
||||
this.addView(view);
|
||||
},
|
||||
|
||||
_renderControlsView: function (formModel) {
|
||||
var analysisNode = this._analysisDefinitionNodesCollection.get(formModel.id);
|
||||
|
||||
var view = new AnalysisControlsView({
|
||||
layerDefinitionModel: this._layerDefinitionModel,
|
||||
analysisFormsCollection: this._analysisFormsCollection,
|
||||
analysisNode: analysisNode,
|
||||
configModel: this._configModel,
|
||||
formModel: formModel,
|
||||
quotaInfo: this._quotaInfo,
|
||||
querySchemaModel: this._getQuerySchemaModelForEstimation(),
|
||||
stackLayoutModel: this._stackLayoutModel,
|
||||
userActions: this._userActions,
|
||||
userModel: this._userModel,
|
||||
deleteAnalysisModel: this._deleteAnalysisModel
|
||||
});
|
||||
this.addView(view);
|
||||
this.$el.append(view.render().el);
|
||||
},
|
||||
|
||||
_getFormModel: function () {
|
||||
return this._analysisFormsCollection.get(this._selectedNodeId) || this._analysisFormsCollection.first();
|
||||
},
|
||||
|
||||
_getQuerySchemaModelForEstimation: function () {
|
||||
var node = this._analysisDefinitionNodesCollection.get(this._selectedNodeId);
|
||||
var index;
|
||||
|
||||
if (!node) {
|
||||
node = this._layerDefinitionModel.getAnalysisDefinitionNodeModel();
|
||||
} else {
|
||||
index = this._analysisDefinitionNodesCollection.indexOf(node);
|
||||
node = this._analysisDefinitionNodesCollection.at(index - 1);
|
||||
}
|
||||
|
||||
return node.querySchemaModel;
|
||||
},
|
||||
|
||||
_onLayerDefSourceChanged: function () {
|
||||
this._analysisFormsCollection.removeUselessModels();
|
||||
},
|
||||
|
||||
_onAnalysisFormsCollectionSync: function (model) {
|
||||
AnalysesService.clearNotAppliedAnalysis(model.get('id'));
|
||||
},
|
||||
|
||||
_setDefaultSelectedNodeId: function () {
|
||||
var selectedNodeId = this._analysisFormsCollection.pluck('id')[0];
|
||||
|
||||
AnalysesService.clearNotAppliedAnalysis(selectedNodeId);
|
||||
Router.goToAnalysisNode(this._layerDefinitionModel.get('id'), selectedNodeId);
|
||||
}
|
||||
});
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
<button class="CDB-Button CDB-Button--dashed CDB-Button--wide CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase u-bSpace--xl u-flex u-justifyCenter u-alignCenter js-add-analysis">
|
||||
<span class="js-plus-icon u-flex u-justifyCenter u-alignCenter u-rSpace--m"></span>
|
||||
<%- _t('editor.layers.analysis-form.add-analysis.label') %>
|
||||
</button>
|
||||
|
||||
<h2 class="CDB-Text CDB-Size-huge is-light u-secondaryTextColor"><%- _t('editor.layers.analysis-form.placeholder-text') %></h2>
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var cdb = require('internal-carto.js');
|
||||
var errorParse = require('builder/helpers/error-parser');
|
||||
|
||||
var ENOUGH_QUOTA_QUERY = _.template("select cdb_dataservices_client.cdb_enough_quota('<%= analysis %>', <%= rows %>);");
|
||||
|
||||
module.exports = {
|
||||
init: function (configModel) {
|
||||
this.SQL = new cdb.SQL({
|
||||
user: configModel.get('user_name'),
|
||||
sql_api_template: configModel.get('sql_api_template'),
|
||||
api_key: configModel.get('api_key')
|
||||
});
|
||||
},
|
||||
|
||||
fetch: function (service, rows) {
|
||||
var query = ENOUGH_QUOTA_QUERY({
|
||||
analysis: service,
|
||||
rows: rows
|
||||
});
|
||||
|
||||
if (!this.deferred || this.deferred.state() !== 'pending') {
|
||||
var deferred = $.Deferred();
|
||||
this.deferred = deferred;
|
||||
|
||||
var errorCallback = function (err) {
|
||||
deferred.reject(errorParse(err));
|
||||
};
|
||||
|
||||
var successCallback = function (data) {
|
||||
// response is something like:
|
||||
// {"rows":[{"cdb_enough_quota":false}],"time":0.024,"fields":{"cdb_enough_quota":{"type":"boolean"}},"total_rows":1}
|
||||
deferred.resolve(data.rows[0].cdb_enough_quota);
|
||||
};
|
||||
|
||||
this.SQL.execute(query, null, {
|
||||
success: successCallback,
|
||||
error: errorCallback
|
||||
});
|
||||
}
|
||||
|
||||
return this.deferred.promise();
|
||||
}
|
||||
};
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
var _ = require('underscore');
|
||||
var $ = require('jquery');
|
||||
var cdb = require('internal-carto.js');
|
||||
var errorParse = require('builder/helpers/error-parser');
|
||||
|
||||
var ESTIMATION_QUOTA_QUERY = _.template('SELECT CDB_EstimateRowCount($$<%= query %>;$$) AS row_count;');
|
||||
|
||||
module.exports = {
|
||||
init: function (configModel) {
|
||||
this.SQL = new cdb.SQL({
|
||||
user: configModel.get('user_name'),
|
||||
sql_api_template: configModel.get('sql_api_template'),
|
||||
api_key: configModel.get('api_key')
|
||||
});
|
||||
},
|
||||
|
||||
fetch: function (query) {
|
||||
var estimationQuery = ESTIMATION_QUOTA_QUERY({
|
||||
query: query
|
||||
});
|
||||
|
||||
if (!this.deferred || this.deferred.state() !== 'pending') {
|
||||
var deferred = $.Deferred();
|
||||
this.deferred = deferred;
|
||||
|
||||
var parseResponse = function (response) {
|
||||
return response.rows[0].row_count;
|
||||
};
|
||||
|
||||
var errorCallback = function (err) {
|
||||
deferred.reject(errorParse(err));
|
||||
};
|
||||
|
||||
var successCallback = function (data) {
|
||||
deferred.resolve(parseResponse(data));
|
||||
};
|
||||
|
||||
this.SQL.execute(estimationQuery, null, {
|
||||
success: successCallback,
|
||||
error: errorCallback
|
||||
});
|
||||
}
|
||||
|
||||
return this.deferred.promise();
|
||||
}
|
||||
};
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var cdb = require('internal-carto.js');
|
||||
var errorParse = require('builder/helpers/error-parser');
|
||||
|
||||
var SERVICE_QUOTA_QUERY = 'select * from cdb_dataservices_client.cdb_service_quota_info();';
|
||||
|
||||
var QuotaInfo = Backbone.Collection.extend({
|
||||
initialize: function (models, options) {
|
||||
if (!options || !options.configModel) throw new Error('configModel is required');
|
||||
|
||||
var configModel = options.configModel;
|
||||
this.SQL = new cdb.SQL({
|
||||
user: configModel.get('user_name'),
|
||||
sql_api_template: configModel.get('sql_api_template'),
|
||||
api_key: configModel.get('api_key')
|
||||
});
|
||||
|
||||
this._state = 'unfetched';
|
||||
this._ready = 'notready';
|
||||
},
|
||||
|
||||
fetch: function (options) {
|
||||
this._success = options && options.success;
|
||||
this._error = options && options.error;
|
||||
|
||||
if (!this.isFetching()) {
|
||||
this._state = 'fetching';
|
||||
this.SQL.execute(SERVICE_QUOTA_QUERY, null, {
|
||||
success: function (data) {
|
||||
this._state = 'fetched';
|
||||
this._ready = 'ready';
|
||||
this._onFetchSuccess(data);
|
||||
this._success && this._success();
|
||||
}.bind(this),
|
||||
error: function (err) {
|
||||
this._state = 'error';
|
||||
this._error && this._error(errorParse(err));
|
||||
}.bind(this)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onFetchSuccess: function (data) {
|
||||
var models = data.rows;
|
||||
_.each(models, function (model) {
|
||||
var m = this.getService(model.service);
|
||||
m ? m.set(model) : this.add(model);
|
||||
}, this);
|
||||
},
|
||||
|
||||
getService: function (service) {
|
||||
return this.findWhere({service: service});
|
||||
},
|
||||
|
||||
isFetching: function () {
|
||||
return this.getState() === 'fetching';
|
||||
},
|
||||
|
||||
isReady: function () {
|
||||
return this._ready === 'ready';
|
||||
},
|
||||
|
||||
needsCheck: function () {
|
||||
var state = this.getState();
|
||||
return state === 'unfetched' || state === 'fetching';
|
||||
},
|
||||
|
||||
getState: function () {
|
||||
return this._state;
|
||||
}
|
||||
});
|
||||
|
||||
var collection;
|
||||
|
||||
module.exports = {
|
||||
get: function (configModel) {
|
||||
if (collection === undefined) {
|
||||
collection = new QuotaInfo([], {
|
||||
configModel: configModel
|
||||
});
|
||||
}
|
||||
return collection;
|
||||
}
|
||||
};
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
/**
|
||||
* Control which analysis requires quota
|
||||
*/
|
||||
|
||||
var unity = function (input) {
|
||||
return input;
|
||||
};
|
||||
|
||||
var routingAnalysisQuota = {
|
||||
name: _t('editor.layers.analysis-form.quota.analysis-type.routing'),
|
||||
api: 'routing',
|
||||
user: 'mapzen_routing',
|
||||
needsQuota: true,
|
||||
transform: unity
|
||||
};
|
||||
|
||||
var dataObservatoryQuota = {
|
||||
name: _t('editor.layers.analysis-form.quota.analysis-type.data-observatory-measure'),
|
||||
api: 'observatory',
|
||||
user: 'obs_general',
|
||||
needsQuota: true,
|
||||
transform: unity
|
||||
};
|
||||
|
||||
var ANALYSES_WITH_QUOTA_MAP = {
|
||||
'trade-area': {
|
||||
name: _t('editor.layers.analysis-form.quota.analysis-type.trade-area'),
|
||||
api: 'isolines',
|
||||
user: 'here_isolines',
|
||||
transform: function (input, formModel) {
|
||||
// the isoline analysis consume N rows x M tracts
|
||||
return input * formModel.get('isolines');
|
||||
},
|
||||
needsQuota: function (quotaInfo) {
|
||||
var info = quotaInfo.getService('isolines');
|
||||
var provider = info && info.get('provider');
|
||||
return !!provider;
|
||||
}
|
||||
},
|
||||
'georeference-street-address': {
|
||||
name: _t('editor.layers.analysis-form.quota.analysis-type.georeference-street-address'),
|
||||
api: 'hires_geocoder',
|
||||
user: 'geocoding',
|
||||
transform: unity,
|
||||
needsQuota: function (quotaInfo) {
|
||||
var info = quotaInfo.getService('hires_geocoder');
|
||||
var provider = info && info.get('provider');
|
||||
return provider !== 'google';
|
||||
}
|
||||
},
|
||||
'georeference-city': {
|
||||
name: _t('editor.layers.analysis-form.quota.analysis-type.georeference-cities'),
|
||||
api: 'hires_geocoder',
|
||||
user: 'geocoding',
|
||||
transform: unity,
|
||||
needsQuota: function (quotaInfo) {
|
||||
var info = quotaInfo.getService('hires_geocoder');
|
||||
var provider = info && info.get('provider');
|
||||
return provider !== 'google';
|
||||
}
|
||||
},
|
||||
'data-observatory-measure': dataObservatoryQuota,
|
||||
'routing-sequential': routingAnalysisQuota,
|
||||
'routing-to-layer-all-to-all': routingAnalysisQuota,
|
||||
'routing-to-single-point': routingAnalysisQuota
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
requiresQuota: function (type, quotaInfo) {
|
||||
if (ANALYSES_WITH_QUOTA_MAP[type] !== undefined) {
|
||||
var needsQuota = ANALYSES_WITH_QUOTA_MAP[type].needsQuota;
|
||||
if (_.isFunction(needsQuota)) {
|
||||
return needsQuota(quotaInfo);
|
||||
} else {
|
||||
return needsQuota;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
transformInput: function (type, input, formModel) {
|
||||
if (ANALYSES_WITH_QUOTA_MAP[type] !== undefined) {
|
||||
var transform = ANALYSES_WITH_QUOTA_MAP[type].transform;
|
||||
return transform(input, formModel);
|
||||
}
|
||||
return input;
|
||||
},
|
||||
|
||||
getAnalysisName: function (type) {
|
||||
if (ANALYSES_WITH_QUOTA_MAP[type]) {
|
||||
return ANALYSES_WITH_QUOTA_MAP[type].name;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getServiceName: function (type) {
|
||||
if (ANALYSES_WITH_QUOTA_MAP[type]) {
|
||||
return ANALYSES_WITH_QUOTA_MAP[type].api;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
getUserDataName: function (type) {
|
||||
if (ANALYSES_WITH_QUOTA_MAP[type]) {
|
||||
return ANALYSES_WITH_QUOTA_MAP[type].user;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
};
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
var _ = require('underscore');
|
||||
var Utils = require('builder/helpers/utils');
|
||||
var AnalysesQuotaOptions = require('./analyses-quota-options');
|
||||
var CONTACT_LINK_TEMPLATE = _.template("<a href='mailto:<%- mail %>'><%- contact %></a>");
|
||||
|
||||
function getOwnerEmail (userModel) {
|
||||
return userModel._organizationModel && userModel._organizationModel.get('admin_email');
|
||||
}
|
||||
|
||||
function getEmail (configModel, userModel) {
|
||||
var isSaas = configModel.get('cartodb_com_hosted') === false;
|
||||
var insideOrganization = userModel.isInsideOrg();
|
||||
var ownerOrganization = userModel.isOrgOwner();
|
||||
var email = _t('editor.layers.analysis-form.quota.emails.support');
|
||||
|
||||
if (isSaas) {
|
||||
if (insideOrganization) {
|
||||
email = ownerOrganization ? _t('editor.layers.analysis-form.quota.emails.support') : getOwnerEmail(userModel);
|
||||
} else {
|
||||
email = _t('editor.layers.analysis-form.quota.emails.saas');
|
||||
}
|
||||
}
|
||||
|
||||
return email;
|
||||
}
|
||||
|
||||
function getMessage (configModel, userModel, notAssignedQuota) {
|
||||
var insideOrganization = userModel.isInsideOrg();
|
||||
var ownerOrganization = userModel.isOrgOwner();
|
||||
var message;
|
||||
|
||||
if (notAssignedQuota) {
|
||||
if (insideOrganization && !ownerOrganization) {
|
||||
message = _t('editor.layers.analysis-form.quota.contact-message.no-quota-assigned.organization');
|
||||
} else {
|
||||
message = _t('editor.layers.analysis-form.quota.contact-message.no-quota-assigned.regular');
|
||||
}
|
||||
} else {
|
||||
if (insideOrganization && !ownerOrganization) {
|
||||
message = _t('editor.layers.analysis-form.quota.contact-message.no-credits.organization');
|
||||
} else {
|
||||
message = _t('editor.layers.analysis-form.quota.contact-message.no-credits.regular');
|
||||
}
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
var getNoCreditMessage = function (quota, type, configModel, userModel) {
|
||||
var link;
|
||||
var message;
|
||||
|
||||
link = CONTACT_LINK_TEMPLATE({
|
||||
mail: getEmail(configModel, userModel),
|
||||
contact: getMessage(configModel, userModel, quota.totalQuota === 0)
|
||||
});
|
||||
|
||||
if (quota.totalQuota === 0) {
|
||||
message = _t('editor.layers.analysis-form.quota.no-quota-assigned-body', {
|
||||
analysis: AnalysesQuotaOptions.getAnalysisName(type),
|
||||
contact: link
|
||||
});
|
||||
} else {
|
||||
message = _t('editor.layers.analysis-form.quota.no-credits-body', {
|
||||
contact: link
|
||||
});
|
||||
}
|
||||
|
||||
return message;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
make: function (data, configModel, userModel) {
|
||||
var blockPrice = Utils.formatNumber(data.quotaInfo.blockPrice / 100);
|
||||
var estimation = data.estimation;
|
||||
var formattedEstimation = Utils.formatNumber(estimation);
|
||||
var blockSize = Utils.formatNumber(data.quotaInfo.blockSize);
|
||||
var body = '';
|
||||
var type = '';
|
||||
var link;
|
||||
|
||||
if (data.quotaInfo.hardLimit) {
|
||||
if (data.creditsLeft > 0) {
|
||||
if (data.quotaInfo.enoughQuota) {
|
||||
// You need to use approximately xxx of your credits.
|
||||
body = _t('editor.layers.analysis-form.quota.enough-quota', {
|
||||
credits: formattedEstimation
|
||||
});
|
||||
type = 'success';
|
||||
} else {
|
||||
// We're sorry the current quota is insufficient to enrich your data. Rows will be set to null and analysis may not complete. Please contact us to extend your quota for this function.
|
||||
link = CONTACT_LINK_TEMPLATE({
|
||||
mail: getEmail(configModel, userModel),
|
||||
contact: getMessage(configModel, userModel, false)
|
||||
});
|
||||
|
||||
body = _t('editor.layers.analysis-form.quota.hard-limit-not-enough-quota', {
|
||||
contact: link
|
||||
});
|
||||
type = 'alert';
|
||||
}
|
||||
} else {
|
||||
// To get access to the [service] you will need more credits. Your current quota is not enough. Contact us.
|
||||
body = getNoCreditMessage(data.quotaInfo, data.type, configModel, userModel);
|
||||
type = 'alert';
|
||||
}
|
||||
} else {
|
||||
if (data.creditsLeft > 0) {
|
||||
// enoughQuota wil be true when soft_limit, becuase in this case, the api asume true
|
||||
if (data.creditsLeft >= estimation) {
|
||||
// You need to use some of your credits for this (data.estimation)
|
||||
body = _t('editor.layers.analysis-form.quota.enough-quota', {
|
||||
credits: formattedEstimation
|
||||
});
|
||||
type = 'success';
|
||||
} else {
|
||||
// Using this analysis component may incur extra fees depending on your account quota. Please review your current quota and/or request a larger allocation.
|
||||
body = _t('editor.layers.analysis-form.quota.soft-limit-enough-quota', {
|
||||
credits: formattedEstimation,
|
||||
blockSize: blockSize,
|
||||
blockPrice: blockPrice
|
||||
});
|
||||
type = 'alert';
|
||||
}
|
||||
} else {
|
||||
if (data.quotaInfo.totalQuota === 0) {
|
||||
body = getNoCreditMessage(data.quotaInfo, data.type, configModel, userModel);
|
||||
} else {
|
||||
// Using this analysis component may incur extra fees depending on your account quota. Please review your current quota and/or request a larger allocation.
|
||||
body = _t('editor.layers.analysis-form.quota.soft-limit-enough-quota', {
|
||||
credits: formattedEstimation,
|
||||
blockSize: blockSize,
|
||||
blockPrice: blockPrice
|
||||
});
|
||||
}
|
||||
type = 'alert';
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: type,
|
||||
body: body
|
||||
};
|
||||
}
|
||||
};
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
var _ = require('underscore');
|
||||
var AnalysesQuotaOptions = require('./analyses-quota-options');
|
||||
/**
|
||||
* Provide info about quota
|
||||
* It handles the source of data: api and user model
|
||||
*/
|
||||
|
||||
var QUOTA_DEFAULTS = {
|
||||
totalQuota: 0,
|
||||
usedQuota: 0,
|
||||
blockSize: 1000,
|
||||
blockPrice: 0,
|
||||
enoughQuota: true
|
||||
};
|
||||
|
||||
module.exports = function (type, userModel, quotaInfo, enoughQuota) {
|
||||
var apiData = quotaInfo.getService(AnalysesQuotaOptions.getServiceName(type)) || {};
|
||||
var userData = userModel.get(AnalysesQuotaOptions.getUserDataName(type)) || {};
|
||||
|
||||
// Remove null | undefined | falsy values
|
||||
userData = _.pick(userData, _.identity);
|
||||
return _.defaults(
|
||||
{
|
||||
blockSize: userData.block_size,
|
||||
blockPrice: userData.block_price
|
||||
},
|
||||
{
|
||||
totalQuota: apiData.get('monthly_quota'),
|
||||
usedQuota: apiData.get('used_quota'),
|
||||
hardLimit: !apiData.get('soft_limit')
|
||||
},
|
||||
{
|
||||
enoughQuota: enoughQuota
|
||||
},
|
||||
QUOTA_DEFAULTS
|
||||
);
|
||||
};
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
// Make common analyses actions available whin editor context
|
||||
var _ = require('underscore');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var AddAnalysisView = require('builder/components/modals/add-analysis/add-analysis-view');
|
||||
var nodeIds = require('builder/value-objects/analysis-node-ids');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'onboardings',
|
||||
'layerDefinitionsCollection',
|
||||
'modals',
|
||||
'userModel',
|
||||
'configModel'
|
||||
];
|
||||
|
||||
module.exports = (function () {
|
||||
return {
|
||||
init: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._notAppliedAnalysis = null;
|
||||
},
|
||||
|
||||
setLayerId: function (layerId) {
|
||||
var layerDefinitionModel = this._layerDefinitionsCollection.get(layerId);
|
||||
if (!layerDefinitionModel) {
|
||||
throw new Error('no layer-definition found for id' + layerId + ', available layer ids are: ' + this._layerDefinitionsCollection.pluck('id') + ')');
|
||||
}
|
||||
|
||||
this._layerId = layerId;
|
||||
},
|
||||
|
||||
setStackLayoutView: function (stackLayoutView) {
|
||||
if (!stackLayoutView) {
|
||||
throw new Error('stackLayoutView is required');
|
||||
}
|
||||
|
||||
this._stackLayoutView = stackLayoutView;
|
||||
},
|
||||
|
||||
generateGeoreferenceAnalysis: function () {
|
||||
var layerDefinitionModel = this._layerDefinitionsCollection.get(this._layerId);
|
||||
|
||||
var letter = layerDefinitionModel.get('letter');
|
||||
var sourceId = layerDefinitionModel.get('source');
|
||||
|
||||
return {
|
||||
source: sourceId,
|
||||
id: letter === nodeIds.letter(sourceId)
|
||||
? nodeIds.next(sourceId)
|
||||
: letter + '1',
|
||||
type: 'georeference-long-lat'
|
||||
};
|
||||
},
|
||||
|
||||
addAnalysis: function () {
|
||||
this._onboardings.destroy();
|
||||
|
||||
var layerDefinitionModel = this._layerDefinitionsCollection.get(this._layerId);
|
||||
|
||||
this._modals.create(function (modalModel) {
|
||||
return new AddAnalysisView({
|
||||
userModel: this._userModel,
|
||||
configModel: this._configModel,
|
||||
modalModel: modalModel,
|
||||
layerDefinitionModel: layerDefinitionModel
|
||||
});
|
||||
}.bind(this), {
|
||||
breadcrumbsEnabled: true
|
||||
});
|
||||
|
||||
this._modals.onDestroyOnce(function (analysisFormAttrs) {
|
||||
if (analysisFormAttrs) {
|
||||
// A analysis option was seleted, redirect to layer-content view with new attrs
|
||||
this._stackLayoutView.model.goToStep(1, layerDefinitionModel, 'layer-content', 'analyses', analysisFormAttrs);
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
saveNotAppliedAnalysis: function (analysis) {
|
||||
this._notAppliedAnalysis = _.extend({ persisted: false }, analysis);
|
||||
},
|
||||
|
||||
getNotAppliedAnalysis: function () {
|
||||
return this._notAppliedAnalysis;
|
||||
},
|
||||
|
||||
clearNotAppliedAnalysis: function () {
|
||||
this._notAppliedAnalysis = null;
|
||||
}
|
||||
};
|
||||
})();
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
<h2 class="CDB-Text CDB-Size-huge is-light u-secondaryTextColor">
|
||||
<%= body %>
|
||||
</h2>
|
||||
Executable
+200
@@ -0,0 +1,200 @@
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var Infobox = require('builder/components/infobox/infobox-factory');
|
||||
var InfoboxModel = require('builder/components/infobox/infobox-model');
|
||||
var InfoboxCollection = require('builder/components/infobox/infobox-collection');
|
||||
var PanelWithOptionsView = require('builder/components/view-options/panel-with-options-view');
|
||||
var panelTemplate = require('./panel-with-options.tpl');
|
||||
var AnalysesContentView = require('./analyses-content-view');
|
||||
var OnboardingLauncher = require('builder/components/onboardings/generic/generic-onboarding-launcher');
|
||||
var OnboardingView = require('builder/components/onboardings/layers/analysis-onboarding/analysis-onboarding-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var ONBOARDING_KEY = 'layer-analyses-onboarding';
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'userActions',
|
||||
'analysisFormsCollection',
|
||||
'layerDefinitionModel',
|
||||
'analysisDefinitionNodesCollection',
|
||||
'configModel',
|
||||
'userModel',
|
||||
'editorModel',
|
||||
'stackLayoutModel',
|
||||
'onboardings',
|
||||
'onboardingNotification',
|
||||
'layerContentModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
module: 'editor/layers/layer-content-views/analyses/analyses-view',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._selectedNodeId = opts.selectedNodeId;
|
||||
|
||||
this._deleteAnalysisModel = new Backbone.Model({
|
||||
analysisId: null
|
||||
});
|
||||
|
||||
this._infoboxModel = new InfoboxModel({
|
||||
state: ''
|
||||
});
|
||||
|
||||
this._overlayModel = new Backbone.Model({
|
||||
visible: this._isLayerHidden()
|
||||
});
|
||||
|
||||
this._onboardingLauncher = new OnboardingLauncher({
|
||||
view: OnboardingView,
|
||||
onboardingNotification: this._onboardingNotification,
|
||||
notificationKey: ONBOARDING_KEY,
|
||||
onboardings: this._onboardings
|
||||
}, {
|
||||
editorModel: this._editorModel,
|
||||
selector: 'LayerOnboarding'
|
||||
});
|
||||
|
||||
this._getQueryAndCheckState();
|
||||
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this._launchOnboarding();
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
|
||||
this._initViews();
|
||||
this._infoboxState();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._layerDefinitionModel, 'change:visible', this._infoboxState);
|
||||
this.listenTo(this._deleteAnalysisModel, 'change:analysisId', this._infoboxState);
|
||||
this.listenTo(this._analysisFormsCollection, 'reset remove', this._getQueryAndCheckState);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var self = this;
|
||||
var infoboxSstates = [
|
||||
{
|
||||
state: 'layer-hidden',
|
||||
createContentView: function () {
|
||||
return Infobox.createWithAction({
|
||||
type: 'alert',
|
||||
title: _t('editor.messages.layer-hidden.title'),
|
||||
body: _t('editor.messages.layer-hidden.body'),
|
||||
action: {
|
||||
label: _t('editor.messages.layer-hidden.show')
|
||||
}
|
||||
});
|
||||
},
|
||||
onAction: self._showHiddenLayer.bind(self)
|
||||
},
|
||||
{
|
||||
state: 'deleting-analysis',
|
||||
createContentView: function () {
|
||||
return Infobox.createWithAction({
|
||||
type: 'alert',
|
||||
title: _t('editor.messages.deleting-analysis.title'),
|
||||
body: _t('editor.messages.deleting-analysis.body'),
|
||||
action: {
|
||||
label: _t('editor.messages.deleting-analysis.delete')
|
||||
}
|
||||
});
|
||||
},
|
||||
onClose: self._resetDeletingAnalysis.bind(self),
|
||||
onAction: self._deleteAnalysis.bind(self)
|
||||
}
|
||||
];
|
||||
|
||||
var infoboxCollection = new InfoboxCollection(infoboxSstates);
|
||||
|
||||
var panelWithOptionsView = new PanelWithOptionsView({
|
||||
template: panelTemplate,
|
||||
className: 'Editor-content',
|
||||
editorModel: self._editorModel,
|
||||
infoboxModel: self._infoboxModel,
|
||||
infoboxCollection: infoboxCollection,
|
||||
createContentView: function () {
|
||||
return new AnalysesContentView({
|
||||
className: 'Editor-content',
|
||||
userActions: self._userActions,
|
||||
analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
|
||||
userModel: self._userModel,
|
||||
analysisFormsCollection: self._analysisFormsCollection,
|
||||
configModel: self._configModel,
|
||||
layerDefinitionModel: self._layerDefinitionModel,
|
||||
stackLayoutModel: self._stackLayoutModel,
|
||||
selectedNodeId: self._selectedNodeId,
|
||||
overlayModel: self._overlayModel,
|
||||
layerContentModel: self._layerContentModel,
|
||||
deleteAnalysisModel: self._deleteAnalysisModel
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.append(panelWithOptionsView.render().el);
|
||||
this.addView(panelWithOptionsView);
|
||||
},
|
||||
|
||||
_getQueryAndCheckState: function () {
|
||||
if (!this._analysisFormsCollection.isEmpty()) {
|
||||
this._infoboxState();
|
||||
}
|
||||
},
|
||||
|
||||
_launchOnboarding: function () {
|
||||
if (this._onboardingNotification.getKey(ONBOARDING_KEY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._analysisFormsCollection.isEmpty()) {
|
||||
this._onboardingLauncher.launch();
|
||||
}
|
||||
|
||||
this._layerDefinitionModel.canBeGeoreferenced()
|
||||
.then(function (canBeGeoreferenced) {
|
||||
if (canBeGeoreferenced) {
|
||||
this._onboardingNotification.setKey(ONBOARDING_KEY, true);
|
||||
}
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_infoboxState: function () {
|
||||
if (this._isLayerHidden()) {
|
||||
this._infoboxModel.set({ state: 'layer-hidden' });
|
||||
this._overlayModel.set({ visible: true });
|
||||
} else if (this._deleteAnalysisModel.get('analysisId')) {
|
||||
this._infoboxModel.set({ state: 'deleting-analysis' });
|
||||
this._overlayModel.set({ visible: true });
|
||||
} else {
|
||||
this._infoboxModel.set({ state: '' });
|
||||
this._overlayModel.set({ visible: false });
|
||||
}
|
||||
},
|
||||
|
||||
_showHiddenLayer: function () {
|
||||
this._layerDefinitionModel.toggleVisible();
|
||||
this._userActions.saveLayer(this._layerDefinitionModel);
|
||||
|
||||
this._infoboxState();
|
||||
},
|
||||
|
||||
_isLayerHidden: function () {
|
||||
return this._layerDefinitionModel.get('visible') === false;
|
||||
},
|
||||
|
||||
_resetDeletingAnalysis: function () {
|
||||
this._deleteAnalysisModel.unset('analysisId');
|
||||
},
|
||||
|
||||
_deleteAnalysis: function () {
|
||||
this._analysisFormsCollection.deleteNode(this._deleteAnalysisModel.get('analysisId'));
|
||||
this._resetDeletingAnalysis();
|
||||
}
|
||||
});
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./analyses-workflow-item.tpl');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
var Analyses = require('builder/data/analyses');
|
||||
var NotificationErrorMessageHandler = require('builder/editor/layers/notification-error-message-handler');
|
||||
var nodeIds = require('builder/value-objects/analysis-node-ids');
|
||||
var layerColors = require('builder/data/layer-colors');
|
||||
var Router = require('builder/routes/router');
|
||||
var linkTemplate = require('./more-info-link.tpl');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
tagName: 'li',
|
||||
className: 'VerticalRadioList-item',
|
||||
|
||||
events: {
|
||||
'click': '_onNodeClick'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.analysisNode && !opts.formModel) throw new Error('analysisNode or formModel is required');
|
||||
|
||||
this._isNew = !opts.analysisNode;
|
||||
this._analysisNode = opts.analysisNode || opts.formModel; // if form model represents a new node there is no analysis yet; use form model as fallback
|
||||
this._layerId = opts.layerId;
|
||||
this._selectedNodeId = opts.selectedNodeId;
|
||||
|
||||
this.listenTo(this._analysisNode, 'change:status', this.render);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var status = this._analysisNode.get('status');
|
||||
var isDone = this._isNew || status === 'ready' || status === 'failed';
|
||||
var isSelected = this._analysisNode.id === this._selectedNodeId;
|
||||
var hasFailed = status === 'failed';
|
||||
var tooltipMessage = this._tooltipMessage(hasFailed);
|
||||
var letter = nodeIds.letter(this._analysisNode.id);
|
||||
var bgColor = layerColors.getColorForLetter(letter);
|
||||
var helpURL = Analyses.link(this._analysisNode.get('type'));
|
||||
|
||||
var linkContent;
|
||||
|
||||
if (isSelected) {
|
||||
linkContent = linkTemplate({
|
||||
link: helpURL,
|
||||
type: this._analysisNode.get('type')
|
||||
});
|
||||
}
|
||||
|
||||
this.clearSubViews();
|
||||
this.$el.html(
|
||||
template({
|
||||
isNew: this._isNew,
|
||||
isDone: isDone,
|
||||
bgColor: bgColor,
|
||||
isSelected: isSelected,
|
||||
nodeId: this._analysisNode.id,
|
||||
hasError: hasFailed,
|
||||
name: this._analysisName(),
|
||||
linkContent: linkContent
|
||||
})
|
||||
);
|
||||
|
||||
if (!this._isNew && !isSelected) {
|
||||
this._tooltip = new TipsyTooltipView({
|
||||
el: this.$('.js-tooltip'),
|
||||
className: hasFailed ? 'is-error' : '',
|
||||
gravity: 'w',
|
||||
title: function () {
|
||||
return tooltipMessage;
|
||||
}
|
||||
});
|
||||
this.addView(this._tooltip);
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_onNodeClick: function () {
|
||||
Router.goToAnalysisNode(this._layerId, this._analysisNode.id);
|
||||
},
|
||||
|
||||
_analysisName: function () {
|
||||
return Analyses.title(this._analysisNode.get('type') || '');
|
||||
},
|
||||
|
||||
_tooltipMessage: function (hasFailed) {
|
||||
if (hasFailed) {
|
||||
var error = this._analysisNode.get('error');
|
||||
var errorMessage = error && error.message && NotificationErrorMessageHandler.extractError(error.message).message;
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
return _t('editor.layers.analysis-form.edit-analysis-tooltip');
|
||||
}
|
||||
});
|
||||
lib/assets/javascripts/builder/editor/layers/layer-content-views/analyses/analyses-workflow-item.tpl
Executable
+30
@@ -0,0 +1,30 @@
|
||||
<div class="
|
||||
VerticalRadioList-itemInner
|
||||
<% if (hasError) { %> has-error<% } %>
|
||||
">
|
||||
<% if (isDone) { %>
|
||||
<div class="VerticalRadioList-radio u-rSpace--m">
|
||||
<input class="CDB-Radio" type="radio" <%- isDone && isSelected ? 'checked' : '' %>>
|
||||
<span class="u-iBlock CDB-Radio-face"></span>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<div class="CDB-LoaderIcon is-blue u-rSpace--m">
|
||||
<svg class="CDB-LoaderIcon-spinner" viewbox="0 0 50 50">
|
||||
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="CDB-Text CDB-Size-small is-semibold u-rSpace u-upperCase u-flex u-alignCenter" style="color: <%- bgColor %>;">
|
||||
<span class="u-rSpace--s"><%- nodeId %></span>
|
||||
<i class="CDB-IconFont CDB-IconFont-ray CDB-Size-small"></i>
|
||||
</div>
|
||||
|
||||
<div class="VerticalRadioList-label CDB-Text CDB-Size-medium js-analysis-name js-tooltip">
|
||||
<%- name %>
|
||||
</div>
|
||||
|
||||
<div class="VerticalRadioList-spacer"></div>
|
||||
|
||||
<%= linkContent %>
|
||||
</div>
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var AnalysesWorkflowItemView = require('./analyses-workflow-item-view');
|
||||
var template = require('./analyses-workflow-list.tpl');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
var utils = require('builder/helpers/utils');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'analysisDefinitionNodesCollection',
|
||||
'analysisFormsCollection',
|
||||
'layerId',
|
||||
'selectedNodeId'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.html(template({
|
||||
layerId: this._layerId
|
||||
}));
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
this._analysisFormsCollection.each(this._renderWorkflowItem, this);
|
||||
var tooltip = new TipsyTooltipView({
|
||||
el: this.$('.js-add-analysis'),
|
||||
gravity: 'w',
|
||||
title: function () {
|
||||
return utils.replaceLastSpaceWithNbsp(_t('editor.layers.analysis-form.add-analysis.tooltip'));
|
||||
},
|
||||
offset: 8
|
||||
});
|
||||
this.addView(tooltip);
|
||||
},
|
||||
|
||||
_renderWorkflowItem: function (formModel) {
|
||||
var view = new AnalysesWorkflowItemView({
|
||||
analysisNode: this._analysisDefinitionNodesCollection.get(formModel.id),
|
||||
formModel: formModel,
|
||||
layerId: this._layerId,
|
||||
selectedNodeId: this._selectedNodeId
|
||||
});
|
||||
this.$('.js-analyes').append(view.render().el);
|
||||
this.addView(view);
|
||||
}
|
||||
});
|
||||
lib/assets/javascripts/builder/editor/layers/layer-content-views/analyses/analyses-workflow-list.tpl
Executable
+7
@@ -0,0 +1,7 @@
|
||||
<button class="CDB-Button CDB-Button--dashed CDB-Button--wide CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase u-bSpace--xl u-flex u-justifyCenter u-alignCenter js-add-analysis" data-layer-id="<%- layerId %>">
|
||||
<span class="js-plus-icon u-flex u-justifyCenter u-alignCenter u-rSpace--m"></span>
|
||||
<%- _t('editor.layers.analysis-form.add-analysis.label') %>
|
||||
</button>
|
||||
|
||||
|
||||
<ul class="VerticalRadioList u-tSpace js-analyes"></ul>
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var AnalysesWorkflowListView = require('./analyses-workflow-list-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'analysisDefinitionNodesCollection',
|
||||
'analysisFormsCollection',
|
||||
'layerDefinitionModel',
|
||||
'selectedNodeId'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this.listenTo(this._analysisDefinitionNodesCollection, 'add', function () {
|
||||
// change:source happens when a node is deleted as well, so we can't just listen to it all the time
|
||||
this._layerDefinitionModel.once('change:source', this.render, this);
|
||||
});
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
|
||||
this._renderAnalysesView();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_renderAnalysesView: function () {
|
||||
var self = this;
|
||||
|
||||
var view = new AnalysesWorkflowListView({
|
||||
analysisDefinitionNodesCollection: self._analysisDefinitionNodesCollection,
|
||||
analysisFormsCollection: self._analysisFormsCollection,
|
||||
layerId: self._layerDefinitionModel.id,
|
||||
selectedNodeId: self._selectedNodeId
|
||||
});
|
||||
|
||||
this.$el.append(view.render().el);
|
||||
this.addView(view);
|
||||
}
|
||||
});
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./analysis-buttons.tpl');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
className: 'u-flex u-justifySpace',
|
||||
|
||||
initialize: function (opts) {
|
||||
this.template = template;
|
||||
this.model = opts.model;
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var html = this.template(this.model.attributes);
|
||||
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
this.$el.append(html);
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.model, 'change:isDisabled', this.render);
|
||||
this.listenTo(this.model, 'change:disableDelete', this.render);
|
||||
// listenTo drops events if view is removed from DOM
|
||||
this.model.on('change:isDone', this.render, this);
|
||||
}
|
||||
});
|
||||
Executable
+21
@@ -0,0 +1,21 @@
|
||||
<button class="
|
||||
CDB-Button
|
||||
CDB-Button--noPadding
|
||||
js-delete
|
||||
<% if (disableDelete) { %> is-disabled<% } %>
|
||||
">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase
|
||||
<% if (isDelete) { %> u-errorTextColor<% } else { %> u-actionTextColor<% } %>">
|
||||
<%- labelDelete %>
|
||||
</span>
|
||||
</button>
|
||||
<button class="CDB-Button CDB-Button--loading CDB-Button--primary js-save
|
||||
<% if (isDisabled) { %> is-disabled<% } %>
|
||||
<% if (!isDone) { %> is-loading<% } %>">
|
||||
<span class="CDB-Button-Text CDB-Text is-semibold CDB-Size-small u-upperCase"><%- labelSave %></span>
|
||||
<div class="CDB-Button-loader CDB-LoaderIcon is-white">
|
||||
<svg class="CDB-LoaderIcon-spinner" viewbox="0 0 50 50">
|
||||
<circle class="CDB-LoaderIcon-path" cx="25" cy="25" r="20" fill="none"/>
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
Executable
+402
@@ -0,0 +1,402 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var Utils = require('builder/helpers/utils');
|
||||
var InfoboxFactory = require('builder/components/infobox/infobox-factory');
|
||||
var InfoboxModel = require('builder/components/infobox/infobox-model');
|
||||
var InfoboxCollection = require('builder/components/infobox/infobox-collection');
|
||||
var AnalysesQuotaPresenter = require('./analyses-quota/analyses-quota-presenter');
|
||||
var AnalysesQuotaOptions = require('./analyses-quota/analyses-quota-options');
|
||||
var AnalysesQuotaProvider = require('./analyses-quota/analyses-quota-provider');
|
||||
var AnalysesQuotaEstimation = require('./analyses-quota/analyses-quota-estimation-input');
|
||||
var AnalysesQuotaEnough = require('./analyses-quota/analyses-quota-enough');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var AnalysisButtonsView = require('./analysis-buttons-view');
|
||||
var AnalysisQuotaView = require('./analysis-quota-view');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'layerDefinitionModel',
|
||||
'configModel',
|
||||
'formModel',
|
||||
'quotaInfo',
|
||||
'querySchemaModel',
|
||||
'stackLayoutModel',
|
||||
'userActions',
|
||||
'userModel',
|
||||
'analysisFormsCollection',
|
||||
'deleteAnalysisModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* View representing the apply and cancel/delete buttons for a form
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
module: 'editor:layers:layer-content-views:analyses:analysis-controls-view',
|
||||
|
||||
className: 'Options-bar',
|
||||
|
||||
events: {
|
||||
'click .js-save': '_onSaveClicked',
|
||||
'click .js-delete': '_onDeleteClicked'
|
||||
},
|
||||
|
||||
_buttonsView: null,
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._analysisNode = opts.analysisNode;
|
||||
|
||||
AnalysesQuotaEstimation.init(opts.configModel);
|
||||
AnalysesQuotaEnough.init(opts.configModel);
|
||||
|
||||
this._viewModel = new Backbone.Model({
|
||||
isNewAnalysis: !opts.analysisNode,
|
||||
hasChanges: !opts.analysisNode,
|
||||
isDone: true,
|
||||
type: this._formModel.get('type')
|
||||
});
|
||||
|
||||
this._infoboxModel = new InfoboxModel({
|
||||
state: this._quotaInfo.getState(),
|
||||
visible: true
|
||||
});
|
||||
|
||||
this._initViewState();
|
||||
this.render = this.render.bind(this);
|
||||
|
||||
this._initBinds();
|
||||
|
||||
if (!opts.analysisNode) {
|
||||
this._fetchQuotaIfNeeded();
|
||||
}
|
||||
|
||||
this._onFormModelChanged = this._onFormModelChanged.bind(this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
var type, isValid, requiresQuota, view;
|
||||
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
type = this._formModel.get('type');
|
||||
isValid = this._formModel.isValid();
|
||||
// If no dataservice, requiresQuota returns false because the model doesn't exist and it's converted to boolean
|
||||
requiresQuota = AnalysesQuotaOptions.requiresQuota(type, this._quotaInfo);
|
||||
|
||||
if (this._isAnalysisDone() && !this._hasChanges()) {
|
||||
view = this._createButtonsView();
|
||||
} else {
|
||||
// If the analysis doesn't require quota, let's not wait or check the fetching state
|
||||
if (!requiresQuota || !isValid) {
|
||||
view = this._createButtonsView();
|
||||
} else {
|
||||
view = this._createQuotaView();
|
||||
}
|
||||
}
|
||||
|
||||
this.addView(view);
|
||||
this.$el.append(view.render().el);
|
||||
this._setTrackingClass();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViewState: function () {
|
||||
this._viewState = new Backbone.Model({
|
||||
canBeGeoreferenced: false
|
||||
});
|
||||
this._setViewState();
|
||||
},
|
||||
|
||||
_setTrackingClass: function () {
|
||||
this.$('.js-save').addClass('track-' + this._formModel.get('type') + '-analysis track-apply');
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._formModel, 'change', this._onFormModelChanged);
|
||||
if (this._analysisNode) {
|
||||
this._bindAnalysisNodeStatusChanges();
|
||||
}
|
||||
this.listenTo(this._viewState, 'change', this.render);
|
||||
},
|
||||
|
||||
_bindAnalysisNodeStatusChanges: function () {
|
||||
this.listenTo(this._analysisNode, 'change:status', function () {
|
||||
this._viewModel.set('isDone', this._isAnalysisDone());
|
||||
var isAnalysisDone = this._isAnalysisDone();
|
||||
if (isAnalysisDone) {
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_onFormModelChanged: function () {
|
||||
var isAnalysisDone = this._isAnalysisDone();
|
||||
var isDone = this._viewModel.get('isNewAnalysis') || isAnalysisDone;
|
||||
var canSave = this._formModel.isValid() && isDone;
|
||||
|
||||
this._viewModel.set({
|
||||
hasChanges: true,
|
||||
type: this._formModel.get('type'),
|
||||
isDisabled: !canSave
|
||||
});
|
||||
|
||||
this.render();
|
||||
this._fetchQuotaIfNeeded();
|
||||
},
|
||||
|
||||
_fetchQuotaIfNeeded: function () {
|
||||
var type = this._formModel.get('type');
|
||||
var isValid = this._formModel.isValid();
|
||||
|
||||
var quota = this._quotaInfo;
|
||||
if ((quota.needsCheck() || AnalysesQuotaOptions.requiresQuota(type, quota)) && isValid) {
|
||||
this._fetchQuota();
|
||||
}
|
||||
},
|
||||
|
||||
_fetchQuota: function () {
|
||||
this._infoboxModel.set('state', 'fetching');
|
||||
|
||||
this._quotaInfo.fetch({
|
||||
success: function () {
|
||||
this._buildQuotaViewInfo();
|
||||
}.bind(this),
|
||||
error: function (error) {
|
||||
this._viewModel.set({error: error});
|
||||
this._infoboxModel.set('state', 'error');
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
_createButtonsView: function () {
|
||||
this._viewModel.set({
|
||||
labelSave: _t('editor.layers.analysis-form.apply-btn'),
|
||||
labelDelete: this._analysisNode
|
||||
? _t('editor.layers.analysis-form.delete-btn')
|
||||
: _t('editor.layers.analysis-form.cancel-btn'),
|
||||
isDelete: this._analysisNode,
|
||||
disableDelete: !this._canDelete(),
|
||||
isDisabled: !this._canSave()
|
||||
});
|
||||
|
||||
if (this._buttonsView === null) {
|
||||
this._buttonsView = new AnalysisButtonsView({
|
||||
model: this._viewModel
|
||||
});
|
||||
}
|
||||
|
||||
return this._buttonsView;
|
||||
},
|
||||
|
||||
_buildQuotaViewInfo: function () {
|
||||
var formModel = this._formModel;
|
||||
var userModel = this._userModel;
|
||||
var type = formModel.get('type');
|
||||
var service = AnalysesQuotaOptions.getServiceName(type);
|
||||
var query = this._querySchemaModel.get('query');
|
||||
var quotaData = this._quotaInfo;
|
||||
|
||||
var payload = {};
|
||||
payload.type = type;
|
||||
|
||||
var errorCallback = function (error) {
|
||||
this._viewModel.set('error', error);
|
||||
this._infoboxModel.set('state', 'error');
|
||||
}.bind(this);
|
||||
|
||||
var checkEnoughQuota = function (data) {
|
||||
// Apply transformation to the input
|
||||
// for example isolines takes tracts in in order to calculate estimation
|
||||
var rows = AnalysesQuotaOptions.transformInput(type, data, formModel);
|
||||
payload.estimation = rows;
|
||||
// with the input, we calculate wether the quota is enough
|
||||
this.enoughQuotaPromise = AnalysesQuotaEnough.fetch(service, rows);
|
||||
return this.enoughQuotaPromise;
|
||||
}.bind(this);
|
||||
|
||||
var buildPayload = function (enoughQuota) {
|
||||
// then we can get the full set of data
|
||||
payload.quotaInfo = AnalysesQuotaProvider(type, userModel, quotaData, enoughQuota);
|
||||
payload.creditsLeft = payload.quotaInfo.totalQuota - payload.quotaInfo.usedQuota;
|
||||
payload.canRunAnalysis = ((enoughQuota && payload.quotaInfo.totalQuota > 0) ||
|
||||
(payload.quotaInfo.hardLimit === false && payload.quotaInfo.totalQuota > 0));
|
||||
|
||||
// we need to check the request's state because it could be another render while permforming the current one
|
||||
// so, to make sure we have all data returned by the apis, we don't raise the green flag until both stats are resolved.
|
||||
var estimationState = this.estimationPromise.state();
|
||||
var enoughQuotaState = this.enoughQuotaPromise.state();
|
||||
|
||||
if (estimationState === 'resolved' && enoughQuotaState === 'resolved') {
|
||||
if (this._infoboxModel.get('state') !== 'ready') {
|
||||
this.payload = payload;
|
||||
// finally we are ready to show the information
|
||||
this._infoboxModel.set('state', 'ready');
|
||||
}
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
var dsReady = quotaData.isReady();
|
||||
|
||||
// Only checks api if analysis belongs to a service with quota
|
||||
if (service !== null && dsReady) {
|
||||
if (formModel.isValid()) {
|
||||
// get the estimation for this analysis
|
||||
this.estimationPromise = AnalysesQuotaEstimation.fetch(query);
|
||||
|
||||
this.estimationPromise
|
||||
.then(checkEnoughQuota, errorCallback)
|
||||
.then(buildPayload, errorCallback);
|
||||
} else {
|
||||
// to render the simple buttons view.
|
||||
this.render();
|
||||
}
|
||||
} else if (!dsReady) {
|
||||
errorCallback(_t('editor.layers.analysis-form.quota.quota-dataservice-down'));
|
||||
}
|
||||
},
|
||||
|
||||
_createQuotaView: function () {
|
||||
var states = [
|
||||
{
|
||||
state: 'fetching',
|
||||
createContentView: function () {
|
||||
return InfoboxFactory.createLoading({
|
||||
body: _t('editor.layers.analysis-form.quota.loading')
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
state: 'ready',
|
||||
createContentView: function () {
|
||||
var payload = this.payload;
|
||||
var quotaMessage = '';
|
||||
|
||||
if (payload.creditsLeft > 0) {
|
||||
quotaMessage = _t('editor.layers.analysis-form.quota.credits-left-message', {
|
||||
smart_count: Utils.formatNumber(payload.creditsLeft)
|
||||
});
|
||||
} else {
|
||||
quotaMessage = _t('editor.layers.analysis-form.quota.no-credits-message');
|
||||
}
|
||||
payload.quotaInfo.quotaMessage = quotaMessage;
|
||||
|
||||
var dataInfobox = _.extend(AnalysesQuotaPresenter.make(payload, this._configModel, this._userModel), {
|
||||
title: _t('editor.layers.analysis-form.quota.title'),
|
||||
action: {
|
||||
label: _t('editor.layers.analysis-form.confirm-analysis'),
|
||||
type: 'primary',
|
||||
disabled: !this._canSave() || !payload.canRunAnalysis
|
||||
},
|
||||
quota: payload.quotaInfo,
|
||||
closable: false
|
||||
});
|
||||
|
||||
return InfoboxFactory.createQuota(dataInfobox);
|
||||
}.bind(this),
|
||||
onAction: function () {
|
||||
var payload = this.payload;
|
||||
if (this._canSave() && payload.canRunAnalysis) {
|
||||
this._saveAnalysis();
|
||||
}
|
||||
}.bind(this)
|
||||
},
|
||||
{
|
||||
state: 'error',
|
||||
createContentView: function () {
|
||||
return InfoboxFactory.createWithAction({
|
||||
title: _t('editor.layers.analysis-form.quota.title'),
|
||||
type: 'error',
|
||||
body: _t('editor.layers.analysis-form.quota.quota-fetch-error', {
|
||||
error: this._viewModel.get('error')
|
||||
}),
|
||||
action: {
|
||||
label: _t('editor.layers.analysis-form.quota.cancel')
|
||||
}
|
||||
});
|
||||
}.bind(this),
|
||||
onAction: function () {
|
||||
this._stackLayoutModel.prevStep('layers');
|
||||
}.bind(this)
|
||||
}
|
||||
];
|
||||
|
||||
return new AnalysisQuotaView({
|
||||
className: 'Infobox-wrapper',
|
||||
infoboxModel: this._infoboxModel,
|
||||
infoboxCollection: new InfoboxCollection(states)
|
||||
});
|
||||
},
|
||||
|
||||
_canSave: function () {
|
||||
var isAnalysisDone = this._isAnalysisDone();
|
||||
var isDone = this._viewModel.get('isNewAnalysis') || isAnalysisDone;
|
||||
var hasChanges = this._viewModel.get('hasChanges');
|
||||
return this._formModel.isValid() && isDone && hasChanges;
|
||||
},
|
||||
|
||||
_onSaveClicked: function () {
|
||||
if (this._canSave()) {
|
||||
this._saveAnalysis();
|
||||
}
|
||||
},
|
||||
|
||||
_canDelete: function () {
|
||||
var nodeDefModel = this._analysisNode;
|
||||
var canDeleteNode = (!nodeDefModel || nodeDefModel.canBeDeletedByUser());
|
||||
var canBeGeoreferenced = this._viewState.get('canBeGeoreferenced');
|
||||
var hasAnalyses = this._layerDefinitionModel.hasAnalyses();
|
||||
|
||||
return (hasAnalyses && canDeleteNode) || (canDeleteNode && !canBeGeoreferenced);
|
||||
},
|
||||
|
||||
_onDeleteClicked: function () {
|
||||
if (this._canDelete()) {
|
||||
this._isLastAnalysisNodeSelected()
|
||||
? this._deleteAnalysisModel.set('analysisId', this._formModel.get('id'))
|
||||
: this._deleteAnalysis();
|
||||
}
|
||||
},
|
||||
|
||||
_hasChanges: function () {
|
||||
return this._viewModel.get('hasChanges') === true;
|
||||
},
|
||||
|
||||
_isAnalysisDone: function () {
|
||||
return this._analysisNode && (this._analysisNode.isDone() || this._analysisNode.hasFailed());
|
||||
},
|
||||
|
||||
_isLastAnalysisNodeSelected: function () {
|
||||
return this._analysisFormsCollection.first() !== this._formModel;
|
||||
},
|
||||
|
||||
_saveAnalysis: function () {
|
||||
this._infoboxModel.set('state', 'idle');
|
||||
var newNode = this._userActions.saveAnalysis(this._formModel);
|
||||
|
||||
if (!this._analysisNode) {
|
||||
this._analysisNode = newNode;
|
||||
this._bindAnalysisNodeStatusChanges();
|
||||
}
|
||||
|
||||
// For cached analysis, if the user edits the analysis to a previous version of it
|
||||
// the request returns the status:ready but the analysis node already has this state
|
||||
// so no changes are triggered
|
||||
this._analysisNode.set({ status: 'launched' }, { silent: true });
|
||||
|
||||
this._viewModel.set({ hasChanges: false, isDone: false });
|
||||
this.render();
|
||||
},
|
||||
|
||||
_deleteAnalysis: function () {
|
||||
this._analysisFormsCollection.deleteNode(this._formModel.get('id'));
|
||||
},
|
||||
|
||||
_setViewState: function () {
|
||||
return this._layerDefinitionModel.canBeGeoreferenced()
|
||||
.then(function (canBeGeoreferenced) {
|
||||
this._viewState.set('canBeGeoreferenced', canBeGeoreferenced);
|
||||
}.bind(this));
|
||||
}
|
||||
});
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
var _ = require('underscore');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var template = require('./aggregate-intersection-form.tpl');
|
||||
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
|
||||
var AGGREGATE_FORMAT_FIELDS_NAMES = [
|
||||
'type',
|
||||
'aggregate_column',
|
||||
'aggregate_function'
|
||||
];
|
||||
|
||||
/**
|
||||
* Form model for the aggregate intersection analysis
|
||||
*/
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
parse: function (attrs) {
|
||||
if (!attrs.aggregate_column) {
|
||||
attrs.aggregate_column = 'cartodb_id';
|
||||
}
|
||||
if (!attrs.aggregate_function) {
|
||||
attrs.aggregate_function = 'count';
|
||||
}
|
||||
attrs.aggregate = {
|
||||
operator: attrs.aggregate_function,
|
||||
attribute: attrs.aggregate_function === 'count' ? '' : attrs.aggregate_column
|
||||
};
|
||||
return attrs;
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._columnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this.listenTo(this._analysisSourceOptionsModel, 'change:fetching', this._setSchema);
|
||||
this.listenTo(this._columnOptions, 'columnsFetched', this._setSchema);
|
||||
|
||||
this.on('change:aggregate', this._onChangeAggregate, this);
|
||||
this.on('change:target', this._onChangeTarget, this);
|
||||
|
||||
this._setSchema();
|
||||
this._fetchColumns();
|
||||
},
|
||||
|
||||
_formatAttrs: function (formAttrs) {
|
||||
var customFormattedFormAttrs = _.pick(formAttrs, ['id', 'source', 'target'].concat(AGGREGATE_FORMAT_FIELDS_NAMES));
|
||||
return BaseAnalysisFormModel.prototype._formatAttrs.call(this, customFormattedFormAttrs);
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {
|
||||
dataFields: 'aggregate',
|
||||
hasTarget: this.get('target') !== undefined
|
||||
};
|
||||
},
|
||||
|
||||
_filterSchemaFieldsByType: function (schema) {
|
||||
// Always include the source and target fields in addition to the type-specific fields
|
||||
return _.pick(schema, ['source', 'target', 'aggregate']);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, this._filterSchemaFieldsByType({
|
||||
source: this._primarySourceSchemaItem(_t('editor.layers.analysis-form.base-layer')),
|
||||
target: {
|
||||
type: 'NodeDataset',
|
||||
title: _t('editor.layers.analysis-form.target-layer'),
|
||||
options: this._getSourceOptionsForSource({
|
||||
sourceAttrName: 'target'
|
||||
}),
|
||||
validators: ['required'],
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: {
|
||||
disabled: this._isSourceDisabled('target')
|
||||
}
|
||||
},
|
||||
aggregate: {
|
||||
type: 'Operators',
|
||||
title: _t('editor.layers.analysis-form.operation'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
options: this._columnOptions.filterByType('number')
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
_onChangeAggregate: function () {
|
||||
var aggregate = this.get('aggregate');
|
||||
this.set({
|
||||
aggregate_column: aggregate.attribute || 'cartodb_id',
|
||||
aggregate_function: aggregate.operator
|
||||
});
|
||||
},
|
||||
|
||||
_onChangeTarget: function () {
|
||||
this.set('aggregate', {
|
||||
operator: 'count',
|
||||
attribute: ''
|
||||
});
|
||||
this._analysisSourceOptionsModel.createSourceNodeUnlessExisting(this.get('target'));
|
||||
this._fetchColumns();
|
||||
},
|
||||
|
||||
_fetchColumns: function () {
|
||||
var target = this.get('target');
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(target);
|
||||
|
||||
if (nodeDefModel) {
|
||||
this._columnOptions.setNode(nodeDefModel);
|
||||
} else if (target) {
|
||||
this._columnOptions.setDataset(target);
|
||||
}
|
||||
},
|
||||
|
||||
_isSourceDisabled: function (sourceAttrName) {
|
||||
return this._isPrimarySource(sourceAttrName) || this._isFetchingOptions();
|
||||
}
|
||||
});
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="source,target">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.select-layer') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.intersect-step-one-desc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo <%- hasTarget ? '' : 'is-disabled' %>">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="<%- dataFields %>">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.measure-by') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--xl"><%- _t('editor.layers.analysis-form.filter-aggregate') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
var _ = require('underscore');
|
||||
var DistanceConverter = require('builder/value-objects/distance-converter');
|
||||
|
||||
var DEFAULT_DISTANCE = 'meters';
|
||||
|
||||
module.exports = {
|
||||
type: 'buffer',
|
||||
label: _t('editor.layers.analysis-form.distance'),
|
||||
parametersDataFields: 'type,distance,radius,isolines,dissolved',
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
var formAttrs = _.extend({}, nodeAttrs);
|
||||
|
||||
try {
|
||||
formAttrs.radius = DistanceConverter.toDistance(nodeAttrs.radius, nodeAttrs.distance);
|
||||
formAttrs.distance = nodeAttrs.distance || DEFAULT_DISTANCE;
|
||||
formAttrs.dissolved = nodeAttrs.dissolved === undefined ? false : nodeAttrs.dissolved;
|
||||
formAttrs.isolines = nodeAttrs.isolines || 1;
|
||||
} catch (err) {
|
||||
formAttrs.radius = 100;
|
||||
formAttrs.isolines = 1;
|
||||
formAttrs.distance = DEFAULT_DISTANCE;
|
||||
formAttrs.dissolved = false;
|
||||
}
|
||||
|
||||
return formAttrs;
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs) {
|
||||
return _.defaults(
|
||||
{
|
||||
radius: DistanceConverter.toMeters(formAttrs.radius, formAttrs.distance)
|
||||
},
|
||||
formAttrs
|
||||
);
|
||||
}
|
||||
};
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
var _ = require('underscore');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var template = require('./area-of-influence-form.tpl');
|
||||
var DistanceConverter = require('builder/value-objects/distance-converter');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
|
||||
var AREA_OF_INFLUENCE_TYPES = require('./area-of-influence-types');
|
||||
var TYPE_TO_META_MAP = {};
|
||||
AREA_OF_INFLUENCE_TYPES.map(function (d) {
|
||||
TYPE_TO_META_MAP[d.type] = d;
|
||||
});
|
||||
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
|
||||
parse: function (attrs) {
|
||||
return _.defaults(
|
||||
_.pick(attrs, 'id', 'source'), // maintain default attrs
|
||||
this._typeDef(attrs.type).parse(attrs)
|
||||
);
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._setSchema();
|
||||
|
||||
this.on('change:type', this._onTypeChanged, this);
|
||||
this.on('change:distance', this._onDistanceChanged, this);
|
||||
this.on('change:kind', this._onKindChanged, this);
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {parametersDataFields: this._typeDef().parametersDataFields};
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel.updateNodeDefinition}
|
||||
*/
|
||||
updateNodeDefinition: function (nodeDefModel) {
|
||||
var attrs = this._formatAttrs(this.attributes);
|
||||
nodeDefModel.clear({silent: true});
|
||||
nodeDefModel.set(attrs);
|
||||
},
|
||||
|
||||
_formatAttrs: function (formAttrs) {
|
||||
var customFormattedFormAttrs = this._typeDef().formatAttrs(formAttrs);
|
||||
return BaseAnalysisFormModel.prototype._formatAttrs.call(this, customFormattedFormAttrs);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this,
|
||||
this._filterSchemaFieldsByType({
|
||||
source: this._primarySourceSchemaItem(),
|
||||
type: {
|
||||
type: 'Radio',
|
||||
text: _t('editor.layers.analysis-form.type'),
|
||||
options: AREA_OF_INFLUENCE_TYPES.map(function (d) {
|
||||
var isAnalysisTypeEnabled = this._analyses.isAnalysisValidByType(d.type, { configModel: this._configModel });
|
||||
var canChangeToType = this._canChangeToType(d.type);
|
||||
var isDisabled = !isAnalysisTypeEnabled || !canChangeToType;
|
||||
var helpMessage = !isAnalysisTypeEnabled ? _t('editor.layers.analysis-form.disabled-by-config') : _t('editor.layers.analysis-form.valid-type');
|
||||
|
||||
return {
|
||||
val: d.type,
|
||||
label: d.label,
|
||||
disabled: isDisabled,
|
||||
help: isDisabled ? helpMessage : ''
|
||||
};
|
||||
}, this)
|
||||
},
|
||||
distance: {
|
||||
type: 'Radio',
|
||||
title: _t('editor.layers.analysis-form.units'),
|
||||
options: _.map(DistanceConverter.OPTIONS, function (d) {
|
||||
return {
|
||||
val: d.distance,
|
||||
label: this._distanceLabel(d.distance)
|
||||
};
|
||||
}, this)
|
||||
},
|
||||
radius: {
|
||||
type: 'Number',
|
||||
label: _t('editor.layers.analysis-form.radius'),
|
||||
validators: ['required', this._radiusInterval()]
|
||||
},
|
||||
kind: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.by'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
options: [
|
||||
{
|
||||
val: 'walk',
|
||||
label: _t('editor.layers.analysis-form.by-walk')
|
||||
}, {
|
||||
val: 'car',
|
||||
label: _t('editor.layers.analysis-form.by-car')
|
||||
}
|
||||
]
|
||||
},
|
||||
isolines: {
|
||||
type: 'Number',
|
||||
title: _t('editor.layers.analysis-form.tracts'),
|
||||
help: _t('editor.layers.analysis-form.tracts-help'),
|
||||
validators: ['required', {
|
||||
type: 'interval',
|
||||
min: 1,
|
||||
max: 4
|
||||
}]
|
||||
},
|
||||
time: {
|
||||
type: 'Number',
|
||||
title: _t('editor.layers.analysis-form.time-seconds'),
|
||||
validators: ['required', this._timeInterval()]
|
||||
},
|
||||
dissolved: {
|
||||
type: 'Radio',
|
||||
title: _t('editor.layers.analysis-form.boundaries'),
|
||||
options: [
|
||||
{
|
||||
val: 'false',
|
||||
label: _t('editor.layers.analysis-form.intact'),
|
||||
help: _t('editor.layers.analysis-form.intact-help')
|
||||
}, {
|
||||
val: 'true',
|
||||
label: _t('editor.layers.analysis-form.dissolved'),
|
||||
help: _t('editor.layers.analysis-form.dissolved-help')
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_canChangeToType: function (type) {
|
||||
if (type === this.get('type')) return true;
|
||||
|
||||
var source = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'));
|
||||
return source.isValidAsInputForType(type);
|
||||
},
|
||||
|
||||
_filterSchemaFieldsByType: function (schema) {
|
||||
// Always include the source field in addition to the type-specific fields
|
||||
var fields = ['source'].concat(this._typeDef().parametersDataFields.split(','));
|
||||
return _.pick(schema, fields);
|
||||
},
|
||||
|
||||
_onTypeChanged: function () {
|
||||
this._replaceAttrs();
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_onDistanceChanged: function () {
|
||||
this._fitParamToRange('radius', this._radiusInterval());
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_onKindChanged: function () {
|
||||
this._fitParamToRange('time', this._timeInterval());
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_fitParamToRange: function (paramName, range) {
|
||||
var val = this.get(paramName);
|
||||
if (val < range.min) {
|
||||
val = range.min;
|
||||
} else if (val > range.max) {
|
||||
val = range.max;
|
||||
}
|
||||
this.set(paramName, val, {silent: true});
|
||||
},
|
||||
|
||||
_replaceAttrs: function () {
|
||||
var attrs = this.parse(this.attributes);
|
||||
this.clear({silent: true});
|
||||
this.set('type', attrs.type, {silent: true}); // re-set type to avoid change:type event to trigger again
|
||||
this.set(attrs);
|
||||
},
|
||||
|
||||
_typeDef: function (type) {
|
||||
type = type || this.get('type');
|
||||
return TYPE_TO_META_MAP[type];
|
||||
},
|
||||
|
||||
_radiusInterval: function () {
|
||||
switch (this.get('distance')) {
|
||||
case 'meters':
|
||||
return {
|
||||
type: 'interval',
|
||||
min: 10,
|
||||
max: 10000
|
||||
};
|
||||
case 'kilometers':
|
||||
case 'miles':
|
||||
return {
|
||||
type: 'interval',
|
||||
min: 0.5,
|
||||
max: 100,
|
||||
step: 0.5
|
||||
};
|
||||
default:
|
||||
return {
|
||||
type: 'interval',
|
||||
min: 1,
|
||||
max: 100
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_timeInterval: function () {
|
||||
switch (this.get('kind')) {
|
||||
case 'car':
|
||||
return {
|
||||
type: 'interval',
|
||||
min: 60,
|
||||
max: 14400,
|
||||
step: 10
|
||||
};
|
||||
case 'walk':
|
||||
default:
|
||||
return {
|
||||
type: 'interval',
|
||||
min: 60,
|
||||
max: 3600
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_distanceLabel: function (distance) {
|
||||
switch (distance) {
|
||||
case 'meters':
|
||||
return _t('editor.layers.analysis-form.meters');
|
||||
case 'kilometers':
|
||||
return _t('editor.layers.analysis-form.kilometers');
|
||||
case 'miles':
|
||||
return _t('editor.layers.analysis-form.miles');
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.parameters') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.parameters-description') %></p>
|
||||
</div>
|
||||
<div class="u-tSpace-xl CDB-Text CDB-Fieldset">
|
||||
<p class="CDB-Legend u-upperCase u-iBlock CDB-Text is-semibold CDB-Size-small u-rSpace--m"><%- _t('editor.layers.analysis-form.base-layer') %></p>
|
||||
<div class="Editor-formInput u-ellipsis" data-editors="source"></div>
|
||||
</div>
|
||||
<div data-fields="<%- parametersDataFields %>"></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
type: 'trade-area',
|
||||
label: _t('editor.layers.analysis-form.time'),
|
||||
parametersDataFields: 'type,kind,time,isolines,dissolved',
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
return {
|
||||
type: 'trade-area',
|
||||
kind: nodeAttrs.kind || 'walk',
|
||||
isolines: nodeAttrs.isolines || 1,
|
||||
time: nodeAttrs.time || 100,
|
||||
dissolved: nodeAttrs.dissolved === 'true' || nodeAttrs.dissolved === true
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs) {
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./area-of-influence-buffer'),
|
||||
require('./area-of-influence-trade-area')
|
||||
];
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
WITH dep_fns AS (
|
||||
SELECT oid, proname, proargnames, proargtypes FROM pg_catalog.pg_proc WHERE proname ~* '^dep_ext_'
|
||||
),
|
||||
fns_argstypes AS (SELECT oid, array_agg((
|
||||
SELECT
|
||||
CASE
|
||||
WHEN typcategory = 'A' THEN 'array'
|
||||
WHEN typcategory = 'N' THEN 'number'
|
||||
WHEN typcategory = 'S' THEN 'string'
|
||||
WHEN typcategory = 'B' THEN 'boolean'
|
||||
ELSE 'unknown' END
|
||||
FROM pg_catalog.pg_type
|
||||
WHERE argtype = pg_catalog.pg_type.oid
|
||||
)) as argtypes
|
||||
FROM (
|
||||
SELECT pg_proc.oid, unnest(pg_proc.proargtypes) as argtype
|
||||
FROM pg_catalog.pg_proc, dep_fns
|
||||
WHERE pg_proc.oid = dep_fns.oid
|
||||
) _q
|
||||
group by oid
|
||||
),
|
||||
fns_meta AS (
|
||||
SELECT
|
||||
dep_fns.proname,
|
||||
dep_fns.proargnames,
|
||||
fns_argstypes.argtypes,
|
||||
CASE WHEN fns_argstypes.argtypes[6] = 'array' THEN
|
||||
-- [whether it has a secondary node or not, where the extra parameters start, where to stop]
|
||||
ARRAY[1,7,array_length(argtypes,1)]
|
||||
ELSE
|
||||
ARRAY[0,5,array_length(argtypes,1)]
|
||||
END as p
|
||||
FROM dep_fns, fns_argstypes
|
||||
WHERE dep_fns.oid = fns_argstypes.oid
|
||||
)
|
||||
SELECT
|
||||
proname as fn_name,
|
||||
proargnames[p[2]:p[3]] as params_names,
|
||||
argtypes[p[2]:p[3]] as params_types,
|
||||
p[1]::boolean as has_secondary_node
|
||||
FROM fns_meta
|
||||
+200
@@ -0,0 +1,200 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var camshaftReference = require('builder/data/camshaft-reference');
|
||||
|
||||
/**
|
||||
* Base model for all analysis form models.
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.analyses) throw new Error('analyses is required');
|
||||
if (!opts.configModel) throw new Error('configModel is required');
|
||||
if (!opts.layerDefinitionModel) throw new Error('layerDefinitionModel is required');
|
||||
if (!opts.analysisSourceOptionsModel) throw new Error('analysisSourceOptionsModel is required');
|
||||
|
||||
this._analyses = opts.analyses;
|
||||
this._configModel = opts.configModel;
|
||||
this._layerDefinitionModel = opts.layerDefinitionModel;
|
||||
this._analysisSourceOptionsModel = opts.analysisSourceOptionsModel;
|
||||
|
||||
this.schema = {};
|
||||
},
|
||||
|
||||
isNew: function () {
|
||||
return true; // so one can call .destroy() on it to clean up bindings etc. and have it removed from its collection
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return undefined; // override to return custom template
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {}; // override to return custom template data
|
||||
},
|
||||
|
||||
_getSourceOption: function () {
|
||||
var sourceNodeDefModel = this._getSourceNodeDefModel();
|
||||
var layerName = sourceNodeDefModel.isSourceType()
|
||||
? this._layerDefinitionModel.getTableName()
|
||||
: this._layerDefinitionModel.getName();
|
||||
|
||||
return sourceNodeDefModel && [{
|
||||
val: sourceNodeDefModel.id,
|
||||
type: 'node',
|
||||
nodeTitle: this._analyses.short_title(sourceNodeDefModel),
|
||||
layerName: layerName,
|
||||
color: sourceNodeDefModel.getColor(),
|
||||
isSourceType: sourceNodeDefModel.isSourceType()
|
||||
}];
|
||||
},
|
||||
|
||||
_getSourceNodeDefModel: function () {
|
||||
var sourceNames = ['source', 'primary_source'];
|
||||
var sourceNodeDefModel = _.reduce(sourceNames, function (memo, sourceName) {
|
||||
if (!memo) {
|
||||
memo = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get(sourceName));
|
||||
}
|
||||
return memo;
|
||||
}, null, this);
|
||||
|
||||
return sourceNodeDefModel;
|
||||
},
|
||||
|
||||
_primarySourceSchemaItem: function (customLabel) {
|
||||
return {
|
||||
type: 'NodeDataset',
|
||||
title: customLabel || _t('editor.layers.analysis-form.base-layer'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
options: this._getSourceOption(),
|
||||
editorAttrs: {disabled: true}
|
||||
};
|
||||
},
|
||||
|
||||
setFormValidationErrors: function (errors) {
|
||||
this._formValidationErrors = errors;
|
||||
this.trigger('change', this);
|
||||
},
|
||||
|
||||
validate: function () {
|
||||
var errors = _.extend(
|
||||
{},
|
||||
camshaftReference.validate(this.attributes),
|
||||
this._formValidationErrors
|
||||
);
|
||||
|
||||
if (!_.isEmpty(errors)) {
|
||||
return errors;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {Backbone.Model.prototype.set} Maintain persisted attr if model is clear'ed
|
||||
*/
|
||||
set: function (key, val, options) {
|
||||
if (key == null) return this;
|
||||
|
||||
var attrs;
|
||||
if (typeof key === 'object') {
|
||||
attrs = key;
|
||||
options = val;
|
||||
} else {
|
||||
(attrs = {})[key] = val;
|
||||
}
|
||||
|
||||
options || (options = {});
|
||||
|
||||
var persisted = attrs.persisted || this.get('persisted');
|
||||
|
||||
Backbone.Model.prototype.set.call(this, attrs, options);
|
||||
|
||||
if (persisted !== undefined) {
|
||||
Backbone.Model.prototype.set.call(this, 'persisted', persisted, {silent: true});
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
updateNodeDefinition: function (nodeDefModel) {
|
||||
var nodeAttrs = this._formatAttrs(this.attributes);
|
||||
nodeDefModel.set(nodeAttrs);
|
||||
},
|
||||
|
||||
/**
|
||||
* @deprecated use updateNodeDefinition instead
|
||||
*/
|
||||
_updateNodeDefinition: function () {
|
||||
this.updateNodeDefinition.apply(this, arguments);
|
||||
},
|
||||
|
||||
createNodeDefinition: function (userActions) {
|
||||
var nodeAttrs = this._formatAttrs(this.attributes);
|
||||
this.set('persisted', true);
|
||||
return userActions.createAnalysisNode(nodeAttrs, this._layerDefinitionModel);
|
||||
},
|
||||
|
||||
_formatAttrs: function (formAttrs) {
|
||||
return _.omit(camshaftReference.parse(formAttrs), 'persisted');
|
||||
},
|
||||
|
||||
_setSchema: function (schema) {
|
||||
this.schema = schema;
|
||||
this.trigger('changeSchema', this);
|
||||
},
|
||||
|
||||
_isPrimarySource: function (sourceAttrName) {
|
||||
return sourceAttrName === this.get('primary_source_name');
|
||||
},
|
||||
|
||||
_isFetchingOptions: function () {
|
||||
return this._analysisSourceOptionsModel.get('fetching');
|
||||
},
|
||||
|
||||
_getSourceOptionsForSource: function (options) {
|
||||
options = options || {};
|
||||
var sourceAttrName = null;
|
||||
var requiredSimpleGeometryType = '*';
|
||||
var ignorePrimarySource = false;
|
||||
var includeSourceNode = options.includeSourceNode || false;
|
||||
|
||||
// options validation and defaults
|
||||
if (options.sourceAttrName === void 0) {
|
||||
throw new Error('sourceAttrName is required.');
|
||||
} else {
|
||||
sourceAttrName = options.sourceAttrName;
|
||||
}
|
||||
|
||||
if (options.requiredSimpleGeometryType !== void 0) {
|
||||
requiredSimpleGeometryType = options.requiredSimpleGeometryType;
|
||||
}
|
||||
|
||||
if (options.ignorePrimarySource) {
|
||||
ignorePrimarySource = true;
|
||||
}
|
||||
|
||||
var currentSource = this.get(sourceAttrName);
|
||||
|
||||
if (!ignorePrimarySource && this._isPrimarySource(sourceAttrName)) {
|
||||
return [currentSource];
|
||||
}
|
||||
if (this._isFetchingOptions()) {
|
||||
return [{
|
||||
val: currentSource,
|
||||
label: _t('editor.layers.analysis-form.loading'),
|
||||
isLoading: true
|
||||
}];
|
||||
} else {
|
||||
// fetched
|
||||
var sourceId = this._layerDefinitionModel.get('source');
|
||||
return this._analysisSourceOptionsModel
|
||||
.getSelectOptions(requiredSimpleGeometryType)
|
||||
.filter(function (d) {
|
||||
return (
|
||||
(includeSourceNode ? true : d.val !== sourceId) &&
|
||||
(options.onlyNodes ? d.type === 'node' : true)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
var _ = require('underscore');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var template = require('./centroid-form.tpl');
|
||||
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');
|
||||
|
||||
var FORMAT_FIELD_NAMES = 'category_column,aggregation,aggregation_column,weight_column';
|
||||
var PARAMETERS_DATA_FIELDS = 'source,category,weight';
|
||||
|
||||
/**
|
||||
* Form model for a centroid and weighted-centroid analysis
|
||||
* It has a rather complicated schema, that depends on several data points and state.
|
||||
* https://github.com/CartoDB/camshaft/blob/master/reference/versions/0.38.0/reference.json
|
||||
*/
|
||||
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
parse: function (attrs) {
|
||||
var operator = {
|
||||
operator: attrs.aggregation || 'count',
|
||||
attribute: attrs.aggregation === 'count' ? '' : attrs.aggregation_column
|
||||
};
|
||||
|
||||
var aggregate_value = (!!attrs.aggregation_column || !!attrs.aggregation) ? operator : '';
|
||||
|
||||
return _.extend(attrs, {
|
||||
category: attrs.category_column || '',
|
||||
weight: attrs.weight_column || '',
|
||||
aggregate: aggregate_value
|
||||
});
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._columnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'))
|
||||
});
|
||||
|
||||
this.listenTo(this._columnOptions, 'columnsFetched', this._setSchema);
|
||||
|
||||
this.on('change:category', this._onChangeCategory, this);
|
||||
this.on('change:aggregate', this._updateAggregation, this);
|
||||
this.on('change:weight', this._onChangeWeight, this);
|
||||
this.on('change:type', this._setSchema, this);
|
||||
|
||||
this._updateAggregation();
|
||||
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
updateNodeDefinition: function (nodeDefModel) {
|
||||
var attrs = this._formatAttrs(this.attributes);
|
||||
nodeDefModel.clear({silent: true});
|
||||
nodeDefModel.set(attrs);
|
||||
},
|
||||
|
||||
_getFormatFieldNames: function () {
|
||||
var fields = FORMAT_FIELD_NAMES.split(',');
|
||||
|
||||
if (this.get('aggregate') === '') {
|
||||
fields = _.difference(fields, ['aggregation', 'aggregation_column']);
|
||||
}
|
||||
|
||||
return fields.join(',');
|
||||
},
|
||||
|
||||
_formatAttrs: function (formAttrs) {
|
||||
var customFormattedFormAttrs = _.pick(formAttrs, ['id', 'source', 'type'].concat(this._getFormatFieldNames().split(',')));
|
||||
|
||||
return BaseAnalysisFormModel.prototype._formatAttrs.call(this, customFormattedFormAttrs);
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {
|
||||
parametersDataFields: PARAMETERS_DATA_FIELDS
|
||||
};
|
||||
},
|
||||
|
||||
/*
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
var operator = {
|
||||
operator: this.get('aggregation'),
|
||||
attribute: this.get('aggregation_column')
|
||||
};
|
||||
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, {
|
||||
source: this._primarySourceSchemaItem(),
|
||||
|
||||
category: {
|
||||
type: 'EnablerEditor',
|
||||
title: '',
|
||||
label: _t('editor.layers.analysis-form.categorize-by'),
|
||||
help: _t('editor.layers.analysis-form.categorize-by-help'),
|
||||
editor: {
|
||||
type: 'Select',
|
||||
options: this._columnOptions.all(),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
label: _t('editor.layers.analysis-form.column')
|
||||
}
|
||||
},
|
||||
|
||||
weight: {
|
||||
type: 'EnablerEditor',
|
||||
title: '',
|
||||
label: _t('editor.layers.analysis-form.weighted-by'),
|
||||
help: _t('editor.layers.analysis-form.weighted-by-help'),
|
||||
validators: ['required'],
|
||||
editor: {
|
||||
type: 'Select',
|
||||
options: this._columnOptions.filterByType('number'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
label: _t('editor.layers.analysis-form.column')
|
||||
}
|
||||
},
|
||||
|
||||
aggregate: {
|
||||
type: 'EnablerEditor',
|
||||
title: '',
|
||||
label: _t('editor.layers.analysis-form.operation'),
|
||||
help: _t('editor.layers.analysis-form.aggregate-by-help'),
|
||||
defaultValue: operator,
|
||||
editor: {
|
||||
type: 'Operators',
|
||||
options: this._columnOptions.filterByType('number'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
label: _t('editor.layers.analysis-form.column')
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_onChangeWeight: function () {
|
||||
if (this.get('weight') !== '') {
|
||||
this.set('type', 'weighted-centroid');
|
||||
this.set('weight_column', this.get('weight'));
|
||||
} else {
|
||||
this.set('type', 'centroid');
|
||||
this.unset('weight_column');
|
||||
}
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_onChangeCategory: function () {
|
||||
// category_column is optional but if we send it empty,
|
||||
// it has influence in the final result
|
||||
if (this.get('category') === '') {
|
||||
this.unset('category_column');
|
||||
} else {
|
||||
this.set('category_column', this.get('category'));
|
||||
}
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_updateAggregation: function () {
|
||||
var aggregate = this.get('aggregate');
|
||||
if (aggregate === '') {
|
||||
this.set({
|
||||
aggregation: 'count',
|
||||
aggregation_column: ''
|
||||
});
|
||||
} else {
|
||||
this.set({
|
||||
aggregation_column: aggregate.attribute,
|
||||
aggregation: aggregate.operator
|
||||
});
|
||||
}
|
||||
|
||||
this._setSchema();
|
||||
}
|
||||
});
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="<%= parametersDataFields %>">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.parameters') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.centroid-desc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="aggregate">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.measure-by') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--xl"><%- _t('editor.layers.analysis-form.filter-aggregate') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+122
@@ -0,0 +1,122 @@
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var template = require('./closest-form.tpl');
|
||||
|
||||
var MAX_RESPONSES = 32;
|
||||
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
parse: function (attrs) {
|
||||
if (!attrs.responses) {
|
||||
attrs.responses = 1;
|
||||
}
|
||||
|
||||
return attrs;
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._resetColumnOptions();
|
||||
this._initBinds();
|
||||
this._setSchema();
|
||||
this._fetchColumns();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._analysisSourceOptionsModel, 'change:fetching', this._onSourceOptionsFetched);
|
||||
this.on('change:target', this._onChangeTarget, this);
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {
|
||||
hasTarget: this.get('target') !== undefined
|
||||
};
|
||||
},
|
||||
|
||||
_resetColumnOptions: function () {
|
||||
this._columnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel
|
||||
});
|
||||
this.listenTo(this._columnOptions, 'columnsFetched', this._onColumnsFetched);
|
||||
},
|
||||
|
||||
_onSourceOptionsFetched: function () {
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_setSchema: function () {
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, {
|
||||
source: this._primarySourceSchemaItem(_t('editor.layers.analysis-form.base-layer')),
|
||||
target: {
|
||||
type: 'NodeDataset',
|
||||
title: _t('editor.layers.analysis-form.target'),
|
||||
options: this._getSourceOptionsForSource({
|
||||
sourceAttrName: 'target',
|
||||
includeSourceNode: true
|
||||
}),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
validators: ['required'],
|
||||
editorAttrs: {
|
||||
disabled: this._isSourceDisabled('target')
|
||||
}
|
||||
},
|
||||
category: {
|
||||
type: 'EnablerEditor',
|
||||
title: '',
|
||||
label: _t('editor.layers.analysis-form.group-by'),
|
||||
help: _t('editor.layers.analysis-form.find-nearest.categorized-help'),
|
||||
editor: {
|
||||
type: 'Select',
|
||||
options: this._columnOptions.filterByType('string'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: {
|
||||
showLabel: false
|
||||
}
|
||||
}
|
||||
},
|
||||
responses: {
|
||||
type: 'Number',
|
||||
title: _t('editor.layers.analysis-form.find-nearest.max-results'),
|
||||
value: 1,
|
||||
validators: ['required', {
|
||||
type: 'interval',
|
||||
min: 1,
|
||||
max: MAX_RESPONSES
|
||||
}]
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_isSourceDisabled: function (sourceAttrName) {
|
||||
return this._isPrimarySource(sourceAttrName) || this._isFetchingOptions();
|
||||
},
|
||||
|
||||
_onChangeTarget: function () {
|
||||
this.set('category', '');
|
||||
this._setSchema();
|
||||
|
||||
this._analysisSourceOptionsModel.createSourceNodeUnlessExisting(this.get('target'));
|
||||
this._fetchColumns();
|
||||
},
|
||||
|
||||
_fetchColumns: function () {
|
||||
var target = this.get('target');
|
||||
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(target);
|
||||
|
||||
if (nodeDefModel) {
|
||||
this._columnOptions.setNode(nodeDefModel);
|
||||
} else if (target) {
|
||||
this._columnOptions.setDataset(target);
|
||||
}
|
||||
},
|
||||
|
||||
_onColumnsFetched: function () {
|
||||
this._setSchema();
|
||||
}
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="source,target">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.select-your-points') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.target-layer-dataset') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Editor-HeaderInfo <%- hasTarget ? '' : 'is-disabled' %>">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="responses,category">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.parameters') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.layers.analysis-form.parameters-description') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+339
@@ -0,0 +1,339 @@
|
||||
var _ = require('underscore');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var template = require('./connect-with-lines.tpl');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');
|
||||
|
||||
var CONNECT_WITH_LINES = require('./connect-with-lines-types');
|
||||
|
||||
var TYPE_TO_META_MAP = {};
|
||||
CONNECT_WITH_LINES.map(function (d) {
|
||||
TYPE_TO_META_MAP[d.type] = d;
|
||||
});
|
||||
|
||||
/**
|
||||
* Form model for a convex hull analysis
|
||||
*/
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
defaults: {
|
||||
closest: true,
|
||||
order_type: 'asc'
|
||||
},
|
||||
|
||||
parse: function (attrs) {
|
||||
return _.defaults(
|
||||
_.pick(attrs, 'id', 'source'), // maintain default attrs
|
||||
this._typeDef(attrs.type).parse(attrs)
|
||||
);
|
||||
},
|
||||
|
||||
validate: function () {
|
||||
var errors = BaseAnalysisFormModel.prototype.validate.apply(this, arguments);
|
||||
|
||||
if (errors && errors.target_column) {
|
||||
if (!this._hasSecondGeometry()) {
|
||||
errors.target_column = _t('editor.layers.analysis-form.second-geom-required');
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._columnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'))
|
||||
});
|
||||
|
||||
this._targetColumnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'))
|
||||
});
|
||||
|
||||
this.listenTo(this._columnOptions, 'columnsFetched', this._setSchema);
|
||||
this.listenTo(this._targetColumnOptions, 'columnsFetched', this._setSchema);
|
||||
this.listenTo(this._analysisSourceOptionsModel, 'change:fetching', this._setSchema);
|
||||
|
||||
this.on('change:target', this._onChangeTarget, this);
|
||||
this.on('change:type', this._onTypeChanged, this);
|
||||
this.on('change:group', this._setSchema, this);
|
||||
|
||||
this._setSchema();
|
||||
this._fetchColumns();
|
||||
},
|
||||
|
||||
_onTypeChanged: function () {
|
||||
this._replaceAttrs();
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_replaceAttrs: function () {
|
||||
var attrs = this.parse(this.attributes);
|
||||
this.clear({ silent: true });
|
||||
this.set('type', attrs.type, { silent: true }); // re-set type to avoid change:type event to trigger again
|
||||
this.set(_.extend(attrs, this.defaults));
|
||||
},
|
||||
|
||||
updateNodeDefinition: function (nodeDefModel) {
|
||||
var attrs = this._formatAttrs(this.attributes);
|
||||
nodeDefModel.clear({ silent: true });
|
||||
nodeDefModel.set(attrs);
|
||||
},
|
||||
|
||||
_formatAttrs: function (formAttrs) {
|
||||
var customFormattedFormAttrs = this._typeDef().formatAttrs(formAttrs, this._columnOptions);
|
||||
return BaseAnalysisFormModel.prototype._formatAttrs.call(this, customFormattedFormAttrs);
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {
|
||||
type: this.get('type'),
|
||||
group: this.get('group'),
|
||||
parametersDataFields: this._typeDef().parametersDataFields
|
||||
};
|
||||
},
|
||||
|
||||
_filterSchemaFieldsByType: function (schema) {
|
||||
// Always include the source field in addition to the type-specific fields
|
||||
var fields = ['source', 'type'].concat(this._typeDef().parametersDataSchema.split(','));
|
||||
return _.pick(schema, fields);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, this._filterSchemaFieldsByType({
|
||||
source: this._primarySourceSchemaItem(),
|
||||
type: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.type'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
options: CONNECT_WITH_LINES.map(function (d) {
|
||||
return {
|
||||
val: d.type,
|
||||
label: d.label
|
||||
};
|
||||
}, this)
|
||||
},
|
||||
source_column: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.base-layer'),
|
||||
placeholder: _t('editor.layers.analysis-form.select-column'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
options: this._getColumnOptions('source_column', ['string', 'number'])
|
||||
},
|
||||
target: {
|
||||
type: 'NodeDataset',
|
||||
title: _t('editor.layers.analysis-form.target'),
|
||||
options: this._getSourceOptionsForSource('target', 'point'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: {
|
||||
disabled: this._isSourceDisabled('target')
|
||||
},
|
||||
validators: ['required']
|
||||
},
|
||||
target_column: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.target-column'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
options: this._getColumnOptionsForTarget()
|
||||
},
|
||||
target_source_column: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.target-column'),
|
||||
options: this._getTargetSourceColumnOptions('target_source_column'),
|
||||
placeholder: _t('editor.layers.analysis-form.select-column'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: {
|
||||
disabled: this._isTargetColumnDisabled()
|
||||
}
|
||||
},
|
||||
destination_longitude: {
|
||||
type: 'Text',
|
||||
title: _t('editor.layers.analysis-form.longitude'),
|
||||
validators: ['required'],
|
||||
editorAttrs: {
|
||||
placeholder: _t('editor.layers.analysis-form.enter-longitude')
|
||||
}
|
||||
},
|
||||
destination_latitude: {
|
||||
type: 'Text',
|
||||
title: _t('editor.layers.analysis-form.latitude'),
|
||||
validators: ['required'],
|
||||
editorAttrs: {
|
||||
placeholder: _t('editor.layers.analysis-form.enter-latitude')
|
||||
}
|
||||
},
|
||||
closest: {
|
||||
type: 'Radio',
|
||||
title: _t('editor.layers.analysis-form.closeness'),
|
||||
options: [
|
||||
{ label: _t('editor.layers.analysis-form.to-closest'), val: 'true' },
|
||||
{ label: _t('editor.layers.analysis-form.all-to-all'), val: 'false' }
|
||||
]
|
||||
},
|
||||
group: {
|
||||
type: 'Enabler',
|
||||
label: _t('editor.layers.analysis-form.group-by'),
|
||||
title: '',
|
||||
editorAttrs: {
|
||||
reversed: true
|
||||
}
|
||||
},
|
||||
category_column: {
|
||||
type: 'EnablerEditor',
|
||||
title: '',
|
||||
label: _t('editor.layers.analysis-form.group-by'),
|
||||
editor: {
|
||||
type: 'Select',
|
||||
options: this._columnOptions.all(),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: {
|
||||
showLabel: false
|
||||
}
|
||||
}
|
||||
},
|
||||
order_column: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.order-by'),
|
||||
options: this._columnOptions.filterByType(['date', 'number']),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
validators: ['required']
|
||||
},
|
||||
order_type: {
|
||||
type: 'Radio',
|
||||
title: _t('editor.layers.analysis-form.order'),
|
||||
options: [
|
||||
{ label: _t('editor.layers.analysis-form.asc'), val: 'asc' },
|
||||
{ label: _t('editor.layers.analysis-form.desc'), val: 'desc' }
|
||||
]
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
_onChangeTarget: function () {
|
||||
this.set('target_source_column', '');
|
||||
this._analysisSourceOptionsModel.createSourceNodeUnlessExisting(this.get('target'));
|
||||
this._fetchColumns();
|
||||
},
|
||||
|
||||
_fetchColumns: function () {
|
||||
var target = this.get('target');
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(target);
|
||||
|
||||
if (nodeDefModel) {
|
||||
this._targetColumnOptions.setNode(nodeDefModel);
|
||||
} else if (target) {
|
||||
this._targetColumnOptions.setDataset(target);
|
||||
}
|
||||
},
|
||||
|
||||
_getColumnOptionsForTarget: function () {
|
||||
var columns = this._getColumnOptions('target_column', 'geometry');
|
||||
|
||||
return _.reject(columns, function (column) {
|
||||
return (column.val === 'the_geom' || column.val === 'the_geom_webmercator');
|
||||
}, this);
|
||||
},
|
||||
|
||||
_getColumnOptions: function (attr, attrType) {
|
||||
var opts = this._columnOptions.filterByType(attrType);
|
||||
|
||||
if (this._columnOptions.get('columnsFetched') && this.get(attr)) {
|
||||
var attrExists = false;
|
||||
var value = this.get(attr);
|
||||
|
||||
var customValue = {
|
||||
val: value,
|
||||
label: '"' + value + '"'
|
||||
};
|
||||
|
||||
if (opts && opts.length) {
|
||||
attrExists = _.find(opts, function (attr) {
|
||||
return attr.val === customValue.val;
|
||||
});
|
||||
}
|
||||
|
||||
if (!attrExists) {
|
||||
opts.push(customValue);
|
||||
opts = _.sortBy(opts, function (d) {
|
||||
return d.val;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return opts;
|
||||
},
|
||||
|
||||
_getTargetSourceColumnOptions: function (attr) {
|
||||
var opts = this._targetColumnOptions.all();
|
||||
|
||||
if (this._targetColumnOptions.get('columnsFetched') && this.get(attr)) {
|
||||
var attrExists = false;
|
||||
var value = this.get(attr);
|
||||
|
||||
var customValue = {
|
||||
val: value,
|
||||
label: '"' + value + '"'
|
||||
};
|
||||
|
||||
if (opts && opts.length) {
|
||||
attrExists = _.find(opts, function (attr) {
|
||||
return attr.val === customValue.val;
|
||||
});
|
||||
}
|
||||
|
||||
if (!attrExists) {
|
||||
opts.push(customValue);
|
||||
opts = _.sortBy(opts, function (d) {
|
||||
return d.val;
|
||||
});
|
||||
}
|
||||
}
|
||||
return opts;
|
||||
},
|
||||
|
||||
_typeDef: function (type) {
|
||||
type = type || this.get('type');
|
||||
return TYPE_TO_META_MAP[type];
|
||||
},
|
||||
|
||||
_getSourceOptionsForSource: function (sourceAttrName, requiredSimpleGeometryType) {
|
||||
var currentSource = this.get(sourceAttrName);
|
||||
|
||||
if (this._isFetchingOptions()) {
|
||||
return [{
|
||||
val: currentSource,
|
||||
label: _t('editor.layers.analysis-form.loading'),
|
||||
isLoading: true
|
||||
}];
|
||||
} else {
|
||||
// fetched
|
||||
var source = this._getSourceOption()[0];
|
||||
return this._analysisSourceOptionsModel
|
||||
.getSelectOptions(requiredSimpleGeometryType)
|
||||
.filter(function (d) {
|
||||
return d.val !== source.val && d.val !== source.layerName;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_hasSecondGeometry: function () {
|
||||
return this._getColumnOptionsForTarget().length;
|
||||
},
|
||||
|
||||
_isTargetColumnDisabled: function () {
|
||||
return !this._targetColumnOptions.get('columnsFetched');
|
||||
},
|
||||
|
||||
_isSourceDisabled: function (sourceAttrName) {
|
||||
return this._isPrimarySource(sourceAttrName) || this._isFetchingOptions();
|
||||
}
|
||||
});
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
module.exports = [
|
||||
require('./connect-with-lines/line-to-single-point'),
|
||||
require('./connect-with-lines/line-source-to-target'),
|
||||
require('./connect-with-lines/line-sequential')
|
||||
];
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo Editor-HeaderInfo--noMargin">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="<%= parametersDataFields %>">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.parameters') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.define-how-connect-points') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (type == 'line-sequential') { %>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderPad u-rSpace--m"></div>
|
||||
<div class="Editor-checker" data-fields="category_column"></div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<% if (type == 'line-source-to-target') { %>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderPad u-rSpace--m"></div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text">
|
||||
|
||||
<div class="Editor-checker Editor-checker--slim u-flex u-alignCenter" data-fields="group"></div>
|
||||
|
||||
<% if (group) { %>
|
||||
<div class="Editor-formInner--nested">
|
||||
<div class="CDB-Text Editor-formInner Editor-formInner--noMargin">
|
||||
<p class="CDB-Legend u-upperCase u-iBlock CDB-Text is-semibold CDB-Size-small u-rSpace--m"><%- _t('editor.layers.analysis-form.source-column') %></p>
|
||||
<div class="Editor-formInput" data-editors="source_column"></div>
|
||||
</div>
|
||||
|
||||
<div class="CDB-Text Editor-formInner">
|
||||
<p class="CDB-Legend u-upperCase u-iBlock CDB-Text is-semibold CDB-Size-small u-rSpace--m"><%- _t('editor.layers.analysis-form.target-col') %></p>
|
||||
<div class="Editor-formInput" data-editors="target_source_column"></div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</form>
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = {
|
||||
type: 'line-sequential',
|
||||
label: _t('editor.layers.analysis-form.line-sequential'),
|
||||
parametersDataFields: 'source,type,order_column,order_type',
|
||||
parametersDataSchema: 'order_column,order_type,category_column',
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
return {
|
||||
type: 'line-sequential',
|
||||
order_column: nodeAttrs.order_column,
|
||||
category_column: nodeAttrs.category_column,
|
||||
order_type: nodeAttrs.order_type
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs, columnOptions) {
|
||||
if (!formAttrs.category_column) {
|
||||
formAttrs = _.omit(formAttrs, 'category_column');
|
||||
}
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = {
|
||||
type: 'line-source-to-target',
|
||||
label: _t('editor.layers.analysis-form.line-source-to-target'),
|
||||
parametersDataFields: 'source,type,target,closest',
|
||||
parametersDataSchema: 'target,closest,group,source_column,target_source_column',
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
return {
|
||||
type: 'line-source-to-target',
|
||||
target: nodeAttrs.target,
|
||||
closest: nodeAttrs.closest,
|
||||
source_column: nodeAttrs.source_column,
|
||||
group: nodeAttrs.target_column && nodeAttrs.source_column,
|
||||
target_source_column: nodeAttrs.target_column
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs, columnOptions) {
|
||||
formAttrs.target_column = formAttrs.target_source_column;
|
||||
|
||||
if (!formAttrs.group) {
|
||||
formAttrs = _.omit(formAttrs, ['source_column', 'target_column']);
|
||||
}
|
||||
|
||||
formAttrs = _.omit(formAttrs, ['target_source_column', 'group']);
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
module.exports = {
|
||||
type: 'line-to-single-point',
|
||||
label: _t('editor.layers.analysis-form.line-to-single-point'),
|
||||
|
||||
parametersDataFields: 'source,type,destination_longitude,destination_latitude',
|
||||
parametersDataSchema: 'destination_longitude,destination_latitude',
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
return {
|
||||
type: 'line-to-single-point',
|
||||
destination_longitude: nodeAttrs.destination_longitude,
|
||||
destination_latitude: nodeAttrs.destination_latitude
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs, columnOptions) {
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var template = require('./data-observatory-measure.tpl');
|
||||
var DataObservartoryMetadata = require('builder/editor/layers/layer-content-views/analyses/data-observatory-metadata');
|
||||
var Utils = require('builder/helpers/utils');
|
||||
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'));
|
||||
|
||||
this.doMetadata = new DataObservartoryMetadata(null, {
|
||||
configModel: this._configModel,
|
||||
geometryType: null
|
||||
});
|
||||
|
||||
// check if the geometry type is available, if not wait until it's finished
|
||||
if (this.nodeDefModel.queryGeometryModel.isFetched()) {
|
||||
this._fetchDOMetadata();
|
||||
} else {
|
||||
this.nodeDefModel.queryGeometryModel.once('change:status', this._fetchDOMetadata, this);
|
||||
}
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_formatAttrs: function (formAttrs) {
|
||||
formAttrs.final_column = Utils.sanitizeString(formAttrs.final_column);
|
||||
return BaseAnalysisFormModel.prototype._formatAttrs.call(this, formAttrs);
|
||||
},
|
||||
|
||||
_fetchDOMetadata: function () {
|
||||
var simpleGeometryType = this.nodeDefModel.queryGeometryModel.get('simple_geom') || 'point';
|
||||
this.doMetadata.setGeometryType(simpleGeometryType);
|
||||
this.listenTo(this.doMetadata, 'sync', this._setSchema);
|
||||
this.on('change:area', this._areaChanged, this);
|
||||
this.on('change:measurement', this._measurementChanged, this);
|
||||
this.on('change:segments', this._setSegment, this);
|
||||
this.doMetadata.fetch();
|
||||
},
|
||||
|
||||
_areaChanged: function () {
|
||||
this.set({ measurement: '', segments: '' }, { silent: true });
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_measurementChanged: function () {
|
||||
this.set({ segments: '' }, { silent: true });
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_normalizeSegment: function (s) {
|
||||
return s.replace(/\./g, '_', /\-/g, '_');
|
||||
},
|
||||
|
||||
_setSegment: function () {
|
||||
var s = this.doMetadata.segment(this.get('area'), this.get('measurement'), this.get('segments'));
|
||||
if (s) {
|
||||
this.set('segment_name', s);
|
||||
}
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
var areas = this.doMetadata.areas();
|
||||
var schema = {
|
||||
source: this._primarySourceSchemaItem(),
|
||||
area: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.data-observatory-measurement-area'),
|
||||
// remove Global for the moment since it only includes Boundaries
|
||||
options: areas.length === 0 ? _t('editor.layers.analysis-form.loading') : areas.filter(function (f) { return f !== 'Global'; }).sort(),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: {
|
||||
disabled: areas.length === 0
|
||||
}
|
||||
},
|
||||
final_column: {
|
||||
title: _t('editor.layers.analysis-form.data-observatory-measurement-column'),
|
||||
type: 'Text',
|
||||
help: _t('editor.layers.analysis-form.data-observatory-measurement-column-help'),
|
||||
validators: ['required']
|
||||
},
|
||||
measurement: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.data-observatory-measurement-measurement'),
|
||||
options: this.doMetadata.measurement(this.get('area')).sort(),
|
||||
dialogMode: DialogConstants.Mode.FLOAT
|
||||
},
|
||||
segments: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.data-observatory-measurement-segments'),
|
||||
options: this.doMetadata.columns(this.get('area'), this.get('measurement')).sort(),
|
||||
dialogMode: DialogConstants.Mode.FLOAT
|
||||
}
|
||||
};
|
||||
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, schema);
|
||||
}
|
||||
});
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="source,final_column">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('analyses.data-observatory-measure.title') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.data-observatory-measurement-desc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="area,measurement,segments">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.parameters') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.layers.analysis-form.data-observatory-measurement-refine') %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
<div class="ListItem ListItem--fixed Measurement">
|
||||
<div data-editor class="u-grow">
|
||||
</div>
|
||||
<button class="ListItem-deleteButton" type="button" data-action="remove">
|
||||
<svg width="8px" height="8px" viewBox="0 0 8 8" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<polygon fill="#1181FB" points="4 3.435128 0.564872 0 0 0.564872 3.435128 4 0 7.435128 0.564872 8 4 4.564872 7.435128 8 8 7.435128 4.564872 4 8 0.564872 7.435128 0"></polygon>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
<div class="u-grow">
|
||||
<div data-items class="Measurements">
|
||||
|
||||
</div>
|
||||
<div class="u-flex u-justifyEnd DataObservatory-addMeasure">
|
||||
<button class="CDB-Text CDB-Size-small u-actionTextColor u-upperCase" type="button" data-action="add">
|
||||
<%- _t('editor.layers.analysis-form.data-observatory.list.add') %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
var _ = require('underscore');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var template = require('./data-observatory-multiple-measure.tpl');
|
||||
var templateList = require('./data-observatory-multiple-measure-list.tpl');
|
||||
var templateItem = require('./data-observatory-multiple-measure-list-item.tpl');
|
||||
var NestedMeasurement = require('./data-observatory-multiple-nested-measure-model');
|
||||
var DataObservatoryRegions = require('builder/data/data-observatory/regions-collection');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var camshaftReference = require('builder/data/camshaft-reference');
|
||||
var itemRegionListTemplate = require('./data-observatory-multiple-region-list-item.tpl');
|
||||
|
||||
var DENOMINATORS_BLACKLIST = ['', 'area'];
|
||||
|
||||
var EMPTY_AREA = ' ';
|
||||
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
defaults: {
|
||||
'area': EMPTY_AREA
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'));
|
||||
|
||||
this.regions = new DataObservatoryRegions(null, {
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: this.nodeDefModel
|
||||
});
|
||||
|
||||
this._initBinds();
|
||||
|
||||
this._setSchema = this._setSchema.bind(this);
|
||||
this._setSchema();
|
||||
|
||||
this.regions.fetch();
|
||||
},
|
||||
|
||||
parse: function (attributes) {
|
||||
var ATTRIBUTES = ['numerators', 'column_names', 'denominators', 'numerator_timespans', 'geom_ids', 'normalizations'];
|
||||
|
||||
var numerators = attributes.numerators;
|
||||
var column_names = attributes.column_names;
|
||||
var denominators = attributes.denominators;
|
||||
var normalizations = attributes.normalizations;
|
||||
var timespans = attributes.numerator_timespans;
|
||||
var geom_ids = attributes.geom_ids;
|
||||
|
||||
var measurements = _.reduce(numerators, function (memo, numerator, index) {
|
||||
memo.push({
|
||||
segment_name: numerator,
|
||||
column_name: column_names[index],
|
||||
normalize: denominators[index],
|
||||
timespan: timespans[index],
|
||||
boundaries: geom_ids[index],
|
||||
normalization: normalizations[index]
|
||||
});
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
return _.extend(_.omit(attributes, ATTRIBUTES), {
|
||||
measurements: measurements
|
||||
});
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
var fields = [];
|
||||
var data = {
|
||||
fields: '',
|
||||
hasArea: this.get('area') !== EMPTY_AREA
|
||||
};
|
||||
|
||||
// Don't show measurements until user selects a region
|
||||
if (this.get('area')) {
|
||||
fields.push('measurements');
|
||||
}
|
||||
|
||||
data.fields = fields.join(',');
|
||||
return data;
|
||||
},
|
||||
|
||||
_formatAttrs: function (formAttrs) {
|
||||
var numerators = this._getNumerators();
|
||||
var columnNames = this._getColumnNames();
|
||||
var normalizers = this._getDenominators();
|
||||
var normalization = this._getNormalization();
|
||||
var timespans = this._getTimespan();
|
||||
var boundaries = this._getBoundaries();
|
||||
|
||||
var attributes = _.extend(
|
||||
{},
|
||||
_.omit(formAttrs, 'measurements'),
|
||||
{
|
||||
numerators: numerators,
|
||||
column_names: columnNames,
|
||||
denominators: normalizers,
|
||||
normalizations: normalization,
|
||||
numerator_timespans: timespans,
|
||||
geom_ids: boundaries
|
||||
}
|
||||
);
|
||||
|
||||
return _.omit(camshaftReference.parse(attributes), 'persisted');
|
||||
},
|
||||
|
||||
validate: function () {
|
||||
var source = this.get('source');
|
||||
var type = this.get('type');
|
||||
var region = this.get('area');
|
||||
var numerators = this._getNumerators();
|
||||
var columnNames = this._getColumnNames();
|
||||
var normalizers = this._getDenominators();
|
||||
var normalizations = this._getNormalization();
|
||||
var timespans = this._getTimespan();
|
||||
var boundaries = this._getBoundaries();
|
||||
|
||||
var errors = _.extend(
|
||||
{},
|
||||
this._validRegion(region),
|
||||
this._validateMatchSegmentsColumnNames(numerators, columnNames),
|
||||
this._isCollectionValid(numerators),
|
||||
this._isCollectionValid(columnNames),
|
||||
this._areUniqueMeasurements(columnNames),
|
||||
camshaftReference.validate({
|
||||
source: source,
|
||||
type: type,
|
||||
numerators: numerators,
|
||||
column_names: columnNames,
|
||||
denominators: normalizers,
|
||||
normalizations: normalizations,
|
||||
numerator_timespans: timespans,
|
||||
geom_ids: boundaries
|
||||
}),
|
||||
this._formValidationErrors
|
||||
);
|
||||
|
||||
if (!_.isEmpty(errors)) {
|
||||
return errors;
|
||||
}
|
||||
},
|
||||
|
||||
// DRY
|
||||
_getNumerators: function () {
|
||||
var measurements = this.get('measurements');
|
||||
return _.map(measurements, function (measurement) {
|
||||
return measurement.segment_name;
|
||||
});
|
||||
},
|
||||
|
||||
_getTimespan: function () {
|
||||
var measurements = this.get('measurements');
|
||||
return _.map(measurements, function (measurement) {
|
||||
return measurement.timespan;
|
||||
});
|
||||
},
|
||||
|
||||
_getBoundaries: function () {
|
||||
var measurements = this.get('measurements');
|
||||
return _.map(measurements, function (measurement) {
|
||||
return measurement.boundaries;
|
||||
});
|
||||
},
|
||||
|
||||
_getNormalization: function () {
|
||||
var measurements = this.get('measurements');
|
||||
return _.map(measurements, function (measurement) {
|
||||
return measurement.normalization;
|
||||
});
|
||||
},
|
||||
|
||||
_getDenominators: function () {
|
||||
var measurements = this.get('measurements');
|
||||
return _.map(measurements, function (measurement) {
|
||||
var normalize = measurement.normalize;
|
||||
// We are inserting area option manually, so we need to filter that value for the backend
|
||||
normalize = (DENOMINATORS_BLACKLIST.indexOf(normalize) >= 0) ? null : normalize;
|
||||
return normalize;
|
||||
});
|
||||
},
|
||||
|
||||
_getColumnNames: function () {
|
||||
var measurements = this.get('measurements');
|
||||
return _.map(measurements, function (measurement) {
|
||||
return measurement.column_name;
|
||||
});
|
||||
},
|
||||
|
||||
_validRegion: function (region) {
|
||||
if (region == null) {
|
||||
return {
|
||||
error: true,
|
||||
message: _t('analyses.data-observatory-measure.errors.mandatory-region')
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_validateMatchSegmentsColumnNames: function (segments, columnNames) {
|
||||
if (segments.length !== 0 && columnNames.length !== 0 && segments.length !== columnNames.length) {
|
||||
return {
|
||||
error: true,
|
||||
message: _t('analyses.data-observatory-measure.errors.column-name-mismatch')
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_isCollectionValid: function (collection) {
|
||||
var allFilled = _.every(collection, function (item) {
|
||||
return item && item !== '';
|
||||
});
|
||||
|
||||
if (!allFilled) {
|
||||
return {
|
||||
error: true,
|
||||
message: _t('analyses.data-observatory-measure.errors.invalid-selection')
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_areUniqueMeasurements: function (columnNames) {
|
||||
var uniques = _.unique(columnNames);
|
||||
if (!_.isEqual(uniques, columnNames)) {
|
||||
return {
|
||||
error: true,
|
||||
message: _t('analyses.data-observatory-measure.errors.measurement-twice')
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.on('change:area', this._setSchema, this);
|
||||
},
|
||||
|
||||
_getRegionOptions: function () {
|
||||
var regions = this.regions;
|
||||
var state = regions.getState();
|
||||
if (state === 'error') {
|
||||
return _t('editor.layers.analysis-form.data-observatory.region.error');
|
||||
} else if (state === 'fetching') {
|
||||
return _t('editor.layers.analysis-form.loading');
|
||||
} else {
|
||||
// remove Global for the moment since it only includes Boundaries
|
||||
return regions.filter(function (f) {
|
||||
var matches = /^Global/.test(f.get('label'));
|
||||
return !matches;
|
||||
}).sort();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
var schema = {
|
||||
source: this._primarySourceSchemaItem(_t('editor.layers.analysis-form.data-observatory.source.label')),
|
||||
area: {
|
||||
type: 'SelectPlaceholder',
|
||||
title: _t('editor.layers.analysis-form.data-observatory.region.label'),
|
||||
collection: this.regions,
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
loading: this.regions.stateModel.get('state') === 'fetching',
|
||||
itemListTemplate: itemRegionListTemplate,
|
||||
validators: ['required'],
|
||||
placeholder: _t('editor.layers.analysis-form.data-observatory.region.placeholder'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.data-observatory.region.search-placeholder'),
|
||||
forcePlaceholder: this.get('area') === EMPTY_AREA
|
||||
},
|
||||
measurements: {
|
||||
type: 'List',
|
||||
itemType: 'MeasurementModel',
|
||||
model: NestedMeasurement,
|
||||
listTemplate: templateList,
|
||||
itemTemplate: templateItem,
|
||||
title: '',
|
||||
editorAttrs: {
|
||||
configModel: this._configModel,
|
||||
regions: this.regions,
|
||||
nodeDefModel: this.nodeDefModel,
|
||||
maxItems: 10,
|
||||
notAddItemByDefault: true
|
||||
},
|
||||
validators: [ function (value, formValues) {
|
||||
var hasError = value.segment_name == null || value.segment_name === '';
|
||||
if (hasError) {
|
||||
return {
|
||||
error: true,
|
||||
message: _t('analyses.data-observatory-measure.errors.measurement-required')
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, schema);
|
||||
}
|
||||
|
||||
});
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="source,area">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.data-observatory.header.title') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.data-observatory.header.description') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="Editor-HeaderInfo <%- hasArea ? '' : 'is-disabled' %>">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="<%- fields %>">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.data-observatory.parameters.title') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.layers.analysis-form.data-observatory.parameters.description') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+403
@@ -0,0 +1,403 @@
|
||||
var $ = require('jquery');
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
var DataObservatoryNormalize = require('builder/data/data-observatory/normalize-collection');
|
||||
var DataObservatoryTimespan = require('builder/data/data-observatory/timespan-collection');
|
||||
var DataObservatoryBoundaries = require('builder/data/data-observatory/boundaries-collection');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var MeasurementModel = require('builder/data/data-observatory/measurement-model');
|
||||
var DataObservatoryColumnName = require('builder/data/data-observatory/column-name');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'regions',
|
||||
'form',
|
||||
'configModel',
|
||||
'nodeDefModel'
|
||||
];
|
||||
|
||||
var DENOMINATORS_BLACKLIST = ['', 'area'];
|
||||
|
||||
var wrap = function (value) {
|
||||
var result = "'" + value + "'";
|
||||
return result;
|
||||
};
|
||||
|
||||
var braces = function (value) {
|
||||
return "'{" + value + "}'";
|
||||
};
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
segment_name: null,
|
||||
normalization: 'prenormalized',
|
||||
normalize: '',
|
||||
timespan: null,
|
||||
boundaries: null,
|
||||
column_name: ''
|
||||
},
|
||||
|
||||
initialize: function (attrs, options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
var fetchOptions = {
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: this._nodeDefModel
|
||||
};
|
||||
|
||||
this.measurement = new MeasurementModel({}, fetchOptions);
|
||||
|
||||
this.normalize = new DataObservatoryNormalize(null, fetchOptions);
|
||||
this.timespan = new DataObservatoryTimespan(null, fetchOptions);
|
||||
this.boundaries = new DataObservatoryBoundaries(null, fetchOptions);
|
||||
this.columnName = new DataObservatoryColumnName(fetchOptions);
|
||||
|
||||
this._fetchMeasurement = this._fetchMeasurement.bind(this);
|
||||
this._syncMeasurement = this._syncMeasurement.bind(this);
|
||||
this._fetchNormalize = this._fetchNormalize.bind(this);
|
||||
this._syncNormalize = this._syncNormalize.bind(this);
|
||||
this._fetchTimespan = this._fetchTimespan.bind(this);
|
||||
this._syncTimespan = this._syncTimespan.bind(this);
|
||||
this._fetchBoundaries = this._fetchBoundaries.bind(this);
|
||||
this._syncBoundaries = this._syncBoundaries.bind(this);
|
||||
this._fetchNewColumn = this._fetchNewColumn.bind(this);
|
||||
this._columnNameSuccess = this._columnNameSuccess.bind(this);
|
||||
this._setSchema = this._setSchema.bind(this);
|
||||
|
||||
if (this.get('normalization') === 'area' && !this.get('normalize')) {
|
||||
this.set({ normalize: 'area' });
|
||||
}
|
||||
|
||||
this._initBinds();
|
||||
this._setSchema();
|
||||
|
||||
// If measurement exists, it means the region changed
|
||||
if (this.get('segment_name') != null) {
|
||||
this._regionChanged();
|
||||
}
|
||||
|
||||
this._initialBoundaryValue = this.get('boundaries');
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this.measurement, 'change:state', this._setSchema);
|
||||
this.listenTo(this.normalize.stateModel, 'change:state', this._setSchema);
|
||||
this.listenTo(this.timespan.stateModel, 'change:state', this._setSchema);
|
||||
this.listenTo(this.boundaries.stateModel, 'change:state', this._setSchema);
|
||||
|
||||
this.on('change:segment_name', this._measurementChanged, this);
|
||||
this.on('change:normalize', this._normalizeChanged, this);
|
||||
this.on('change:timespan', this._timestampChanged, this);
|
||||
},
|
||||
|
||||
_cleanColumnName: function () {
|
||||
// Avoiding more killing events!
|
||||
this.attributes.column_name = '';
|
||||
},
|
||||
|
||||
_regionChanged: function () {
|
||||
this._cleanColumnName();
|
||||
|
||||
this._fetchMeasurement()
|
||||
.then(this._syncMeasurement)
|
||||
.then(this._fetchNormalize)
|
||||
.then(this._syncNormalize)
|
||||
.then(this._fetchTimespan)
|
||||
.then(this._syncTimespan)
|
||||
.then(this._fetchBoundaries)
|
||||
.then(this._syncBoundaries)
|
||||
.then(this._fetchNewColumn);
|
||||
},
|
||||
|
||||
_measurementChanged: function () {
|
||||
this._cleanColumnName();
|
||||
|
||||
this._fetchNormalize()
|
||||
.then(this._syncNormalize)
|
||||
.then(this._fetchTimespan)
|
||||
.then(this._syncTimespan)
|
||||
.then(this._fetchBoundaries)
|
||||
.then(this._syncBoundaries)
|
||||
.then(this._fetchNewColumn);
|
||||
},
|
||||
|
||||
_normalizeChanged: function () {
|
||||
this._cleanColumnName();
|
||||
|
||||
this._setNormalization();
|
||||
this._fetchTimespan()
|
||||
.then(this._syncTimespan)
|
||||
.then(this._fetchBoundaries)
|
||||
.then(this._syncBoundaries)
|
||||
.then(this._fetchNewColumn);
|
||||
},
|
||||
|
||||
_timestampChanged: function () {
|
||||
this._cleanColumnName();
|
||||
|
||||
this._fetchBoundaries()
|
||||
.then(this._syncBoundaries)
|
||||
.then(this._fetchNewColumn);
|
||||
},
|
||||
|
||||
_fetchMeasurement: function () {
|
||||
var area = this._form.model.get('area');
|
||||
var fetchOptions = {
|
||||
area: area && braces(area),
|
||||
measurement: braces(this.get('segment_name'))
|
||||
};
|
||||
return this.measurement.fetch(fetchOptions);
|
||||
},
|
||||
|
||||
_fetchNormalize: function () {
|
||||
var fetchOptions = this._getUserSelection();
|
||||
return this.normalize.fetch(fetchOptions);
|
||||
},
|
||||
|
||||
_syncMeasurement: function () {
|
||||
var deferred = $.Deferred();
|
||||
var defaults = this.defaults;
|
||||
|
||||
// if measurement has no value, break the chain and restore the measurement
|
||||
if (!this.measurement.getValue()) {
|
||||
deferred.reject();
|
||||
// we don't want to trigger more listeners that do more fetch
|
||||
this.set(defaults, {silent: true});
|
||||
this._setSchema();
|
||||
this._form.trigger('change');
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
return deferred.promise();
|
||||
},
|
||||
|
||||
_syncNormalize: function () {
|
||||
var deferred = $.Deferred();
|
||||
var measurement = this.measurement;
|
||||
var normalize = this.get('normalize');
|
||||
var selected = this.normalize.setSelected(normalize);
|
||||
var areaAllowed = (measurement && measurement.get('type') === 'Numeric' && measurement.get('aggregate') === 'sum');
|
||||
|
||||
if (!selected && normalize !== 'area' || !selected && normalize === 'area' && !areaAllowed) {
|
||||
this.set({ normalize: '' }, { silent: true });
|
||||
this._setSchema();
|
||||
}
|
||||
|
||||
this._setNormalization();
|
||||
deferred.resolve();
|
||||
return deferred.promise();
|
||||
},
|
||||
|
||||
_fetchTimespan: function () {
|
||||
var fetchOptions = this._getUserSelection();
|
||||
return this.timespan.fetch(fetchOptions);
|
||||
},
|
||||
|
||||
_syncTimespan: function () {
|
||||
var deferred = $.Deferred();
|
||||
var timespan = this.get('timespan');
|
||||
var selected = this.timespan.setSelected(timespan);
|
||||
|
||||
if (!selected) {
|
||||
// We don't need setSchema here, because the time span is a controlled component
|
||||
// this means we pass the collection as attribute and the editor render itself when changes
|
||||
this.timespan.selectFirstAsDefault();
|
||||
selected = this.timespan.getSelectedItem();
|
||||
if (selected) {
|
||||
timespan = selected.getValue();
|
||||
this.set({ timespan: timespan }, { silent: true });
|
||||
}
|
||||
}
|
||||
|
||||
deferred.resolve();
|
||||
return deferred.promise();
|
||||
},
|
||||
|
||||
_fetchBoundaries: function () {
|
||||
var fetchOptions = this._getUserSelection();
|
||||
return this.boundaries.fetch(fetchOptions);
|
||||
},
|
||||
|
||||
_syncBoundaries: function () {
|
||||
var deferred = $.Deferred();
|
||||
var hasInitialBoundaryValue = this._initialBoundaryValue != null;
|
||||
var selected = hasInitialBoundaryValue && this.boundaries.setSelected(this._initialBoundaryValue);
|
||||
|
||||
if (!selected) {
|
||||
this.set({ boundaries: null }, { silent: true });
|
||||
this._setSchema();
|
||||
}
|
||||
|
||||
this._initialBoundaryValue = null;
|
||||
deferred.resolve();
|
||||
return deferred.promise();
|
||||
},
|
||||
|
||||
_fetchNewColumn: function () {
|
||||
var columnOptions = this._getColumnNameOptions();
|
||||
this.columnName.fetch(columnOptions);
|
||||
},
|
||||
|
||||
_columnNameSuccess: function (data) {
|
||||
var rows;
|
||||
var metadata;
|
||||
|
||||
rows = data.rows;
|
||||
if (!rows || rows.length === 0) return;
|
||||
|
||||
metadata = rows[0].obs_getmeta;
|
||||
if (!metadata || metadata.length === 0) return;
|
||||
|
||||
var suggestedName = metadata[0].suggested_name;
|
||||
|
||||
this.set({ column_name: suggestedName });
|
||||
this._setSchema();
|
||||
this._form.trigger('change');
|
||||
},
|
||||
|
||||
_setNormalization: function () {
|
||||
var denom = this.get('normalize');
|
||||
denom = denom === '' ? null : denom;
|
||||
var normalization = 'prenormalized';
|
||||
|
||||
if (denom) {
|
||||
normalization = (denom === 'area') ? 'area' : 'denominated';
|
||||
}
|
||||
|
||||
this.set('normalization', normalization);
|
||||
},
|
||||
|
||||
_getColumnNameOptions: function () {
|
||||
var normalize = this.get('normalize') || null;
|
||||
if (normalize !== null) {
|
||||
normalize = (DENOMINATORS_BLACKLIST.indexOf(normalize) >= 0) ? null : normalize;
|
||||
}
|
||||
var normalization = this.get('normalization');
|
||||
|
||||
var defaults = {
|
||||
numer_id: null,
|
||||
denom_id: null,
|
||||
numer_timespan: null
|
||||
};
|
||||
|
||||
var columnOptions = _.defaults({
|
||||
numer_id: this.get('segment_name'),
|
||||
denom_id: normalize,
|
||||
numer_timespan: this.get('timespan') || null,
|
||||
normalization: normalization
|
||||
}, defaults);
|
||||
|
||||
return _.extend(
|
||||
{
|
||||
success: this._columnNameSuccess
|
||||
},
|
||||
_.pick(columnOptions, _.identity)
|
||||
);
|
||||
},
|
||||
|
||||
_getNormalizeOptions: function () {
|
||||
var measurement = this.measurement;
|
||||
var options = this.normalize.models;
|
||||
|
||||
if (measurement && measurement.get('type') === 'Numeric' && measurement.get('aggregate') === 'sum') {
|
||||
options = options.concat([{
|
||||
label: _t('analyses.data-observatory-measure.area'),
|
||||
val: 'area'
|
||||
}]);
|
||||
}
|
||||
|
||||
return options;
|
||||
},
|
||||
|
||||
_getUserSelection: function () {
|
||||
var normalize = this.get('normalize');
|
||||
// We are inserting area option manually, so we need to filter that value for the backend
|
||||
normalize = (DENOMINATORS_BLACKLIST.indexOf(normalize) >= 0) ? null : wrap(normalize);
|
||||
|
||||
var timespan = this.get('timespan');
|
||||
timespan = timespan != null ? wrap(timespan) : null;
|
||||
|
||||
var region = wrap(this._form.model.get('area'));
|
||||
var measurement = wrap(this.get('segment_name'));
|
||||
|
||||
return {
|
||||
region: region,
|
||||
measurement: measurement,
|
||||
denom_id: normalize,
|
||||
timespan: timespan
|
||||
};
|
||||
},
|
||||
|
||||
_getInitialBoundaryValue: function () {
|
||||
return this.get('boundaries') || this.boundaries.getPreSelectedBoundaryValue();
|
||||
},
|
||||
|
||||
_setSchema: function () {
|
||||
var normalizeOptions = this._getNormalizeOptions();
|
||||
var noMeasurement = this.get('segment_name') == null;
|
||||
var isDisabled = noMeasurement || normalizeOptions.length === 0;
|
||||
var schema = {
|
||||
segment_name: {
|
||||
type: 'DataObservatoryDropdown',
|
||||
title: _t('editor.layers.analysis-form.data-observatory.measurements.label'),
|
||||
editorAttrs: {
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: this._nodeDefModel,
|
||||
model: this._form.model,
|
||||
placeholder: _t('editor.layers.analysis-form.data-observatory.measurements.placeholder'),
|
||||
measurementModel: this.measurement,
|
||||
loading: this.measurement.get('state') === 'fetching'
|
||||
},
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
region: this._form.model.get('area'),
|
||||
validators: ['required']
|
||||
},
|
||||
normalize: {
|
||||
type: 'EnablerEditor',
|
||||
title: '',
|
||||
label: _t('editor.layers.analysis-form.data-observatory.normalize.label'),
|
||||
isDisabled: isDisabled,
|
||||
editor: {
|
||||
type: 'Select',
|
||||
options: normalizeOptions,
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: {
|
||||
disabled: isDisabled,
|
||||
placeholder: _t('editor.layers.analysis-form.data-observatory.normalize.placeholder'),
|
||||
disabledPlaceholder: _t('editor.layers.analysis-form.data-observatory.normalize.disabled-placeholder'),
|
||||
showSearch: false,
|
||||
showLabel: false,
|
||||
loading: this.normalize.stateModel.get('state') === 'fetching'
|
||||
}
|
||||
}
|
||||
},
|
||||
timespan: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.data-observatory.timespan.label'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
isDisabled: noMeasurement,
|
||||
collection: this.timespan,
|
||||
editorAttrs: {
|
||||
placeholder: _t('editor.layers.analysis-form.data-observatory.timespan.placeholder')
|
||||
}
|
||||
},
|
||||
boundaries: {
|
||||
type: 'Slider',
|
||||
title: _t('editor.layers.analysis-form.data-observatory.boundaries.label'),
|
||||
values: this.boundaries.getValues(),
|
||||
labels: this.boundaries.getLabels(),
|
||||
value: this._getInitialBoundaryValue(),
|
||||
initial: 'highest'
|
||||
},
|
||||
column_name: {
|
||||
type: 'Hidden'
|
||||
},
|
||||
normalization: {
|
||||
type: 'Hidden'
|
||||
}
|
||||
};
|
||||
|
||||
this.schema = schema;
|
||||
this.trigger('updateSchema', this.schema);
|
||||
}
|
||||
});
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<button type="button" class="CDB-ListDecoration-itemLink u-actionTextColor
|
||||
<% if (isSelected) { %> is-selected <% } %>
|
||||
" title="<%- name %>">
|
||||
<div class="u-bSpace"><%- name %> (<%- options.measurements %>)</div>
|
||||
</button>
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
var _ = require('underscore');
|
||||
var cdb = require('internal-carto.js');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var availableFunctionsQuery = require('./available-functions.tpl');
|
||||
var template = require('./deprecated-sql-function.tpl');
|
||||
var track = require('builder/helpers/errorTracking.js');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'configModel'
|
||||
];
|
||||
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
initialize: function (attrs, opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
this._SQL = new cdb.SQL({
|
||||
user: this._configModel.get('user_name'),
|
||||
sql_api_template: this._configModel.get('sql_api_template'),
|
||||
api_key: this._configModel.get('api_key')
|
||||
});
|
||||
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.set('fetchStatus', 'unfetched');
|
||||
this._functionsInfo = [];
|
||||
|
||||
this._setSchema();
|
||||
this._initBinds();
|
||||
this._fetchAvailableFunctions();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this, 'change:fetchStatus', this._onFetchStatusChanged);
|
||||
this.listenTo(this, 'change:function_name', this._onFunctionSelected);
|
||||
this.listenTo(this._analysisSourceOptionsModel, 'change:fetching', this._onSourceOptionsFetched);
|
||||
},
|
||||
|
||||
validate: function (attrs, options) {
|
||||
this._serializeFunctionArgs();
|
||||
|
||||
return BaseAnalysisFormModel.prototype.validate.call(this, attrs, options);
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
var fields = [];
|
||||
var data = {
|
||||
fields: ''
|
||||
};
|
||||
var selectedFunction = this._getSelectedFunction();
|
||||
if (selectedFunction) {
|
||||
if (selectedFunction.hasSecondaryNode) {
|
||||
fields.push('secondary_source');
|
||||
}
|
||||
|
||||
_.reduce(selectedFunction.params, function (memo, value, key) {
|
||||
memo.push(key);
|
||||
return memo;
|
||||
}, fields, this);
|
||||
}
|
||||
data.fields = fields.join(',');
|
||||
|
||||
return data;
|
||||
},
|
||||
|
||||
_buildSchema: function () {
|
||||
var schema = {};
|
||||
var selectedFunction = this._getSelectedFunction();
|
||||
|
||||
// Source
|
||||
schema.primary_source = this._primarySourceSchemaItem(_t('editor.layers.analysis-form.deprecated-sql-function.input'));
|
||||
|
||||
// Function
|
||||
schema.function_name = {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.deprecated-sql-function.function'),
|
||||
options: this._parseFunctionOptions(),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: this._getSelectFunctionAttrs()
|
||||
};
|
||||
|
||||
if (selectedFunction) {
|
||||
if (selectedFunction.hasSecondaryNode) {
|
||||
schema.secondary_source = {
|
||||
type: 'NodeDataset',
|
||||
title: _t('editor.layers.analysis-form.deprecated-sql-function.target'),
|
||||
options: this._getSourceOptionsForSource({
|
||||
sourceAttrName: 'secondary_source'
|
||||
}),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: {
|
||||
disabled: this._isSourceDisabled('secondary_source')
|
||||
},
|
||||
validators: ['required']
|
||||
};
|
||||
}
|
||||
|
||||
_.each(selectedFunction.params, function (value, key) {
|
||||
this._addParamToSchema(key, selectedFunction.params[key], schema);
|
||||
}, this);
|
||||
}
|
||||
|
||||
return schema;
|
||||
},
|
||||
|
||||
_setSchema: function () {
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, this._buildSchema());
|
||||
},
|
||||
|
||||
_addParamToSchema: function (key, type, schema) {
|
||||
var item = null;
|
||||
var initialValue = null;
|
||||
|
||||
switch (type) {
|
||||
case 'string': {
|
||||
item = {
|
||||
type: 'Text',
|
||||
title: key,
|
||||
validators: ['required']
|
||||
};
|
||||
initialValue = '';
|
||||
break;
|
||||
}
|
||||
case 'number': {
|
||||
item = {
|
||||
type: 'Number',
|
||||
title: key,
|
||||
showSlider: false,
|
||||
validators: ['required']
|
||||
};
|
||||
initialValue = 0;
|
||||
break;
|
||||
}
|
||||
case 'boolean': {
|
||||
item = {
|
||||
type: 'Radio',
|
||||
title: key,
|
||||
options: [
|
||||
{val: 'true', label: 'true'},
|
||||
{val: 'false', label: 'false'}
|
||||
]
|
||||
};
|
||||
initialValue = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (item) {
|
||||
schema[key] = item;
|
||||
if (!this.has(key)) {
|
||||
this.set(key, initialValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_onSourceOptionsFetched: function () {
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_onFetchStatusChanged: function () {
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_onFunctionSelected: function () {
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_getSelectedFunction: function () {
|
||||
var functionName = this.get('function_name');
|
||||
if (functionName) {
|
||||
return _.first(_.filter(this._functionsInfo, function (info) {
|
||||
return info.functionName === functionName;
|
||||
}, this));
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
_fetchAvailableFunctions: function () {
|
||||
var query = availableFunctionsQuery();
|
||||
this.set('fetchStatus', 'fetching');
|
||||
|
||||
this._SQL.execute(query, null, {
|
||||
success: function (metadata) {
|
||||
this._functionsInfo = this._parseFunctionsMetadata(metadata);
|
||||
this.set('fetchStatus', 'fetched');
|
||||
}.bind(this),
|
||||
error: function (errors) {
|
||||
this.set('fetchStatus', 'error');
|
||||
if (errors && errors.responseJSON) {
|
||||
track(errors.responseJSON);
|
||||
}
|
||||
}.bind(this)
|
||||
});
|
||||
},
|
||||
|
||||
_parseFunctionsMetadata: function (metadata) {
|
||||
if (!metadata || !metadata.rows) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return _.map(metadata.rows, function (data) {
|
||||
var info = {};
|
||||
info.functionName = this._capitalizeFunctionName(data.fn_name);
|
||||
info.hasSecondaryNode = data.has_secondary_node;
|
||||
info.params = {};
|
||||
for (var i = 0; i < data.params_names.length; i++) {
|
||||
info.params[data.params_names[i]] = data.params_types[i];
|
||||
}
|
||||
return info;
|
||||
}, this);
|
||||
},
|
||||
|
||||
_capitalizeFunctionName: function (name) {
|
||||
var depExtRegex = /^dep_ext/i;
|
||||
return name && name.replace(depExtRegex, 'DEP_EXT');
|
||||
},
|
||||
|
||||
_getSelectFunctionAttrs: function () {
|
||||
var fetchStatus = this.get('fetchStatus');
|
||||
var status = {
|
||||
disabled: true
|
||||
};
|
||||
|
||||
switch (fetchStatus) {
|
||||
case 'unfetched':
|
||||
break;
|
||||
case 'fetching':
|
||||
status = {
|
||||
disabled: true,
|
||||
loading: true
|
||||
};
|
||||
break;
|
||||
case 'fetched': {
|
||||
status = {};
|
||||
if (this._functionsInfo.length > 0) {
|
||||
status.placeholder = _t('editor.layers.analysis-form.deprecated-sql-function.choose-function-small');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'error': {
|
||||
status = {
|
||||
disabled: true,
|
||||
placeholder: 'error'
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
},
|
||||
|
||||
_parseFunctionOptions: function () {
|
||||
return _.map(this._functionsInfo, function (info) {
|
||||
return {
|
||||
val: info.functionName,
|
||||
label: info.functionName
|
||||
};
|
||||
}, this);
|
||||
},
|
||||
|
||||
_serializeFunctionArgs: function () {
|
||||
var values = [];
|
||||
var selectedFunction = this._getSelectedFunction();
|
||||
if (selectedFunction) {
|
||||
values = _.reduce(selectedFunction.params, function (memo, value, key) {
|
||||
memo.push(this.get(key));
|
||||
return memo;
|
||||
}, [], this);
|
||||
|
||||
this.set('function_args', values);
|
||||
}
|
||||
},
|
||||
|
||||
_isPrimarySource: function (sourceAttrName) {
|
||||
return sourceAttrName === this.get('source');
|
||||
},
|
||||
|
||||
_isSourceDisabled: function (sourceAttrName) {
|
||||
return this._isPrimarySource(sourceAttrName) || this._isFetchingOptions();
|
||||
}
|
||||
});
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="primary_source,function_name">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.deprecated-sql-function.title') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.layers.analysis-form.deprecated-sql-function.choose-function') %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if (fields) { %>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="<%= fields %>">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.deprecated-sql-function.params') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--m"><%- _t('editor.layers.analysis-form.deprecated-sql-function.define-params') %></p>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
</form>
|
||||
+174
@@ -0,0 +1,174 @@
|
||||
var _ = require('underscore');
|
||||
var camshaftReference = require('builder/data/camshaft-reference');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var ARRAY_SPLIT_COMBO = ',';
|
||||
|
||||
/**
|
||||
* A fallback form model in case the type is not supported (yet).
|
||||
*/
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this.listenTo(this._analysisSourceOptionsModel, 'change:fetching', this._updateSchema);
|
||||
|
||||
this._updateSchema();
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return undefined;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {};
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._formatAttrs}
|
||||
*/
|
||||
_formatAttrs: function () {
|
||||
var attrs = BaseAnalysisFormModel.prototype._formatAttrs.apply(this, arguments);
|
||||
|
||||
var params = camshaftReference.paramsForType(this.get('type'));
|
||||
for (var name in params) {
|
||||
var param = params[name];
|
||||
if (param.type === 'array') {
|
||||
if (!_.isString(attrs[name]) || attrs[name].trim() === '') {
|
||||
attrs[name] = null;
|
||||
} else {
|
||||
attrs[name] = attrs[name]
|
||||
.split(ARRAY_SPLIT_COMBO)
|
||||
.map(function (val) {
|
||||
return val.trim();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return attrs;
|
||||
},
|
||||
|
||||
_updateSchema: function () {
|
||||
var schema = {
|
||||
type: {
|
||||
type: 'Text',
|
||||
title: 'Type',
|
||||
editorAttrs: {disabled: true}
|
||||
}
|
||||
};
|
||||
var params = camshaftReference.paramsForType(this.get('type'));
|
||||
|
||||
var sourceCount = _.reduce(Object.keys(params), function (memo, name) {
|
||||
if (params[name].type === 'node') {
|
||||
memo++;
|
||||
}
|
||||
return memo;
|
||||
}, 0);
|
||||
|
||||
Object.keys(params)
|
||||
.forEach(function (name) {
|
||||
var param = params[name];
|
||||
var label = name + ' (' + param.type + ')';
|
||||
var validators = [];
|
||||
var isRequired = !param.optional;
|
||||
|
||||
if (isRequired) {
|
||||
label += '*';
|
||||
validators.push('required');
|
||||
}
|
||||
|
||||
switch (param.type) {
|
||||
case 'node':
|
||||
schema[name] = sourceCount === 1
|
||||
? {
|
||||
type: 'Select',
|
||||
text: label,
|
||||
options: [ this.get('source') ],
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: { disabled: true }
|
||||
}
|
||||
: {
|
||||
type: 'Select',
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
title: label,
|
||||
options: this._getSourceOptionsForSource({
|
||||
sourceAttrName: 'target',
|
||||
ignorePrimarySource: true
|
||||
})
|
||||
};
|
||||
break;
|
||||
case 'string':
|
||||
var fieldDef;
|
||||
|
||||
// If is meant to represent a column try to get columns if possible
|
||||
if (/column/.test(name) && sourceCount === 1) {
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'));
|
||||
if (nodeDefModel && nodeDefModel.querySchemaModel.columnsCollection.length > 0) {
|
||||
fieldDef = {
|
||||
type: 'Text',
|
||||
title: label,
|
||||
validators: validators
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (!fieldDef) {
|
||||
fieldDef = {
|
||||
type: 'Text',
|
||||
title: label,
|
||||
validators: validators
|
||||
};
|
||||
}
|
||||
|
||||
schema[name] = fieldDef;
|
||||
break;
|
||||
case 'enum':
|
||||
schema[name] = {
|
||||
type: 'Select',
|
||||
title: label,
|
||||
options: param.values.map(function (val) {
|
||||
return {
|
||||
val: val,
|
||||
label: val
|
||||
};
|
||||
}),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
validators: validators
|
||||
};
|
||||
break;
|
||||
case 'number':
|
||||
schema[name] = {
|
||||
type: 'Text',
|
||||
label: label,
|
||||
validators: validators
|
||||
};
|
||||
break;
|
||||
case 'boolean':
|
||||
schema[name] = {
|
||||
type: 'Radio',
|
||||
text: label,
|
||||
options: [
|
||||
{val: 'true', label: 'true'},
|
||||
{val: 'false', label: 'false'}
|
||||
],
|
||||
validators: validators
|
||||
};
|
||||
break;
|
||||
case 'array':
|
||||
schema[name] = {
|
||||
type: 'Text',
|
||||
title: label,
|
||||
validators: validators,
|
||||
help: 'Separate values by "' + ARRAY_SPLIT_COMBO + '"'
|
||||
};
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}, this);
|
||||
|
||||
this._setSchema(schema);
|
||||
}
|
||||
});
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var template = require('./filter-by-node-column.tpl');
|
||||
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');
|
||||
|
||||
/**
|
||||
* Form model for a centroid and weighted-centroid analysis
|
||||
* It has a rather complicated schema, that depends on several data points and state.
|
||||
*/
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'));
|
||||
this._columnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: nodeDefModel
|
||||
});
|
||||
|
||||
this._filterColumnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this.listenTo(this._columnOptions, 'columnsFetched', this._setSchema);
|
||||
this.listenTo(this._filterColumnOptions, 'columnsFetched', this._setSchema);
|
||||
this.listenTo(this._analysisSourceOptionsModel, 'change:fetching', this._setSchema);
|
||||
this.on('change:filter_source', this._onFilterSourceChange, this);
|
||||
this._onFilterSourceChange();
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_onFilterSourceChange: function () {
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('filter_source'));
|
||||
if (nodeDefModel) {
|
||||
this._filterColumnOptions.setNode(nodeDefModel);
|
||||
this._setSchema();
|
||||
}
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {
|
||||
hasFilterSource: this.get('filter_source') !== undefined
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
var schema = {
|
||||
source: this._primarySourceSchemaItem(),
|
||||
filter_source: {
|
||||
type: 'NodeDataset',
|
||||
title: _t('editor.layers.analysis-form.linked-layer'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: {
|
||||
disabled: this._isFetchingOptions()
|
||||
},
|
||||
options: this._getSourceOptionsForSource({
|
||||
sourceAttrName: 'filter_source',
|
||||
ignorePrimarySource: true,
|
||||
onlyNodes: true
|
||||
})
|
||||
},
|
||||
column: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.source-column'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
options: this._columnOptions.all()
|
||||
},
|
||||
filter_column: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.filter-column'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
validators: ['required', this._validateFilterColumn.bind(this)],
|
||||
options: this._filterColumnOptions.all()
|
||||
}
|
||||
};
|
||||
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, schema);
|
||||
},
|
||||
|
||||
_validateFilterColumn: function (value, formValues) {
|
||||
var filterColumnValue = formValues.filter_column;
|
||||
var columnValue = formValues.column;
|
||||
|
||||
// if we don't have both fields we don't validate
|
||||
if (filterColumnValue === void 0 || columnValue === void 0) return null;
|
||||
|
||||
var error = {
|
||||
message: _t('editor.layers.analysis-form.should-match')
|
||||
};
|
||||
|
||||
var filterColumn = this._filterColumnOptions.findColumn(filterColumnValue);
|
||||
var filterColumnType = filterColumn && filterColumn.type;
|
||||
|
||||
var column = this._columnOptions.findColumn(columnValue);
|
||||
var columnType = column && column.type;
|
||||
|
||||
if (filterColumnType === columnType) {
|
||||
return null; // types match
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
});
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="source,filter_source">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.select-layer') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.link-layer-desc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo <%- hasFilterSource ? '' : 'is-disabled' %>">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="column,filter_column">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.key-columns') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--xl"><%- _t('editor.layers.analysis-form.key-columns-desc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
var PARAMS_FOR_IS_EQUAL = 'kind,text';
|
||||
|
||||
module.exports = {
|
||||
type: 'filter-category',
|
||||
label: 'Filter category',
|
||||
parametersDataFields: 'text,accept_reject',
|
||||
filterDataFields: 'accept,reject',
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
var formAttrs = _.extend({}, nodeAttrs);
|
||||
var kind = nodeAttrs.kind;
|
||||
var text;
|
||||
|
||||
formAttrs.type = 'filter-category';
|
||||
formAttrs.column = nodeAttrs.column || '';
|
||||
|
||||
if (nodeAttrs.accept) {
|
||||
text = nodeAttrs.accept.join(',');
|
||||
} else if (nodeAttrs.reject) {
|
||||
text = nodeAttrs.reject.join(',');
|
||||
}
|
||||
|
||||
if (this._isFromBooleanKind(kind)) {
|
||||
text = text === 'null' ? null : text === 'true';
|
||||
}
|
||||
|
||||
formAttrs.text = text;
|
||||
|
||||
formAttrs.accept_reject = (nodeAttrs.accept || !(nodeAttrs.accept || nodeAttrs.reject)) ? 'accept' : 'reject';
|
||||
|
||||
return formAttrs;
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs) {
|
||||
var attrs = ['id', 'type', 'source', 'column'];
|
||||
var kind = formAttrs.kind;
|
||||
var isFromIsEqualToKind = this._isFromIsEqualToKind(kind);
|
||||
var isFromBooleanKind = this._isFromBooleanKind(kind);
|
||||
var text;
|
||||
|
||||
if (isFromIsEqualToKind) {
|
||||
text = [+formAttrs.text];
|
||||
} else if (isFromBooleanKind) {
|
||||
text = [formAttrs.text];
|
||||
} else if (formAttrs.text) {
|
||||
text = formAttrs.text.split(',');
|
||||
}
|
||||
|
||||
if (formAttrs.accept_reject === 'accept' || isFromIsEqualToKind) {
|
||||
formAttrs.accept = text;
|
||||
} else if (formAttrs.accept_reject === 'reject') {
|
||||
formAttrs.reject = text;
|
||||
}
|
||||
|
||||
if (isFromIsEqualToKind || isFromBooleanKind) {
|
||||
return _.pick(formAttrs, attrs.concat(['kind', formAttrs.accept_reject]));
|
||||
} else {
|
||||
return _.pick(formAttrs, attrs.concat(formAttrs.accept_reject));
|
||||
}
|
||||
},
|
||||
|
||||
_isFromBooleanKind: function (kind) {
|
||||
return kind === 'is-boolean';
|
||||
},
|
||||
|
||||
_isFromIsEqualToKind: function (kind) {
|
||||
return kind === 'is-equal-to';
|
||||
},
|
||||
|
||||
getParameters: function (kind) {
|
||||
var params = this.parametersDataFields;
|
||||
|
||||
if (kind === 'is-equal-to') {
|
||||
params = PARAMS_FOR_IS_EQUAL;
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
};
|
||||
+340
@@ -0,0 +1,340 @@
|
||||
var _ = require('underscore');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var template = require('./filter-form.tpl');
|
||||
var ColumnData = require('builder/editor/layers/layer-content-views/analyses/column-data');
|
||||
var ColumnRowData = require('builder/data/column-row-data');
|
||||
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');
|
||||
|
||||
var FILTER_TYPES = require('./filter-types');
|
||||
var TYPE_TO_META_MAP = {};
|
||||
|
||||
FILTER_TYPES.map(function (d) {
|
||||
TYPE_TO_META_MAP[d.type] = d;
|
||||
});
|
||||
|
||||
var FILTER_RANGE_KINDS = [
|
||||
{ val: 'between', label: _t('editor.layers.filter-options.between') },
|
||||
{ val: 'is-equal-to', label: _t('editor.layers.filter-options.is-equal-to') },
|
||||
{ val: 'is-greater-than', label: _t('editor.layers.filter-options.is-greater-than') },
|
||||
{ val: 'is-greater-or-equal-than', label: _t('editor.layers.filter-options.is-greater-or-equal-than') },
|
||||
{ val: 'is-less-than', label: _t('editor.layers.filter-options.is-less-than') },
|
||||
{ val: 'is-less-or-equal-than', label: _t('editor.layers.filter-options.is-less-or-equal-than') }
|
||||
];
|
||||
|
||||
var LESS_GREATER_KINDS = ['is-greater-or-equal-than', 'is-less-or-equal-than', 'is-less-than', 'is-less-than'];
|
||||
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
|
||||
defaults: {
|
||||
accept_reject: 'accept'
|
||||
},
|
||||
|
||||
parse: function (attrs) {
|
||||
return _.defaults(
|
||||
_.pick(attrs, 'id', 'source', 'column'), // maintain default attrs
|
||||
this._typeDef(attrs.type).parse(attrs)
|
||||
);
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'));
|
||||
|
||||
this._columnData = new ColumnData({
|
||||
column: this.get('column'),
|
||||
type: this._getSelectedColumnType()
|
||||
}, {
|
||||
nodeDefModel: this._nodeDefModel,
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this._columnRowData = new ColumnRowData({
|
||||
column: this.get('column')
|
||||
}, {
|
||||
nodeDefModel: this._nodeDefModel,
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this._columnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: this._nodeDefModel
|
||||
});
|
||||
|
||||
this.on('change:kind change:column', this._updateSchema, this);
|
||||
this.on('change:histogram_stats', this._setSchema, this);
|
||||
|
||||
this.listenTo(this._columnOptions, 'columnsFetched', this._setSchema);
|
||||
this.listenTo(this._columnRowData, 'columnsFetched', this._setSchema);
|
||||
this.listenTo(this._columnData, 'columnsFetched', this._storeHistogramStats);
|
||||
|
||||
this._columnData.fetch();
|
||||
this._columnRowData.fetch();
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {
|
||||
column: this.get('column'),
|
||||
histogram_stats: this.get('histogram_stats'),
|
||||
parametersDataFields: this._typeDef().getParameters(this.get('kind'), this.get('column'))
|
||||
};
|
||||
},
|
||||
|
||||
_formatAttrs: function (formAttrs) {
|
||||
var column = this.get('column');
|
||||
var customFormattedFormAttrs = this._typeDef().formatAttrs(formAttrs, column);
|
||||
return BaseAnalysisFormModel.prototype._formatAttrs.call(this, customFormattedFormAttrs);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel.updateNodeDefinition}
|
||||
*/
|
||||
updateNodeDefinition: function (nodeDefModel) {
|
||||
var attrs = this._formatAttrs(this.attributes);
|
||||
nodeDefModel.clear({ silent: true });
|
||||
nodeDefModel.set(attrs);
|
||||
},
|
||||
|
||||
_getInputOptions: function () {
|
||||
return _.map(this._columnRowData.getRows(), function (d) {
|
||||
return { label: d, val: d };
|
||||
});
|
||||
},
|
||||
|
||||
_setSchema: function () {
|
||||
var requiredValidators = [{
|
||||
type: 'requiredBoolean'
|
||||
}];
|
||||
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, this._filterSchemaFieldsByType({
|
||||
source: this._primarySourceSchemaItem(_t('editor.layers.analysis-form.base-layer')),
|
||||
column: {
|
||||
type: 'Select',
|
||||
text: _t('editor.layers.analysis-form.column'),
|
||||
options: this._columnOptions.filterByType(['string', 'number', 'boolean']),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
validators: ['required']
|
||||
},
|
||||
kind: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.filter'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
options: this._getKindOptions(),
|
||||
editorAttrs: {
|
||||
showSearch: false
|
||||
}
|
||||
},
|
||||
greater_than_or_equal: {
|
||||
type: 'Text',
|
||||
title: this._getMinOrEqualLabel(),
|
||||
validators: ['required'],
|
||||
editorAttrs: {
|
||||
placeholder: _t('editor.layers.analysis-form.write-min-or-equal-value')
|
||||
}
|
||||
},
|
||||
less_than_or_equal: {
|
||||
type: 'Text',
|
||||
title: this._getMaxOrEqualLabel(),
|
||||
validators: ['required'],
|
||||
editorAttrs: {
|
||||
placeholder: _t('editor.layers.analysis-form.write-max-or-equal-value')
|
||||
}
|
||||
},
|
||||
greater_than: {
|
||||
type: 'Text',
|
||||
title: this._getMinLabel(),
|
||||
validators: ['required'],
|
||||
editorAttrs: {
|
||||
placeholder: _t('editor.layers.analysis-form.write-min-value')
|
||||
}
|
||||
},
|
||||
less_than: {
|
||||
type: 'Text',
|
||||
title: this._getMaxLabel(),
|
||||
validators: ['required'],
|
||||
editorAttrs: {
|
||||
placeholder: _t('editor.layers.analysis-form.write-max-value')
|
||||
}
|
||||
},
|
||||
text: {
|
||||
type: this._getInputType(),
|
||||
title: _t('editor.layers.analysis-form.value'),
|
||||
validators: this._isBoolean() ? requiredValidators : ['required'],
|
||||
options: this._getInputOptions(),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: {
|
||||
column: 'column',
|
||||
nodeDefModel: this._nodeDefModel,
|
||||
configModel: this._configModel,
|
||||
placeholder: _t('editor.layers.analysis-form.select-value')
|
||||
}
|
||||
},
|
||||
accept_reject: {
|
||||
type: 'Radio',
|
||||
title: _t('editor.layers.analysis-form.results'),
|
||||
options: [
|
||||
{ label: _t('editor.layers.analysis-form.show'), val: 'accept' },
|
||||
{ label: _t('editor.layers.analysis-form.hide'), val: 'reject' }
|
||||
],
|
||||
validators: ['required']
|
||||
}
|
||||
}));
|
||||
|
||||
this._generateHistogram();
|
||||
this._generateHistogramStats();
|
||||
},
|
||||
|
||||
_getInputType: function () {
|
||||
var rows = this._columnRowData.getRows();
|
||||
return rows && rows.length ? 'LazySelect' : 'Text';
|
||||
},
|
||||
|
||||
_getMinOrEqualLabel: function () {
|
||||
return _.contains(LESS_GREATER_KINDS, this.get('kind')) ? _t('editor.layers.analysis-form.value') : _t('editor.layers.analysis-form.min-or-equal');
|
||||
},
|
||||
|
||||
_getMaxOrEqualLabel: function () {
|
||||
return _.contains(LESS_GREATER_KINDS, this.get('kind')) ? _t('editor.layers.analysis-form.value') : _t('editor.layers.analysis-form.max-or-equal');
|
||||
},
|
||||
|
||||
_getMinLabel: function () {
|
||||
return _.contains(LESS_GREATER_KINDS, this.get('kind')) ? _t('editor.layers.analysis-form.value') : _t('editor.layers.analysis-form.min');
|
||||
},
|
||||
|
||||
_getMaxLabel: function () {
|
||||
return _.contains(LESS_GREATER_KINDS, this.get('kind')) ? _t('editor.layers.analysis-form.value') : _t('editor.layers.analysis-form.max');
|
||||
},
|
||||
|
||||
_getSourceColumns: function () {
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'));
|
||||
|
||||
var sourceColumns = nodeDefModel.querySchemaModel.columnsCollection.map(function (columnModel) {
|
||||
var columnName = columnModel.get('name');
|
||||
return {
|
||||
val: columnName,
|
||||
label: columnName,
|
||||
type: columnModel.get('type')
|
||||
};
|
||||
});
|
||||
|
||||
return sourceColumns;
|
||||
},
|
||||
|
||||
_filterSchemaFieldsByType: function (schema) {
|
||||
// Always include the source and column fields in addition to the type-specific fields
|
||||
var kind = this.get('kind');
|
||||
var column = this.get('column');
|
||||
var fields = ['source', 'column'];
|
||||
var parameters = this._typeDef().getParameters(kind, column);
|
||||
if (parameters !== '') {
|
||||
fields = fields.concat(parameters.split(','));
|
||||
}
|
||||
return _.pick(schema, fields);
|
||||
},
|
||||
|
||||
_updateSchema: function () {
|
||||
var columnType = this._getSelectedColumnType();
|
||||
this._columnData.set({
|
||||
column: this.get('column'),
|
||||
type: columnType
|
||||
});
|
||||
this._columnRowData.set('column', this.get('column'));
|
||||
|
||||
this._setType();
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_getSelectedColumnType: function () {
|
||||
var columns = this._getSourceColumns();
|
||||
var columnType = null;
|
||||
|
||||
for (var i in columns) {
|
||||
if (columns[i]['label'] === this.get('column')) {
|
||||
columnType = columns[i]['type'];
|
||||
}
|
||||
}
|
||||
|
||||
return columnType;
|
||||
},
|
||||
|
||||
_getKindOptions: function () {
|
||||
if (this.get('type') !== 'filter-category' || this.get('kind') === 'is-equal-to') {
|
||||
return FILTER_RANGE_KINDS;
|
||||
}
|
||||
},
|
||||
|
||||
_setType: function () {
|
||||
var columnType = this._getSelectedColumnType();
|
||||
var kind = this.get('kind');
|
||||
var attrs = {
|
||||
type: 'filter-range'
|
||||
};
|
||||
|
||||
if (columnType === 'number' && kind === 'is-equal-to') {
|
||||
attrs.type = 'filter-category';
|
||||
attrs.accept_reject = 'accept';
|
||||
} else if (columnType === 'number') {
|
||||
attrs.kind = this._typeDef(attrs.type).getKind(kind);
|
||||
} else if (columnType === 'string') {
|
||||
attrs.kind = null;
|
||||
attrs.type = 'filter-category';
|
||||
} else if (columnType === 'boolean') {
|
||||
attrs.kind = 'is-boolean';
|
||||
attrs.type = 'filter-category';
|
||||
}
|
||||
|
||||
this.set(attrs);
|
||||
},
|
||||
|
||||
_isBoolean: function () {
|
||||
return this._getSelectedColumnType() === 'boolean';
|
||||
},
|
||||
|
||||
_getColumnType: function (columnName) {
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'));
|
||||
var column = nodeDefModel.querySchemaModel.columnsCollection.find(function (c) {
|
||||
return c.get('name') === columnName;
|
||||
}, this);
|
||||
|
||||
return column && column.get('type');
|
||||
},
|
||||
|
||||
_generateHistogramStats: function (data) {
|
||||
if (data) {
|
||||
this.trigger('onColData', this);
|
||||
}
|
||||
},
|
||||
|
||||
_storeHistogramStats: function (data) {
|
||||
if (data && this._getSelectedColumnType() === 'number') {
|
||||
this.set('histogram_stats', data.attributes);
|
||||
} else {
|
||||
this.set('histogram_stats', null);
|
||||
}
|
||||
},
|
||||
|
||||
_generateHistogram: function () {
|
||||
var type;
|
||||
var tableName = this._getSourceOption()[0].layerName;
|
||||
var columnName = this.get('column');
|
||||
|
||||
if (columnName) {
|
||||
type = this._getColumnType(columnName);
|
||||
}
|
||||
|
||||
if (tableName && columnName && type === 'number') {
|
||||
this.trigger('generateHistogram', { columnName: columnName, tableName: tableName });
|
||||
}
|
||||
},
|
||||
|
||||
_typeDef: function (type) {
|
||||
type = type || this.get('type');
|
||||
return TYPE_TO_META_MAP[type];
|
||||
}
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.select-column') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.to-filter-by') %></p>
|
||||
</div>
|
||||
<div class="u-tSpace-xl CDB-Text CDB-Fieldset">
|
||||
<p class="CDB-Legend u-upperCase u-iBlock CDB-Text is-semibold CDB-Size-small u-rSpace--m"><%- _t('editor.layers.analysis-form.base-layer') %></p>
|
||||
<div class="Editor-formInput u-ellipsis" data-editors="source"></div>
|
||||
</div>
|
||||
<div class="u-tSpace-xl CDB-Text CDB-Fieldset">
|
||||
<p class="CDB-Legend u-upperCase u-iBlock CDB-Text is-semibold CDB-Size-small u-rSpace--m"><%- _t('editor.layers.analysis-form.target-column') %></p>
|
||||
<div class="Editor-formInput" data-editors="column"></div>
|
||||
</div>
|
||||
<% if (histogram_stats) { %>
|
||||
<div class="u-tSpace-xl CDB-Text CDB-Fieldset ">
|
||||
<p class="CDB-Legend u-upperCase u-iBlock CDB-Text is-semibold CDB-Size-small u-rSpace--m"></p>
|
||||
<div class="Analysis-Histogram js-histogram">
|
||||
<ul class="Analysis-HistogramInfo u-flex CDB-Text CDB-Size-small u-secondaryTextColor u-upperCase">
|
||||
<li class="u-rSpace"><span class="js-min"></span> <%- _t('editor.layers.analysis-form.min') %></li>
|
||||
<li><span class="js-max"></span> <%- _t('editor.layers.analysis-form.max') %></li>
|
||||
</ul>
|
||||
<div class="Analysis-HistogramChart js-histogramChart"></div>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo <%- column ? '' : 'is-disabled' %>">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="<%- parametersDataFields %>">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.parameters') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--xl"><%- _t('editor.layers.analysis-form.parameters-description') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
var _ = require('underscore');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var template = require('./filter-intersection-form.tpl');
|
||||
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
/**
|
||||
* Filter points in polygon model
|
||||
*/
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._columnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this.listenTo(this._analysisSourceOptionsModel, 'change:fetching', this._setSchema);
|
||||
this.listenTo(this._columnOptions, 'columnsFetched', this._setSchema);
|
||||
|
||||
this.on('change:target', this._onChangeTarget, this);
|
||||
|
||||
this._setSchema();
|
||||
this._fetchColumns();
|
||||
},
|
||||
|
||||
_formatAttrs: function (formAttrs) {
|
||||
var customFormattedFormAttrs = _.pick(formAttrs, ['id', 'source', 'target', 'type']);
|
||||
return BaseAnalysisFormModel.prototype._formatAttrs.call(this, customFormattedFormAttrs);
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {};
|
||||
},
|
||||
|
||||
_filterSchemaFieldsByType: function (schema) {
|
||||
// Always include the source and target fields in addition to the type-specific fields
|
||||
return _.pick(schema, ['source', 'target', 'type']);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, this._filterSchemaFieldsByType({
|
||||
source: this._primarySourceSchemaItem(_t('editor.layers.analysis-form.base-layer')),
|
||||
target: {
|
||||
type: 'NodeDataset',
|
||||
title: _t('editor.layers.analysis-form.target'),
|
||||
options: this._getSourceOptionsForSource({
|
||||
sourceAttrName: 'target',
|
||||
requiredSimpleGeometryType: 'point'
|
||||
}),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
validators: ['required'],
|
||||
editorAttrs: {
|
||||
disabled: this._isSourceDisabled('target')
|
||||
}
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
_onChangeTarget: function () {
|
||||
this._analysisSourceOptionsModel.createSourceNodeUnlessExisting(this.get('target'));
|
||||
this._fetchColumns();
|
||||
},
|
||||
|
||||
_fetchColumns: function () {
|
||||
var target = this.get('target');
|
||||
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(target);
|
||||
|
||||
if (nodeDefModel) {
|
||||
this._columnOptions.setNode(nodeDefModel);
|
||||
} else if (target) {
|
||||
this._columnOptions.setDataset(target);
|
||||
}
|
||||
},
|
||||
|
||||
_isSourceDisabled: function (sourceAttrName) {
|
||||
return this._isPrimarySource(sourceAttrName) || this._isFetchingOptions();
|
||||
}
|
||||
});
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="source,target">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.select-your-points') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.target-layer-dataset') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
var PARAMETERS_FOR_KIND = {
|
||||
'between': 'kind,greater_than_or_equal,less_than_or_equal',
|
||||
'is-equal-to': 'kind,text',
|
||||
'is-less-than': 'kind,less_than',
|
||||
'is-less-or-equal-than': 'kind,less_than_or_equal',
|
||||
'is-greater-than': 'kind,greater_than',
|
||||
'is-greater-or-equal-than': 'kind,greater_than_or_equal'
|
||||
};
|
||||
|
||||
var DEFAULT_PARAMETERS = PARAMETERS_FOR_KIND['between'];
|
||||
|
||||
module.exports = {
|
||||
type: 'filter-range',
|
||||
label: 'Filter range', // TODO: localize?
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
var formAttrs = _.extend({}, nodeAttrs);
|
||||
|
||||
formAttrs.type = 'filter-range';
|
||||
formAttrs.kind = nodeAttrs.kind || 'between';
|
||||
formAttrs.column = nodeAttrs.column || '';
|
||||
formAttrs.greater_than_or_equal = nodeAttrs.greater_than_or_equal != null ? nodeAttrs.greater_than_or_equal : 0;
|
||||
formAttrs.less_than_or_equal = nodeAttrs.less_than_or_equal != null ? nodeAttrs.less_than_or_equal : 50;
|
||||
formAttrs.greater_than = nodeAttrs.greater_than != null ? nodeAttrs.greater_than : 0;
|
||||
formAttrs.less_than = nodeAttrs.less_than != null ? nodeAttrs.less_than : 50;
|
||||
|
||||
return formAttrs;
|
||||
},
|
||||
|
||||
getParameters: function (kind, column) {
|
||||
if (column) {
|
||||
kind = this.getKind(kind);
|
||||
return PARAMETERS_FOR_KIND[kind];
|
||||
}
|
||||
|
||||
return DEFAULT_PARAMETERS;
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs, column) {
|
||||
var kind = this.getKind(formAttrs.kind);
|
||||
|
||||
var attrs = ['id', 'type', 'source', 'column'];
|
||||
var parameters = this.getParameters(kind, column);
|
||||
if (parameters !== '') {
|
||||
attrs = attrs.concat(parameters.split(','));
|
||||
}
|
||||
|
||||
return _.pick(formAttrs, attrs);
|
||||
},
|
||||
|
||||
getKind: function (kind) {
|
||||
if (!kind || !_.contains(_.keys(PARAMETERS_FOR_KIND), kind)) {
|
||||
kind = 'between';
|
||||
}
|
||||
|
||||
return kind;
|
||||
}
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
module.exports = [
|
||||
require('./filter-category-model'),
|
||||
require('./filter-range-model')
|
||||
];
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
module.exports = {
|
||||
type: 'georeference-admin-region',
|
||||
label: _t('editor.layers.analysis-form.georeference.admin-region'),
|
||||
fieldAttrs: {
|
||||
admin_region_column: {
|
||||
showSearch: true
|
||||
},
|
||||
country: {
|
||||
showSearch: true,
|
||||
allowFreeTextInput: true
|
||||
}
|
||||
},
|
||||
|
||||
validatorFor: {
|
||||
admin_region_column: ['required']
|
||||
},
|
||||
|
||||
parametersDataFields: 'admin_region_column,country',
|
||||
|
||||
getParameters: function () {
|
||||
var params = this.parametersDataFields;
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
return {
|
||||
type: 'georeference-admin-region',
|
||||
admin_region_column: nodeAttrs.admin_region_column,
|
||||
country: nodeAttrs.country || nodeAttrs.country_column
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs, columnOptions) {
|
||||
if (formAttrs.country && columnOptions.findColumn(formAttrs.country, 'string')) {
|
||||
formAttrs.country_column = formAttrs.country;
|
||||
delete formAttrs.country;
|
||||
} else {
|
||||
delete formAttrs.country_column;
|
||||
}
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = {
|
||||
type: 'georeference-city',
|
||||
label: _t('editor.layers.analysis-form.georeference.city'),
|
||||
fieldAttrs: {
|
||||
city_column: {
|
||||
showSearch: true
|
||||
},
|
||||
admin_region: {
|
||||
showSearch: true,
|
||||
allowFreeTextInput: true
|
||||
},
|
||||
country: {
|
||||
showSearch: true,
|
||||
allowFreeTextInput: true
|
||||
}
|
||||
},
|
||||
|
||||
validatorFor: {
|
||||
city_column: ['required']
|
||||
},
|
||||
|
||||
parametersDataFields: 'city_column,admin_region,country',
|
||||
|
||||
getParameters: function () {
|
||||
var params = this.parametersDataFields;
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
return {
|
||||
type: 'georeference-city',
|
||||
city_column: nodeAttrs.city_column,
|
||||
admin_region: nodeAttrs.admin_region || nodeAttrs.admin_region_column,
|
||||
country: nodeAttrs.country || nodeAttrs.country_column
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs, columnOptions) {
|
||||
_.each(['country', 'admin_region'], function (attr) {
|
||||
if (formAttrs[attr] && columnOptions.findColumn(formAttrs[attr], 'string')) {
|
||||
formAttrs[attr + '_column'] = formAttrs[attr];
|
||||
delete formAttrs[attr];
|
||||
} else {
|
||||
delete formAttrs[attr + '_column'];
|
||||
}
|
||||
}, this);
|
||||
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
module.exports = {
|
||||
type: 'georeference-country',
|
||||
label: _t('editor.layers.analysis-form.georeference.country'),
|
||||
fieldAttrs: {
|
||||
country_column: {
|
||||
showSearch: true
|
||||
}
|
||||
},
|
||||
|
||||
validatorFor: {
|
||||
country_column: ['required']
|
||||
},
|
||||
|
||||
parametersDataFields: 'country_column',
|
||||
|
||||
getParameters: function () {
|
||||
var params = this.parametersDataFields;
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
return {
|
||||
type: 'georeference-country',
|
||||
country_column: nodeAttrs.country_column || nodeAttrs.country
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs) {
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
var _ = require('underscore');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var template = require('./georeference-form.tpl');
|
||||
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');
|
||||
var GEOREFERENCE_TYPES = require('./georeference-types');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
|
||||
var TYPE_TO_META_MAP = {};
|
||||
GEOREFERENCE_TYPES.map(function (d) {
|
||||
TYPE_TO_META_MAP[d.type] = d;
|
||||
});
|
||||
|
||||
var GEOREFERENCE_PLACEHOLDER = 'georeference-placeholder';
|
||||
|
||||
/**
|
||||
* Form model for a georeference analysis
|
||||
* It has a rather complicated schema, that depends on several data points and state.
|
||||
*/
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
parse: function (attrs) {
|
||||
return _.defaults(
|
||||
_.pick(attrs, 'id', 'source', 'context'), // maintain default attrs
|
||||
this._typeDef(attrs.type).parse(attrs)
|
||||
);
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._columnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'))
|
||||
});
|
||||
|
||||
this.listenTo(this._columnOptions, 'columnsFetched', this._setSchema);
|
||||
this.on('change:type change:context', this._onTypeChanged, this);
|
||||
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
updateNodeDefinition: function (nodeDefModel) {
|
||||
var attrs = this._formatAttrs(this.attributes);
|
||||
nodeDefModel.clear({silent: true});
|
||||
nodeDefModel.set(attrs);
|
||||
},
|
||||
|
||||
_formatAttrs: function (formAttrs) {
|
||||
var customFormattedFormAttrs = this._typeDef().formatAttrs(formAttrs, this._columnOptions);
|
||||
return BaseAnalysisFormModel.prototype._formatAttrs.call(this, customFormattedFormAttrs);
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {
|
||||
parametersDataFields: this._typeDef().getParameters(this.get('context')),
|
||||
hasType: this.get('type') !== GEOREFERENCE_PLACEHOLDER
|
||||
};
|
||||
},
|
||||
|
||||
_filterSchemaFieldsByType: function (schema) {
|
||||
// Always include the source field in addition to the type-specific fields
|
||||
// If the model has a context attribute it will be used to filter the fields
|
||||
var context = this.get('context');
|
||||
var fields = ['source', 'type'].concat(this._typeDef().getParameters(context).split(','));
|
||||
|
||||
return _.pick(schema, fields);
|
||||
},
|
||||
|
||||
validate: function () {
|
||||
// Return something so form can't be applied on the placeholder
|
||||
if (this.get('type') === GEOREFERENCE_PLACEHOLDER) return { type: 'required' };
|
||||
return BaseAnalysisFormModel.prototype.validate.call(this);
|
||||
},
|
||||
|
||||
/*
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
var hasPlaceholder = this.get('type') === GEOREFERENCE_PLACEHOLDER;
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, this._filterSchemaFieldsByType({
|
||||
type: {
|
||||
type: 'SelectPlaceholder',
|
||||
title: _t('editor.layers.analysis-form.type'),
|
||||
options: _.compact(
|
||||
GEOREFERENCE_TYPES.map(function (d) {
|
||||
if (!this._analyses.isAnalysisValidByType(d.type, { configModel: this._configModel })) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
hidden: d.type === GEOREFERENCE_PLACEHOLDER,
|
||||
val: d.type,
|
||||
label: d.label
|
||||
};
|
||||
}, this)
|
||||
),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
forcePlaceholder: hasPlaceholder,
|
||||
placeholder: _t('editor.layers.analysis-form.select-type-placeholder')
|
||||
},
|
||||
context: {
|
||||
type: 'Toggle',
|
||||
title: '',
|
||||
label: '',
|
||||
options: [
|
||||
{
|
||||
val: 'advance',
|
||||
label: _t('editor.layers.analysis-form.georeference.advance'),
|
||||
help: _t('editor.layers.analysis-form.georeference.street-address-help'),
|
||||
selected: false
|
||||
}, {
|
||||
val: 'normal',
|
||||
label: _t('editor.layers.analysis-form.georeference.normal'),
|
||||
selected: true
|
||||
}
|
||||
]
|
||||
},
|
||||
source: {
|
||||
type: 'NodeDataset',
|
||||
text: _t('editor.layers.analysis-form.base-layer'),
|
||||
options: this._getSourceOption(),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: { disabled: true }
|
||||
},
|
||||
latitude: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.latitude'),
|
||||
options: this._columnOptions.filterByType('number'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
validators: this._getFieldValidator('latitude'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-latitude'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.search-by-column-name')
|
||||
},
|
||||
longitude: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.longitude'),
|
||||
options: this._columnOptions.filterByType('number'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
validators: this._getFieldValidator('longitude'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-longitude'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.search-by-column-name')
|
||||
},
|
||||
admin_region: {
|
||||
type: 'EnablerEditor',
|
||||
title: '',
|
||||
label: _t('editor.layers.analysis-form.admin-region'),
|
||||
help: _t('editor.layers.analysis-form.georeference.admin-region-extended-help'),
|
||||
validators: this._getFieldValidator('admin_region'),
|
||||
editor: {
|
||||
type: 'Select',
|
||||
options: this._getColumnOptions('admin_region', 'string'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: this._getFieldAttrs('admin_region'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-admin'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.georeference.enter-region-name')
|
||||
}
|
||||
},
|
||||
admin_region_column: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.admin-region'),
|
||||
options: this._columnOptions.filterByType('string'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
help: _t('editor.layers.analysis-form.georeference.admin-region-help'),
|
||||
validators: this._getFieldValidator('admin_region'),
|
||||
editorAttrs: this._getFieldAttrs('admin_region'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-admin'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.search-by-column-name')
|
||||
},
|
||||
country_column: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.country'),
|
||||
options: this._columnOptions.filterByType('string'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
help: _t('editor.layers.analysis-form.georeference.country-help'),
|
||||
validators: this._getFieldValidator('country_column'),
|
||||
editorAttrs: this._getFieldAttrs('country_column'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-a-country'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.search-by-column-name')
|
||||
},
|
||||
city: {
|
||||
type: 'EnablerEditor',
|
||||
title: '',
|
||||
label: _t('editor.layers.analysis-form.city'),
|
||||
help: _t('editor.layers.analysis-form.georeference.city-extended-help'),
|
||||
validators: this._getFieldValidator('city'),
|
||||
editor: {
|
||||
type: 'Select',
|
||||
options: this._getColumnOptions('city', 'string'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: this._getFieldAttrs('city'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-city'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.georeference.enter-city-name')
|
||||
}
|
||||
},
|
||||
city_column: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.city'),
|
||||
options: this._columnOptions.filterByType('string'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
help: _t('editor.layers.analysis-form.georeference.city-help'),
|
||||
validators: this._getFieldValidator('city_column'),
|
||||
editorAttrs: this._getFieldAttrs('city_column'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-city'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.search-by-column-name')
|
||||
},
|
||||
postal_code_column: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.postal-code'),
|
||||
options: this._columnOptions.filterByType(['string', 'number']),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
help: _t('editor.layers.analysis-form.georeference.postal-code-help'),
|
||||
validators: this._getFieldValidator('postal_code_column'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-postal-code'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.search-by-column-name')
|
||||
},
|
||||
ip_address: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.georeference.ip-address-column'),
|
||||
options: this._columnOptions.filterByType('string'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
help: _t('editor.layers.analysis-form.georeference.ip-address-help'),
|
||||
validators: this._getFieldValidator('ip_address'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-ip'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.search-by-column-name')
|
||||
},
|
||||
street_address_template: {
|
||||
type: 'CodeEditor',
|
||||
title: '',
|
||||
label: '',
|
||||
tokens: this._getColumnHints(this._columnOptions.filterByType(['string', 'number'])),
|
||||
validators: this._getFieldValidator('street_address_template'),
|
||||
placeholder: '{{street_number}} {{street_name}}, {{postal_code}} {{country}}'
|
||||
},
|
||||
street_address_column: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.georeference.street-address-column'),
|
||||
options: this._columnOptions.filterByType('string'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
help: _t('editor.layers.analysis-form.georeference.street-address-column-help'),
|
||||
validators: this._getFieldValidator('street_address_column'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-street-address'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.search-by-column-name')
|
||||
},
|
||||
state: {
|
||||
type: 'EnablerEditor',
|
||||
title: '',
|
||||
label: _t('editor.layers.analysis-form.state'),
|
||||
help: _t('editor.layers.analysis-form.georeference.state-help'),
|
||||
validators: this._getFieldValidator('state'),
|
||||
editor: {
|
||||
type: 'Select',
|
||||
options: this._getColumnOptions('state', 'string'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: this._getFieldAttrs('state'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-state'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.georeference.enter-state-name')
|
||||
}
|
||||
},
|
||||
country: {
|
||||
type: 'EnablerEditor',
|
||||
title: '',
|
||||
label: _t('editor.layers.analysis-form.country'),
|
||||
help: _t('editor.layers.analysis-form.georeference.country-extended-help'),
|
||||
validators: this._getFieldValidator('country'),
|
||||
editor: {
|
||||
type: 'Select',
|
||||
options: this._getColumnOptions('country', 'string'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: this._getFieldAttrs('country'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-country'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.georeference.enter-country-name')
|
||||
}
|
||||
},
|
||||
postal_code_country: {
|
||||
type: 'Suggest',
|
||||
title: _t('editor.layers.analysis-form.country'),
|
||||
options: this._columnOptions.filterByType('string'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
help: _t('editor.layers.analysis-form.georeference.country-extended-help'),
|
||||
validators: this._getFieldValidator('postal_code_country'),
|
||||
editorAttrs: this._getFieldAttrs('postal_code_country'),
|
||||
placeholder: _t('editor.layers.analysis-form.georeference.select-country'),
|
||||
searchPlaceholder: _t('editor.layers.analysis-form.georeference.enter-country-name')
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
_onTypeChanged: function () {
|
||||
this._replaceAttrs();
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_replaceAttrs: function () {
|
||||
var attrs = this.parse(this.attributes);
|
||||
this.clear({silent: true});
|
||||
this.set({
|
||||
type: attrs.type,
|
||||
context: attrs.context
|
||||
}, { silent: true }); // re-set type to avoid change:type change:context event to trigger again
|
||||
this.set(attrs);
|
||||
},
|
||||
|
||||
_typeDef: function (type) {
|
||||
type = type || this.get('type');
|
||||
return TYPE_TO_META_MAP[type];
|
||||
},
|
||||
|
||||
_getFieldAttrs: function (fieldName) {
|
||||
return this._typeDef() && this._typeDef().fieldAttrs[fieldName];
|
||||
},
|
||||
|
||||
_getFieldValidator: function (fieldName) {
|
||||
return this._typeDef() && this._typeDef().validatorFor[fieldName] || null;
|
||||
},
|
||||
|
||||
_getColumnHints: function (columns) {
|
||||
if (!columns) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [{
|
||||
keywords: columns.map(function (column) { return column.val; }).join(' '),
|
||||
type: 'Column_name'
|
||||
}];
|
||||
},
|
||||
|
||||
_getColumnOptions: function (attr, attrType) {
|
||||
var opts = this._columnOptions.filterByType(attrType);
|
||||
|
||||
if (this._columnOptions.get('columnsFetched') && this.get(attr)) {
|
||||
var attrExists = false;
|
||||
var value = this.get(attr);
|
||||
|
||||
var customValue = {
|
||||
val: value,
|
||||
label: '"' + value + '"'
|
||||
};
|
||||
|
||||
if (opts && opts.length) {
|
||||
attrExists = _.find(opts, function (attr) {
|
||||
return attr.val === customValue.val;
|
||||
});
|
||||
}
|
||||
|
||||
if (!attrExists) {
|
||||
opts.push(customValue);
|
||||
opts = _.sortBy(opts, function (d) {
|
||||
return d.val;
|
||||
});
|
||||
}
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.select-type') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.georeference.description') %></p>
|
||||
</div>
|
||||
<div class="u-tSpace-xl CDB-Text CDB-Fieldset">
|
||||
<p class="CDB-Legend u-upperCase u-iBlock CDB-Text is-semibold CDB-Size-small u-rSpace--m"><%- _t('editor.layers.analysis-form.base-layer') %></p>
|
||||
<div class="Editor-formInput u-ellipsis" data-editors="source"></div>
|
||||
</div>
|
||||
<div class="u-tSpace-xl CDB-Text CDB-Fieldset">
|
||||
<p class="CDB-Legend u-upperCase u-iBlock CDB-Text is-semibold CDB-Size-small u-rSpace--m"><%- _t('editor.layers.analysis-form.data-type') %></p>
|
||||
<div class="Editor-formInput" data-editors="type"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo <%- hasType ? '' : 'is-disabled' %>">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="<%= parametersDataFields %>">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.parameters') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--xl"><%- _t('editor.layers.analysis-form.tune-analysis') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
module.exports = {
|
||||
type: 'georeference-ip-address',
|
||||
label: _t('editor.layers.analysis-form.georeference.ip-address'),
|
||||
fieldAttrs: {
|
||||
ip_address: {
|
||||
showSearch: true
|
||||
}
|
||||
},
|
||||
|
||||
validatorFor: {
|
||||
ip_address: ['required']
|
||||
},
|
||||
|
||||
parametersDataFields: 'ip_address',
|
||||
|
||||
getParameters: function () {
|
||||
var params = this.parametersDataFields;
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
return {
|
||||
type: 'georeference-ip-address',
|
||||
ip_address: nodeAttrs.ip_address
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs) {
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
module.exports = {
|
||||
type: 'georeference-long-lat',
|
||||
label: _t('editor.layers.analysis-form.georeference.long-lat'),
|
||||
parametersDataFields: 'latitude,longitude',
|
||||
fieldAttrs: {
|
||||
latitude: {
|
||||
showSearch: true
|
||||
},
|
||||
longitude: {
|
||||
showSearch: true
|
||||
}
|
||||
},
|
||||
|
||||
validatorFor: {
|
||||
latitudde: ['required'],
|
||||
longitude: ['required']
|
||||
},
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
return {
|
||||
type: 'georeference-long-lat',
|
||||
latitude: nodeAttrs.latitude,
|
||||
longitude: nodeAttrs.longitude
|
||||
};
|
||||
},
|
||||
|
||||
getParameters: function () {
|
||||
var params = this.parametersDataFields;
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs) {
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
module.exports = {
|
||||
type: 'georeference-placeholder',
|
||||
label: null,
|
||||
keyAttr: 'label',
|
||||
parametersDataFields: 'latitude,longitude',
|
||||
fieldAttrs: {
|
||||
latitude: {
|
||||
showSearch: true
|
||||
},
|
||||
longitude: {
|
||||
showSearch: true
|
||||
}
|
||||
},
|
||||
|
||||
validatorFor: {
|
||||
},
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
return {
|
||||
type: 'georeference-placeholder',
|
||||
latitude: null,
|
||||
longitude: null
|
||||
};
|
||||
},
|
||||
|
||||
getParameters: function () {
|
||||
var params = this.parametersDataFields;
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs) {
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
module.exports = {
|
||||
type: 'georeference-postal-code',
|
||||
label: _t('editor.layers.analysis-form.georeference.postal-code'),
|
||||
fieldAttrs: {
|
||||
postal_code_column: {
|
||||
showSearch: true
|
||||
},
|
||||
postal_code_country: {
|
||||
showSearch: true,
|
||||
allowFreeTextInput: true
|
||||
}
|
||||
},
|
||||
|
||||
validatorFor: {
|
||||
postal_code_column: ['required'],
|
||||
postal_code_country: ['required']
|
||||
},
|
||||
|
||||
parametersDataFields: 'postal_code_column,postal_code_country',
|
||||
|
||||
getParameters: function () {
|
||||
var params = this.parametersDataFields;
|
||||
|
||||
return params;
|
||||
},
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
return {
|
||||
type: 'georeference-postal-code',
|
||||
postal_code_column: nodeAttrs.postal_code_column || '',
|
||||
postal_code_country: nodeAttrs.postal_code_country || nodeAttrs.country_column || ''
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs, columnOptions) {
|
||||
if (formAttrs.postal_code_country && columnOptions.findColumn(formAttrs.postal_code_country, 'string')) {
|
||||
formAttrs.country_column = formAttrs.postal_code_country;
|
||||
delete formAttrs.postal_code_country;
|
||||
} else {
|
||||
formAttrs.country = formAttrs.postal_code_country;
|
||||
delete formAttrs.country_column;
|
||||
}
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
var PARAMETERS_FOR_CONTEXT = {
|
||||
'normal': 'street_address_column,context,city,state,country',
|
||||
'advance': 'street_address_template,context,city,state,country'
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
type: 'georeference-street-address',
|
||||
label: _t('editor.layers.analysis-form.georeference.street-address'),
|
||||
fieldAttrs: {
|
||||
street_address_column: {
|
||||
showSearch: true
|
||||
},
|
||||
city: {
|
||||
showSearch: true,
|
||||
allowFreeTextInput: true
|
||||
},
|
||||
state: {
|
||||
showSearch: true,
|
||||
allowFreeTextInput: true
|
||||
},
|
||||
country: {
|
||||
showSearch: true,
|
||||
allowFreeTextInput: true
|
||||
}
|
||||
},
|
||||
|
||||
validatorFor: {
|
||||
street_address_column: ['required'],
|
||||
street_address_template: ['required']
|
||||
},
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
var attrs = {
|
||||
type: 'georeference-street-address',
|
||||
city: nodeAttrs.city || nodeAttrs.city_column || '',
|
||||
state: nodeAttrs.state || nodeAttrs.state_column || '',
|
||||
country: nodeAttrs.country || nodeAttrs.country_column || ''
|
||||
};
|
||||
|
||||
if (nodeAttrs.context === 'advance') {
|
||||
attrs.street_address_template = nodeAttrs.street_address_column && this._wrapInBraces(nodeAttrs.street_address_column) || nodeAttrs.street_address_template || '';
|
||||
} else {
|
||||
attrs.street_address_column = nodeAttrs.street_address_column || '';
|
||||
}
|
||||
|
||||
return attrs;
|
||||
},
|
||||
|
||||
_wrapInBraces: function (fragments) {
|
||||
return ['{{', fragments, '}}'].join('');
|
||||
},
|
||||
|
||||
getParameters: function (context) {
|
||||
if (!context) {
|
||||
context = 'normal';
|
||||
}
|
||||
return PARAMETERS_FOR_CONTEXT[context];
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs, columnOptions) {
|
||||
_.each(['city', 'state', 'country'], function (attr) {
|
||||
if (formAttrs[attr] && columnOptions.findColumn(formAttrs[attr], 'string')) {
|
||||
formAttrs[attr + '_column'] = formAttrs[attr];
|
||||
delete formAttrs[attr];
|
||||
} else {
|
||||
delete formAttrs[attr + '_column'];
|
||||
}
|
||||
}, this);
|
||||
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
module.exports = [
|
||||
require('./georeference-placeholder'),
|
||||
require('./georeference-long-lat-model'),
|
||||
require('./georeference-city-model'),
|
||||
require('./georeference-country-model'),
|
||||
require('./georeference-admin-region-model'),
|
||||
require('./georeference-postal-code-model'),
|
||||
require('./georeference-ip-address-model'),
|
||||
require('./georeference-street-address-model')
|
||||
];
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
var _ = require('underscore');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var template = require('./group-points.tpl');
|
||||
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');
|
||||
|
||||
var ANALYSES_TYPES = require('./group-points-types');
|
||||
|
||||
var TYPE_TO_META_MAP = {};
|
||||
ANALYSES_TYPES.map(function (d) {
|
||||
TYPE_TO_META_MAP[d.type] = d;
|
||||
});
|
||||
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
parse: function (attrs) {
|
||||
return _.defaults(
|
||||
_.pick(attrs, 'id', 'source'), // maintain default attrs
|
||||
this._typeDef(attrs.type).parse(attrs)
|
||||
);
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._columnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'))
|
||||
});
|
||||
|
||||
this.listenTo(this._columnOptions, 'columnsFetched', this._setSchema);
|
||||
this.on('change:aggregate', this._updateAggregation, this);
|
||||
this.on('change:type', this._onTypeChanged, this);
|
||||
|
||||
this._updateAggregation();
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_updateAggregation: function () {
|
||||
var aggregate = this.get('aggregate');
|
||||
|
||||
if (aggregate === '') {
|
||||
this.set({aggregation: 'count', aggregation_column: ''});
|
||||
} else {
|
||||
this.set({aggregation_column: aggregate.attribute, aggregation: aggregate.operator});
|
||||
}
|
||||
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_onTypeChanged: function () {
|
||||
this._replaceAttrs();
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
_replaceAttrs: function () {
|
||||
var attrs = this.parse(this.attributes);
|
||||
this.clear({ silent: true });
|
||||
this.set('type', attrs.type, { silent: true }); // re-set type to avoid change:type event to trigger again
|
||||
this.set(_.extend(attrs, this.defaults));
|
||||
},
|
||||
|
||||
updateNodeDefinition: function (nodeDefModel) {
|
||||
var attrs = this._formatAttrs(this.attributes);
|
||||
nodeDefModel.clear({ silent: true });
|
||||
nodeDefModel.set(attrs);
|
||||
},
|
||||
|
||||
_formatAttrs: function (formAttrs) {
|
||||
var customFormattedFormAttrs = this._typeDef().formatAttrs(formAttrs, this._columnOptions);
|
||||
return BaseAnalysisFormModel.prototype._formatAttrs.call(this, customFormattedFormAttrs);
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {
|
||||
parametersDataFields: this._typeDef().parametersDataFields
|
||||
};
|
||||
},
|
||||
|
||||
_filterSchemaFieldsByType: function (schema) {
|
||||
// Always include the source field in addition to the type-specific fields
|
||||
var fields = ['source', 'type', 'aggregate'].concat(this._typeDef().parametersDataFields.split(','));
|
||||
return _.pick(schema, fields);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, this._filterSchemaFieldsByType({
|
||||
source: this._primarySourceSchemaItem(),
|
||||
type: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.method'),
|
||||
options: ANALYSES_TYPES.map(function (d) {
|
||||
return {
|
||||
val: d.type,
|
||||
label: d.label
|
||||
};
|
||||
}, this),
|
||||
dialogMode: DialogConstants.Mode.FLOAT
|
||||
},
|
||||
target_percentage: {
|
||||
type: 'Number',
|
||||
title: _t('editor.layers.analysis-form.target-percent'),
|
||||
help: _t('editor.layers.analysis-form.target-percent-help'),
|
||||
validators: ['required', {
|
||||
type: 'interval',
|
||||
min: 0,
|
||||
max: 100
|
||||
}]
|
||||
},
|
||||
allow_holes: {
|
||||
type: 'Radio',
|
||||
title: _t('editor.layers.analysis-form.allow-holes'),
|
||||
options: [
|
||||
{ val: false, label: _t('editor.layers.analysis-form.no') },
|
||||
{ val: true, label: _t('editor.layers.analysis-form.yes') }
|
||||
]
|
||||
},
|
||||
category_column: {
|
||||
type: 'EnablerEditor',
|
||||
title: '',
|
||||
label: _t('editor.layers.analysis-form.group-by'),
|
||||
editor: {
|
||||
type: 'Select',
|
||||
options: this._columnOptions.all(),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: {
|
||||
showLabel: false
|
||||
}
|
||||
}
|
||||
},
|
||||
aggregate: {
|
||||
type: 'Operators',
|
||||
title: _t('editor.layers.analysis-form.operation'),
|
||||
options: this._columnOptions.filterByType('number'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
_getColumnOptions: function (attr, attrType) {
|
||||
var opts = this._columnOptions.filterByType(attrType);
|
||||
|
||||
if (this._columnOptions.get('columnsFetched') && this.get(attr)) {
|
||||
var attrExists = false;
|
||||
var value = this.get(attr);
|
||||
|
||||
var customValue = {
|
||||
val: value,
|
||||
label: '"' + value + '"'
|
||||
};
|
||||
|
||||
if (opts && opts.length) {
|
||||
attrExists = _.find(opts, function (attr) {
|
||||
return attr.val === customValue.val;
|
||||
});
|
||||
}
|
||||
|
||||
if (!attrExists) {
|
||||
opts.push(customValue);
|
||||
opts = _.sortBy(opts, function (d) {
|
||||
return d.val;
|
||||
});
|
||||
}
|
||||
}
|
||||
return opts;
|
||||
},
|
||||
|
||||
_typeDef: function (type) {
|
||||
type = type || this.get('type');
|
||||
return TYPE_TO_META_MAP[type];
|
||||
}
|
||||
});
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
module.exports = [
|
||||
require('./group-points/convex-hull'),
|
||||
require('./group-points/concave-hull'),
|
||||
require('./group-points/bounding-circle'),
|
||||
require('./group-points/bounding-box')
|
||||
];
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="<%= parametersDataFields %>">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.parameters') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.parameters-description') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="aggregate">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.measure-by') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--xl"><%- _t('editor.layers.analysis-form.filter-aggregate') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = {
|
||||
type: 'bounding-box',
|
||||
label: _t('editor.layers.analysis-form.bounding-box'),
|
||||
parametersDataFields: 'source,type,category_column',
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
if (!nodeAttrs.aggregation_column) {
|
||||
nodeAttrs.aggregation_column = 'cartodb_id';
|
||||
}
|
||||
if (!nodeAttrs.aggregation) {
|
||||
nodeAttrs.aggregation = 'count';
|
||||
}
|
||||
var aggregate = {
|
||||
operator: nodeAttrs.aggregation,
|
||||
attribute: nodeAttrs.aggregation === 'count' ? '' : nodeAttrs.aggregation_column
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'bounding-box',
|
||||
category_column: nodeAttrs.category_column,
|
||||
aggregate: aggregate
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs) {
|
||||
formAttrs = _.omit(formAttrs, 'aggregate');
|
||||
|
||||
if (!formAttrs.aggregation_column) {
|
||||
formAttrs = _.omit(formAttrs, 'aggregation_column');
|
||||
}
|
||||
|
||||
if (!formAttrs.category_column) {
|
||||
formAttrs = _.omit(formAttrs, 'category_column');
|
||||
}
|
||||
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = {
|
||||
type: 'bounding-circle',
|
||||
label: _t('editor.layers.analysis-form.bounding-circle'),
|
||||
parametersDataFields: 'source,type,category_column',
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
if (!nodeAttrs.aggregation_column) {
|
||||
nodeAttrs.aggregation_column = 'cartodb_id';
|
||||
}
|
||||
if (!nodeAttrs.aggregation) {
|
||||
nodeAttrs.aggregation = 'count';
|
||||
}
|
||||
var aggregate = {
|
||||
operator: nodeAttrs.aggregation,
|
||||
attribute: nodeAttrs.aggregation === 'count' ? '' : nodeAttrs.aggregation_column
|
||||
};
|
||||
return {
|
||||
type: 'bounding-circle',
|
||||
category_column: nodeAttrs.category_column,
|
||||
aggregate: aggregate
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs) {
|
||||
formAttrs = _.omit(formAttrs, 'aggregate');
|
||||
|
||||
if (!formAttrs.aggregation_column) {
|
||||
formAttrs = _.omit(formAttrs, 'aggregation_column');
|
||||
}
|
||||
|
||||
if (!formAttrs.category_column) {
|
||||
formAttrs = _.omit(formAttrs, 'category_column');
|
||||
}
|
||||
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = {
|
||||
type: 'concave-hull',
|
||||
label: _t('editor.layers.analysis-form.concave-hull'),
|
||||
parametersDataFields: 'source,type,target_percentage,allow_holes,category_column',
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
if (!nodeAttrs.aggregation_column) {
|
||||
nodeAttrs.aggregation_column = 'cartodb_id';
|
||||
}
|
||||
if (!nodeAttrs.aggregation) {
|
||||
nodeAttrs.aggregation = 'count';
|
||||
}
|
||||
|
||||
var aggregate = {
|
||||
operator: nodeAttrs.aggregation,
|
||||
attribute: nodeAttrs.aggregation === 'count' ? '' : nodeAttrs.aggregation_column
|
||||
};
|
||||
|
||||
var target_percentage = 70;
|
||||
|
||||
if (nodeAttrs.target_percent === 0) {
|
||||
target_percentage = 0;
|
||||
} else if (nodeAttrs.target_percent) {
|
||||
target_percentage = nodeAttrs.target_percent * 100;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'concave-hull',
|
||||
target_percentage: target_percentage,
|
||||
category_column: nodeAttrs.category_column,
|
||||
aggregate: aggregate
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs) {
|
||||
formAttrs = _.omit(formAttrs, 'aggregate');
|
||||
|
||||
formAttrs.target_percent = formAttrs.target_percentage / 100;
|
||||
formAttrs = _.omit(formAttrs, 'target_percentage');
|
||||
|
||||
if (!formAttrs.aggregation_column) {
|
||||
formAttrs = _.omit(formAttrs, 'aggregation_column');
|
||||
}
|
||||
|
||||
if (!formAttrs.allow_holes) {
|
||||
formAttrs = _.omit(formAttrs, 'allow_holes');
|
||||
}
|
||||
|
||||
if (!formAttrs.category_column) {
|
||||
formAttrs = _.omit(formAttrs, 'category_column');
|
||||
}
|
||||
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
module.exports = {
|
||||
type: 'convex-hull',
|
||||
label: _t('editor.layers.analysis-form.convex-hull'),
|
||||
parametersDataFields: 'source,type,category_column',
|
||||
|
||||
parse: function (nodeAttrs) {
|
||||
if (!nodeAttrs.aggregation_column) {
|
||||
nodeAttrs.aggregation_column = 'cartodb_id';
|
||||
}
|
||||
if (!nodeAttrs.aggregation) {
|
||||
nodeAttrs.aggregation = 'count';
|
||||
}
|
||||
var aggregate = {
|
||||
operator: nodeAttrs.aggregation,
|
||||
attribute: nodeAttrs.aggregation === 'count' ? '' : nodeAttrs.aggregation_column
|
||||
};
|
||||
return {
|
||||
type: 'convex-hull',
|
||||
category_column: nodeAttrs.category_column,
|
||||
aggregate: aggregate
|
||||
};
|
||||
},
|
||||
|
||||
formatAttrs: function (formAttrs) {
|
||||
formAttrs = _.omit(formAttrs, 'aggregate');
|
||||
|
||||
if (!formAttrs.aggregation_column) {
|
||||
formAttrs = _.omit(formAttrs, 'aggregation_column');
|
||||
}
|
||||
|
||||
if (!formAttrs.category_column) {
|
||||
formAttrs = _.omit(formAttrs, 'category_column');
|
||||
}
|
||||
|
||||
return formAttrs;
|
||||
}
|
||||
};
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var template = require('./kmeans-form.tpl');
|
||||
|
||||
/**
|
||||
* Form model for a means analysis
|
||||
* It has a rather complicated schema, that depends on several data points and state.
|
||||
*/
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
defaults: {
|
||||
clusters: 2
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, {
|
||||
source: this._primarySourceSchemaItem(),
|
||||
clusters: {
|
||||
type: 'Number',
|
||||
title: _t('editor.layers.analysis-form.clusters-num'),
|
||||
validators: ['required', {
|
||||
type: 'interval',
|
||||
min: 2,
|
||||
max: 16
|
||||
}]
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.parameters') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.tune-clusters') %></p>
|
||||
</div>
|
||||
<div data-fields="source,clusters"></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
var _ = require('underscore');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var template = require('./merge-form.tpl');
|
||||
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var MERGE_FIELDS = 'join_operator,left_source_column,right_source_column,source_geometry_selector,left_source_columns,right_source_columns';
|
||||
|
||||
/**
|
||||
* Form model for the merge analysis
|
||||
*/
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
defaults: {
|
||||
join_operator: 'inner',
|
||||
source_geometry: 'left_source',
|
||||
right_source_columns: []
|
||||
},
|
||||
|
||||
parse: function (attrs) {
|
||||
if (attrs.source_geometry === 'left_source' || !attrs.source_geometry) {
|
||||
attrs.source_geometry_selector = attrs.left_source;
|
||||
} else {
|
||||
attrs.source_geometry_selector = attrs.right_source;
|
||||
}
|
||||
|
||||
return attrs;
|
||||
},
|
||||
|
||||
initialize: function (attrs, options) {
|
||||
BaseAnalysisFormModel.prototype.initialize.call(this, attrs, options);
|
||||
|
||||
this.on('change', this._onChange, this);
|
||||
|
||||
this._leftColumnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this._rightColumnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel
|
||||
});
|
||||
|
||||
this.listenTo(this._analysisSourceOptionsModel, 'change:fetching', this._setSchema);
|
||||
|
||||
this.listenTo(this._leftColumnOptions, 'columnsFetched', this._setSchema);
|
||||
this.listenTo(this._rightColumnOptions, 'columnsFetched', this._setSchema);
|
||||
|
||||
this.on('change:type', this._setSchema, this);
|
||||
this.on('change:right_source', this._onChangeRightSource, this);
|
||||
this.on('change:join_operator, change:right_source, change:left_source_column, change:right_source_column', this._setSchema, this);
|
||||
|
||||
this._setSchema();
|
||||
this._fetchLeftColumns();
|
||||
this._fetchRightColumns();
|
||||
},
|
||||
|
||||
_getFormFieldNames: function () {
|
||||
return MERGE_FIELDS;
|
||||
},
|
||||
|
||||
_getFormatFieldNames: function () {
|
||||
return MERGE_FIELDS;
|
||||
},
|
||||
|
||||
_formatAttrs: function (formAttrs) {
|
||||
var customFormattedFormAttrs = _.pick(formAttrs, ['id', 'type', 'left_source', 'right_source'].concat(this._getFormatFieldNames().split(',')));
|
||||
|
||||
if (this.get('left_source') === this.get('source_geometry_selector')) {
|
||||
customFormattedFormAttrs.source_geometry = 'left_source';
|
||||
} else {
|
||||
customFormattedFormAttrs.source_geometry = 'right_source';
|
||||
}
|
||||
|
||||
if (!formAttrs.left_source_columns) {
|
||||
customFormattedFormAttrs.left_source_columns = [];
|
||||
}
|
||||
|
||||
if (!formAttrs.right_source_columns) {
|
||||
customFormattedFormAttrs.right_source_columns = [];
|
||||
}
|
||||
|
||||
return BaseAnalysisFormModel.prototype._formatAttrs.call(this, customFormattedFormAttrs);
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {
|
||||
right_source: this.get('right_source'),
|
||||
hasLeftAndRightSourceColumns: this.get('left_source_column') && this.get('right_source_column')
|
||||
};
|
||||
},
|
||||
|
||||
_filterSchemaFieldsByType: function (schema) {
|
||||
// Always include the source and target fields in addition to the type-specific fields
|
||||
return _.pick(schema, ['left_source', 'right_source'].concat(this._getFormFieldNames().split(',')));
|
||||
},
|
||||
|
||||
_onChangeRightSource: function () {
|
||||
this._fetchRightColumns();
|
||||
this.unset('right_source_column');
|
||||
this.set('right_source_columns', []);
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
var schemaFields = {
|
||||
left_source: this._primarySourceSchemaItem(_t('editor.layers.analysis-form.base-layer')),
|
||||
right_source: {
|
||||
type: 'NodeDataset',
|
||||
title: _t('editor.layers.analysis-form.target'),
|
||||
options: this._getSourceOptionsForSource('right_source', '*'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
validators: ['required'],
|
||||
editorAttrs: {
|
||||
disabled: this._isSourceDisabled('right_source'),
|
||||
placeholder: _t('editor.layers.analysis-form.select-data-source')
|
||||
}
|
||||
},
|
||||
join_operator: {
|
||||
type: 'Radio',
|
||||
title: _t('editor.layers.analysis-form.join-type'),
|
||||
options: [
|
||||
{ label: _t('editor.layers.analysis-form.append'), val: 'left', help: _t('editor.layers.analysis-form.append-help') },
|
||||
{ label: _t('editor.layers.analysis-form.intersect'), val: 'inner', help: _t('editor.layers.analysis-form.intersect-help') }
|
||||
],
|
||||
validators: ['required']
|
||||
},
|
||||
source_geometry_selector: {
|
||||
type: 'NodeDataset',
|
||||
title: _t('editor.layers.analysis-form.geometry-from'),
|
||||
options: this._getSourcesSelector(),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
editorAttrs: {
|
||||
disabled: this._isSourceDisabled('right_source')
|
||||
}
|
||||
},
|
||||
left_source_columns: {
|
||||
type: 'MultiSelect',
|
||||
title: _t('editor.layers.analysis-form.base-data'),
|
||||
options: this._leftColumnOptions.filterByType(['string', 'number', 'date', 'boolean']),
|
||||
dialogMode: DialogConstants.Mode.FLOAT
|
||||
},
|
||||
right_source_columns: {
|
||||
type: 'MultiSelect',
|
||||
title: _t('editor.layers.analysis-form.target-data'),
|
||||
options: this._rightColumnOptions.filterByType(['string', 'number', 'date', 'boolean']),
|
||||
dialogMode: DialogConstants.Mode.FLOAT
|
||||
}
|
||||
};
|
||||
|
||||
schemaFields['left_source_column'] = {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.source-column'),
|
||||
options: this._leftColumnOptions.all(),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
validators: this.get('right_source') ? ['required'] : [],
|
||||
editorAttrs: {
|
||||
placeholder: _t('editor.layers.analysis-form.select-column')
|
||||
}
|
||||
};
|
||||
|
||||
schemaFields['right_source_column'] = {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.target-column'),
|
||||
options: this._rightColumnOptions.all(),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
validators: this.get('right_source') ? ['required', this._validateRightSourceColumn.bind(this)] : [],
|
||||
editorAttrs: {
|
||||
placeholder: _t('editor.layers.analysis-form.select-column')
|
||||
}
|
||||
};
|
||||
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, this._filterSchemaFieldsByType(schemaFields));
|
||||
},
|
||||
|
||||
_validateRightSourceColumn: function (value, formValues) {
|
||||
var leftColumnValue = formValues.left_source_column;
|
||||
var rightColumnValue = formValues.right_source_column;
|
||||
|
||||
// if we don't have both fields we don't validate
|
||||
if (leftColumnValue === void 0 || rightColumnValue === void 0) return null;
|
||||
|
||||
var error = {
|
||||
message: _t('editor.layers.analysis-form.should-match')
|
||||
};
|
||||
|
||||
var leftColumn = this._leftColumnOptions.findColumn(leftColumnValue);
|
||||
var leftColumnType = leftColumn && leftColumn.type;
|
||||
|
||||
var rightColumn = this._rightColumnOptions.findColumn(rightColumnValue);
|
||||
var rightColumnType = rightColumn && rightColumn.type;
|
||||
|
||||
if (leftColumnType === rightColumnType) {
|
||||
return null; // types match
|
||||
}
|
||||
|
||||
return error;
|
||||
},
|
||||
|
||||
_fetchColumns: function (sourceName, columnOptions) {
|
||||
var source = this.get(sourceName);
|
||||
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(source);
|
||||
|
||||
if (nodeDefModel) {
|
||||
columnOptions.setNode(nodeDefModel);
|
||||
} else if (source) {
|
||||
columnOptions.setDataset(source);
|
||||
}
|
||||
},
|
||||
|
||||
_fetchLeftColumns: function () {
|
||||
this._fetchColumns('left_source', this._leftColumnOptions);
|
||||
},
|
||||
|
||||
_fetchRightColumns: function () {
|
||||
this._fetchColumns('right_source', this._rightColumnOptions);
|
||||
},
|
||||
|
||||
_isSourceDisabled: function (sourceAttrName) {
|
||||
return this._isPrimarySource(sourceAttrName) || this._isFetchingOptions();
|
||||
},
|
||||
|
||||
_getSourcesSelector: function () {
|
||||
return _.compact([this._getSourceOption()[0], this._getRightSource()]);
|
||||
},
|
||||
|
||||
_getRightSource: function () {
|
||||
if (this.get('right_source')) {
|
||||
var source = this._getSourceOptionsForSourceId(this.get('right_source'), 'right_source', '*');
|
||||
if (source) {
|
||||
return {
|
||||
val: this.get('right_source'),
|
||||
type: source.type,
|
||||
nodeTitle: source.nodeTitle,
|
||||
layerName: source.layerName,
|
||||
color: source.color
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_getSourceOptionsForSourceId: function (sourceId, sourceAttrName, requiredSimpleGeometryType) {
|
||||
var options = this._analysisSourceOptionsModel.getSelectOptions(requiredSimpleGeometryType);
|
||||
var searchFn = function (d) {
|
||||
// Can't select own layer as source, so exclude it
|
||||
return d.val === sourceId;
|
||||
};
|
||||
|
||||
if (options.find) {
|
||||
return options.find(searchFn);
|
||||
} else {
|
||||
return _.find(options, searchFn);
|
||||
}
|
||||
},
|
||||
|
||||
_getSourceOptionsForSource: function (sourceAttrName, requiredSimpleGeometryType) {
|
||||
var currentSource = this.get(sourceAttrName);
|
||||
var nodeOptions = this._analysisSourceOptionsModel.getSelectOptions(requiredSimpleGeometryType);
|
||||
|
||||
if (this._isFetchingOptions() && nodeOptions.length === 0) {
|
||||
return [{
|
||||
val: currentSource,
|
||||
label: _t('editor.layers.analysis-form.loading'),
|
||||
isLoading: true
|
||||
}];
|
||||
} else {
|
||||
// fetched or populated
|
||||
var source = this._getSourceOption()[0];
|
||||
return this._analysisSourceOptionsModel
|
||||
.getSelectOptions(requiredSimpleGeometryType)
|
||||
.filter(function (d) {
|
||||
return d.val !== source.val && d.val !== source.layerName;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_getSourceOption: function () {
|
||||
var leftSourceId = this.get('left_source');
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(leftSourceId);
|
||||
var layerName = nodeDefModel && nodeDefModel.isSourceType()
|
||||
? this._layerDefinitionModel.getTableName()
|
||||
: this._layerDefinitionModel.getName();
|
||||
|
||||
return [{
|
||||
val: leftSourceId,
|
||||
type: 'node',
|
||||
nodeTitle: this._analyses.short_title(nodeDefModel),
|
||||
layerName: layerName,
|
||||
color: nodeDefModel && nodeDefModel.getColor(),
|
||||
isSourceType: nodeDefModel && nodeDefModel.isSourceType()
|
||||
}];
|
||||
},
|
||||
|
||||
_onChange: function () {
|
||||
var changedSourceId = this.changed.left_source || this.changed.right_source;
|
||||
if (changedSourceId) {
|
||||
this._analysisSourceOptionsModel.createSourceNodeUnlessExisting(changedSourceId);
|
||||
}
|
||||
}
|
||||
});
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="left_source,right_source,join_operator">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.merge-step-one-title') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.target-layer-dataset') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo <%- right_source ? '' : 'is-disabled' %>">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">2</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="left_source_column,right_source_column">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.key-columns') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--xl"><%- _t('editor.layers.analysis-form.key-columns-desc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo <%- hasLeftAndRightSourceColumns ? '' : 'is-disabled' %>">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">3</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="source_geometry_selector,left_source_columns,right_source_columns">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.output-data') %></h2>
|
||||
</div>
|
||||
<p class="CDB-Text u-upperCase CDB-FontSize-small u-altTextColor u-bSpace--xl"><%- _t('editor.layers.analysis-form.keep-data') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var template = require('./moran-form.tpl');
|
||||
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');
|
||||
|
||||
/**
|
||||
* Form model for a moran analysis
|
||||
* It has a rather complicated schema, that depends on several data points and state.
|
||||
*/
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
defaults: {
|
||||
significance: 0.05,
|
||||
neighbours: 5,
|
||||
permutations: 99,
|
||||
w_type: 'knn'
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
|
||||
this._columnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'))
|
||||
});
|
||||
|
||||
this.listenTo(this._columnOptions, 'columnsFetched', this._setSchema);
|
||||
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
/*
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
var schema = {
|
||||
source: this._primarySourceSchemaItem(),
|
||||
numerator_column: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.numerator'),
|
||||
placeholder: _t('editor.layers.analysis-form.select-column'),
|
||||
options: this._columnOptions.filterByType('number'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
help: _t('editor.layers.analysis-form.numerator-help')
|
||||
},
|
||||
denominator_column: {
|
||||
type: 'EnablerEditor',
|
||||
title: '',
|
||||
label: _t('editor.layers.analysis-form.normalize'),
|
||||
help: _t('editor.layers.analysis-form.normalize-help'),
|
||||
editor: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.normalize'),
|
||||
placeholder: _t('editor.layers.analysis-form.select-column'),
|
||||
options: this._columnOptions.filterByType('number'),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
disabledPlaceholder: _t('editor.layers.analysis-form.enable-normalize')
|
||||
}
|
||||
},
|
||||
significance: {
|
||||
type: 'Number',
|
||||
title: _t('editor.layers.analysis-form.significance'),
|
||||
help: _t('editor.layers.analysis-form.significance-help'),
|
||||
validators: ['required', {
|
||||
type: 'interval',
|
||||
min: 0,
|
||||
max: 1,
|
||||
step: 0.05
|
||||
}]
|
||||
},
|
||||
neighbours: {
|
||||
type: 'Number',
|
||||
title: _t('editor.layers.analysis-form.neighbors'),
|
||||
help: _t('editor.layers.analysis-form.neighbors-help'),
|
||||
validators: ['required', {
|
||||
type: 'interval',
|
||||
min: 1,
|
||||
max: 100
|
||||
}]
|
||||
}
|
||||
// hidden as per https://github.com/CartoDB/cartodb/pull/7893#issuecomment-226803659
|
||||
/*
|
||||
permutations: {
|
||||
type: 'Number',
|
||||
title: _t('editor.layers.analysis-form.permutations'),
|
||||
validators: ['required', {
|
||||
type: 'interval',
|
||||
min: 1,
|
||||
max: 100
|
||||
}]
|
||||
},
|
||||
w_type: {
|
||||
type: 'Select',
|
||||
title: _t('editor.layers.analysis-form.weight-type'),
|
||||
options: ['knn', 'queen'],
|
||||
validators: []
|
||||
}
|
||||
*/
|
||||
};
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, schema);
|
||||
},
|
||||
|
||||
updateNodeDefinition: function (nodeDefModel) {
|
||||
var nodeAttrs = this._formatAttrs(this.attributes);
|
||||
// It could happen that denominator_column is not present, and if we use `set`
|
||||
// instead of the thing below, it could keep returning that attribute for the analysis
|
||||
nodeDefModel.attributes = nodeAttrs;
|
||||
}
|
||||
});
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.parameters') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.moran-desc') %></p>
|
||||
</div>
|
||||
<div data-fields="source,numerator_column,denominator_column,significance,neighbours"></div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var template = require('./sampling-form.tpl');
|
||||
var _ = require('underscore');
|
||||
|
||||
var DEFAULT_PERCENT = 10;
|
||||
|
||||
/**
|
||||
* Form model for a sampling
|
||||
*/
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
this.bind('change:percent', function () {
|
||||
this.set('sampling', +this.get('percent') / 100);
|
||||
}, this);
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
parse: function (attrs) {
|
||||
return _.defaults(attrs, {
|
||||
percent: DEFAULT_PERCENT,
|
||||
sampling: DEFAULT_PERCENT / 100
|
||||
});
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
var schema = {
|
||||
source: this._primarySourceSchemaItem(),
|
||||
percent: {
|
||||
type: 'Number',
|
||||
title: _t('editor.layers.analysis-form.sampling-rate'),
|
||||
validators: ['required', {
|
||||
type: 'interval',
|
||||
min: 0,
|
||||
max: 100,
|
||||
step: 1
|
||||
}],
|
||||
editorAttrs: {
|
||||
help: _t('editor.layers.analysis-form.sampling-form-model.help')
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, schema);
|
||||
}
|
||||
});
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="source,percent">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('analyses.sampling.title') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.sampling-desc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
var DialogConstants = require('builder/components/form-components/_constants/_dialogs');
|
||||
var template = require('./spatial-markov-trend.tpl');
|
||||
var ColumnOptions = require('builder/editor/layers/layer-content-views/analyses/column-options');
|
||||
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
initialize: function () {
|
||||
BaseAnalysisFormModel.prototype.initialize.apply(this, arguments);
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(this.get('source'));
|
||||
this._columnOptions = new ColumnOptions({}, {
|
||||
configModel: this._configModel,
|
||||
nodeDefModel: nodeDefModel
|
||||
});
|
||||
this.listenTo(this._columnOptions, 'columnsFetched', this._setSchema);
|
||||
this._setSchema();
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return template;
|
||||
},
|
||||
|
||||
/**
|
||||
* @override {BaseAnalysisFormModel._setSchema}
|
||||
*/
|
||||
_setSchema: function () {
|
||||
var schema = {
|
||||
source: this._primarySourceSchemaItem(),
|
||||
time_columns: {
|
||||
type: 'MultiSelect',
|
||||
title: _t('editor.layers.analysis-form.spatial-markov-trend-time-columns'),
|
||||
options: this._columnOptions.filterByType(['string', 'number']),
|
||||
dialogMode: DialogConstants.Mode.FLOAT,
|
||||
help: _t('editor.layers.analysis-form.spatial-markov-trend-time-columns-help'),
|
||||
editorAttrs: {
|
||||
showSearch: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BaseAnalysisFormModel.prototype._setSchema.call(this, schema);
|
||||
}
|
||||
|
||||
});
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
<form>
|
||||
<div class="Editor-HeaderInfo">
|
||||
<div class="Editor-HeaderNumeration CDB-Text is-semibold u-rSpace--m">1</div>
|
||||
<div class="Editor-HeaderInfo-inner CDB-Text" data-fields="source,time_columns">
|
||||
<div class="Editor-HeaderInfo-title u-bSpace--m">
|
||||
<h2 class="CDB-Text CDB-HeaderInfo-titleText CDB-Size-large"><%- _t('editor.layers.analysis-form.select-columns') %></h2>
|
||||
</div>
|
||||
<div class="Editor-HeaderInfo-subtitle u-bSpace--m">
|
||||
<p class="CDB-Text CDB-FontSize-small u-upperCase u-altTextColor"><%- _t('editor.layers.analysis-form.spatial-markov-trend-desc') %></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
var _ = require('underscore');
|
||||
var BaseAnalysisFormModel = require('./base-analysis-form-model');
|
||||
|
||||
/**
|
||||
* A fallback form model in case the type is not supported (yet).
|
||||
*/
|
||||
module.exports = BaseAnalysisFormModel.extend({
|
||||
|
||||
validate: function () {
|
||||
return true; // always fail, should not be able to save an unknown node
|
||||
},
|
||||
|
||||
getTemplate: function () {
|
||||
return _.template('<form>Unknown analysis type <%- type %></form>');
|
||||
},
|
||||
|
||||
getTemplateData: function () {
|
||||
return {type: this.get('type') || ''};
|
||||
},
|
||||
|
||||
// Don't allow to update or create an unknown type
|
||||
updateNodeDefinition: function () {},
|
||||
createNodeDefinition: function () {}
|
||||
|
||||
});
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var TableStats = require('builder/components/modals/add-widgets/tablestats');
|
||||
var Utils = require('builder/helpers/utils');
|
||||
require('builder/components/form-components/index');
|
||||
|
||||
var CHECK_FORM_VALIDATION_TOO = {validate: true};
|
||||
|
||||
/**
|
||||
* This view is required because a schema change require the Backbone.Form object to be re-created to update the view as expected.
|
||||
*/
|
||||
module.exports = CoreView.extend({
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.formModel) throw new Error('formModel is required');
|
||||
if (!opts.configModel) throw new Error('configModel is required');
|
||||
|
||||
this._configModel = opts.configModel;
|
||||
this._userModel = opts.userModel;
|
||||
this._formModel = opts.formModel;
|
||||
|
||||
this.link = this._formModel._analyses.link(this._formModel);
|
||||
|
||||
this.tableStats = new TableStats({
|
||||
configModel: this._configModel,
|
||||
userModel: this._userModel
|
||||
});
|
||||
|
||||
this._formModel.on('changeSchema', this.render, this);
|
||||
this._formModel.on('generateHistogram', this._generateHistogram, this);
|
||||
this._formModel.on('change:histogram_stats', this._showHistogramStats, this);
|
||||
|
||||
this.add_related_model(this._formModel);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
|
||||
this._formView = new Backbone.Form({
|
||||
model: this._formModel,
|
||||
trackingClass: 'track-' + this._formModel.get('type') + '-analysis',
|
||||
template: this._formModel.getTemplate(),
|
||||
templateData: this._formModel.getTemplateData()
|
||||
});
|
||||
|
||||
this._formView.bind('change', this._onChangeAnalysisFormView, this);
|
||||
this.$el.append(this._formView.render().el);
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_onChangeAnalysisFormView: function (data) {
|
||||
var formId = this._formView.cid;
|
||||
this._formView.model.setFormValidationErrors(undefined);
|
||||
var formValue = this._formView.getValue();
|
||||
this._formView.model.set(formValue); // <-- This can trigger change events and their callbacks will be run. That could provoke some 'race conditions'.
|
||||
this._showAnalysisFormErrors(formId); // To avoid race condition of running this function against a new form view.
|
||||
},
|
||||
|
||||
_showAnalysisFormErrors: function (formId) {
|
||||
if (this._formView.cid === formId && this._formView.$el) {
|
||||
var formValidationErrors = this._formView.commit(CHECK_FORM_VALIDATION_TOO);
|
||||
this._formView.model.setFormValidationErrors(formValidationErrors);
|
||||
}
|
||||
},
|
||||
|
||||
_generateHistogram: function (data) {
|
||||
this.tableStats.graphFor(data.tableName, data.columnName, this._drawHistogram.bind(this));
|
||||
},
|
||||
|
||||
_showHistogramStats: function () {
|
||||
var stats = this._formModel.get('histogram_stats');
|
||||
|
||||
if (!stats || !_.isNumber(stats.max) || !_.isNumber(stats.min)) {
|
||||
this.$('.js-histogram').hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var max = stats.max < 10000 ? Utils.formatNumber(stats.max) : Utils.readizableNumber(stats.max);
|
||||
var min = stats.min < 10000 ? Utils.formatNumber(stats.min) : Utils.readizableNumber(stats.min);
|
||||
|
||||
this.$('.js-max').html(max);
|
||||
this.$('.js-min').html(min);
|
||||
this.$('.js-histogram').show();
|
||||
},
|
||||
|
||||
_drawHistogram: function (graph) {
|
||||
var stats = this._formModel.get('histogram_stats');
|
||||
|
||||
if (!stats || !_.isNumber(stats.max) || !_.isNumber(stats.min)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$('.js-histogramChart').html(graph.getHistogram({
|
||||
color: '#9DE0AD',
|
||||
width: 158,
|
||||
height: 20,
|
||||
bins: 20
|
||||
}));
|
||||
|
||||
this._showHistogramStats();
|
||||
},
|
||||
|
||||
/**
|
||||
* @override CoreView.prototype.clearSubViews
|
||||
*/
|
||||
clearSubViews: function () {
|
||||
if (this._formView) {
|
||||
this._formView.remove(); // the Backbone.Form equivalent to "view.clean()"
|
||||
}
|
||||
|
||||
return CoreView.prototype.clearSubViews.apply(this, arguments);
|
||||
}
|
||||
|
||||
});
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var Analyses = require('builder/data/analyses');
|
||||
var AnalysesService = require('builder/editor/layers/layer-content-views/analyses/analyses-service');
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
|
||||
initialize: function (models, opts) {
|
||||
if (!opts.configModel) throw new Error('configModel is required');
|
||||
if (!opts.layerDefinitionModel) throw new Error('layerDefinitionModel is required');
|
||||
if (!opts.analysisSourceOptionsModel) throw new Error('analysisSourceOptionsModel is required');
|
||||
if (!opts.userActions) throw new Error('userActions is required');
|
||||
|
||||
this._configModel = opts.configModel;
|
||||
this._layerDefinitionModel = opts.layerDefinitionModel;
|
||||
this._analysisSourceOptionsModel = opts.analysisSourceOptionsModel;
|
||||
this._userActions = opts.userActions;
|
||||
},
|
||||
|
||||
model: function (attrs, opts) {
|
||||
var self = opts.collection;
|
||||
|
||||
var FormModel = Analyses.findFormModelByType(attrs.type);
|
||||
|
||||
return new FormModel(attrs, {
|
||||
configModel: self._configModel,
|
||||
analyses: Analyses,
|
||||
layerDefinitionModel: self._layerDefinitionModel,
|
||||
analysisSourceOptionsModel: self._analysisSourceOptionsModel,
|
||||
collection: self,
|
||||
parse: true
|
||||
});
|
||||
},
|
||||
|
||||
resetByLayerDefinition: function () {
|
||||
var current = this._layerDefinitionModel.getAnalysisDefinitionNodeModel();
|
||||
|
||||
if (current) {
|
||||
var notAppliedAnalysis = AnalysesService.getNotAppliedAnalysis();
|
||||
var attrsList = [];
|
||||
|
||||
if (notAppliedAnalysis) {
|
||||
attrsList.push(notAppliedAnalysis);
|
||||
}
|
||||
|
||||
this._walkAnalysisChain(current, this._layerDefinitionModel, function (nodeDefModel) {
|
||||
var attrs = _.extend({ persisted: true }, nodeDefModel.attributes);
|
||||
attrsList.push(attrs);
|
||||
});
|
||||
|
||||
this.reset(attrsList);
|
||||
}
|
||||
},
|
||||
|
||||
addHead: function (attrs) {
|
||||
AnalysesService.saveNotAppliedAnalysis(attrs);
|
||||
|
||||
this.remove(attrs.id); // if having the same id it's because the existing node is a temporary one,
|
||||
return this.add(attrs, { at: 0 }); // so replace it with the new attrs
|
||||
},
|
||||
|
||||
deleteNode: function (nodeId) {
|
||||
var nodeDefModel = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(nodeId);
|
||||
if (nodeDefModel) {
|
||||
this._userActions.deleteAnalysisNode(nodeId);
|
||||
} else {
|
||||
this.remove(nodeId);
|
||||
}
|
||||
},
|
||||
|
||||
removeUselessModels: function () {
|
||||
var removed = [];
|
||||
|
||||
this.models.forEach(function (model) {
|
||||
var useless = this._layerDefinitionModel.findAnalysisDefinitionNodeModel(model.id) === undefined;
|
||||
if (useless) {
|
||||
removed.push(model);
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (removed.length > 0) {
|
||||
this.remove(removed, { silent: true });
|
||||
this.trigger('destroyedModels');
|
||||
}
|
||||
},
|
||||
|
||||
_walkAnalysisChain: function (current, layerDefModel, visitorFn) {
|
||||
if (!current.hasPrimarySource()) return;
|
||||
|
||||
visitorFn.call(this, current);
|
||||
|
||||
var next = current.getPrimarySource();
|
||||
if (layerDefModel.isOwnerOfAnalysisNode(next)) {
|
||||
this._walkAnalysisChain(next, layerDefModel, visitorFn);
|
||||
}
|
||||
}
|
||||
});
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var InfoboxView = require('builder/components/infobox/infobox-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'infoboxModel',
|
||||
'infoboxCollection'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var view = new InfoboxView({
|
||||
infoboxModel: this._infoboxModel,
|
||||
infoboxCollection: this._infoboxCollection
|
||||
});
|
||||
this.addView(view);
|
||||
this.$el.append(view.render().el);
|
||||
}
|
||||
});
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
var _ = require('underscore');
|
||||
var queueAsync = require('d3-queue').queue;
|
||||
var Backbone = require('backbone');
|
||||
var analyses = require('builder/data/analyses');
|
||||
|
||||
/**
|
||||
* Analysis source options are not trivial to get and requires some coordination of events.
|
||||
*
|
||||
* It's intended to be fetched
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
fetching: false,
|
||||
nodes_options: []
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.analysisDefinitionNodesCollection) throw new Error('analysisDefinitionNodesCollection is required');
|
||||
if (!opts.layerDefinitionsCollection) throw new Error('layerDefinitionsCollection is required');
|
||||
if (!opts.tablesCollection) throw new Error('tablesCollection is required');
|
||||
|
||||
this._analysisDefinitionNodesCollection = opts.analysisDefinitionNodesCollection;
|
||||
this._layerDefinitionsCollection = opts.layerDefinitionsCollection;
|
||||
this._tablesCollection = opts.tablesCollection;
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
this.set({
|
||||
fetching: true,
|
||||
nodes_options: []
|
||||
});
|
||||
|
||||
var queue = queueAsync();
|
||||
|
||||
this._fetchTables(queue);
|
||||
this._createOptionsFromNodes(queue);
|
||||
|
||||
queue.awaitAll(function () {
|
||||
this.set('fetching', false);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String, Array<String>} requiredSimpleGeometryTypes
|
||||
*/
|
||||
getSelectOptions: function (requiredSimpleGeometryTypes) {
|
||||
var nodeOptions = this.get('nodes_options');
|
||||
if (this.get('fetching') && nodeOptions.length === 0) {
|
||||
return nodeOptions;
|
||||
}
|
||||
|
||||
if (_.isString(requiredSimpleGeometryTypes)) {
|
||||
requiredSimpleGeometryTypes = [requiredSimpleGeometryTypes];
|
||||
}
|
||||
|
||||
return _.flatten([
|
||||
nodeOptions
|
||||
.filter(function (d) {
|
||||
return this._isValidGeometry(requiredSimpleGeometryTypes, d.simpleGeometryType);
|
||||
}, this)
|
||||
.map(function (d) {
|
||||
var node = this._analysisDefinitionNodesCollection.get(d.val);
|
||||
var layer = this._layerDefinitionsCollection.findOwnerOfAnalysisNode(node);
|
||||
var layerName = node.isSourceType()
|
||||
? layer.getTableName()
|
||||
: layer.getName();
|
||||
|
||||
return _.extend({
|
||||
layerName: layerName,
|
||||
nodeTitle: analyses.short_title(node),
|
||||
color: node.getColor(),
|
||||
type: 'node',
|
||||
isSourceType: node.isSourceType()
|
||||
}, d);
|
||||
}, this),
|
||||
this._getTablesSelectOptions(requiredSimpleGeometryTypes)
|
||||
]);
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {String} val val from select option
|
||||
*/
|
||||
createSourceNodeUnlessExisting: function (val) {
|
||||
var tableModel = this._tablesCollection.get(val);
|
||||
|
||||
// If there is no table with given val as id, then it must be a layer source which already exist, so nothing to do
|
||||
if (tableModel) {
|
||||
var tableName = tableModel.get('name');
|
||||
this._analysisDefinitionNodesCollection.createSourceNode({
|
||||
id: tableName, // to avoid duplicats
|
||||
tableName: tableName
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_isValidGeometry: function (requiredSimpleGeometryTypes, simpleGeometryType) {
|
||||
return _.contains(requiredSimpleGeometryTypes, simpleGeometryType) || requiredSimpleGeometryTypes[0] === '*';
|
||||
},
|
||||
|
||||
_fetchTables: function (queue) {
|
||||
queue.defer(function (cb) {
|
||||
this._tablesCollection.fetch({
|
||||
data: {
|
||||
show_liked: false,
|
||||
show_stats: false,
|
||||
show_table_size_and_row_count: false,
|
||||
show_permission: false,
|
||||
show_synchronization: false,
|
||||
show_uses_builder_features: false,
|
||||
load_totals: false,
|
||||
per_page: 1000
|
||||
}
|
||||
});
|
||||
this._tablesCollection.once('sync error', function () {
|
||||
cb();
|
||||
});
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
_createOptionsFromNodes: function (queue) {
|
||||
var self = this;
|
||||
|
||||
this._analysisDefinitionNodesCollection
|
||||
.each(function (nodeDefModel) {
|
||||
var queryGeometryModel = nodeDefModel.queryGeometryModel;
|
||||
|
||||
queue.defer(function (cb) {
|
||||
if (queryGeometryModel.isFetched()) {
|
||||
self._appendNodeOption(nodeDefModel);
|
||||
return cb();
|
||||
}
|
||||
|
||||
if (!queryGeometryModel.canFetch()) {
|
||||
return cb();
|
||||
}
|
||||
|
||||
var onStatusChange = function () {
|
||||
if (queryGeometryModel.get('status') === 'fetching') return;
|
||||
queryGeometryModel.off('change:status', onStatusChange);
|
||||
self._appendNodeOption(nodeDefModel);
|
||||
cb();
|
||||
};
|
||||
queryGeometryModel.on('change:status', onStatusChange);
|
||||
queryGeometryModel.fetch();
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_appendNodeOption: function (nodeDefModel) {
|
||||
var geom = nodeDefModel.queryGeometryModel.get('simple_geom');
|
||||
// If a node doesn't belong to a layer, we should not append it as a node option
|
||||
// It could be an intersection node, for example.
|
||||
var belongsToLayer = this._layerDefinitionsCollection.findOwnerOfAnalysisNode(nodeDefModel);
|
||||
|
||||
if (!geom || !belongsToLayer) return;
|
||||
|
||||
this.get('nodes_options')
|
||||
.push({
|
||||
simpleGeometryType: geom,
|
||||
val: nodeDefModel.id,
|
||||
label: nodeDefModel.id
|
||||
});
|
||||
},
|
||||
|
||||
_getTablesSelectOptions: function (requiredSimpleGeometryTypes) {
|
||||
return this._tablesCollection.reduce(function (memo, m) {
|
||||
var simpleGeometryType = m.getGeometryType()[0];
|
||||
|
||||
if (this._isValidGeometry(requiredSimpleGeometryTypes, simpleGeometryType)) {
|
||||
memo.push({
|
||||
val: m.id,
|
||||
label: m.get('name'),
|
||||
type: 'dataset'
|
||||
});
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, [], this);
|
||||
}
|
||||
|
||||
});
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var CDB = require('internal-carto.js');
|
||||
var queryTemplate = _.template('SELECT min(<%= column %>) as min, max(<%= column %>) as max FROM (<%= sql %>) _table_sql');
|
||||
var queryBooleanTemplate = _.template('SELECT DISTINCT <%= column %> as val FROM (<%= sql %>) _table_sql');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.configModel) throw new Error('configModel param is required');
|
||||
if (!opts.nodeDefModel) throw new Error('nodeDefModel param is required');
|
||||
|
||||
this._nodeDefModel = opts.nodeDefModel;
|
||||
|
||||
this._SQL = new CDB.SQL({
|
||||
user: opts.configModel.get('user_name'),
|
||||
sql_api_template: opts.configModel.get('sql_api_template'),
|
||||
api_key: opts.configModel.get('api_key')
|
||||
});
|
||||
|
||||
this._query = opts.nodeDefModel.querySchemaModel.get('query');
|
||||
|
||||
this.on('change:column', this.fetch, this);
|
||||
},
|
||||
|
||||
_onQueryDone: function (r) {
|
||||
if (_.size(r.rows) > 0) {
|
||||
this.trigger('columnsFetched', _.first(r.rows));
|
||||
}
|
||||
},
|
||||
|
||||
fetch: function () {
|
||||
var template = this.get('type') === 'boolean' ? queryBooleanTemplate : queryTemplate;
|
||||
|
||||
if (this.get('column') && this._query) {
|
||||
this._SQL.execute(
|
||||
template({
|
||||
sql: this._query,
|
||||
column: this.get('column')
|
||||
}),
|
||||
null,
|
||||
{
|
||||
extra_params: ['page', 'rows_per_page'],
|
||||
page: 0,
|
||||
rows_per_page: 40,
|
||||
success: this._onQueryDone.bind(this),
|
||||
error: function () {
|
||||
// TODO: what happens if fails?
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
Executable
+99
@@ -0,0 +1,99 @@
|
||||
var _ = require('underscore');
|
||||
var CDB = require('internal-carto.js');
|
||||
var Backbone = require('backbone');
|
||||
var tableQueryTemplate = _.template('SELECT * FROM <%= tableName %>');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
columnsFetched: false
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.configModel) throw new Error('configModel is required');
|
||||
|
||||
this._columnOptions = [];
|
||||
|
||||
this._SQL = new CDB.SQL({
|
||||
user: opts.configModel.get('user_name'),
|
||||
sql_api_template: opts.configModel.get('sql_api_template'),
|
||||
api_key: opts.configModel.get('api_key')
|
||||
});
|
||||
|
||||
if (opts.nodeDefModel) {
|
||||
this.setNode(opts.nodeDefModel);
|
||||
}
|
||||
},
|
||||
|
||||
_fetch: function () {
|
||||
if (this._query) {
|
||||
this.set('columnsFetched', false);
|
||||
this._SQL.execute(
|
||||
this._query,
|
||||
null,
|
||||
{
|
||||
extra_params: ['page', 'rows_per_page'],
|
||||
page: 0,
|
||||
rows_per_page: 0,
|
||||
success: this._onQueryDone.bind(this),
|
||||
error: function () {
|
||||
// TODO: what happens if fails?
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
setDataset: function (tableName) {
|
||||
this._columnOptions = [];
|
||||
this._query = tableQueryTemplate({ tableName: tableName });
|
||||
this._fetch();
|
||||
},
|
||||
|
||||
setNode: function (nodeDefModel) {
|
||||
this._columnOptions = [];
|
||||
this._query = nodeDefModel.querySchemaModel.get('query');
|
||||
this._fetch();
|
||||
},
|
||||
|
||||
findColumn: function (columnName, columnType) {
|
||||
return _.find(this.filterByType(columnType), function (column) {
|
||||
return column.val === columnName;
|
||||
}, this);
|
||||
},
|
||||
|
||||
all: function () {
|
||||
return this.filterByType();
|
||||
},
|
||||
|
||||
filterByType: function (columnType) {
|
||||
if (!_.size(this._columnOptions)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var columns = _.map(this._columnOptions, function (value, key) {
|
||||
var columnName = key;
|
||||
return {
|
||||
val: columnName,
|
||||
label: columnName,
|
||||
type: value.type
|
||||
};
|
||||
});
|
||||
|
||||
if (_.isArray(columnType)) {
|
||||
columns = columns.filter(function (column) {
|
||||
return _.contains(columnType, column.type);
|
||||
});
|
||||
} else if (columnType) {
|
||||
columns = columns.filter(function (column) {
|
||||
return column.type === columnType;
|
||||
});
|
||||
}
|
||||
return columns;
|
||||
},
|
||||
|
||||
_onQueryDone: function (r) {
|
||||
this._columnOptions = r.fields;
|
||||
this.set('columnsFetched', true);
|
||||
this.trigger('columnsFetched');
|
||||
}
|
||||
});
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var cdb = require('internal-carto.js');
|
||||
|
||||
function getQueryForGeometryType (geometryType) {
|
||||
if (geometryType === 'polygon') {
|
||||
return "select * from obs_legacybuildermetadata('sum')";
|
||||
} else {
|
||||
return 'select * from obs_legacybuildermetadata()';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* this class fetches information from observatory metadata
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
initialize: function (attrs, opts) {
|
||||
this._configModel = opts.configModel;
|
||||
this._geometryType = opts.geometryType;
|
||||
},
|
||||
|
||||
sync: function (method, model, options) {
|
||||
var sql = new cdb.SQL({
|
||||
user: this._configModel.get('user_name'),
|
||||
sql_api_template: this._configModel.get('sql_api_template'),
|
||||
api_key: this._configModel.get('api_key')
|
||||
});
|
||||
sql.execute(getQueryForGeometryType(this._geometryType)).done(function (data) {
|
||||
options.success && options.success(data);
|
||||
}).error(function (errors) {
|
||||
// console.log("errors:" + errors);
|
||||
});
|
||||
},
|
||||
|
||||
setGeometryType: function (geometryType) {
|
||||
this._geometryType = geometryType;
|
||||
},
|
||||
|
||||
parse: function (attrs) {
|
||||
var a = {};
|
||||
_.each(attrs.rows, function (r) {
|
||||
var sub = {};
|
||||
_.each(r.subsection, function (s) {
|
||||
var cols = {};
|
||||
_.each(s.f1.columns, function (c) {
|
||||
cols[c.f1.name] = c.f1.id;
|
||||
});
|
||||
sub[s.f1.name] = cols;
|
||||
});
|
||||
a[r.name] = sub;
|
||||
});
|
||||
return a;
|
||||
},
|
||||
|
||||
measurement: function (area) {
|
||||
var subsection = this.get(area);
|
||||
if (subsection) {
|
||||
return _.keys(subsection);
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
columns: function (area, subsectionName) {
|
||||
var subsection = this.get(area);
|
||||
if (subsection) {
|
||||
return _.keys(subsection[subsectionName] || {});
|
||||
}
|
||||
return [];
|
||||
},
|
||||
|
||||
segment: function (area, subsectionName, column) {
|
||||
var subsection = this.get(area);
|
||||
if (subsection) {
|
||||
return subsection[subsectionName][column];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
areas: function () {
|
||||
return _.keys(this.attributes);
|
||||
}
|
||||
});
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<% if (link) { %>
|
||||
<a class="Editor-HeaderInfo-link CDB-Text CDB-FontSize-small u-lSpace u-actionTextColor u-upperCase track-<%- type %>-form-learn" href="<%- link %>" target="_blank">
|
||||
<%- _t('editor.layers.analysis-form.more-info') %>
|
||||
</a>
|
||||
<% } %>
|
||||
Executable
+2
@@ -0,0 +1,2 @@
|
||||
<div class="Editor-content js-content"></div>
|
||||
<div class="js-info"></div>
|
||||
Reference in New Issue
Block a user