CartoDB/cartodb20

View on GitHub
lib/assets/javascripts/cartodb/table/header/header_sync_info.js

Summary

Maintainability
A
35 mins
Test Coverage

  /**
   *  Header sync info when visualization is table type
   *
   *  - If there is any change in the state, it will be rendered again.
   *
   *  new cdb.admin.SyncInfo({
   *    model: synchronization_model
   *  });
   */

  cdb.admin.SyncInfo = cdb.core.View.extend({

    tagName: 'div',
    className: 'sync_info',

    _SYNC_GAP: 15,      // Gap necessary to perform next synchronization
                        // Value in MINUTES
    _POLLING_GAP: 15,   // Gap necessary to start polling and checking
                        // synchronization. Value in SECONDS

    _TEXTS: {
      enabled:            _t('This table will be synced again <%- run_at %>'),
      few_moments:        _t('in a few moments'),
      sync_now_disabled:  _t('You will be able to sync manually <%- gap %> minutes after your last synchronization')
    },

    events: {
      'click a.sync_options': '_onClickOptions',
      'click a.sync_now':     '_onClickSyncNow'
    },

    initialize: function() {
      _.bindAll(this, '_startSync');

      this.dataLayer = this.options.dataLayer;
      this.table = this.dataLayer.table;
      this.model = this.table.synchronization;
      this.template = cdb.templates.getTemplate('table/header/views/sync_info_content');
      this.model.bind('change', this.render, this);

      // Check sync now button
      if (this._isSyncing()) {
        this._showSyncNow();
        this._startSync();
      }
    },

    render: function() {
      var attrs = _.clone(this.model.attributes);
      var self = this;

      // Destroy tipsy just in case
      this._destroyTipsy();

      attrs.ran_at = moment(attrs.ran_at || new Date()).fromNow();

      // Due to the time we need to polling, we have to display to the user
      // that the sync will be in a moment
      if (!attrs.run_at || (new Date(attrs.run_at) <= new Date())) {
        attrs.run_at = this._TEXTS.few_moments;
      } else {
        attrs.run_at = moment(attrs.run_at).fromNow();
      }

      // Can table be synced?
      attrs.canSync = this._canTableSyncNow() ? true : false ;

      // Come from external source?
      attrs.fromExternalSource = this.model.get("from_external_source");

      // Render
      this.$el
        .html(this.template(attrs))
        .attr('class', '')
        .addClass(attrs.state + ' ' + this.className);

      // Tipsy?
      this.$(".sync_now_disabled").tipsy({
        gravity:  's',
        fade:     true,
        title: function() {
          return _.template(self._TEXTS.sync_now_disabled)({ gap: self._SYNC_GAP })
        }
      });

      return this;
    },

    _bindEvents: function() {
      this.model.bind('change:state', this._onStateChange, this);
    },

    _unbindEvents: function() {
      this.model.unbind('change:state', this._onStateChange, this)
    },

    // Helpers

    _canTableSyncNow: function() {
      var ran_at = new Date(this.model.get('ran_at'));
      var now = new Date();
      var state = this.model.get('state');
      var gap = this._SYNC_GAP*60*1000;   // Importer needs some time to perform the next sync,
                                          // set 15 min as default.

      // If table is syncing... false!
      if (state === "syncing") {
        return false
      }

      if (( now.getTime() - ran_at.getTime() ) > gap) {
        return true;
      } else {
        return false;
      }
    },

    _isSyncing: function() {
      return this.model.get('state') === "syncing";
    },


    // UI bind events //

    _onClickSyncNow: function(e) {
      if (e) this.killEvent(e);

      if (this._canTableSyncNow()) {
        // Set syncing state
        this.model.set('state', 'syncing');
        // Show dialog
        this._showSyncNow();
        // Enqueue synchronization
        this.model.syncNow(this._startSync);
      }
    },

    _onClickOptions: function(e) {
      if (e) this.killEvent(e);
      var dialog = new cdb.editor.SyncView({
        clean_on_hide: true,
        enter_to_confirm: true,
        table: this.table
      });
      dialog.appendToBody();
    },


    _startSync: function() {
      // Render again
      this.render();
      // Bind events
      this._bindEvents();
      // We MUST wait before start polling
      var self = this;
      _.delay(function() {
        self.model.pollCheck();
      }, (this._POLLING_GAP * 1000)); // polling gap * miliseconds
    },

    _finishSync: function() {
      this._unbindEvents();
      this.model.destroyCheck();
      this._hideSyncNow();

      // Reload table and map data
      this.dataLayer.invalidate();
      this.table.data().refresh();
    },

    _onStateChange: function() {
      this._finishSync();

      // Success state could be wrong if any error_code
      // or error_message appears in the model
      if (this.model.get('state') === "success" &&
          (this.model.get('error_code') || this.model.get('error_message'))
        ) {
        this.model.set('state', 'failure');
      }

      this.render();
    },


    // Show or hide sync now modal //

    _showSyncNow: function(e) {
      if (e) this.killEvent(e);

      var modal = cdb.editor.ViewFactory.createDialogByTemplate('common/templates/loading', {
        title: 'Your dataset is being synced…',
        quote: "This action will take some time. In the meantime the UI is disabled, but APIs will work as usual."
      }, { sticky: true }).render();
      this.sync_now = modal;
      modal.appendToBody();
    },

    _hideSyncNow: function() {
      if (this.sync_now) {
        this.sync_now.hide();
      }
    },


    _destroyTipsy: function() {
      // Remove tipsy
      if (this.$('.sync_now_disabled').data('tipsy')) {
        this.$('.sync_now_disabled')
          .unbind('mouseenter mouseleave')
          .data('tipsy').remove();
      }
    },

    clean: function() {
      this._destroyTipsy();
      cdb.core.View.prototype.clean.call(this);
    }

  })