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';
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 = [
28 export class CatalogService {
30 ccvmMap: {[ccvm: string]: IdlObject[]} = {};
31 cmfMap: {[cmf: string]: IdlObject} = {};
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.
40 private idl: IdlService,
41 private net: NetService,
42 private org: OrgService,
43 private unapi: UnapiService,
44 private pcrud: PcrudService,
45 private bibService: BibRecordService
48 search(ctx: CatalogSearchContext): Promise<void> {
49 ctx.searchState = CatalogSearchState.SEARCHING;
51 const fullQuery = ctx.compileSearch();
53 console.debug(`search query: ${fullQuery}`);
55 let method = 'open-ils.search.biblio.multiclass.query';
60 return new Promise((resolve, reject) => {
62 'open-ils.search', method, {
63 limit : ctx.pager.limit + 1,
64 offset : ctx.pager.offset
66 ).subscribe(result => {
67 this.applyResultData(ctx, result);
68 ctx.searchState = CatalogSearchState.COMPLETE;
74 applyResultData(ctx: CatalogSearchContext, result: any): void {
76 ctx.pager.resultCount = result.count;
78 // records[] tracks the current page of bib summaries.
81 // If this is a new search, reset the result IDs collection.
82 if (this.lastFacetKey !== result.facet_key) {
86 result.ids.forEach((blob, idx) => ctx.addResultId(blob[0], idx));
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> {
93 const depth = ctx.global ?
94 ctx.org.root().ou_type().depth() :
95 ctx.searchOrg.ou_type().depth();
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;
109 fetchFacets(ctx: CatalogSearchContext): Promise<void> {
112 return Promise.reject('Cannot fetch facets without results');
115 if (this.lastFacetKey === ctx.result.facet_key) {
116 ctx.result.facetData = this.lastFacetData;
117 return Promise.resolve();
120 return new Promise((resolve, reject) => {
121 this.net.request('open-ils.search',
122 'open-ils.search.facet_cache.retrieve',
124 ).subscribe(facets => {
125 const facetData = {};
126 Object.keys(facets).forEach(cmfId => {
127 const facetHash = facets[cmfId];
128 const cmf = this.cmfMap[cmfId];
131 Object.keys(facetHash).forEach(value => {
132 const count = facetHash[value];
133 cmfData.push({value : value, count : count});
136 if (!facetData[cmf.field_class()]) {
137 facetData[cmf.field_class()] = {};
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;
151 this.lastFacetKey = ctx.result.facet_key;
152 this.lastFacetData = ctx.result.facetData = facetData;
158 fetchCcvms(): Promise<void> {
160 if (Object.keys(this.ccvmMap).length) {
161 return Promise.resolve();
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);
175 compileCcvms(ccvms: IdlObject[]): void {
176 ccvms.forEach(ccvm => {
177 if (!this.ccvmMap[ccvm.ctype()]) {
178 this.ccvmMap[ccvm.ctype()] = [];
180 this.ccvmMap[ccvm.ctype()].push(ccvm);
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;
192 fetchCmfs(): Promise<void> {
193 // At the moment, we only need facet CMFs.
194 if (Object.keys(this.cmfMap).length) {
195 return Promise.resolve();
198 return new Promise((resolve, reject) => {
199 this.pcrud.search('cmf',
200 {facet_field : 't'}, {}, {atomic: true, anonymous: true}
203 cmfs.forEach(c => this.cmfMap[c.id()] = c);