Skip to content

Commit

Permalink
Merge pull request #125 from NYPL-discovery/pb/add-standard-number-se…
Browse files Browse the repository at this point in the history
…arch

Add standard number search
  • Loading branch information
nonword authored Oct 16, 2018
2 parents 4adcc9c + 7a0fc9f commit 3ef4359
Show file tree
Hide file tree
Showing 40 changed files with 12,749 additions and 683 deletions.
1 change: 1 addition & 0 deletions data/contexts/resource.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"idBarcode": "dcterms:identifier",
"idBnum": "dcterms:identifier",
"identifier": "dcterms:identifier",
"identifierStatus": "bf:identifierStatus",
"idCallnum": "dcterms:identifier",
"idCatnyp": "dcterms:identifier",
"idDcc": "dcterms:identifier",
Expand Down
20 changes: 7 additions & 13 deletions lib/jsonld_serializers.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,18 @@ class JsonLdItemSerializer extends JsonLdSerializer {
if (Array.isArray(v)) return v.map(formatVal)
else if (v && typeof v === 'object') {
var formatted = v
// Reassign id to @id
if (formatted.id) {
formatted = Object.assign({ '@id': v.id }, formatted)
delete formatted.id
}
// Reassign id to @id, type to @type, value to @value
; ['id', 'type', 'value'].forEach((specialProp) => {
if (formatted[specialProp]) {
formatted[`@${specialProp}`] = formatted[specialProp]
delete formatted[specialProp]
}
})
// TODO need to correct this in the indexer, not here
if (formatted.label) {
formatted.prefLabel = formatted.label
delete formatted.label
}
if (formatted.type) {
formatted['@type'] = formatted.type
delete formatted.type
}
return formatted
} else return v
}
Expand Down Expand Up @@ -219,8 +217,6 @@ class ResourceSerializer extends JsonLdItemSerializer {
statements () {
var stmts = JsonLdItemSerializer.prototype.statements.call(this)

stmts.identifier = stmts.identifier.map(this._ensureIdentifierIsUrnStyle)

if (this.body.parentUri) stmts.memberOf = R.flatten([util.eachValue(this.body.parentUri, (id) => ({ '@type': 'nypl:Resource', '@id': `res:${id}` }))])

// Parse all contributor_(aut|ill|...) statements:
Expand Down Expand Up @@ -284,8 +280,6 @@ class ItemResourceSerializer extends JsonLdItemSerializer {
statements () {
var stmts = JsonLdItemSerializer.prototype.statements.call(this)

stmts.identifier = stmts.identifier.map(this._ensureIdentifierIsUrnStyle)

if (stmts.identifier) {
// Add idNyplSourceId convenience property by parsing identifiers that match urn:[source]:[id]
this.body.identifier
Expand Down
146 changes: 115 additions & 31 deletions lib/resources.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ const SEARCH_SCOPES = {
},
callnumber: {
fields: ['shelfMark']
},
standard_number: {
fields: ['shelfMark', 'identifierV2.value', 'uri', 'identifier', 'items.identifierV2.value']
}
}

Expand Down Expand Up @@ -470,33 +473,80 @@ const escapeQuery = function (str) {
}

/**
* Given GET params, returns a plainobject suitable for use in a ES query.
* For a set of parsed request params, returns a plainobject representing the
* keyword aspects of the ES query.
*
* @param {object} params - A hash of request params including `search_scope`, `q`
*
* @return {object} ES query object suitable to be POST'd to ES endpoint
*/
const buildElasticQuery = function (params) {
const buildElasticQueryForKeywords = function (params) {
// Fill these with our top-level clauses:
var shoulds = []

// clean up params
;['q'].forEach(function (param) {
if (params[param]) {
params[param] = params[param].replace(/date:/g, 'dateStartYear:')
params[param] = params[param].replace(/location:/g, 'locations:')
params[param] = params[param].replace(/subject:/g, 'subjectLiteral:')
const should = []

// We have an array of fields to match.
// Seperate the root-level fields from nested fields by building an object like this:
// {
// _root: [ 'fieldName1', 'fieldName2' ],
// nestedName1: { 'nestedName1.nestedProperty1', 'nestedName1.nestedProperty2' }
// }
const fieldMap = SEARCH_SCOPES[params.search_scope].fields.reduce((map, fieldName) => {
// Most fields will be matched at root level:
let nestedName = '_root'
// Any field starting with the following is a nested field:
if (['items'].indexOf(fieldName.split('.').shift()) >= 0) {
nestedName = fieldName.split('.').shift()
}
if (!map[nestedName]) map[nestedName] = []
map[nestedName].push(fieldName)
return map
}, { _root: [] })

should.push({
'query_string': {
'fields': fieldMap._root,
'query': escapeQuery(params.q),
'default_operator': 'AND'
}
})

if (params.q) {
shoulds.push({
'query_string': {
'fields': SEARCH_SCOPES[params.search_scope].fields,
// default_field: defaultField,
'query': escapeQuery(params.q),
'default_operator': 'AND'
}
// Add nested queries (if any) to things that *should* match:
Object.keys(fieldMap)
.filter((nestedName) => nestedName !== '_root')
.forEach((nestedName) => {
should.push({
nested: {
path: nestedName,
query: {
query_string: {
fields: fieldMap[nestedName],
query: escapeQuery(params.q),
default_operator: 'AND'
}
}
}
})
})

const query = {}
if (should.length > 0) {
query.bool = { should }
}

return query
}

/**
* For a set of parsed request params, returns a plainobject representing the
* filter aspects of the ES query.
*
* @param {object} params - A hash of request params including `filters`
*
* @return {object} ES query object suitable to be POST'd to ES endpoint
*/
const buildElasticQueryForFilters = function (params) {
var filterClausesWithPaths = []

if (params.filters) {
filterClausesWithPaths = Object.keys(params.filters).map((prop) => {
var config = FILTER_CONFIG[prop]
Expand Down Expand Up @@ -527,26 +577,19 @@ const buildElasticQuery = function (params) {
})
}

// Build ES query:
var query = {}

// Gather root (not nested) filters:
let rootFilterClauses = filterClausesWithPaths
.filter((clauseWithPath) => !clauseWithPath.path)
.map((clauseWithPath) => clauseWithPath.clause)

if (shoulds.length + rootFilterClauses.length > 0) {
query.bool = {}
}
if (shoulds.length > 0) {
query.bool.should = shoulds
}
const query = {}

// Add nested filters:
filterClausesWithPaths.filter((clauseWithPath) => clauseWithPath.path)
filterClausesWithPaths
// Nested filters have a `path` property:
.filter((clauseWithPath) => clauseWithPath.path)
.forEach((clauseWithPath) => {
if (!query.nested) query.nested = {}

// TODO: Note we seem to lack support for applying multiple nested filters
query.nested = {
path: clauseWithPath.path,
query: {
Expand All @@ -558,7 +601,48 @@ const buildElasticQuery = function (params) {
})

if (rootFilterClauses.length > 0) {
query.bool.filter = rootFilterClauses
query.bool = { filter: rootFilterClauses }
}

return query
}

/**
* Given GET params, returns a plainobject suitable for use in a ES query.
*
* @param {object} params - A hash of request params including `filters`,
* `search_scope`, `q`
*
* @return {object} ES query object suitable to be POST'd to ES endpoint
*/
const buildElasticQuery = function (params) {
// Build ES query:
var query = {}

// clean up params
;['q'].forEach(function (param) {
if (params[param]) {
params[param] = params[param].replace(/date:/g, 'dateStartYear:')
params[param] = params[param].replace(/location:/g, 'locations:')
params[param] = params[param].replace(/subject:/g, 'subjectLiteral:')
}
})

if (params.q) {
// Merge keyword-specific ES query into the query we're building:
query.bool = buildElasticQueryForKeywords(params).bool
}

if (params.filters) {
// Merge filter-specific ES query into the query we're building:
const filterQuery = buildElasticQueryForFilters(params)
if (filterQuery.bool) {
if (!query.bool) query.bool = {}
query.bool.filter = filterQuery.bool.filter
}
if (filterQuery.nested) {
query.nested = filterQuery.nested
}
}

return query
Expand Down
12 changes: 7 additions & 5 deletions swagger.v0.1.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
"tags": [
"discovery"
],
"summary": "Search resources",
"description": "Match by keyword\n\n> `/resources?q=war peace`\n\nFilters are applied using a `filters` param that expects this syntax on the query string:\n\n> /resources?filters[property1]=value1&filters[property2]=value2\n\nWhere `property*` is one of: \n\n> 'owner', 'subjectLiteral', 'holdingLocation', 'deliveryLocation', 'language', 'materialType', 'mediaType', 'carrierType', 'publisher', 'contributor', 'creator', 'issuance', 'createdYear', 'dateAfter', or 'dateBefore'.\n\nSee [the app README for more examples](https://github.com/NYPL-discovery/discovery-api/blob/master/README.md)\n",
"summary": "Search resources (bibs and items)",
"description": "Match by keyword\n> `/resources?q=war peace`\n\nFilters are applied using a `filters` param that expects this syntax on the query string:\n\n> `/resources?filters[property1]=value1&filters[property2]=value2`\n\nWhere `property*` is one of: \n\n> 'owner', 'subjectLiteral', 'holdingLocation', 'deliveryLocation', 'language', 'materialType', 'mediaType', 'carrierType', 'publisher', 'contributor', 'creator', 'issuance', 'createdYear', 'dateAfter', or 'dateBefore'.\n\nSee [the app README for more examples](https://github.com/NYPL-discovery/discovery-api/blob/master/README.md)\n",
"parameters": [
{
"name": "q",
Expand All @@ -38,6 +38,7 @@
"description": "Page number to return",
"required": false,
"type": "integer",
"default": 1,
"minimum": 1
},
{
Expand Down Expand Up @@ -67,7 +68,7 @@
{
"name": "sort_direction",
"in": "query",
"description": "Override the default direction for the current sort (asc, desc). Default depends on the field.",
"description": "Override the default direction for the current sort (asc, desc). Default depends on the field. (title defaults to asc, date defaults to desc, creator defaults to asc, relevance is fixed desc)",
"required": false,
"type": "string",
"enum": [
Expand All @@ -78,7 +79,7 @@
{
"name": "search_scope",
"in": "query",
"description": "Specify what (group of) fields to match against (all, title, contributor, subject, series, callnumber)",
"description": "Specify what (group of) fields to match against (all, title, contributor, subject, series, callnumber, standard_number). See https://github.com/NYPL-discovery/discovery-api/blob/master/lib/resources.js to review specific fields matched in each scope and how they're boosted.",
"required": false,
"type": "string",
"enum": [
Expand All @@ -87,7 +88,8 @@
"contributor",
"subject",
"series",
"callnumber"
"callnumber",
"standard_number"
],
"default": "all"
},
Expand Down
14 changes: 9 additions & 5 deletions swagger.v0.1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,18 @@ paths:
get:
tags:
- discovery
summary: Search resources
summary: Search resources (bibs and items)
description: >
Match by keyword
> `/resources?q=war peace`
Filters are applied using a `filters` param that expects this syntax on
the query string:
> /resources?filters[property1]=value1&filters[property2]=value2
> `/resources?filters[property1]=value1&filters[property2]=value2`
Where `property*` is one of:
Expand Down Expand Up @@ -55,6 +54,7 @@ paths:
description: Page number to return
required: false
type: integer
default: 1
minimum: 1
- name: per_page
in: query
Expand All @@ -79,7 +79,8 @@ paths:
in: query
description: >-
Override the default direction for the current sort (asc, desc).
Default depends on the field.
Default depends on the field. (title defaults to asc, date defaults
to desc, creator defaults to asc, relevance is fixed desc)
required: false
type: string
enum:
Expand All @@ -89,7 +90,9 @@ paths:
in: query
description: >-
Specify what (group of) fields to match against (all, title,
contributor, subject, series, callnumber)
contributor, subject, series, callnumber, standard_number). See
https://github.com/NYPL-discovery/discovery-api/blob/master/lib/resources.js
to review specific fields matched in each scope and how they're boosted.
required: false
type: string
enum:
Expand All @@ -99,6 +102,7 @@ paths:
- subject
- series
- callnumber
- standard_number
default: all
- name: 'filters[*]'
in: query
Expand Down
4 changes: 0 additions & 4 deletions test/delivery-locations-resolver.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,6 @@ const scholarRooms = [
id: 'loc:mala',
label: 'Schwarzman Building - Allen Scholar Room'
},
{
id: 'loc:maln',
label: 'Schwarzman Building - Noma Scholar Room'
},
{
id: 'loc:malw',
label: 'Schwarzman Building - Wertheim Scholar Room'
Expand Down
Loading

0 comments on commit 3ef4359

Please sign in to comment.