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

PI-2649 Reply by email on error #4529

Merged
merged 3 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,10 @@ package uk.gov.justice.digital.hmpps
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import org.mockito.Mockito.atLeastOnce
import org.mockito.kotlin.any
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.eq
import org.mockito.kotlin.verify
import org.mockito.kotlin.*
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.mock.mockito.MockBean
Expand All @@ -20,6 +18,7 @@ import uk.gov.justice.digital.hmpps.message.Notification
import uk.gov.justice.digital.hmpps.messaging.EmailMessage
import uk.gov.justice.digital.hmpps.messaging.Handler
import uk.gov.justice.digital.hmpps.resourceloader.ResourceLoader.get
import uk.gov.justice.digital.hmpps.service.MailboxService
import uk.gov.justice.digital.hmpps.telemetry.TelemetryMessagingExtensions.notificationReceived
import uk.gov.justice.digital.hmpps.telemetry.TelemetryService

Expand All @@ -34,6 +33,9 @@ internal class IntegrationTest {
@MockBean
lateinit var telemetryService: TelemetryService

@MockBean
lateinit var mailBoxService: MailboxService

@Test
fun `contact is created`() {
val notification = Notification(get<EmailMessage>("successful-message"))
Expand Down Expand Up @@ -113,8 +115,8 @@ internal class IntegrationTest {
@Test
fun `error when missing crn`() {
val notification = Notification(get<EmailMessage>("no-crn"))
val exception = assertThrows<IllegalArgumentException> { handler.handle(notification) }
assertThat(exception.message, equalTo("No CRN in message subject"))
assertDoesNotThrow { handler.handle(notification) }
verify(mailBoxService).onUnableToCreateContactFromEmail(any())
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.openfolder.kotlinasyncapi.annotation.Schema
import org.openfolder.kotlinasyncapi.annotation.channel.Channel
import org.openfolder.kotlinasyncapi.annotation.channel.Message
import org.openfolder.kotlinasyncapi.annotation.channel.Publish
import org.springframework.context.ApplicationEventPublisher
import org.springframework.ldap.NameNotFoundException
import org.springframework.ldap.core.AttributesMapper
import org.springframework.ldap.core.LdapTemplate
Expand All @@ -19,6 +20,7 @@ import uk.gov.justice.digital.hmpps.datetime.DeliusDateFormatter
import uk.gov.justice.digital.hmpps.entity.*
import uk.gov.justice.digital.hmpps.entity.ContactType.Code.EMAIL
import uk.gov.justice.digital.hmpps.entity.Person.Companion.CRN_REGEX
import uk.gov.justice.digital.hmpps.exception.IgnorableMessageException
import uk.gov.justice.digital.hmpps.message.Notification
import uk.gov.justice.digital.hmpps.telemetry.TelemetryMessagingExtensions.notificationReceived
import uk.gov.justice.digital.hmpps.telemetry.TelemetryService
Expand All @@ -37,56 +39,66 @@ class Handler(
private val personManagerRepository: PersonManagerRepository,
private val staffRepository: StaffRepository,
private val ldapTemplate: LdapTemplate,
private val eventPublisher: ApplicationEventPublisher,
) : NotificationHandler<EmailMessage>, AuditableService(auditedInteractionService) {
@Publish(messages = [Message(title = "email-message", payload = Schema(EmailMessage::class))])
override fun handle(notification: Notification<EmailMessage>) = audit(ADD_CONTACT) { audit ->
telemetryService.notificationReceived(notification)
val message = notification.message
override fun handle(notification: Notification<EmailMessage>) = try {
audit(ADD_CONTACT) { audit ->
telemetryService.notificationReceived(notification)
val message = notification.message

val crn = message.extractCrn()
val emailAddress =
message.fromEmailAddress.takeIf { it.endsWith("@justice.gov.uk") || it.endsWith("@digital.justice.gov.uk") }
?: throw IllegalArgumentException("Email address does not end with @justice.gov.uk or @digital.justice.gov.uk")
val person = personRepository.getByCrn(crn)
val manager = personManagerRepository.getManager(person.id)
val staffId = findStaffIdForEmailAddress(emailAddress) ?: manager.staffId
val fullNotes = """
val crn = message.extractCrn()
val emailAddress =
message.fromEmailAddress.takeIf { it.endsWith("@justice.gov.uk") || it.endsWith("@digital.justice.gov.uk") }
?: throw IllegalArgumentException("Email address does not end with @justice.gov.uk or @digital.justice.gov.uk")
val person = personRepository.getByCrn(crn)
val manager = personManagerRepository.getManager(person.id)
val staffId = findStaffIdForEmailAddress(emailAddress) ?: manager.staffId
val fullNotes = """
|This contact was created automatically from a forwarded email sent by ${message.fromEmailAddress} ${message.onAt}.
|Subject: ${message.subject}
|
|${htmlToMarkdownConverter.convert(message.bodyContent)}
""".trimMargin()
val contact = contactRepository.save(
Contact(
personId = person.id,
externalReference = "urn:uk:gov:hmpps:justice-email:${message.id}",
type = contactTypeRepository.getByCode(EMAIL),
date = message.receivedDateTime,
startTime = message.receivedDateTime,
description = "Email - ${message.subject.replace(CRN_REGEX.toRegex(), "").trim()}".truncated(),
notes = fullNotes,
staffId = staffId,
teamId = manager.teamId,
providerId = manager.providerId,
val contact = contactRepository.save(
Contact(
personId = person.id,
externalReference = "urn:uk:gov:hmpps:justice-email:${message.id}",
type = contactTypeRepository.getByCode(EMAIL),
date = message.receivedDateTime,
startTime = message.receivedDateTime,
description = "Email - ${message.subject.replace(CRN_REGEX.toRegex(), "").trim()}".truncated(),
notes = fullNotes,
staffId = staffId,
teamId = manager.teamId,
providerId = manager.providerId,
)
)
)
audit["contactId"] = contact.id
audit["contactId"] = contact.id

telemetryService.trackEvent(
"CreatedContact", mapOf(
"crn" to crn,
"staffId" to staffId.toString(),
"contactId" to contact.id.toString(),
"messageId" to message.id,
telemetryService.trackEvent(
"CreatedContact", mapOf(
"crn" to crn,
"staffId" to staffId.toString(),
"contactId" to contact.id.toString(),
"messageId" to message.id,
)
)
)
}
} catch (_: IgnorableMessageException) {
}

private fun EmailMessage.extractCrn(): String {
val crns = CRN_REGEX.toRegex().findAll(subject).map { it.value }.distinct()
return when (crns.count()) {
1 -> crns.single().uppercase()
0 -> throw IllegalArgumentException("No CRN in message subject")
0 -> {
eventPublisher.publishEvent(
UnableToCreateContactFromEmail(this, "Unable to parse CRN from message subject")
)
throw IgnorableMessageException("No CRN in message subject")
}

else -> throw IllegalArgumentException("Multiple CRNs in message subject")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package uk.gov.justice.digital.hmpps.messaging

data class UnableToCreateContactFromEmail(val email: EmailMessage, val reason: String)
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package uk.gov.justice.digital.hmpps.service

import com.microsoft.graph.models.EmailAddress
import com.microsoft.graph.models.ItemBody
import com.microsoft.graph.models.Message
import com.microsoft.graph.models.Recipient
import com.microsoft.graph.serviceclient.GraphServiceClient
import com.microsoft.graph.users.item.sendmail.SendMailPostRequestBody
import io.opentelemetry.api.trace.SpanKind
import io.opentelemetry.instrumentation.annotations.WithSpan
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.event.EventListener
import org.springframework.stereotype.Service
import uk.gov.justice.digital.hmpps.message.MessageAttributes
import uk.gov.justice.digital.hmpps.message.Notification
import uk.gov.justice.digital.hmpps.messaging.EmailMessage
import uk.gov.justice.digital.hmpps.messaging.UnableToCreateContactFromEmail
import uk.gov.justice.digital.hmpps.publisher.NotificationPublisher
import uk.gov.justice.digital.hmpps.telemetry.TelemetryService

Expand All @@ -31,6 +37,21 @@ class MailboxService(
}
}

@EventListener(UnableToCreateContactFromEmail::class)
fun onUnableToCreateContactFromEmail(event: UnableToCreateContactFromEmail) {
val toEmailAddress = EmailAddress().apply { address = event.email.fromEmailAddress }
val message = Message().apply {
subject = "Unable to create contact from email"
body = ItemBody().apply { content = "Reason for the contact not being created: ${event.reason}" }
toRecipients = listOf(Recipient().apply { emailAddress = toEmailAddress })
}
graphServiceClient.me().sendMail().post(SendMailPostRequestBody().apply { setMessage(message) })
telemetryService.trackEvent(
"UnableToCreateContactFromEmail",
mapOf("emailId" to event.email.id, "reason" to event.reason)
)
}

private fun getUnreadMessages() = graphServiceClient
.users()
.byUserId(emailAddress)
Expand Down
Loading