Skip to content

Commit

Permalink
Merge 97c481a into 7174c57
Browse files Browse the repository at this point in the history
  • Loading branch information
latonv committed Jun 29, 2023
2 parents 7174c57 + 97c481a commit b3d4f41
Show file tree
Hide file tree
Showing 5 changed files with 162 additions and 9 deletions.
6 changes: 6 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,12 @@ export {
export { SearchResponse } from './src/responses/search-response';
export { SearchResponseHeader } from './src/responses/search-response-header';
export { SearchResponseParams } from './src/responses/search-response-params';
export {
CollectionExtraInfo,
CollectionSearchDocs,
RelatedCollection,
UserDetails,
} from './src/responses/collection-extra-info';

export { MetadataSearchBackend } from './src/search-backend/metadata-search-backend';
export { FulltextSearchBackend } from './src/search-backend/fulltext-search-backend';
Expand Down
13 changes: 8 additions & 5 deletions src/models/hit-types/hit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,19 @@ import type { TextHit } from './text-hit';
export type HitType = 'item' | 'text';

/**
* Applying this to the Result type forces Intellisense to present Result as
* a type in its own right, and not as the underlying merge type it aliases.
* Really just to keep things clean at the call site.
* Additional information provided by the PPS about hits, separately from
* their fields map.
*/
interface PreserveAlias {} // eslint-disable-line @typescript-eslint/no-empty-interface
interface HitInfo {
hit_type?: HitType;
index?: string;
service_backend?: string;
}

/**
* Result is an expansive type definition encompassing all the optional
* and required properties that may occur on any type of search result
* ('hit') returned by the various search backends. (Most metadata
* properties are optional anyway).
*/
export type SearchResult = Partial<ItemHit & TextHit> & PreserveAlias;
export type SearchResult = Partial<ItemHit & TextHit> & HitInfo;
55 changes: 55 additions & 0 deletions src/responses/collection-extra-info.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Metadata } from '../models/metadata';

/**
* Extra info about the target collection that is returned for
* the `collection_details` page type.
*/
export interface CollectionExtraInfo {
thumbnail_url?: string;
search_doc_fields?: CollectionSearchDocs;
has_items_with_searchable_text?: boolean;
forum_identifier?: string;
forum_count?: number;
review_count?: number;
related_collection_details?: RelatedCollection[];
uploader_details?: UserDetails;
contributors_details?: UserDetails[];
public_metadata?: typeof Metadata.prototype.rawMetadata;
}

/**
* Fields from the search docs returned for the `collection_details`
* page type.
*/
export interface CollectionSearchDocs {
item_count?: number;
files_count?: number;
collection_size?: number;
collection_files_count?: number;
month?: number;
week?: number;
downloads?: number;
num_favorites?: number;
title_message?: string | null;
primary_collection?: string | null;
}

/**
* Info about a related collection, as returned for the `collection_details`
* page type.
*/
export interface RelatedCollection {
identifier: string;
title?: string;
item_count?: number;
}

/**
* Info about a user (e.g., uploaders/contributors), as returned for
* the `collection_details` page type.
*/
export interface UserDetails {
screen_name?: string;
useritem?: string;
is_archivist?: boolean;
}
20 changes: 16 additions & 4 deletions src/responses/search-response-details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Aggregation } from '../models/aggregation';
import { SearchResult, HitType } from '../models/hit-types/hit';
import { ItemHit } from '../models/hit-types/item-hit';
import { TextHit } from '../models/hit-types/text-hit';
import { CollectionExtraInfo } from './collection-extra-info';
import type { SearchHitSchema } from './search-hit-schema';

/**
Expand All @@ -12,6 +13,7 @@ export interface SearchResponseBody {
hits: SearchResponseHits;
aggregations?: Record<string, Aggregation>;
collection_titles?: Record<string, string>;
collection_extra_info?: CollectionExtraInfo;
}

/**
Expand Down Expand Up @@ -59,20 +61,26 @@ export class SearchResponseDetails {
*/
collectionTitles?: Record<string, string>;

/**
* Extra info about the target collection, returned when the page type is
* `collection_details`.
*/
collectionExtraInfo?: CollectionExtraInfo;

/**
* The hit schema for this response
*/
schema?: SearchHitSchema;

constructor(body: SearchResponseBody, schema: SearchHitSchema) {
this.schema = schema;
const hitType = schema?.hit_type;
const schemaHitType = schema?.hit_type;

this.totalResults = body?.hits?.total ?? 0;
this.returnedCount = body?.hits?.returned ?? 0;
this.results =
body?.hits?.hits?.map((hit: SearchResult) =>
SearchResponseDetails.createResult(hitType, hit)
SearchResponseDetails.createResult(hit.hit_type ?? schemaHitType, hit)
) ?? [];

// Construct Aggregation objects
Expand All @@ -89,6 +97,10 @@ export class SearchResponseDetails {
if (body?.collection_titles) {
this.collectionTitles = body.collection_titles;
}

if (body?.collection_extra_info) {
this.collectionExtraInfo = body.collection_extra_info;
}
}

/**
Expand All @@ -104,8 +116,8 @@ export class SearchResponseDetails {
case 'text':
return new TextHit(result);
default:
// The hit type doesn't tell us what to construct, so just return the input as-is
return result;
// The hit type doesn't tell us what to construct, so just construct an ItemHit
return new ItemHit(result);

Check warning on line 120 in src/responses/search-response-details.ts

View check run for this annotation

Codecov / codecov/patch

src/responses/search-response-details.ts#L119-L120

Added lines #L119 - L120 were not covered by tests
}
}
}
77 changes: 77 additions & 0 deletions test/responses/search-response-details.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const responseBody: SearchResponseBody = {
collection_titles: {
baz: 'Baz Collection',
},
collection_extra_info: {
thumbnail_url: 'foo',
},
};

describe('SearchResponseDetails', () => {
Expand Down Expand Up @@ -63,6 +66,49 @@ describe('SearchResponseDetails', () => {
expect(details.results[1].collection?.value).to.equal('baz');
});

it('prefers hit-type specified on hit itself over schema hit-type', () => {
const responseBodyWithTextHit = Object.assign(
{},
responseBody
) as SearchResponseBody;
responseBodyWithTextHit.hits = {
total: 2,
returned: 2,
hits: [
{
hit_type: 'text',
fields: {
identifier: 'foo',
mediatype: 'texts',
},
},
{
fields: {
identifier: 'bar',
collection: ['baz'],
},
},
],
};

const responseSchema = {
hit_type: 'item' as HitType,
field_properties: {},
};

const details = new SearchResponseDetails(
responseBodyWithTextHit,
responseSchema
);
expect(details.results[0]).to.be.instanceOf(TextHit); // From hit, not schema
expect(details.results[0].identifier).to.equal('foo');
expect(details.results[0].mediatype?.value).to.equal('texts');
expect(details.results[0].creator?.value).to.be.undefined;
expect(details.results[1]).to.be.instanceOf(ItemHit); // From schema
expect(details.results[1].identifier).to.equal('bar');
expect(details.results[1].collection?.value).to.equal('baz');
});

it('includes aggregations', () => {
const aggsResponseBody: SearchResponseBody = {
hits: {
Expand Down Expand Up @@ -131,4 +177,35 @@ describe('SearchResponseDetails', () => {
expect(details.results.length).to.equal(2);
expect(details.collectionTitles).to.be.undefined;
});

it('provides access to collection extra info', () => {
const responseSchema = {
hit_type: 'item' as HitType,
field_properties: {},
};

const details = new SearchResponseDetails(responseBody, responseSchema);
expect(details.results.length).to.equal(2);
expect(details.collectionExtraInfo).to.deep.equal({ thumbnail_url: 'foo' });
});

it('collection extra info is optional', () => {
const responseBodyWithoutExtraInfo = Object.assign(
{},
responseBody
) as SearchResponseBody;
delete responseBodyWithoutExtraInfo.collection_extra_info;

const responseSchema = {
hit_type: 'item' as HitType,
field_properties: {},
};

const details = new SearchResponseDetails(
responseBodyWithoutExtraInfo,
responseSchema
);
expect(details.results.length).to.equal(2);
expect(details.collectionExtraInfo).to.be.undefined;
});
});

0 comments on commit b3d4f41

Please sign in to comment.