CartoDB/cartodb20

View on GitHub
lib/assets/javascripts/cartodb/common/background_polling/models/upload_model.js

Summary

Maintainability
F
3 days
Test Coverage
var Backbone = require('backbone-cdb-v3');
var UploadConfig = require('./upload_config');
var Utils = require('cdb.Utils');
var moment = require('moment-v3');
var _ = require('underscore-cdb-v3');

/**
 * Model that let user upload files to our endpoints.
 *
 * NOTE: this model extends Backbone.Model instead of cdb.core.Model, because it's required for the
 * vendor/backbone-model-file-upload.
 */
module.exports = Backbone.Model.extend({

  url: '/api/v1/imports',

  fileAttribute: 'filename',

  defaults: {
    type: '',
    value: '',
    interval: 0,
    privacy: '',
    progress: 0,
    state: 'idle',
    service_item_id: '',
    service_name: '',
    option: '',
    content_guessing: true,
    type_guessing: true,
    create_vis: true
  },

  initialize: function(val, opts) {
    this.user = opts && opts.user;
    this._initBinds();
    // We need to validate entry attributes
    this._validate(this.attributes, { validate: true});
  },

  isValidToUpload: function() {
    return this.get('value') && this.get('state') !== 'error';
  },

  setFresh: function(d) {
    if (d && !_.isEmpty(d)) {
      // Set upload properties except create_vis (defined when created)
      this.set(_.omit(d, 'create_vis'));
    } else {
      this.clear();
    }
  },

  _initBinds: function() {
    this.bind('progress', function(progress) {
      this.set({
        progress: progress*100,
        state: 'uploading'
      });
    }, this);

    this.bind('change:value', function() {
      if (this.get('state') === "error") {
        this.set({ state: 'idle' })
        this.unset('get_error_text', { silent: true });
      }
    }, this);

    this.bind('error invalid', function(m, d) {
      this.set({
        state: 'error',
        error_code: (d && d.error_code) || '',
        get_error_text: {
          title: 'Invalid import',
          what_about: (d && d.msg) || ''
        }
      }, { silent: true });
      // We need this, if not validate will run again and again and again... :(
      this.trigger('change');
    }, this);
  },

  validate: function(attrs) {
    if (!attrs) return;

    if (attrs.type === "file") {
      // Number of files
      if (attrs.value && attrs.value.length) {
        return {
          msg: "Unfortunately only one file is allowed per upload"
        }
      }

      // File name
      var name = attrs.value.name;
      if (!name) {
        return {
          msg: "File name should be defined"
        }
      }

      // File extension
      var ext = name.substr(name.lastIndexOf('.') + 1);
      if (ext) {
        ext = ext.toLowerCase();
      }
      if (!_.contains(UploadConfig.fileExtensions, ext)) {
        return {
          msg: "Unfortunately this file extension is not allowed"
        }
      }
      // File size
      if (this.user && ((this.user.get('remaining_byte_quota') * UploadConfig.fileTimesBigger) < attrs.value.size)) {
        return {
          msg: "Unfortunately the size of the file is bigger than your remaining quota",
          error_code: 8001
        }
      }
    }

    if (attrs.type === "remote") {
      // Valid remote visualization id?
      if (!attrs.remote_visualization_id) {
        return {
          msg: "The remote visualization id was not specified"
        }

      }
      // Remote size?
      if (this.user && attrs.size && ((this.user.get('remaining_byte_quota') * UploadConfig.fileTimesBigger) < attrs.size)) {
        return {
          msg: "Unfortunately the size of the remote dataset is bigger than your remaining quota",
          error_code: 8001
        }
      }
    }

    if (attrs.type === "url") {
      // Valid URL?
      if (!Utils.isURL(attrs.value)) {
        return {
          msg: "Unfortunately the URL provided is not valid"
        }
      }
    }

    if (attrs.type === "sql") {
      if (!attrs.value) {
        return {
          msg: "Query is not provided"
        }
      }
    }

    if (attrs.type === "duplication") {
      if (!attrs.value) {
        return {
          msg: "Dataset copy is not defined"
        }
      }
    }

    if (attrs.type === "service" && attrs.service_name === "twitter_search") {
      var service_item_id = attrs.service_item_id;

      // Empty?
      if (!service_item_id || _.isEmpty(service_item_id)) {
        return {
          msg: "Twitter data is empty"
        }
      }

      // Categories?
      if (_.isEmpty(service_item_id.categories)) {
        return {
          msg: "Twitter categories are not valid"
        }
      }

      // Dates?
      var dates = service_item_id.dates;
      if (!dates || _.isEmpty(dates)) {
        return {
          msg: "Twitter dates are empty"
        }
      }
      var isToDateValid = moment(dates.fromDate) <= moment(new Date());
      if (!dates.fromDate || !dates.toDate || !isToDateValid) {
        return {
          msg: "Twitter dates are not valid"
        }
      }
    }
  },

  isValid: function() {
    return this.get('value') && this.get('state') !== "error"
  },

  upload: function() {
    if (this.get('type') === "file") {
      var self = this;
      this.xhr = this.save(
        {
          filename: this.get('value')
        },
        {
          success: function(m) {
            m.set('state', 'uploaded');
          },
          error: function(m, msg) {

            var message = 'Unfortunately there was a connection error';

            if (msg && msg.status === 429) {
              var response = JSON.parse(msg.responseText);
              message = response.errors.imports;
            }

            self.set({
              state: 'error',
              get_error_text: { title: 'There was an error', what_about: message }
            });

          },
          complete: function() {
            delete self.xhr;
          }
        }
      );
    }
  },

  stopUpload: function() {
    if (this.xhr) this.xhr.abort();
  },

  setGuessing: function(val) {
    this.set({
      type_guessing: val,
      content_guessing: val
    });
  }

});