Skip to content

Commit

Permalink
Add support for simple aggregations (#9)
Browse files Browse the repository at this point in the history
There are certain fields in the aggregations (facets), particularly the year histogram facet where the data is different depending on how it is requested: "simple" facets vs json-style "advanced" facets. This adds support for both formats so we can request the year histogram independently from the other facets.
  • Loading branch information
jbuckner committed Apr 19, 2022
1 parent 27ffa10 commit a08c19b
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 31 deletions.
11 changes: 10 additions & 1 deletion demo/app-root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -116,10 +116,19 @@ export class AppRoot extends LitElement {
async search(e: Event): Promise<void> {
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) {
Expand Down
9 changes: 7 additions & 2 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
10 changes: 9 additions & 1 deletion src/models/aggregation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
43 changes: 28 additions & 15 deletions src/search-params.ts
Original file line number Diff line number Diff line change
@@ -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;
}

/**
Expand All @@ -30,15 +36,23 @@ export class AggregateSearchParams {
* }
* ]
*
* @returns {Record<string, AggregateSearchParam>[]}
* @returns string | undefined}
* @memberof AggregateSearchParams
*/
get asSearchParams(): Record<string, AggregateSearchParam>[] {
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(',');
}
}
}

Expand Down Expand Up @@ -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;
Expand Down
66 changes: 55 additions & 11 deletions test/search-params.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit a08c19b

Please sign in to comment.