From: Bill Erickson Date: Thu, 24 May 2018 16:24:29 +0000 (-0400) Subject: LP#1750894 Cascade settings auto-magic migration X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=6f3ef203440160b04142d439f29125ba04742e40;p=working%2FEvergreen.git LP#1750894 Cascade settings auto-magic migration Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/src/templates/staff/admin/workstation/t_stored_prefs.tt2 b/Open-ILS/src/templates/staff/admin/workstation/t_stored_prefs.tt2 index e4c34c99f5..6722748644 100644 --- a/Open-ILS/src/templates/staff/admin/workstation/t_stored_prefs.tt2 +++ b/Open-ILS/src/templates/staff/admin/workstation/t_stored_prefs.tt2 @@ -20,42 +20,6 @@ Click on the delete (X) button to remove a preference value. - -
-
-
-
-

- [% l('Experimental: Migrate Workstation Settings to Server') %] -

-
-
-

-[% | l %]Settings may be migrated to the server using the first option below -as many times as needed for testing and verification. Once local settings -are deleted via the second option, no settings will remain to migrate, unless -they are added back through some external mechanism.[% END %] -

-

-[% | l %]Note that just because a setting exists under "Server Prefs" below -does not mean it has been migrated. Check the value by clicking on the -setting name to be sure.[% END %] -

-
- - -
-
-
-
-
-
diff --git a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js index fa0df895f3..893a2dcf09 100644 --- a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js +++ b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js @@ -804,33 +804,6 @@ function($scope , $q , egCore , egConfirmDialog , egProgressDialog , $timeout) { } ); } - - $scope.migrateServerSettings = function(deleteLocal) { - - var promise = !deleteLocal ? $q.when() : - egConfirmDialog.open( - egCore.strings.HATCH_SERVER_SETTINGS_MIGRATION_CONFIRM, '', {} - ).result; - - promise.then(function() { - egProgressDialog.open(); - // timeout added because closing a progress dialog too quickly - // (before it's registered that it's opened) can leave the - // dialog stranded open. This is only an issue when no local - // settings data exists to migrate, causing the migration to - // be instantaneous. - $timeout(function() { - egCore.hatch.migrateServerSettings(deleteLocal).then( - function() {}, - function() {}, - function() { - egProgressDialog.increment(); - } - )['finally'](egProgressDialog.close); - }, 100); - }); - } - }]) .controller('WSRegCtrl', diff --git a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js index ef3944f926..7eb8cf0555 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js +++ b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js @@ -49,6 +49,7 @@ function($scope , $q , $window , $location , $timeout , egCore , checkinSvc , eg $scope.grid_persist_key = $scope.is_capture ? 'circ.checkin.capture' : 'circ.checkin.checkin'; + // TODO: add this to the setting batch lookup below egCore.hatch.getItem('circ.checkin.strict_barcode') .then(function(sb){ $scope.strict_barcode = sb }); @@ -88,9 +89,18 @@ function($scope , $q , $window , $location , $timeout , egCore , checkinSvc , eg } // set modifiers from stored preferences - angular.forEach(modifiers, function(mod) { - egCore.hatch.getItem('eg.circ.checkin.' + mod) - .then(function(val) { if (val) $scope.modifiers[mod] = true }); + // We know these settings live on the server, so manually batch + // them for speed. + // TODO: once Hatch storage is deprecated, add a getItemBatch function. + var snames = modifiers.map(function(m) {return 'eg.circ.checkin.' + m;}); + egCore.hatch.getServerItemBatch(snames).then(function(settings) { + angular.forEach(settings, function(val, key) { + if (val === true) { + var parts = key.split('.') + var mod = parts.pop(); + $scope.modifiers[mod] = true; + } + }) }); // set / unset a checkin modifier diff --git a/Open-ILS/web/js/ui/default/staff/services/hatch.js b/Open-ILS/web/js/ui/default/staff/services/hatch.js index c360b79555..cd97e3ef1c 100644 --- a/Open-ILS/web/js/ui/default/staff/services/hatch.js +++ b/Open-ILS/web/js/ui/default/staff/services/hatch.js @@ -39,6 +39,10 @@ angular.module('egCoreMod') // Only affects *RemoteItem calls. service.keyCache = {}; + // Keep a local copy of all retrieved setting summaries, which indicate + // which setting types exist for each setting. + service.serverSettingSummaries = {}; + /** * List string prefixes for On-Call storage keys. On-Call keys * are those that can be set/get/remove'd from localStorage when @@ -67,47 +71,30 @@ angular.module('egCoreMod') /** * Settings with these prefixes will always live in the browser. */ - service.browserOnlyPrefixes = ['eg.workstation', 'eg.hatch']; - - // For testing (and possibly migration) purposes, hard-code the - // list of settings that are available to be applied on the server, - // either via workstation or user settings. Ultimately, the - // assumption will be that all settings live on the server except - // those specified by service.browserOnlyPrefixes. - service.serverSettings = [ - 'eg.circ.checkin.no_precat_alert', - 'eg.circ.checkin.noop', - 'eg.circ.checkin.void_overdues', - 'eg.circ.checkin.auto_print_holds_transits', - 'eg.circ.checkin.clear_expired', - 'eg.circ.checkin.retarget_holds', - 'eg.circ.checkin.retarget_holds_all', - 'eg.circ.checkin.hold_as_transit', - 'eg.circ.checkin.manual_float', - 'circ.checkin.strict_barcode', - 'eg.circ.patron.summary.collapse', - 'circ.bills.receiptonpay', - 'circ.renew.strict_barcode', - 'cat.holdings_show_copies', - 'cat.holdings_show_empty', - 'cat.holdings_show_vols' + service.browserOnlyPrefixes = [ + 'eg.workstation', + 'eg.hatch', + 'eg.cache', + 'current_tag_table_marc21_biblio', + 'FFPosTable_REC', + 'FFValueTable_REC' ]; - service.keyStoredOnServer = function(key) { + service.keyStoredInBrowser = function(key) { if (service.disableServerSettings) { - return false; + // When server-side storage is disabled, treat every + // setting like it's stored locally. + return true; } var browserOnly = false; - angular.forEach(service.browserOnlyPrefixes, function(pfx) { + service.browserOnlyPrefixes.forEach(function(pfx) { if (key.match(new RegExp('^' + pfx))) browserOnly = true; }); - if (browserOnly) return false; - - return service.serverSettings.indexOf(key) > -1 + return browserOnly; } // write a message to the Hatch port @@ -261,23 +248,37 @@ angular.module('egCoreMod') // get the value for a stored item service.getItem = function(key) { - if (service.keyStoredOnServer(key)) + if (!service.keyStoredInBrowser(key)) { return service.getServerItem(key); + } - if (!service.useSettings()) - return $q.when(service.getLocalItem(key)); + var deferred = $q.defer(); - if (service.hatchAvailable) - return service.getRemoteItem(key); + service.getBrowserItem(key).then( + function(val) { deferred.resolve(val); }, - if (service.keyIsOnCall(key)) { - console.warn("Unable to getItem from Hatch: " + key + - ". Retrieving item from local storage instead"); + function() { // Hatch error + if (service.keyIsOnCall(key)) { + console.warn("Unable to getItem from Hatch: " + key + + ". Retrieving item from local storage instead"); + deferred.resolve(service.getLocalItem(key)); + } + + deferred.reject("Unable to getItem from Hatch: " + key); + } + ); + return deferred.promise; + } + + service.getBrowserItem = function(key) { + if (service.useSettings()) { + if (service.hatchAvailable) { + return service.getRemoteItem(key); + } + } else { return $q.when(service.getLocalItem(key)); } - - console.error("Unable to getItem from Hatch: " + key); return $q.reject(); } @@ -296,7 +297,7 @@ angular.module('egCoreMod') service.getLocalItem = function(key) { var val = $window.localStorage.getItem(key); - if (val == null) return; + if (val === null || val === undefined) return; try { return JSON.parse(val); } catch(E) { @@ -325,38 +326,75 @@ angular.module('egCoreMod') */ service.setItem = function(key, value) { - if (service.keyStoredOnServer(key)) + if (!service.keyStoredInBrowser(key)) { return service.setServerItem(key, value); + } - if (!service.useSettings()) - return $q.when(service.setLocalItem(key, value)); + var deferred = $q.defer(); + service.setBrowserItem(key, value).then( + function(val) {deferred.resolve(val);}, - if (service.hatchAvailable) - return service.setRemoteItem(key, value); + function() { // Hatch error - if (service.keyIsOnCall(key)) { - console.warn("Unable to setItem in Hatch: " + - key + ". Setting in local storage instead"); + if (service.keyIsOnCall(key)) { + console.warn("Unable to setItem in Hatch: " + + key + ". Setting in local storage instead"); + + deferred.resolve(service.setLocalItem(key, value)); + } + deferred.reject("Unable to setItem in Hatch: " + key); + } + ); + } + service.setBrowserItem = function(key, value) { + if (service.useSettings()) { + if (service.hatchAvailable) { + return service.setRemoteItem(key, value); + } else { + return $q.reject('Unable to get item from hatch'); + } + } else { return $q.when(service.setLocalItem(key, value)); } - - console.error("Unable to setItem in Hatch: " + key); - return $q.reject(); } - service.setServerItem = function(key, value) { if (!service.auth) service.auth = $injector.get('egAuth'); if (!service.auth.token()) return $q.when(); + + // If we have already attempted to retrieve a value for this + // setting, then we can tell up front whether applying a value + // at the server will be an option. If not, store locally. + var summary = service.serverSettingSummaries[key]; + if (summary && + summary.has_user_setting() === 'f' && + summary.has_workstation_setting() === 'f') { + + console.warn('No server setting type exists for ' + key); + service.setLocalItem(key, value); + return $q.when(); + } + var settings = {}; settings[key] = value; + return egNet.request( 'open-ils.actor', 'open-ils.actor.settings.apply.user_or_ws', service.auth.token(), settings - ).then(function(settings) { - return service.keyCache[key] = value; + ).then(function(appliedCount) { + + if (appliedCount === 0) { + console.warn('No server setting type exists for ' + key); + // We were unable to store the setting on the server, + // presumably becuase no server-side setting type exists. + // Add to local storage instead. + service.setLocalItem(key, value); + } + + service.keyCache[key] = value; + return appliedCount; }); } @@ -373,85 +411,58 @@ angular.module('egCoreMod') 'open-ils.actor.settings.retrieve.atomic', [key], service.auth.token() ).then(function(settings) { - var val = settings[0].value(); - // The server returns null for undefined settings. - // Treat as undefined for backwards compat. - service.keyCache[key] = (val === null) ? undefined : val; - return service.keyCache[key]; + return service.handleServerItemResponse(settings[0]); }); } - service.migrateServerSettings = function(deleteLocal) { - - if (!service.auth) service.auth = $injector.get('egAuth'); - if (!service.auth.token()) return $q.reject('no authtoken'); + service.handleServerItemResponse = function(summary) { + var key = summary.name(); + var val = summary.value(); - var deferred = $q.defer(); - var settings = service.serverSettings.slice(0); // clone + summary.value(null); // avoid duplicate value caches + service.serverSettingSummaries[key] = summary; - // Allow get/remove calls to fall back to local options - // during migration. - service.disableServerSettings = true; + if (val !== null) { + // We have a server setting. Nothing left to do. + return $q.when(service.keyCache[key] = val); + } - function migrateNext(key) { + if (summary.has_user_setting() === 'f' && + summary.has_workstation_setting() === 'f') { - if (!key) { - service.disableServerSettings = false; - return deferred.resolve(); - } + console.warn('No server setting type exists for ' + + key + ', using local value.'); - service.migrateOneServerSetting(key, deleteLocal).then( - function() { - deferred.notify('migrated ' + key); - migrateNext(settings.shift()); - }, - function() { - service.disableServerSettings = false; - deferred.reject( - 'Something failed during settings migration'); - } - ); + return service.getBrowserItem(key); } - migrateNext(settings.shift()); - return deferred.promise; - } + // A server setting type exists, but no server value exists. + // See if we can migrate a local setting to the server. - service.migrateOneServerSetting = function(key, deleteLocal) { var deferred = $q.defer(); + service.getBrowserItem(key).then(function(browserVal) { - service.getItem(key).then(function(value) { - - if (value === undefined || value === null) { - console.log(key + ' has no value to migrate'); - // Nothing to migrate. - if (deleteLocal) { - // for good measure. - service.removeItem(key); - } - return deferred.resolve(); + if (browserVal === null && browserVal === undefined) { + // No local value to migrate. + return deferred.resolve(service.keyCache[key] = undefined); } - service.setServerItem(key, value).then( - function() { - console.debug( - 'setting ' + key + ' successfully stored on server'); - - if (!deleteLocal) { - return deferred.resolve(); + // Migrate the local value to the server. + + service.setServerItem(key, browserVal).then( + function(appliedCount) { + if (appliedCount === 1) { + console.info('setting ' + key + ' successfully ' + + 'migrated to a server setting'); + service.removeBrowserItem(key); // fire & forget + } else { + console.error('error migrating setting to server,' + + ' falling back to local value'); } - - service.removeItem(key).then(function() { - console.debug( - 'setting ' + key + ' removed from workstation'); - deferred.resolve(); - }); - }, - function() { - deferred.reject('applying server setting ' + key); + deferred.resolve(service.keyCache[key] = undefined); } ); - }) + }); return deferred.promise; } @@ -473,7 +484,7 @@ angular.module('egCoreMod') function(setting) { var val = setting.value(); // The server returns null for undefined settings. - // Treat as undefined for backwards compat. + // Treat as undefined locally for backwards compat. service.keyCache[setting.name()] = foundValues[setting.name()] = (val === null) ? undefined : val; @@ -497,8 +508,11 @@ angular.module('egCoreMod') // If the value is raw, pass it as 'value'. If it was // externally JSONified, pass it via jsonified. service.setLocalItem = function(key, value, jsonified) { - if (jsonified === undefined ) + if (jsonified === undefined ) { jsonified = JSON.stringify(value); + } else if (value === undefined) { + return; + } $window.localStorage.setItem(key, jsonified); } @@ -563,24 +577,40 @@ angular.module('egCoreMod') // remove a stored item service.removeItem = function(key) { - if (service.keyStoredOnServer(key)) + + if (!service.keyStoredInBrowser(key)) { return service.removeServerItem(key); + } - if (!service.useSettings()) - return $q.when(service.removeLocalItem(key)); + var deferred = $q.defer(); + service.removeBrowserItem(key).then( + function(response) {deferred.resolve(response);}, + function() { // Hatch error - if (service.hatchAvailable) - return service.removeRemoteItem(key); + if (service.keyIsOnCall(key)) { + console.warn("Unable to removeItem from Hatch: " + key + + ". Removing item from local storage instead"); - if (service.keyIsOnCall(key)) { - console.warn("Unable to removeItem from Hatch: " + key + - ". Removing item from local storage instead"); + deferred.resolve(service.removeLocalItem(key)); + } + deferred.reject("Unable to removeItem from Hatch: " + key); + } + ); + + return deferred.promise; + } + + service.removeBrowserItem = function(key) { + if (service.useSettings()) { + if (service.hatchAvailable) { + return service.removeRemoteItem(key); + } else { + return $q.reject('error talking to Hatch'); + } + } else { return $q.when(service.removeLocalItem(key)); } - - console.error("Unable to removeItem from Hatch: " + key); - return $q.reject(); } service.removeServerItem = function(key) {