Skip to content

Commit

Permalink
search facets
Browse files Browse the repository at this point in the history
  • Loading branch information
theorm committed Nov 8, 2024
1 parent 4ba2640 commit c867ae1
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 44 deletions.
4 changes: 2 additions & 2 deletions src/hooks/transformation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { HookContext, HookFunction } from '@feathersjs/feathers'
import { ImpressoApplication } from '../types'

export const transformResponse = <S, I, O>(
transformer: (item: I) => O,
transformer: ((item: I) => O) | ((item: I, context: HookContext<ImpressoApplication>) => O),
condition?: (context: HookContext<ImpressoApplication>) => boolean
): HookFunction<ImpressoApplication, S> => {
return context => {
Expand All @@ -11,7 +11,7 @@ export const transformResponse = <S, I, O>(

if (context.result != null) {
const ctx = context as any
ctx.result = transformer(context.result as I)
ctx.result = transformer(context.result as I, context)
}
return context
}
Expand Down
24 changes: 10 additions & 14 deletions src/models/generated/schemasPublic.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,19 +243,17 @@ export interface SearchFacetBucket {
*/
count: number;
/**
* Value of the 'type' element
* Value that represents the bucket.
*/
val: string;
value: string | number;
/**
* UID of the 'type' element. Same as 'val'
* Unique ID of the value, if relevant and different from the value itself.
*/
uid?: string;
/**
* The item in the bucket. Particular objct schema depends on the facet type
* Label of the value, if relevant.
*/
item?: {
[k: string]: unknown;
};
label?: string;
}
/**
* Facet bucket
Expand Down Expand Up @@ -289,19 +287,17 @@ export interface SearchFacetBucket {
*/
count: number;
/**
* Value of the 'type' element
* Value that represents the bucket.
*/
val: string;
value: string | number;
/**
* UID of the 'type' element. Same as 'val'
* Unique ID of the value, if relevant and different from the value itself.
*/
uid?: string;
/**
* The item in the bucket. Particular objct schema depends on the facet type
* Label of the value, if relevant.
*/
item?: {
[k: string]: unknown;
};
label?: string;
}


Expand Down
20 changes: 9 additions & 11 deletions src/schema/schemasPublic/SearchFacetBucket.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,20 @@
"title": "Search Facet Bucket",
"description": "Facet bucket",
"additionalProperties": false,
"required": ["count", "val"],
"properties": {
"count": {
"type": "integer",
"description": "Number of items in the bucket"
"description": "Number of items in the bucket",
"minimum": 0
},
"val": {
"type": "string",
"description": "Value of the 'type' element"
"value": {
"anyOf": [{ "type": "string" }, { "type": "number" }, { "type": "integer" }],
"description": "Value that represents the bucket."
},
"uid": {
"label": {
"type": "string",
"description": "UID of the 'type' element. Same as 'val'"
},
"item": {
"description": "The item in the bucket. Particular objct schema depends on the facet type"
"description": "Label of the value, if relevant."
}
}
},
"required": ["count", "value"]
}
5 changes: 4 additions & 1 deletion src/services/search-facets/search-facets.hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import { eachFilterValidator, paramsValidator } from '../search/search.validator
import { getIndexMeta } from './search-facets.class'
import { IndexId, OrderByChoices, facetTypes } from './search-facets.schema'
import { parseFilters } from '../../util/queryParameters'
import { transformResponse } from '../../hooks/transformation'
import { inPublicApi } from '../../hooks/redaction'
import { transformSearchFacet } from '../../transformers/searchFacet'

const getAndFindHooks = (index: IndexId) => [
validate({
Expand Down Expand Up @@ -116,6 +119,6 @@ export const getHooks = (index: IndexId) => ({

after: {
find: [resolveCollections(), resolveTextReuseClusters()],
get: [resolveCollections(), resolveTextReuseClusters()],
get: [resolveCollections(), resolveTextReuseClusters(), transformResponse(transformSearchFacet, inPublicApi)],
},
})
10 changes: 7 additions & 3 deletions src/services/search-facets/search-facets.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,11 @@ const toPascalCase = (s: string) => {

export const getDocs = (index: IndexId): ServiceSwaggerOptions => ({
description: `${facetNames[index]} facets`,
securities: ['get', 'find'],
securities: ['get' /*, 'find' */],
operations: {
// RK: I disabled the find operation because it can be entirely replaced by individual get operations,
// not used in impresso-py and adds extra maintenance burden.
/*
find: {
operationId: `find${toPascalCase(index)}Facets`,
description: `Get mutliple ${facetNames[index]} facets`,
Expand All @@ -119,6 +122,7 @@ export const getDocs = (index: IndexId): ServiceSwaggerOptions => ({
schema: 'SearchFacet',
}),
},
*/
get: {
operationId: `get${toPascalCase(index)}Facet`,
description: `Get a single ${facetNames[index]} facet`,
Expand All @@ -137,8 +141,8 @@ export const getDocs = (index: IndexId): ServiceSwaggerOptions => ({
...getStandardParameters({ method: 'find' }),
],
responses: getStandardResponses({
method: 'get',
schema: 'SearchFacet',
method: 'find',
schema: 'SearchFacetBucket',
}),
},
},
Expand Down
27 changes: 15 additions & 12 deletions src/services/search-facets/search-facets.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,23 @@ const SupportedIndexes: IndexId[] = Object.keys(SolrMappings).map(key => key.rep

export default (app: ImpressoApplication) => {
// Initialize our service with any options it requires
const isPublicApi = app.get('isPublicApi')

SupportedIndexes.forEach(index => {
app.use(
`search-facets/${index}`,
new Service({
app,
index,
name: `search-facets-${index}`,
}),
{
events: [],
docs: createSwaggerServiceOptions({ schemas: {}, docs: getDocs(index) }),
} as ServiceOptions
)
const svc = new Service({
app,
index,
name: `search-facets-${index}`,
})
// not exposing find method in public API
if (isPublicApi) {
;(svc as any).find = undefined
}

app.use(`search-facets/${index}`, svc, {
events: [],
docs: createSwaggerServiceOptions({ schemas: {}, docs: getDocs(index) }),
} as ServiceOptions)
// Get our initialized service so that we can register hooks
const service = app.service(`search-facets/${index}`)
service.hooks(getHooks(index))
Expand Down
6 changes: 5 additions & 1 deletion src/transformers/contentItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@ export const transformContentItem = (input: ContentItemPrivate): ContentItemPubl
transcript: input.content ?? '',
locations: input.locations?.map(toEntityMention) ?? [],
persons: input.persons?.map(toEntityMention) ?? [],
topics: input.topics?.map(toTopicMention)?.filter(v => v != null) ?? [],
topics:
input.topics
?.map(toTopicMention)
?.filter(v => v != null)
.map(v => v as TopicMention) ?? [],
transcriptLength: input.size ?? 0,
totalPages: input.nbPages,
languageCode: input.language?.toLowerCase(),
Expand Down
89 changes: 89 additions & 0 deletions src/transformers/searchFacet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { HookContext } from '@feathersjs/feathers'
import { ImpressoApplication } from '../types'

import {
SearchFacet,
BaseFind,
SearchFacetBucket as SearchFacetBucketInternal,
SearchFacetRangeBucket,
} from '../models/generated/schemas'
import { SearchFacetBucket } from '../models/generated/schemasPublic'
import Collection from '../models/collections.model'
import Newspaper from '../models/newspapers.model'
import Entity from '../models/entities.model'
import Topic from '../models/topics.model'

interface FacetContainer extends BaseFind {
data: SearchFacetBucket[]
}

const transformBucket = (
input: SearchFacetBucketInternal | SearchFacetRangeBucket,
facetType: string
): SearchFacetBucket => {
switch (facetType) {
case 'contentLength':
case 'month':
case 'textReuseClusterSize':
case 'textReuseClusterLexicalOverlap':
case 'textReuseClusterDayDelta':
return {
count: input.count,
value: typeof input.val === 'string' ? parseInt(input.val) : input.val,
}
case 'country':
case 'type':
case 'language':
case 'accessRight':
return {
count: input.count,
value: String(input.val),
}
case 'topic':
const topicItem = (input as any)?.item as Topic
return {
count: input.count,
value: String(input.val),
label: topicItem.words.map(({ w, p }) => `${w} (${p})`).join(', '),
}
case 'collection':
const collectionItem = (input as any)?.item as Collection
return {
count: input.count,
value: String(input.val),
label: collectionItem != null ? collectionItem.name : undefined,
}
case 'newspaper':
const newspaperItem = (input as any)?.item as Newspaper
return {
count: input.count,
value: String(input.val),
label: newspaperItem.name,
}
case 'person':
case 'location':
const entityItem = (input as any)?.item as Entity
return {
count: input.count,
value: String(input.val),
label: entityItem.name,
}
default:
return {
count: input.count,
value: input.val,
}
}
}

export const transformSearchFacet = (input: SearchFacet, context: HookContext<ImpressoApplication>): FacetContainer => {
console.log('III', input, context.id)

return {
data: input.buckets.map(b => transformBucket(b, context.id as string)),
total: input.numBuckets,
limit: context.params?.query?.limit ?? input.buckets.length,
offset: context.params?.query?.offset ?? 0,
info: {},
}
}

0 comments on commit c867ae1

Please sign in to comment.