Initial commit

This commit is contained in:
zhongjin
2020-06-15 10:58:47 +08:00
commit 4f1dfe7564
8590 changed files with 1516878 additions and 0 deletions
@@ -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);
}
});
@@ -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;