diff --git a/src/models/aggregation.ts b/src/models/aggregation.ts index 512ebb5f..47223751 100644 --- a/src/models/aggregation.ts +++ b/src/models/aggregation.ts @@ -20,6 +20,10 @@ export enum AggregationSortType { * Sort ascending alphabetically by key */ ALPHABETICAL, + /** + * Sort descending numerically by key + */ + NUMERIC, } export interface AggregationOptions { @@ -67,16 +71,22 @@ export class Aggregation { * returned as-is. * * @param sortType What to sort the buckets on. - * Accepted values are `AggregationSortType.COUNT` (descending order) and - * `AggregationSortType.ALPHABETICAL` (ascending order). + * Accepted values are: + * - `AggregationSortType.COUNT` (descending order) + * - `AggregationSortType.ALPHABETICAL` (ascending order) + * - `AggregationSortType.NUMERIC` (descending order) */ @Memoize() getSortedBuckets(sortType?: AggregationSortType): Bucket[] | number[] { - // Don't apply sorts to numeric buckets. + const copiedBuckets = [...this.buckets] as Bucket[] | number[]; + + // Don't apply sorts to number[] aggregations (like year_histogram). + // Note this _doesn't_ apply to ordinary year aggregations, which have + // keyed buckets just like most other facet types. // Assumption here is that all the buckets have the same type as the // first bucket (which should be true in principle). - if (typeof this.buckets[0] === 'number') { - return [...(this.buckets as number[])]; + if (this.isRawNumberBuckets(copiedBuckets)) { + return copiedBuckets; } // Default locale & collation options @@ -84,13 +94,28 @@ export class Aggregation { switch (sortType) { case AggregationSortType.ALPHABETICAL: - return [...(this.buckets as Bucket[])].sort((a, b) => { + return copiedBuckets.sort((a, b) => { return collator.compare(a.key.toString(), b.key.toString()); }); + case AggregationSortType.NUMERIC: + return copiedBuckets.sort((a, b) => { + return Number(b.key) - Number(a.key); + }); case AggregationSortType.COUNT: default: // Sorted by count by default - return [...(this.buckets as Bucket[])]; + return copiedBuckets; } } + + /** + * Type guard for number[] buckets, since the buckets provided by the PPS + * may be either keyed objects or just an array of numbers. Currently only + * `year_histogram` facets are of the latter type. + */ + private isRawNumberBuckets( + buckets: Bucket[] | number[] + ): buckets is number[] { + return typeof this.buckets[0] === 'number'; + } } diff --git a/src/responses/search-request-params.ts b/src/responses/search-request-params.ts index 0671de2a..06d37794 100644 --- a/src/responses/search-request-params.ts +++ b/src/responses/search-request-params.ts @@ -20,4 +20,5 @@ export interface SearchRequestParams { sort?: string[]; aggregations?: string[]; aggregations_size?: number; + uid?: string; } diff --git a/test/models/aggregation.test.ts b/test/models/aggregation.test.ts new file mode 100644 index 00000000..1177e66c --- /dev/null +++ b/test/models/aggregation.test.ts @@ -0,0 +1,101 @@ +import { expect } from '@open-wc/testing'; +import { + Aggregation, + AggregationSortType, + Bucket, +} from '../../src/models/aggregation'; + +describe('Aggregation model', () => { + it('constructs with options', () => { + const buckets = [1, 2, 3, 4]; + const doc_count_error_upper_bound = 10; + const sum_other_doc_count = 20; + const first_bucket_key = 0; + const last_bucket_key = 3; + const number_buckets = 4; + const interval = 1; + + const agg = new Aggregation({ + buckets, + doc_count_error_upper_bound, + sum_other_doc_count, + first_bucket_key, + last_bucket_key, + number_buckets, + interval, + }); + + expect(agg.buckets).to.equal(buckets); + expect(agg.doc_count_error_upper_bound).to.equal( + doc_count_error_upper_bound + ); + expect(agg.sum_other_doc_count).to.equal(sum_other_doc_count); + expect(agg.first_bucket_key).to.equal(first_bucket_key); + expect(agg.last_bucket_key).to.equal(last_bucket_key); + expect(agg.number_buckets).to.equal(number_buckets); + expect(agg.interval).to.equal(interval); + }); + + it('expects default sorted buckets by count', async () => { + const buckets: Bucket[] = [ + { key: 'a', doc_count: 5 }, + { key: 'e', doc_count: 4 }, + { key: 'b', doc_count: 3 }, + { key: 'c', doc_count: 2 }, + { key: 'd', doc_count: 1 }, + ]; + const agg = new Aggregation({ buckets }); + + expect(agg.getSortedBuckets(AggregationSortType.COUNT)).to.deep.equal( + buckets + ); + }); + + it('sorts buckets alphabetically', async () => { + const buckets: Bucket[] = [ + { key: 'e', doc_count: 4 }, + { key: 'c', doc_count: 2 }, + { key: 'b', doc_count: 3 }, + { key: 'd', doc_count: 1 }, + { key: 'a', doc_count: 5 }, + ]; + const agg = new Aggregation({ buckets }); + + expect( + agg.getSortedBuckets(AggregationSortType.ALPHABETICAL) + ).to.deep.equal([ + { key: 'a', doc_count: 5 }, + { key: 'b', doc_count: 3 }, + { key: 'c', doc_count: 2 }, + { key: 'd', doc_count: 1 }, + { key: 'e', doc_count: 4 }, + ]); + }); + + it('sorts buckets numerically', async () => { + const buckets: Bucket[] = [ + { key: 400, doc_count: 4 }, + { key: 1999, doc_count: 2 }, + { key: 1001, doc_count: 3 }, + { key: 1900, doc_count: 1 }, + { key: 2005, doc_count: 5 }, + ]; + const agg = new Aggregation({ buckets }); + + expect(agg.getSortedBuckets(AggregationSortType.NUMERIC)).to.deep.equal([ + { key: 2005, doc_count: 5 }, + { key: 1999, doc_count: 2 }, + { key: 1900, doc_count: 1 }, + { key: 1001, doc_count: 3 }, + { key: 400, doc_count: 4 }, + ]); + }); + + it('does not sort raw number buckets', async () => { + const buckets: number[] = [3, 5, 2, 4, 1]; + const agg = new Aggregation({ buckets }); + expect(agg.getSortedBuckets(AggregationSortType.COUNT)).to.deep.equal( + buckets + ); + }); +});