LP#1779158 Angular vandelay starting import form
authorBill Erickson <berickxx@gmail.com>
Fri, 29 Jun 2018 22:30:15 +0000 (18:30 -0400)
committerBill Erickson <berickxx@gmail.com>
Thu, 11 Oct 2018 18:56:30 +0000 (14:56 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/src/eg2/src/app/staff/cat/vandelay/export.component.html
Open-ILS/src/eg2/src/app/staff/cat/vandelay/import.component.html
Open-ILS/src/eg2/src/app/staff/cat/vandelay/import.component.ts
Open-ILS/src/eg2/src/app/staff/cat/vandelay/routing.module.ts
Open-ILS/src/eg2/src/app/staff/cat/vandelay/vandelay.component.html
Open-ILS/src/eg2/src/app/staff/cat/vandelay/vandelay.component.ts
Open-ILS/src/eg2/src/app/staff/cat/vandelay/vandelay.service.ts

index 83bbc2d..e8b65e4 100644 (file)
@@ -1,2 +1,36 @@
 
-IMPORT COMPONENT
+<eg-progress-dialog #progressDialog></eg-progress-dialog>
+
+<div class="pl-4 pr-4">
+  <h2 i18n>MARC File Upload</h2>
+  <div class="row pb-2">
+    <div class="col-lg-3">
+      <label class="font-weight-bold" i18n>Record Type</label>
+    </div>
+    <div class="col-lg-3">
+      <select class="form-control" [(ngModel)]="recordType" 
+        (change)="recordTypeChanged($event)">
+        <option value='bib' i18n>Bibliographic Records</option>
+        <option value='auth' i18n>Authority Records</option> 
+        <option value='bib-acq' i18n>Acquisitions Records</option>
+      </select>
+    </div>
+  </div>
+  <div class="row">
+    <div class="col-lg-3">
+      <label class="font-weight-bold" i18n>Create a New Upload Queue</label>
+    </div>
+    <div class="col-lg-3">
+      <input type="text" class="form-control" [(ngModel)]="newQueueName"/>
+    </div>
+    <div class="col-lg-3">
+      <label class="font-weight-bold" i18n>or Add to an Existing Queue</label>
+    </div>
+    <div class="col-lg-3">
+      <eg-typeahead [entries]="queueEntries"></eg-typeahead>
+    </div>
+  </div>
+</div>
+
+
+
index 40c65a5..7ae7ee3 100644 (file)
@@ -1,14 +1,88 @@
-import {Component, OnInit} from '@angular/core';
+import {Component, OnInit, AfterViewInit, Input, ViewChild} from '@angular/core';
+import {IdlObject} from '@eg/core/idl.service';
+import {OrgService} from '@eg/core/org.service';
+import {TypeaheadEntry} from '@eg/share/typeahead/typeahead.component';
+import {VandelayService} from './vandelay.service';
+import {ProgressDialogComponent} from '@eg/share/dialog/progress.component';
 
 @Component({
   templateUrl: 'import.component.html'
 })
-export class ImportComponent implements OnInit {
+export class ImportComponent implements OnInit, AfterViewInit {
 
-    constructor() {}
+    recordType: string;
+    newQueueName: string;
+    queueEntries: TypeaheadEntry[];
+    allQueues: {[qtype: string]: IdlObject[]};
+    activeQueues: {[qtype: string]: IdlObject[]}; 
+    attrDefs: {[atype: string]: IdlObject[]};
+    bibSources: IdlObject[];
+    bibBuckets: IdlObject[];
+    matchSets: {[stype: string]: IdlObject[]};
+    defaultMatchSet: string;
 
-    ngOnInit() {
+    @ViewChild('progressDialog')
+        private progressDialog: ProgressDialogComponent;
+
+    constructor(
+        private org: OrgService,
+        private vandelay: VandelayService
+    ) {
+        this.recordType = 'bib';
+        this.queueEntries = [];
+        this.attrDefs = {};
+        this.matchSets = {};
+        this.activeQueues = {};
+    }
+
+    ngOnInit() {}
+
+    ngAfterViewInit() {
+        // loading startup data changes our data in the midst of a
+        // lifecycle hook.  Run after timeout.
+        setTimeout(() => this.loadStartupData());
+    }
+
+    // Display record-typ appropriate data when the record type changes
+    recordTypeChanged($event) {
+        //this.formatQueueSelector();
     }
 
+    loadStartupData() {
+        this.progressDialog.open();
+
+        const promises = [
+            this.vandelay.getAttrDefs('bib')
+                .then(defs => this.attrDefs.bib = defs),
+            this.vandelay.getAttrDefs('auth')
+                .then(defs => this.attrDefs.auth = defs),
+            this.vandelay.getActiveQueues('bib')
+                .then(queues => {
+                    this.activeQueues.bib = queues;
+                    this.formatQueueSelector();
+                }),
+            this.vandelay.getActiveQueues('auth')
+                .then(queues => this.activeQueues.auth = queues),
+            this.vandelay.getMatchSets()
+                .then(sets => this.matchSets = sets),
+            this.vandelay.getBibBuckets()
+                .then(bkts => this.bibBuckets = bkts),
+            this.vandelay.getBibSources()
+                .then(srcs => this.bibSources = srcs),
+            this.org.settings(['vandelay.default_match_set']).then(
+                s => this.defaultMatchSet = s['vandelay.default_match_set'])
+        ];
+
+        this.progressDialog.update({value: 0, max: promises.length});
+        promises.forEach(p => p.then(() => this.progressDialog.increment()));
+        Promise.all(promises).then(() => this.progressDialog.close());
+    }
+
+    formatQueueSelector() {
+        const queues = this.activeQueues[this.recordType];
+        this.queueEntries = queues.map(q => {
+            return {id: q.id(), label: q.name()};
+        });
+    }
 }
 
index 232f51f..c37e172 100644 (file)
@@ -7,7 +7,6 @@ import {ExportComponent} from './export.component';
 const routes: Routes = [{
   path: '',
   component: VandelayComponent,
-  //resolve: {vandelayResolver : VandelayResolver},
   children: [{
     path: '',
     pathMatch: 'full',
index 07db35f..e399b43 100644 (file)
@@ -1,3 +1,5 @@
+<eg-staff-banner bannerText="MARC Record Import/Export" i18n-bannerText>
+</eg-staff-banner>
 
 <ul class="nav nav-pills nav-fill p-3">
   <li class="nav-item">
@@ -34,6 +36,5 @@
   </li>
 </ul>
 
-<eg-progress-dialog #progressDialog></eg-progress-dialog>
-
 <router-outlet></router-outlet>
+
index 9239f11..0bfad42 100644 (file)
@@ -1,7 +1,6 @@
 import {Component, OnInit, AfterViewInit, ViewChild} from '@angular/core';
 import {Router, ActivatedRoute, NavigationEnd} from "@angular/router";
 import {take} from 'rxjs/operators/take';
-import {ProgressDialogComponent} from '@eg/share/dialog/progress.component';
 import {VandelayService} from './vandelay.service';
 import {IdlObject} from '@eg/core/idl.service';
 
@@ -9,22 +8,8 @@ import {IdlObject} from '@eg/core/idl.service';
   templateUrl: 'vandelay.component.html'
 })
 export class VandelayComponent implements OnInit, AfterViewInit {
-
     tab: string;
 
-    attrDefs = {bib: [], auth: []};
-    allQueues = {bib: [], auth: []};
-    activeQueues = {bib: [], auth: []}; // complete === 'f'
-    bibSources: IdlObject[];
-    bibBuckets: IdlObject[];
-    bibTrashGroups: IdlObject[];
-    copyStatuses: IdlObject[];
-    importItemDefs: IdlObject[];
-    matchSets: {[stype: string]: IdlObject[]};
-
-    @ViewChild('progressDialog')
-        private progressDialog: ProgressDialogComponent;
-
     constructor(
         private router: Router,
         private route: ActivatedRoute,
@@ -40,68 +25,10 @@ export class VandelayComponent implements OnInit, AfterViewInit {
                 .subscribe(segments => this.tab = segments[0].path);
             }
         });
-
-        this.attachDataListeners();
-    }
-
-    ngOnInit() {
-    }
-
-    // Manage the shared data here so it persists across tabs.
-    // Load the shared data after view init so the page can initialize
-    // first and display the loading message.
-    ngAfterViewInit() {
-
-        // loading startup data causes data in this component to change
-        // inside its own lifecycle hook, which is a no-no.  Run after
-        // the hook.
-        setTimeout(() => {
-
-            this.vandelay.loadStartupData(this.progressDialog).then(ok => {
-
-                // Pull data that does not change during a vandelay 
-                // UI directly from the service.
-                this.bibSources = this.vandelay.bibSources;
-                this.bibBuckets = this.vandelay.bibBuckets;
-                this.bibTrashGroups = this.vandelay.bibTrashGroups;
-                this.copyStatuses = this.vandelay.copyStatuses;
-            });
-        });
     }
 
+    ngOnInit() {}
 
-    // Listen for global data changes, store data locally so it 
-    // may be propagated to child components.
-    attachDataListeners() {
-
-        this.vandelay.onAttrDefsUpdate$.subscribe(collection => {
-            const dtype = collection.dtype;
-            this.attrDefs[dtype] = collection.defs;
-        });
-
-        // Bib/auth queue list
-        this.vandelay.onQueueListUpdate$.subscribe(collection => {
-            const qtype = collection.qtype;
-            collection.queues.forEach(q => {
-                this.allQueues[qtype] = q;
-                if (q.complete() === 'f') {
-                    this.activeQueues[qtype] = q;
-                }
-            });
-        });
-
-        this.vandelay.onItemImportDefsUpdate$.subscribe(
-            defs => this.importItemDefs = defs
-        );
-
-        this.vandelay.onMatchSetsUpdate$.subscribe(sets => {
-            sets.forEach(s => {
-                if (!this.matchSets[s.mtype()]) {
-                    this.matchSets[s.mtype()] = [];
-                }
-                this.matchSets[s.mtype()].push(s);
-            });
-        });
-    }
+    ngAfterViewInit() {}
 }
 
index cf8e7fb..4413326 100644 (file)
@@ -13,21 +13,15 @@ import {ProgressDialogComponent} from '@eg/share/dialog/progress.component';
 @Injectable()
 export class VandelayService {
 
-    // Data that may change within the vandelay UI is propagated
-    // via EventEmitters
-    onQueueListUpdate$: EventEmitter<any>;
-    onItemImportDefsUpdate$: EventEmitter<IdlObject[]>;
-    onMatchSetsUpdate$: EventEmitter<IdlObject[]>;
-    onAttrDefsUpdate$: EventEmitter<any>;
-
-    // Data that persists during a vandelay session needs no emitters.
+    allQueues: {[qtype: string]: IdlObject[]};
+    activeQueues: {[qtype: string]: IdlObject[]}; 
+    attrDefs: {[atype: string]: IdlObject[]};
     bibSources: IdlObject[];
     bibBuckets: IdlObject[];
-    bibTrashGroups: IdlObject[];
     copyStatuses: IdlObject[];
-
-    // Settings to retrieve (and cache) at load time.
-    orgSettings = ['vandelay.default_match_set'];
+    matchSets: {[stype: string]: IdlObject[]};
+    importItemAttrDefs: IdlObject[];
+    bibTrashGroups: IdlObject[];
 
     constructor(
         private idl: IdlService,
@@ -38,141 +32,126 @@ export class VandelayService {
         private pcrud: PcrudService,
         private perm: PermService
     ) {
-        this.onQueueListUpdate$ = new EventEmitter<any>();
-        this.onItemImportDefsUpdate$ = new EventEmitter<IdlObject[]>();
-        this.onMatchSetsUpdate$ = new EventEmitter<IdlObject[]>();
-        this.onAttrDefsUpdate$ = new EventEmitter<any>();
-    }
-
-    loadStartupData(dialog: ProgressDialogComponent): Promise<any> {
-
-        dialog.open();
-
-        const promises = [
-            this.getAttrDefs('bib').toPromise(),
-            this.getAttrDefs('auth').toPromise(),
-            this.getQueueList('bib').toPromise(),
-            this.getQueueList('auth').toPromise(),
-            this.getItemImportDefs().toPromise(),
-            this.getMatchSets().toPromise(),
-            this.getBibBuckets(),
-            this.getBibSources(),
-            this.getBibTrashGroups(),
-            this.getCopyStatuses(),
-            this.org.settings(this.orgSettings)
-        ];
-
-        dialog.update({value: 0, max: promises.length});
-        promises.forEach(p => p.then(_ => dialog.increment()));
-
-        return Promise.all(promises).then(ok => dialog.close());
+        this.attrDefs = {};
+        this.activeQueues = {};
+        this.allQueues = {};
+        this.matchSets = {};
     }
 
-    getAttrDefs(dtype: string): Observable<IdlObject> {
+    getAttrDefs(dtype: string): Promise<IdlObject[]> {
+        if (this.attrDefs[dtype]) {
+            return Promise.resolve(this.attrDefs[dtype]);
+        }
         const cls = (dtype === 'bib') ? 'vqbrad' : 'vqarad';
-        const defs = [];
-
         const orderBy = {};
         orderBy[cls] = 'id'
-
-        return this.pcrud.retrieveAll(cls, {order_by: orderBy})
-        .pipe(tap(
-            def => defs.push(def),
-            err => {},
-            ()  => {
-                console.debug(`Fetched ${defs.length} ${dtype} attr def(s)`);
-                this.onAttrDefsUpdate$.emit({dtype: dtype, defs: defs});
-            }
-        ));
+        return this.pcrud.retrieveAll(cls, 
+            {order_by: orderBy}, {atomic: true}).toPromise()
+        .then(list => {
+            this.attrDefs[dtype] = list;
+            return list;
+        });
     }
 
     // Returns a promise resolved with the list of queues.
     // Also emits the onQueueListUpdate event so listeners
     // can detect queue content changes.
-    getQueueList(qtype: string, filters?: any): Observable<IdlObject> {
-        const qt = (qtype === 'bib') ? qtype : 'authority';
-        const queues = [];
+    getActiveQueues(qtype: string): Promise<IdlObject[]> {
+        if (this.activeQueues[qtype]) {
+            return Promise.resolve(this.activeQueues[qtype]);
+        }
 
+        // could be a big list, invoke in streaming mode
+        const queues = [];
+        const qt = (qtype === 'bib') ? qtype : 'authority';
         return this.net.request(
             'open-ils.vandelay',
             `open-ils.vandelay.${qt}_queue.owner.retrieve`,
-            this.auth.token(), null, filters
+            this.auth.token(), null, {complete: 'f'}
         ).pipe(tap(
             queue => {
-                const evt = this.evt.parse(queue);
-                if (evt) { return alert(evt); } // should not happen
-                queues.push(queue);
-            },
-            err => {console.error(err)},
-            ()  => {
-                console.debug(`Fetched ${queues.length} "${qtype}" queue(s)`);
-                this.onQueueListUpdate$.emit({qtype: qtype, queues: queues});
+                if (!this.activeQueues[qtype]) {
+                    this.activeQueues[qtype] = [];
+                }
+                this.activeQueues[qtype].push(queue);
             }
-        ));
+        )).toPromise().then(() => queues);
     }
 
-    getBibSources(): Promise<any> {
+    getBibSources(): Promise<IdlObject[]> {
+        if (this.bibSources) {
+            return Promise.resolve(this.bibSources);
+        }
+
         return this.pcrud.retrieveAll('cbs', 
           {order_by: {cbs: 'id'}}, 
           {atomic: true}
         ).toPromise().then(sources => {
-            console.debug(`Fetched ${sources.length} bib source(s)`);
             this.bibSources = sources;
+            return sources;
         });
     }
 
-    getItemImportDefs(): Observable<IdlObject> {
-        const defs = [];
-        const owners = this.org.ancestors(this.auth.user().ws_ou(), true);
+    getItemImportDefs(): Promise<IdlObject[]> {
+        if (this.importItemAttrDefs) {
+            return Promise.resolve(this.importItemAttrDefs);
+        }
 
-        return this.pcrud.search('viiad', {owner: owners})
-        .pipe(tap(
-            def => defs.push(def),
-            err => {},
-            ()  => {
-                console.debug(`Fetched ${defs.length} import item def(s)`);
-                this.onItemImportDefsUpdate$.emit(defs);
-            }
-        ));
+        const owners = this.org.ancestors(this.auth.user().ws_ou(), true);
+        return this.pcrud.search('viiad', {owner: owners}, {atomic: true})
+        .toPromise().then(defs => {
+            this.importItemAttrDefs = defs;
+            return defs;
+        });
     }
 
-    getMatchSets(): Observable<IdlObject[]> {
-        const sets = [];
+    getMatchSets(): Promise<{[mtype:string]: IdlObject[]}> {
+        if (this.matchSets) {
+            return Promise.resolve(this.matchSets);
+        }
+
         const owners = this.org.ancestors(this.auth.user().ws_ou(), true);
         return this.pcrud.search('vms', {owner: owners})
-        .pipe(tap(
-            s   => sets.push(s),
-            err => {},
-            ()  => {
-                console.debug(`Fetched ${sets.length} match set(s)`);
-                this.onMatchSetsUpdate$.emit(sets);
-            }
-        ));
+        .toPromise().then(sets => {
+            sets.forEach(s => {
+                if (!this.matchSets[s.mtype()]) {
+                    this.matchSets[s.mtype()] = [];
+                }
+                this.matchSets[s.mtype()].push(s);
+            });
+            return this.matchSets;
+        });
     }
 
-    getBibBuckets(): Promise<any> {
-        this.bibBuckets = [];
+    getBibBuckets(): Promise<IdlObject[]> {
+        if (this.bibBuckets) {
+            return Promise.resolve(this.bibBuckets);
+        }
 
+        const bkts = [];
         return this.net.request(
             'open-ils.actor', 
             'open-ils.actor.container.retrieve_by_class',
             this.auth.token(), this.auth.user().id(), 'biblio', 'staff_client'
-        ).pipe(tap(
-            bkt => this.bibBuckets.push(bkt),
-            err => {},
-            ()  => console.debug(`Fetched ${this.bibBuckets.length} bucket(s)`)
-        )).toPromise();
+        ).pipe(tap(bkt => bkts.push(bkt))).toPromise().then(() => bkts);
     }
 
     getCopyStatuses(): Promise<any> {
+        if (this.copyStatuses) {
+            return Promise.resolve(this.copyStatuses);
+        }
         return this.pcrud.retrieveAll('ccs', {}, {atomic: true})
         .toPromise().then(stats => {
-            console.debug(`Fetched ${stats.length} copy status(es)`);
             this.copyStatuses = stats;
+            return stats;
         });
     }
 
     getBibTrashGroups(): Promise<any> {
+        if (this.bibTrashGroups) {
+            return Promise.resolve(this.bibTrashGroups);
+        }
+
         const owners = this.org.ancestors(this.auth.user().ws_ou(), true);
 
         return this.pcrud.search('vibtg', 
@@ -180,11 +159,10 @@ export class VandelayService {
             {vibtg : ['label']},
             {atomic: true}
         ).toPromise().then(groups => {
-            console.debug(`Fetched ${groups.length} bib trash field group(s)`);
             this.bibTrashGroups = groups;
+            return groups;
         });
     }
 
-
 }