LP#1848550: client-side caching of org settings for AngularJS
authorJeff Davis <jeff.davis@bc.libraries.coop>
Wed, 27 Nov 2019 00:12:37 +0000 (16:12 -0800)
committerBill Erickson <berickxx@gmail.com>
Tue, 28 Apr 2020 19:10:38 +0000 (15:10 -0400)
The web client almost always does live lookups any time it needs to
check an org setting.  But these settings rarely change, so it would
make sense to cache them.  They're already cached using Lovefield in
order to support offline; this commit checks the cache first, and only
does a live lookup if the setting is uncached.

The settings cache is cleared on login to ensure stale values don't
stick around forever.  To refresh the cache, simply logout and then
login; cached values will be deleted and the latest values will be
retrieved from the server when they are needed.

Signed-off-by: Jeff Davis <jeff.davis@bc.libraries.coop>
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Signed-off-by: Chris Sharp <csharp@georgialibraries.org>
Open-ILS/web/js/ui/default/staff/services/auth.js
Open-ILS/web/js/ui/default/staff/services/lovefield.js
Open-ILS/web/js/ui/default/staff/services/org.js

index 03e5d43..50818e5 100644 (file)
@@ -6,8 +6,10 @@
 angular.module('egCoreMod')
 
 .factory('egAuth', 
-       ['$q','$timeout','$rootScope','$window','$location','egNet','egHatch',
-function($q , $timeout , $rootScope , $window , $location , egNet , egHatch) {
+       ['$q','$timeout','$rootScope','$window','$location','egNet','egHatch','$injector',
+function($q , $timeout , $rootScope , $window , $location , egNet , egHatch , $injector) {
+
+    var egLovefield = null;
 
     var service = {
         // the currently active user (au) object
@@ -288,9 +290,13 @@ function($q , $timeout , $rootScope , $window , $location , egNet , egHatch) {
     }
 
     service.handle_login_ok = function(args, evt) {
+        if (!egLovefield) {
+            egLovefield = $injector.get('egLovefield');
+        }
         service.ws = args.workstation; 
         egHatch.setLoginSessionItem('eg.auth.token', evt.payload.authtoken);
         egHatch.setLoginSessionItem('eg.auth.time', evt.payload.authtime);
+        egLovefield.destroySettingsCache(); // force refresh of settings cache on login (LP#1848550)
         service.poll();
     }
 
index e7ec496..683390a 100644 (file)
@@ -335,6 +335,15 @@ angular.module('egCoreMod')
         );
     }
 
+    service.destroySettingsCache = function () {
+        if (lf.isOffline || service.cannotConnect) return $q.when();
+        return service.request({
+            schema: 'cache',
+            table: 'Setting',
+            action: 'deleteAll'
+        });
+    }
+
     service.setListInOfflineCache = function (type, list) {
         if (lf.isOffline || service.cannotConnect) return $q.when();
 
index 36c9ed2..99657d1 100644 (file)
@@ -111,6 +111,9 @@ function($q,  egEnv,  egAuth,  egNet , $injector) {
         if (!angular.isArray(names)) names = [names];
 
         if (lf.isOffline) {
+            // for offline, just use whatever we have managed to cache,
+            // even if the value is expired (since we can't refresh it
+            // from the server)
             return egLovefield.getSettingsCache(names).then(
                 function(settings) {
                     var hash = {};
@@ -126,26 +129,71 @@ function($q,  egEnv,  egAuth,  egNet , $injector) {
 
         if (!egAuth.user()) return $q.when();
 
-        var deferred = $q.defer();
         ou_id = ou_id || egAuth.user().ws_ou();
-        var here = (ou_id == egAuth.user().ws_ou());
+        if (ou_id != egAuth.user().ws_ou()) {
+            // we only cache settings for the current working location;
+            // if we have requested settings for some other org unit,
+            // skip the cache and pull settings directly from the server
+            return service.settingsFromServer(names, ou_id);
+        }
+
+        var deferred = $q.defer();
+        
+        var newNames = [];
+        angular.forEach(names, function(name) {
+            if (!angular.isDefined(service.cachedSettings[name]))
+                // we don't have a value for this setting yet 
+                newNames.push(name)
+        });
 
-       
-        if (here) { 
-            // only cache org settings retrieved for the current 
-            // workstation org unit.
-            var newNames = [];
+        // only retrieve uncached values
+        names = newNames;
+        if (names.length == 0)
+            return $q.when(service.cachedSettings);
+
+        // get settings from offline cache where possible;
+        // otherwise, get settings from server
+        egLovefield.getSettingsCache(names)
+        .then(function(settings) {
+
+            // populate values from offline cache
+            angular.forEach(settings, function (s) {
+                service.cachedSettings[s.name] = s.value;
+            });
+
+            // check if any requested settings were not in offline cache
+            var uncached = [];
             angular.forEach(names, function(name) {
                 if (!angular.isDefined(service.cachedSettings[name]))
-                    newNames.push(name)
+                    uncached.push(name);
             });
 
-            // only retrieve uncached values
-            names = newNames;
-            if (names.length == 0)
-                return $q.when(service.cachedSettings);
+            if (uncached.length == 0) {
+                // all requested settings were in the offline cache already
+                deferred.resolve(service.cachedSettings);
+            } else {
+                // cache was missing some settings; grab those from the server
+                service.settingsFromServer(uncached, ou_id)
+                .then(function() {
+                    deferred.resolve(service.cachedSettings);
+                });
+            }
+        });
+        return deferred.promise;
+    }
+
+    service.settingsFromServer = function(names, ou_id) {
+        if (!egLovefield) {
+            egLovefield = $injector.get('egLovefield');
         }
 
+        // allow non-array
+        if (!angular.isArray(names)) names = [names];
+
+        var deferred = $q.defer();
+        ou_id = ou_id || egAuth.user().ws_ou();
+        var here = (ou_id == egAuth.user().ws_ou());
+
         egNet.request(
             'open-ils.actor',
             'open-ils.actor.ou_setting.ancestor_default.batch',