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