Initial commit
This commit is contained in:
Executable
+8
@@ -0,0 +1,8 @@
|
||||
<ul class="CodeMirror-error">
|
||||
<li class="CodeMirror-errorMessage u-lSpace--xl u-rSpace--xl">
|
||||
<%- _t('components.codemirror.syntax-error') %>: <span><%- message %></span>
|
||||
</li>
|
||||
<li class="CodeMirror-errorDocs">
|
||||
<a href="https://carto.com/developers/sql-api/" target="_black"><%- _t('components.codemirror.docs') %></a>
|
||||
</li>
|
||||
</ul>
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
var _ = require('underscore');
|
||||
|
||||
var BLACKLISTED_COLUMNS = ['created_at', 'the_geom', 'the_geom_webmercator', 'updated_at'];
|
||||
|
||||
module.exports = function (nodeDefModel, layerDefinitionModel) {
|
||||
var tuplesItems = {};
|
||||
|
||||
if (!layerDefinitionModel) return tuplesItems; // e.g. if the node is one of those temporary tables
|
||||
|
||||
var querySchemaModel = nodeDefModel.querySchemaModel;
|
||||
if (!querySchemaModel) return tuplesItems;
|
||||
|
||||
querySchemaModel.columnsCollection.each(function (m) {
|
||||
var columnName = m.get('name');
|
||||
|
||||
if (!_.contains(BLACKLISTED_COLUMNS, columnName)) {
|
||||
var key = columnName + '-' + m.get('type');
|
||||
var tuples = tuplesItems[key] = tuplesItems[key] || [];
|
||||
tuples.push({
|
||||
analysisDefinitionNodeModel: nodeDefModel,
|
||||
layerDefinitionModel: layerDefinitionModel,
|
||||
columnModel: m
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return tuplesItems;
|
||||
};
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
var $ = require('jquery');
|
||||
var Backbone = require('backbone');
|
||||
var createTuplesItems = require('./create-tuples-items');
|
||||
var widgetsTypes = require('./widgets-types');
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
render: false
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
if (!opts.layerDefinitionModel) { throw new Error('layerDefinitionModel is required'); }
|
||||
if (!opts.widgetDefinitionsCollection) { throw new Error('widgetDefinitionsCollection is required'); }
|
||||
if (!opts.tableStats) { throw new Error('tablestats is required'); }
|
||||
|
||||
this._layerDefinitionModel = opts.layerDefinitionModel;
|
||||
this._analysisDefinitionNode = this._layerDefinitionModel.getAnalysisDefinitionNodeModel();
|
||||
this._widgetDefinitionsCollection = opts.widgetDefinitionsCollection;
|
||||
this._tableStats = opts.tableStats;
|
||||
this._columnsCollection = new Backbone.Collection();
|
||||
},
|
||||
|
||||
getCollection: function () {
|
||||
return this._columnsCollection;
|
||||
},
|
||||
|
||||
createColumnCollection: function () {
|
||||
var tuplesItems = createTuplesItems(this._analysisDefinitionNode, this._layerDefinitionModel);
|
||||
var models = widgetsTypes(tuplesItems);
|
||||
this._columnsCollection.reset(models);
|
||||
this._generateGraphs();
|
||||
},
|
||||
|
||||
_generateGraphs: function () {
|
||||
var self = this;
|
||||
|
||||
var promises = this._columnsCollection.map(function (stat) {
|
||||
var table = stat.get('table');
|
||||
var column = stat.get('name');
|
||||
var deferred = new $.Deferred();
|
||||
|
||||
self._locateWidget(stat);
|
||||
|
||||
self._tableStats.graphFor(table, column, function (graph) {
|
||||
if (graph.stats) {
|
||||
stat.set({graph: graph});
|
||||
}
|
||||
deferred.resolve();
|
||||
});
|
||||
|
||||
return deferred.promise();
|
||||
}, this);
|
||||
|
||||
$.when.apply($, promises).done(function () {
|
||||
self.set({render: self._columnsCollection.length > 0});
|
||||
});
|
||||
},
|
||||
|
||||
findWidget: function (model) {
|
||||
var type = model.get('type');
|
||||
var column = model.get('name');
|
||||
|
||||
return this._widgetDefinitionsCollection.findWhere({
|
||||
type: type,
|
||||
source: model.analysisDefinitionNodeModel().id,
|
||||
column: column
|
||||
});
|
||||
},
|
||||
|
||||
_locateWidget: function (model) {
|
||||
var widget = this.findWidget(model);
|
||||
|
||||
if (widget) {
|
||||
model.set({
|
||||
selected: true,
|
||||
widget: widget
|
||||
}, {silent: true});
|
||||
}
|
||||
},
|
||||
|
||||
getColumnsWithWidgetAndGraph: function () {
|
||||
return this._columnsCollection.filter(function (model) {
|
||||
return !!model.get('widget') && !!model.get('graph');
|
||||
});
|
||||
},
|
||||
|
||||
getColumnsWithWidget: function () {
|
||||
return this._columnsCollection.filter(function (model) {
|
||||
return !!model.get('widget') && !model.get('graph');
|
||||
});
|
||||
},
|
||||
|
||||
getColumnsWithGraph: function () {
|
||||
return this._columnsCollection.filter(function (model) {
|
||||
return !!model.get('graph') && !model.get('widget');
|
||||
});
|
||||
},
|
||||
|
||||
getColumnsWithoutGraph: function () {
|
||||
return this._columnsCollection.filter(function (model) {
|
||||
return !model.get('graph') && !model.get('widget');
|
||||
});
|
||||
}
|
||||
});
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
<h2 class="CDB-Text CDB-Size-huge is-light u-secondaryTextColor">
|
||||
<%= body %>
|
||||
</h2>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<h2 class="CDB-Text CDB-Size-huge is-light u-secondaryTextColor">
|
||||
<%= message %>
|
||||
</h2>
|
||||
<h2 class="CDB-Text CDB-Size-huge is-light u-secondaryTextColor u-tSpace-xl">
|
||||
<%= action %>
|
||||
</h2>
|
||||
Executable
+53
@@ -0,0 +1,53 @@
|
||||
<% if (renderLoading) { %>
|
||||
<div class="Data-loaderContainer">
|
||||
<div class="CDB-Loader is-visible"></div>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="FormPlaceholder-paragraph u-tSpace-m u-flex">
|
||||
<div class="FormPlaceholder-step u-rSpace--m"></div>
|
||||
<div class="FormPlaceholder-inner">
|
||||
<span class="FormPlaceholder-label FormPlaceholder-size--med u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--med u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--med"></span>
|
||||
<span class="FormPlaceholder-label FormPlaceholder-size--short u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--long u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--long"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="FormPlaceholder-paragraph u-tSpace-m u-flex">
|
||||
<div class="FormPlaceholder-step u-rSpace--m"></div>
|
||||
<div class="FormPlaceholder-inner">
|
||||
<span class="FormPlaceholder-label FormPlaceholder-size--med u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--med u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--med"></span>
|
||||
<span class="FormPlaceholder-label FormPlaceholder-size--short u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--long u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--long"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="FormPlaceholder-paragraph u-tSpace-m u-flex">
|
||||
<div class="FormPlaceholder-step u-rSpace--m"></div>
|
||||
<div class="FormPlaceholder-inner">
|
||||
<span class="FormPlaceholder-label FormPlaceholder-size--med u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--med u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--med"></span>
|
||||
<span class="FormPlaceholder-label FormPlaceholder-size--short u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--long u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--long"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="FormPlaceholder-paragraph u-tSpace-m u-flex">
|
||||
<div class="FormPlaceholder-step u-rSpace--m"></div>
|
||||
<div class="FormPlaceholder-inner">
|
||||
<span class="FormPlaceholder-label FormPlaceholder-size--med u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--med u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--med"></span>
|
||||
<span class="FormPlaceholder-label FormPlaceholder-size--short u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--long u-tSpace-xl"></span>
|
||||
<span class="FormPlaceholder-title FormPlaceholder-size--long"></span>
|
||||
</div>
|
||||
</div>
|
||||
Executable
+271
@@ -0,0 +1,271 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var StatView = require('./stat-view');
|
||||
var dataNotReadyTemplate = require('./data-content-not-ready.tpl');
|
||||
var dataErrorTemplate = require('./data-content-error.tpl');
|
||||
var dataNoGeometryTemplate = require('./data-content-nogeometry.tpl');
|
||||
var layerTabMessageTemplate = require('builder/editor/layers/layer-tab-message.tpl');
|
||||
var actionErrorTemplate = require('builder/editor/layers/sql-error-action.tpl');
|
||||
var addFeaturePointIcon = require('builder/components/icon/templates/add-feature-point.tpl');
|
||||
var addFeatureLineIcon = require('builder/components/icon/templates/add-feature-line.tpl');
|
||||
var addFeaturePolygonIcon = require('builder/components/icon/templates/add-feature-polygon.tpl');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var STATES = require('builder/data/query-base-status');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'columnsModel',
|
||||
'infoboxModel',
|
||||
'overlayModel',
|
||||
'queryGeometryModel',
|
||||
'stackLayoutModel',
|
||||
'userActions',
|
||||
'layerContentModel',
|
||||
'layerDefinitionModel'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
module: 'editor/layers/layer-content-views/data/data-content-view',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._initViewState();
|
||||
this._initModels();
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
_initViewState: function () {
|
||||
this._viewState = new Backbone.Model({
|
||||
hasGeom: true,
|
||||
isDataFiltered: false,
|
||||
canBeGeoreferenced: false
|
||||
});
|
||||
|
||||
this._setViewState();
|
||||
},
|
||||
|
||||
_initModels: function () {
|
||||
this._columnsCollection = this._columnsModel.getCollection();
|
||||
|
||||
this.model = new Backbone.Model({
|
||||
renderLoading: true
|
||||
});
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._layerContentModel, 'change:state', this._setViewState);
|
||||
this.listenTo(this._columnsModel, 'change:render', this._handleStats);
|
||||
this.listenTo(this._columnsCollection, 'change:selected', this._handleWidget);
|
||||
this.listenTo(this._overlayModel, 'change:visible', this._toggleOverlay);
|
||||
this.listenTo(this._viewState, 'change', this.render);
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
|
||||
if (this._isErrored()) {
|
||||
this._showError();
|
||||
} else if (this._viewState.get('isDataFiltered')) {
|
||||
this._showFilteredData();
|
||||
} else if (this._viewState.get('canBeGeoreferenced')) {
|
||||
this._showGeocodeMessage();
|
||||
} else if (!this._viewState.get('hasGeom')) {
|
||||
this._showNoGeometryData();
|
||||
} else {
|
||||
this._renderStats();
|
||||
}
|
||||
|
||||
this._toggleOverlay();
|
||||
return this;
|
||||
},
|
||||
|
||||
_toggleOverlay: function () {
|
||||
var isDisabled = this._overlayModel.get('visible');
|
||||
this.$el.toggleClass('is-disabled', isDisabled);
|
||||
},
|
||||
|
||||
_showLoading: function () {
|
||||
this.$el.empty();
|
||||
this.$el.append(
|
||||
dataNotReadyTemplate({
|
||||
renderLoading: this._layerContentModel.get('renderLoading')
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_showError: function () {
|
||||
this.$el.append(
|
||||
dataErrorTemplate({
|
||||
body: _t('editor.error-query.body', {
|
||||
action: actionErrorTemplate({
|
||||
label: _t('editor.error-query.label')
|
||||
})
|
||||
})
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_showNoGeometryData: function () {
|
||||
this.$el.append(
|
||||
dataNoGeometryTemplate({
|
||||
message: _t('editor.data.no-geometry-data.message'),
|
||||
action: _t('editor.data.no-geometry-data.action-message', {
|
||||
pointIcon: addFeaturePointIcon(),
|
||||
lineIcon: addFeatureLineIcon(),
|
||||
polygonIcon: addFeaturePolygonIcon()
|
||||
})
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_showFilteredData: function () {
|
||||
this.$el.append(
|
||||
layerTabMessageTemplate({
|
||||
message: _t('editor.layers.warnings.no-data.message'),
|
||||
action: _t('editor.layers.warnings.no-data.action-message')
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_showGeocodeMessage: function () {
|
||||
this.$el.append(
|
||||
layerTabMessageTemplate({
|
||||
message: _t('editor.layers.warnings.geocode.message'),
|
||||
action: _t('editor.layers.warnings.geocode.action-message')
|
||||
})
|
||||
);
|
||||
},
|
||||
|
||||
_isErrored: function () {
|
||||
return this._layerContentModel.isErrored();
|
||||
},
|
||||
|
||||
_columnsReady: function () {
|
||||
return this._columnsModel.get('render') === true;
|
||||
},
|
||||
|
||||
_handleWidget: function (model) {
|
||||
model.get('selected') === true
|
||||
? this._addWidget(model)
|
||||
: this._destroyWidget(model);
|
||||
},
|
||||
|
||||
_addWidget: function (model) {
|
||||
var widgetModel = this._columnsModel.findWidget(model);
|
||||
|
||||
widgetModel
|
||||
? model.set({widget: widgetModel})
|
||||
: this._saveWidget(model);
|
||||
},
|
||||
|
||||
_destroyWidget: function (model) {
|
||||
var widgetModel = model.get('widget');
|
||||
widgetModel && widgetModel.destroy();
|
||||
},
|
||||
|
||||
_saveWidget: function (model) {
|
||||
var self = this;
|
||||
var widgetModel;
|
||||
|
||||
this._userActions.saveWidgetOption(model)
|
||||
.then(function (newWidgetModel) {
|
||||
widgetModel = newWidgetModel;
|
||||
|
||||
model.set({widget: widgetModel});
|
||||
|
||||
if (model.get('type') === 'time-series') {
|
||||
self._normalizeTimeSeriesColumn(model);
|
||||
}
|
||||
|
||||
return self._userActions.updateWidgetsOrder(model);
|
||||
})
|
||||
.then(function () {
|
||||
self.render();
|
||||
});
|
||||
},
|
||||
|
||||
_normalizeTimeSeriesColumn: function (lastModel) {
|
||||
var existingDefModel = this._columnsCollection.findWhere(function (model) {
|
||||
return model.get('type') === 'time-series' && model.get('selected') === true && model.cid !== lastModel.cid;
|
||||
});
|
||||
|
||||
existingDefModel && existingDefModel.set({
|
||||
selected: false,
|
||||
widget: null
|
||||
});
|
||||
},
|
||||
|
||||
_handleStats: function () {
|
||||
if (this._columnsReady()) {
|
||||
// we force render because the state could be ready already
|
||||
this._layerContentModel.set({
|
||||
state: STATES.fetched,
|
||||
renderLoading: true
|
||||
}, {
|
||||
silent: true
|
||||
});
|
||||
this.render();
|
||||
} else {
|
||||
// if there are not stats, we hide loading and show placeholder
|
||||
this._layerContentModel.set({
|
||||
state: STATES.fetching,
|
||||
renderLoading: false
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_renderStats: function () {
|
||||
if (!this._columnsReady()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var withWidgetAndGraph = this._columnsModel.getColumnsWithWidgetAndGraph();
|
||||
var withWidget = this._columnsModel.getColumnsWithWidget();
|
||||
var withGraph = this._columnsModel.getColumnsWithGraph();
|
||||
var withoutGraph = this._columnsModel.getColumnsWithoutGraph();
|
||||
var all;
|
||||
|
||||
if (withWidgetAndGraph.length === 0 && withWidget.length === 0 &&
|
||||
withGraph.length === 0 && withoutGraph.length === 0) {
|
||||
this._infoboxModel.set({
|
||||
state: 'no-data',
|
||||
renderLoading: false
|
||||
});
|
||||
this._showLoading();
|
||||
} else {
|
||||
this.$el.empty();
|
||||
all = _.union(withWidgetAndGraph, withWidget, withGraph, withoutGraph);
|
||||
this._renderColumns(all);
|
||||
}
|
||||
},
|
||||
|
||||
_renderColumns: function (stats) {
|
||||
_.each(stats, function (stat, index) {
|
||||
var view = new StatView({
|
||||
stackLayoutModel: this._stackLayoutModel,
|
||||
statModel: stat
|
||||
});
|
||||
|
||||
this.addView(view);
|
||||
this.$el.append(view.render().el);
|
||||
}, this);
|
||||
},
|
||||
|
||||
_setViewState: function () {
|
||||
var self = this;
|
||||
var hasGeomPromise = this._queryGeometryModel.hasValueAsync();
|
||||
var isDataFilteredPromise = this._layerDefinitionModel.isDataFiltered();
|
||||
var geoReferencePromise = this._layerDefinitionModel.canBeGeoreferenced();
|
||||
|
||||
Promise.all([hasGeomPromise, isDataFilteredPromise, geoReferencePromise])
|
||||
.then(function (values) {
|
||||
self._viewState.set({
|
||||
hasGeom: values[0],
|
||||
isDataFiltered: values[1],
|
||||
canBeGeoreferenced: values[2]
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
var Backbone = require('backbone');
|
||||
var _ = require('underscore');
|
||||
var UndoManager = require('builder/data/undo-manager.js');
|
||||
|
||||
/**
|
||||
* Data SQL Undo-redo model
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
|
||||
defaults: {
|
||||
content: ''
|
||||
},
|
||||
|
||||
initialize: function (attrs, opts) {
|
||||
this._history = this._generateHistory(opts && opts.history);
|
||||
|
||||
UndoManager.init(this, {
|
||||
track: true,
|
||||
history: this._history
|
||||
});
|
||||
},
|
||||
|
||||
_generateHistory: function (history) {
|
||||
if (history && history.length) {
|
||||
var data = _.reduce(history, function (memo, sql) {
|
||||
memo.push({
|
||||
content: sql
|
||||
});
|
||||
return memo;
|
||||
}, [], this);
|
||||
return data;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
getHistory: function () {
|
||||
return _.pluck(
|
||||
this.getUndoHistory(),
|
||||
'content'
|
||||
);
|
||||
}
|
||||
});
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
var CoreView = require('backbone/core-view');
|
||||
var CodeMirrorView = require('builder/components/code-mirror/code-mirror-view');
|
||||
var ErrorTemplate = require('./code-mirror-error.tpl');
|
||||
var FactoryHints = require('builder/editor/editor-hints/factory-hints');
|
||||
var SQLHints = require('builder/editor/editor-hints/sql-hints');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'layerDefinitionModel',
|
||||
'querySchemaModel',
|
||||
'codemirrorModel',
|
||||
'onApplyEvent'
|
||||
];
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
module: 'editor:layers:layer-content-views:data-sql-view',
|
||||
|
||||
className: 'Editor-dataContentSQL Editor-content',
|
||||
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
|
||||
FactoryHints.init({
|
||||
querySchemaModel: this._querySchemaModel,
|
||||
layerDefinitionModel: this._layerDefinitionModel,
|
||||
tokens: SQLHints
|
||||
});
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._querySchemaModel.on('change:query', this._onQueryChanged, this);
|
||||
this.add_related_model(this._querySchemaModel);
|
||||
},
|
||||
|
||||
_onQueryChanged: function (mdl, status) {
|
||||
if (status === 'fetched') {
|
||||
this.codeMirrorView.updateHints(SQLHints.reset().hints);
|
||||
}
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var hints = FactoryHints.reset().hints;
|
||||
this.codeMirrorView = new CodeMirrorView({
|
||||
model: this._codemirrorModel,
|
||||
hints: hints,
|
||||
mode: 'text/x-pgsql',
|
||||
tips: [
|
||||
_t('editor.data.code-mirror.tip')
|
||||
],
|
||||
errorTemplate: ErrorTemplate
|
||||
});
|
||||
|
||||
this.codeMirrorView.bind('codeSaved', this._triggerCodeSaved, this);
|
||||
this.addView(this.codeMirrorView);
|
||||
this.$el.append(this.codeMirrorView.render().el);
|
||||
},
|
||||
|
||||
_updateEditorContent: function () {
|
||||
this.codeMirrorView.setContent(this._layerDefinitionModel.get('sql'));
|
||||
},
|
||||
|
||||
_triggerCodeSaved: function (code, view) {
|
||||
this._onApplyEvent && this._onApplyEvent();
|
||||
}
|
||||
});
|
||||
+466
@@ -0,0 +1,466 @@
|
||||
var _ = require('underscore');
|
||||
var Backbone = require('backbone');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var PanelWithOptionsView = require('builder/components/view-options/panel-with-options-view');
|
||||
var ScrollView = require('builder/components/scroll/scroll-view');
|
||||
var DatasetBaseView = require('builder/components/dataset/dataset-base-view');
|
||||
var DataContentView = require('./data-content-view');
|
||||
var DataSQLView = require('./data-sql-view');
|
||||
var TabPaneView = require('builder/components/tab-pane/tab-pane-view');
|
||||
var TabPaneCollection = require('builder/components/tab-pane/tab-pane-collection');
|
||||
var Toggler = require('builder/components/toggler/toggler-view');
|
||||
var UndoButtons = require('builder/components/undo-redo/undo-redo-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 TableStats = require('builder/components/modals/add-widgets/tablestats');
|
||||
var DataColumnsModel = require('./data-columns-model');
|
||||
var SQLNotifications = require('builder/sql-notifications');
|
||||
var MetricsTracker = require('builder/components/metrics/metrics-tracker');
|
||||
var MetricsTypes = require('builder/components/metrics/metrics-types');
|
||||
var OnboardingLauncher = require('builder/components/onboardings/generic/generic-onboarding-launcher');
|
||||
var OnboardingView = require('builder/components/onboardings/layers/data-onboarding/data-onboarding-view');
|
||||
var checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
var LocaleHelper = require('builder/helpers/locale');
|
||||
|
||||
var ONBOARDING_KEY = 'layer-data-onboarding';
|
||||
|
||||
var REQUIRED_OPTS = [
|
||||
'widgetDefinitionsCollection',
|
||||
'stackLayoutModel',
|
||||
'userActions',
|
||||
'layerDefinitionModel',
|
||||
'userModel',
|
||||
'onboardings',
|
||||
'onboardingNotification',
|
||||
'layerContentModel'
|
||||
];
|
||||
|
||||
var getNotificationMessage = function (error, type) {
|
||||
var prefix = 'notifications.sql.';
|
||||
var key = prefix + type;
|
||||
var result = LocaleHelper.linkify(key);
|
||||
return result || (error && _.first(error)) || _t('notifications.sql.unknown.body');
|
||||
};
|
||||
|
||||
module.exports = DatasetBaseView.extend({
|
||||
initialize: function (opts) {
|
||||
checkAndBuildOpts(opts, REQUIRED_OPTS, this);
|
||||
this._nodeModel = opts.layerDefinitionModel.getAnalysisDefinitionNodeModel();
|
||||
this._querySchemaModel = this._nodeModel.querySchemaModel;
|
||||
this._queryGeometryModel = this._nodeModel.queryGeometryModel;
|
||||
|
||||
DatasetBaseView.prototype.initialize.call(this, {
|
||||
layerDefinitionModel: opts.layerDefinitionModel,
|
||||
editorModel: opts.editorModel,
|
||||
configModel: opts.configModel,
|
||||
querySchemaModel: this._querySchemaModel
|
||||
});
|
||||
|
||||
this._initModels();
|
||||
|
||||
this._tableStats = new TableStats({
|
||||
configModel: this._configModel,
|
||||
userModel: this._userModel
|
||||
});
|
||||
|
||||
this._columnsModel = new DataColumnsModel({}, {
|
||||
layerDefinitionModel: this._layerDefinitionModel,
|
||||
widgetDefinitionsCollection: this._widgetDefinitionsCollection,
|
||||
tableStats: this._tableStats
|
||||
});
|
||||
|
||||
SQLNotifications.track(this);
|
||||
|
||||
this._parseSQL = this._internalParseSQL.bind(this, this._afterAlterDataSuccess);
|
||||
|
||||
this._onboardingLauncher = new OnboardingLauncher({
|
||||
view: OnboardingView,
|
||||
onboardingNotification: this._onboardingNotification,
|
||||
notificationKey: ONBOARDING_KEY,
|
||||
onboardings: this._onboardings
|
||||
}, {
|
||||
editorModel: this._editorModel,
|
||||
selector: 'LayerOnboarding'
|
||||
});
|
||||
|
||||
this._configPanes();
|
||||
this._initBinds();
|
||||
this._initData();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this._launchOnboarding();
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
|
||||
this._initViews();
|
||||
|
||||
return this;
|
||||
},
|
||||
|
||||
_initModels: function () {
|
||||
this._infoboxModel = new InfoboxModel({
|
||||
state: this._isLayerHidden()
|
||||
? 'layer-hidden'
|
||||
: ''
|
||||
});
|
||||
|
||||
this._layerDefinitionModel.canBeGeoreferenced()
|
||||
.then(function (canBeGeoreferenced) {
|
||||
if (!this._isLayerHidden() && canBeGeoreferenced) {
|
||||
this._infoboxModel.set('state', 'georeference');
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
this._overlayModel = new Backbone.Model({
|
||||
visible: this._isLayerHidden()
|
||||
});
|
||||
|
||||
this._applyButtonStatusModel = new Backbone.Model({
|
||||
loading: false
|
||||
});
|
||||
|
||||
this._togglerModel = new Backbone.Model({
|
||||
labels: [_t('editor.data.data-toggle.values'), _t('editor.data.data-toggle.cartocss')],
|
||||
active: this._editorModel.isEditing(),
|
||||
disabled: this._editorModel.isDisabled(),
|
||||
tooltip: _t('editor.data.data-toggle.tooltip')
|
||||
});
|
||||
},
|
||||
|
||||
_launchOnboarding: function () {
|
||||
if (this._onboardingNotification.getKey(ONBOARDING_KEY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._editorModel.isEditing()) {
|
||||
this._onboardingLauncher.launch({
|
||||
numberOfWidgets: this._widgetDefinitionsCollection.length,
|
||||
hasTimeSeries: this._widgetDefinitionsCollection.isThereTimeSeries(),
|
||||
hasAnimatedTimeSeries: this._widgetDefinitionsCollection.isThereTimeSeries({ animated: true })
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_initData: function () {
|
||||
if (this._hasFetchedQuerySchema()) {
|
||||
this._onQuerySchemaStatusChange();
|
||||
}
|
||||
},
|
||||
|
||||
_redirectIfErrors: function () {
|
||||
var errors = this._querySchemaModel.get('query_errors');
|
||||
if (errors && errors.length > 0) {
|
||||
this._showErrors();
|
||||
this._editorModel.set({
|
||||
edition: true
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onQuerySchemaChange: function () {
|
||||
if (this._hasFetchedQuerySchema()) {
|
||||
this._codemirrorModel.set({ content: this._querySchemaModel.get('query') });
|
||||
}
|
||||
},
|
||||
|
||||
_onQuerySchemaStatusChange: function () {
|
||||
if (this._hasFetchedQuerySchema()) {
|
||||
this._columnsModel.createColumnCollection();
|
||||
}
|
||||
},
|
||||
|
||||
_hasFetchedQuerySchema: function () {
|
||||
return this._querySchemaModel.isFetched();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.listenTo(this._editorModel, 'change:edition', this._onChangeEdition);
|
||||
this.listenTo(this._editorModel, 'change:disabled', this._onChangeDisabled);
|
||||
this.listenTo(this._togglerModel, 'change:active', this._onTogglerChanged);
|
||||
|
||||
this.listenTo(this._querySchemaModel, 'change:query', this._onQuerySchemaChange);
|
||||
this.listenTo(this._querySchemaModel, 'change:status', this._onQuerySchemaStatusChange);
|
||||
this.listenTo(this._querySchemaModel, 'change:query_errors', this._showErrors);
|
||||
|
||||
this.listenTo(this._sqlModel, 'undo redo', function () {
|
||||
this._codemirrorModel.set('content', this._sqlModel.get('content'));
|
||||
}.bind(this));
|
||||
|
||||
this.listenTo(this._layerDefinitionModel, 'change:visible', this._infoboxState);
|
||||
|
||||
this._querySchemaModel.bind('change:status', function () {
|
||||
this._applyButtonStatusModel.set('loading', this._querySchemaModel.isFetching());
|
||||
}, this);
|
||||
},
|
||||
|
||||
_runQuery: function (query, callback) {
|
||||
// Should change the query to geometry model before the schema model
|
||||
this._queryGeometryModel.set({
|
||||
query: query
|
||||
});
|
||||
|
||||
this._querySchemaModel.set({
|
||||
query: query,
|
||||
status: 'unfetched'
|
||||
});
|
||||
|
||||
SQLNotifications.showNotification({
|
||||
status: 'loading',
|
||||
info: _t('notifications.sql.applying'),
|
||||
closable: false
|
||||
});
|
||||
|
||||
var saveSQL = _.after(2, callback);
|
||||
var errorSQL = function (err) {
|
||||
var type = err && err.status;
|
||||
var error = getNotificationMessage(err.error, type);
|
||||
this._forceErrors(error, {
|
||||
showEditorError: type === 400
|
||||
});
|
||||
}.bind(this);
|
||||
|
||||
this._querySchemaModel.fetch({ success: saveSQL, error: errorSQL });
|
||||
this._queryGeometryModel.fetch({ complete: saveSQL, error: errorSQL });
|
||||
},
|
||||
|
||||
_saveSQL: function (response) {
|
||||
if (response.status !== 200) {
|
||||
return;
|
||||
}
|
||||
|
||||
var query = this._codemirrorModel.get('content');
|
||||
|
||||
this._sqlModel.set('content', query);
|
||||
this._userActions.saveAnalysisSourceQuery(query, this._nodeModel, this._layerDefinitionModel);
|
||||
this._querySchemaModel.set('query_errors', []);
|
||||
|
||||
SQLNotifications.showNotification({
|
||||
status: 'success',
|
||||
info: _t('notifications.sql.success'),
|
||||
closable: true
|
||||
});
|
||||
|
||||
this._checkClearButton();
|
||||
|
||||
MetricsTracker.track(MetricsTypes.APPLIED_SQL, {
|
||||
node_id: this._nodeModel.get('id'),
|
||||
sql: this._nodeModel.get('query')
|
||||
});
|
||||
|
||||
MetricsTracker.track(MetricsTypes.USED_ADVANCED_MODE, {
|
||||
mode_type: 'sql'
|
||||
});
|
||||
},
|
||||
|
||||
_isQueryAlreadyApplied: function (query) {
|
||||
var history = this._sqlModel.getUndoHistory();
|
||||
var last = _.last(history);
|
||||
return last && last.content && last.content.toLowerCase() === query.toLowerCase();
|
||||
},
|
||||
|
||||
_updateVisNotification: function () {
|
||||
SQLNotifications.showNotification({
|
||||
status: 'success',
|
||||
info: _t('notifications.sql.success'),
|
||||
closable: true
|
||||
});
|
||||
},
|
||||
|
||||
_defaultSQL: function () {
|
||||
return this._nodeModel.getDefaultQuery();
|
||||
},
|
||||
|
||||
_afterAlterDataSuccess: function () {
|
||||
var originalQuery = this._defaultSQL();
|
||||
this._userActions.saveAnalysisSourceQuery(originalQuery, this._nodeModel, this._layerDefinitionModel);
|
||||
},
|
||||
|
||||
_hasAnalysisApplied: function () {
|
||||
return this._nodeModel.get('type') !== 'source';
|
||||
},
|
||||
|
||||
_onChangeEdition: function () {
|
||||
this._infoboxState();
|
||||
|
||||
var edition = this._editorModel.get('edition');
|
||||
var index = edition ? 1 : 0;
|
||||
this._collectionPane.at(index).set({ selected: true });
|
||||
this._togglerModel.set({ active: edition });
|
||||
},
|
||||
|
||||
_onChangeDisabled: function () {
|
||||
var disabled = this._editorModel.get('disabled');
|
||||
this._togglerModel.set({ disabled: disabled });
|
||||
},
|
||||
|
||||
_onTogglerChanged: function () {
|
||||
var checked = this._togglerModel.get('active');
|
||||
this._editorModel.set({ edition: checked });
|
||||
},
|
||||
|
||||
_configPanes: function () {
|
||||
var self = this;
|
||||
var tabPaneTabs = [{
|
||||
selected: !this._editorModel.get('edition'),
|
||||
createContentView: function () {
|
||||
return new ScrollView({
|
||||
createContentView: function () {
|
||||
return new DataContentView({
|
||||
className: 'Editor-content',
|
||||
stackLayoutModel: self._stackLayoutModel,
|
||||
columnsModel: self._columnsModel,
|
||||
queryGeometryModel: self._queryGeometryModel,
|
||||
userActions: self._userActions,
|
||||
infoboxModel: self._infoboxModel,
|
||||
overlayModel: self._overlayModel,
|
||||
layerContentModel: self._layerContentModel,
|
||||
layerDefinitionModel: self._layerDefinitionModel
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
createActionView: function () {
|
||||
return new CoreView();
|
||||
}
|
||||
}, {
|
||||
selected: this._editorModel.get('edition'),
|
||||
createContentView: function () {
|
||||
return new DataSQLView({
|
||||
layerDefinitionModel: self._layerDefinitionModel,
|
||||
querySchemaModel: self._querySchemaModel,
|
||||
codemirrorModel: self._codemirrorModel,
|
||||
onApplyEvent: self._parseSQL
|
||||
});
|
||||
},
|
||||
createActionView: function () {
|
||||
var isAnalysis = self._hasAnalysisApplied();
|
||||
if (!isAnalysis) {
|
||||
self._checkClearButton();
|
||||
return new UndoButtons({
|
||||
trackModel: self._sqlModel,
|
||||
editorModel: self._editorModel,
|
||||
clearModel: self._clearSQLModel,
|
||||
applyStatusModel: self._applyButtonStatusModel,
|
||||
applyButton: true,
|
||||
clearButton: true,
|
||||
onApplyClick: self._parseSQL,
|
||||
onClearClick: self._clearSQL.bind(self)
|
||||
});
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
this._collectionPane = new TabPaneCollection(tabPaneTabs);
|
||||
},
|
||||
|
||||
_confirmReadOnly: function () {
|
||||
this._infoboxModel.set({ state: '' });
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var self = this;
|
||||
|
||||
var infoboxSstates = [
|
||||
{
|
||||
state: 'readonly',
|
||||
createContentView: function () {
|
||||
return Infobox.createWithAction({
|
||||
type: 'code',
|
||||
title: _t('editor.data.messages.sql-readonly.title'),
|
||||
body: _t('editor.data.messages.sql-readonly.body'),
|
||||
action: {
|
||||
label: _t('editor.data.messages.sql-readonly.accept')
|
||||
}
|
||||
});
|
||||
},
|
||||
onAction: self._confirmReadOnly.bind(self)
|
||||
}, {
|
||||
state: 'no-data',
|
||||
createContentView: function () {
|
||||
return Infobox.createInfo({
|
||||
type: 'alert',
|
||||
title: _t('editor.data.messages.empty-data.title'),
|
||||
body: _t('editor.data.messages.empty-data.body')
|
||||
});
|
||||
}
|
||||
}, {
|
||||
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)
|
||||
}
|
||||
];
|
||||
|
||||
var infoboxCollection = new InfoboxCollection(infoboxSstates);
|
||||
|
||||
var panelWithOptionsView = new PanelWithOptionsView({
|
||||
className: 'Editor-content',
|
||||
editorModel: self._editorModel,
|
||||
infoboxModel: self._infoboxModel,
|
||||
infoboxCollection: infoboxCollection,
|
||||
createContentView: function () {
|
||||
return new TabPaneView({
|
||||
collection: self._collectionPane
|
||||
});
|
||||
},
|
||||
createControlView: function () {
|
||||
return new Toggler({
|
||||
model: self._togglerModel
|
||||
});
|
||||
},
|
||||
createActionView: function () {
|
||||
return new TabPaneView({
|
||||
collection: self._collectionPane,
|
||||
createContentKey: 'createActionView'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.$el.append(panelWithOptionsView.render().el);
|
||||
this.addView(panelWithOptionsView);
|
||||
|
||||
// TOFIX
|
||||
// If there are errors in the SQL, open the sql editor by default
|
||||
// we defer it to not interfere with the high order tabpane item selection
|
||||
setTimeout(this._redirectIfErrors.bind(this), 0);
|
||||
},
|
||||
|
||||
_infoboxState: function () {
|
||||
var edition = this._editorModel.get('edition');
|
||||
var isAnalysis = this._hasAnalysisApplied();
|
||||
|
||||
if (edition && isAnalysis) {
|
||||
this._codemirrorModel.set({ readonly: true });
|
||||
this._infoboxModel.set({ state: 'readonly' });
|
||||
} else if (this._isLayerHidden()) {
|
||||
this._infoboxModel.set({ state: 'layer-hidden' });
|
||||
this._overlayModel.set({ visible: true });
|
||||
} else {
|
||||
this._codemirrorModel.set({ readonly: false });
|
||||
this._infoboxModel.set({ state: '' });
|
||||
this._overlayModel.set({ visible: false });
|
||||
}
|
||||
},
|
||||
|
||||
_showHiddenLayer: function () {
|
||||
var savingOptions = {
|
||||
shouldPreserveAutoStyle: true
|
||||
};
|
||||
this._layerDefinitionModel.toggleVisible();
|
||||
this._userActions.saveLayer(this._layerDefinitionModel, savingOptions);
|
||||
},
|
||||
|
||||
_isLayerHidden: function () {
|
||||
return this._layerDefinitionModel.get('visible') === false;
|
||||
}
|
||||
});
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<ul class="u-flex CDB-Text CDB-Size-small u-upperCase u-bSpace u-secondaryTextColor">
|
||||
<li class='u-rSpace--m js-null'></li>
|
||||
<li class='js-percent'></li>
|
||||
</ul>
|
||||
<div class="js-category-stat"></div>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<h4 class="CDB-Text CDB-Size-huge js-formula-stat"></h4>
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
<div class="CDB-Text CDB-Size-small u-upperCase u-bSpace u-secondaryTextColor js-histogram-numbers"></div>
|
||||
<div class="js-histogram-stat"></div>
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
var _ = require('underscore');
|
||||
var CoreView = require('backbone/core-view');
|
||||
var template = require('./stat.tpl');
|
||||
var utils = require('builder/helpers/utils');
|
||||
var templateCategory = require('./stat-category.tpl');
|
||||
var templateHistogram = require('./stat-histogram.tpl');
|
||||
var templateFormula = require('./stat-formula.tpl');
|
||||
var TipsyTooltipView = require('builder/components/tipsy-tooltip-view');
|
||||
var Router = require('builder/routes/router');
|
||||
|
||||
module.exports = CoreView.extend({
|
||||
module: 'editor/layers/layer-content-views/data/stat-view',
|
||||
|
||||
className: 'StatsList',
|
||||
|
||||
events: {
|
||||
'click .js-checkbox': '_onSelect',
|
||||
'click .js-style': '_onEdit'
|
||||
},
|
||||
|
||||
initialize: function (opts) {
|
||||
if (!opts.statModel) throw new Error('statModel is required');
|
||||
|
||||
this._statModel = opts.statModel;
|
||||
this._initBinds();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
this.clearSubViews();
|
||||
this.$el.empty();
|
||||
this._initViews();
|
||||
return this;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this._statModel.on('change:selected', this.render, this);
|
||||
this.add_related_model(this._statModel);
|
||||
},
|
||||
|
||||
_initViews: function () {
|
||||
var graph = this._statModel.get('graph');
|
||||
var type = this._statModel.get('type');
|
||||
|
||||
this._renderTemplate();
|
||||
|
||||
if (graph) {
|
||||
// No graph for formula
|
||||
if (type === 'histogram') {
|
||||
this._showHistogram(graph);
|
||||
} else if (type === 'category') {
|
||||
this._showCategory(graph);
|
||||
} else if (type === 'formula') {
|
||||
this._showFormula(graph);
|
||||
}
|
||||
}
|
||||
|
||||
if (this._isSelected()) {
|
||||
var tooltip = new TipsyTooltipView({
|
||||
el: this.$('.js-help'),
|
||||
gravity: 'w',
|
||||
title: function () {
|
||||
return _t('editor.data.stats.help');
|
||||
}
|
||||
});
|
||||
this.addView(tooltip);
|
||||
}
|
||||
},
|
||||
|
||||
_renderTemplate: function () {
|
||||
this.$el.append(template({
|
||||
id: this.cid,
|
||||
column: this._statModel.get('title'),
|
||||
type: this._statModel.get('column'),
|
||||
graph: this._statModel.get('type'),
|
||||
isSelected: this._isSelected()
|
||||
}));
|
||||
},
|
||||
|
||||
_isSelected: function () {
|
||||
return this._statModel.get('selected');
|
||||
},
|
||||
|
||||
_onSelect: function () {
|
||||
this._statModel.set('selected', !this._statModel.get('selected'));
|
||||
},
|
||||
|
||||
_onEdit: function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
Router.goToWidget(this._statModel.get('widget').get('id'));
|
||||
},
|
||||
|
||||
_showCategory: function (graph) {
|
||||
if (graph.stats && graph.stats.freqs) {
|
||||
this.$('.js-stat').append(templateCategory());
|
||||
this.$('.js-category-stat').append(graph.getCategory({
|
||||
color: '#9DE0AD',
|
||||
width: 262,
|
||||
height: 10
|
||||
}));
|
||||
|
||||
this.$('.js-null').text(utils.formatDecimalPositions(graph.getNullsPercentage() * 100) + _t('editor.data.stats.null'));
|
||||
if (this.options.statModel.attributes.column !== 'boolean') {
|
||||
this.$('.js-percent').text(utils.formatDecimalPositions(graph.getPercentageInTopCategories() * 100) + _t('editor.data.stats.top-cat'));
|
||||
} else {
|
||||
var numOfTrues = graph.getTrues();
|
||||
if (utils.isNumeric(numOfTrues)) {
|
||||
this.$('.js-percent').text(utils.formatDecimalPositions(numOfTrues * 100) + _t('editor.data.stats.trues'));
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_showHistogram: function (graph) {
|
||||
if (graph.stats) {
|
||||
this.$('.js-stat').append(templateHistogram());
|
||||
this.$('.js-histogram-stat').append(graph.getHistogram({
|
||||
color: '#9DE0AD',
|
||||
width: 262,
|
||||
height: 20,
|
||||
bins: 20
|
||||
}));
|
||||
this.$('.js-histogram-numbers').text(graph.getNullsPercentage() + _t('editor.data.stats.null'));
|
||||
}
|
||||
},
|
||||
|
||||
_showFormula: function (graph) {
|
||||
var aggregation;
|
||||
if (graph.stats) {
|
||||
aggregation = graph.getCount();
|
||||
if (_.isNumber(aggregation)) {
|
||||
this.$('.js-stat').append(templateFormula());
|
||||
this.$('.js-formula-stat').text(aggregation.toFixed().toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
<div class="StatsList-item">
|
||||
<div class="u-rSpace--m">
|
||||
<input class="CDB-Checkbox js-checkbox" id="<%- id %>" type="checkbox" <% if (isSelected) { %>checked="checked"<% } %> />
|
||||
<span class="u-iBlock CDB-Checkbox-face"></span>
|
||||
</div>
|
||||
|
||||
<div class="WidgetList-inner js-inner">
|
||||
<div class="StatsList-header u-flex u-justifySpace u-alignCenter u-bSpace--m">
|
||||
<label class="u-ellipsis CDB-Text CDB-Size-medium" for="<%- id %>"><%- _t('editor.data.stats.add-widget') %></label>
|
||||
<% if (isSelected) { %>
|
||||
<button class="StatsList-style CDB-Text CDB-Size-small js-style u-actionTextColor js-help">
|
||||
<div class="StatsList-arrow CDB-Shape-Arrow is-blue u-iBlock u-rSpace--m"></div> <%- _t('editor.data.stats.edit') %>
|
||||
</button>
|
||||
<% } %>
|
||||
</div>
|
||||
<div class="u-flex u-alignCenter u-bSpace">
|
||||
<h2 class="js-title u-ellipsis CDB-Text CDB-Size-large u-rSpace--m"><%- column %></h2>
|
||||
<div class="StatsList-tag Tag Tag--outline Tag-outline--grey CDB-Text CDB-Size-small u-upperCase"><%- type %></div>
|
||||
</div>
|
||||
<div class="js-stat"></div>
|
||||
</div>
|
||||
</div>
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
var _ = require('underscore');
|
||||
var FormulaOptionModel = require('builder/components/modals/add-widgets/formula/formula-option-model');
|
||||
var CategoryOptionModel = require('builder/components/modals/add-widgets/category/category-option-model');
|
||||
var HistogramOptionModel = require('builder/components/modals/add-widgets/histogram/histogram-option-model');
|
||||
var TimeSeriesOptionModel = require('builder/components/modals/add-widgets/time-series/time-series-option-model');
|
||||
|
||||
var CARTODB_ID = 'cartodb_id';
|
||||
|
||||
module.exports = function (tuplesItems) {
|
||||
return _.reduce(tuplesItems, function (memo, tuples) {
|
||||
var columnModel = tuples[0].columnModel;
|
||||
var layerDef = tuples[0].layerDefinitionModel;
|
||||
var table = layerDef.getTableName();
|
||||
var type = columnModel.get('type');
|
||||
var columnName = columnModel.get('name');
|
||||
var model;
|
||||
|
||||
if (columnName === CARTODB_ID) {
|
||||
model = new FormulaOptionModel({
|
||||
tuples: tuples,
|
||||
table: table,
|
||||
title: _t('editor.data.stats.feature-count'),
|
||||
column: columnModel.get('type'),
|
||||
operation: 'count',
|
||||
name: columnName
|
||||
});
|
||||
memo.push(model);
|
||||
} else if (type === 'string' || type === 'boolean') {
|
||||
model = new CategoryOptionModel({
|
||||
tuples: tuples,
|
||||
table: table,
|
||||
title: columnName,
|
||||
name: columnName,
|
||||
column: columnModel.get('type'),
|
||||
aggregation: 'count' // or sum
|
||||
});
|
||||
memo.push(model);
|
||||
} else if (columnModel.get('type') === 'number') {
|
||||
model = new HistogramOptionModel({
|
||||
tuples: tuples,
|
||||
table: table,
|
||||
title: columnName,
|
||||
name: columnName,
|
||||
column: columnModel.get('type'),
|
||||
bins: 10
|
||||
});
|
||||
memo.push(model);
|
||||
} else if (columnModel.get('type') === 'date') {
|
||||
model = new TimeSeriesOptionModel({
|
||||
tuples: tuples,
|
||||
table: table,
|
||||
title: columnName,
|
||||
name: columnName,
|
||||
column: columnModel.get('type')
|
||||
});
|
||||
memo.push(model);
|
||||
}
|
||||
return memo;
|
||||
}, []);
|
||||
};
|
||||
Reference in New Issue
Block a user