From 78854d6040360194c4557cfa40940d638ce8bfeb Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Tue, 18 Dec 2018 13:15:56 -0500 Subject: [PATCH] LP1806087 Group formats and editions Signed-off-by: Bill Erickson --- Open-ILS/examples/fm_IDL.xml | 16 +++- .../src/app/share/catalog/bib-record.service.ts | 99 ++++++++++++++++++++-- .../src/app/share/catalog/catalog-url.service.ts | 6 +- .../eg2/src/app/share/catalog/catalog.service.ts | 37 ++++++-- .../eg2/src/app/share/catalog/search-context.ts | 14 ++- .../app/staff/catalog/result/record.component.html | 18 ++-- .../app/staff/catalog/result/record.component.ts | 10 ++- .../app/staff/catalog/search-form.component.html | 2 +- .../src/app/staff/catalog/search-form.component.ts | 1 + 9 files changed, 177 insertions(+), 26 deletions(-) diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 1be2c885e8..e934ae6142 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -3899,18 +3899,25 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + + + + + + + + @@ -3943,7 +3950,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA - + @@ -3953,6 +3960,11 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + + + + + { if (this.attributes[attr.attr()]) { - this.attributes[attr.attr()].push(attr.value()); + // Avoid dupes + if (this.attributes[attr.attr()].indexOf(attr.value()) < 0) { + this.attributes[attr.attr()].push(attr.value()); + } } else { this.attributes[attr.attr()] = [attr.value()]; } @@ -83,9 +88,16 @@ export class BibRecordSummary { return Promise.resolve(this.holdCount); } + let method = 'open-ils.circ.bre.holds.count'; + let target = this.id; + + if (this.metabibId) { + method = 'open-ils.circ.mmr.holds.count'; + target = this.metabibId; + } + return this.net.request( - 'open-ils.circ', - 'open-ils.circ.bre.holds.count', this.id + 'open-ils.circ', method, target ).toPromise().then(count => this.holdCount = count); } @@ -133,7 +145,7 @@ export class BibRecordService { } // Avoid fetching the MARC blob by specifying which fields on the - // bre to select. Note that fleshed fields are explicitly selected. + // bre to select. Note that fleshed fields are implicitly selected. fetchableBreFields(): string[] { return this.idl.classes.bre.fields .filter(f => !f.virtual && f.name !== 'marc') @@ -169,6 +181,81 @@ export class BibRecordService { })); } + // A Metabib Summary is a BibRecordSummary with the lead record as + // its core bib record plus attributes (e.g. formats) from related + // records. + getMetabibSummary(metabibIds: number | number[], + orgId?: number, orgDepth?: number): Observable { + + const ids = [].concat(metabibIds); + + if (ids.length === 0) { + return from([]); + } + + return this.pcrud.search('mmr', {id: ids}, + {flesh: 1, flesh_fields: {mmr: ['source_maps']}}, + {anonymous: true} + ).pipe(mergeMap(mmr => this.compileMetabib(mmr, orgId, orgDepth))); + } + + // 'metabib' must have its "source_maps" field fleshed. + // Get bib summaries for all related bib records so we can + // extract data that must be appended to the master record summary. + compileMetabib(metabib: IdlObject, + orgId?: number, orgDepth?: number): Observable { + + // TODO: Create an API similar to the one that builds a combined + // mods blob for metarecords, except using display fields, etc. + // For now, this seems to get the job done. + + // Non-master records + const relatedBibIds = metabib.source_maps() + .map(map => map.source()) + .filter(id => id !== metabib.master_record()); + + let observer; + const observable = new Observable(o => observer = o); + + // NOTE: getBibSummary calls getHoldingsSummary against + // the bib record unnecessarily. It's called again below. + // Reconsider this approach (see also note above about API). + this.getBibSummary(metabib.master_record(), orgId, orgDepth) + .subscribe(summary => { + summary.metabibId = metabib.id(); + + let promise; + + if (relatedBibIds.length > 0) { + + // Grab data for MR bib summary augmentation + promise = this.pcrud.search('mraf', {id: relatedBibIds}) + .pipe(tap(attr => summary.record.mattrs().push(attr))) + .toPromise(); + } else { + + // Metarecord has only one constituent bib. + promise = Promise.resolve(); + } + + promise.then(() => { + + // Re-compile with augmented data + summary.compileRecordAttrs(); + + // Fetch holdings data for the metarecord + this.getHoldingsSummary(metabib.id(), orgId, orgDepth, true) + .then(holdingsSummary => { + summary.holdingsSummary = holdingsSummary; + observer.next(summary); + observer.complete(); + }); + }); + }); + + return observable; + } + // Flesh the creator and editor fields. // Handling this separately lets us pull from the cache and // avoids the requirement that the main bib query use a staff @@ -209,12 +296,12 @@ export class BibRecordService { } getHoldingsSummary(recordId: number, - orgId: number, orgDepth: number): Promise { + orgId: number, orgDepth: number, isMetarecord?: boolean): Promise { const holdingsSummary = []; return this.unapi.getAsXmlDocument({ - target: 'bre', + target: isMetarecord ? 'mmr' : 'bre', id: recordId, extras: '{holdings_xml}', format: 'holdings_xml', diff --git a/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts index 60c31284a5..cfec1d9076 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/catalog-url.service.ts @@ -80,7 +80,8 @@ export class CatalogUrlService { params.joinOp = []; params.matchOp = []; - ['format', 'available', 'hasBrowseEntry', 'date1', 'date2', 'dateOp'] + ['format', 'available', 'hasBrowseEntry', 'date1', + 'date2', 'dateOp', 'isMetarecord', 'fromMetarecord'] .forEach(field => { if (ts[field]) { params[field] = ts[field]; @@ -195,7 +196,8 @@ export class CatalogUrlService { } else if (params.has('query')) { // Scalars - ['format', 'available', 'date1', 'date2', 'dateOp'] + ['format', 'available', 'date1', 'date2', + 'dateOp', 'isMetarecord', 'fromMetarecord'] .forEach(field => { if (params.has(field)) { ts[field] = params.get(field); diff --git a/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts b/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts index 93eb964533..c741acbae6 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/catalog.service.ts @@ -114,7 +114,6 @@ export class CatalogService { let fullQuery; if (ctx.identSearch.isSearchable()) { - console.log('IDENT IS SEARCHABLE'); fullQuery = ctx.compileIdentSearchQuery(); } else { fullQuery = ctx.compileTermSearchQuery(); @@ -123,6 +122,12 @@ export class CatalogService { console.debug(`search query: ${fullQuery}`); let method = 'open-ils.search.biblio.multiclass.query'; + if (ctx.termSearch.isSearchable() + && ctx.termSearch.isMetarecord + && !ctx.termSearch.fromMetarecord) { + method = 'open-ils.search.metabib.multiclass.query'; + } + if (ctx.isStaff) { method += '.staff'; } @@ -166,11 +171,33 @@ export class CatalogService { ctx.org.root().ou_type().depth() : ctx.searchOrg.ou_type().depth(); - return this.bibService.getBibSummary( - ctx.currentResultIds(), ctx.searchOrg.id(), depth) - .pipe(map(summary => { + // Term search, looking for metarecords, but no + // specific metarecord has been selected for display. + const isMeta = ( + ctx.termSearch.isSearchable() && + ctx.termSearch.isMetarecord && + !ctx.termSearch.fromMetarecord + ); + + let observable: Observable; + + if (isMeta) { + observable = this.bibService.getMetabibSummary( + ctx.currentResultIds(), ctx.searchOrg.id(), depth); + } else { + observable = this.bibService.getBibSummary( + ctx.currentResultIds(), ctx.searchOrg.id(), depth); + } + + return observable.pipe(map(summary => { // Responses are not necessarily returned in request-ID order. - const idx = ctx.currentResultIds().indexOf(summary.record.id()); + let idx; + if (isMeta) { + idx = ctx.currentResultIds().indexOf(summary.metabibId); + } else { + idx = ctx.currentResultIds().indexOf(summary.id); + } + if (ctx.result.records) { // May be reset when quickly navigating results. ctx.result.records[idx] = summary; diff --git a/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts b/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts index e4d65d4a35..56ab52783d 100644 --- a/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts +++ b/Open-ILS/src/eg2/src/app/share/catalog/search-context.ts @@ -122,7 +122,13 @@ export class CatalogTermContext { ccvmFilters: {[ccvmCode: string]: string[]}; facetFilters: FacetFilter[]; copyLocations: string[]; // ID's, but treated as strings in the UI. - isMetarecord: boolean; // TODO + + // True when searching for metarecords + isMetarecord: boolean; + + // Filter results by records which link to this metarecord ID. + fromMetarecord: number; + hasBrowseEntry: string; // "entryId,fieldId" date1: number; date2: number; @@ -140,6 +146,7 @@ export class CatalogTermContext { this.date1 = null; this.date2 = null; this.dateOp = 'is'; + this.fromMetarecord = null; // Apply empty string values for each ccvm filter this.ccvmFilters = {}; @@ -150,6 +157,7 @@ export class CatalogTermContext { return ( this.query[0] !== '' || this.hasBrowseEntry !== '' + || this.fromMetarecord !== null ); } @@ -416,6 +424,10 @@ export class CatalogSearchContext { str += ` has_browse_entry(${ts.hasBrowseEntry})`; } + if (ts.fromMetarecord) { + str += ` from_metarecord(${ts.fromMetarecord})`; + } + if (ts.format) { str += ' format(' + ts.format + ')'; } diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html index 54b79c195b..ddfacc0fb5 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html +++ b/Open-ILS/src/eg2/src/app/staff/catalog/result/record.component.html @@ -32,7 +32,7 @@ @@ -48,12 +48,15 @@
- - - - {{iconFormatLabel(summary.attributes.icon_format[0])}} - + + + + + {{iconFormatLabel(icon)}} + + + {{summary.display.edition}} {{summary.display.pubdate}}
@@ -119,6 +122,7 @@
diff --git a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts index a558011f61..049b677255 100644 --- a/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts +++ b/Open-ILS/src/eg2/src/app/staff/catalog/search-form.component.ts @@ -193,6 +193,7 @@ export class SearchFormComponent implements OnInit, AfterViewInit { this.context.browseSearch.reset(); this.context.identSearch.reset(); this.context.termSearch.hasBrowseEntry = ''; + this.context.termSearch.fromMetarecord = null; this.staffCat.search(); break; -- 2.11.0