Initial commit
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
const Backbone = require('backbone');
|
||||
const ImportsCollection = require('dashboard/data/imports-collection');
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
const pollingsTimer = 3000;
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'userModel',
|
||||
'configModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Background polling default model
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Model.extend({
|
||||
defaults: {
|
||||
showGeocodingDatasetURLButton: false,
|
||||
showSuccessDetailsButton: true,
|
||||
geocodingsPolling: false, // enable geocodings polling
|
||||
importsPolling: false // enable imports polling
|
||||
},
|
||||
|
||||
initialize: function (attributes, options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this.importsCollection = options.importsCollection || new ImportsCollection(null, {
|
||||
userModel: this._userModel,
|
||||
configModel: this._configModel
|
||||
});
|
||||
this._initBinds();
|
||||
this.startPollings();
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.importsCollection.bind('change:state', function (mdl) {
|
||||
this.trigger('change', mdl, this);
|
||||
this._onImportsStateChange(mdl);
|
||||
}, this);
|
||||
this.importsCollection.bind('remove', function (mdl) {
|
||||
this.trigger('importRemoved', mdl, this);
|
||||
}, this);
|
||||
this.importsCollection.bind('add', function (mdl) {
|
||||
this.trigger('importAdded', mdl, this);
|
||||
}, this);
|
||||
},
|
||||
|
||||
// Helper functions
|
||||
|
||||
getTotalFailedItems: function () {
|
||||
return this.importsCollection.failedItems().length;
|
||||
},
|
||||
|
||||
removeImportItem: function (mdl) {
|
||||
if (!mdl) {
|
||||
return false;
|
||||
}
|
||||
this.importsCollection.remove(mdl);
|
||||
},
|
||||
|
||||
addImportItem: function (mdl) {
|
||||
if (!mdl) {
|
||||
return false;
|
||||
}
|
||||
this.importsCollection.add(mdl);
|
||||
},
|
||||
|
||||
canAddImport: function () {
|
||||
return this.importsCollection.canImport();
|
||||
},
|
||||
|
||||
getTotalImports: function () {
|
||||
return this.importsCollection.size();
|
||||
},
|
||||
|
||||
getTotalPollings: function () {
|
||||
return this.importsCollection.size();
|
||||
},
|
||||
|
||||
stopPollings: function () {
|
||||
if (this.get('importsPolling')) {
|
||||
this.importsCollection.destroyCheck();
|
||||
}
|
||||
},
|
||||
|
||||
startPollings: function () {
|
||||
// Don't start pollings inmediately,
|
||||
// wait some seconds
|
||||
setTimeout(() => {
|
||||
if (this.get('importsPolling')) {
|
||||
this.importsCollection.pollCheck();
|
||||
}
|
||||
}, pollingsTimer);
|
||||
},
|
||||
|
||||
// onChange functions
|
||||
_onImportsStateChange: function () {},
|
||||
|
||||
clean: function () {
|
||||
this.importsCollection.unbind(null, null, this);
|
||||
Backbone.Model.prototype.clean.apply(this);
|
||||
}
|
||||
|
||||
});
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
const BackgroundPollingModel = require('dashboard/data/background-polling/background-polling-model');
|
||||
|
||||
/**
|
||||
* Background polling model for the dashboard context.
|
||||
*/
|
||||
module.exports = BackgroundPollingModel.extend({
|
||||
|
||||
_onImportsStateChange: function (importsModel) {
|
||||
if (this._shouldRedirect(importsModel)) {
|
||||
this._redirectTo(importsModel.getRedirectUrl(this._userModel));
|
||||
return;
|
||||
}
|
||||
|
||||
if (importsModel.hasCompleted()) {
|
||||
this.trigger('importCompleted', importsModel, this);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines if the user can be redirected after the given
|
||||
* import has been completed. The following conditions must be true:
|
||||
* - Import has been completed
|
||||
* - No imports are still running
|
||||
* - No other imports have been previously completed
|
||||
* - Import has a redirect URL for the given user
|
||||
* - Import was NOT a twitter import
|
||||
* - Number of tables created:
|
||||
* - n if import was a .carto file
|
||||
* - 1 otherwise
|
||||
*/
|
||||
_shouldRedirect: function (importsModel) {
|
||||
return importsModel.hasCompleted() &&
|
||||
this.importsCollection.allImportsCompletedOrFailed() &&
|
||||
this.importsCollection.getCompletedItemsCount() === 1 &&
|
||||
!importsModel.isTwitterImport() &&
|
||||
importsModel.getRedirectUrl(this._userModel) &&
|
||||
(importsModel.getNumberOfTablesCreated() === 1 ||
|
||||
importsModel.isCartoImport());
|
||||
},
|
||||
|
||||
_redirectTo: function (url) {
|
||||
window.location = url;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,22 @@
|
||||
const _ = require('underscore');
|
||||
const Poller = require('./poller');
|
||||
|
||||
const GeocodingModelPoller = function (model) {
|
||||
const POLLING_INTERVAL = 2000;
|
||||
|
||||
const options = {
|
||||
interval: POLLING_INTERVAL,
|
||||
stopWhen: function (model) {
|
||||
return model.hasFailed() || model.hasCompleted();
|
||||
},
|
||||
error: function (model) {
|
||||
model.trigger('change');
|
||||
}
|
||||
};
|
||||
|
||||
Poller.call(this, model, options);
|
||||
};
|
||||
|
||||
GeocodingModelPoller.prototype = _.extend({}, Poller.prototype);
|
||||
|
||||
module.exports = GeocodingModelPoller;
|
||||
@@ -0,0 +1,116 @@
|
||||
const Backbone = require('backbone');
|
||||
const _ = require('underscore');
|
||||
const GeocodingModelPoller = require('./geocoding-model-poller');
|
||||
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'configModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Geocoding model
|
||||
*
|
||||
*/
|
||||
module.exports = Backbone.Model.extend({
|
||||
options: {
|
||||
startPollingAutomatically: true
|
||||
},
|
||||
|
||||
defaults: {
|
||||
kind: '',
|
||||
formatter: '',
|
||||
table_name: '',
|
||||
state: ''
|
||||
},
|
||||
|
||||
initialize: function (options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
|
||||
this._initBinds();
|
||||
// TODO: Check if we can remove this thing
|
||||
_.extend(this.options, options);
|
||||
|
||||
this.poller = new GeocodingModelPoller(this);
|
||||
|
||||
if (this.options.startPollingAutomatically) {
|
||||
this._checkModel();
|
||||
}
|
||||
},
|
||||
|
||||
url: function (method) {
|
||||
var version = this._configModel.urlVersion('geocoding', method);
|
||||
|
||||
const base = `/api/${version}/geocodings/`;
|
||||
|
||||
if (this.isNew()) {
|
||||
return base;
|
||||
}
|
||||
return base + this.id;
|
||||
},
|
||||
|
||||
setUrlRoot: function (urlRoot) {
|
||||
this.urlRoot = urlRoot;
|
||||
},
|
||||
|
||||
_initBinds: function () {
|
||||
this.bind('change:id', this._checkModel, this);
|
||||
},
|
||||
|
||||
_checkModel: function () {
|
||||
if (this.get('id')) {
|
||||
this.pollCheck();
|
||||
} else {
|
||||
this._saveModel();
|
||||
}
|
||||
},
|
||||
|
||||
_saveModel: function () {
|
||||
if (this.isNew()) {
|
||||
this.save({}, {
|
||||
error: () => {
|
||||
this.set({
|
||||
state: 'failed',
|
||||
error: {
|
||||
title: 'Oops, there was a problem',
|
||||
description: 'Unfortunately there was an error starting the geocoder'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
pollCheck: function () {
|
||||
this.poller.start();
|
||||
},
|
||||
|
||||
destroyCheck: function () {
|
||||
this.poller.stop();
|
||||
},
|
||||
|
||||
getError: function () {
|
||||
return this.get('error');
|
||||
},
|
||||
|
||||
hasFailed: function () {
|
||||
var state = this.get('state');
|
||||
return state === 'failed' || state === 'reset' || state === 'cancelled';
|
||||
},
|
||||
|
||||
hasCompleted: function () {
|
||||
return this.get('state') === 'finished';
|
||||
},
|
||||
|
||||
isOngoing: function () {
|
||||
return !this.hasCompleted() && !this.hasFailed();
|
||||
},
|
||||
|
||||
cancelGeocoding: function () {
|
||||
this.save({ state: 'cancelled' }, { wait: true });
|
||||
},
|
||||
|
||||
resetGeocoding: function () {
|
||||
this.set('state', 'reset');
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
const _ = require('underscore');
|
||||
const Backbone = require('backbone');
|
||||
const GeocodingModel = require('./geocoding-model');
|
||||
const pollTimer = 60000;
|
||||
|
||||
const checkAndBuildOpts = require('builder/helpers/required-opts');
|
||||
|
||||
const REQUIRED_OPTS = [
|
||||
'configModel',
|
||||
'userModel'
|
||||
];
|
||||
|
||||
/**
|
||||
* Geocodings collection
|
||||
*
|
||||
* - Check ongoing geocodings in order to add them
|
||||
* to the collection.
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = Backbone.Collection.extend({
|
||||
model: GeocodingModel,
|
||||
|
||||
url: function (method) {
|
||||
const version = this._configModel.urlVersion('geocoding', method);
|
||||
return `/api/${version}/geocodings`;
|
||||
},
|
||||
|
||||
initialize: function (models, options) {
|
||||
checkAndBuildOpts(options, REQUIRED_OPTS, this);
|
||||
},
|
||||
|
||||
parse: function (response) {
|
||||
_.each(response.geocodings, data => {
|
||||
// Check if that geocoding exists...
|
||||
const geocodings = this.filter(mdl => mdl.get('id') === data.id);
|
||||
|
||||
if (geocodings.length === 0) {
|
||||
this._checkOngoingGeocoding(
|
||||
new GeocodingModel(data, { startPollingAutomatically: false })
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return this.models;
|
||||
},
|
||||
|
||||
_checkOngoingGeocoding: function (model) {
|
||||
if (!this._visModel) {
|
||||
// If there is NOT a vis, let's start polling
|
||||
// this geocoding model
|
||||
this.add(model);
|
||||
model.pollCheck();
|
||||
} else {
|
||||
// If there is a vis, let's check if that
|
||||
// geocoding belongs to the visualization
|
||||
this.vis.map.layers.each(layer => {
|
||||
if (layer.table && layer.table.id === model.get('table_name')) {
|
||||
this.add(model);
|
||||
model.pollCheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
// Public methods
|
||||
|
||||
canGeocode: function () {
|
||||
return !this.any(function (model) {
|
||||
return model.isOngoing();
|
||||
});
|
||||
},
|
||||
|
||||
fetchGeocodings: function () {
|
||||
this.fetch({
|
||||
error: e => this.destroyCheck()
|
||||
});
|
||||
},
|
||||
|
||||
pollCheck: function (i) {
|
||||
if (this.pollTimer) return;
|
||||
|
||||
this.pollTimer = setInterval(() => {
|
||||
this.fetchGeocodings();
|
||||
}, pollTimer);
|
||||
|
||||
this.fetchGeocodings();
|
||||
},
|
||||
|
||||
destroyCheck: function () {
|
||||
clearInterval(this.pollTimer);
|
||||
delete this.pollTimer;
|
||||
},
|
||||
|
||||
failedItems: function () {
|
||||
return this.filter(function (item) {
|
||||
return item.hasFailed();
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
const _ = require('underscore');
|
||||
/*
|
||||
* Periodically fetches a model/collection. It waits for ongoing
|
||||
* fetch requests before trying to fetch again. A stop condition
|
||||
* can be specified.
|
||||
*
|
||||
* Usage example:
|
||||
*
|
||||
* var poller = new Poller(model, {
|
||||
* interval: 1000,
|
||||
* stopWhen: function (model) {
|
||||
* return model.get('state') === 'completed';
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* poller.start();
|
||||
*
|
||||
* // ...
|
||||
*
|
||||
* poller.stop();
|
||||
*
|
||||
*/
|
||||
const Poller = function (model, options) {
|
||||
this.model = model;
|
||||
this.numberOfRequests = 0;
|
||||
this.polling = false;
|
||||
this.interval = options['interval'];
|
||||
|
||||
if (typeof this.interval !== 'function') {
|
||||
this.interval = function () { return options['interval']; };
|
||||
}
|
||||
|
||||
this.stopWhen = options['stopWhen'];
|
||||
this.error = options['error'];
|
||||
this.autoStart = options['autoStart'];
|
||||
|
||||
if (this.autoStart) {
|
||||
this.start();
|
||||
}
|
||||
};
|
||||
|
||||
Poller.prototype.start = function () {
|
||||
if (this.timeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._scheduleFetch();
|
||||
};
|
||||
|
||||
Poller.prototype._scheduleFetch = function () {
|
||||
this.timeout = setTimeout(this._fetch.bind(this), this.interval(this.numberOfRequests));
|
||||
};
|
||||
|
||||
Poller.prototype._fetch = function () {
|
||||
if (!this.polling) {
|
||||
this.polling = true;
|
||||
this.model.fetch({
|
||||
success: () => {
|
||||
this.polling = false;
|
||||
this.numberOfRequests++;
|
||||
if (this._continuePolling()) {
|
||||
this._scheduleFetch();
|
||||
}
|
||||
},
|
||||
error: (e) => {
|
||||
_.isFunction(this.error) && this.error(this.model);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Poller.prototype._continuePolling = function () {
|
||||
return !this.stopWhen ||
|
||||
(_.isFunction(this.stopWhen) && !this.stopWhen(this.model));
|
||||
};
|
||||
|
||||
Poller.prototype.stop = function () {
|
||||
this.polling = false;
|
||||
clearTimeout(this.timeout);
|
||||
delete this.timeout;
|
||||
};
|
||||
|
||||
module.exports = Poller;
|
||||
Reference in New Issue
Block a user