LP#626157 Ang2 experiments
authorBill Erickson <berickxx@gmail.com>
Wed, 29 Nov 2017 22:26:37 +0000 (17:26 -0500)
committerBill Erickson <berickxx@gmail.com>
Mon, 11 Dec 2017 17:39:51 +0000 (12:39 -0500)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/webby-src/src/app/base.module.ts
Open-ILS/webby-src/src/app/core/auth.service.ts
Open-ILS/webby-src/src/app/core/net.service.ts
Open-ILS/webby-src/src/app/core/org.service.ts
Open-ILS/webby-src/src/app/core/pcrud.service.ts [new file with mode: 0644]
Open-ILS/webby-src/src/app/resolver.service.ts

index 0a5ff2b..0bd2a07 100644 (file)
@@ -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: [
   ],
index 3e78d87..4a956a5 100644 (file)
@@ -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;
     };
 
index c6082a7..b7b3648 100644 (file)
@@ -29,16 +29,23 @@ export class EgNetRequest {
     params     : any[];
     observer   : Observer<any>;
     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<EgNetRequest>();
     }
 
-    // Variadic params version
+    // Standard request call -- Variadic params version
     request(service: String, method: String, ...params: any[]): Observable<any> {
         return this.requestWithParamList(service, method, params);
     }
@@ -67,22 +74,25 @@ export class EgNetService {
     // Array params version
     requestWithParamList(service: String, 
         method: String, params: any[]): Observable<any> {
+        return this.requestCompiled(
+            new EgNetRequest(service, method, params));
+    }
 
-        var request = new EgNetRequest(service, method, params);
-
+    requestCompiled(request: EgNetRequest): Observable<any> {
         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);
             }
 
index 44eea6a..d641555 100644 (file)
@@ -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<void> {
+        
+        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 (file)
index 0000000..efe1d3e
--- /dev/null
@@ -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<EgPcrudResponse>;
+
+    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<EgPcrudContext> {
+        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<EgPcrudResponse> {
+        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<EgPcrudResponse> {
+        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<EgPcrudResponse> {
+        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<EgPcrudResponse> {
+        return this.cud('create', list)
+    }
+    update(list: EgIdlObject[]): Observable<EgPcrudResponse> {
+        return this.cud('update', list)
+    }
+    remove(list: EgIdlObject[]): Observable<EgPcrudResponse> {
+        return this.cud('delete', list)
+    }
+    autoApply(list: EgIdlObject[]): Observable<EgPcrudResponse> { // RENAMED
+        return this.cud('auto',   list)
+    }
+
+    xactClose(): Observable<EgPcrudResponse> {
+        return this.sendRequest(
+            'open-ils.pcrud.transaction.' + this.xactCloseMode,
+            [this.token()]
+        );
+    };
+
+    xactBegin(): Observable<EgPcrudResponse> {
+        return this.sendRequest(
+            'open-ils.pcrud.transaction.begin', [this.token()]
+        );
+    };
+
+    private dispatch(method: string, params: any[]): Observable<EgPcrudResponse> {
+        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<EgPcrudResponse>): Observable<EgPcrudResponse> {
+        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<EgPcrudResponse> {
+
+        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<EgPcrudResponse> {
+
+        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<EgPcrudContext> {
+        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<EgPcrudResponse> {
+        return this.newContext().retrieve(fmClass, pkey, pcrudOps, reqOps);
+    }
+
+    retrieveAll(fmClass: string, pcrudOps?: any, 
+        reqOps?: EgPcrudReqOps): Observable<EgPcrudResponse> {
+        return this.newContext().retrieveAll(fmClass, pcrudOps, reqOps);
+    }
+
+    search(fmClass: string, search: any, 
+        pcrudOps?: any, reqOps?: EgPcrudReqOps): Observable<EgPcrudResponse> {
+        return this.newContext().search(fmClass, search, pcrudOps, reqOps);
+    }
+
+    create(list: EgIdlObject[]): Observable<EgPcrudResponse> {
+        return this.newContext().create(list);
+    }
+
+    update(list: EgIdlObject[]): Observable<EgPcrudResponse> {
+        return this.newContext().update(list);
+    }
+
+    remove(list: EgIdlObject[]): Observable<EgPcrudResponse> {
+        return this.newContext().remove(list);
+    }
+
+    autoApply(list: EgIdlObject[]): Observable<EgPcrudResponse> { 
+        return this.newContext().autoApply(list);
+    }
+}
+
+
index 9f41183..984e888 100644 (file)
@@ -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<Promise<void>> {
 
-    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<Promise<void>> {
 
         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...
     }
 }