LP1779158 Cache new queues / display active queues
[Evergreen.git] / Open-ILS / src / eg2 / src / app / staff / cat / vandelay / vandelay.service.ts
1 import {Injectable} from '@angular/core';
2 import {Observable} from 'rxjs/Observable';
3 import {tap} from 'rxjs/operators/tap';
4 import {map} from 'rxjs/operators/map';
5 import {HttpClient} from '@angular/common/http';
6 import {saveAs} from 'file-saver/FileSaver';
7 import {IdlService, IdlObject} from '@eg/core/idl.service';
8 import {OrgService} from '@eg/core/org.service';
9 import {NetService} from '@eg/core/net.service';
10 import {AuthService} from '@eg/core/auth.service';
11 import {PcrudService} from '@eg/core/pcrud.service';
12 import {PermService} from '@eg/core/perm.service';
13 import {EventService} from '@eg/core/event.service';
14 import {ProgressDialogComponent} from '@eg/share/dialog/progress.component';
15
16 export const VANDELAY_EXPORT_PATH = '/exporter';
17 export const VANDELAY_UPLOAD_PATH = '/vandelay-upload';
18
19 export class VandelayImportSelection {
20     recordIds: number[];
21     queue: IdlObject;
22     importQueue: boolean; // import the whole queue
23     overlayMap: {[qrId: number]: /* breId */ number};
24
25     constructor() {
26        this.recordIds = [];
27        this.overlayMap = {};
28     }
29 }
30
31 @Injectable()
32 export class VandelayService {
33
34     allQueues: {[qtype: string]: IdlObject[]};
35     attrDefs: {[atype: string]: IdlObject[]};
36     bibSources: IdlObject[];
37     bibBuckets: IdlObject[];
38     copyStatuses: IdlObject[];
39     matchSets: {[stype: string]: IdlObject[]};
40     importItemAttrDefs: IdlObject[];
41     bibTrashGroups: IdlObject[];
42     mergeProfiles: IdlObject[];
43
44     // Used for tracking records between the queue page and
45     // the import page.  Fields managed externally.
46     importSelection: VandelayImportSelection;
47
48     // Track the last grid offset in the queue page so we
49     // can return the user to the same page of data after
50     // going to the matches page.
51     queuePageOffset: number;
52
53     constructor(
54         private http: HttpClient,
55         private idl: IdlService,
56         private org: OrgService,
57         private evt: EventService,
58         private net: NetService,
59         private auth: AuthService,
60         private pcrud: PcrudService,
61         private perm: PermService
62     ) {
63         this.attrDefs = {};
64         this.allQueues = {};
65         this.matchSets = {};
66         this.importSelection = null;
67         this.queuePageOffset = 0;
68     }
69
70     getAttrDefs(dtype: string): Promise<IdlObject[]> {
71         if (this.attrDefs[dtype]) {
72             return Promise.resolve(this.attrDefs[dtype]);
73         }
74         const cls = (dtype === 'bib') ? 'vqbrad' : 'vqarad';
75         const orderBy = {};
76         orderBy[cls] = 'id'
77         return this.pcrud.retrieveAll(cls, 
78             {order_by: orderBy}, {atomic: true}).toPromise()
79         .then(list => {
80             this.attrDefs[dtype] = list;
81             return list;
82         });
83     }
84
85     getMergeProfiles(): Promise<IdlObject[]> {
86         if (this.mergeProfiles) {
87             return Promise.resolve(this.mergeProfiles);
88         }
89
90         const owners = this.org.ancestors(this.auth.user().ws_ou(), true);
91         return this.pcrud.search('vmp', 
92             {owner: owners}, {order_by: {vmp: ['name']}}, {atomic: true})
93         .toPromise().then(profiles => {
94             this.mergeProfiles = profiles;
95             return profiles;
96         });
97     }
98
99     // Returns a promise resolved with the list of queues.
100     getAllQueues(qtype: string): Promise<IdlObject[]> {
101         if (this.allQueues[qtype]) {
102             return Promise.resolve(this.allQueues[qtype]);
103         } else {
104             this.allQueues[qtype] = [];
105         }
106
107         // could be a big list, invoke in streaming mode
108         return this.net.request(
109             'open-ils.vandelay',
110             `open-ils.vandelay.${qtype}_queue.owner.retrieve`,
111             this.auth.token()
112         ).pipe(tap(
113             queue => this.allQueues[qtype].push(queue)
114         )).toPromise().then(() => this.allQueues[qtype]);
115     }
116
117     getBibSources(): Promise<IdlObject[]> {
118         if (this.bibSources) {
119             return Promise.resolve(this.bibSources);
120         }
121
122         return this.pcrud.retrieveAll('cbs', 
123           {order_by: {cbs: 'id'}}, 
124           {atomic: true}
125         ).toPromise().then(sources => {
126             this.bibSources = sources;
127             return sources;
128         });
129     }
130
131     getItemImportDefs(): Promise<IdlObject[]> {
132         if (this.importItemAttrDefs) {
133             return Promise.resolve(this.importItemAttrDefs);
134         }
135
136         const owners = this.org.ancestors(this.auth.user().ws_ou(), true);
137         return this.pcrud.search('viiad', {owner: owners}, {}, {atomic: true})
138         .toPromise().then(defs => {
139             this.importItemAttrDefs = defs;
140             return defs;
141         });
142     }
143
144     // todo: differentiate between biblio and authority a la queue api
145     getMatchSets(mtype: string): Promise<IdlObject[]> {
146     
147         const mstype = mtype.match(/bib/) ? 'biblio' : 'authority';
148
149         if (this.matchSets[mtype]) {
150             return Promise.resolve(this.matchSets[mtype]);
151         } else {
152             this.matchSets[mtype] = [];
153         }
154
155         const owners = this.org.ancestors(this.auth.user().ws_ou(), true);
156
157         return this.pcrud.search('vms', 
158             {owner: owners, mtype: mstype}, {}, {atomic: true})
159         .toPromise().then(sets => {
160             this.matchSets[mtype] = sets;
161             return sets;
162         });
163     }
164
165     getBibBuckets(): Promise<IdlObject[]> {
166         if (this.bibBuckets) {
167             return Promise.resolve(this.bibBuckets);
168         }
169
170         const bkts = [];
171         return this.net.request(
172             'open-ils.actor', 
173             'open-ils.actor.container.retrieve_by_class',
174             this.auth.token(), this.auth.user().id(), 'biblio', 'staff_client'
175         //).pipe(tap(bkt => bkts.push(bkt))).toPromise().then(() => bkts);
176         ).toPromise().then(bkts => {
177             this.bibBuckets = bkts;
178             return bkts;
179         });
180     }
181
182     getCopyStatuses(): Promise<any> {
183         if (this.copyStatuses) {
184             return Promise.resolve(this.copyStatuses);
185         }
186         return this.pcrud.retrieveAll('ccs', {}, {atomic: true})
187         .toPromise().then(stats => {
188             this.copyStatuses = stats;
189             return stats;
190         });
191     }
192
193     getBibTrashGroups(): Promise<any> {
194         if (this.bibTrashGroups) {
195             return Promise.resolve(this.bibTrashGroups);
196         }
197
198         const owners = this.org.ancestors(this.auth.user().ws_ou(), true);
199
200         return this.pcrud.search('vibtg', 
201             {always_apply : 'f', owner: owners}, 
202             {vibtg : ['label']},
203             {atomic: true}
204         ).toPromise().then(groups => {
205             this.bibTrashGroups = groups;
206             return groups;
207         });
208     }
209
210
211     // Create a queue and return the ID of the new queue via promise.
212     createQueue(
213         queueName: string, 
214         recordType: string, 
215         importDefId: number, 
216         matchSet: number, 
217         matchBucket: number): Promise<number> {
218
219         const method = `open-ils.vandelay.${recordType}_queue.create`;
220
221         let qType = recordType;
222         if (recordType.match(/bib_acq/)) {
223             let qType = 'acq';
224         }
225
226         return new Promise((resolve, reject) => {
227             this.net.request(
228                 'open-ils.vandelay', method, 
229                 this.auth.token(), queueName, null, qType, 
230                 matchSet, importDefId, matchBucket
231             ).subscribe(queue => {
232                 const e = this.evt.parse(queue);
233                 if (e) { 
234                     reject(e);
235                 } else {
236                     // createQueue is always called after queues have
237                     // been fetched and cached.  
238                     this.allQueues[qType].push(queue);
239                     resolve(queue.id());
240                 }
241             });
242         });
243     }
244
245     getQueuedRecords(queueId: number, queueType: string, 
246       options?: any, limitToMatches?: boolean): Observable<any> {
247
248         const qtype = queueType.match(/bib/) ? 'bib' : 'auth';
249
250         let method = 
251           `open-ils.vandelay.${qtype}_queue.records.retrieve`;
252
253         if (limitToMatches) {
254             method = 
255               `open-ils.vandelay.${qtype}_queue.records.matches.retrieve`;
256         }
257
258         return this.net.request('open-ils.vandelay', 
259             method, this.auth.token(), queueId, options);
260     }
261
262     // Download a queue as a MARC file.
263     exportQueue(queue: IdlObject, nonImported?: boolean) {
264
265         const etype = queue.queue_type().match(/auth/) ? 'auth' : 'bib';
266
267         let url = 
268           `${VANDELAY_EXPORT_PATH}?type=${etype}&queueid=${queue.id()}`
269
270         let saveName = queue.name();
271            
272         if (nonImported) {
273             url += '&nonimported=1';
274             saveName += '_nonimported';
275         }
276
277         saveName += '.mrc';
278
279         this.http.get(url, {responseType: 'text'}).subscribe(
280             data => {
281                 saveAs(
282                     new Blob([data], {type: 'application/octet-stream'}),
283                     saveName
284                 );
285             },
286             err  => {
287                 console.error(err);
288             }
289         );
290     }
291
292     // Poll every 2 seconds for session tracker updates so long 
293     // as the session tracker is active.
294     // Returns an Observable of tracker objects.
295     pollSessionTracker(id: number): Observable<IdlObject> {
296         return new Observable(observer => {
297             this.getNextSessionTracker(id, observer);
298         });
299     }
300
301     getNextSessionTracker(id: number, observer: any) {
302
303                 // No need for this to be an authoritative call.
304         // It will complete eventually regardless.
305         this.pcrud.retrieve('vst', id).subscribe(
306             tracker => {
307                 if (tracker && tracker.state() === 'active') {
308                     observer.next(tracker);
309                     setTimeout(() => 
310                         this.getNextSessionTracker(id, observer), 2000);
311                 } else {
312                     console.debug(
313                         `Vandelay session tracker ${id} is ${tracker.state()}`);
314                     observer.complete();
315                 }
316             }
317         );
318     }
319 }
320