From: Bill Erickson Date: Wed, 29 Nov 2017 22:26:37 +0000 (-0500) Subject: LP#626157 Ang2 experiments X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=005335db3a446dc9c0be5e6744ec33479c9b7aee;p=working%2FEvergreen.git LP#626157 Ang2 experiments Signed-off-by: Bill Erickson --- diff --git a/Open-ILS/webby-src/src/app/base.module.ts b/Open-ILS/webby-src/src/app/base.module.ts index 0a5ff2beb1..0bd2a07278 100644 --- a/Open-ILS/webby-src/src/app/base.module.ts +++ b/Open-ILS/webby-src/src/app/base.module.ts @@ -15,10 +15,12 @@ import {WelcomeComponent} from './welcome.component'; // Import and 'provide' globally required services. import {EgEventService} from '@eg/core/event.service'; +import {EgStoreService} from '@eg/core/store.service'; import {EgIdlService} from '@eg/core/idl.service'; import {EgNetService} from '@eg/core/net.service'; import {EgAuthService} from '@eg/core/auth.service'; -import {EgStoreService} from '@eg/core/store.service'; +import {EgPcrudService} from '@eg/core/pcrud.service'; +import {EgOrgService} from '@eg/core/org.service'; @NgModule({ declarations: [ @@ -33,10 +35,12 @@ import {EgStoreService} from '@eg/core/store.service'; ], providers: [ EgEventService, + EgStoreService, EgIdlService, EgNetService, EgAuthService, - EgStoreService + EgPcrudService, + EgOrgService ], exports: [ ], diff --git a/Open-ILS/webby-src/src/app/core/auth.service.ts b/Open-ILS/webby-src/src/app/core/auth.service.ts index 3e78d87ab2..4a956a598e 100644 --- a/Open-ILS/webby-src/src/app/core/auth.service.ts +++ b/Open-ILS/webby-src/src/app/core/auth.service.ts @@ -11,11 +11,11 @@ import { EgStoreService } from './store.service'; // Models a login instance. class EgAuthUser { user: EgIdlObject; - workstation: String; // workstation name - token: String; - authtime: Number; + workstation: string; // workstation name + token: string; + authtime: number; - constructor(token: String, authtime: Number, workstation?: String) { + constructor(token: string, authtime: number, workstation?: string) { this.token = token; this.workstation = workstation; this.authtime = authtime; @@ -24,10 +24,10 @@ class EgAuthUser { // Params required for calling the login() method. interface EgAuthLoginArgs { - username: String, - password: String, - type: String, - workstation?: String + username: string, + password: string, + type: string, + workstation?: string } export enum EgAuthWsState { @@ -65,11 +65,11 @@ export class EgAuthService { }; // Workstation name. - workstation(): String { + workstation(): string { return this.activeUser.workstation; }; - token(): String { + token(): string { return this.activeUser ? this.activeUser.token : null; }; diff --git a/Open-ILS/webby-src/src/app/core/net.service.ts b/Open-ILS/webby-src/src/app/core/net.service.ts index c6082a75b3..b7b3648f27 100644 --- a/Open-ILS/webby-src/src/app/core/net.service.ts +++ b/Open-ILS/webby-src/src/app/core/net.service.ts @@ -29,16 +29,23 @@ export class EgNetRequest { params : any[]; observer : Observer; superseded : Boolean = false; + // If set, this will be used instead of a one-off OpenSRF.ClientSession. + session? : any; // Last EgEvent encountered by this request. // Most callers will not need to import EgEvent since the parsed // event will be available here. evt: EgEvent; - constructor(service: String, method: String, params: any[]) { + constructor(service: String, method: String, params: any[], session?: any) { this.service = service; this.method = method; this.params = params; + if (session) { + this.session = session; + } else { + this.session = new OpenSRF.ClientSession(service); + } } } @@ -59,7 +66,7 @@ export class EgNetService { this.authExpired$ = new EventEmitter(); } - // Variadic params version + // Standard request call -- Variadic params version request(service: String, method: String, ...params: any[]): Observable { return this.requestWithParamList(service, method, params); } @@ -67,22 +74,25 @@ export class EgNetService { // Array params version requestWithParamList(service: String, method: String, params: any[]): Observable { + return this.requestCompiled( + new EgNetRequest(service, method, params)); + } - var request = new EgNetRequest(service, method, params); - + requestCompiled(request: EgNetRequest): Observable { return Observable.create( observer => { request.observer = observer; - this.sendRequest(request); + this.sendCompiledRequest(request); } ); } - private sendRequest(request: EgNetRequest): void { + // Version with pre-compiled EgNetRequest object + sendCompiledRequest(request: EgNetRequest): void { OpenSRF.Session.transport = OSRF_TRANSPORT_TYPE_WS; var this_ = this; - new OpenSRF.ClientSession(request.service).request({ + request.session.request({ async : true, method : request.method, params : request.params, @@ -104,6 +114,12 @@ export class EgNetService { let msg = `${request.method} failed! stat=${statCode} msg=${statMsg}`; console.error(msg); + + if (request.service == 'open-ils.pcrud' && statCode == 401) { + // 401 is the PCRUD equivalent of a NO_SESSION event + this_.authExpired$.emit(request); + } + request.observer.error(msg); } diff --git a/Open-ILS/webby-src/src/app/core/org.service.ts b/Open-ILS/webby-src/src/app/core/org.service.ts index 44eea6a01f..d641555243 100644 --- a/Open-ILS/webby-src/src/app/core/org.service.ts +++ b/Open-ILS/webby-src/src/app/core/org.service.ts @@ -1,6 +1,7 @@ import {Injectable} from '@angular/core'; import {Observable} from 'rxjs/Rx'; import {EgIdlObject, EgIdlService} from './idl.service'; +import {EgPcrudService} from './pcrud.service'; type EgOrgNodeOrId = number | EgIdlObject; @@ -11,6 +12,10 @@ export class EgOrgService { private orgList: EgIdlObject[] = []; private orgTree: EgIdlObject; // root node + children + constructor( + private egPcrud: EgPcrudService + ) {} + get(nodeOrId: EgOrgNodeOrId): EgIdlObject { if (typeof nodeOrId == 'object') return nodeOrId; @@ -82,6 +87,46 @@ export class EgOrgService { return list; } + sortTree(sortField?: string, node?: EgIdlObject): void { + if (!sortField) sortField = 'shortname'; + if (!node) node = this.orgTree; + node.children( + node.children.sort((a, b) => { + return a[sortField]() < b[sortField]() ? -1 : 1 + }) + ); + node.children.forEach(n => this.sortTree(n)); + } + + absorbTree(node?: EgIdlObject): void { + if (!node) { + node = this.orgTree; + this.orgMap = {}; + this.orgList = []; + } + this.orgMap[node.id()] = node; + this.orgList.push(node); + node.children().forEach(c => this.absorbTree(c)); + } + + /** + * Grabs all of the org units from the server, chops them up into + * various shapes, then returns an "all done" promise. + */ + fetchOrgs(): Promise { + + console.log('fetching..'); + return this.egPcrud.search('aou', {parent_ou : null}, + {flesh : -1, flesh_fields : {aou : ['children', 'ou_type']}}, + {anonymous : true} + ).toPromise().then(tree => { + // ingest tree, etc. + this.orgTree = tree; + this.absorbTree(); + console.log('TREE FETCHED: ' + tree); + }); + } + // NOTE: see ./org-settings.service for settings // TODO: ^-- } diff --git a/Open-ILS/webby-src/src/app/core/pcrud.service.ts b/Open-ILS/webby-src/src/app/core/pcrud.service.ts new file mode 100644 index 0000000000..efe1d3e796 --- /dev/null +++ b/Open-ILS/webby-src/src/app/core/pcrud.service.ts @@ -0,0 +1,311 @@ +import {Injectable} from '@angular/core'; +import {Observable, Observer} from 'rxjs/Rx'; +//import {toPromise} from 'rxjs/operators'; +import {EgIdlService, EgIdlObject} from './idl.service'; +import {EgNetService, EgNetRequest} from './net.service'; +import {EgAuthService} from './auth.service'; + +// Used for debugging. +declare var js2JSON: (jsThing:any) => string; +declare var OpenSRF: any; // creating sessions + +export interface EgPcrudReqOps { + authoritative?: boolean; + anonymous?: boolean; + idlist?: boolean; + atomic?: boolean; +} + +// For for documentation purposes. +type EgPcrudResponse = any; + +export class EgPcrudContext { + + static verboseLogging: boolean = true; // + static identGenerator: number = 0; // for debug logging + + private ident: number; + private authoritative: boolean; + private xactCloseMode: string; + private cudIdx: number; + private cudAction: string; + private cudLast: EgPcrudResponse; + private cudList: EgIdlObject[]; + + private egIdl: EgIdlService; + private egNet: EgNetService; + private egAuth: EgAuthService; + + // Tracks nested CUD actions + cudObserver: Observer; + + session: any; // OpenSRF.ClientSession + + constructor( // passed in by parent service -- not injected + egIdl: EgIdlService, + egNet: EgNetService, + egAuth: EgAuthService + ) { + this.egIdl = egIdl; + this.egNet = egNet; + this.egAuth = egAuth; + this.xactCloseMode = 'rollback'; + this.ident = EgPcrudContext.identGenerator++; + this.session = new OpenSRF.ClientSession('open-ils.pcrud'); + } + + toString(): string { + return '[PCRUDContext ' + this.ident + ']'; + } + + log(msg: string): void { + if (EgPcrudContext.verboseLogging) + console.debug(this + ': ' + msg); + } + + err(msg: string): void { + console.error(this + ': ' + msg); + } + + token(reqOps?: EgPcrudReqOps): string { + return (reqOps && reqOps.anonymous) ? + 'ANONYMOUS' : this.egAuth.token(); + } + + connect(): Promise { + this.log('connect'); + return new Promise( (resolve, reject) => { + this.session.connect({ + onconnect : () => { resolve(this); } + }); + }) + } + + disconnect(): void { + this.log('disconnect'); + this.session.disconnect(); + } + + retrieve(fmClass: string, pkey: Number | string, + pcrudOps?: any, reqOps?: EgPcrudReqOps): Observable { + if (!reqOps) reqOps = {}; + this.authoritative = reqOps.authoritative || false; + return this.dispatch( + `open-ils.pcrud.retrieve.${fmClass}`, + [this.token(reqOps), pkey, pcrudOps]); + } + + retrieveAll(fmClass: string, pcrudOps?: any, + reqOps?: EgPcrudReqOps): Observable { + let search = {}; + search[this.egIdl.classes[fmClass].pkey] = {'!=' : null}; + return this.search(fmClass, search, pcrudOps, reqOps); + } + + search(fmClass: string, search: any, + pcrudOps?: any, reqOps?: EgPcrudReqOps): Observable { + reqOps = reqOps || {}; + this.authoritative = reqOps.authoritative || false; + + let returnType = reqOps.idlist ? 'id_list' : 'search'; + let method = `open-ils.pcrud.${returnType}.${fmClass}`; + + if (reqOps.atomic) method += '.atomic'; + + return this.dispatch(method, [this.token(reqOps), search, pcrudOps]); + } + + create(list: EgIdlObject[]): Observable { + return this.cud('create', list) + } + update(list: EgIdlObject[]): Observable { + return this.cud('update', list) + } + remove(list: EgIdlObject[]): Observable { + return this.cud('delete', list) + } + autoApply(list: EgIdlObject[]): Observable { // RENAMED + return this.cud('auto', list) + } + + xactClose(): Observable { + return this.sendRequest( + 'open-ils.pcrud.transaction.' + this.xactCloseMode, + [this.token()] + ); + }; + + xactBegin(): Observable { + return this.sendRequest( + 'open-ils.pcrud.transaction.begin', [this.token()] + ); + }; + + private dispatch(method: string, params: any[]): Observable { + if (this.authoritative) { + return this.wrapXact(() => { + return this.sendRequest(method, params); + }); + } else { + return this.sendRequest(method, params) + } + }; + + + // => connect + // => xact_begin + // => action + // => xact_close(commit/rollback) + // => disconnect + wrapXact(mainFunc: () => Observable): Observable { + let this_ = this; + + return Observable.create(observer => { + + // 1. connect + this.connect() + + // 2. start the transaction + .then(() => {return this_.xactBegin().toPromise()}) + + // 3. execute the main body + .then(() => { + + mainFunc().subscribe( + res => observer.next(res), + err => observer.error(err), + () => { + this_.xactClose().toPromise().then(() => { + // 5. disconnect + this_.disconnect(); + // 6. all done + observer.complete(); + }); + } + ); + }) + }); + }; + + private sendRequest(method: string, + params: any[]): Observable { + + this.log(`sendRequest(${method})`); + + return this.egNet.requestCompiled( + new EgNetRequest( + 'open-ils.pcrud', method, params, this.session) + ); + } + + private cud(action: string, + list: EgIdlObject | EgIdlObject[]): Observable { + + this.log(`CUD(): ${action}`); + + this.cudIdx = 0; + this.cudAction = action; + this.xactCloseMode = 'commit'; + + if (!Array.isArray(list)) this.cudList = [list]; + + let this_ = this; + + return this.wrapXact(() => { + return Observable.create(observer => { + this_.cudObserver = observer; + this_.nextCudRequest(); + }); + }); + } + + /** + * Loops through the list of objects to update and sends + * them one at a time to the server for processing. Once + * all are done, the cudObserver is resolved. + */ + nextCudRequest(): void { + let this_ = this; + + if (this.cudIdx >= this.cudList.length) { + this.cudObserver.complete(); + return; + } + + let action = this.cudAction; + let fmObj = this.cudList[this.cudIdx++]; + + if (action == 'auto') { + if (fmObj.ischanged()) action = 'update'; + if (fmObj.isnew()) action = 'create'; + if (fmObj.isdeleted()) action = 'delete'; + + if (action == 'auto') { + // object does not need updating; move along + this.nextCudRequest(); + } + } + + this.sendRequest( + `open-ils.pcrud.${action}.${fmObj.classname}`, + [this.token(), fmObj] + ).subscribe( + res => this_.cudObserver.next(res), + err => this_.cudObserver.error(err), + () => this_.nextCudRequest() + ); + }; +} + +@Injectable() +export class EgPcrudService { + + constructor( + private egIdl: EgIdlService, + private egNet: EgNetService, + private egAuth: EgAuthService + ) {} + + // Pass-thru functions for one-off PCRUD calls + + connect(): Promise { + return this.newContext().connect(); + } + + newContext(): EgPcrudContext { + return new EgPcrudContext(this.egIdl, this.egNet, this.egAuth); + } + + retrieve(fmClass: string, pkey: Number | string, + pcrudOps?: any, reqOps?: EgPcrudReqOps): Observable { + return this.newContext().retrieve(fmClass, pkey, pcrudOps, reqOps); + } + + retrieveAll(fmClass: string, pcrudOps?: any, + reqOps?: EgPcrudReqOps): Observable { + return this.newContext().retrieveAll(fmClass, pcrudOps, reqOps); + } + + search(fmClass: string, search: any, + pcrudOps?: any, reqOps?: EgPcrudReqOps): Observable { + return this.newContext().search(fmClass, search, pcrudOps, reqOps); + } + + create(list: EgIdlObject[]): Observable { + return this.newContext().create(list); + } + + update(list: EgIdlObject[]): Observable { + return this.newContext().update(list); + } + + remove(list: EgIdlObject[]): Observable { + return this.newContext().remove(list); + } + + autoApply(list: EgIdlObject[]): Observable { + return this.newContext().autoApply(list); + } +} + + diff --git a/Open-ILS/webby-src/src/app/resolver.service.ts b/Open-ILS/webby-src/src/app/resolver.service.ts index 9f41183490..984e888ed1 100644 --- a/Open-ILS/webby-src/src/app/resolver.service.ts +++ b/Open-ILS/webby-src/src/app/resolver.service.ts @@ -1,12 +1,17 @@ -import { Injectable } from '@angular/core'; -import { Router, Resolve, RouterStateSnapshot, - ActivatedRouteSnapshot } from '@angular/router'; -import { EgIdlService } from '@eg/core/idl.service'; +import {Injectable} from '@angular/core'; +import {Router, Resolve, RouterStateSnapshot, + ActivatedRouteSnapshot} from '@angular/router'; +import {EgIdlService} from '@eg/core/idl.service'; +import {EgOrgService} from '@eg/core/org.service'; @Injectable() export class EgBaseResolver implements Resolve> { - constructor(private router: Router, private egIdl: EgIdlService) {} + constructor( + private router: Router, + private egIdl: EgIdlService, + private egOrg: EgOrgService, + ) {} resolve( route: ActivatedRouteSnapshot, @@ -14,8 +19,10 @@ export class EgBaseResolver implements Resolve> { console.debug('EgBaseResolver:resolve()'); + // Safe to assume some data is needed by all applications. this.egIdl.parseIdl(); - return Promise.resolve(); + return this.egOrg.fetchOrgs(); + // load other common stuff... } }