linagora/openpaas-esn

View on GitHub
modules/linagora.esn.graceperiod/frontend/js/grace-period.service.js

Summary

Maintainability
D
1 day
Test Coverage
(function() {
  'use strict';

  angular.module('linagora.esn.graceperiod')
    .factory('gracePeriodService', gracePeriodService);

  function gracePeriodService(
    $timeout,
    $log,
    $q,
    _,
    notifyService,
    gracePeriodRestangularService,
    HTTP_LAG_UPPER_BOUND,
    GRACE_DELAY,
    ERROR_DELAY,
    $rootScope,
    gracePeriodLiveNotificationService,
    EsnI18nString,
    DEFAULT_GRACE_MESSAGE
  ) {
    var tasks = {};

    return {
      grace: grace,
      askUserForCancel: askUserForCancel,
      cancel: cancel,
      flush: flush,
      flushAllTasks: flushAllTasks,
      getTasksFor: getTasksFor,
      hasTask: hasTask
    };

    function removeTask(id) {
      var task = tasks[id];

      if (task) {
        task.notification && task.notification.close();
        delete tasks[id];
      }

      return !!task;
    }

    function retryCancelBeforeEnd(id, task, previousError) {
      return task.justBeforeEnd.then(function() {
        return gracePeriodRestangularService
          .one('tasks')
          .one(id)
          .withHttpConfig({timeout: HTTP_LAG_UPPER_BOUND}).remove();
      }, function() {

        throw previousError;
      });
    }

    function cancel(id) {
      var task = tasks[id];

      if (!removeTask(id)) {
        return $q.reject('Canceling invalid task id: ' + id);
      }

      return gracePeriodRestangularService
        .one('tasks')
        .one(id)
        .withHttpConfig({timeout: task.justBeforeEnd})
        .remove()
        .catch(function(error) {
          $log.error('Could not cancel graceperiod, we will try again at the end of the graceperiod', error);

          return retryCancelBeforeEnd(id, task, error);
        });
    }

    function flush(id) {
      if (!removeTask(id)) {
        return $q.reject('Flushing invalid task id: ' + id);
      }

      return gracePeriodRestangularService.one('tasks').one(id).put();
    }

    function flushAllTasks() {
      return $q.all(Object.keys(tasks).map(flush));
    }

    function getTasksFor(contextQuery) {
      var result = [];

      if (!angular.isDefined(contextQuery)) {
        return result;
      }

      Object.keys(tasks).forEach(function(taskId) {
        var contextMatched, taskContext = tasks[taskId].context;

        if (taskContext) {
          contextMatched = Object.keys(contextQuery).every(function(contextKey) {
            return taskContext[contextKey] === contextQuery[contextKey];
          });

          if (contextMatched) {
            result.push(taskId);
          }
        }
      });

      return result;
    }

    function timeoutPromise(duration) {
      return duration > 0 ? $timeout(angular.noop, duration) : $q.reject();
    }

    function addTask(taskId, context, notification, delay) {
      if (!taskId) {
        throw new Error('You should at least provide a task id');
      }

      tasks[taskId] = {
        notification: notification,
        context: context,
        justBeforeEnd: timeoutPromise((delay || GRACE_DELAY) - HTTP_LAG_UPPER_BOUND)
      };
    }

    function grace(options) {
      options = angular.extend({delay: GRACE_DELAY}, DEFAULT_GRACE_MESSAGE, options);

      if (!options.id) {
        throw new Error('You should at least provide an id');
      }

      var deferred = $q.defer();

      var userAskNotification = askUserForCancel(options.performedAction, options.cancelText, options.delay);
      var userCancelPromise = userAskNotification.promise;

      addTask(options.id, options.context, userAskNotification.notification, options.delay);

      var taskPromise = gracePeriodLiveNotificationService.registerListeners(options.id);
      var resolved = false;
      var cancelInTry = false;

      function checkNotResolved(callback) {
        return function() {
          if (!resolved) {
            callback.apply(this, arguments);
            resolved = true;
          }
        };
      }

      taskPromise.then(checkNotResolved(function() {
        displaySuccess(options.successText);
        deferred.resolve(cancelInTry ? {cancelled: true, cancelFailed: true} : {cancelled: false});
      }), checkNotResolved(function() {
        displayError(options.gracePeriodFail);
        deferred.reject(cancelInTry ? {cancelled: true, cancelFailed: false} : {cancelled: false});
      }));

      userCancelPromise.then(function(result) {
        if (!result.cancelled) {
          //we do not resolve the confirmation will be notified by the taskPromise
          return;
        }

        if (resolved) {
          displayError(options.cancelTooLate);
        } else {
          cancelInTry = true;
          cancel(options.id).then(checkNotResolved(function() {
            displaySuccess(options.cancelSuccess);
            deferred.reject({cancelled: true, cancelFailed: false});
            result.success();
          }), checkNotResolved(function(error) {
            result.error(options.cancelFailed, error.statusTask);
            deferred.resolve({cancelled: true, cancelFailed: true});
          }));
        }
      });

      deferred.promise.finally(function() {
        removeTask(options.id);
      });

      return deferred.promise;
    }

    function hasTask(taskId) {
      return !!tasks[taskId];
    }

    function displayMessage(message, type, delay) {
      if (!message) {
        return;
      }

      if (_.isString(message)) {
        notifyService({
          message: message
        }, {
          type: type,
          delay: delay
        });
      } else {
        var notification = notifyService({
          message: (message instanceof EsnI18nString) ? message : message.text,
          hideCross: message.hideCross
        }, {
          type: type,
          delay: message.delay || delay
        });

        notification.setCancelAction({
          linkText: message.actionText,
          action: function() {
            $rootScope.$applyAsync(function() {
              message.action();
            });
          }
        });
      }
    }

    function displayError(errorMessage) {
      displayMessage(errorMessage, 'danger', ERROR_DELAY);
    }

    function displaySuccess(message) {
      displayMessage(message, 'success');
    }

    function askUserForCancel(text, linkText, delay) {
      var deferred = $q.defer();

      var notification = notifyService({
        message: text
      }, {
        type: 'success',
        delay: delay,
        onClosed: function() {
          $rootScope.$applyAsync(function() {
            deferred.resolve({ cancelled: false });
          });
        }
      });

      notification.setCancelAction({
        linkText: linkText,
        action: function() {
          $rootScope.$applyAsync(function() {
            deferred.resolve({
              cancelled: true,
              success: function() {
                notification.close();
              },
              error: displayError
            });
          });
        }
      });

      return {
        notification: notification,
        promise: deferred.promise
      };
    }
  }
})();