Files
term_portal/express/models/helpers/search/index.js
T

227 lines
6.2 KiB
JavaScript

const Portal = require('../../portal')
const { getSlovenianLanguageId } = require('..')
const slCollator = new Intl.Collator('sl', { sensitivity: 'base' })
// Return a new array, with slovenian language removed and isSlovenianSource set to true if present.
exports.separateSlovenianLanguage = async languages => {
const slovenianLanguageId = await getSlovenianLanguageId()
let isSlovenianSource = false
const foreignLanguages = languages.filter(language => {
if (language !== slovenianLanguageId) return true
isSlovenianSource = true
return false
})
return [foreignLanguages, isSlovenianSource]
}
// Simplify and enhance search engine's hits output into useful entries object.
exports.prepareEntries = hits => {
const entriesByCategory = hits.body.hits.hits.reduce(
(agg, hit) => {
const entry = hit._source
switch (hit._score) {
case 6:
case 5:
agg.byTerm.push(entry)
break
case 4:
case 3:
agg.byForeignTerm.push(entry)
break
case 2:
case 1:
agg.byOther.push(entry)
break
default:
agg.uncategorized.push(entry)
}
return agg
},
{ byTerm: [], byForeignTerm: [], byOther: [], uncategorized: [] }
)
return entriesByCategory
}
// Transform search engine's aggregation raw output into correct and friendly format.
exports.prepareAggregation = async (aggregationRaw, determinedLanguage) => {
const { aggregations, hits } = aggregationRaw.body
const hitsCount = hits.total.value
if (!hitsCount) return
const primaryDomainIds = aggregations.primaryDomains.buckets.map(
bucket => bucket.key
)
const dictionaryIds = aggregations.dictionaries.buckets.map(
bucket => bucket.key
)
const languageIdSet = new Set()
const refinedSourceLanguageBuckets = []
aggregations.foreign.targetLanguages.buckets.forEach(bucket =>
languageIdSet.add(bucket.key)
)
const slovenianHitCount = aggregations.slovenianHits?.doc_count
// "Match all" aggregation returns no slovenian hits and source languages.
if (slovenianHitCount) {
const slovenianLanguageId = await getSlovenianLanguageId()
const slovenianBucket = {
key: slovenianLanguageId,
doc_count: slovenianHitCount
}
let wasSlovenianBucketInserted = false
aggregations.foreign.foreignHits.sourceLanguages.buckets.forEach(bucket => {
if (!wasSlovenianBucketInserted && bucket.doc_count < slovenianHitCount) {
languageIdSet.add(slovenianLanguageId)
refinedSourceLanguageBuckets.push(slovenianBucket)
wasSlovenianBucketInserted = true
}
languageIdSet.add(bucket.key)
refinedSourceLanguageBuckets.push(bucket)
})
if (!wasSlovenianBucketInserted) {
languageIdSet.add(slovenianLanguageId)
refinedSourceLanguageBuckets.push(slovenianBucket)
}
} else if (slovenianHitCount === 0) {
aggregations.foreign.foreignHits.sourceLanguages.buckets.forEach(bucket => {
languageIdSet.add(bucket.key)
refinedSourceLanguageBuckets.push(bucket)
})
}
const languageIds = [...languageIdSet]
const names = await Portal.getSearchAggregateNames(
primaryDomainIds,
dictionaryIds,
languageIds,
determinedLanguage
)
const aggregation = {
sourceLanguages: refinedSourceLanguageBuckets.map(
({ key: id, doc_count: hits }) => {
return { id, name: names.languages[id], hits }
}
),
targetLanguages: aggregations.foreign.targetLanguages.buckets.map(
({ key: id, doc_count: hits }) => {
return { id, name: names.languages[id], hits }
}
),
primaryDomains: aggregations.primaryDomains.buckets.map(
({ key: id, doc_count: hits }) => {
return { id, name: names.primaryDomains[id], hits }
}
),
dictionaries: aggregations.dictionaries.buckets.map(
({ key: id, doc_count: hits }) => {
return { id, name: names.dictionaries[id], hits }
}
),
sources: aggregations.sources.buckets.map(
({ key: id, doc_count: hits }) => {
return { id, name: id, hits }
}
)
}
sortAggregation(aggregation)
return aggregation
}
// Enhance aggregation for display in search filters.
exports.prepareSeachFilterData = (aggregation, filters) => {
const enhancedAggregation = {}
if (aggregation) {
for (const category of Object.keys(aggregation)) {
const enhancedCategoryMembers = aggregation[category].map(member => {
if (filters[category].includes(member.id)) member.checked = true
return member
})
enhancedAggregation[category] = enhancedCategoryMembers
}
}
return enhancedAggregation
}
// Maps editor search api field names into search engine ones.
exports.fieldMap = {
term: 'term',
synonyms: 'synonyms',
label: 'label',
definition: 'definition',
other: 'other',
domainLabels: 'domainLabels',
links: 'links',
foreignTerms: 'foreignEntries.terms',
foreignSynonyms: 'foreignEntries.synonyms',
foreignDefinition: 'foreignEntries.definition'
}
// Generate search engine DSL wildcard query fragment for a single field.
exports.queryForField = (field, searchTokens) => {
return {
bool: {
filter: searchTokens.map(token => {
return {
wildcard: {
[field]: {
value: token
}
}
}
})
}
}
}
// Simplify and enhance search engine's editor hits output into useful entries list.
exports.prepareEditorEntries = hits => {
const entries = hits.body.hits.hits.map(hit => {
const entry = hit._source
entry.foreignTerm = hit.fields['foreignEntries.terms']?.[0]
entry.commentActivityIndicator = hit.fields.commentActivityIndicator[0]
return entry
})
return entries
}
function sortAggregation(aggregation) {
for (const categoryBuckets of Object.values(aggregation)) {
categoryBuckets.sort(bucketCompareFn)
}
}
function bucketCompareFn(bucketA, bucketB) {
if (bucketA.hits !== bucketB.hits) return 0
return slCollator.compare(bucketA.name, bucketB.name)
}
// Simplify search engine's consultancy hits output into useful entries list.
exports.prepareConsultancyEntries = hits => {
const entries = hits.body.hits.hits.map(hit => hit._source)
return entries
}