diff --git a/src/main/kotlin/no/nav/syfo/consumer/pdl/PdlConsumer.kt b/src/main/kotlin/no/nav/syfo/consumer/pdl/PdlConsumer.kt index 7d1a6369b..8ab48a710 100644 --- a/src/main/kotlin/no/nav/syfo/consumer/pdl/PdlConsumer.kt +++ b/src/main/kotlin/no/nav/syfo/consumer/pdl/PdlConsumer.kt @@ -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().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().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() + 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().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) } diff --git a/src/main/kotlin/no/nav/syfo/consumer/pdl/PdlHentPersonResponse.kt b/src/main/kotlin/no/nav/syfo/consumer/pdl/PdlHentPersonResponse.kt index 8cd1c5848..ea9a97c86 100644 --- a/src/main/kotlin/no/nav/syfo/consumer/pdl/PdlHentPersonResponse.kt +++ b/src/main/kotlin/no/nav/syfo/consumer/pdl/PdlHentPersonResponse.kt @@ -1,6 +1,7 @@ package no.nav.syfo.consumer.pdl data class HentPersonResponse( + val errors: List?, val data: HentPersonData ) @@ -8,20 +9,10 @@ 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, val navn: List @@ -36,3 +27,24 @@ data class Navn( val mellomnavn: String?, val etternavn: String ) + +data class PdlError( + val message: String, + val locations: List, + val path: List?, + 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}" +} diff --git a/src/main/kotlin/no/nav/syfo/consumer/pdl/PdlResponse.kt b/src/main/kotlin/no/nav/syfo/consumer/pdl/PdlResponse.kt deleted file mode 100644 index 5a093790d..000000000 --- a/src/main/kotlin/no/nav/syfo/consumer/pdl/PdlResponse.kt +++ /dev/null @@ -1,37 +0,0 @@ -package no.nav.syfo.consumer.pdl - -import java.io.Serializable - -data class PdlIdentResponse( - val errors: List?, - val data: PdlHentIdenter? -) - -data class PdlHentIdenter( - val hentIdenter: PdlIdenter? -) : Serializable - -data class PdlIdenter( - val identer: List -) : Serializable - -data class PdlIdent( - val ident: String -) : Serializable - -data class PdlError( - val message: String, - val locations: List, - val path: List?, - val extensions: PdlErrorExtension -) - -data class PdlErrorLocation( - val line: Int?, - val column: Int? -) - -data class PdlErrorExtension( - val code: String?, - val classification: String -) diff --git a/src/main/kotlin/no/nav/syfo/metrics/AppMetrics.kt b/src/main/kotlin/no/nav/syfo/metrics/AppMetrics.kt index bfe2bc493..a49049ee1 100644 --- a/src/main/kotlin/no/nav/syfo/metrics/AppMetrics.kt +++ b/src/main/kotlin/no/nav/syfo/metrics/AppMetrics.kt @@ -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) @@ -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()) diff --git a/src/main/kotlin/no/nav/syfo/service/MerVeiledningVarselFinder.kt b/src/main/kotlin/no/nav/syfo/service/MerVeiledningVarselFinder.kt index e43c0da99..0c71e2496 100644 --- a/src/main/kotlin/no/nav/syfo/service/MerVeiledningVarselFinder.kt +++ b/src/main/kotlin/no/nav/syfo/service/MerVeiledningVarselFinder.kt @@ -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 diff --git a/src/main/kotlin/no/nav/syfo/service/SykepengerMaxDateService.kt b/src/main/kotlin/no/nav/syfo/service/SykepengerMaxDateService.kt index 5843add7f..c97db6afd 100644 --- a/src/main/kotlin/no/nav/syfo/service/SykepengerMaxDateService.kt +++ b/src/main/kotlin/no/nav/syfo/service/SykepengerMaxDateService.kt @@ -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) @@ -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) } diff --git a/src/main/kotlin/no/nav/syfo/utils/HttpUtil.kt b/src/main/kotlin/no/nav/syfo/utils/HttpUtil.kt index fcb16b20b..48b0cfa66 100644 --- a/src/main/kotlin/no/nav/syfo/utils/HttpUtil.kt +++ b/src/main/kotlin/no/nav/syfo/utils/HttpUtil.kt @@ -21,7 +21,7 @@ fun httpClient(): HttpClient { } } -fun httpClientWithRetry(): HttpClient { +fun httpClientWithRetry(expectSuccess: Boolean = false): HttpClient { return HttpClient(CIO) { install(HttpRequestRetry) { retryOnExceptionIf(2) { _, cause -> @@ -39,5 +39,6 @@ fun httpClientWithRetry(): HttpClient { install(HttpTimeout) { requestTimeoutMillis = 60000 } + this.expectSuccess = expectSuccess } }