Skip to content

Commit

Permalink
Merge pull request #1351 from ministryofjustice/feature/aps-193-add-u…
Browse files Browse the repository at this point in the history
…rls-to-timeline-reponses

APS-193 add urls to timeline reponses
  • Loading branch information
davidatkinsuk authored Jan 18, 2024
2 parents 5dfd044 + 0054507 commit c2908d8
Show file tree
Hide file tree
Showing 28 changed files with 534 additions and 99 deletions.
1 change: 0 additions & 1 deletion detekt-baseline.xml
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,6 @@
<ID>TooGenericExceptionThrown:ApplicationsController.kt$ApplicationsController$throw RuntimeException("Unsupported Application type: ${application::class.qualifiedName}")</ID>
<ID>TooGenericExceptionThrown:ApplicationsController.kt$ApplicationsController$throw RuntimeException("Unsupported SubmitApplication type: ${submitApplication::class.qualifiedName}")</ID>
<ID>TooGenericExceptionThrown:ApplicationsController.kt$ApplicationsController$throw RuntimeException("Unsupported UpdateApplication type: ${body::class.qualifiedName}")</ID>
<ID>TooGenericExceptionThrown:ApplicationsTransformer.kt$ApplicationsTransformer$throw RuntimeException("Only CAS1 is currently supported")</ID>
<ID>TooGenericExceptionThrown:ApplicationsTransformer.kt$ApplicationsTransformer$throw RuntimeException("Unrecognised application type when transforming: ${domain::class.qualifiedName}")</ID>
<ID>TooGenericExceptionThrown:ApplicationsTransformer.kt$ApplicationsTransformer$throw RuntimeException("Unrecognised application type when transforming: ${jpa::class.qualifiedName}")</ID>
<ID>TooGenericExceptionThrown:ApprovedPremisesApplicationEntityFactory.kt$ApprovedPremisesApplicationEntityFactory$throw RuntimeException("Must provide a createdByUser")</ID>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.convert

import org.springframework.core.convert.TypeDescriptor
import org.springframework.core.convert.converter.GenericConverter
import org.springframework.stereotype.Component
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.UrlTemplate

@Component
class UrlTemplateConverter : GenericConverter {
override fun getConvertibleTypes(): MutableSet<GenericConverter.ConvertiblePair> {
return mutableSetOf(GenericConverter.ConvertiblePair(String::class.java, UrlTemplate::class.java))
}

override fun convert(source: Any?, sourceType: TypeDescriptor, targetType: TypeDescriptor): Any {
val input = source as String
return UrlTemplate(input)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,22 @@ import javax.persistence.Table
@Repository
interface DomainEventRepository : JpaRepository<DomainEventEntity, UUID> {

@Query("SELECT new uk.gov.justice.digital.hmpps.approvedpremisesapi.model.DomainEventSummary(cast(d.id as string), d.type, d.occurredAt) FROM DomainEventEntity d WHERE d.applicationId = :applicationId")
@Query(
"""
SELECT
cast(d.id as TEXT),
d.type,
d.occurred_at as occurredAt,
cast(d.application_id as TEXT) as applicationId,
cast(d.assessment_id as TEXT) as assessmentId,
cast(d.booking_id as TEXT) as bookingId,
cast(b.premises_id as TEXT) as premisesId
FROM domain_events d
LEFT OUTER JOIN bookings b ON b.id = d.booking_id
WHERE d.application_id = :applicationId
""",
nativeQuery = true,
)
fun findAllTimelineEventsByApplicationId(applicationId: UUID): List<DomainEventSummary>

@Query(
Expand All @@ -46,6 +61,7 @@ data class DomainEventEntity(
@Id
val id: UUID,
val applicationId: UUID?,
val assessmentId: UUID?,
val bookingId: UUID?,
val crn: String,
@Enumerated(value = EnumType.STRING)
Expand Down Expand Up @@ -81,9 +97,11 @@ data class DomainEventEntity(
else -> throw RuntimeException("Unsupported DomainEventData type ${T::class.qualifiedName}/${this.type.name}")
}

checkNotNull(this.applicationId) { "application id should not be null" }

return DomainEvent(
id = this.id,
applicationId = this.applicationId!!,
applicationId = this.applicationId,
crn = this.crn,
occurredAt = this.occurredAt.toInstant(),
data = data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import java.util.UUID
data class DomainEvent<T> (
val id: UUID,
val applicationId: UUID? = null,
val assessmentId: UUID? = null,
val bookingId: UUID? = null,
val crn: String,
val occurredAt: Instant,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.model

import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.DomainEventType
import java.time.OffsetDateTime
import java.sql.Timestamp
import java.util.UUID

data class DomainEventSummary(
val id: String,
val type: DomainEventType,
val occurredAt: OffsetDateTime,
)
interface DomainEventSummary {
val id: String
val type: DomainEventType
val occurredAt: Timestamp
val applicationId: UUID?
val assessmentId: UUID?
val bookingId: UUID?
val premisesId: UUID?
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.validated
import uk.gov.justice.digital.hmpps.approvedpremisesapi.results.AuthorisableActionResult
import uk.gov.justice.digital.hmpps.approvedpremisesapi.results.ValidatableActionResult
import uk.gov.justice.digital.hmpps.approvedpremisesapi.transformer.ApplicationTimelineNoteTransformer
import uk.gov.justice.digital.hmpps.approvedpremisesapi.transformer.ApplicationsTransformer
import uk.gov.justice.digital.hmpps.approvedpremisesapi.transformer.ApplicationTimelineTransformer
import uk.gov.justice.digital.hmpps.approvedpremisesapi.transformer.AssessmentClarificationNoteTransformer
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.getMetadata
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.getPageable
Expand All @@ -81,7 +81,6 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.service.cas3.DomainEvent
class ApplicationService(
private val userRepository: UserRepository,
private val applicationRepository: ApplicationRepository,
private val applicationsTransformer: ApplicationsTransformer,
private val jsonSchemaService: JsonSchemaService,
private val offenderService: OffenderService,
private val userService: UserService,
Expand All @@ -101,6 +100,7 @@ class ApplicationService(
private val objectMapper: ObjectMapper,
@Value("\${url-templates.frontend.application}") private val applicationUrlTemplate: String,
private val probationRegionRepository: ProbationRegionRepository,
private val applicationTimelineTransformer: ApplicationTimelineTransformer,
) {
fun getAllApplicationsForUsername(userDistinguishedName: String, serviceName: ServiceName): List<ApplicationSummary> {
val userEntity = userRepository.findByDeliusUsername(userDistinguishedName)
Expand Down Expand Up @@ -1039,7 +1039,7 @@ class ApplicationService(
fun getApplicationTimeline(applicationId: UUID): List<TimelineEvent> {
val domainEvents = domainEventService.getAllDomainEventsForApplication(applicationId)
val timelineEvents = domainEvents.map {
applicationsTransformer.transformDomainEventSummaryToTimelineEvent(it)
applicationTimelineTransformer.transformDomainEventSummaryToTimelineEvent(it)
}.toMutableList()

timelineEvents += getAllInformationRequestEventsForApplication(applicationId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ class AssessmentService(
DomainEvent(
id = domainEventId,
applicationId = application.id,
assessmentId = assessment.id,
crn = application.crn,
occurredAt = acceptedAt.toInstant(),
data = ApplicationAssessedEnvelope(
Expand Down Expand Up @@ -594,6 +595,7 @@ class AssessmentService(
DomainEvent(
id = domainEventId,
applicationId = application.id,
assessmentId = assessment.id,
crn = application.crn,
occurredAt = rejectedAt.toInstant(),
data = ApplicationAssessedEnvelope(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ class DomainEventService(
DomainEventEntity(
id = domainEvent.id,
applicationId = domainEvent.applicationId,
assessmentId = domainEvent.assessmentId,
bookingId = bookingId,
crn = domainEvent.crn,
type = enumTypeFromDataType(domainEvent.data!!::class.java),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ class DomainEventService(
DomainEventEntity(
id = domainEvent.id,
applicationId = domainEvent.applicationId,
assessmentId = domainEvent.assessmentId,
bookingId = domainEvent.bookingId,
crn = domainEvent.crn,
type = enumTypeFromDataType(domainEvent.data::class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ class DomainEventService(
DomainEventEntity(
id = domainEvent.id,
applicationId = domainEvent.applicationId,
assessmentId = domainEvent.assessmentId,
bookingId = domainEvent.bookingId,
crn = domainEvent.crn,
type = enumTypeFromDataType(domainEvent.data::class),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ class ApplicationTimelineNoteTransformer {
occurredAt = jpa.createdAt.toInstant(),
content = jpa.body,
createdBy = jpa.createdBy.toString(),
associatedUrls = emptyList(),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.transformer

import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.TimelineEvent
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.TimelineEventAssociatedUrl
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.TimelineEventType
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.TimelineEventUrlType
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.DomainEventType
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.DomainEventSummary
import uk.gov.justice.digital.hmpps.approvedpremisesapi.util.UrlTemplate

@Component
class ApplicationTimelineTransformer(
@Value("\${url-templates.frontend.application}") private val applicationUrlTemplate: UrlTemplate,
@Value("\${url-templates.frontend.assessment}") private val assessmentUrlTemplate: UrlTemplate,
@Value("\${url-templates.frontend.booking}") private val bookingUrlTemplate: UrlTemplate,
) {

fun transformDomainEventSummaryToTimelineEvent(domainEventSummary: DomainEventSummary): TimelineEvent {
val associatedUrls = listOfNotNull(
applicationUrlOrNull(domainEventSummary),
assessmentUrlOrNull(domainEventSummary),
bookingUrlOrNull(domainEventSummary),
)

return TimelineEvent(
id = domainEventSummary.id,
type = transformDomainEventTypeToTimelineEventType(domainEventSummary.type),
occurredAt = domainEventSummary.occurredAt.toInstant(),
associatedUrls = associatedUrls,
)
}

private fun applicationUrlOrNull(domainEventSummary: DomainEventSummary) = domainEventSummary.applicationId?.let {
TimelineEventAssociatedUrl(
TimelineEventUrlType.application,
applicationUrlTemplate.resolve(mapOf("id" to domainEventSummary.applicationId.toString())),
)
}

private fun assessmentUrlOrNull(domainEventSummary: DomainEventSummary) = domainEventSummary.assessmentId?.let {
TimelineEventAssociatedUrl(
TimelineEventUrlType.assessment,
assessmentUrlTemplate.resolve(mapOf("id" to domainEventSummary.assessmentId.toString())),
)
}

private fun bookingUrlOrNull(domainEventSummary: DomainEventSummary) = domainEventSummary.bookingId?.let {
domainEventSummary.premisesId?.let {
TimelineEventAssociatedUrl(
TimelineEventUrlType.booking,
bookingUrlTemplate.resolve(
mapOf(
"premisesId" to domainEventSummary.premisesId.toString(),
"bookingId" to domainEventSummary.bookingId.toString(),
),
),
)
}
}

fun transformDomainEventTypeToTimelineEventType(domainEventType: DomainEventType): TimelineEventType {
return when (domainEventType) {
DomainEventType.APPROVED_PREMISES_APPLICATION_SUBMITTED -> TimelineEventType.approvedPremisesApplicationSubmitted
DomainEventType.APPROVED_PREMISES_APPLICATION_ASSESSED -> TimelineEventType.approvedPremisesApplicationAssessed
DomainEventType.APPROVED_PREMISES_BOOKING_MADE -> TimelineEventType.approvedPremisesBookingMade
DomainEventType.APPROVED_PREMISES_PERSON_ARRIVED -> TimelineEventType.approvedPremisesPersonArrived
DomainEventType.APPROVED_PREMISES_PERSON_NOT_ARRIVED -> TimelineEventType.approvedPremisesPersonNotArrived
DomainEventType.APPROVED_PREMISES_PERSON_DEPARTED -> TimelineEventType.approvedPremisesPersonDeparted
DomainEventType.APPROVED_PREMISES_BOOKING_NOT_MADE -> TimelineEventType.approvedPremisesBookingNotMade
DomainEventType.APPROVED_PREMISES_BOOKING_CANCELLED -> TimelineEventType.approvedPremisesBookingCancelled
DomainEventType.APPROVED_PREMISES_BOOKING_CHANGED -> TimelineEventType.approvedPremisesBookingChanged
DomainEventType.APPROVED_PREMISES_APPLICATION_WITHDRAWN -> TimelineEventType.approvedPremisesApplicationWithdrawn
else -> throw IllegalArgumentException("Only CAS1 is currently supported")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,15 @@ import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApplicationEn
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesApplicationEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.AssessmentDecision
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.AssessmentEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.DomainEventType
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.OfflineApplicationEntity
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.ApprovedPremisesApplicationStatus
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.DomainEventSummary
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.PersonInfoResult
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.PersonRisks
import uk.gov.justice.digital.hmpps.approvedpremisesapi.model.prisonsapi.InOutStatus
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ApplicationSummary as ApiApplicationSummary
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ApprovedPremisesApplicationStatus as ApiApprovedPremisesApplicationStatus
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.ApprovedPremisesApplicationSummary as ApiApprovedPremisesApplicationSummary
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.TemporaryAccommodationApplicationSummary as ApiTemporaryAccommodationApplicationSummary
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.TimelineEvent as APITimelineEvent
import uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.TimelineEventType as APITimelineEventType
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApplicationSummary as DomainApplicationSummary
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.ApprovedPremisesApplicationSummary as DomainApprovedPremisesApplicationSummary
import uk.gov.justice.digital.hmpps.approvedpremisesapi.jpa.entity.Cas2ApplicationSummary as DomainCas2ApplicationSummary
Expand Down Expand Up @@ -205,28 +201,4 @@ class ApplicationsTransformer(
AssessmentDecision.REJECTED -> uk.gov.justice.digital.hmpps.approvedpremisesapi.api.model.AssessmentDecision.rejected
null -> null
}

fun transformDomainEventSummaryToTimelineEvent(domainEventSummary: DomainEventSummary): APITimelineEvent {
return APITimelineEvent(
id = domainEventSummary.id,
type = transformDomainEventTypeToTimelineEventType(domainEventSummary.type),
occurredAt = domainEventSummary.occurredAt.toInstant(),
)
}

fun transformDomainEventTypeToTimelineEventType(domainEventType: DomainEventType): APITimelineEventType {
return when (domainEventType) {
DomainEventType.APPROVED_PREMISES_APPLICATION_SUBMITTED -> APITimelineEventType.approvedPremisesApplicationSubmitted
DomainEventType.APPROVED_PREMISES_APPLICATION_ASSESSED -> APITimelineEventType.approvedPremisesApplicationAssessed
DomainEventType.APPROVED_PREMISES_BOOKING_MADE -> APITimelineEventType.approvedPremisesBookingMade
DomainEventType.APPROVED_PREMISES_PERSON_ARRIVED -> APITimelineEventType.approvedPremisesPersonArrived
DomainEventType.APPROVED_PREMISES_PERSON_NOT_ARRIVED -> APITimelineEventType.approvedPremisesPersonNotArrived
DomainEventType.APPROVED_PREMISES_PERSON_DEPARTED -> APITimelineEventType.approvedPremisesPersonDeparted
DomainEventType.APPROVED_PREMISES_BOOKING_NOT_MADE -> APITimelineEventType.approvedPremisesBookingNotMade
DomainEventType.APPROVED_PREMISES_BOOKING_CANCELLED -> APITimelineEventType.approvedPremisesBookingCancelled
DomainEventType.APPROVED_PREMISES_BOOKING_CHANGED -> APITimelineEventType.approvedPremisesBookingChanged
DomainEventType.APPROVED_PREMISES_APPLICATION_WITHDRAWN -> APITimelineEventType.approvedPremisesApplicationWithdrawn
else -> throw RuntimeException("Only CAS1 is currently supported")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@ class AssessmentClarificationNoteTransformer {
id = jpa.id.toString(),
type = TimelineEventType.approvedPremisesInformationRequest,
occurredAt = jpa.createdAt.toInstant(),
associatedUrls = emptyList(),
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package uk.gov.justice.digital.hmpps.approvedpremisesapi.util

class UrlTemplate(val template: String) {
fun resolve(args: Map<String, String>) =
args.entries.fold(template) { acc, (key, value) -> acc.replace("#$key", value) }
}
2 changes: 1 addition & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ url-templates:
frontend:
application: http://localhost:3000/applications/#id
assessment: http://localhost:3000/assessments/#id
booking: http://localhost:3000/premises/{premisesId}/bookings/{bookingId}
booking: http://localhost:3000/premises/#premisesId/bookings/#bookingId
cas2:
application: http://localhost:3000/applications/#id
submitted-application-overview: http://localhost:3000/assess/applications/#applicationId/overview
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ALTER TABLE domain_events ADD COLUMN assessment_id UUID NULL;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
UPDATE domain_events d
SET assessment_id = a.id
FROM assessments a
WHERE d.type = 'APPROVED_PREMISES_APPLICATION_ASSESSED' AND
d.assessment_id IS NULL AND
a.application_id = d.application_id AND
a.reallocated_at IS NULL AND
a.decision IS NOT NULL AND
a.submitted_at = d.occurred_at;

-- there are a few domain events in prod that don't match the above rule
-- for those we assign them a completed assessment related to the same
-- application when there is one and only one such assessment

UPDATE domain_events d
SET assessment_id = a.id
FROM assessments a
WHERE
d.type = 'APPROVED_PREMISES_APPLICATION_ASSESSED' AND
d.assessment_id IS NULL AND
a.application_id = d.application_id AND
a.reallocated_at IS NULL AND
a.decision IS NOT NULL AND
d.id IN (
SELECT d1.id
FROM domain_events d1
INNER JOIN assessments a1 ON a1.application_id = d1.application_id
WHERE d1.type = 'APPROVED_PREMISES_APPLICATION_ASSESSED' AND
d1.assessment_id IS NULL AND
a1.reallocated_at IS NULL AND
a1.decision IS NOT NULL
GROUP BY d1.id
HAVING count(a1.id) = 1
);
Loading

0 comments on commit c2908d8

Please sign in to comment.