Skip to content

Commit

Permalink
Continue refactor to wrappers round search client to make testing easier
Browse files Browse the repository at this point in the history
  • Loading branch information
chrissearle committed Nov 20, 2024
1 parent d906553 commit eafc62f
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 188 deletions.
8 changes: 4 additions & 4 deletions src/main/kotlin/no/java/conf/plugins/Search.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import com.jillesvangurp.ktsearch.KtorRestClient
import com.jillesvangurp.ktsearch.SearchClient
import io.github.oshai.kotlinlogging.KotlinLogging
import io.ktor.server.application.Application
import io.ktor.server.application.call
import io.ktor.server.request.receiveNullable
import io.ktor.server.response.respondText
import io.ktor.server.routing.get
Expand All @@ -16,6 +15,7 @@ import no.java.conf.model.search.TextSearchRequest
import no.java.conf.service.SearchService
import no.java.conf.service.search.ElasticIndexer
import no.java.conf.service.search.ElasticIngester
import no.java.conf.service.search.ElasticSearcher

private val logger = KotlinLogging.logger {}

Expand Down Expand Up @@ -50,24 +50,24 @@ fun Application.searchClient() =
)

fun searchService(
searchClient: SearchClient,
indexer: ElasticIndexer,
ingester: ElasticIngester,
searcher: ElasticSearcher,
skipIndex: Boolean
) = SearchService(
client = searchClient,
indexer = indexer,
ingester = ingester,
searcher = searcher,
skipIndex = skipIndex,
)

fun Application.searchService(): SearchService {
val searchClient = searchClient()

return searchService(
searchClient = searchClient,
indexer = ElasticIndexer(searchClient),
ingester = ElasticIngester(searchClient),
searcher = ElasticSearcher(searchClient),
skipIndex =
environment.config
.property("elastic.skipindex")
Expand Down
175 changes: 4 additions & 171 deletions src/main/kotlin/no/java/conf/service/SearchService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,17 @@ package no.java.conf.service
import arrow.core.raise.Raise
import arrow.core.raise.ensure
import arrow.core.raise.ensureNotNull
import com.jillesvangurp.jsondsl.json
import com.jillesvangurp.ktsearch.Aggregations
import com.jillesvangurp.ktsearch.SearchClient
import com.jillesvangurp.ktsearch.count
import com.jillesvangurp.ktsearch.parseHits
import com.jillesvangurp.ktsearch.parsedBuckets
import com.jillesvangurp.ktsearch.search
import com.jillesvangurp.ktsearch.termsResult
import com.jillesvangurp.searchdsls.querydsl.SearchDSL
import com.jillesvangurp.searchdsls.querydsl.TermsAgg
import com.jillesvangurp.searchdsls.querydsl.agg
import com.jillesvangurp.searchdsls.querydsl.bool
import com.jillesvangurp.searchdsls.querydsl.exists
import com.jillesvangurp.searchdsls.querydsl.nested
import com.jillesvangurp.searchdsls.querydsl.simpleQueryString
import com.jillesvangurp.searchdsls.querydsl.terms
import io.github.oshai.kotlinlogging.KotlinLogging
import no.java.conf.model.AggregationsNotFound
import no.java.conf.model.ApiError
import no.java.conf.model.IndexNotReady
import no.java.conf.model.SearchMissing
import no.java.conf.model.search.AggregateResponse
import no.java.conf.model.search.FormatAggregate
import no.java.conf.model.search.LanguageAggregate
import no.java.conf.model.search.SearchResponse
import no.java.conf.model.search.SessionResponse
import no.java.conf.model.search.TextSearchRequest
import no.java.conf.model.search.VideoSearchResponse
import no.java.conf.model.search.YearAggregate
import no.java.conf.model.search.hasFilter
import no.java.conf.model.search.hasQuery
import no.java.conf.model.sessions.Session
import no.java.conf.service.search.ElasticIndexer
import no.java.conf.service.search.ElasticIngester
import no.java.conf.service.search.ElasticSearcher
import kotlin.time.measureTimedValue

private const val INDEX_NAME = "javazone"
Expand All @@ -50,9 +27,9 @@ enum class State {
}

class SearchService(
private val client: SearchClient,
private val indexer: ElasticIndexer,
private val ingester: ElasticIngester,
private val searcher: ElasticSearcher,
private val skipIndex: Boolean,
) {
private var readyState = State.NEW
Expand Down Expand Up @@ -105,15 +82,7 @@ class SearchService(
suspend fun allVideos(): List<VideoSearchResponse> {
ensureReady(readyState)

val docCount = client.totalDocs()

val sessions =
client.search(INDEX_NAME) {
resultSize = docCount.toInt()
query = exists("video")
}

return sessions.parseHits<VideoSearchResponse>().sortedBy { -it.year }
return searcher.allVideos(INDEX_NAME)
}

fun state(): State = readyState
Expand All @@ -126,27 +95,7 @@ class SearchService(

ensureReady(readyState)

val docCount = client.totalDocs()

val searchResult =
client.search(INDEX_NAME) {
addQuery(docCount, searchRequest)

addLanguageAggregate(docCount)
addFormatAggregate(docCount)
addYearAggregate(docCount)

logger.debug { this.json() }
}

ensure(searchResult.aggregations != null) {
raise(AggregationsNotFound)
}

return SearchResponse(
searchResult.parseHits<SessionResponse>().sortedBy { -it.year },
searchResult.aggregations!!.buildResponse()
)
return searcher.textSearch(INDEX_NAME, searchRequest)
}
}

Expand All @@ -155,119 +104,3 @@ private fun Raise<ApiError>.ensureReady(readyState: State) {
raise(IndexNotReady)
}
}

private fun Aggregations.buildResponse() =
AggregateResponse(
languages =
this
.termsResult("by-language")
.parsedBuckets
.map { LanguageAggregate(it.parsed.key, it.parsed.docCount) },
formats =
this
.termsResult("by-format")
.parsedBuckets
.map { FormatAggregate(it.parsed.key, it.parsed.docCount) },
years =
this
.termsResult("by-year")
.parsedBuckets
.map { YearAggregate(it.parsed.key.toInt(), it.parsed.docCount) }
.sortedBy { -it.year },
)

private fun SearchDSL.addLanguageAggregate(docCount: Long) {
this.agg(
"by-language",
TermsAgg(Session::language.name) {
aggSize = docCount
minDocCount = 1
}
)
}

private fun SearchDSL.addFormatAggregate(docCount: Long) {
this.agg(
"by-format",
TermsAgg(Session::format.name) {
aggSize = docCount
minDocCount = 1
}
)
}

private fun SearchDSL.addYearAggregate(docCount: Long) {
this.agg(
"by-year",
TermsAgg(Session::year) {
aggSize = docCount
minDocCount = 1
}
)
}

private fun SearchDSL.addQuery(
docCount: Long,
searchRequest: TextSearchRequest
) {
logger.debug { "Building query for $searchRequest" }

resultSize =
when (!searchRequest.hasQuery() && !searchRequest.hasFilter()) {
true -> 0
false -> docCount.toInt()
}

val queryString =
when {
searchRequest.hasQuery() -> searchRequest.query
searchRequest.hasFilter() -> "*"
else -> null
}

val textQuery =
queryString?.let { q ->
bool {
should(
simpleQueryString(q, "title", "abstract", "intendedAudience"),
nested {
path = "speakers"
query = simpleQueryString(q, "speakers.name", "speakers.bio")
}
)
}
}

val yearQuery =
searchRequest.years.ifEmpty { null }?.let { years ->
terms(
field = "year",
values = years.map { it.toString() }.toTypedArray()
)
}

val languageQuery =
searchRequest.languages.ifEmpty { null }?.let { languages ->
terms(
field = "language",
values = languages.toTypedArray()
)
}

val formatQuery =
searchRequest.formats.ifEmpty { null }?.let { formats ->
terms(
field = "format",
values = formats.toTypedArray()
)
}

this.query =
bool {
must(
listOfNotNull(textQuery, yearQuery, languageQuery, formatQuery)
)
}
}

private suspend fun SearchClient.totalDocs() = this.count(INDEX_NAME).count
Loading

0 comments on commit eafc62f

Please sign in to comment.