Skip to content

Commit

Permalink
PI-2543 Enable GOV.UK Notify + add cron job for sending SMS reminders (
Browse files Browse the repository at this point in the history
  • Loading branch information
marcus-bcl authored Oct 30, 2024
1 parent c99a2e9 commit 691f051
Show file tree
Hide file tree
Showing 12 changed files with 200 additions and 14 deletions.
1 change: 1 addition & 0 deletions projects/appointment-reminders-and-delius/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-csv")
implementation(libs.springdoc)
implementation(libs.notify)

dev(project(":libs:dev-tools"))
dev("com.h2database:h2")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
apiVersion: batch/v1
kind: CronJob
metadata:
name: unpaid-work-appointment-reminders
spec:
schedule: {{ index .Values "jobs" "unpaid-work-appointment-reminders" "schedule" }}
concurrencyPolicy: Forbid
failedJobsHistoryLimit: 1
successfulJobsHistoryLimit: 1
jobTemplate:
spec:
template:
spec:
serviceAccountName: appointment-reminders-and-delius
containers:
- name: update-custody-key-dates
image: "ghcr.io/ministryofjustice/hmpps-probation-integration-services/appointment-reminders-and-delius:{{ .Values.version }}"
securityContext:
capabilities:
drop:
- ALL
runAsNonRoot: true
allowPrivilegeEscalation: false
seccompProfile:
type: RuntimeDefault
env:
{{- range $secret, $envs := index .Values "generic-service" "namespace_secrets" }}
{{- range $key, $val := $envs }}
- name: {{ $key }}
valueFrom:
secretKeyRef:
key: {{ trimSuffix "?" $val }}
name: {{ $secret }}{{ if hasSuffix "?" $val }}
optional: true{{ end }} {{- end }}
{{- end }}
{{- range $key, $val := index .Values "generic-service" "env" }}
- name: {{ $key }}
value: "{{ $val }}"
{{- end }}
- name: MESSAGING_CONSUMER_ENABLED
value: "false"
- name: JOBS_UNPAID-WORK-APPOINTMENT-REMINDERS_ENABLED
value: "true"
- name: JOBS_UNPAID-WORK-APPOINTMENT-REMINDERS_PROVIDER
value: "N56"
- name: JOBS_UNPAID-WORK-APPOINTMENT-REMINDERS_DRY-RUN
value: "{{ index .Values "jobs" "unpaid-work-appointment-reminders" "dry-run" }}"
restartPolicy: Never
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ generic-service:

generic-prometheus-alerts:
businessHoursOnly: true

jobs:
unpaid-work-appointment-reminders:
dry-run: false
schedule: '0 18 * * *' # 6:00pm UTC every day
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,9 @@ generic-service:
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in-preprod.hmpps.service.justice.gov.uk/auth/issuer

generic-prometheus-alerts:
businessHoursOnly: true
businessHoursOnly: true

jobs:
unpaid-work-appointment-reminders:
dry-run: false
schedule: '0 18 * * *' # 6:00pm UTC every day
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@ generic-service:
SENTRY_ENVIRONMENT: prod
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: https://sign-in.hmpps.service.justice.gov.uk/auth/.well-known/jwks.json
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in.hmpps.service.justice.gov.uk/auth/issuer

jobs:
unpaid-work-appointment-reminders:
dry-run: true
schedule: '0 18 * * *' # 6:00pm UTC every day
2 changes: 2 additions & 0 deletions projects/appointment-reminders-and-delius/deploy/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ generic-service:
SPRING_DATASOURCE_PASSWORD: DB_PASSWORD
appointment-reminders-and-delius-sentry:
SENTRY_DSN: SENTRY_DSN
appointment-reminders-and-delius-govuk-notify:
GOVUK-NOTIFY_API-KEY: API_KEY

generic-prometheus-alerts:
targetApplication: appointment-reminders-and-delius
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package uk.gov.justice.digital.hmpps
import org.junit.jupiter.api.Test
import org.mockito.kotlin.any
import org.mockito.kotlin.eq
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
Expand All @@ -15,17 +16,29 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import uk.gov.justice.digital.hmpps.model.UnpaidWorkAppointment
import uk.gov.justice.digital.hmpps.repository.UpwAppointmentRepository
import uk.gov.justice.digital.hmpps.service.UnpaidWorkAppointmentsService
import uk.gov.justice.digital.hmpps.telemetry.TelemetryService
import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken
import uk.gov.service.notify.NotificationClient

@AutoConfigureMockMvc
@SpringBootTest(webEnvironment = RANDOM_PORT)
internal class IntegrationTest {
@Autowired
lateinit var mockMvc: MockMvc

@Autowired
lateinit var unpaidWorkAppointmentsService: UnpaidWorkAppointmentsService

@MockBean
lateinit var upwAppointmentRepository: UpwAppointmentRepository

@MockBean
lateinit var notificationClient: NotificationClient

@MockBean
lateinit var telemetryService: TelemetryService

@Test
fun `returns csv report`() {
whenever(upwAppointmentRepository.getUnpaidWorkAppointments(any(), eq("N56"))).thenReturn(
Expand Down Expand Up @@ -55,4 +68,33 @@ internal class IntegrationTest {
)
)
}

@Test
fun `sends messages to govuk notify`() {
whenever(upwAppointmentRepository.getUnpaidWorkAppointments(any(), eq("N56"))).thenReturn(
listOf(
object : UnpaidWorkAppointment {
override val firstName = "Test"
override val mobileNumber = "07000000000"
override val appointmentDate = "01/01/2000"
override val crn = "A123456"
override val eventNumbers = "1"
override val upwAppointmentIds = "123, 456"
}
)
)

unpaidWorkAppointmentsService.sendUnpaidWorkAppointmentReminders("N56")

verify(notificationClient).sendSms(
"cd713c1b-1b27-45a0-b493-37a34666635a",
"07000000000",
mapOf("firstName" to "Test", "date" to "01/01/2000"),
"123, 456"
)
verify(telemetryService).trackEvent(
"SentUnpaidWorkAppointmentReminder",
mapOf("crn" to "A123456", "upwAppointmentIds" to "123, 456")
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package uk.gov.justice.digital.hmpps.config

import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import uk.gov.service.notify.NotificationClient

@Configuration
class NotificationClientConfig {
@Bean
fun notificationClient(@Value("\${govuk-notify.api-key}") apiKey: String) = NotificationClient(apiKey)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package uk.gov.justice.digital.hmpps.cronjob

import org.springframework.beans.factory.annotation.Value
import org.springframework.boot.SpringApplication.exit
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.event.ApplicationStartedEvent
import org.springframework.context.ApplicationContext
import org.springframework.context.ApplicationListener
import org.springframework.stereotype.Component
import uk.gov.justice.digital.hmpps.service.UnpaidWorkAppointmentsService
import kotlin.system.exitProcess

@Component
@ConditionalOnProperty("jobs.unpaid-work-appointment-reminders.enabled")
class UpwAppointmentRemindersJob(
@Value("\${jobs.unpaid-work-appointment-reminders.provider}")
private val providerCode: String,
@Value("\${jobs.unpaid-work-appointment-reminders.dry-run:false}")
private val dryRun: Boolean,
private val service: UnpaidWorkAppointmentsService,
private val applicationContext: ApplicationContext,
) : ApplicationListener<ApplicationStartedEvent> {
override fun onApplicationEvent(applicationStartedEvent: ApplicationStartedEvent) {
service.sendUnpaidWorkAppointmentReminders(providerCode, dryRun)
exitProcess(exit(applicationContext, { 0 }))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package uk.gov.justice.digital.hmpps.service

import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.repository.UpwAppointmentRepository
import uk.gov.justice.digital.hmpps.telemetry.TelemetryService
import uk.gov.service.notify.NotificationClient
import java.time.LocalDate

@Service
class UnpaidWorkAppointmentsService(
private val upwAppointmentRepository: UpwAppointmentRepository,
private val notificationClient: NotificationClient,
private val telemetryService: TelemetryService,
@Value("\${govuk-notify.templates.upw-appointment-reminder}") private val templateId: String,
) {
fun sendUnpaidWorkAppointmentReminders(providerCode: String, dryRun: Boolean = false) {
upwAppointmentRepository.getUnpaidWorkAppointments(LocalDate.now().plusDays(2), providerCode)
.forEach {
notificationClient.sendSms(
templateId,
it.mobileNumber,
mapOf("firstName" to it.firstName, "date" to it.appointmentDate),
it.upwAppointmentIds
)
telemetryService.trackEvent(
"SentUnpaidWorkAppointmentReminder",
mapOf("crn" to it.crn, "upwAppointmentIds" to it.upwAppointmentIds)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ management:

info.productId: HMPPS518 # https://developer-portal.hmpps.service.justice.gov.uk/products/185

govuk-notify.templates:
upw-appointment-reminder: cd713c1b-1b27-45a0-b493-37a34666635a

---
# Shared dev/test config
spring.config.activate.on-profile: [ "dev", "integration-test" ]
Expand All @@ -46,6 +49,8 @@ seed.database: true
wiremock.enabled: true
context.initializer.classes: uk.gov.justice.digital.hmpps.wiremock.WireMockInitialiser

govuk-notify.api-key: test

logging.level:
uk.gov.justice.digital.hmpps: DEBUG
org.hibernate.tool.schema: ERROR
Expand Down
28 changes: 15 additions & 13 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -70,30 +70,32 @@ rootProject.allChildren()
dependencyResolutionManagement {
versionCatalogs {
create("libs") {
library("asyncapi", "org.openfolder:kotlin-asyncapi-spring-web:3.0.3")
library("aws-autoconfigure", "io.awspring.cloud:spring-cloud-aws-autoconfigure:3.2.0")
library("aws-starter", "io.awspring.cloud:spring-cloud-aws-starter:3.2.0")
library("aws-query-protocol", "software.amazon.awssdk:aws-query-protocol:2.29.1")
library("aws-sns", "io.awspring.cloud:spring-cloud-aws-starter-sns:3.2.0")
library("aws-sqs", "io.awspring.cloud:spring-cloud-aws-starter-sqs:3.2.0")
library("aws-starter", "io.awspring.cloud:spring-cloud-aws-starter:3.2.0")
library("aws-sts", "software.amazon.awssdk:sts:2.29.1")
library("aws-query-protocol", "software.amazon.awssdk:aws-query-protocol:2.29.1")
bundle(
"aws-messaging",
listOf("aws-autoconfigure", "aws-starter", "aws-sns", "aws-sqs", "aws-sts", "aws-query-protocol")
)
library("mockito-kotlin", "org.mockito.kotlin:mockito-kotlin:5.4.0")
library("mockito-inline", "org.mockito:mockito-inline:5.2.0")
bundle("mockito", listOf("mockito-kotlin", "mockito-inline"))
library("flipt", "io.flipt:flipt-java:1.1.1")
library("insights", "com.microsoft.azure:applicationinsights-web:3.6.1")
library("sentry", "io.sentry:sentry-spring-boot-starter-jakarta:7.16.0")
library("mockito-inline", "org.mockito:mockito-inline:5.2.0")
library("mockito-kotlin", "org.mockito.kotlin:mockito-kotlin:5.4.0")
library("notify", "uk.gov.service.notify:notifications-java-client:5.2.1-RELEASE")
library(
"opentelemetry-annotations",
"io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:2.9.0"
)
bundle("telemetry", listOf("insights", "opentelemetry-annotations", "sentry"))
library("sentry", "io.sentry:sentry-spring-boot-starter-jakarta:7.16.0")
library("springdoc", "org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0")
library("asyncapi", "org.openfolder:kotlin-asyncapi-spring-web:3.0.3")
library("wiremock", "org.wiremock:wiremock-standalone:3.9.2")
library("flipt", "io.flipt:flipt-java:1.1.1")

bundle(
"aws-messaging",
listOf("aws-autoconfigure", "aws-starter", "aws-sns", "aws-sqs", "aws-sts", "aws-query-protocol")
)
bundle("mockito", listOf("mockito-kotlin", "mockito-inline"))
bundle("telemetry", listOf("insights", "opentelemetry-annotations", "sentry"))
}
}
}
Expand Down

0 comments on commit 691f051

Please sign in to comment.