Skip to content

Commit

Permalink
Relax ISO8601DateTime skalar ved å støtte flere formater
Browse files Browse the repository at this point in the history
Endringer oppsummert:
* ISO8601DateTime skalar i produsent api støtter nå input både med og uten offset
  * uten offset antas oslo lokal tid
* Input typen til kalenderavtale startTidspunkt og sluttTidspunkt i mutations endres til å bruke ISO8601DateTime i stedet for ISO8601LocalDateTime
  * Bakgrunn for dette er å gjøre det lettere å bruke nySak og nyKalenderavtale sammen. Slippe å forholde seg til to formater på tidspunkt.

Detaljer:

Under panseret brukte denne skalaren ISO_OFFSET_DATE_TIME som parser. Som medførte at input som hadde denne skalaren som type alltid måtte ha med offset.

Her endres skalaren til å bruke ISO_DATE_TIME som parser samt at den parsede verdien konverteres til en OffsetDateTime i UTC tid. Dersom input kommer uten offset så antas input å representere oslo lokal tid. Dette er reflektert i API dokumentasjonen.

Typen til kalenderavtale starttidspunkt og slutttidspunkt endres fra ISO8601LocalDateTime til ISO8601DateTime. Hovedmotivasjonen her er at det er dårlig DX å tvinges til å forholde seg til to formater på tidspunkt i mutations man må gjøre sammen. E.g. nySak & nyKalenderavtale.

Denne endringen endrer kun skalarens oppførsel på en bakoverkompatibel måte.

Det er derimot en breaking change for brukere av kalenderavtale mutations, men dette er ikke tatt i bruk i prod enda.
Vurderer det som verdt å endre kontrakten nå, før det er i bruk i prod.
Utrulling av endring blir koordinert med teamet som holder på å ta dette i bruk i dev.
  • Loading branch information
kenglxn committed Dec 17, 2024
1 parent 2239996 commit ef8dda9
Show file tree
Hide file tree
Showing 8 changed files with 74 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package no.nav.arbeidsgiver.notifikasjon.infrastruktur

import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import com.zaxxer.hikari.metrics.prometheus.PrometheusMetricsTrackerFactory
import kotlinx.coroutines.*
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.json.laxObjectMapper
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.json.writeValueAsStringSupportingTypeInfoInCollections
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package no.nav.arbeidsgiver.notifikasjon.infrastruktur.graphql
import graphql.language.StringValue
import graphql.schema.*
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.ISO8601Period
import no.nav.arbeidsgiver.notifikasjon.tid.atOsloAsOffsetDateTime
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoField
import java.time.temporal.TemporalAccessor
import java.time.temporal.TemporalQuery

Expand Down Expand Up @@ -64,8 +67,14 @@ object Scalars {
val ISO8601DateTime: GraphQLScalarType =
dateTimeScalar(
name = "ISO8601DateTime",
dateTimeFormatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME,
temporalQuery = OffsetDateTime::from
dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME,
temporalQuery = {
if (it.isSupported(ChronoField.OFFSET_SECONDS)) {
OffsetDateTime.from(it).withOffsetSameInstant(ZoneOffset.UTC)
} else {
LocalDateTime.from(it).atOsloAsOffsetDateTime().withOffsetSameInstant(ZoneOffset.UTC)
}
}
)

val ISO8601LocalDateTime: GraphQLScalarType =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import no.nav.arbeidsgiver.notifikasjon.produsent.ProdusentRepository
import no.nav.arbeidsgiver.notifikasjon.produsent.api.MutationKalenderavtale.KalenderavtaleTilstand.AVLYST
import no.nav.arbeidsgiver.notifikasjon.produsent.api.MutationKalenderavtale.KalenderavtaleTilstand.VENTER_SVAR_FRA_ARBEIDSGIVER
import no.nav.arbeidsgiver.notifikasjon.produsent.tilProdusentModel
import no.nav.arbeidsgiver.notifikasjon.tid.inOsloLocalDateTime
import java.time.Instant
import java.time.LocalDateTime
import java.time.OffsetDateTime
import java.util.*

Expand All @@ -40,10 +40,10 @@ internal class MutationKalenderavtale(
tekst = env.getTypedArgument<String>("tekst"),
lenke = env.getTypedArgument<String>("lenke"),
mottakere = env.getTypedArgument<List<MottakerInput>>("mottakere"),
startTidspunkt = env.getTypedArgument<LocalDateTime>("startTidspunkt"),
sluttTidspunkt = env.getTypedArgumentOrNull<LocalDateTime>("sluttTidspunkt"),
startTidspunkt = env.getTypedArgument<OffsetDateTime>("startTidspunkt"),
sluttTidspunkt = env.getTypedArgumentOrNull<OffsetDateTime>("sluttTidspunkt"),
lokasjon = env.getTypedArgumentOrNull<NyKalenderavtaleInput.LokasjonInput?>("lokasjon"),
erDigitalt = env.getTypedArgumentOrNull<Boolean>("erDigitalt") ?: false,
erDigitalt = env.getTypedArgumentOrNull<Boolean>("erDigitalt") == true,
tilstand = env.getTypedArgumentOrDefault<KalenderavtaleTilstand>("tilstand") { VENTER_SVAR_FRA_ARBEIDSGIVER },
eksterneVarsler = env.getTypedArgumentOrDefault<List<EksterntVarselInput>>("eksterneVarsler") { emptyList() },
paaminnelse = env.getTypedArgumentOrNull<PaaminnelseInput>("paaminnelse"),
Expand All @@ -60,8 +60,8 @@ internal class MutationKalenderavtale(
nyTilstand = env.getTypedArgumentOrNull<KalenderavtaleTilstand>("nyTilstand"),
nyTekst = env.getTypedArgumentOrNull<String>("nyTekst"),
nyLenke = env.getTypedArgumentOrNull<String>("nyLenke"),
nyttStartTidspunkt = env.getTypedArgumentOrNull<LocalDateTime>("nyttStartTidspunkt"),
nyttSluttTidspunkt = env.getTypedArgumentOrNull<LocalDateTime>("nyttSluttTidspunkt"),
nyttStartTidspunkt = env.getTypedArgumentOrNull<OffsetDateTime>("nyttStartTidspunkt"),
nyttSluttTidspunkt = env.getTypedArgumentOrNull<OffsetDateTime>("nyttSluttTidspunkt"),
nyLokasjon = env.getTypedArgumentOrNull<NyKalenderavtaleInput.LokasjonInput?>("nyLokasjon"),
nyErDigitalt = env.getTypedArgumentOrNull<Boolean>("nyErDigitalt"),
eksterneVarsler = env.getTypedArgumentOrDefault<List<EksterntVarselInput>>("eksterneVarsler") { emptyList() }.ifEmpty { null },
Expand All @@ -81,8 +81,8 @@ internal class MutationKalenderavtale(
nyTilstand = env.getTypedArgumentOrNull<KalenderavtaleTilstand>("nyTilstand"),
nyTekst = env.getTypedArgumentOrNull<String>("nyTekst"),
nyLenke = env.getTypedArgumentOrNull<String>("nyLenke"),
nyttStartTidspunkt = env.getTypedArgumentOrNull<LocalDateTime>("nyttStartTidspunkt"),
nyttSluttTidspunkt = env.getTypedArgumentOrNull<LocalDateTime>("nyttSluttTidspunkt"),
nyttStartTidspunkt = env.getTypedArgumentOrNull<OffsetDateTime>("nyttStartTidspunkt"),
nyttSluttTidspunkt = env.getTypedArgumentOrNull<OffsetDateTime>("nyttSluttTidspunkt"),
nyLokasjon = env.getTypedArgumentOrNull<NyKalenderavtaleInput.LokasjonInput?>("nyLokasjon"),
nyErDigitalt = env.getTypedArgumentOrNull<Boolean>("nyErDigitalt"),
eksterneVarsler = env.getTypedArgumentOrDefault<List<EksterntVarselInput>>("eksterneVarsler") { emptyList() }.ifEmpty { null },
Expand Down Expand Up @@ -120,8 +120,8 @@ internal class MutationKalenderavtale(
val tekst: String,
val lenke: String,
val mottakere: List<MottakerInput>,
val startTidspunkt: LocalDateTime,
val sluttTidspunkt: LocalDateTime?,
val startTidspunkt: OffsetDateTime,
val sluttTidspunkt: OffsetDateTime?,
val lokasjon: LokasjonInput?,
val erDigitalt: Boolean,
val tilstand: KalenderavtaleTilstand,
Expand Down Expand Up @@ -157,8 +157,8 @@ internal class MutationKalenderavtale(
virksomhetsnummer = virksomhetsnummer,
produsentId = produsentId,
kildeAppNavn = kildeAppNavn,
startTidspunkt = startTidspunkt,
sluttTidspunkt = sluttTidspunkt,
startTidspunkt = startTidspunkt.inOsloLocalDateTime(),
sluttTidspunkt = sluttTidspunkt?.inOsloLocalDateTime(),
tilstand = tilstand.tilHendelseModel(),
lokasjon = lokasjon?.tilHendelseModel(),
erDigitalt = erDigitalt,
Expand All @@ -168,7 +168,7 @@ internal class MutationKalenderavtale(
påminnelse = paaminnelse?.tilDomene(
notifikasjonOpprettetTidspunkt = opprettetTidspunkt,
frist = null,
startTidspunkt = startTidspunkt,
startTidspunkt = startTidspunkt.inOsloLocalDateTime(),
virksomhetsnummer = virksomhetsnummer,
),
)
Expand Down Expand Up @@ -286,14 +286,14 @@ internal class MutationKalenderavtale(
)
} else if (
nyttStartTidspunkt != null &&
nyttStartTidspunkt!!.isAfter(eksisterende.sluttTidspunkt)
nyttStartTidspunkt!!.inOsloLocalDateTime().isAfter(eksisterende.sluttTidspunkt)
) {
onValidationError(
Error.UgyldigKalenderavtale("startTidspunkt må være før sluttTidspunkt")
)
} else if (
nyttSluttTidspunkt != null
&& nyttSluttTidspunkt!!.isBefore(eksisterende.startTidspunkt)
&& nyttSluttTidspunkt!!.inOsloLocalDateTime().isBefore(eksisterende.startTidspunkt)
) {
onValidationError(
Error.UgyldigKalenderavtale("startTidspunkt må være før sluttTidspunkt")
Expand All @@ -310,16 +310,16 @@ internal class MutationKalenderavtale(
tilstand = nyTilstand?.tilHendelseModel(),
lenke = nyLenke,
tekst = nyTekst,
startTidspunkt = nyttStartTidspunkt,
sluttTidspunkt = nyttSluttTidspunkt,
startTidspunkt = nyttStartTidspunkt?.inOsloLocalDateTime(),
sluttTidspunkt = nyttSluttTidspunkt?.inOsloLocalDateTime(),
lokasjon = nyLokasjon?.tilHendelseModel(),
erDigitalt = nyErDigitalt,
hardDelete = hardDelete?.tilHendelseModel(),
eksterneVarsler = eksterneVarsler?.map { it.tilHendelseModel(eksisterende.virksomhetsnummer) } ?: emptyList(),
påminnelse = if (nyTilstand == AVLYST) null else paaminnelse?.tilDomene(
notifikasjonOpprettetTidspunkt = eksisterende.opprettetTidspunkt,
frist = null,
startTidspunkt = nyttStartTidspunkt ?: eksisterende.startTidspunkt,
startTidspunkt = (nyttStartTidspunkt?.inOsloLocalDateTime() ?: eksisterende.startTidspunkt),
virksomhetsnummer = eksisterende.virksomhetsnummer,
),
idempotenceKey = idempotenceKey,
Expand All @@ -332,8 +332,8 @@ internal class MutationKalenderavtale(
abstract val nyTilstand: KalenderavtaleTilstand?
abstract val nyTekst: String?
abstract val nyLenke: String?
abstract val nyttStartTidspunkt: LocalDateTime?
abstract val nyttSluttTidspunkt: LocalDateTime?
abstract val nyttStartTidspunkt: OffsetDateTime?
abstract val nyttSluttTidspunkt: OffsetDateTime?
abstract val nyLokasjon: NyKalenderavtaleInput.LokasjonInput?
abstract val nyErDigitalt: Boolean?
abstract val hardDelete: HardDeleteUpdateInput?
Expand Down Expand Up @@ -365,8 +365,8 @@ internal class MutationKalenderavtale(
override val nyTilstand: KalenderavtaleTilstand?,
override val nyTekst: String?,
override val nyLenke: String?,
override val nyttStartTidspunkt: LocalDateTime?,
override val nyttSluttTidspunkt: LocalDateTime?,
override val nyttStartTidspunkt: OffsetDateTime?,
override val nyttSluttTidspunkt: OffsetDateTime?,
override val nyLokasjon: NyKalenderavtaleInput.LokasjonInput?,
override val nyErDigitalt: Boolean?,
override val eksterneVarsler: List<EksterntVarselInput>?,
Expand All @@ -381,8 +381,8 @@ internal class MutationKalenderavtale(
override val nyTilstand: KalenderavtaleTilstand?,
override val nyTekst: String?,
override val nyLenke: String?,
override val nyttStartTidspunkt: LocalDateTime?,
override val nyttSluttTidspunkt: LocalDateTime?,
override val nyttStartTidspunkt: OffsetDateTime?,
override val nyttSluttTidspunkt: OffsetDateTime?,
override val nyLokasjon: NyKalenderavtaleInput.LokasjonInput?,
override val nyErDigitalt: Boolean?,
override val eksterneVarsler: List<EksterntVarselInput>?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import no.nav.arbeidsgiver.notifikasjon.hendelse.HendelseModel
import no.nav.arbeidsgiver.notifikasjon.hendelse.HendelseModel.KalenderavtaleTilstand
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.local_database.*
import no.nav.arbeidsgiver.notifikasjon.skedulert_påminnelse.Notifikasjontilstand.*
import no.nav.arbeidsgiver.notifikasjon.tid.inOsloLocalDateTime
import java.sql.Connection
import java.time.Instant
import java.time.LocalDate
Expand Down
13 changes: 6 additions & 7 deletions app/src/main/resources/produsent.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ directive @MaxValue(upToIncluding: Int) on ARGUMENT_DEFINITION
directive @NorwegianMobilePhoneNumber on INPUT_FIELD_DEFINITION | ARGUMENT_DEFINITION

"""
DateTime med offset etter ISO8601-standaren. F.eks. '2011-12-03T10:15:30+01:00'.
Er representert som String.
DateTime etter ISO8601-standaren. F.eks. '2011-12-03T10:15:30+01:00'.
Dersom tidssone ikke er oppgitt, så antar vi at tidspunktet er Oslo-tid ('Europe/Oslo').
"""
scalar ISO8601DateTime

Expand Down Expand Up @@ -254,12 +253,12 @@ type KalenderavtaleData {
"""
Når avtalen starter.
"""
startTidspunkt: ISO8601LocalDateTime!
startTidspunkt: ISO8601DateTime!

"""
Når avtalen slutter.
"""
sluttTidspunkt: ISO8601LocalDateTime
sluttTidspunkt: ISO8601DateTime

"""
Her kan dere oppgi en fysisk adresse som brukeren kan møte opp på dersom dere har det.
Expand Down Expand Up @@ -449,12 +448,12 @@ type Mutation {
"""
Når avtalen starter.
"""
startTidspunkt: ISO8601LocalDateTime!
startTidspunkt: ISO8601DateTime!

"""
Når avtalen slutter.
"""
sluttTidspunkt: ISO8601LocalDateTime
sluttTidspunkt: ISO8601DateTime

"""
Her kan dere oppgi en fysisk adresse som brukeren kan møte opp på dersom dere har det.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package no.nav.arbeidsgiver.notifikasjon.infrastruktur.graphql

import io.kotest.core.spec.style.DescribeSpec
import io.kotest.datatest.withData
import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.instanceOf
import no.nav.arbeidsgiver.notifikasjon.infrastruktur.kafka.kafkaObjectMapper
import java.time.OffsetDateTime

class ScalarsTest : DescribeSpec({
describe("ISO8601DateTime") {
withData(
"2020-01-01T00:01Z", // 00:01 UTC == 01:01 Oslo
"2020-01-01T01:01+01:00", // 01:01 +1 == 01:01 Oslo
"2020-01-01T01:01+01:00[Europe/Oslo]", // == 01:01 Oslo
"2020-01-01T01:01", // 01:01 Oslo
"2020-01-01T01:01:00.00", // 01:01 Oslo
) { ts ->
Scalars.ISO8601DateTime.coercing.parseValue(ts).let {
it shouldBe instanceOf<OffsetDateTime>()
it!! shouldBeEqual OffsetDateTime.parse("2020-01-01T00:01Z")

kafkaObjectMapper.writeValueAsString(it) shouldBe "\"2020-01-01T00:01:00Z\""
}
}
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ class IdempotensOppførselForProdusentApiTests : DescribeSpec({
// language=GraphQL
return """
mutation NyKalenderavtale(
${'$'}startTidspunkt: ISO8601LocalDateTime! = "2024-10-12T07:00:00.00"
${'$'}sluttTidspunkt: ISO8601LocalDateTime
${'$'}startTidspunkt: ISO8601DateTime! = "2024-10-12T07:00:00.00"
${'$'}sluttTidspunkt: ISO8601DateTime
${'$'}lokasjon: LokasjonInput
${'$'}erDigitalt: Boolean
${'$'}tilstand: KalenderavtaleTilstand
Expand Down
4 changes: 2 additions & 2 deletions test-produsent/src/Komponenter/KalenderAvtale.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ const NY_KALENDERAVTALE = gql`
$eksternId: String!
$lenke: String!
$tekst: String!
$startTidspunkt: ISO8601LocalDateTime!
$sluttTidspunkt: ISO8601LocalDateTime
$startTidspunkt: ISO8601DateTime!
$sluttTidspunkt: ISO8601DateTime
$eksterneVarsler: [EksterntVarselInput!]!
$paaminnelse: PaaminnelseInput
$lokasjon: LokasjonInput
Expand Down

0 comments on commit ef8dda9

Please sign in to comment.