<commit_before>// --------------------------------------------------------------------------------------------------------------------
//
// oauth.js - the base class for all Oauth 1.0a web services in node-awssum
//
// Copyright (c) 2012 AppsAttic Ltd - http://www.appsattic.com/
// Written by Andrew Chilton <chilts@appsattic.com>
//
// License: http://opensource.org/licenses/MIT
//
// --------------------------------------------------------------------------------------------------------------------

// --------------------------------------------------------------------------------------------------------------------
// requires

// built-ins
var util = require('util');
var crypto = require('crypto');

// dependencies
var _ = require('underscore');
var passgen = require('passgen');

// our own
var awssum = require('./awssum');
var escape = require('../lib/escape.js');

// --------------------------------------------------------------------------------------------------------------------
// package variables

var MARK = 'oauth: ';

var debug = false;

// --------------------------------------------------------------------------------------------------------------------
// constructor

var OAuth = function(consumerKey, consumerSecret) {
    var self = this;

    // call the superclass for initialisation
    OAuth.super_.call(this);

    // check that we have each of these values
    if ( ! consumerKey ) {
        throw MARK + 'consumerKey is required';
    }
    if ( ! consumerSecret ) {
        throw MARK + 'consumerSecret is required';
    }

    // allow setting of these variables
    var token, tokenSecret;
    self.setToken = function(str) {
        if ( ! str ) {
            throw MARK + 'token is required when setting it';
        }
        token = str;
    };
    self.setTokenSecret = function(str) {
        if ( ! str ) {
            throw MARK + 'tokenSecret is required when setting it';
        }
        tokenSecret = str;
    };

    // allow access to all of these things
    self.consumerKey    = function() { return consumerKey;    };
    self.consumerSecret = function() { return consumerSecret; };
    self.token          = function() { return token;           };
    self.tokenSecret    = function() { return tokenSecret;     };

    return self;
};

// inherit from AwsSum
util.inherits(OAuth, awssum.AwsSum);

// --------------------------------------------------------------------------------------------------------------------
// extra request headers

function extrasContentLength(options, args) {
    var self = this;

    // add the Content-Length header we need
    if ( args.ContentLength ) {
        options.headers['Content-Length'] = args.ContentLength;
        return;
    }
    if ( options.body ) {
        // ToDo: switch this to do the same buffer thing as nfriedly
        options.headers['Content-Length'] = options.body.length;
        return;
    }
    // else, it must be zero
    options.headers['Content-Length'] = 0;
}

// --------------------------------------------------------------------------------------------------------------------
// functions common between all OAuth services

var RequestToken = {
    // request
    'method'      : 'POST',
    'host'        : function() { return this.requestTokenHost(); },
    'path'        : function() { return this.requestTokenPath(); },
    'args'        : {
        'OAuthCallback' : {
            required : true,
            name     : 'oauth_callback',
            type     : 'param',
        },
    },
    'addExtras' : extrasContentLength, // required for Tumblr, Twitter is fine without it
    'authentication' : false,
    // response
    extractBody : 'application/x-www-form-urlencoded', // may be overriden by inheriting class, so make sure this stays
    extractBodyWhenError : 'blob',
};

var GetToken = {
    // request
    'method'      : 'POST',
    'host'        : function() { return this.accessTokenHost(); },
    'path'        : function() { return this.accessTokenPath(); },
    'args'        : {
        'OAuthVerifier' : {
            required : true,
            name     : 'oauth_verifier',
            type     : 'param',
        },
    },
    'addExtras' : extrasContentLength, // required for Tumblr, Twitter is fine without it
    'authentication' : false,
    // response
    extractBody : 'application/x-www-form-urlencoded', // may be overriden by inheriting class, so make sure this stays
};

OAuth.prototype['RequestToken'] = awssum.makeOperation(RequestToken);
OAuth.prototype['GetToken'] = awssum.makeOperation(GetToken);

// --------------------------------------------------------------------------------------------------------------------
// functions to be overriden by inheriting class

// see ./awssum.js, plus additional methods such as:
//
// * requestTokenHost
// * requestTokenPath
// * authorizeHost
// * authorizePath
// * accessTokenHost
// * accessTokenPath

OAuth.prototype.protocol = function() {
    return 'https';
};

OAuth.prototype.oauthSignatureType = function() {
    // From: http://tools.ietf.org/html/rfc5849#section-3.5
    // could be 'header', 'body' or 'param'
    return 'header';
};

OAuth.prototype.addCommonOptions = function(options, args) {
    var self = this;

    // get the date in epoch
    var timestamp = parseInt((new Date()).valueOf()/1000);
    var nonce = passgen.create(12);

    // add in the common params for OAuth 1.0a

    // Common oauth params:
    // * oauth_consumer_key
    // * oauth_version
    // * oauth_timestamp
    // * oauth_nonce
    // * oauth_signature_method
    // * oauth_signature
    //
    // For RequestToken:
    // * oauth_token
    // * oauth_callback
    //
    // For GetToken:
    // * oauth_verifier (from the user or the redirect from the OAuth provide)
    // * oauth_token (from RequestToken)
    //
    // For an authenticated operation:
    // * oauth_token (from GetToken)
    // * oauth_token_secret (from GetToken)

    // oauth params (needed for signing) and all the incoming params
    var oauthParams = [];
    oauthParams.push({ 'name' : 'oauth_consumer_key',     'value' : self.consumerKey() });
    oauthParams.push({ 'name' : 'oauth_version',          'value' : '1.0'              });
    oauthParams.push({ 'name' : 'oauth_timestamp',        'value' : timestamp          });
    oauthParams.push({ 'name' : 'oauth_nonce',            'value' : nonce              });
    oauthParams.push({ 'name' : 'oauth_signature_method', 'value' : 'HMAC-SHA1'        });
    if ( self.token() ) {
        oauthParams.push({ 'name' : 'oauth_token', 'value' : self.token() });
    }
    options.params.forEach(function(v, i) {
        oauthParams.push(v);
    });

    // sign ALL of the params, including those passed in and those for OAuth
    if ( self.oauthSignatureType() === 'param' ) {
        console.log('---> RIGHT HERE <---');
        options.params.push({ 'name' : 'oauth_consumer_key',     'value' : self.consumerKey() });
        options.params.push({ 'name' : 'oauth_version',          'value' : '1.0'              });
        options.params.push({ 'name' : 'oauth_timestamp',        'value' : timestamp          });
        options.params.push({ 'name' : 'oauth_nonce',            'value' : nonce              });
        options.params.push({ 'name' : 'oauth_signature_method', 'value' : 'HMAC-SHA1'        });
        if ( self.token() ) {
            options.params.push({ 'name' : 'oauth_token', 'value' : self.token() });
        }
    }
    else if ( self.oauthSignatureType() === 'header' ) {
        // nothing to do
    }

    // make the signature
    var strToSign = escape(options.method.toUpperCase()) + '&' + escape(self.protocol() + '://' + options.host + options.path);
    var pairs = _(oauthParams)
        .chain()
        .sortBy(function(p) { return p.name })
        .map(function(v, i) { return '' + v.name + '=' + escape(v.value) })
        .join('&')
        .value()
    ;
    strToSign += '&' + escape(pairs);

    // sign the request string
    var signatureKey = escape(self.consumerSecret()) + '&';
    if ( self.tokenSecret() ) {
        signatureKey += escape(self.tokenSecret());
    }

    var signature = crypto
        .createHmac('sha1', signatureKey)
        .update(strToSign)
        .digest('base64');

    // add the 'Authorization' header
    if ( self.oauthSignatureType() === 'header' ) {
        options.headers['Authorization'] = 'OAuth ' + [
            'oauth_version="1.0"',
            'oauth_consumer_key="' + escape(self.consumerKey()) + '"',
            'oauth_timestamp="' + escape(timestamp)  + '"',
            'oauth_nonce="' + nonce + '"',
            'oauth_signature_method="HMAC-SHA1"',
            'oauth_signature="' + escape(signature) + '"',
        ].join(', ');
        if ( self.token() ) {
            options.headers['Authorization'] += ', oauth_token="' + self.token() + '"'
        }
    }
    else if ( self.oauthSignatureType() === 'param' ) {
        // add the oauth_signature onto the params
        options.params.push({ 'name' : 'oauth_signature', 'value' : signature });
    }

    if ( debug ) {
        console.log('-------------------------------------------------------------------------------');
        console.log('OAuth Signature:');
        console.log('- signatureKey   : ', signatureKey);
        console.log('- strTosign      : ', strToSign);
        console.log('- signature      : ', signature);
        console.log('-------------------------------------------------------------------------------');
    }
};

OAuth.prototype.extractBody = function() {
    // most OAuth things return an encoded form
    return 'application/x-www-form-urlencoded';
};

// --------------------------------------------------------------------------------------------------------------------
// exports

// object constructor
exports.OAuth = OAuth;

// --------------------------------------------------------------------------------------------------------------------
<commit_msg>Slight code formatting change<commit_after>// --------------------------------------------------------------------------------------------------------------------
//
// oauth.js - the base class for all Oauth 1.0a web services in node-awssum
//
// Copyright (c) 2012 AppsAttic Ltd - http://www.appsattic.com/
// Written by Andrew Chilton <chilts@appsattic.com>
//
// License: http://opensource.org/licenses/MIT
//
// --------------------------------------------------------------------------------------------------------------------

// --------------------------------------------------------------------------------------------------------------------
// requires

// built-ins
var util = require('util');
var crypto = require('crypto');

// dependencies
var _ = require('underscore');
var passgen = require('passgen');

// our own
var awssum = require('./awssum');
var escape = require('../lib/escape.js');

// --------------------------------------------------------------------------------------------------------------------
// package variables

var MARK = 'oauth: ';

var debug = false;

// --------------------------------------------------------------------------------------------------------------------
// constructor

var OAuth = function(consumerKey, consumerSecret) {
    var self = this;

    // call the superclass for initialisation
    OAuth.super_.call(this);

    // check that we have each of these values
    if ( ! consumerKey ) {
        throw MARK + 'consumerKey is required';
    }
    if ( ! consumerSecret ) {
        throw MARK + 'consumerSecret is required';
    }

    // allow setting of these variables
    var token, tokenSecret;
    self.setToken = function(str) {
        if ( ! str ) {
            throw MARK + 'token is required when setting it';
        }
        token = str;
    };
    self.setTokenSecret = function(str) {
        if ( ! str ) {
            throw MARK + 'tokenSecret is required when setting it';
        }
        tokenSecret = str;
    };

    // allow access to all of these things
    self.consumerKey    = function() { return consumerKey;    };
    self.consumerSecret = function() { return consumerSecret; };
    self.token          = function() { return token;           };
    self.tokenSecret    = function() { return tokenSecret;     };

    return self;
};

// inherit from AwsSum
util.inherits(OAuth, awssum.AwsSum);

// --------------------------------------------------------------------------------------------------------------------
// extra request headers

function extrasContentLength(options, args) {
    var self = this;

    // add the Content-Length header we need
    if ( args.ContentLength ) {
        options.headers['Content-Length'] = args.ContentLength;
        return;
    }
    if ( options.body ) {
        // ToDo: switch this to do the same buffer thing as nfriedly
        options.headers['Content-Length'] = options.body.length;
        return;
    }
    // else, it must be zero
    options.headers['Content-Length'] = 0;
}

// --------------------------------------------------------------------------------------------------------------------
// functions common between all OAuth services

var RequestToken = {
    // request
    'method'      : 'POST',
    'host'        : function() { return this.requestTokenHost(); },
    'path'        : function() { return this.requestTokenPath(); },
    'args'        : {
        'OAuthCallback' : {
            required : true,
            name     : 'oauth_callback',
            type     : 'param',
        },
    },
    'addExtras' : extrasContentLength, // required for Tumblr, Twitter is fine without it
    'authentication' : false,
    // response
    extractBody : 'application/x-www-form-urlencoded', // may be overriden by inheriting class, so make sure this stays
    extractBodyWhenError : 'blob',
};

var GetToken = {
    // request
    'method'      : 'POST',
    'host'        : function() { return this.accessTokenHost(); },
    'path'        : function() { return this.accessTokenPath(); },
    'args'        : {
        'OAuthVerifier' : {
            required : true,
            name     : 'oauth_verifier',
            type     : 'param',
        },
    },
    'addExtras' : extrasContentLength, // required for Tumblr, Twitter is fine without it
    'authentication' : false,
    // response
    // may be overriden by inheriting class, so make sure this stays
    'extractBody' : 'application/x-www-form-urlencoded',
};

OAuth.prototype['RequestToken'] = awssum.makeOperation(RequestToken);
OAuth.prototype['GetToken'] = awssum.makeOperation(GetToken);

// --------------------------------------------------------------------------------------------------------------------
// functions to be overriden by inheriting class

// see ./awssum.js, plus additional methods such as:
//
// * requestTokenHost
// * requestTokenPath
// * authorizeHost
// * authorizePath
// * accessTokenHost
// * accessTokenPath

OAuth.prototype.protocol = function() {
    return 'https';
};

OAuth.prototype.oauthSignatureType = function() {
    // From: http://tools.ietf.org/html/rfc5849#section-3.5
    // could be 'header', 'body' or 'param'
    return 'header';
};

OAuth.prototype.addCommonOptions = function(options, args) {
    var self = this;

    // get the date in epoch
    var timestamp = parseInt((new Date()).valueOf()/1000);
    var nonce = passgen.create(12);

    // add in the common params for OAuth 1.0a

    // Common oauth params:
    // * oauth_consumer_key
    // * oauth_version
    // * oauth_timestamp
    // * oauth_nonce
    // * oauth_signature_method
    // * oauth_signature
    //
    // For RequestToken:
    // * oauth_token
    // * oauth_callback
    //
    // For GetToken:
    // * oauth_verifier (from the user or the redirect from the OAuth provide)
    // * oauth_token (from RequestToken)
    //
    // For an authenticated operation:
    // * oauth_token (from GetToken)
    // * oauth_token_secret (from GetToken)

    // oauth params (needed for signing) and all the incoming params
    var oauthParams = [];
    oauthParams.push({ 'name' : 'oauth_consumer_key',     'value' : self.consumerKey() });
    oauthParams.push({ 'name' : 'oauth_version',          'value' : '1.0'              });
    oauthParams.push({ 'name' : 'oauth_timestamp',        'value' : timestamp          });
    oauthParams.push({ 'name' : 'oauth_nonce',            'value' : nonce              });
    oauthParams.push({ 'name' : 'oauth_signature_method', 'value' : 'HMAC-SHA1'        });
    if ( self.token() ) {
        oauthParams.push({ 'name' : 'oauth_token', 'value' : self.token() });
    }
    options.params.forEach(function(v, i) {
        oauthParams.push(v);
    });

    // sign ALL of the params, including those passed in and those for OAuth
    if ( self.oauthSignatureType() === 'param' ) {
        console.log('---> RIGHT HERE <---');
        options.params.push({ 'name' : 'oauth_consumer_key',     'value' : self.consumerKey() });
        options.params.push({ 'name' : 'oauth_version',          'value' : '1.0'              });
        options.params.push({ 'name' : 'oauth_timestamp',        'value' : timestamp          });
        options.params.push({ 'name' : 'oauth_nonce',            'value' : nonce              });
        options.params.push({ 'name' : 'oauth_signature_method', 'value' : 'HMAC-SHA1'        });
        if ( self.token() ) {
            options.params.push({ 'name' : 'oauth_token', 'value' : self.token() });
        }
    }
    else if ( self.oauthSignatureType() === 'header' ) {
        // nothing to do
    }

    // make the signature
    var strToSign = escape(options.method.toUpperCase()) + '&' + escape(self.protocol() + '://' + options.host + options.path);
    var pairs = _(oauthParams)
        .chain()
        .sortBy(function(p) { return p.name })
        .map(function(v, i) { return '' + v.name + '=' + escape(v.value) })
        .join('&')
        .value()
    ;
    strToSign += '&' + escape(pairs);

    // sign the request string
    var signatureKey = escape(self.consumerSecret()) + '&';
    if ( self.tokenSecret() ) {
        signatureKey += escape(self.tokenSecret());
    }

    var signature = crypto
        .createHmac('sha1', signatureKey)
        .update(strToSign)
        .digest('base64');

    // add the 'Authorization' header
    if ( self.oauthSignatureType() === 'header' ) {
        options.headers['Authorization'] = 'OAuth ' + [
            'oauth_version="1.0"',
            'oauth_consumer_key="' + escape(self.consumerKey()) + '"',
            'oauth_timestamp="' + escape(timestamp)  + '"',
            'oauth_nonce="' + nonce + '"',
            'oauth_signature_method="HMAC-SHA1"',
            'oauth_signature="' + escape(signature) + '"',
        ].join(', ');
        if ( self.token() ) {
            options.headers['Authorization'] += ', oauth_token="' + self.token() + '"'
        }
    }
    else if ( self.oauthSignatureType() === 'param' ) {
        // add the oauth_signature onto the params
        options.params.push({ 'name' : 'oauth_signature', 'value' : signature });
    }

    if ( debug ) {
        console.log('-------------------------------------------------------------------------------');
        console.log('OAuth Signature:');
        console.log('- signatureKey   : ', signatureKey);
        console.log('- strTosign      : ', strToSign);
        console.log('- signature      : ', signature);
        console.log('-------------------------------------------------------------------------------');
    }
};

OAuth.prototype.extractBody = function() {
    // most OAuth things return an encoded form
    return 'application/x-www-form-urlencoded';
};

// --------------------------------------------------------------------------------------------------------------------
// exports

// object constructor
exports.OAuth = OAuth;

// --------------------------------------------------------------------------------------------------------------------
<|endoftext|>


<commit_before>var JSONAPIClient = require('json-api-client');
var Model = JSONAPIClient.Model;
var makeHTTPRequest = JSONAPIClient.makeHTTPRequest;
var ls = require('local-storage');

var config = require('./config');
var apiClient = require('./api-client');

// Use this to override the default API-specific headers.
var JSON_HEADERS = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
};

// We don't want to wait until the token is already expired before refreshing it.
var TOKEN_EXPIRATION_ALLOWANCE = 5 * 60 * 1000;

// Save local storage stuff as something totally obvious
var LOCAL_STORAGE_PREFIX = 'panoptesClientOAuth_';

// Specify whether to use local or session storage for session data.
var SESSION_STORAGE = window.localStorage;

// Create our model, then do stuff with it later
module.exports = new Model({
  _bearerRefreshTimeout: NaN,
  _clientAppId: ls.get(LOCAL_STORAGE_PREFIX + 'clientAppId'),
  _currentSessionCheckPromise: null,
  _currentUserPromise: null,
  _tokenDetails: null,
  _customRedirects: false,

  checkBearerToken: function() {
    var awaitBearerToken;
    if (this._bearerTokenWillExpire()) {
      awaitBearerToken = this._refreshBearerToken();
    } else {
      var tokenDetails = JSON.parse(SESSION_STORAGE.getItem(LOCAL_STORAGE_PREFIX + 'tokenDetails'));
      awaitBearerToken = Promise.resolve(tokenDetails);
    }
    return awaitBearerToken;
  },

  checkCurrent: function() {
    console.log('Checking current user');

    // If we're checking for an existing session already, defer this until
    // it's finished
    var initialCheck = this._currentSessionCheckPromise || Promise.resolve();

    if (!this._currentUserPromise) {
      this.update({
        _currentUserPromise: initialCheck
          .then(this._getSession)
          .catch(function() {
            // Nobody's signed in. This isn't an error.
            console.info('No current user');
            return null;
          }),
      });
    }

    return this._currentUserPromise;
  },

  init: function (appID, options) {
    return new Promise(function(resolve, reject) {

      // Don't init if we're in an iFrame, as we could be the token refresh process
      if (window.frameElement) {
        return false;
      }

      console.log('Using OAuth (implicit grant) for login');
      console.info('Setting app ID to', appID);
      ls.get(LOCAL_STORAGE_PREFIX + 'clientAppId', appID);
      this._clientAppId = appID;
      if (options.customRedirects) this._customRedirects = options.customRedirects;

      // Handle new token details if we've completed a sign in
      if (checkUrlForToken(window.location.hash)) {
        console.log('Token found in URL');
        var tokenDetails = tokenFromLocation(window.location);
        this._handleNewBearerToken(tokenDetails);

        if (!this._customRedirects) {
          // And redirect to the desired page
          var url = ls.get(LOCAL_STORAGE_PREFIX + 'redirectUri');
          location.assign(url);
        }
      }

      // If not, let's try and pick up an existing Panoptes session anyway
      this.checkBearerToken()
        .then(function (tokenDetails) {
          this._handleNewBearerToken(tokenDetails);
          resolve(tokenDetails);
        }.bind(this))
        .catch(function (error) {
          // We probably haven't signed in before
          console.info(error);
          resolve(null);
        });

    }.bind(this));

  },

  signIn: function(redirectUri) {
    console.log('Signing in with OAuth');
    var originalArguments = arguments;
    return this.checkCurrent()
      .then(function (token) {
        if (token) {
          return this.signOut().then(function() {
            return this.signIn.apply(this, originalArguments);
          }.bind(this));
        } else {
          this._saveRedirectUri(redirectUri);
          location.assign(this._createOAuthUrl(redirectUri));
        }
      }.bind(this));
  },

  signOut: function() {
    console.log('Signing out');
    return this.checkCurrent().then(function(user) {
      if (user) {
        return this._getAuthToken().then(function(token) {
          var url = config.oauthHost + '/users/sign_out';

          var deleteHeaders = Object.create(JSON_HEADERS);
          deleteHeaders['X-CSRF-Token'] = token;

          return makeHTTPRequest('DELETE', url, null, deleteHeaders)
            .then(function() {
              this._deleteBearerToken();
              this.update({
                _currentUserPromise: Promise.resolve(null),
              });
              console.info('Signed out');
              return null;
            }.bind(this))
            .catch(function(request) {
              console.error('Failed to sign out');
              return apiClient.handleError(request);
            }.bind(this));
        }.bind(this));
      } else {
        throw new Error('Failed to sign out; not signed in');
      }
    }.bind(this));
  },

  _createOAuthUrl: function(redirectUri) {
    if (!this._clientAppId) {
      throw new Error('Client app ID not set');
    }

    return [
      config.oauthHost,
      '/oauth/authorize',
      '?response_type=token',
      '&client_id=',
      this._clientAppId,
      '&redirect_uri=',
      redirectUri
    ].join('');
  },

  _bearerTokenWillExpire: function() {
    var tokenDetails = JSON.parse(SESSION_STORAGE.getItem(LOCAL_STORAGE_PREFIX + 'tokenDetails'));
    if (tokenDetails) {
      return Date.now() >= tokenDetails.expires_at - TOKEN_EXPIRATION_ALLOWANCE;
    } else {
      return false;
    }
  },

  _getNewToken: function() {
    var redirectUri = ls.get(LOCAL_STORAGE_PREFIX + 'redirectUri');
    this.update({
      _currentSessionCheckPromise: new Promise(function(resolve, reject) {

        if (!redirectUri) {
          reject(Error('No redirect URI found'));
        }

        // Create a new iFrame
        var url = this._createOAuthUrl(redirectUri);
        this._iframe = createIFrame(url);

        // Try and get the token details from our iFrame. If it throws an error,
        // it's because we're being redirected to the signin page (and therefore
        // there is no session) - so we should also replace the security error
        // with a more relevant one.
        this._iframe.onload = function() {
          try {
            var iframeLocation = this._iframe.contentWindow.location;
            if (checkUrlForToken(iframeLocation.hash)) {
              console.info('Found existing Panoptes session');
              var newTokenDetails = tokenFromLocation(iframeLocation);
              resolve(newTokenDetails);
            } else {
              throw new TypeError('Valid OAuth details not found in URL');
            }
          } catch (error) {
            if (error.name === 'SecurityError') {
              console.warn('No existing Panoptes session found');
            }
            reject(error);
          } finally {
            this._iframe = destroyIFrame(this._iframe);
          }
        }.bind(this);
      }.bind(this))
    });

    return this._currentSessionCheckPromise;
  },

  _deleteBearerToken: function() {
    console.log('Deleting bearer token');
    this._tokenDetails = null;
    delete apiClient.headers.Authorization;
    SESSION_STORAGE.removeItem(LOCAL_STORAGE_PREFIX + 'tokenDetails');
  },

  _getAuthToken: function() {
    console.log('Getting auth token');
    var url = config.oauthHost + '/users/sign_in/?now=' + Date.now();
    return makeHTTPRequest('GET', url, null, JSON_HEADERS)
      .then(function(response) {
        var authToken = response.header['x-csrf-token'];
        console.info('Got auth token', authToken.slice(-6));
        return authToken;
      })
      .catch(function(response) {
        console.error('Failed to get auth token');
        apiClient.handleError(response);
      });
  },

  _getSession: function() {
    console.log('Getting session');
    return apiClient.get('/me')
      .then(function(users) {
        var user = users[0];
        console.info('Got session', user.login, user.id);
        return user;
      })
      .catch(function(error) {
        console.error('Failed to get session');
        throw error;
      });
  },

  _handleExpiredToken: function() {
    var tokenDetails = JSON.parse(SESSION_STORAGE.getItem(LOCAL_STORAGE_PREFIX + 'tokenDetails'));
    var tokenHasExpired = false;
    if (tokenDetails && tokenDetails.expires_at) {
      tokenHasExpired = Date.now() > tokenDetails.expires_at;
    }
    if (tokenHasExpired) {
      console.info('Panoptes session has expired');
      this._deleteBearerToken();
    }
  },

  _handleNewBearerToken: function(tokenDetails) {
    if (tokenDetails && tokenDetails.access_token) {
      console.log('Got new bearer token', tokenDetails.access_token.slice(-6));
      this._tokenDetails = tokenDetails;
      apiClient.headers.Authorization = 'Bearer ' + tokenDetails.access_token;

      var refresh = this._refreshBearerToken.bind(this);
      var timeToRefresh = (tokenDetails.expires_in * 1000) - TOKEN_EXPIRATION_ALLOWANCE;
      this._bearerRefreshTimeout = setTimeout(refresh, timeToRefresh);
      tokenDetails.expires_at = Date.now() + (tokenDetails.expires_in * 1000);
      SESSION_STORAGE.setItem(LOCAL_STORAGE_PREFIX + 'tokenDetails', JSON.stringify(tokenDetails));
    }
    return tokenDetails;
  },

  _refreshBearerToken: function() {
    return this._getNewToken()
      .then(function(tokenDetails) {
        return this._handleNewBearerToken(tokenDetails);
      }.bind(this))
      .catch(function (error) {
        console.log(error);
        this._handleExpiredToken();
        return null;
      }.bind(this));
  },

  _saveRedirectUri: function(redirectUri) {
    console.info('Saving redirectUri:', redirectUri);
    ls.set(LOCAL_STORAGE_PREFIX + 'redirectUri', redirectUri);
  }

});
/*******************************************
  Utility functions
*******************************************/
function checkUrlForToken(string) {
  return string.indexOf('access_token') !== -1 &&
    string.indexOf('token_type=bearer') !== -1;
}

function tokenFromLocation(loc) {
  var params = loc.hash.slice(1).split('&');
  var tokenDetails = {};
  params.forEach(function(paramString) {
    var param = paramString.split('=');
    tokenDetails[param[0]] = param[1];
  });
  return tokenDetails;
}

function createIFrame(url) {
  var iframe = document.createElement('iframe');
  iframe.setAttribute('src', url);
  iframe.setAttribute('style', 'display: none;');
  document.body.appendChild(iframe);
  return iframe;
}

function destroyIFrame(iframe) {
  iframe.parentNode.removeChild(iframe);
  return null;
}
<commit_msg>Have a default for the initialization options<commit_after>var JSONAPIClient = require('json-api-client');
var Model = JSONAPIClient.Model;
var makeHTTPRequest = JSONAPIClient.makeHTTPRequest;
var ls = require('local-storage');

var config = require('./config');
var apiClient = require('./api-client');

// Use this to override the default API-specific headers.
var JSON_HEADERS = {
  'Content-Type': 'application/json',
  'Accept': 'application/json',
};

// We don't want to wait until the token is already expired before refreshing it.
var TOKEN_EXPIRATION_ALLOWANCE = 5 * 60 * 1000;

// Save local storage stuff as something totally obvious
var LOCAL_STORAGE_PREFIX = 'panoptesClientOAuth_';

// Specify whether to use local or session storage for session data.
var SESSION_STORAGE = window.localStorage;

// Create our model, then do stuff with it later
module.exports = new Model({
  _bearerRefreshTimeout: NaN,
  _clientAppId: ls.get(LOCAL_STORAGE_PREFIX + 'clientAppId'),
  _currentSessionCheckPromise: null,
  _currentUserPromise: null,
  _tokenDetails: null,
  _customRedirects: false,

  checkBearerToken: function() {
    var awaitBearerToken;
    if (this._bearerTokenWillExpire()) {
      awaitBearerToken = this._refreshBearerToken();
    } else {
      var tokenDetails = JSON.parse(SESSION_STORAGE.getItem(LOCAL_STORAGE_PREFIX + 'tokenDetails'));
      awaitBearerToken = Promise.resolve(tokenDetails);
    }
    return awaitBearerToken;
  },

  checkCurrent: function() {
    console.log('Checking current user');

    // If we're checking for an existing session already, defer this until
    // it's finished
    var initialCheck = this._currentSessionCheckPromise || Promise.resolve();

    if (!this._currentUserPromise) {
      this.update({
        _currentUserPromise: initialCheck
          .then(this._getSession)
          .catch(function() {
            // Nobody's signed in. This isn't an error.
            console.info('No current user');
            return null;
          }),
      });
    }

    return this._currentUserPromise;
  },

  init: function (appID, options) {
    return new Promise(function(resolve, reject) {

      // Don't init if we're in an iFrame, as we could be the token refresh process
      if (window.frameElement) {
        return false;
      }

      console.log('Using OAuth (implicit grant) for login');
      console.info('Setting app ID to', appID);
      ls.get(LOCAL_STORAGE_PREFIX + 'clientAppId', appID);
      this._clientAppId = appID;
      var initializationOptions = options || {};
      if (initializationOptions.customRedirects) this._customRedirects = initializationOptions.customRedirects;

      // Handle new token details if we've completed a sign in
      if (checkUrlForToken(window.location.hash)) {
        console.log('Token found in URL');
        var tokenDetails = tokenFromLocation(window.location);
        this._handleNewBearerToken(tokenDetails);

        if (!this._customRedirects) {
          // And redirect to the desired page
          var url = ls.get(LOCAL_STORAGE_PREFIX + 'redirectUri');
          location.assign(url);
        }
      }

      // If not, let's try and pick up an existing Panoptes session anyway
      this.checkBearerToken()
        .then(function (tokenDetails) {
          this._handleNewBearerToken(tokenDetails);
          resolve(tokenDetails);
        }.bind(this))
        .catch(function (error) {
          // We probably haven't signed in before
          console.info(error);
          resolve(null);
        });

    }.bind(this));

  },

  signIn: function(redirectUri) {
    console.log('Signing in with OAuth');
    var originalArguments = arguments;
    return this.checkCurrent()
      .then(function (token) {
        if (token) {
          return this.signOut().then(function() {
            return this.signIn.apply(this, originalArguments);
          }.bind(this));
        } else {
          this._saveRedirectUri(redirectUri);
          location.assign(this._createOAuthUrl(redirectUri));
        }
      }.bind(this));
  },

  signOut: function() {
    console.log('Signing out');
    return this.checkCurrent().then(function(user) {
      if (user) {
        return this._getAuthToken().then(function(token) {
          var url = config.oauthHost + '/users/sign_out';

          var deleteHeaders = Object.create(JSON_HEADERS);
          deleteHeaders['X-CSRF-Token'] = token;

          return makeHTTPRequest('DELETE', url, null, deleteHeaders)
            .then(function() {
              this._deleteBearerToken();
              this.update({
                _currentUserPromise: Promise.resolve(null),
              });
              console.info('Signed out');
              return null;
            }.bind(this))
            .catch(function(request) {
              console.error('Failed to sign out');
              return apiClient.handleError(request);
            }.bind(this));
        }.bind(this));
      } else {
        throw new Error('Failed to sign out; not signed in');
      }
    }.bind(this));
  },

  _createOAuthUrl: function(redirectUri) {
    if (!this._clientAppId) {
      throw new Error('Client app ID not set');
    }

    return [
      config.oauthHost,
      '/oauth/authorize',
      '?response_type=token',
      '&client_id=',
      this._clientAppId,
      '&redirect_uri=',
      redirectUri
    ].join('');
  },

  _bearerTokenWillExpire: function() {
    var tokenDetails = JSON.parse(SESSION_STORAGE.getItem(LOCAL_STORAGE_PREFIX + 'tokenDetails'));
    if (tokenDetails) {
      return Date.now() >= tokenDetails.expires_at - TOKEN_EXPIRATION_ALLOWANCE;
    } else {
      return false;
    }
  },

  _getNewToken: function() {
    var redirectUri = ls.get(LOCAL_STORAGE_PREFIX + 'redirectUri');
    this.update({
      _currentSessionCheckPromise: new Promise(function(resolve, reject) {

        if (!redirectUri) {
          reject(Error('No redirect URI found'));
        }

        // Create a new iFrame
        var url = this._createOAuthUrl(redirectUri);
        this._iframe = createIFrame(url);

        // Try and get the token details from our iFrame. If it throws an error,
        // it's because we're being redirected to the signin page (and therefore
        // there is no session) - so we should also replace the security error
        // with a more relevant one.
        this._iframe.onload = function() {
          try {
            var iframeLocation = this._iframe.contentWindow.location;
            if (checkUrlForToken(iframeLocation.hash)) {
              console.info('Found existing Panoptes session');
              var newTokenDetails = tokenFromLocation(iframeLocation);
              resolve(newTokenDetails);
            } else {
              throw new TypeError('Valid OAuth details not found in URL');
            }
          } catch (error) {
            if (error.name === 'SecurityError') {
              console.warn('No existing Panoptes session found');
            }
            reject(error);
          } finally {
            this._iframe = destroyIFrame(this._iframe);
          }
        }.bind(this);
      }.bind(this))
    });

    return this._currentSessionCheckPromise;
  },

  _deleteBearerToken: function() {
    console.log('Deleting bearer token');
    this._tokenDetails = null;
    delete apiClient.headers.Authorization;
    SESSION_STORAGE.removeItem(LOCAL_STORAGE_PREFIX + 'tokenDetails');
  },

  _getAuthToken: function() {
    console.log('Getting auth token');
    var url = config.oauthHost + '/users/sign_in/?now=' + Date.now();
    return makeHTTPRequest('GET', url, null, JSON_HEADERS)
      .then(function(response) {
        var authToken = response.header['x-csrf-token'];
        console.info('Got auth token', authToken.slice(-6));
        return authToken;
      })
      .catch(function(response) {
        console.error('Failed to get auth token');
        apiClient.handleError(response);
      });
  },

  _getSession: function() {
    console.log('Getting session');
    return apiClient.get('/me')
      .then(function(users) {
        var user = users[0];
        console.info('Got session', user.login, user.id);
        return user;
      })
      .catch(function(error) {
        console.error('Failed to get session');
        throw error;
      });
  },

  _handleExpiredToken: function() {
    var tokenDetails = JSON.parse(SESSION_STORAGE.getItem(LOCAL_STORAGE_PREFIX + 'tokenDetails'));
    var tokenHasExpired = false;
    if (tokenDetails && tokenDetails.expires_at) {
      tokenHasExpired = Date.now() > tokenDetails.expires_at;
    }
    if (tokenHasExpired) {
      console.info('Panoptes session has expired');
      this._deleteBearerToken();
    }
  },

  _handleNewBearerToken: function(tokenDetails) {
    if (tokenDetails && tokenDetails.access_token) {
      console.log('Got new bearer token', tokenDetails.access_token.slice(-6));
      this._tokenDetails = tokenDetails;
      apiClient.headers.Authorization = 'Bearer ' + tokenDetails.access_token;

      var refresh = this._refreshBearerToken.bind(this);
      var timeToRefresh = (tokenDetails.expires_in * 1000) - TOKEN_EXPIRATION_ALLOWANCE;
      this._bearerRefreshTimeout = setTimeout(refresh, timeToRefresh);
      tokenDetails.expires_at = Date.now() + (tokenDetails.expires_in * 1000);
      SESSION_STORAGE.setItem(LOCAL_STORAGE_PREFIX + 'tokenDetails', JSON.stringify(tokenDetails));
    }
    return tokenDetails;
  },

  _refreshBearerToken: function() {
    return this._getNewToken()
      .then(function(tokenDetails) {
        return this._handleNewBearerToken(tokenDetails);
      }.bind(this))
      .catch(function (error) {
        console.log(error);
        this._handleExpiredToken();
        return null;
      }.bind(this));
  },

  _saveRedirectUri: function(redirectUri) {
    console.info('Saving redirectUri:', redirectUri);
    ls.set(LOCAL_STORAGE_PREFIX + 'redirectUri', redirectUri);
  }

});
/*******************************************
  Utility functions
*******************************************/
function checkUrlForToken(string) {
  return string.indexOf('access_token') !== -1 &&
    string.indexOf('token_type=bearer') !== -1;
}

function tokenFromLocation(loc) {
  var params = loc.hash.slice(1).split('&');
  var tokenDetails = {};
  params.forEach(function(paramString) {
    var param = paramString.split('=');
    tokenDetails[param[0]] = param[1];
  });
  return tokenDetails;
}

function createIFrame(url) {
  var iframe = document.createElement('iframe');
  iframe.setAttribute('src', url);
  iframe.setAttribute('style', 'display: none;');
  document.body.appendChild(iframe);
  return iframe;
}

function destroyIFrame(iframe) {
  iframe.parentNode.removeChild(iframe);
  return null;
}
<|endoftext|>


<commit_before>(function () {

    'use strict';

    angular
        .module('app', [
            'bulma',
            'hljs',

            'app.modals',
            'app.progress',
            'app.tabs'
        ])
        .config(config)
        .run(run);

    config.$inject = ['hljsServiceProvider'];

    /**
     * App config
     */
    function config(hljsServiceProvider) {
        console.log(hljsServiceProvider);
        hljsServiceProvider.setOptions({
            tabReplace: '    '
        });
    }

    /**
     * App run
     */
    function run() {
    }

})();<commit_msg>removed console.log<commit_after>(function () {

    'use strict';

    angular
        .module('app', [
            'bulma',
            'hljs',

            'app.modals',
            'app.progress',
            'app.tabs'
        ])
        .config(config)
        .run(run);

    config.$inject = ['hljsServiceProvider'];

    /**
     * App config
     */
    function config(hljsServiceProvider) {
        hljsServiceProvider.setOptions({
            tabReplace: '    '
        });
    }

    /**
     * App run
     */
    function run() {
    }

})();<|endoftext|>


<commit_before>angular.module('pmsiplan').directive('resourceHistory', ['AngularDataStore', function(AngularDataStore) {
    return {
        templateUrl: 'partials/directive/resource-history.html',
        scope: {
            id: '=id',
            entries: '=entries'
        },
        link: function(scope, element, attrs) {
            var nbEntryPerPage = 25,
                projects = {},
                deliveries = {},
                getDelivery = function(entry) {
                    if(deliveries[entry.content.delivery]) {
                        entry.delivery = deliveries[entry.content.delivery];
                    } else {
                        AngularDataStore.findBy('delivery', {_id: entry.content.delivery}).then(function(delivs) {
                            deliveries[entry.content.delivery] = delivs[0];

                            entry.delivery = deliveries[entry.content.delivery];
                        });
                    }
                },
                getProject = function(entry) {
                    if(projects[entry.content.project]) {
                        entry.project = projects[entry.content.project];
                    } else {
                        AngularDataStore.findBy('project', {_id: entry.content.project}).then(function(proj) {
                            projects[entry.content.project] = proj[0];

                            entry.project = projects[entry.content.project];
                        });
                    }
                },
                callback = function(entries) {
                    entries = entries
                        .sort(function (a, b) {
                            if (a.date < b.date) {
                                return 1;
                            }

                            if (a.date > b.date) {
                                return -1;
                            }

                            return 0;
                        })
                        .map(function(entry) {
                            try {
                                entry.content = JSON.parse(entry.content);
                            } catch(err) {}

                            return entry;
                        });

                    if (scope.id) {
                        entries = entries.filter(function(entry) {
                            console.log(entry.content._id, scope.id);
                            return entry.content._id === scope.id;
                        });
                    }

                    scope.nbPages = Math.ceil(entries.length / nbEntryPerPage);

                    if(scope.page + 1 > scope.nbPages) {
                        return;
                    }

                    scope.history = scope.history.concat(entries
                        .slice(nbEntryPerPage * scope.page, nbEntryPerPage * (scope.page + 1))
                        .map(function(entry) {
                            entry.showContent = function() {
                                if (!entry.isContentVisible) {
                                    if(entry.content.project) {
                                        getProject(entry);
                                    }

                                    if(entry.content.delivery) {
                                        getDelivery(entry);
                                    }
                                }

                                entry.isContentVisible = !entry.isContentVisible;
                            };

                            if(entry.resource == 'project_delivery') {
                                getProject(entry);
                            }

                            return entry;
                        })
                    );
                };

            scope.history = [];
            scope.page = 0;
            scope.fetch = function() {
                if(attrs.type && !scope.entries) {
                    scope.type = attrs.type;

                    AngularDataStore.findBy('histo', { resource: attrs.type }).then(function(entries) {
                        scope.entries = entries;

                        callback(scope.entries);
                    });
                }

                if(scope.entries) {
                    callback(scope.entries);
                }
            };
            scope.more = function() {
                scope.page = scope.page + 1;

                scope.fetch();
            };
            scope.isProperty = function(obj, prop) {
                return typeof obj[prop] === "string";
            };

            scope.fetch();
        }
    };
}]);
<commit_msg>Fix history tables<commit_after>angular.module('pmsiplan').directive('resourceHistory', ['AngularDataStore', function(AngularDataStore) {
    return {
        templateUrl: 'partials/directive/resource-history.html',
        scope: {
            id: '=id',
            entries: '=entries'
        },
        link: function(scope, element, attrs) {
            var nbEntryPerPage = 25,
                projects = {},
                deliveries = {},
                getDelivery = function(entry) {
                    if(deliveries[entry.content.delivery]) {
                        entry.delivery = deliveries[entry.content.delivery];
                    } else {
                        AngularDataStore.findBy('delivery', {_id: entry.content.delivery}).then(function(delivs) {
                            deliveries[entry.content.delivery] = delivs[0];

                            entry.delivery = deliveries[entry.content.delivery];
                        });
                    }
                },
                getProject = function(entry) {
                    if(projects[entry.content.project]) {
                        entry.project = projects[entry.content.project];
                    } else {
                        AngularDataStore.findBy('project', {_id: entry.content.project}).then(function(proj) {
                            projects[entry.content.project] = proj[0];

                            entry.project = projects[entry.content.project];
                        });
                    }
                },
                callback = function(entries) {
                    entries = entries
                        .sort(function (a, b) {
                            if (a.date < b.date) {
                                return 1;
                            }

                            if (a.date > b.date) {
                                return -1;
                            }

                            return 0;
                        })
                        .map(function(entry) {
                            try {
                                entry.content = JSON.parse(entry.content);
                            } catch(err) {}

                            return entry;
                        });

                    if (scope.id) {
                        entries = entries.filter(function(entry) {
                            console.log(entry.content._id, scope.id);
                            return entry.content._id === scope.id;
                        });
                    }

                    scope.nbPages = Math.ceil(entries.length / nbEntryPerPage);

                    if(scope.page + 1 > scope.nbPages) {
                        return;
                    }

                    scope.history = scope.history.concat(entries
                        .slice(nbEntryPerPage * scope.page, nbEntryPerPage * (scope.page + 1))
                        .map(function(entry) {
                            entry.showContent = function() {
                                if (!entry.isContentVisible) {
                                    if(entry.content.project) {
                                        getProject(entry);
                                    }

                                    if(entry.content.delivery) {
                                        getDelivery(entry);
                                    }
                                }

                                entry.isContentVisible = !entry.isContentVisible;
                            };

                            if(entry.resource == 'project_delivery') {
                                getProject(entry);
                            }

                            return entry;
                        })
                    );
                };

            scope.history = [];
            scope.page = 0;
            scope.fetch = function() {
                if(attrs.type && !scope.entries) {
                    scope.type = attrs.type;

                    AngularDataStore.findBy('histo', { resource: attrs.type }).then(function(entries) {
                        scope.entries = entries;

                        callback(scope.entries);
                    });
                }

                if(scope.entries) {
                    callback(scope.entries);
                }
            };
            scope.more = function() {
                scope.page = scope.page + 1;

                scope.fetch();
            };
            scope.isProperty = function(obj, prop) {
                return ['function', 'object'].indexOf(typeof obj[prop]) === -1 && prop !== '_id';
            };

            scope.fetch();
        }
    };
}]);
<|endoftext|>


<commit_before>/**
 * @file searchcondition.js
 * @brief
 * @author Medhi BOULNEMOUR (INRA UMR1095)
 * @date 2017-06-30
 * @copyright Copyright (c) 2016 INRA/CIRAD
 * @license MIT (see LICENSE file)
 * @details
 */

var Marionette = require('backbone.marionette');

var View = Marionette.View.extend({
    tagName: 'div',
    className: 'condition',
    template: require('../templates/searchcondition.html'),
    templateContext: function () {
        return {first_element: this.first_element}
    },

    ui: {
        remove: 'span.action.remove',
        field: 'div.field select',
        condition: 'div.condition select',
        field_value: '.input-group input',
        field_value_group: 'div.field_value div.form-group',
        row_operator: 'div.operator select',
        left_parenthesis: 'span.action.parenthesis.parenthesis-left',
        right_parenthesis: 'span.action.parenthesis.parenthesis-right'
    },

    events: {
        'click @ui.remove': 'onRemove',
        'change @ui.condition': 'onConditionChange',
        'change @ui.field': 'onChangeField',
        'change @ui.field_value': 'onUIChange',
        'change @ui.row_operator': 'onUIChange',
        'click @ui.left_parenthesis': 'onOpenGroup',
        'click @ui.right_parenthesis': 'onCloseGroup'
    },

    initialize: function (options) {
        this.parent = options.parent;
        this.columns = {};
        this.first_element = (this.model.collection.models[0] === this.model);
        this.open_group = 0;
        this.close_group = 0;
    },

    onRender: function () {
        var operator = this.$el.find('div.search-condition').find('div.operator').children('div').children('select');
        operator.append('<option value="and">' + _t('AND') + '</option>');
        operator.append('<option value="or">' + _t('OR') + '</option>');
        operator.selectpicker({}).selectpicker('val', 'and');

        if (this.first_element) {
            this.$el.find('div.search-condition').find('div.operator').parent().hide();
        } else {
            operator.selectpicker({}).selectpicker('val', this.model.get('operator'));
        }

        this.ui.condition.selectpicker({container: '.modal'});
        this.ui.field.selectpicker({
            liveSearch: true,
            container: '.modal'
        });

        this.ui.field.val(this.model.get('field'));
        this.ui.condition.val(this.model.get('condition'));
        this.ui.field_value.val(this.model.get('field_value'));
    },

    onConditionChange: function () {
        var field = this.ui.field.val();
        var column = this.columns[field];

        if (this.widget.allow_multiple) {
            this.widget.destroy();

            if (_.indexOf([
                    'in',
                    'notin',
                    'contains',
                    'not_contains',
                    'contained_by',
                    'not_contained_by',
                    'overlap',
                    'not_overlap'
                ], this.ui.condition.val())) {
                this.widget.create(column.format, this.ui.field_value_group, false, column.group, column.type, {
                    multiple: true,
                    extended_search: false
                });
            } else {
                this.widget.create(column.format, this.ui.field_value_group, false, column.group, column.type, {
                    multiple: false,
                    extended_search: false
                });
            }
        } else {
            if (_.indexOf([
                    'in',
                    'notin',
                    'contains',
                    'not_contains',
                    'contained_by',
                    'not_contained_by',
                    'overlap',
                    'not_overlap'
                ], this.ui.condition.val())) {
                this.ui.field_value_group.find('select').prop('multiple', true).selectpicker('destroy').selectpicker({container: '.modal'});
            } else {
                this.ui.field_value_group.find('select').prop('multiple', false).selectpicker('destroy').selectpicker({container: '.modal'});
            }
        }
        this.onUIChange();
    },

    onUIChange: function () {
        if (this.ui.condition.val() === 'isnull' || this.ui.condition.val() === 'notnull') {
            this.ui.field_value_group.hide();
        } else {
            this.ui.field_value_group.show();
        }

        var value = null;
        var operator = null;

        if (this.widget) {
            value = this.widget.values();
        } else {
            value = this.$el.find('.input-group input').val();
        }
        if (!this.first_element) {
            operator = this.ui.row_operator.val()
        }
        this.model.set({
            row_operator: operator,
            field: this.ui.field.val(),
            condition: this.ui.condition.val(),
            field_value: value,
            open_group: this.open_group,
            close_group: this.close_group
        });
    },

    onOpenGroup: function () {
        switch (this.open_group) {
            case 0:
                this.open_group = 1;
                this.ui.left_parenthesis.addClass('activated');
                this.ui.left_parenthesis.html('[');
                break;

            case 1:
                this.open_group = 2;
                this.ui.left_parenthesis.addClass('activated');
                this.ui.left_parenthesis.html('[[');
                break;

            case 2:
                this.open_group = 0;
                this.ui.left_parenthesis.removeClass('activated');
                this.ui.left_parenthesis.html('[');
                break;
        }
        this.parent.validParenthesis();
    },

    onCloseGroup: function () {
        switch (this.close_group) {
            case 0:
                this.close_group = 1;
                this.ui.right_parenthesis.addClass('activated');
                this.ui.right_parenthesis.html(']');
                break;

            case 1:
                this.close_group = 2;
                this.ui.right_parenthesis.addClass('activated');
                this.ui.right_parenthesis.html(']]');
                break;

            case 2:
                this.close_group = 0;
                this.ui.right_parenthesis.removeClass('activated');
                this.ui.right_parenthesis.html(']');
                break;
        }
        this.parent.validParenthesis();
    },

    onChangeField: function () {
        var field = this.ui.field.val();
        var column = this.columns[field];

        if (!column) {
            return;
        }

        if (this.widget) {
            this.widget.destroy();
            this.widget = null;
        }

        this.ui.field_value.cleanField();
        this.ui.field_value.empty();

        if (column.format) {
            this.widget = application.descriptor.widgets.newElement(column.format.type);
            if (this.widget) {
                this.widget.create(column.format, this.ui.field_value_group, false, column.group, column.type, {extended_search: false});
            }
        } else {
            var input = $('<input type="text" class="search-value form-control" name="search-value"/>');
            this.ui.field_value_group.append(input);
        }

        this.ui.condition.children('option').remove();

        var operators_labels = {
            'isnull': _t('Undefined'),
            'notnull': _t('Defined'),
            'eq': _t('Exact'),
            'neq': _t('Different from'),
            'in': _t('Include'),
            'notin': _t('Not include'),
            'icontains': _t('Contains'),
            'lte': _t('Lesser than'),
            'gte': _t('Greater than'),
            'contains': _t('belong to (intersection)'),
            'not_contains': _t('Not belong to (intersection)'),
            'overlap': _t('Belong to (union)'),
            'not_overlap': _t('Not belong to (union)'),
            'contained_by': _t('Contained by'),
            'not_contained_by': _t('Not contained by')
        };

        for (var i = 0; i < column.available_operators.length; i++) {
            var operator_code = column.available_operators[i];
            this.ui.condition.append('<option value="' + operator_code + '">' + operators_labels[operator_code] + '</option>');
        }

        this.ui.condition.selectpicker('refresh');
        if (column.available_operators.includes('eq')) {
            this.ui.condition.selectpicker({container: '.modal'}).selectpicker('val', 'eq');
        } else {
            this.ui.condition.selectpicker({container: '.modal'}).selectpicker('val', column.available_operators[0]);
        }

        this.onConditionChange();
    },

    onRemove: function () {
        if (this.first_element) {
            var new_first_model = this.parent.collection.models[1];
            var new_first_view = this.parent.children.findByModel(new_first_model);

            new_first_view.ui.row_operator.selectpicker('val', 'and');

            new_first_model.set('row_operator', null);
            new_first_view.first_element = true;

            new_first_view.onUIChange();
            new_first_view.$el.find('div.operator').parent().hide();
        }
        this.model.collection.remove(this.model.cid);
        this.parent.validParenthesis();
    },

    onBeforeDetach: function () {
        if (this.widget) {
            this.widget.destroy();
            this.widget = null;
        }
    }
});

module.exports = View;
<commit_msg>FIX: multiple value field on advanced search conditions<commit_after>/**
 * @file searchcondition.js
 * @brief
 * @author Medhi BOULNEMOUR (INRA UMR1095)
 * @date 2017-06-30
 * @copyright Copyright (c) 2016 INRA/CIRAD
 * @license MIT (see LICENSE file)
 * @details
 */

var Marionette = require('backbone.marionette');

var View = Marionette.View.extend({
    tagName: 'div',
    className: 'condition',
    template: require('../templates/searchcondition.html'),
    templateContext: function () {
        return {first_element: this.first_element}
    },

    ui: {
        remove: 'span.action.remove',
        field: 'div.field select',
        condition: 'div.condition select',
        field_value: '.input-group input',
        field_value_group: 'div.field_value div.form-group',
        row_operator: 'div.operator select',
        left_parenthesis: 'span.action.parenthesis.parenthesis-left',
        right_parenthesis: 'span.action.parenthesis.parenthesis-right'
    },

    events: {
        'click @ui.remove': 'onRemove',
        'change @ui.condition': 'onConditionChange',
        'change @ui.field': 'onChangeField',
        'change @ui.field_value': 'onUIChange',
        'change @ui.row_operator': 'onUIChange',
        'click @ui.left_parenthesis': 'onOpenGroup',
        'click @ui.right_parenthesis': 'onCloseGroup'
    },

    initialize: function (options) {
        this.parent = options.parent;
        this.columns = {};
        this.first_element = (this.model.collection.models[0] === this.model);
        this.open_group = 0;
        this.close_group = 0;
    },

    onRender: function () {
        var operator = this.$el.find('div.search-condition').find('div.operator').children('div').children('select');
        operator.append('<option value="and">' + _t('AND') + '</option>');
        operator.append('<option value="or">' + _t('OR') + '</option>');
        operator.selectpicker({}).selectpicker('val', 'and');

        if (this.first_element) {
            this.$el.find('div.search-condition').find('div.operator').parent().hide();
        } else {
            operator.selectpicker({}).selectpicker('val', this.model.get('operator'));
        }

        this.ui.condition.selectpicker({container: '.modal'});
        this.ui.field.selectpicker({
            liveSearch: true,
            container: '.modal'
        });

        this.ui.field.val(this.model.get('field'));
        this.ui.condition.val(this.model.get('condition'));
        this.ui.field_value.val(this.model.get('field_value'));
    },

    onConditionChange: function () {
        var field = this.ui.field.val();
        var column = this.columns[field];

        if (this.widget.allow_multiple) {
            this.widget.destroy();

            if ([
                    'in',
                    'notin',
                    'contains',
                    'not_contains',
                    'contained_by',
                    'not_contained_by',
                    'overlap',
                    'not_overlap'
                ].includes(this.ui.condition.val())) {
                this.widget.create(column.format, this.ui.field_value_group, false, column.group, column.type, {
                    multiple: true,
                    extended_search: false
                });
            } else {
                this.widget.create(column.format, this.ui.field_value_group, false, column.group, column.type, {
                    multiple: false,
                    extended_search: false
                });
            }
        } else {
            if ([
                    'in',
                    'notin',
                    'contains',
                    'not_contains',
                    'contained_by',
                    'not_contained_by',
                    'overlap',
                    'not_overlap'
                ].includes(this.ui.condition.val())) {
                this.ui.field_value_group.find('select').prop('multiple', true).selectpicker('destroy').selectpicker({container: '.modal'});
            } else {
                this.ui.field_value_group.find('select').prop('multiple', false).selectpicker('destroy').selectpicker({container: '.modal'});
            }
        }
        this.onUIChange();
    },

    onUIChange: function () {
        if (this.ui.condition.val() === 'isnull' || this.ui.condition.val() === 'notnull') {
            this.ui.field_value_group.hide();
        } else {
            this.ui.field_value_group.show();
        }

        var value = null;
        var operator = null;

        if (this.widget) {
            value = this.widget.values();
        } else {
            value = this.$el.find('.input-group input').val();
        }
        if (!this.first_element) {
            operator = this.ui.row_operator.val()
        }
        this.model.set({
            row_operator: operator,
            field: this.ui.field.val(),
            condition: this.ui.condition.val(),
            field_value: value,
            open_group: this.open_group,
            close_group: this.close_group
        });
    },

    onOpenGroup: function () {
        switch (this.open_group) {
            case 0:
                this.open_group = 1;
                this.ui.left_parenthesis.addClass('activated');
                this.ui.left_parenthesis.html('[');
                break;

            case 1:
                this.open_group = 2;
                this.ui.left_parenthesis.addClass('activated');
                this.ui.left_parenthesis.html('[[');
                break;

            case 2:
                this.open_group = 0;
                this.ui.left_parenthesis.removeClass('activated');
                this.ui.left_parenthesis.html('[');
                break;
        }
        this.parent.validParenthesis();
    },

    onCloseGroup: function () {
        switch (this.close_group) {
            case 0:
                this.close_group = 1;
                this.ui.right_parenthesis.addClass('activated');
                this.ui.right_parenthesis.html(']');
                break;

            case 1:
                this.close_group = 2;
                this.ui.right_parenthesis.addClass('activated');
                this.ui.right_parenthesis.html(']]');
                break;

            case 2:
                this.close_group = 0;
                this.ui.right_parenthesis.removeClass('activated');
                this.ui.right_parenthesis.html(']');
                break;
        }
        this.parent.validParenthesis();
    },

    onChangeField: function () {
        var field = this.ui.field.val();
        var column = this.columns[field];

        if (!column) {
            return;
        }

        if (this.widget) {
            this.widget.destroy();
            this.widget = null;
        }

        this.ui.field_value.cleanField();
        this.ui.field_value.empty();

        if (column.format) {
            this.widget = application.descriptor.widgets.newElement(column.format.type);
            if (this.widget) {
                this.widget.create(column.format, this.ui.field_value_group, false, column.group, column.type, {extended_search: false});
            }
        } else {
            var input = $('<input type="text" class="search-value form-control" name="search-value"/>');
            this.ui.field_value_group.append(input);
        }

        this.ui.condition.children('option').remove();

        var operators_labels = {
            'isnull': _t('Undefined'),
            'notnull': _t('Defined'),
            'eq': _t('Exact'),
            'neq': _t('Different from'),
            'in': _t('Include'),
            'notin': _t('Not include'),
            'icontains': _t('Contains'),
            'lte': _t('Lesser than'),
            'gte': _t('Greater than'),
            'contains': _t('belong to (intersection)'),
            'not_contains': _t('Not belong to (intersection)'),
            'overlap': _t('Belong to (union)'),
            'not_overlap': _t('Not belong to (union)'),
            'contained_by': _t('Contained by'),
            'not_contained_by': _t('Not contained by')
        };

        for (var i = 0; i < column.available_operators.length; i++) {
            var operator_code = column.available_operators[i];
            this.ui.condition.append('<option value="' + operator_code + '">' + operators_labels[operator_code] + '</option>');
        }

        this.ui.condition.selectpicker('refresh');
        if (column.available_operators.includes('eq')) {
            this.ui.condition.selectpicker({container: '.modal'}).selectpicker('val', 'eq');
        } else {
            this.ui.condition.selectpicker({container: '.modal'}).selectpicker('val', column.available_operators[0]);
        }

        this.onConditionChange();
    },

    onRemove: function () {
        if (this.first_element) {
            var new_first_model = this.parent.collection.models[1];
            var new_first_view = this.parent.children.findByModel(new_first_model);

            new_first_view.ui.row_operator.selectpicker('val', 'and');

            new_first_model.set('row_operator', null);
            new_first_view.first_element = true;

            new_first_view.onUIChange();
            new_first_view.$el.find('div.operator').parent().hide();
        }
        this.model.collection.remove(this.model.cid);
        this.parent.validParenthesis();
    },

    onBeforeDetach: function () {
        if (this.widget) {
            this.widget.destroy();
            this.widget = null;
        }
    }
});

module.exports = View;
<|endoftext|>


<commit_before>"use strict";

// let require load all the toplevel needed script and call us on domReady
define(['logManager',
        'loaderCircles'], function (logManager,
                                    LoaderCircles) {

    var LayoutManager,
        LAYOUT_PATH = 'js/Layouts/',
        PANEL_PATH = 'js/Panels/';

    LayoutManager = function () {
        this._currentLayout = undefined;
        this._currentLayoutName = undefined;
        this._logger = logManager.create('LayoutManager');
        this._logger.debug('LayoutManager created.');
        this._startProgressBar();
        this._panels = {};
    };

    LayoutManager.prototype.loadLayout = function (layout, fnCallback) {
        var self = this;

        this._logger.debug('LayoutManager loadLayout...');

        //destroy existing
        if (this._currentLayout) {
            this._logger.debug('Destroying layout: ' + this._currentLayoutName);
            this._currentLayout.destroy();
            this._currentLayout = undefined;
            this._currentLayoutName = undefined;

            //just to make sure that HTML BODY is cleared out correctly
            $('body').empty();
        }

        this._startProgressBar();

        //load new one
        this._logger.debug("Downloading layout '" + layout + "'...");

        require([LAYOUT_PATH + layout],
            function (Layout) {
                if (Layout) {
                    self._logger.debug("Layout '" + layout + "' has been downloaded...");
                    self._currentLayoutName = layout;
                    self._currentLayout = new Layout();
                    self._currentLayout.init();
                    if (fnCallback) {
                        fnCallback.call(self);
                    }
                } else {
                    self._logger.error("Layout '" + layout + "' has been downloaded...BUT UNDEFINED!!!");
                }
            },
            function (err) {
                //on error
                self._logger.error("Failed to load layout because of '" + err.requireType + "' with module '" + err.requireModules[0] + "'...");
            });
    };

    LayoutManager.prototype.loadPanel = function (params, fnCallback) {
        var self = this,
            name = params.name,
            container = params.container,
            rPath = PANEL_PATH + name,
            fn;

        this._logger.debug('LayoutManager loadPanel with name: "' + name + '", container: "' + container + '"');

        fn = function () {
            if (fnCallback) {
                fnCallback.call(self);
            }
        };

        if (this._panels[name]) {
            this._logger.error('A Panel with the same name already exist!!!');
        } else {
            require([rPath],
                function (Panel) {
                    if (Panel) {
                        self._logger.debug("Panel '" + name + "' has been downloaded...");
                        self._panels[name] = new Panel(self, params.params);

                        self._currentLayout.addToContainer(self._panels[name], container);
                        self._panels[name].afterAppend();
                    } else {
                        self._logger.error("Panel '" + name + "' has been downloaded...BUT UNDEFINED!!!");
                    }
                    fn();
                },
                function (err) {
                    //on error
                    self._logger.error("Failed to load Panel '" + rPath + "' because of '" + err.requireType + "' with module '" + err.requireModules[0] + "'...");
                    fn();
                });
        }
    };

    LayoutManager.prototype.addPanel = function (name, panel, container) {
        this._panels[name] = panel;
        this._currentLayout.addToContainer(panel, 'main');
        panel.afterAppend();
    };

    LayoutManager.prototype.removePanel = function (name) {
        if (this._panels[name]) {
            this._currentLayout.remove(this._panels[name]);
            delete this._panels[name];
        }
    };

    LayoutManager.prototype._startProgressBar = function () {
        var _loader;

        //start progressbar
        _loader = new LoaderCircles({"containerElement": $('body')});
        //_loader.setSize(100);
        _loader.start();
    };

    LayoutManager.prototype.setPanelReadOnly = function (readOnly) {
        var it;

        for (it in this._panels) {
            if (this._panels.hasOwnProperty(it)) {
                this._panels[it].setReadOnly(readOnly);
            }
        }
    };

    return LayoutManager;
});<commit_msg>addPanel container fix<commit_after>"use strict";

// let require load all the toplevel needed script and call us on domReady
define(['logManager',
        'loaderCircles'], function (logManager,
                                    LoaderCircles) {

    var LayoutManager,
        LAYOUT_PATH = 'js/Layouts/',
        PANEL_PATH = 'js/Panels/';

    LayoutManager = function () {
        this._currentLayout = undefined;
        this._currentLayoutName = undefined;
        this._logger = logManager.create('LayoutManager');
        this._logger.debug('LayoutManager created.');
        this._startProgressBar();
        this._panels = {};
    };

    LayoutManager.prototype.loadLayout = function (layout, fnCallback) {
        var self = this;

        this._logger.debug('LayoutManager loadLayout...');

        //destroy existing
        if (this._currentLayout) {
            this._logger.debug('Destroying layout: ' + this._currentLayoutName);
            this._currentLayout.destroy();
            this._currentLayout = undefined;
            this._currentLayoutName = undefined;

            //just to make sure that HTML BODY is cleared out correctly
            $('body').empty();
        }

        this._startProgressBar();

        //load new one
        this._logger.debug("Downloading layout '" + layout + "'...");

        require([LAYOUT_PATH + layout],
            function (Layout) {
                if (Layout) {
                    self._logger.debug("Layout '" + layout + "' has been downloaded...");
                    self._currentLayoutName = layout;
                    self._currentLayout = new Layout();
                    self._currentLayout.init();
                    if (fnCallback) {
                        fnCallback.call(self);
                    }
                } else {
                    self._logger.error("Layout '" + layout + "' has been downloaded...BUT UNDEFINED!!!");
                }
            },
            function (err) {
                //on error
                self._logger.error("Failed to load layout because of '" + err.requireType + "' with module '" + err.requireModules[0] + "'...");
            });
    };

    LayoutManager.prototype.loadPanel = function (params, fnCallback) {
        var self = this,
            name = params.name,
            container = params.container,
            rPath = PANEL_PATH + name,
            fn;

        this._logger.debug('LayoutManager loadPanel with name: "' + name + '", container: "' + container + '"');

        fn = function () {
            if (fnCallback) {
                fnCallback.call(self);
            }
        };

        if (this._panels[name]) {
            this._logger.error('A Panel with the same name already exist!!!');
        } else {
            require([rPath],
                function (Panel) {
                    if (Panel) {
                        self._logger.debug("Panel '" + name + "' has been downloaded...");
                        self._panels[name] = new Panel(self, params.params);

                        self._currentLayout.addToContainer(self._panels[name], container);
                        self._panels[name].afterAppend();
                    } else {
                        self._logger.error("Panel '" + name + "' has been downloaded...BUT UNDEFINED!!!");
                    }
                    fn();
                },
                function (err) {
                    //on error
                    self._logger.error("Failed to load Panel '" + rPath + "' because of '" + err.requireType + "' with module '" + err.requireModules[0] + "'...");
                    fn();
                });
        }
    };

    LayoutManager.prototype.addPanel = function (name, panel, container) {
        this._panels[name] = panel;
        this._currentLayout.addToContainer(panel, container);
        panel.afterAppend();
    };

    LayoutManager.prototype.removePanel = function (name) {
        if (this._panels[name]) {
            this._currentLayout.remove(this._panels[name]);
            delete this._panels[name];
        }
    };

    LayoutManager.prototype._startProgressBar = function () {
        var _loader;

        //start progressbar
        _loader = new LoaderCircles({"containerElement": $('body')});
        _loader.start();
    };

    LayoutManager.prototype.setPanelReadOnly = function (readOnly) {
        var it;

        for (it in this._panels) {
            if (this._panels.hasOwnProperty(it)) {
                this._panels[it].setReadOnly(readOnly);
            }
        }
    };

    return LayoutManager;
});<|endoftext|>


<commit_before>import React, {Component} from 'react'
import {Button, Modal} from 'react-bootstrap'
import 'components/ReportApprovals.css'

import Fieldset from 'components/Fieldset'
import moment from 'moment'

const APPROVE = 'APPROVE'
const REJECT = 'REJECT'

export default class ReportApprovals extends Component {

    constructor(props) {
        super(props)
        this.state = { }
        this.renderApprovalAction = this.renderApprovalAction.bind(this)
        this.showApproversModal = this.showApproversModal.bind(this)
        this.closeApproversModal = this.closeApproversModal.bind(this)
    }

    render() {
        let report = this.props.report
        let title = "Approval State"
        let fieldset = null
        if(this.props.fullReport) {
            fieldset = this.renderFullApprovalView(report, title)
        } else {
            fieldset = this.renderCompactApprovalView(report, title)
        }
        return fieldset
    }

    approvalType(type) {
        switch(type) {
            case APPROVE:
                return {text: 'Approved', cssClass: 'btn-success approved'}
            case REJECT:
                return {text: 'Rejected', cssClass: 'btn-danger rejected'}
            default:
                return {text: 'Unknown', cssClass: 'default'}
        }
    }

    renderFullApprovalView(report, title){
        return (
            <Fieldset id="approvals" className="approval-fieldset" title={title}>
                { report.approvalStatus.map(action =>
                    this.renderApprovalAction(action)
                )}
            </Fieldset>
        )
    }

    renderCompactApprovalView(report, title){
        return (
            <Fieldset className="approval-fieldset compact" title={title}>
                { report.approvalStatus.map(action =>
                    this.renderCompactApprovalAction(action)
                )}
            </Fieldset>
        )
    }

    renderApprovalAction(action) {
        let step = action.step
        let approvalButton = this.renderApprovalButton(action)
        let approvalModal = this.renderApprovalModal(action)
        let approvalStatus = this.renderApprovalStatus(action)
        let approvalDetails = this.renderApprovalDetails(action)
        return (

                { approvalStatus }
                { approvalButton }
                { approvalDetails }
                { approvalModal }

        )
    }

    renderCompactApprovalAction(action) {
        let approvalButton = this.renderApprovalButton(action)
        let approvalModal = this.renderApprovalModal(action)
        return (

                { approvalButton }
                { approvalModal }

        )
    }

    renderApprovalDetails(action) {
        if(action.type){
            return(

                    By {action.person.name}

                    On {moment(action.createdAt).format('D MMM YYYY, h:mm a')}

            )
        }
    }

    renderApprovalStatus(action) {
        let status = (action.type) ? this.approvalType(action.type).text : 'Pending'
        return
{status}

    }

    renderApprovalButton(action) {
        let step = action.step
        let buttonColor = (action.type) ? 'btn-success ' : 'btn-pending '
        return (
            <Button className={buttonColor + 'btn-sm'} onClick={this.showApproversModal.bind(this, step)}>
                {step.name}
            </Button>
        )
    }

    showApproversModal(step) {
        step.showModal = true
        this.setState(this.state)
    }

    closeApproversModal(step) {
        step.showModal = false
        this.setState(this.state)
    }

    renderApprovalModal(action) {
        let step = action.step
        let approvalStatus = this.renderApprovalModalStatus(action)
        return (
            <Modal show={step.showModal} onHide={this.closeApproversModal.bind(this, step)}>
                <Modal.Header closeButton>
                    <Modal.Title>Approvers for {step.name}</Modal.Title>
                </Modal.Header>
                <Modal.Body>

                    {step.approvers.map(p =>

{p.name} - {p.person && p.person.name}

                    )}

                </Modal.Body>
                <Modal.Footer>{ approvalStatus }</Modal.Footer>
            </Modal>
        )
    }

    renderApprovalModalStatus(action){
        let pending = Pending
        if(action.type){
            let approvalType = this.approvalType(action.type)
            let cssClass = 'label ' + approvalType.cssClass
            return (
                 {approvalType.text} by {action.person.name} on
                     {moment(action.createdAt).format('D MMM YYYY, h:mm a')}

            )
        }
        return pending
    }
}
<commit_msg>NCI-Agency/anet#179 Add time to approval state details view in report view<commit_after>import React, {Component} from 'react'
import {Button, Modal} from 'react-bootstrap'
import 'components/ReportApprovals.css'

import Fieldset from 'components/Fieldset'
import moment from 'moment'

const APPROVE = 'APPROVE'
const REJECT = 'REJECT'

export default class ReportApprovals extends Component {

    constructor(props) {
        super(props)
        this.state = { }
        this.renderApprovalAction = this.renderApprovalAction.bind(this)
        this.showApproversModal = this.showApproversModal.bind(this)
        this.closeApproversModal = this.closeApproversModal.bind(this)
    }

    render() {
        let report = this.props.report
        let title = "Approval State"
        let fieldset = null
        if(this.props.fullReport) {
            fieldset = this.renderFullApprovalView(report, title)
        } else {
            fieldset = this.renderCompactApprovalView(report, title)
        }
        return fieldset
    }

    approvalType(type) {
        switch(type) {
            case APPROVE:
                return {text: 'Approved', cssClass: 'btn-success approved'}
            case REJECT:
                return {text: 'Rejected', cssClass: 'btn-danger rejected'}
            default:
                return {text: 'Unknown', cssClass: 'default'}
        }
    }

    renderFullApprovalView(report, title){
        return (
            <Fieldset id="approvals" className="approval-fieldset" title={title}>
                { report.approvalStatus.map(action =>
                    this.renderApprovalAction(action)
                )}
            </Fieldset>
        )
    }

    renderCompactApprovalView(report, title){
        return (
            <Fieldset className="approval-fieldset compact" title={title}>
                { report.approvalStatus.map(action =>
                    this.renderCompactApprovalAction(action)
                )}
            </Fieldset>
        )
    }

    renderApprovalAction(action) {
        let step = action.step
        let approvalButton = this.renderApprovalButton(action)
        let approvalModal = this.renderApprovalModal(action)
        let approvalStatus = this.renderApprovalStatus(action)
        let approvalDetails = this.renderApprovalDetails(action)
        return (

                { approvalStatus }
                { approvalButton }
                { approvalDetails }
                { approvalModal }

        )
    }

    renderCompactApprovalAction(action) {
        let approvalButton = this.renderApprovalButton(action)
        let approvalModal = this.renderApprovalModal(action)
        return (

                { approvalButton }
                { approvalModal }

        )
    }

    renderApprovalDetails(action) {
        if(action.type){
            return(

                    By {action.person.name}

                        On {moment(action.createdAt).format('D MMM YYYY')}

                        At {moment(action.createdAt).format('h:mm a')}

            )
        }
    }

    renderApprovalStatus(action) {
        let status = (action.type) ? this.approvalType(action.type).text : 'Pending'
        return
{status}

    }

    renderApprovalButton(action) {
        let step = action.step
        let buttonColor = (action.type) ? 'btn-success ' : 'btn-pending '
        return (
            <Button className={buttonColor + 'btn-sm'} onClick={this.showApproversModal.bind(this, step)}>
                {step.name}
            </Button>
        )
    }

    showApproversModal(step) {
        step.showModal = true
        this.setState(this.state)
    }

    closeApproversModal(step) {
        step.showModal = false
        this.setState(this.state)
    }

    renderApprovalModal(action) {
        let step = action.step
        let approvalStatus = this.renderApprovalModalStatus(action)
        return (
            <Modal show={step.showModal} onHide={this.closeApproversModal.bind(this, step)}>
                <Modal.Header closeButton>
                    <Modal.Title>Approvers for {step.name}</Modal.Title>
                </Modal.Header>
                <Modal.Body>

                    {step.approvers.map(p =>

{p.name} - {p.person && p.person.name}

                    )}

                </Modal.Body>
                <Modal.Footer>{ approvalStatus }</Modal.Footer>
            </Modal>
        )
    }

    renderApprovalModalStatus(action){
        let pending = Pending
        if(action.type){
            let approvalType = this.approvalType(action.type)
            let cssClass = 'label ' + approvalType.cssClass
            return (
                 {approvalType.text} by {action.person.name} on
                     {moment(action.createdAt).format('D MMM YYYY, h:mm a')}

            )
        }
        return pending
    }
}
<|endoftext|>