uPortal-Project/uportal-home

View on GitHub
web/src/main/webapp/my-app/layout/services.js

Summary

Maintainability
D
1 day
Test Coverage
/*
 * Licensed to Apereo under one or more contributor license
 * agreements. See the NOTICE file distributed with this work
 * for additional information regarding copyright ownership.
 * Apereo licenses this file to you under the Apache License,
 * Version 2.0 (the "License"); you may not use this file
 * except in compliance with the License.  You may obtain a
 * copy of the License at the following location:
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
'use strict';

define(['angular', 'jquery'], function(angular, $) {
  var accessDeniedTemplate=
    '<p><strong>Sorry, you\'re not authorized to access this.</p><br><br>' +
    '<div class="center">' +
    '<i class=\'fa fa-exclamation-triangle fa-5x\'></i></div>' +
    '<p>If you\'re here by accident, head back to your My-UW ' +
    '<a href=\'/web\'>homepage</a>.</p>' +
    '<p>For help with authorization, contact the ' +
    '<a href="https://kb.wisc.edu/helpdesk">DoIT Help Desk</a>.</p>';

  return angular.module('my-app.layout.services', [])
    .factory('layoutService',
      ['$sce', '$http', '$log', 'miscService',
      'mainService', '$sessionStorage', '$q', 'SERVICE_LOC', 'APP_FLAGS',
      function($sce, $http, $log, miscService,
        mainService, $sessionStorage, $q, SERVICE_LOC, APP_FLAGS) {
        /**
        * NEW LAYOUT
        * To use new service layout
        * set useNewLayout flag in override.js to true,
        * and useOldLayout to false
        ************************/

        if (APP_FLAGS.useNewLayout) {
          $log.log('using new layout');

          var getLayout = function() {
            $log.log('getLayout (new backend version)');
            return checkLayoutCache().then(function(data) {
              var successFn;
              var errorFn;
              var defer;
              // first, check the local storage...
              if (data) {
                $log.log('getLayout (new version): found layout in cache: ' +
                  data);
                defer = $q.defer();
                defer.resolve(data);
                return defer.promise;
              }
              $log.log('getlayout (new version) did not find layout in cache');

              /**
               * Persists old layout data as new layout and uses that old-is-new
               * layout in current session.
               */
              var persistAndUse = function(result) {
                $log.log('persistAndUse: got data from old layout backend: ' +
                  result.data.layout);
                var formattedOldLayout = formatLayoutForCache(result.data);

                $log.log('persistAndUse: formatted old layout data as: ' +
                  formattedOldLayout.layout);
                // Parse out the fnames from the old layout JSON
                // The array of fnames is the new layout
                // Publish that to the new backend, and use it.
                // Old format:
                //  {layout: [ {"fname": "some-fname", ...},
                // {"fname": "some-other-fname", ...}]}
                // New format:
                //  {layout: ["some-fname", "some-other-fname"]}

                var oldArrayOfMaps = formattedOldLayout.layout;
                var newLayoutRepresentation = [];

                // use a Set to drop subsequent duplicates when migrating
                var fnameSet = new Set();
                for (var i = 0; i < oldArrayOfMaps.length; i++) {
                  var fname = oldArrayOfMaps[i].fname;
                  $log.log('Object in the array is ' +
                    angular.toJson(oldArrayOfMaps[i]));
                  $log.log('fname is ' + fname);
                  if (!fnameSet.has(fname)) {
                    fnameSet.add(fname);
                    newLayoutRepresentation.push(fname);
                  } else {
                    $log.log('Dropped duplicate fname ' + fname);
                  }
                }

                $log.log('persistAndUse: ' +
                  'parsed to fname array representation: ' +
                  newLayoutRepresentation);
                // persist the old layout to the new layout store
                $http({
                  method: 'POST',
                  url: SERVICE_LOC.newLayout,
                  data: {'layout': newLayoutRepresentation, 'new': false},
                  dataType: 'json',
                });

                storeLayoutInCache(
                  {'layout': newLayoutRepresentation, 'new': false});
                return {'layout': newLayoutRepresentation, 'new': false};
              };

              successFn = function(result) {
                $log.log('successFn with data from new layout service: ' +
                  result.data.layout);
                if (result.data.new) {
                  $log.log('User is new to the new layout service.');
                  // oh! It's a new user
                  // never before seen by new layout service.
                  // check the old layout service for the user's old layout
                  // and paste it into the new service.
                  return $http.get(
                    SERVICE_LOC.context + SERVICE_LOC.layout, {cache: true} )
                      .then(persistAndUse);
                } else {
                  $log.log('User is NOT new to new layout service.');
                  var data = formatLayoutForCache(result.data);
                  $log.log('Formatted new layout data as ' + data.layout);
                  storeLayoutInCache(data);
                  return data;
                }
              };

              errorFn = function(reason) {
                miscService.redirectUser(reason.status,
                  'layoutService request to ' + SERVICE_LOC.newLayout +
                    ' in new getLayout');
              };
            return $http.get(
                SERVICE_LOC.newLayout)
                  .then(successFn, errorFn);
            });
          };

          var addToHome = function addToHomeFunction(portlet) {
            var fname = portlet.fname;
            return getLayout().then(function(data) {
              var newLayout = [fname].concat(data.layout);
              return $http({
                method: 'POST',
                url: SERVICE_LOC.newLayout,
                data: {'layout': newLayout, 'new': false},
                dataType: 'json',
              });
            });
          };

          var removeFromHome = function removeFromHomeFunction(fname) {
            return getLayout().then(function(data) {
              var newLayout = data.layout.filter(function(val) {
                return val !== fname;
              });
              return $http({
                  method: 'POST',
                  url: SERVICE_LOC.newLayout,
                  data: {'layout': newLayout, 'new': false},
                  dataType: 'json',
              });
            });
          };

          var moveStuff = function moveStuffFunction() {
            return getLayout().then(function(data) {
              var newLayout = data.layout;
                return $http({
                  method: 'POST',
                  url: SERVICE_LOC.newLayout,
                  data: {'layout': newLayout, 'new': false},
                  dataType: 'json',
              });
            });
          };
        }

        /**
        ** OLD LAYOUT
        ** To use old service layout
        ** set useOldLayout flag in override.js to true,
        ** and useNewLayout to false
        *************************/

        if (APP_FLAGS.useOldLayout) {
          $log.log('using old layout');

          var getLayout = function() {
            return checkLayoutCache().then(function(data) {
                var successFn;
                var errorFn;
                var defer;

                // first, check the local storage...
                if (data) {
                    defer = $q.defer();
                    defer.resolve(data);
                    return defer.promise;
                }

                successFn = function(result) {
                    var data = formatLayoutForCache(result.data);
                    storeLayoutInCache(data);
                    return data;
                };

                errorFn = function(reason) {
                    miscService.redirectUser(reason.status, 'layoutService getLayout call (old implementation)');
                };

                // no caching...  request from the server
                return $http.get(
                  SERVICE_LOC.context + SERVICE_LOC.layout, {cache: true} )
                    .then(successFn, errorFn);
            });
          };

          var addToHome = function addToHomeFunction(portlet) {
              var fname = portlet.fname;
              var tabName = SERVICE_LOC.layoutTab;
              return $.ajax({
                  url: SERVICE_LOC.base + 'layout?action=addPortlet&fname=' +
                    fname + '&tabName=' + tabName,
                  type: 'POST',
                  data: null,
                  dataType: 'json',
                  async: true,
                  success: function(request, text) {
                      $log.log('Added ' + portlet.fname + ' successfully');
                      miscService.pushGAEvent(
                        'Layout Modification', 'Add', portlet.name);
                      return true;
                  },
                  error: function(request, text, error) {
                      $log.warn('failed to add app to home.');
                      return false;
                  },
              });
          };

          var removeFromHome = function removeFromHomeFunction(fname) {
              return $.ajax({
                  url: SERVICE_LOC.base +
                    'layout?action=removeByFName&fname=' + fname,
                  type: 'POST',
                  data: null,
                  dataType: 'json',
                  async: true,
                  success: function(request, text) {
                      $log.log('removed ' + fname + ' successfully.');
                      miscService.pushGAEvent(
                        'Layout Modification', 'Remove', fname);
                  },
                  error: function(request, text, error) {
                  },
              });
          };

          var moveStuff = function moveStuffFunction(
                index, length, sourceId, previousNodeId, nextNodeId) {
            var insertNode = function(sourceId, previousNodeId, nextNodeId) {
              var saveOrderURL = SERVICE_LOC.base +
                  'layout?action=movePortletAjax' +
                  '&sourceId=' + sourceId +
                  '&previousNodeId=' + previousNodeId +
                  '&nextNodeId=' + nextNodeId;
              $log.log(saveOrderURL);
              $.ajax({
                  url: saveOrderURL,
                  type: 'POST',
                  data: null,
                  dataType: 'json',
                  async: true,
                  success: function() {
                    $log.log('layout move successful.');
                  },
                  error: function(request, text, error) {
                    $log.error('Error persisting move ' + saveOrderURL);
                  },
              });
            };

            insertNode(sourceId, previousNodeId, nextNodeId);
          };
        }

        var getUncachedLayout = function() {
          $log.log('in getUncachedLayout');
          var successFn = function(result) {
              var data = formatLayoutForCache(result.data);
              storeLayoutInCache(data);
              return data;
          };

          var errorFn = function(reason) {
              miscService.redirectUser(reason.status, 'layoutService getUncachedLayout');
          };

          // no caching...  request from the server
          return $http.get(SERVICE_LOC.context + SERVICE_LOC.layout,
            {cache: true} )
              .then(successFn, errorFn);
        };

        var addToLayoutByFname = function addToLayoutByFname(fname) {
          var tabName = SERVICE_LOC.layoutTab;
          return $.ajax({
              url: SERVICE_LOC.base + 'layout?action=addPortlet&fname=' +
                fname + '&tabName=' + tabName,
              type: 'POST',
              data: null,
              dataType: 'json',
              async: true,
              success: function() {
                  $log.log('Added ' + fname + ' successfully');
                  miscService.pushGAEvent(
                    'Layout Modification', 'Add', fname);
                  },
              error: function() {
                  $log.warn('failed to add app to home.');
                  return false;
              },
          });
        };

        var checkLayoutCache = function() {
            var userPromise = mainService.getUser();
            return userPromise.then(function(user) {
                if ($sessionStorage.sessionKey === user.sessionKey &&
                    $sessionStorage.layout) {
                  return $sessionStorage.layout;
                }

                return null;
            });
        };

        var storeLayoutInCache = function(data) {
            var userPromise = mainService.getUser();
            userPromise.then(function(user) {
                $sessionStorage.sessionKey = user.sessionKey;
                $sessionStorage.layout = data;
                return user;
            }).catch(function() {
              $log.warn('Could not getUser');
            });
        };

        var formatLayoutForCache = function(data) {
          var result = {
            'layout': [],
          };
          //  Check if there are duplicate fnames in data.layout
          var filteredDataLayout = data.layout.filter(function(item, ind) {
              return data.layout.indexOf(item) == ind;
          });

          // layout will map to an array of objects each with an fname field.
          // those fnames represent, in order,
          // the home page content for the user
          if ($.isPlainObject(filteredDataLayout) &&
              $.isArray(data.layout.folders)) { // layout.json v1
            var folders = data.layout.folders.filter(function(el) {
              var result = false;
              if (el && SERVICE_LOC.layoutTab === el.title) {
                result = true;
              }
              return result;
            });
            if (folders && 0 < folders.length) {
              result.layout = folders[0].portlets;
            }
          } else if ( data.layout.navigation &&
              $.isPlainObject(data.layout.navigation) &&
              $.isArray(data.layout.navigation.tabs)) { // v4-3
            // var tabs will be tabs matching the layoutTab name
            // expected to be an array with length 1
            var tabs = data.layout.navigation.tabs.filter(function(el) {
              var result = false;
              if (el && SERVICE_LOC.layoutTab === el.name) {
                result = true;
              }
              return result;
            });
            if (tabs && 0 < tabs.length) {
              var columns = tabs[0].content;
              var portlets = [];
              if ($.isArray(columns)) {
                columns.forEach( function(column) {
                  portlets = portlets.concat(column.content);
                });
              }
              result.layout = portlets;
            }
          } else if ($.isArray(filteredDataLayout)) { // layoutDoc
            result.layout = filteredDataLayout;
          }

          return result;
        };

        var getApp = function(fname) {
            return $http.get(SERVICE_LOC.base + 'portlet/' +fname + '.json',
              {cache: true})
              .then(
                function(result) {
                    return result;
                },
                function(reason) {
                    miscService.redirectUser(reason.status, 'getApp call');
                    if (reason.status === 403) {
                      reason.deniedTemplate =
                        $sce.trustAsHtml(accessDeniedTemplate);
                    }
                    return reason;
                }
            );
        };

        var getNewStuffFeed = function() {
          return $http.get(SERVICE_LOC.newstuffInfo, {cache: true})
              .then(function(result) {
                  return result.data.stuff;
                },
                function(reason) {
                  miscService.redirectUser(reason.status,
                    'new stuff json feed call');
                }
              );
        };

        var getWidgetJson = function(portlet) {
          return $http.get(portlet.widgetURL, {cache: true})
              .then(function(result) {
                  var data = result.data;
                  if (data) {
                    if (data.result) {
                      portlet.widgetData = data.result;
                    }
                    if (data.content) {
                      portlet.widgetContent = data.content;
                    }
                    $log.log(portlet.fname +
                      '\'s widget data came back with data');
                  }
                  return data;
                },
                function(reason) {
                    miscService.redirectUser(reason.status,
                      'widget json for ' + portlet.fname + ' failed.');
                }
              );
        };


        var getExclusiveMarkup = function(portlet) {
          return $http.get(SERVICE_LOC.context +
              '/p/' + portlet.fname + '/exclusive/render.uP', {cache: true})
              .then(function(result) {
                  var data = result.data;
                  if (data) {
                    portlet.exclusiveContent = $sce.trustAsHtml(data);
                    $log.log(portlet.fname +
                      '\'s exclusive data came back with data');
                  } else {
                    portlet.exclusiveContent =
                        '<div class="alert alert-danger" role="alert">' +
                        'This service is unavailable right now. ' +
                        'Please check back later.</div>';
                  }

                  return data;
                },
                function(reason) {
                  if (reason.status===403) {
                    portlet.exclusiveContent =
                      $sce.trustAsHtml(accessDeniedTemplate);
                  } else {
                    miscService.redirectUser(reason.status,
                      'exclusive markup for ' + portlet.fname + ' failed.');
                  }
                }
              );
        };

        var getGuestMode = function() {
          return mainService.isGuest();
        };

          var getRSSJsonified = function(feedURL) {
            // This is a hack.
            // It would be maybe healthier to enhance RSS widget to
            // support
            // 1. configure explicitly with URL that generates the JSON
            // 2. configure with pithy key to feed ( campus_news )
            // 3. configure explicitly with RSS URL and automatically
            //    run that through an RSS to JSON converter.
            // Whereas this hack repurposes existing configuration for (3)
            // to do (1).
          return $http.get(feedURL, {cache: true});
        };

        return {
            getLayout: getLayout,
            getUncachedLayout: getUncachedLayout,
            formatLayoutForCache: formatLayoutForCache,
            getApp: getApp,
            moveStuff: moveStuff,
            getNewStuffFeed: getNewStuffFeed,
            addToHome: addToHome,
            addToLayoutByFname: addToLayoutByFname,
            removeFromHome: removeFromHome,
            getWidgetJson: getWidgetJson,
            getExclusiveMarkup: getExclusiveMarkup,
            getGuestMode: getGuestMode,
            getRSSJsonified: getRSSJsonified,
        };
    }]);
});