From 877b3c15ad379292a1afdad0d7eaecb04cc9d60e Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Fri, 3 Aug 2018 18:41:16 -0400 Subject: [PATCH] LP#1775466 Server settings continued, needs a bit more testing Signed-off-by: Bill Erickson --- Open-ILS/src/eg2/src/app/core/auth.service.ts | 29 +++--- .../src/eg2/src/app/core/server-store.service.ts | 114 +++++++++++++++++++++ Open-ILS/src/eg2/src/app/core/store.service.ts | 87 +++------------- .../src/app/share/grid/grid-toolbar.component.html | 4 +- .../src/app/share/grid/grid-toolbar.component.ts | 4 +- .../src/eg2/src/app/share/grid/grid.component.ts | 4 +- Open-ILS/src/eg2/src/app/share/grid/grid.ts | 62 +++++++---- .../src/eg2/src/app/share/util/audio.service.ts | 4 +- .../workstations/workstations.component.ts | 45 ++++---- .../eg2/src/app/staff/catalog/resolver.service.ts | 4 +- Open-ILS/src/eg2/src/app/staff/login.component.ts | 9 +- 11 files changed, 214 insertions(+), 152 deletions(-) create mode 100644 Open-ILS/src/eg2/src/app/core/server-store.service.ts diff --git a/Open-ILS/src/eg2/src/app/core/auth.service.ts b/Open-ILS/src/eg2/src/app/core/auth.service.ts index 7d58dda0ef..84897f0093 100644 --- a/Open-ILS/src/eg2/src/app/core/auth.service.ts +++ b/Open-ILS/src/eg2/src/app/core/auth.service.ts @@ -286,23 +286,22 @@ export class AuthService { } return new Promise((resolve, reject) => { - this.store.getItem('eg.workstation.all') - .then(workstations => { - - if (workstations) { - const ws = workstations.filter( - w => Number(w.id) === Number(this.user().wsid()))[0]; - - if (ws) { - this.activeUser.workstation = ws.name; - this.workstationState = AuthWsState.VALID; - return resolve(); - } + const workstations = + this.store.getLocalItem('eg.workstation.all'); + + if (workstations) { + const ws = workstations.filter( + w => Number(w.id) === Number(this.user().wsid()))[0]; + + if (ws) { + this.activeUser.workstation = ws.name; + this.workstationState = AuthWsState.VALID; + return resolve(); } + } - this.workstationState = AuthWsState.NOT_FOUND_LOCAL; - reject(); - }); + this.workstationState = AuthWsState.NOT_FOUND_LOCAL; + reject(); }); } diff --git a/Open-ILS/src/eg2/src/app/core/server-store.service.ts b/Open-ILS/src/eg2/src/app/core/server-store.service.ts new file mode 100644 index 0000000000..c08dab85a2 --- /dev/null +++ b/Open-ILS/src/eg2/src/app/core/server-store.service.ts @@ -0,0 +1,114 @@ +/** + * Set and get server-stored settings. + */ +import {Injectable} from '@angular/core'; +import {AuthService} from './auth.service'; +import {NetService} from './net.service'; + +// Settings summary objects returned by the API +interface ServerSettingSummary { + name: string; + value: string; + has_org_setting: boolean; + has_user_setting: boolean; + has_workstation_setting: boolean; +} + +@Injectable({providedIn: 'root'}) +export class ServerStoreService { + + cache: {[key: string]: ServerSettingSummary}; + + constructor( + private net: NetService, + private auth: AuthService) { + this.cache = {}; + } + + setItem(key: string, value: any): Promise { + + if (!this.auth.token()) { + return Promise.reject('Auth required to apply settings'); + } + + const setting: any = {}; + setting[key] = value; + + return this.net.request( + 'open-ils.actor', + 'open-ils.actor.settings.apply.user_or_ws', + this.auth.token(), setting) + + .toPromise().then(appliedCount => { + + if (Number(appliedCount) > 0) { // value applied + return this.cache[key] = value; + } + + return Promise.reject( + `No user or workstation setting type exists for: "${key}".\n` + + 'Create a ws/user setting type or use setLocalItem() to ' + + 'store the value locally.' + ); + }); + } + + // Returns a single setting value + getItem(key: string): Promise { + return this.getItemBatch([key]).then( + settings => settings[key] + ); + } + + // Returns a set of key/value pairs for the requested settings + getItemBatch(keys: string[]): Promise { + + const values: any = {}; + keys.forEach(key => { + if (this.cache[key]) { + values[key] = this.cache[key]; + } + }); + + if (keys.length === Object.keys(values).length) { + // All values are cached already + return Promise.resolve(values); + } + + if (!this.auth.token()) { + // Authtokens require for fetching server settings, but + // calls to retrieve settings could potentially occur + // before auth completes -- Ideally not, but just to be safe. + return Promise.resolve({}); + } + + // Server call required. Limit the settings to lookup to those + // we don't already have cached. + const serverKeys = []; + keys.forEach(key => { + if (!Object.keys(values).includes(key)) { + serverKeys.push(key); + } + }); + + return new Promise((resolve, reject) => { + this.net.request( + 'open-ils.actor', + 'open-ils.actor.settings.retrieve', + serverKeys, this.auth.token() + ).subscribe( + summary => { + this.cache[summary.name] = + values[summary.name] = summary.value; + }, + err => reject, + () => resolve(values) + ); + }); + } + + removeItem(key: string): Promise { + return this.setItem(key, null); + } +} + diff --git a/Open-ILS/src/eg2/src/app/core/store.service.ts b/Open-ILS/src/eg2/src/app/core/store.service.ts index 43ffa6f148..af1e0e924b 100644 --- a/Open-ILS/src/eg2/src/app/core/store.service.ts +++ b/Open-ILS/src/eg2/src/app/core/store.service.ts @@ -1,18 +1,16 @@ /** * Store and retrieve data from various sources. + * + * Data Types: + * 1. LocalItem: Stored in window.localStorage and persist indefinitely. + * 2. SessionItem: Stored in window.sessionStorage and persist until + * the end of the current browser tab/window. Data is only available + * to the tab/window where the data was set. + * 3. LoginItem: Stored as session cookies and persist until the browser + * is closed. These values are avalable to all browser windows/tabs. */ -import {Injectable, Injector} from '@angular/core'; +import {Injectable} from '@angular/core'; import {CookieService} from 'ngx-cookie'; -import {NetService} from './net.service'; - -// Settings summary objects returned by the API -export interface ServerSettingSummary { - name: string; - value: string; - has_org_setting: boolean; - has_user_setting: boolean; - has_workstation_setting: boolean; -} @Injectable({providedIn: 'root'}) export class StoreService { @@ -23,11 +21,6 @@ export class StoreService { // Note cookies shared with /eg/staff must be stored at "/" loginSessionBasePath = '/'; - // Loaded on demand to avoid circular ref in compiler. - auth: any; - - cache: {[key: string]: ServerSettingSummary}; - // Set of keys whose values should disappear at logout. loginSessionKeys: string[] = [ 'eg.auth.token', @@ -37,10 +30,7 @@ export class StoreService { ]; constructor( - private injector: Injector, - private cookieService: CookieService, - private net: NetService) { - this.cache = {}; + private cookieService: CookieService) { } private parseJson(valJson: string): any { @@ -62,32 +52,21 @@ export class StoreService { this.loginSessionKeys.push(key); } - setItem(key: string, val: any, isJson?: Boolean): Promise { - // TODO: route keys appropriately - this.setLocalItem(key, val, isJson); - return Promise.resolve(); - } - - setLocalItem(key: string, val: any, isJson?: Boolean): void { + setLocalItem(key: string, val: any, isJson?: boolean): void { if (!isJson) { val = JSON.stringify(val); } - // console.debug(`${key} ${val}`); window.localStorage.setItem(key, val); } - setServerItem(key: string, val: any): Promise { - return Promise.resolve(); - } - - setSessionItem(key: string, val: any, isJson?: Boolean): void { + setSessionItem(key: string, val: any, isJson?: boolean): void { if (!isJson) { val = JSON.stringify(val); } window.sessionStorage.setItem(key, val); } - setLoginSessionItem(key: string, val: any, isJson?: Boolean): void { + setLoginSessionItem(key: string, val: any, isJson?: boolean): void { if (!isJson) { val = JSON.stringify(val); } @@ -95,41 +74,10 @@ export class StoreService { {path : this.loginSessionBasePath, secure: true}); } - getItem(key: string): Promise { - // TODO: route keys appropriately - return Promise.resolve(this.getLocalItem(key)); - } - getLocalItem(key: string): any { return this.parseJson(window.localStorage.getItem(key)); } - getServerItem(key: string): Promise { - - if (this.cache[key]) { - return Promise.resolve(this.cache[key].value); - } - - if (!this.auth) { - this.auth = this.injector.get('AuthService'); - } - - if (this.auth.token()) { - return Promise.reject('auth token required for server settings'); - } - - return this.net.request( - 'open-ils.actor', - 'open-ils.actor.settings.retrieve', - [key], this.auth.token() - ).toPromise().then( - (summary: ServerSettingSummary) => { - this.cache[summary.name] = summary; - return summary.value; - } - ); - } - getSessionItem(key: string): any { return this.parseJson(window.sessionStorage.getItem(key)); } @@ -138,19 +86,10 @@ export class StoreService { return this.parseJson(this.cookieService.get(key)); } - removeItem(key: string): Promise { - // TODO: route keys appropriately - return Promise.resolve(this.removeLocalItem(key)); - } - removeLocalItem(key: string): void { window.localStorage.removeItem(key); } - removeServerItem(key: string): Promise { - return Promise.resolve(); - } - removeSessionItem(key: string): void { window.sessionStorage.removeItem(key); } diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html index 6cd7b6bb2d..48b9e3913e 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.html @@ -112,9 +112,9 @@ Manage Column Widths + (click)="saveGridConfig()"> save - Save Columns + Save Grid Settings diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts index fd15ba986f..5c8b523498 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts +++ b/Open-ILS/src/eg2/src/app/share/grid/grid-toolbar.component.ts @@ -25,12 +25,12 @@ export class GridToolbarComponent implements OnInit { ngOnInit() {} - saveColumns() { + saveGridConfig() { // TODO: when server-side settings are supported, this operation // may offer to save to user/workstation OR org unit settings // depending on perms. - this.gridContext.saveColumns().then( + this.gridContext.saveGridConfig().then( // hide the with config after saving ok => this.colWidthConfig.isVisible = false, err => console.error(`Error saving columns: ${err}`) diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts index 3a6d073744..db02e627ad 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.component.ts @@ -3,7 +3,7 @@ import {Component, Input, Output, OnInit, AfterViewInit, EventEmitter, import {Subscription} from 'rxjs/Subscription'; import {IdlService} from '@eg/core/idl.service'; import {OrgService} from '@eg/core/org.service'; -import {StoreService} from '@eg/core/store.service'; +import {ServerStoreService} from '@eg/core/server-store.service'; import {FormatService} from '@eg/core/format.service'; import {GridContext, GridColumn, GridDataSource, GridRowFlairEntry} from './grid'; @@ -88,7 +88,7 @@ export class GridComponent implements OnInit, AfterViewInit, OnDestroy { constructor( private idl: IdlService, private org: OrgService, - private store: StoreService, + private store: ServerStoreService, private format: FormatService ) { this.context = diff --git a/Open-ILS/src/eg2/src/app/share/grid/grid.ts b/Open-ILS/src/eg2/src/app/share/grid/grid.ts index b2af62144d..0c40a7633c 100644 --- a/Open-ILS/src/eg2/src/app/share/grid/grid.ts +++ b/Open-ILS/src/eg2/src/app/share/grid/grid.ts @@ -6,7 +6,7 @@ import {Observable} from 'rxjs/Observable'; import {Subscription} from 'rxjs/Subscription'; import {IdlService, IdlObject} from '@eg/core/idl.service'; import {OrgService} from '@eg/core/org.service'; -import {StoreService} from '@eg/core/store.service'; +import {ServerStoreService} from '@eg/core/server-store.service'; import {FormatService} from '@eg/core/format.service'; import {Pager} from '@eg/share/util/pager'; @@ -280,10 +280,8 @@ export class GridColumnSet { compileSaveObject(): GridColumnPersistConf[] { // only store information about visible columns. - const conf = this.displayColumns(); - - // scrunch the data down to just the needed info - return conf.map(col => { + // scrunch the data down to just the needed info. + return this.displayColumns().map(col => { const c: GridColumnPersistConf = {name : col.name}; if (col.align !== 'left') { c.align = col.align; } if (col.flex !== 2) { c.flex = Number(col.flex); } @@ -429,12 +427,14 @@ export class GridContext { // Services injected by our grid component idl: IdlService; org: OrgService; - store: StoreService; + store: ServerStoreService; format: FormatService; constructor( - idl: IdlService, org: OrgService, - store: StoreService, format: FormatService) { + idl: IdlService, + org: OrgService, + store: ServerStoreService, + format: FormatService) { this.idl = idl; this.org = org; @@ -459,7 +459,7 @@ export class GridContext { // Load initial settings and data. initData() { - this.applyColumnsConfig() + this.applyGridConfig() .then(ok => this.dataSource.requestPage(this.pager)) .then(ok => this.listenToPager()); } @@ -468,9 +468,21 @@ export class GridContext { this.ignorePager(); } - applyColumnsConfig(): Promise { - return this.getColumnsConfig(this.persistKey) - .then(conf => this.columnSet.applyColumnSettings(conf)); + applyGridConfig(): Promise { + return this.getGridConfig(this.persistKey) + .then(conf => { + let columns = []; + if (conf) { + columns = conf.columns; + if (conf.limit) { + this.pager.limit = conf.limit; + } + } + + // This is called regardless of the presence of saved + // settings so defaults can be applied. + this.columnSet.applyColumnSettings(columns); + }); } reload() { @@ -834,24 +846,24 @@ export class GridContext { }); } - - saveColumns(): Promise { + saveGridConfig(): Promise { if (!this.persistKey) { throw new Error('Grid persistKey required to save columns'); } - const compiled: GridColumnPersistConf[] = - this.columnSet.compileSaveObject(); - return this.store.setItem('eg.grid.' + this.persistKey, compiled); - } + const conf = new GridPersistConf(); + conf.version = 2; + conf.limit = this.pager.limit; + conf.columns = this.columnSet.compileSaveObject(); + return this.store.setItem('eg.grid.' + this.persistKey, conf); + } - // TODO: saveColumnsAsOrgSetting(...) + // TODO: saveGridConfigAsOrgSetting(...) - getColumnsConfig(persistKey: string): Promise { - if (!persistKey) { return Promise.resolve([]); } + getGridConfig(persistKey: string): Promise { + if (!persistKey) { return Promise.resolve(null); } return this.store.getItem('eg.grid.' + persistKey); } - } export class GridColumnPersistConf { @@ -861,6 +873,12 @@ export class GridColumnPersistConf { align?: string; } +export class GridPersistConf { + version: number; + limit: number; + columns: GridColumnPersistConf[]; +} + // Actions apply to specific rows export class GridToolbarAction { diff --git a/Open-ILS/src/eg2/src/app/share/util/audio.service.ts b/Open-ILS/src/eg2/src/app/share/util/audio.service.ts index 9e5b9a9d49..3f3320aa34 100644 --- a/Open-ILS/src/eg2/src/app/share/util/audio.service.ts +++ b/Open-ILS/src/eg2/src/app/share/util/audio.service.ts @@ -17,7 +17,7 @@ * workstation settings. */ import {Injectable, EventEmitter} from '@angular/core'; -import {StoreService} from '@eg/core/store.service'; +import {ServerStoreService} from '@eg/core/server-store.service'; const AUDIO_BASE_URL = '/audio/notifications/'; @Injectable() @@ -26,7 +26,7 @@ export class AudioService { // map of requested audio path to resolved path private urlCache: {[path: string]: string} = {}; - constructor(private store: StoreService) {} + constructor(private store: ServerStoreService) {} play(path: string): void { if (path) { diff --git a/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.ts b/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.ts index 2c204e59e1..9fb0d7e37b 100644 --- a/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/admin/workstation/workstations/workstations.component.ts @@ -50,20 +50,15 @@ export class WorkstationsComponent implements OnInit { ) {} ngOnInit() { - this.store.getItem('eg.workstation.all') - .then(list => this.workstations = list || []) - .then(noop => this.store.getItem('eg.workstation.default')) - .then(defWs => { - this.defaultName = defWs; - this.selectedName = this.auth.workstation() || defWs; - }) - .then(noop => { - const rm = this.route.snapshot.paramMap.get('remove'); - if (rm) { - this.removeSelected(this.removeWorkstation = rm); - } - }); + this.workstations = this.store.getLocalItem('eg.workstation.all'); + this.defaultName = this.store.getLocalItem('eg.workstation.default'); + this.selectedName = this.auth.workstation() || this.defaultName; + const rm = this.route.snapshot.paramMap.get('remove'); + if (rm) { + this.removeSelected(this.removeWorkstation = rm); + } + // TODO: use the org selector limitPerm option this.perm.hasWorkPermAt(['REGISTER_WORKSTATION'], true) .then(perms => { // Disable org units that cannot have users and any @@ -90,7 +85,7 @@ export class WorkstationsComponent implements OnInit { setDefault(): void { if (this.selected()) { this.defaultName = this.selected().name; - this.store.setItem('eg.workstation.default', this.defaultName); + this.store.setLocalItem('eg.workstation.default', this.defaultName); } } @@ -100,11 +95,11 @@ export class WorkstationsComponent implements OnInit { } this.workstations = this.workstations.filter(w => w.name !== name); - this.store.setItem('eg.workstation.all', this.workstations); + this.store.setLocalItem('eg.workstation.all', this.workstations); if (this.defaultName === name) { this.defaultName = null; - this.store.removeItem('eg.workstation.default'); + this.store.removeLocalItem('eg.workstation.default'); } } @@ -177,16 +172,14 @@ export class WorkstationsComponent implements OnInit { }; this.workstations.push(ws); - this.store.setItem('eg.workstation.all', this.workstations) - .then(ok => { - this.newName = ''; - // when registering our first workstation, mark it as the - // default and show it as selected in the ws selector. - if (this.workstations.length === 1) { - this.selectedName = ws.name; - this.setDefault(); - } - }); + this.store.setLocalItem('eg.workstation.all', this.workstations) + this.newName = ''; + // when registering our first workstation, mark it as the + // default and show it as selected in the ws selector. + if (this.workstations.length === 1) { + this.selectedName = ws.name; + this.setDefault(); + } } } diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts b/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts index f91e1fd92c..729beea0b0 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/resolver.service.ts @@ -3,7 +3,7 @@ import {Observable} from 'rxjs/Observable'; import {Observer} from 'rxjs/Observer'; import {Router, Resolve, RouterStateSnapshot, ActivatedRouteSnapshot} from '@angular/router'; -import {StoreService} from '@eg/core/store.service'; +import {ServerStoreService} from '@eg/core/server-store.service'; import {NetService} from '@eg/core/net.service'; import {OrgService} from '@eg/core/org.service'; import {AuthService} from '@eg/core/auth.service'; @@ -16,7 +16,7 @@ export class CatalogResolver implements Resolve> { constructor( private router: Router, - private store: StoreService, + private store: ServerStoreService, private org: OrgService, private net: NetService, private auth: AuthService, diff --git a/Open-ILS/src/eg2/src/app/staff/login.component.ts b/Open-ILS/src/eg2/src/app/staff/login.component.ts index 0083c321bb..7d663bf907 100644 --- a/Open-ILS/src/eg2/src/app/staff/login.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/login.component.ts @@ -35,11 +35,10 @@ export class StaffLoginComponent implements OnInit { // Focus username this.renderer.selectRootElement('#username').focus(); - this.store.getItem('eg.workstation.all') - .then(list => this.workstations = list || []) - .then(list => this.store.getItem('eg.workstation.default')) - .then(defWs => this.args.workstation = defWs) - .then(noOp => this.applyWorkstation()); + this.workstations = this.store.getLocalItem('eg.workstation.all'); + this.args.workstation = + this.store.getLocalItem('eg.workstation.default'); + this.applyWorkstation(); } applyWorkstation() { -- 2.11.0