Skip to content

Commit

Permalink
Add a bit more logging and refactor of PDL consumer (#536)
Browse files Browse the repository at this point in the history
  • Loading branch information
AudunSorheim authored Aug 1, 2024
1 parent 5830ff9 commit 0b3e2d8
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 156 deletions.
147 changes: 40 additions & 107 deletions src/main/kotlin/no/nav/syfo/consumer/pdl/PdlConsumer.kt
Original file line number Diff line number Diff line change
@@ -1,141 +1,74 @@
package no.nav.syfo.consumer.pdl

import io.ktor.client.call.*
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.network.sockets.*
import no.nav.syfo.UrlEnv
import no.nav.syfo.auth.AzureAdTokenConsumer
import no.nav.syfo.metrics.COUNT_CALL_PDL_FAIL
import no.nav.syfo.metrics.COUNT_CALL_PDL_SUCCESS
import no.nav.syfo.utils.httpClientWithRetry
import no.nav.syfo.utils.isAlderMindreEnnGittAr
import org.slf4j.LoggerFactory
import java.io.FileNotFoundException

open class PdlConsumer(private val urlEnv: UrlEnv, private val azureAdTokenConsumer: AzureAdTokenConsumer) {
private val client = httpClientWithRetry()
private val httpClient = httpClientWithRetry(expectSuccess = true)
private val log = LoggerFactory.getLogger(PdlConsumer::class.qualifiedName)

open suspend fun getFnr(aktorId: String): String? {
val response = callPdl(IDENTER_QUERY, aktorId)

return when (response?.status) {
HttpStatusCode.OK -> {
val pdlResponse = response.body<PdlIdentResponse>().data?.hentIdenter?.identer?.first()?.ident
pdlResponse
}

HttpStatusCode.NoContent -> {
log.error("Could not get fnr from PDL: No content found in the response body")
null
}

HttpStatusCode.Unauthorized -> {
log.error("Could not get fnr from PDL: Unable to authorize")
null
}

else -> {
log.error("Could not get fnr from PDL: $response")
null
}
suspend fun isBrukerYngreEnnGittMaxAlder(ident: String, maxAlder: Int): Boolean {
val fodselsdato = hentPerson(ident)?.getFodselsdato()
if (fodselsdato == null) {
log.warn("Returnert fødselsdato for en person fra PDL er null. Fortsetter som om bruker er yngre enn $maxAlder år da fødselsdato er ukjent.")
return true
} else {
return isAlderMindreEnnGittAr(fodselsdato, maxAlder)
}
}

suspend fun isBrukerYngreEnnGittMaxAlder(ident: String, maxAlder: Int): Boolean {
val response = callPdl(PERSON_QUERY, ident)
suspend fun hentPerson(
personIdent: String,
): HentPersonData? {
val token = azureAdTokenConsumer.getToken(urlEnv.pdlScope)
val query = getPdlQuery("/pdl/hentPerson.graphql")
val request = PdlRequest(query, Variables(personIdent))

val response: HttpResponse = httpClient.post(urlEnv.pdlUrl) {
setBody(request)
header(HttpHeaders.ContentType, "application/json")
header(HttpHeaders.Authorization, "Bearer $token")
header(PDL_BEHANDLINGSNUMMER_HEADER, BEHANDLINGSNUMMER_VURDERE_RETT_TIL_SYKEPENGER)
}

return when (response?.status) {
when (response.status) {
HttpStatusCode.OK -> {
val fodselsdato = response.body<HentPersonResponse>().data.getFodselsdato()
if (fodselsdato == null) {
log.warn("Returnert fødselsdato for en person fra PDL er null. Fortsetter som om bruker er yngre enn $maxAlder år da fødselsdato er ukjent.")
return true
val pdlPersonReponse = response.body<HentPersonResponse>()
return if (!pdlPersonReponse.errors.isNullOrEmpty()) {
COUNT_CALL_PDL_FAIL.increment()
pdlPersonReponse.errors.forEach {
log.error("Error while requesting person from PersonDataLosningen: ${it.errorMessage()}")
}
null
} else {
return isAlderMindreEnnGittAr(fodselsdato, maxAlder)
COUNT_CALL_PDL_SUCCESS.increment()
pdlPersonReponse.data
}
}

HttpStatusCode.NoContent -> {
log.error("Could not get adressesperre from PDL: No content found in the response body")
true
}

HttpStatusCode.Unauthorized -> {
log.error("Could not get adressesperre from PDL: Unable to authorize")
true
}

else -> {
log.error("Could not get adressesperre from PDL: $response")
true
COUNT_CALL_PDL_FAIL.increment()
log.error("Request with url: $urlEnv.pdlUrl failed with reponse code ${response.status.value}")
return null
}
}
}

open suspend fun hentPerson(fnr: String): HentPersonData? {
val response = callPdl(PERSON_QUERY, fnr)

return when (response?.status) {
HttpStatusCode.OK -> {
val pdlResponse = response.body<HentPersonResponse>().data
pdlResponse
}

HttpStatusCode.NoContent -> {
log.error("Could not get navn from PDL: No content found in the response body")
null
}

HttpStatusCode.Unauthorized -> {
log.error("Could not get navn from PDL: Unable to authorize")
null
}

else -> {
log.error("Could not get fnr from PDL: $response")
null
}
}
}

private suspend fun callPdl(service: String, ident: String): HttpResponse? {
val token = azureAdTokenConsumer.getToken(urlEnv.pdlScope)
val bearerTokenString = "Bearer $token"
val graphQueryResourcePath = "$QUERY_PATH_PREFIX/$service"
val graphQuery =
this::class.java.getResource(graphQueryResourcePath)?.readText()?.replace("[\n\r]", "")
?: throw FileNotFoundException("Could not found resource: $graphQueryResourcePath")
val requestBody = PdlRequest(graphQuery, Variables(ident))
return try {
log.info("Calling PDL")
val response = client.post(urlEnv.pdlUrl) {
headers {
append(PDL_BEHANDLINGSNUMMER_HEADER, BEHANDLINGSNUMMER_VURDERE_RETT_TIL_SYKEPENGER)
append(HttpHeaders.ContentType, ContentType.Application.Json)
append(HttpHeaders.Authorization, bearerTokenString)
}
setBody(requestBody)
}
log.info("Received response from PDL with status: ${response.status}")
response
} catch (e: SocketTimeoutException) {
log.error("SocketTimeoutException while calling PDL ($service): ${e.message}", e)
null
} catch (e: ClientRequestException) {
log.error("ClientRequestException while calling PDL ($service): ${e.message}", e)
null
} catch (e: Exception) {
log.error("Error while calling PDL ($service): ${e.message}", e)
null
}
private fun getPdlQuery(graphQueryResourcePath: String): String {
return this::class.java.getResource(graphQueryResourcePath)?.readText()?.replace("[\n\r]", "")
?: throw FileNotFoundException("Could not found resource: $graphQueryResourcePath")
}

class LocalPdlConsumer(urlEnv: UrlEnv, azureAdTokenConsumer: AzureAdTokenConsumer) :
PdlConsumer(urlEnv, azureAdTokenConsumer) {
override suspend fun getFnr(aktorId: String): String {
return aktorId.substring(0, 11)
}
}
PdlConsumer(urlEnv, azureAdTokenConsumer)
}
32 changes: 22 additions & 10 deletions src/main/kotlin/no/nav/syfo/consumer/pdl/PdlHentPersonResponse.kt
Original file line number Diff line number Diff line change
@@ -1,27 +1,18 @@
package no.nav.syfo.consumer.pdl

data class HentPersonResponse(
val errors: List<PdlError>?,
val data: HentPersonData
)

data class HentPersonData(
val hentPerson: HentPerson
)

fun HentPersonData.getFullNameAsString(): String {
val navn = this.hentPerson.navn.first()

return "${navn.fornavn}${getMellomnavn(navn.mellomnavn)} ${navn.etternavn}"
}

fun HentPersonData.getFodselsdato(): String? {
return this.hentPerson.foedselsdato.first().foedselsdato
}

private fun getMellomnavn(mellomnavn: String?): String {
return if (mellomnavn !== null) " $mellomnavn" else ""
}

data class HentPerson(
val foedselsdato: List<Foedselsdato>,
val navn: List<Navn>
Expand All @@ -36,3 +27,24 @@ data class Navn(
val mellomnavn: String?,
val etternavn: String
)

data class PdlError(
val message: String,
val locations: List<PdlErrorLocation>,
val path: List<String>?,
val extensions: PdlErrorExtension
)

data class PdlErrorLocation(
val line: Int?,
val column: Int?
)

data class PdlErrorExtension(
val code: String?,
val classification: String
)

fun PdlError.errorMessage(): String {
return "${this.message} with code: ${extensions.code} and classification: ${extensions.classification}"
}
37 changes: 0 additions & 37 deletions src/main/kotlin/no/nav/syfo/consumer/pdl/PdlResponse.kt

This file was deleted.

10 changes: 10 additions & 0 deletions src/main/kotlin/no/nav/syfo/metrics/AppMetrics.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ const val METRICS_NS = "esyfovarsel"
const val MER_VEILEDNING_NOTICE_SENT = "${METRICS_NS}_mer_veiledning_notice_sent"
const val SVAR_MOTEBEHOV_NOTICE_SENT = "${METRICS_NS}_svar_motebehov_notice_sent"
const val NOTICE_SENT = "${METRICS_NS}_notice_sent"
const val CALL_PDL_SUCCESS = "${METRICS_NS}_call_pdl_success_count"
const val CALL_PDL_FAIL = "${METRICS_NS}_call_pdl_fail_count"

val METRICS_REGISTRY =
PrometheusMeterRegistry(PrometheusConfig.DEFAULT, CollectorRegistry.defaultRegistry, Clock.SYSTEM)
Expand All @@ -35,6 +37,14 @@ val COUNT_ALL_NOTICE_SENT: Counter = Counter
.description("Counts the number of all types of notice sent")
.register(METRICS_REGISTRY)

val COUNT_CALL_PDL_SUCCESS: Counter = Counter.builder(CALL_PDL_SUCCESS)
.description("Counts the number of successful calls to pdl")
.register(METRICS_REGISTRY)

val COUNT_CALL_PDL_FAIL: Counter = Counter.builder(CALL_PDL_FAIL)
.description("Counts the number of failed calls to pdl")
.register(METRICS_REGISTRY)

fun tellMerVeiledningVarselSendt(varslerSendt: Int) {
COUNT_ALL_NOTICE_SENT.increment(varslerSendt.toDouble())
COUNT_MER_VEILEDNING_NOTICE_SENT.increment(varslerSendt.toDouble())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class MerVeiledningVarselFinder(
}
}

suspend fun isBrukerYngreEnn67Ar(fnr: String): Boolean {
private suspend fun isBrukerYngreEnn67Ar(fnr: String): Boolean {
val storedBirthdateList = databaseAccess.fetchFodselsdatoByFnr(fnr)
val storedBirthdate = if (storedBirthdateList.isNotEmpty()) storedBirthdateList.first() else null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@ import no.nav.syfo.db.storeInfotrygdUtbetaling
import no.nav.syfo.db.storeSpleisUtbetaling
import no.nav.syfo.kafka.consumers.infotrygd.domain.InfotrygdSource
import no.nav.syfo.kafka.consumers.utbetaling.domain.UtbetalingSpleis
import org.slf4j.LoggerFactory
import java.time.LocalDate

class SykepengerMaxDateService(private val databaseInterface: DatabaseInterface, private val pdlConsumer: PdlConsumer) {

private val log = LoggerFactory.getLogger(SykepengerMaxDateService::class.qualifiedName)

suspend fun processUtbetalingSpleisEvent(utbetaling: UtbetalingSpleis) {
val fnr = utbetaling.fødselsnummer
processFodselsdato(fnr)
Expand Down Expand Up @@ -45,6 +48,7 @@ class SykepengerMaxDateService(private val databaseInterface: DatabaseInterface,
private suspend fun processFodselsdato(fnr: String) {
val lagretFodselsdato = databaseInterface.fetchFodselsdatoByFnr(fnr)
if (lagretFodselsdato.isEmpty()) {
log.info("Mangler lagret fødselsdato, henter fra PDL")
val fodselsdato = pdlConsumer.hentPerson(fnr)?.getFodselsdato()
databaseInterface.storeFodselsdato(fnr, fodselsdato)
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/kotlin/no/nav/syfo/utils/HttpUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fun httpClient(): HttpClient {
}
}

fun httpClientWithRetry(): HttpClient {
fun httpClientWithRetry(expectSuccess: Boolean = false): HttpClient {
return HttpClient(CIO) {
install(HttpRequestRetry) {
retryOnExceptionIf(2) { _, cause ->
Expand All @@ -39,5 +39,6 @@ fun httpClientWithRetry(): HttpClient {
install(HttpTimeout) {
requestTimeoutMillis = 60000
}
this.expectSuccess = expectSuccess
}
}

0 comments on commit 0b3e2d8

Please sign in to comment.