Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[chore] Replace sync getCached across the code with a centralised async version #483

Merged
merged 1 commit into from
Jan 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions src/hooks/resolvers/articles.resolvers.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { buildResolvers } from '../../internalServices/cachedResolvers'
import { resolveAsync } from '../../util/solr/adapters'
const lodash = require('lodash')
const debug = require('debug')('impresso/hooks/resolvers:articles')
Expand All @@ -8,16 +9,22 @@ const resolveTopics = () => async context => {
if (!context.result) {
debug('resolveTopics: no "context.result" found')
} else if (context.result.data && context.result.data.length) {
context.result.data = context.result.data.map(d => {
if (!d.topics) {
const resolvers = buildResolvers(context.app)

context.result.data = await Promise.all(
context.result.data.map(async d => {
if (!d.topics) {
return d
}
d.topics = await Promise.all(
d.topics.map(async at => {
at.topic = await resolvers.topic(at.topicUid, 'topic')
return at
})
)
return d
}
d.topics = d.topics.map(at => {
at.topic = Topic.getCached(at.topicUid)
return at
})
return d
})
)
} else if (context.result.topics && context.result.topics.length) {
debug(`resolveTopics: "context.result.topics" found with ${context.result.topics.length} topics`)

Expand Down
22 changes: 13 additions & 9 deletions src/hooks/search-info.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { HookContext } from '@feathersjs/feathers'
import { AppServices, ImpressoApplication } from '../types'
import { mediaSourceToNewspaper } from '../services/newspapers/newspapers.class'
import { buildResolvers } from '../internalServices/cachedResolvers'

const debug = require('debug')('impresso/hooks:search-info')

const Topic = require('../models/topics.model')

/**
* check if there are any params to be added to our beloved facets.
* This hook **must** follow facets validation.
Expand Down Expand Up @@ -63,11 +62,14 @@ const resolveFacets = () => async (context: HookContext<ImpressoApplication, App

if (context.result.info.facets.topic) {
debug('resolveFacets for topics')
context.result.info.facets.topic.buckets = context.result.info.facets.newspaper.buckets.map((d: any) => ({
...d,
item: Topic.getCached(d.val),
uid: d.val,
}))
const resolvers = buildResolvers(context.app)
context.result.info.facets.topic.buckets = await Promise.all(
context.result.info.facets.newspaper.buckets.map(async (d: any) => ({
...d,
item: await resolvers.topic(d.val),
uid: d.val,
}))
)
}
}
}
Expand All @@ -88,10 +90,12 @@ const resolveQueryComponents = () => async (context: HookContext<ImpressoApplica
d.items = d.q.map((uid: string) => mediaSourceToNewspaper(mediaSourcesLookup[uid]))
}
} else if (d.type === 'topic') {
const resolvers = buildResolvers(context.app)

if (!Array.isArray(d.q)) {
d.items = [Topic.getCached(d.q)]
d.items = [resolvers.topic(d.q)]
} else {
d.items = d.q.map((uid: string) => Topic.getCached(uid))
d.items = await Promise.all(d.q.map(async (uid: string) => await resolvers.topic(uid)))
}
} else if (d.type === 'collection' && context.params.user) {
// eslint-disable-next-line no-await-in-loop
Expand Down
16 changes: 8 additions & 8 deletions src/internalServices/cachedResolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,30 @@ import { Newspaper as NewspaperInternal } from '../models/generated/schemas'

export type CachedFacetType = 'newspaper' | 'topic' | 'person' | 'location' | 'collection' | 'year'

type IResolver<T> = (id: string, type: CachedFacetType) => Promise<T | undefined>
export type IResolver<T> = (id: string) => Promise<T | undefined>

export type ICachedResolvers = Record<CachedFacetType, IResolver<any>>

const collectionResolver: IResolver<Collection> = async (id: string, _) =>
const collectionResolver: IResolver<Collection> = async (id: string) =>
new Collection({
uid: id,
name: id,
})

const entityResolver: IResolver<Entity> = async (id: string, type: CachedFacetType) =>
const entityResolver = async (id: string, type: CachedFacetType) =>
new Entity({
uid: id,
type,
name: Entity.getNameFromUid(id),
})

const topicResolver: IResolver<Topic> = async (id: string, _) => Topic.getCached(id)
const topicResolver: IResolver<Topic> = async (id: string) => Topic.getCached(id)

const yearResolver: IResolver<Year> = async (id: string, _) => Year.getCached(id)
const yearResolver: IResolver<Year> = async (id: string) => Year.getCached(id)

const getNewspaperResolver = (app: ImpressoApplication): IResolver<NewspaperInternal> => {
const mediaSources = app.service('media-sources')
return async (id: string, _) => {
return async (id: string) => {
const lookup = await mediaSources.getLookup()
const item = lookup[id]
return optionalMediaSourceToNewspaper(item)
Expand All @@ -40,8 +40,8 @@ const getNewspaperResolver = (app: ImpressoApplication): IResolver<NewspaperInte

export const buildResolvers = (app: ImpressoApplication): ICachedResolvers => ({
collection: collectionResolver,
location: entityResolver,
person: entityResolver,
location: (id: string) => entityResolver(id, 'location'),
person: (id: string) => entityResolver(id, 'person'),
topic: topicResolver,
year: yearResolver,
newspaper: getNewspaperResolver(app),
Expand Down
2 changes: 1 addition & 1 deletion src/models/search-facets.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ class SearchFacetBucket implements ISearchFacetBucket {
const uid = String(val)
const resolver = resolvers[type as CachedFacetType]

const item = resolver != null ? await resolver(uid, type as CachedFacetType) : undefined
const item = resolver != null ? await resolver(uid) : undefined

return new SearchFacetBucket({
val,
Expand Down
108 changes: 54 additions & 54 deletions src/services/filters-items/extractors.js
Original file line number Diff line number Diff line change
@@ -1,82 +1,82 @@
const newspapersIndex = require('../../data')('newspapers');
const Topic = require('../../models/topics.model');
const Entity = require('../../models/entities.model');
const Year = require('../../models/years.model');
import { buildResolvers } from '../../internalServices/cachedResolvers'
const Entity = require('../../models/entities.model')

const isDateRangeString = v => v.match(/.+ TO .+/) != null;
const getDateStrings = v => v.match(/(.+) TO (.+)/).slice(1, 3);
const isDateRangeString = v => v.match(/.+ TO .+/) != null
const getDateStrings = v => v.match(/(.+) TO (.+)/).slice(1, 3)

function daterangeExtractor ({ q = '' }) {
const values = Array.isArray(q) ? q : [q];
function daterangeExtractor({ q = '' }) {
const values = Array.isArray(q) ? q : [q]

// if `q` is an array with two date strings, return one item for them
const isTwoDatesArray = values.length === 2 && values.filter(isDateRangeString).length === 0;
const isTwoDatesArray = values.length === 2 && values.filter(isDateRangeString).length === 0
if (isTwoDatesArray) {
const [start, end] = values;
return [{ start, end }];
const [start, end] = values
return [{ start, end }]
}

// otherwise parse ranges
return values.map((value) => {
const [start, end] = getDateStrings(value);
return { start, end };
});
return values.map(value => {
const [start, end] = getDateStrings(value)
return { start, end }
})
}

function newspaperExtractor ({ q = '' }) {
const codes = Array.isArray(q) ? q : [q];
return codes.map(code => newspapersIndex.values[code.trim()] || {});
async function newspaperExtractor({ q = '' }, app) {
const resolvers = buildResolvers(app)

const codes = Array.isArray(q) ? q : [q]
return await Promise.all(codes.map(async code => resolvers.newspaper(code.trim())))
}

function topicExtractor ({ q = '' }) {
const items = Array.isArray(q) ? q : [q];
return items
.map(item => Topic.getCached(item.trim()))
.filter(item => item != null);
async function topicExtractor({ q = '' }, app) {
const resolvers = buildResolvers(app)
const items = Array.isArray(q) ? q : [q]
return await Promise.all(items.map(async item => await resolvers.topic(item.trim()))).filter(item => item != null)
}

function entityExtractor ({ q = '' }) {
const items = Array.isArray(q) ? q : [q];
return items
.map(item => Entity.getCached(item.trim()))
.filter(item => item != null);
async function entityExtractor({ q = '' }, app) {
const resolvers = buildResolvers(app)
const items = Array.isArray(q) ? q : [q]
return await Promise.all(
items.map(async item => {
const uid = item.trim()
const type = Entity.getTypeFromUid(uid)
return type === 'person' ? await resolvers.person(uid) : await resolvers.location(uid)
})
).filter(item => item != null)
}

function yearExtractor ({ q = '' }) {
const items = Array.isArray(q) ? q : [q];
return items
.map(item => Year.getCached(item.trim()))
.filter(item => item != null);
async function yearExtractor({ q = '' }, app) {
const resolvers = buildResolvers(app)

const items = Array.isArray(q) ? q : [q]
return await Promise.all(items.map(async item => resolvers.year(item.trim()))).filter(item => item != null)
}

async function collectionExtractor ({ q = '' }, app) {
const items = Array.isArray(q) ? q : [q];
async function collectionExtractor({ q = '' }, app) {
const items = Array.isArray(q) ? q : [q]

try {
return await Promise.all(items.map(async (item) => {
const payload = { query: { nameOnly: true } };
return app.service('collections').get(item.trim(), payload);
}));
return await Promise.all(
items.map(async item => {
const payload = { query: { nameOnly: true } }
return app.service('collections').get(item.trim(), payload)
})
)
} catch (error) {
if (error.name === 'NotFound') return [];
throw error;
if (error.name === 'NotFound') return []
throw error
}
}

function numberRangeExtractor ({ q = '' }) {
const [start, end] = Array.isArray(q)
? q
: q.trim().split(' TO ');
return start && end
? [{ start: parseInt(start, 10), end: parseInt(end, 10) }]
: [];
function numberRangeExtractor({ q = '' }) {
const [start, end] = Array.isArray(q) ? q : q.trim().split(' TO ')
return start && end ? [{ start: parseInt(start, 10), end: parseInt(end, 10) }] : []
}

function simpleValueExtractor ({ q = '' }) {
const items = Array.isArray(q)
? q
: [q.trim()];
return items.map(uid => ({ uid }));
function simpleValueExtractor({ q = '' }) {
const items = Array.isArray(q) ? q : [q.trim()]
return items.map(uid => ({ uid }))
}

module.exports = {
Expand All @@ -88,4 +88,4 @@ module.exports = {
collectionExtractor,
numberRangeExtractor,
simpleValueExtractor,
};
}
31 changes: 7 additions & 24 deletions src/services/search/search.extractors.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { keyBy, isEmpty, assignIn, clone, isUndefined, fromPairs } from 'lodash'
import Article from '../../models/articles.model'
import Newspaper from '../../models/newspapers.model'
import Topic from '../../models/topics.model'
import Entity from '../../models/entities.model'
import Year from '../../models/years.model'
import { filtersToQueryAndVariables, getRegionCoordinatesFromDocument } from '../../util/solr'
import { Service } from '../articles/articles.class'
import { ImpressoApplication } from '../../types'
import { optionalMediaSourceToNewspaper } from '../newspapers/newspapers.class'
import { buildResolvers, CachedFacetType, IResolver } from '../../internalServices/cachedResolvers'

function getAricleMatchesAndRegions(
article: Article | undefined,
Expand Down Expand Up @@ -81,28 +77,15 @@ export async function getItemsFromSolrResponse(
})
}

async function addCachedItems(bucket: { val: any }, provider: (id: string) => any) {
if (isUndefined(provider)) return bucket
async function addCachedItems(bucket: { val: any }, resolver: IResolver<any>, type: CachedFacetType) {
if (isUndefined(resolver)) return bucket
return {
...bucket,
item: await provider(bucket.val),
item: await resolver(bucket.val),
uid: bucket.val,
}
}

type CacheProviderType = 'newspaper' | 'topic' | 'person' | 'location' | 'year'

const getCacheProviders = (
app: ImpressoApplication
): Record<CacheProviderType, (id: string) => Promise<any> | any> => ({
newspaper: async (id: string) =>
optionalMediaSourceToNewspaper(await app.service('media-sources').getMediaSource(id)),
topic: Topic.getCached,
person: Entity.getCached,
location: Entity.getCached,
year: Year.getCached,
})

/**
* Extract facets from Solr response.
* @param {object} response Solr response
Expand All @@ -114,14 +97,14 @@ export async function getFacetsFromSolrResponse(
) {
const { facets = {} } = response

const cacheProviders = getCacheProviders(app)
const resolvers = buildResolvers(app)

const facetPairs = await Promise.all(
Object.keys(facets).map(async facetLabel => {
if (!facets[facetLabel].buckets) return [facetLabel, facets[facetLabel]]
const cacheProvider = cacheProviders[facetLabel as CacheProviderType]
const resolver = resolvers[facetLabel as CachedFacetType]
const buckets = await Promise.all(
facets[facetLabel].buckets.map(async (b: any) => addCachedItems(b, cacheProvider))
facets[facetLabel].buckets.map(async (b: any) => addCachedItems(b, resolver, facetLabel as CachedFacetType))
)

return [facetLabel, assignIn(clone(facets[facetLabel]), { buckets })]
Expand Down
Loading
Loading