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';
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 = [
29 export class CatalogService {
31 ccvmMap: {[ccvm: string]: IdlObject[]} = {};
32 cmfMap: {[cmf: string]: IdlObject} = {};
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.
41 private idl: IdlService,
42 private net: NetService,
43 private org: OrgService,
44 private unapi: UnapiService,
45 private pcrud: PcrudService,
46 private bibService: BibRecordService
49 search(ctx: CatalogSearchContext): Promise<void> {
50 ctx.searchState = CatalogSearchState.SEARCHING;
52 const fullQuery = ctx.compileSearch();
54 console.debug(`search query: ${fullQuery}`);
56 let method = 'open-ils.search.biblio.multiclass.query';
61 return new Promise((resolve, reject) => {
63 'open-ils.search', method, {
64 limit : ctx.pager.limit + 1,
65 offset : ctx.pager.offset
67 ).subscribe(result => {
68 this.applyResultData(ctx, result);
69 ctx.searchState = CatalogSearchState.COMPLETE;
75 applyResultData(ctx: CatalogSearchContext, result: any): void {
77 ctx.pager.resultCount = result.count;
79 // records[] tracks the current page of bib summaries.
82 // If this is a new search, reset the result IDs collection.
83 if (this.lastFacetKey !== result.facet_key) {
87 result.ids.forEach((blob, idx) => ctx.addResultId(blob[0], idx));
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> {
94 const depth = ctx.global ?
95 ctx.org.root().ou_type().depth() :
96 ctx.searchOrg.ou_type().depth();
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;
110 fetchFacets(ctx: CatalogSearchContext): Promise<void> {
113 return Promise.reject('Cannot fetch facets without results');
116 if (this.lastFacetKey === ctx.result.facet_key) {
117 ctx.result.facetData = this.lastFacetData;
118 return Promise.resolve();
121 return new Promise((resolve, reject) => {
122 this.net.request('open-ils.search',
123 'open-ils.search.facet_cache.retrieve',
125 ).subscribe(facets => {
126 const facetData = {};
127 Object.keys(facets).forEach(cmfId => {
128 const facetHash = facets[cmfId];
129 const cmf = this.cmfMap[cmfId];
132 Object.keys(facetHash).forEach(value => {
133 const count = facetHash[value];
134 cmfData.push({value : value, count : count});
137 if (!facetData[cmf.field_class()]) {
138 facetData[cmf.field_class()] = {};
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;
152 this.lastFacetKey = ctx.result.facet_key;
153 this.lastFacetData = ctx.result.facetData = facetData;
159 fetchCcvms(): Promise<void> {
161 if (Object.keys(this.ccvmMap).length) {
162 return Promise.resolve();
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);
176 compileCcvms(ccvms: IdlObject[]): void {
177 ccvms.forEach(ccvm => {
178 if (!this.ccvmMap[ccvm.ctype()]) {
179 this.ccvmMap[ccvm.ctype()] = [];
181 this.ccvmMap[ccvm.ctype()].push(ccvm);
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;
193 fetchCmfs(): Promise<void> {
194 // At the moment, we only need facet CMFs.
195 if (Object.keys(this.cmfMap).length) {
196 return Promise.resolve();
199 return new Promise((resolve, reject) => {
200 this.pcrud.search('cmf',
201 {facet_field : 't'}, {}, {atomic: true, anonymous: true}
204 cmfs.forEach(c => this.cmfMap[c.id()] = c);