LP#1801984 Upgrading Angular 6 to Angular 7
[Evergreen.git] / Open-ILS / src / eg2 / src / app / share / catalog / catalog.service.ts
1 import {Injectable} from '@angular/core';
2 import {Observable} from 'rxjs';
3 import {mergeMap, map} from 'rxjs/operators';
4 import {OrgService} from '@eg/core/org.service';
5 import {UnapiService} from '@eg/share/catalog/unapi.service';
6 import {IdlService, IdlObject} from '@eg/core/idl.service';
7 import {NetService} from '@eg/core/net.service';
8 import {PcrudService} from '@eg/core/pcrud.service';
9 import {CatalogSearchContext, CatalogSearchState} from './search-context';
10 import {BibRecordService, BibRecordSummary} from './bib-record.service';
11
12 // CCVM's we care about in a catalog context
13 // Don't fetch them all because there are a lot.
14 export const CATALOG_CCVM_FILTERS = [
15     'item_type',
16     'item_form',
17     'item_lang',
18     'audience',
19     'audience_group',
20     'vr_format',
21     'bib_level',
22     'lit_form',
23     'search_format',
24     'icon_format'
25 ];
26
27 @Injectable()
28 export class CatalogService {
29
30     ccvmMap: {[ccvm: string]: IdlObject[]} = {};
31     cmfMap: {[cmf: string]: IdlObject} = {};
32
33     // Keep a reference to the most recently retrieved facet data,
34     // since facet data is consistent across a given search.
35     // No need to re-fetch with every page of search data.
36     lastFacetData: any;
37     lastFacetKey: string;
38
39     constructor(
40         private idl: IdlService,
41         private net: NetService,
42         private org: OrgService,
43         private unapi: UnapiService,
44         private pcrud: PcrudService,
45         private bibService: BibRecordService
46     ) {}
47
48     search(ctx: CatalogSearchContext): Promise<void> {
49         ctx.searchState = CatalogSearchState.SEARCHING;
50
51         const fullQuery = ctx.compileSearch();
52
53         console.debug(`search query: ${fullQuery}`);
54
55         let method = 'open-ils.search.biblio.multiclass.query';
56         if (ctx.isStaff) {
57             method += '.staff';
58         }
59
60         return new Promise((resolve, reject) => {
61             this.net.request(
62                 'open-ils.search', method, {
63                     limit : ctx.pager.limit + 1,
64                     offset : ctx.pager.offset
65                 }, fullQuery, true
66             ).subscribe(result => {
67                 this.applyResultData(ctx, result);
68                 ctx.searchState = CatalogSearchState.COMPLETE;
69                 resolve();
70             });
71         });
72     }
73
74     applyResultData(ctx: CatalogSearchContext, result: any): void {
75         ctx.result = result;
76         ctx.pager.resultCount = result.count;
77
78         // records[] tracks the current page of bib summaries.
79         result.records = [];
80
81         // If this is a new search, reset the result IDs collection.
82         if (this.lastFacetKey !== result.facet_key) {
83             ctx.resultIds = [];
84         }
85
86         result.ids.forEach((blob, idx) => ctx.addResultId(blob[0], idx));
87     }
88
89     // Appends records to the search result set as they arrive.
90     // Returns a void promise once all records have been retrieved
91     fetchBibSummaries(ctx: CatalogSearchContext): Promise<void> {
92
93         const depth = ctx.global ?
94             ctx.org.root().ou_type().depth() :
95             ctx.searchOrg.ou_type().depth();
96
97         return this.bibService.getBibSummary(
98             ctx.currentResultIds(), ctx.searchOrg.id(), depth)
99         .pipe(map(summary => {
100             // Responses are not necessarily returned in request-ID order.
101             const idx = ctx.currentResultIds().indexOf(summary.record.id());
102             if (ctx.result.records) {
103                 // May be reset when quickly navigating results.
104                 ctx.result.records[idx] = summary;
105             }
106         })).toPromise();
107     }
108
109     fetchFacets(ctx: CatalogSearchContext): Promise<void> {
110
111         if (!ctx.result) {
112             return Promise.reject('Cannot fetch facets without results');
113         }
114
115         if (this.lastFacetKey === ctx.result.facet_key) {
116             ctx.result.facetData = this.lastFacetData;
117             return Promise.resolve();
118         }
119
120         return new Promise((resolve, reject) => {
121             this.net.request('open-ils.search',
122                 'open-ils.search.facet_cache.retrieve',
123                 ctx.result.facet_key
124             ).subscribe(facets => {
125                 const facetData = {};
126                 Object.keys(facets).forEach(cmfId => {
127                     const facetHash = facets[cmfId];
128                     const cmf = this.cmfMap[cmfId];
129
130                     const cmfData = [];
131                     Object.keys(facetHash).forEach(value => {
132                         const count = facetHash[value];
133                         cmfData.push({value : value, count : count});
134                     });
135
136                     if (!facetData[cmf.field_class()]) {
137                         facetData[cmf.field_class()] = {};
138                     }
139
140                     facetData[cmf.field_class()][cmf.name()] = {
141                         cmfLabel : cmf.label(),
142                         valueList : cmfData.sort((a, b) => {
143                             if (a.count > b.count) { return -1; }
144                             if (a.count < b.count) { return 1; }
145                             // secondary alpha sort on display value
146                             return a.value < b.value ? -1 : 1;
147                         })
148                     };
149                 });
150
151                 this.lastFacetKey = ctx.result.facet_key;
152                 this.lastFacetData = ctx.result.facetData = facetData;
153                 resolve();
154             });
155         });
156     }
157
158     fetchCcvms(): Promise<void> {
159
160         if (Object.keys(this.ccvmMap).length) {
161             return Promise.resolve();
162         }
163
164         return new Promise((resolve, reject) => {
165             this.pcrud.search('ccvm',
166                 {ctype : CATALOG_CCVM_FILTERS}, {},
167                 {atomic: true, anonymous: true}
168             ).subscribe(list => {
169                 this.compileCcvms(list);
170                 resolve();
171             });
172         });
173     }
174
175     compileCcvms(ccvms: IdlObject[]): void {
176         ccvms.forEach(ccvm => {
177             if (!this.ccvmMap[ccvm.ctype()]) {
178                 this.ccvmMap[ccvm.ctype()] = [];
179             }
180             this.ccvmMap[ccvm.ctype()].push(ccvm);
181         });
182
183         Object.keys(this.ccvmMap).forEach(cType => {
184             this.ccvmMap[cType] =
185                 this.ccvmMap[cType].sort((a, b) => {
186                     return a.value() < b.value() ? -1 : 1;
187                 });
188         });
189     }
190
191
192     fetchCmfs(): Promise<void> {
193         // At the moment, we only need facet CMFs.
194         if (Object.keys(this.cmfMap).length) {
195             return Promise.resolve();
196         }
197
198         return new Promise((resolve, reject) => {
199             this.pcrud.search('cmf',
200                 {facet_field : 't'}, {}, {atomic: true, anonymous: true}
201             ).subscribe(
202                 cmfs => {
203                     cmfs.forEach(c => this.cmfMap[c.id()] = c);
204                     resolve();
205                 }
206             );
207         });
208     }
209 }