diff --git a/demo/app-root.ts b/demo/app-root.ts index 43d72500..3ae2b7ec 100644 --- a/demo/app-root.ts +++ b/demo/app-root.ts @@ -13,7 +13,7 @@ import { nothing } from 'lit-html'; import { Metadata } from '../src/models/metadata'; import { MetadataResponse } from '../src/responses/metadata/metadata-response'; import { SearchResponse } from '../src/responses/search/search-response'; -import { SearchParams } from '../src/search-params'; +import { AggregateSearchParams, SearchParams } from '../src/search-params'; import { SearchService } from '../src/search-service'; import { SearchServiceInterface } from '../src/search-service-interface'; @@ -116,10 +116,19 @@ export class AppRoot extends LitElement { async search(e: Event): Promise { e.preventDefault(); const term = this.searchInput.value; + const aggregations = new AggregateSearchParams({ + advancedParams: [ + { + field: 'year', + size: 100, + }, + ], + }); const searchParams = new SearchParams({ query: term, rows: 10, fields: ['identifier', 'title'], + aggregations, }); const result = await this.searchService.search(searchParams); if (result?.success) { diff --git a/index.ts b/index.ts index a9a8540c..ebf64bcd 100644 --- a/index.ts +++ b/index.ts @@ -33,5 +33,10 @@ export { SearchResponseParams } from './src/responses/search/search-response-par export { DefaultSearchBackend } from './src/search-backend/default-search-backend'; export { SearchServiceInterface } from './src/search-service-interface'; export { SearchService } from './src/search-service'; -export { SearchParams } from './src/search-params'; -export { SortParam } from './src/search-params'; +export { + SearchParams, + SortParam, + SortDirection, + AggregateSearchParams, + AggregateSearchParam, +} from './src/search-params'; diff --git a/package.json b/package.json index abcc6266..b2a2239a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@internetarchive/search-service", - "version": "0.2.6", + "version": "0.2.5", "description": "A search service for the Internet Archive", "license": "AGPL-3.0-only", "main": "dist/index.js", diff --git a/src/models/aggregation.ts b/src/models/aggregation.ts index 0847ca4c..b5ff10db 100644 --- a/src/models/aggregation.ts +++ b/src/models/aggregation.ts @@ -18,5 +18,13 @@ export interface Bucket { export interface Aggregation { doc_count_error_upper_bound?: number; sum_other_doc_count?: number; - buckets: Bucket[]; + /** + * The year_histogram returns a `number` array, and + * other facets return a `Bucket` array + */ + buckets: Bucket[] | number[]; + first_bucket_key?: number; + last_bucket_key?: number; + number_buckets?: number; + interval?: number; } diff --git a/src/search-params.ts b/src/search-params.ts index 861dc7ba..68151ac4 100644 --- a/src/search-params.ts +++ b/src/search-params.ts @@ -1,13 +1,19 @@ export interface AggregateSearchParam { field: string; - size: number; + size?: number; } export class AggregateSearchParams { - searchParams: AggregateSearchParam[]; + private advancedParams?: AggregateSearchParam[]; - constructor(searchParams: AggregateSearchParam[]) { - this.searchParams = searchParams; + private simpleParams?: string[]; + + constructor(options: { + simpleParams?: string[]; + advancedParams?: AggregateSearchParam[]; + }) { + this.advancedParams = options.advancedParams; + this.simpleParams = options.simpleParams; } /** @@ -30,15 +36,23 @@ export class AggregateSearchParams { * } * ] * - * @returns {Record[]} + * @returns string | undefined} * @memberof AggregateSearchParams */ - get asSearchParams(): Record[] { - return this.searchParams.map(param => { - return { - terms: param, - }; - }); + get asSearchParams(): string | undefined { + if (this.advancedParams) { + const params = this.advancedParams.map(param => { + return { + terms: param, + }; + }); + const stringified = JSON.stringify(params); + return stringified; + } + + if (this.simpleParams) { + return this.simpleParams.join(','); + } } } @@ -124,10 +138,9 @@ export class SearchParams { params.append('sort', sortStrings.join(',')); } - if (this.aggregations) { - const searchParams = this.aggregations.asSearchParams; - const stringified = JSON.stringify(searchParams); - params.append('user_aggs', stringified); + const searchParams = this.aggregations?.asSearchParams; + if (searchParams) { + params.append('user_aggs', searchParams); } return params; diff --git a/test/search-params.test.ts b/test/search-params.test.ts index 6883fddf..92dbc3f7 100644 --- a/test/search-params.test.ts +++ b/test/search-params.test.ts @@ -83,18 +83,62 @@ describe('SearchParams', () => { expect(queryAsString).to.equal(expected); }); - it('properly generates a URLSearchParam with aggregations', async () => { + it('properly generates a URLSearchParam with advanced aggregations', async () => { const query = 'title:foo AND collection:bar'; - const aggregations = new AggregateSearchParams([ - { - field: 'foo', - size: 10, - }, - { - field: 'bar', - size: 7, - }, - ]); + const aggregations = new AggregateSearchParams({ + advancedParams: [ + { + field: 'foo', + size: 10, + }, + { + field: 'bar', + size: 7, + }, + ], + }); + const params = new SearchParams({ + query, + aggregations, + }); + const urlSearchParam = params.asUrlSearchParams; + const queryAsString = urlSearchParam.toString(); + const expected = + 'q=title%3Afoo+AND+collection%3Abar&output=json&user_aggs=%5B%7B%22terms%22%3A%7B%22field%22%3A%22foo%22%2C%22size%22%3A10%7D%7D%2C%7B%22terms%22%3A%7B%22field%22%3A%22bar%22%2C%22size%22%3A7%7D%7D%5D'; + expect(queryAsString).to.equal(expected); + }); + + it('properly generates a URLSearchParam with simple aggregations', async () => { + const query = 'title:foo AND collection:bar'; + const aggregations = new AggregateSearchParams({ + simpleParams: ['year', 'collection', 'subject'], + }); + const params = new SearchParams({ + query, + aggregations, + }); + const urlSearchParam = params.asUrlSearchParams; + const queryAsString = urlSearchParam.toString(); + const expected = + 'q=title%3Afoo+AND+collection%3Abar&output=json&user_aggs=year%2Ccollection%2Csubject'; + expect(queryAsString).to.equal(expected); + }); + + it('advanced aggregations take precedence if both simple and advanced provided', async () => { + const query = 'title:foo AND collection:bar'; + const aggregations = new AggregateSearchParams({ + advancedParams: [ + { + field: 'foo', + size: 10, + }, + { + field: 'bar', + size: 7, + }, + ], + simpleParams: ['year'], + }); const params = new SearchParams({ query, aggregations,