Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IS-1646: Consume huskelapp-status in personoversiktstatus #316

Merged
merged 4 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .nais/naiserator-dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,5 @@ spec:
# Bruk samme dato i enhetens-oversikt-query og index
- name: ARENA_CUTOFF
value: "2023-03-10"
- name: IS_HUSKELAPP_CONSUMER_ENABLED
value: "true"
2 changes: 2 additions & 0 deletions .nais/naiserator-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,5 @@ spec:
# Bruk samme dato i enhetens-oversikt-query og index
- name: ARENA_CUTOFF
value: "2023-03-10"
- name: IS_HUSKELAPP_CONSUMER_ENABLED
value: "false"
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ data class Environment(
port = getEnvVar("REDIS_PORT", "6379").toInt(),
secret = getEnvVar("REDIS_PASSWORD"),
),

val isHuskelappConsumerEnabled: Boolean = getEnvVar("IS_HUSKELAPP_CONSUMER_ENABLED").toBoolean()
)

fun getEnvVar(varName: String, defaultValue: String? = null) =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const val queryGetPersonerWithOppgaveAndOldEnhet =
OR behandlerdialog_ubesvart_ubehandlet = 't'
OR behandlerdialog_avvist_ubehandlet = 't'
OR aktivitetskrav_vurder_stans_ubehandlet = 't'
OR huskelapp_active = 't'
)
AND (tildelt_enhet_updated_at IS NULL OR tildelt_enhet_updated_at <= NOW() - INTERVAL '24 HOURS')
ORDER BY tildelt_enhet_updated_at ASC
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ const val queryPersonOppfolgingstilfelleVirksomhetNoVirksomhetsnavnList =
OR behandlerdialog_ubesvart_ubehandlet = 't'
OR behandlerdialog_avvist_ubehandlet = 't'
OR aktivitetskrav_vurder_stans_ubehandlet = 't'
OR huskelapp_active = 't'
)
ORDER BY PERSON_OPPFOLGINGSTILFELLE_VIRKSOMHET.created_at ASC
LIMIT 1000
Expand Down
45 changes: 45 additions & 0 deletions src/main/kotlin/no/nav/syfo/huskelapp/HuskelappService.kt
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hvordan kan man evt implementere Repository-konseptet i slike consumers der vi ønsker å ha én connection oppe hele tiden? Jeg føler jo at logikken som skjer inne i forEachen hører hjemme i en service 🤷🏼‍♂️

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Måtte kanskje hatt en repository-metode ala createOrUpdatePersonoppgaver(huskelapp: List<Huskelapp>), men lit enig i at den logikken hører mer hjemme her...

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package no.nav.syfo.huskelapp

import no.nav.syfo.application.database.DatabaseInterface
import no.nav.syfo.huskelapp.domain.Huskelapp
import no.nav.syfo.huskelapp.kafka.COUNT_KAFKA_CONSUMER_HUSKELAPP_READ
import no.nav.syfo.personstatus.db.createPersonOversiktStatus
import no.nav.syfo.personstatus.db.getPersonOversiktStatusList
import no.nav.syfo.personstatus.db.updateHuskelappActive
import org.slf4j.Logger
import org.slf4j.LoggerFactory

class HuskelappService(
private val database: DatabaseInterface,
) {
fun processHuskelapp(records: List<Huskelapp>) {
database.connection.use { connection ->
records.forEach { huskelapp ->
val existingPersonOversiktStatus = connection.getPersonOversiktStatusList(
fnr = huskelapp.personIdent.value,
).firstOrNull()

if (existingPersonOversiktStatus == null) {
val personoversiktStatus = huskelapp.toPersonoversiktStatus()
connection.createPersonOversiktStatus(
commit = false,
personOversiktStatus = personoversiktStatus,
)
} else {
connection.updateHuskelappActive(
isHuskelappActive = huskelapp.isActive,
personIdent = huskelapp.personIdent,
)
}

log.info("Received huskelapp with uuid=${huskelapp.uuid}")
COUNT_KAFKA_CONSUMER_HUSKELAPP_READ.increment()
}
connection.commit()
}
}

companion object {
val log: Logger = LoggerFactory.getLogger(this::class.java)
}
}
17 changes: 17 additions & 0 deletions src/main/kotlin/no/nav/syfo/huskelapp/domain/Huskelapp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package no.nav.syfo.huskelapp.domain

import no.nav.syfo.domain.PersonIdent
import no.nav.syfo.personstatus.domain.PersonOversiktStatus
import java.util.UUID

data class Huskelapp(
val uuid: UUID,
val personIdent: PersonIdent,
val isActive: Boolean,
) {
fun toPersonoversiktStatus() = PersonOversiktStatus(
fnr = personIdent.value,
).copy(
huskelappActive = isActive,
)
Comment on lines +12 to +16
Copy link
Contributor Author

@eirikdahlen eirikdahlen Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gjorde sånn i stedet for å "fylle ut" det store objektet, nå som vi har default-values.
Hadde det vært penere med en .create()-metode? I så fall burde man kanskje lage flere, for aktivitetskrav, huskelapp, dialogmøtestatusendring etc etc.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 Hadde nok vært fint og ryddet litt opp i det ja, men får ta det i en egen PR.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package no.nav.syfo.huskelapp.kafka

import no.nav.syfo.huskelapp.HuskelappService
import no.nav.syfo.kafka.KafkaConsumerService
import org.apache.kafka.clients.consumer.ConsumerRecords
import org.apache.kafka.clients.consumer.KafkaConsumer
import org.slf4j.LoggerFactory
import java.time.Duration

class HuskelappConsumer(
private val huskelappService: HuskelappService
) : KafkaConsumerService<KafkaHuskelapp> {

override val pollDurationInMillis: Long = 1000

override fun pollAndProcessRecords(kafkaConsumer: KafkaConsumer<String, KafkaHuskelapp>) {
val records = kafkaConsumer.poll(Duration.ofMillis(pollDurationInMillis))
if (records.count() > 0) {
processRecords(records)
kafkaConsumer.commitSync()
}
}

private fun processRecords(
consumerRecords: ConsumerRecords<String, KafkaHuskelapp>,
) {
val (tombstones, validRecords) = consumerRecords.partition { it.value() == null }

if (tombstones.isNotEmpty()) {
val numberOfTombstones = tombstones.size
log.error("Value of $numberOfTombstones ConsumerRecord are null, most probably due to a tombstone. Contact the owner of the topic if an error is suspected")
COUNT_KAFKA_CONSUMER_HUSKELAPP_TOMBSTONE.increment(numberOfTombstones.toDouble())
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vi sender vel ikke tombstones på denne topicen, så vet ikke om vi trenger dette?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nei, jeg tenker vi kan fjerne det.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Endret nå, prøvde å ta i bruk en requireNoNulls, den kaster feil hvis det plutselig skulle komme null-verdier her.
Returns an original collection containing all the non-null elements, throwing an IllegalArgumentException if there are any null elements.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


huskelappService.processHuskelapp(
records = validRecords.map { it.value().toHuskelapp() }
)
}

companion object {
private val log = LoggerFactory.getLogger(HuskelappConsumer::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package no.nav.syfo.huskelapp.kafka

import io.micrometer.core.instrument.Counter
import no.nav.syfo.metric.METRICS_NS
import no.nav.syfo.metric.METRICS_REGISTRY

const val KAFKA_CONSUMER_HUSKELAPP_BASE = "${METRICS_NS}_kafka_consumer_huskelapp"
const val KAFKA_CONSUMER_HUSKELAPP_READ = "${KAFKA_CONSUMER_HUSKELAPP_BASE}_read"
const val KAFKA_CONSUMER_HUSKELAPP_TOMBSTONE = "${KAFKA_CONSUMER_HUSKELAPP_BASE}_tombstone"

val COUNT_KAFKA_CONSUMER_HUSKELAPP_READ: Counter = Counter.builder(KAFKA_CONSUMER_HUSKELAPP_READ)
.description("Counts the number of reads from topic - huskelapp")
.register(METRICS_REGISTRY)
val COUNT_KAFKA_CONSUMER_HUSKELAPP_TOMBSTONE: Counter = Counter.builder(KAFKA_CONSUMER_HUSKELAPP_TOMBSTONE)
.description("Counts the number of tombstones received from topic - huskelapp")
.register(METRICS_REGISTRY)
44 changes: 44 additions & 0 deletions src/main/kotlin/no/nav/syfo/huskelapp/kafka/HuskelappTask.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package no.nav.syfo.huskelapp.kafka

import no.nav.syfo.application.ApplicationState
import no.nav.syfo.application.database.database
import no.nav.syfo.application.kafka.KafkaEnvironment
import no.nav.syfo.application.kafka.kafkaAivenConsumerConfig
import no.nav.syfo.huskelapp.HuskelappService
import no.nav.syfo.kafka.launchKafkaTask
import no.nav.syfo.util.configuredJacksonMapper
import org.apache.kafka.clients.consumer.ConsumerConfig
import org.apache.kafka.common.serialization.Deserializer
import java.util.*

const val HUSKELAPP_TOPIC =
"teamsykefravr.huskelapp"

fun launchHuskelappConsumer(
applicationState: ApplicationState,
kafkaEnvironment: KafkaEnvironment,
) {
val huskelappService = HuskelappService(
database = database
)
val huskelappConsumer = HuskelappConsumer(
huskelappService = huskelappService,
)
val consumerProperties = Properties().apply {
putAll(kafkaAivenConsumerConfig(kafkaEnvironment = kafkaEnvironment))
this[ConsumerConfig.MAX_POLL_RECORDS_CONFIG] = "1"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Én av gangen, eller skulle vi tatt flere? 🤷🏼‍♂️

Copy link
Contributor

@andersrognstad andersrognstad Oct 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ser vi går for 1 noen steder og 10 andre steder... husker ikke om det var noen spesielle grunner til at man burde begrense til 1 🤔 Kanskje noe samtidighets-problematikk. Går jo an å prøve 10 (eller enda flere) så skrur vi heller ned hvis det blir et problem.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okey 👍🏼

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ja, i dette tilfellet går det sikkert bra med 10 eller flere. Vi bør begrense det til 1 dersom konsumeringen gjør noe mer enn å skrive til databasen, feks lager en ny record på et annet topic eller skriver til MQ eller noe annet "eksternt".

this[ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG] = KafkaHuskelappDeserializer::class.java.canonicalName
}
launchKafkaTask(
applicationState = applicationState,
topic = HUSKELAPP_TOPIC,
consumerProperties = consumerProperties,
kafkaConsumerService = huskelappConsumer
)
}

class KafkaHuskelappDeserializer : Deserializer<KafkaHuskelapp> {
private val mapper = configuredJacksonMapper()
override fun deserialize(topic: String, data: ByteArray): KafkaHuskelapp =
mapper.readValue(data, KafkaHuskelapp::class.java)
}
22 changes: 22 additions & 0 deletions src/main/kotlin/no/nav/syfo/huskelapp/kafka/KafkaHuskelapp.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package no.nav.syfo.huskelapp.kafka

import no.nav.syfo.domain.PersonIdent
import no.nav.syfo.huskelapp.domain.Huskelapp
import java.time.OffsetDateTime
import java.util.UUID

data class KafkaHuskelapp(
val uuid: UUID,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Så at det var UUID i ishuskelapp. Deserialisereren fikser det, eller?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tror det skal gå bra, men vi får teste i dev 💥

val personIdent: String,
val veilederIdent: String,
val tekst: String,
val isActive: Boolean,
val createdAt: OffsetDateTime,
val updatedAt: OffsetDateTime,
) {
fun toHuskelapp() = Huskelapp(
uuid = uuid,
personIdent = PersonIdent(personIdent),
isActive = isActive,
)
}
8 changes: 8 additions & 0 deletions src/main/kotlin/no/nav/syfo/kafka/KafkaModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import no.nav.syfo.application.Environment
import no.nav.syfo.client.azuread.AzureAdClient
import no.nav.syfo.dialogmotekandidat.kafka.launchKafkaTaskDialogmotekandidatEndring
import no.nav.syfo.dialogmotestatusendring.kafka.launchKafkaTaskDialogmoteStatusendring
import no.nav.syfo.huskelapp.kafka.launchHuskelappConsumer
import no.nav.syfo.identhendelse.kafka.launchKafkaTaskIdenthendelse
import no.nav.syfo.oppfolgingstilfelle.kafka.launchKafkaTaskOppfolgingstilfellePerson
import no.nav.syfo.pdlpersonhendelse.kafka.launchKafkaTaskPersonhendelse
Expand Down Expand Up @@ -51,4 +52,11 @@ fun launchKafkaModule(
applicationState = applicationState,
environment = environment,
)

if (environment.isHuskelappConsumerEnabled) {
launchHuskelappConsumer(
applicationState = applicationState,
kafkaEnvironment = environment.kafka,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ data class PersonOversiktStatusDTO(
val aktivitetskravVurderingFrist: LocalDate?,
val behandlerdialogUbehandlet: Boolean,
val aktivitetskravVurderStansUbehandlet: Boolean,
val huskelappActive: Boolean,
)

data class PersonOppfolgingstilfelleDTO(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,22 @@ fun Connection.updateAktivitetskravVurderStans(
it.execute()
}
}

const val queryUpdatePersonOversiktStatusHuskelappActive =
"""
UPDATE PERSON_OVERSIKT_STATUS
SET huskelapp_active = ?, sist_endret = ?
WHERE fnr = ?
"""

fun Connection.updateHuskelappActive(
isHuskelappActive: Boolean,
personIdent: PersonIdent,
) {
this.prepareStatement(queryUpdatePersonOversiktStatusHuskelappActive).use {
it.setBoolean(1, isHuskelappActive)
it.setObject(2, Timestamp.from(Instant.now()))
it.setString(3, personIdent.value)
it.execute()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ const val queryCreatePersonOversiktStatus =
behandlerdialog_svar_ubehandlet,
behandlerdialog_ubesvart_ubehandlet,
behandlerdialog_avvist_ubehandlet,
aktivitetskrav_vurder_stans_ubehandlet
) VALUES (DEFAULT, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
aktivitetskrav_vurder_stans_ubehandlet,
huskelapp_active
) VALUES (DEFAULT, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
RETURNING id
"""

Expand Down Expand Up @@ -107,6 +108,7 @@ fun Connection.createPersonOversiktStatus(
it.setBoolean(28, personOversiktStatus.behandlerdialogUbesvartUbehandlet)
it.setBoolean(29, personOversiktStatus.behandlerdialogAvvistUbehandlet)
it.setBoolean(30, personOversiktStatus.aktivitetskravVurderStansUbehandlet)
it.setBoolean(31, personOversiktStatus.huskelappActive)
it.executeQuery().toList { getInt("id") }.firstOrNull()
} ?: throw SQLException("Creating PersonOversikStatus failed, no rows affected.")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const val queryHentUbehandledePersonerTilknyttetEnhet = """
OR behandlerdialog_ubesvart_ubehandlet = 't'
OR behandlerdialog_avvist_ubehandlet = 't'
OR aktivitetskrav_vurder_stans_ubehandlet = 't'
OR huskelapp_active = 't'
)
);
"""
Expand Down Expand Up @@ -115,6 +116,7 @@ fun ResultSet.toPPersonOversiktStatus(): PPersonOversiktStatus =
behandlerdialogUbesvartUbehandlet = getObject("behandlerdialog_ubesvart_ubehandlet") as Boolean,
behandlerdialogAvvistUbehandlet = getObject("behandlerdialog_avvist_ubehandlet") as Boolean,
aktivitetskravVurderStansUbehandlet = getObject("aktivitetskrav_vurder_stans_ubehandlet") as Boolean,
huskelappActive = getObject("huskelapp_active") as Boolean,
)

fun ResultSet.toVeilederBrukerKnytning(): VeilederBrukerKnytning =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,33 +34,35 @@ data class PPersonOversiktStatus(
val behandlerdialogUbesvartUbehandlet: Boolean,
val behandlerdialogAvvistUbehandlet: Boolean,
val aktivitetskravVurderStansUbehandlet: Boolean,
val huskelappActive: Boolean,
)

fun PPersonOversiktStatus.toPersonOversiktStatus(
personOppfolgingstilfelleVirksomhetList: List<PersonOppfolgingstilfelleVirksomhet>,
) = PersonOversiktStatus(
fnr = this.fnr,
navn = this.navn,
enhet = this.enhet,
veilederIdent = this.veilederIdent,
motebehovUbehandlet = this.motebehovUbehandlet,
oppfolgingsplanLPSBistandUbehandlet = this.oppfolgingsplanLPSBistandUbehandlet,
dialogmotesvarUbehandlet = this.dialogmotesvarUbehandlet,
dialogmotekandidat = this.dialogmotekandidat,
dialogmotekandidatGeneratedAt = this.dialogmotekandidatGeneratedAt,
motestatus = this.motestatus,
motestatusGeneratedAt = this.motestatusGeneratedAt,
latestOppfolgingstilfelle = this.toPersonOppfolgingstilfelle(
fnr = fnr,
navn = navn,
enhet = enhet,
veilederIdent = veilederIdent,
motebehovUbehandlet = motebehovUbehandlet,
oppfolgingsplanLPSBistandUbehandlet = oppfolgingsplanLPSBistandUbehandlet,
dialogmotesvarUbehandlet = dialogmotesvarUbehandlet,
dialogmotekandidat = dialogmotekandidat,
dialogmotekandidatGeneratedAt = dialogmotekandidatGeneratedAt,
motestatus = motestatus,
motestatusGeneratedAt = motestatusGeneratedAt,
latestOppfolgingstilfelle = toPersonOppfolgingstilfelle(
personOppfolgingstilfelleVirksomhetList = personOppfolgingstilfelleVirksomhetList,
),
aktivitetskrav = this.aktivitetskrav?.let { AktivitetskravStatus.valueOf(this.aktivitetskrav) },
aktivitetskravStoppunkt = this.aktivitetskravStoppunkt,
aktivitetskravSistVurdert = this.aktivitetskravUpdatedAt,
aktivitetskravVurderingFrist = this.aktivitetskravVurderingFrist,
behandlerdialogSvarUbehandlet = this.behandlerdialogSvarUbehandlet,
behandlerdialogUbesvartUbehandlet = this.behandlerdialogUbesvartUbehandlet,
behandlerdialogAvvistUbehandlet = this.behandlerdialogAvvistUbehandlet,
aktivitetskravVurderStansUbehandlet = this.aktivitetskravVurderStansUbehandlet,
aktivitetskrav = aktivitetskrav?.let { AktivitetskravStatus.valueOf(aktivitetskrav) },
aktivitetskravStoppunkt = aktivitetskravStoppunkt,
aktivitetskravSistVurdert = aktivitetskravUpdatedAt,
aktivitetskravVurderingFrist = aktivitetskravVurderingFrist,
behandlerdialogSvarUbehandlet = behandlerdialogSvarUbehandlet,
behandlerdialogUbesvartUbehandlet = behandlerdialogUbesvartUbehandlet,
behandlerdialogAvvistUbehandlet = behandlerdialogAvvistUbehandlet,
aktivitetskravVurderStansUbehandlet = aktivitetskravVurderStansUbehandlet,
huskelappActive = huskelappActive,
)

fun PPersonOversiktStatus.toPersonOppfolgingstilfelle(
Expand Down
Loading