Skip to content

Commit

Permalink
fix: sort on all languages for anbefaltTerm, case-insensitive
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffreiffers committed Oct 14, 2024
1 parent 8f4f10f commit 6d4f455
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 40 deletions.
12 changes: 6 additions & 6 deletions src/main/kotlin/no/fdk/concept_catalog/model/SortField.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package no.fdk.concept_catalog.model

enum class SortFieldEnum(val label: String) {
SIST_ENDRET("sistEndret"),
ANBEFALT_TERM_NB("anbefaltTerm.nb"),
enum class SortFieldEnum {
SIST_ENDRET,
ANBEFALT_TERM,
}

enum class SortDirection(val label: String) {
ASC("ASC"),
DESC("DESC"),
enum class SortDirection {
ASC,
DESC,
}

class SortField(
Expand Down
130 changes: 108 additions & 22 deletions src/main/kotlin/no/fdk/concept_catalog/service/ConceptSearchService.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package no.fdk.concept_catalog.service

import co.elastic.clients.elasticsearch._types.ScriptSortType
import co.elastic.clients.elasticsearch._types.SortOrder
import co.elastic.clients.elasticsearch._types.query_dsl.Operator
import co.elastic.clients.elasticsearch._types.query_dsl.TextQueryType
Expand All @@ -18,19 +19,38 @@ class ConceptSearchService(
private val elasticsearchOperations: ElasticsearchOperations
) {

private var anbefaltTermSortScript: String =
"""
if (doc.containsKey('anbefaltTerm.navn.nb.keyword') && !doc['anbefaltTerm.navn.nb.keyword'].empty) {
return doc['anbefaltTerm.navn.nb.keyword'].value.toLowerCase();
} else if (doc.containsKey('anbefaltTerm.navn.nn.keyword') && !doc['anbefaltTerm.navn.nn.keyword'].empty) {
return doc['anbefaltTerm.navn.nn.keyword'].value.toLowerCase();
} else if (doc.containsKey('anbefaltTerm.navn.en.keyword') && !doc['anbefaltTerm.navn.en.keyword'].empty) {
return doc['anbefaltTerm.navn.en.keyword'].value.toLowerCase();
} else {
return '';
}
"""

fun suggestConcepts(orgNumber: String, published: Boolean?, query: String): SearchHits<CurrentConcept> =
elasticsearchOperations.search(
suggestionQuery(orgNumber, published, query),
CurrentConcept::class.java,
IndexCoordinates.of("concepts-current")
)

fun searchCurrentConcepts(orgNumber: String, search: SearchOperation): SearchHits<CurrentConcept> =
elasticsearchOperations.search(
search.toElasticQuery(orgNumber),
CurrentConcept::class.java,
IndexCoordinates.of("concepts-current")
)
fun searchCurrentConcepts(orgNumber: String, search: SearchOperation): SearchHits<CurrentConcept> {
try {
val query = search.toElasticQuery(orgNumber)
return elasticsearchOperations.search(
query,
CurrentConcept::class.java,
IndexCoordinates.of("concepts-current")
)
} catch (e: Exception) {
throw RuntimeException("Failed to search for concepts", e)

Check warning on line 51 in src/main/kotlin/no/fdk/concept_catalog/service/ConceptSearchService.kt

View check run for this annotation

Codecov / codecov/patch

src/main/kotlin/no/fdk/concept_catalog/service/ConceptSearchService.kt#L50-L51

Added lines #L50 - L51 were not covered by tests
}
}

private fun suggestionQuery(orgNumber: String, published: Boolean?, query: String): Query {
val builder = NativeQuery.builder()
Expand All @@ -40,14 +60,92 @@ class ConceptSearchService(
}
}
builder.withQuery { queryBuilder ->
queryBuilder.matchPhrasePrefix { matchBuilder ->
matchBuilder.query(query)
.field("anbefaltTerm.navn.nb")
queryBuilder.bool { boolBuilder ->
boolBuilder.should { shouldBuilder1 ->
shouldBuilder1.matchPhrasePrefix { matchBuilder ->
matchBuilder.query(query)
.field("anbefaltTerm.navn.nb")
}
}
boolBuilder.should { shouldBuilder2 ->
shouldBuilder2.bool { nnFieldCheck ->
nnFieldCheck.mustNot { mustNotBuilder ->
mustNotBuilder.exists { existsBuilder ->
existsBuilder.field("anbefaltTerm.navn.nb")
}
}
nnFieldCheck.should { shouldBuilder ->
shouldBuilder.matchPhrasePrefix { matchBuilder ->
matchBuilder.query(query)
.field("anbefaltTerm.navn.nn")
}
}
}
}
boolBuilder.should { shouldBuilder3 ->
shouldBuilder3.bool { enFieldCheck ->
enFieldCheck.mustNot { mustNotBuilder1 ->
mustNotBuilder1.exists { existsBuilder1 ->
existsBuilder1.field("anbefaltTerm.navn.nb")
}
}
enFieldCheck.mustNot { mustNotBuilder2 ->
mustNotBuilder2.exists { existsBuilder2 ->
existsBuilder2.field("anbefaltTerm.navn.nn")
}
}
enFieldCheck.should { shouldBuilder ->
shouldBuilder.matchPhrasePrefix { matchBuilder ->
matchBuilder.query(query)
.field("anbefaltTerm.navn.en")
}
}
}
}
}
}
builder.withSort { sortBuilder ->
sortBuilder.script { scriptSortBuilder ->
scriptSortBuilder
.type(ScriptSortType.String)
.order(SortOrder.Asc)
.script { scriptBuilder ->
scriptBuilder.inline { inlineScriptBuilder ->
inlineScriptBuilder
.lang("painless")
.source(anbefaltTermSortScript)
}
}
}
}
return builder.build()
}

private fun SortField.buildSort(builder: NativeQueryBuilder) {
if (field == SortFieldEnum.ANBEFALT_TERM) {
builder.withSort { sortBuilder ->
sortBuilder.script { scriptSortBuilder ->
scriptSortBuilder
.type(ScriptSortType.String)
.order(sortDirection())
.script { scriptBuilder ->
scriptBuilder.inline { inlineScriptBuilder ->
inlineScriptBuilder
.lang("painless")
.source(anbefaltTermSortScript)
}
}
}
}
} else {
builder.withSort { sortBuilder ->
sortBuilder.field { fieldBuilder ->
fieldBuilder.field("endringslogelement.endringstidspunkt").order(sortDirection())
}
}
}
}

private fun SearchOperation.toElasticQuery(orgNumber: String): Query {
val builder = NativeQuery.builder()
builder.withFilter { queryBuilder ->
Expand All @@ -59,13 +157,7 @@ class ConceptSearchService(
)
}
}
if (sort != null) {
builder.withSort { sortBuilder ->
sortBuilder.field { fieldBuilder ->
fieldBuilder.field(sort.sortField()).order(sort.sortDirection())
}
}
}
sort?.buildSort(builder)
if (!query.isNullOrBlank()) builder.addFieldsQuery(fields, query)
builder.withPageable(Pageable.ofSize(pagination.getSize()).withPage(pagination.getPage()))

Expand Down Expand Up @@ -105,12 +197,6 @@ class ConceptSearchService(
else -> SortOrder.Desc
}

private fun SortField.sortField(): String =
when (field) {
SortFieldEnum.ANBEFALT_TERM_NB -> "anbefaltTerm.navn.nb.keyword"
else -> "endringslogelement.endringstidspunkt"
}

private fun QueryFields.exactPaths(): List<String> =
listOf(
if (anbefaltTerm) languagePaths("anbefaltTerm.navn", 30)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,6 @@ class ConceptService(

fun searchConcepts(orgNumber: String, search: SearchOperation): Paginated {
val hits = conceptSearchService.searchCurrentConcepts(orgNumber, search)

return hits.map { it.content }
.map { it.toDBO() }
.map { it.withHighestVersionDTO() }
Expand Down
25 changes: 23 additions & 2 deletions src/test/kotlin/no/fdk/concept_catalog/contract/SearchConcepts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -572,11 +572,30 @@ class SearchConcepts : ApiTestContext() {
assertEquals(listOf(BEGREP_2, BEGREP_0, BEGREP_1), result.hits)
}

@Test
fun `Query returns sorted results ordered by anbefaltTerm ascending`() {
val searchOp = SearchOperation(
query = "",
sort = SortField(field = SortFieldEnum.ANBEFALT_TERM, direction = SortDirection.ASC)
)
val rsp = authorizedRequest(
"/begreper/search?orgNummer=123456789",
port, mapper.writeValueAsString(searchOp), JwtToken(Access.ORG_WRITE).toString(),
HttpMethod.POST
)
assertEquals(HttpStatus.OK.value(), rsp["status"])

val result: Paginated = mapper.readValue(rsp["body"] as String)
assertEquals(BEGREP_0.id, result.hits[0].id)
assertEquals(BEGREP_1.id, result.hits[1].id)
assertEquals(BEGREP_2.id, result.hits[2].id)
}

@Test
fun `Query returns sorted results ordered by anbefaltTerm descending`() {
val searchOp = SearchOperation(
query = "",
sort = SortField(field = SortFieldEnum.ANBEFALT_TERM_NB, direction = SortDirection.DESC)
sort = SortField(field = SortFieldEnum.ANBEFALT_TERM, direction = SortDirection.DESC)
)
val rsp = authorizedRequest(
"/begreper/search?orgNummer=123456789",
Expand All @@ -586,7 +605,9 @@ class SearchConcepts : ApiTestContext() {
assertEquals(HttpStatus.OK.value(), rsp["status"])

val result: Paginated = mapper.readValue(rsp["body"] as String)
assertEquals(listOf(BEGREP_0, BEGREP_2, BEGREP_1), result.hits)
assertEquals(BEGREP_2.id, result.hits[0].id)
assertEquals(BEGREP_1.id, result.hits[1].id)
assertEquals(BEGREP_0.id, result.hits[2].id)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,19 +66,19 @@ class SuggestConcepts : ApiTestContext() {
assertEquals(HttpStatus.OK.value(), rsp["status"])

val expected = listOf(
Suggestion(
id = BEGREP_2.id!!,
originaltBegrep = BEGREP_2.originaltBegrep!!,
erPublisert = BEGREP_2.erPublisert,
anbefaltTerm = BEGREP_2.anbefaltTerm,
definisjon = BEGREP_2.definisjon?.copy(kildebeskrivelse = null)),
Suggestion(
id = BEGREP_1.id!!,
originaltBegrep = BEGREP_1.originaltBegrep!!,
erPublisert = BEGREP_1.erPublisert,
anbefaltTerm = BEGREP_1.anbefaltTerm,
definisjon = BEGREP_1.definisjon?.copy(kildebeskrivelse = null)
)
),
Suggestion(
id = BEGREP_2.id!!,
originaltBegrep = BEGREP_2.originaltBegrep!!,
erPublisert = BEGREP_2.erPublisert,
anbefaltTerm = BEGREP_2.anbefaltTerm,
definisjon = BEGREP_2.definisjon?.copy(kildebeskrivelse = null))
)

val result: List<Suggestion> = mapper.readValue(rsp["body"] as String)
Expand Down
4 changes: 2 additions & 2 deletions src/test/kotlin/no/fdk/concept_catalog/utils/TestData.kt
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ val BEGREP_2 = Begrep(
gjeldendeRevisjon = null,
status = Status.HOERING,
statusURI = "http://publications.europa.eu/resource/authority/concept-status/CANDIDATE",
anbefaltTerm = Term(navn = mapOf(Pair("nb", "Begrep 2"))),
anbefaltTerm = Term(navn = mapOf(Pair("nn", "begrep 2"))),
tillattTerm = mapOf(Pair("nb", listOf("Lorem ipsum"))),
ansvarligVirksomhet = Virksomhet(
id = "123456789"
Expand Down Expand Up @@ -241,7 +241,7 @@ val BEGREP_3 = Begrep(
erPublisert = true,
publiseringsTidspunkt = ZonedDateTime.of(2020, 1, 2, 12,0,0,0, ZoneId.of("Europe/Oslo")).toInstant(),
gjeldendeRevisjon = null,
anbefaltTerm = Term(navn = mapOf(Pair("nn", "Begrep 3"))),
anbefaltTerm = Term(navn = mapOf(Pair("nb", ""), Pair("nn", "Begrep 3"))),
definisjon = Definisjon(
tekst = mapOf(Pair("nb", "definisjon")),
kildebeskrivelse = Kildebeskrivelse(
Expand Down

0 comments on commit 6d4f455

Please sign in to comment.