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

MAN-319 PI-2749: MPOP Activity Search API #4567

Merged
merged 5 commits into from
Jan 22, 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
5 changes: 3 additions & 2 deletions projects/manage-supervision-and-delius/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ apply(plugin = "com.google.cloud.tools.jib")
dependencies {
implementation(project(":libs:audit"))
implementation(project(":libs:commons"))
implementation(project(":libs:oauth-server"))
implementation(project(":libs:limited-access"))
implementation(project(":libs:document-management"))
implementation(project(":libs:limited-access"))
implementation(project(":libs:oauth-client"))
implementation(project(":libs:oauth-server"))
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-security")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ generic-service:
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: https://sign-in-dev.hmpps.service.justice.gov.uk/auth/.well-known/jwks.json
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in-dev.hmpps.service.justice.gov.uk/auth/issuer
INTEGRATIONS_ALFRESCO_URL: https://hmpps-delius-alfresco-test.apps.live.cloud-platform.service.justice.gov.uk/alfresco/service/noms-spg
INTEGRATIONS_PROBATION-SEARCH_URL: https://probation-offender-search-dev.hmpps.service.justice.gov.uk

generic-prometheus-alerts:
businessHoursOnly: true
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ generic-service:
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_JWK_SET_URI: https://sign-in-preprod.hmpps.service.justice.gov.uk/auth/.well-known/jwks.json
SPRING_SECURITY_OAUTH2_RESOURCESERVER_JWT_ISSUER_URI: https://sign-in-preprod.hmpps.service.justice.gov.uk/auth/issuer
INTEGRATIONS_ALFRESCO_URL: https://alfresco.pre-prod.delius.probation.hmpps.dsd.io/alfresco/service/noms-spg
INTEGRATIONS_PROBATION-SEARCH_URL: https://probation-offender-search-preprod.hmpps.service.justice.gov.uk

generic-prometheus-alerts:
businessHoursOnly: true
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ generic-service:
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
INTEGRATIONS_ALFRESCO_URL: https://alfresco.probation.service.justice.gov.uk/alfresco/service/noms-spg
INTEGRATIONS_PROBATION-SEARCH_URL: https://probation-offender-search.hmpps.service.justice.gov.uk
3 changes: 3 additions & 0 deletions projects/manage-supervision-and-delius/deploy/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ generic-service:
SPRING_DATASOURCE_PASSWORD: DB_PASSWORD
manage-supervision-and-delius-sentry:
SENTRY_DSN: SENTRY_DSN
manage-supervision-and-delius-client-credentials:
OAUTH2_CLIENT-ID: CLIENT_ID
OAUTH2_CLIENT-SECRET: CLIENT_SECRET

generic-prometheus-alerts:
targetApplication: manage-supervision-and-delius
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"request": {
"method": "POST",
"urlPath": "/auth/oauth/token"
},
"response": {
"headers": {
"Content-Type": "application/json"
},
"status": 200,
"jsonBody": {
"access_token": "token",
"token_type": "bearer",
"expires_in": 9999999999,
"scope": "read write",
"sub": "probation-integration-dev",
"auth_source": "none",
"iss": "https://sign-in-dev.hmpps.service.justice.gov.uk/auth/issuer"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package uk.gov.justice.digital.hmpps

import com.github.tomakehurst.wiremock.WireMockServer
import com.github.tomakehurst.wiremock.client.WireMock
import com.github.tomakehurst.wiremock.client.WireMock.aResponse
import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.equalTo
import org.junit.jupiter.api.Test
Expand All @@ -9,12 +13,18 @@ import org.springframework.boot.test.context.SpringBootTest
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import uk.gov.justice.digital.hmpps.api.model.activity.PersonActivity
import uk.gov.justice.digital.hmpps.client.ActivitySearchRequest
import uk.gov.justice.digital.hmpps.client.ContactSearchResponse
import uk.gov.justice.digital.hmpps.client.ContactSearchResult
import uk.gov.justice.digital.hmpps.data.generator.ContactGenerator
import uk.gov.justice.digital.hmpps.data.generator.PersonGenerator.OVERVIEW
import uk.gov.justice.digital.hmpps.service.toActivity
import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.contentAsJson
import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.objectMapper
import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withJson
import uk.gov.justice.digital.hmpps.test.MockMvcExtensions.withToken

@AutoConfigureMockMvc
Expand All @@ -23,6 +33,61 @@ internal class ActivityIntegrationTest {
@Autowired
lateinit var mockMvc: MockMvc

@Autowired
lateinit var wireMockServer: WireMockServer

@Test
fun `activity search calls probation offender activity search`() {

val person = OVERVIEW
val searchResponse = ContactSearchResponse(
page = 0, totalResults = 10, totalPages = 11, size = 3,
results = listOf(
ContactSearchResult(
crn = person.crn,
id = ContactGenerator.FIRST_APPT_CONTACT.id
),
ContactSearchResult(
crn = person.crn,
id = ContactGenerator.FIRST_NON_APPT_CONTACT.id
),
ContactSearchResult(
crn = person.crn,
id = ContactGenerator.NEXT_APPT_CONTACT.id
)
)
)
wireMockServer.stubFor(
WireMock.post(urlPathEqualTo("/probation-search/search/activity"))
.willReturn(
aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody(objectMapper.writeValueAsString(searchResponse))
)
)

val res = mockMvc
.perform(
post("/activity/${person.crn}").withToken()
.withJson(
ActivitySearchRequest(crn = person.crn)
)
)
.andExpect(status().isOk)
.andReturn().response.contentAsJson<PersonActivity>()

assertThat(res.personSummary.crn, equalTo(person.crn))
assertThat(res.activities.size, equalTo(3))
assertThat(res.activities[0].id, equalTo(ContactGenerator.FIRST_APPT_CONTACT.id))
assertThat(
res.activities[0].location?.officeName,
equalTo(ContactGenerator.FIRST_APPT_CONTACT.toActivity().location?.officeName)
)
assertThat(res.activities[1].id, equalTo(ContactGenerator.FIRST_NON_APPT_CONTACT.id))
assertThat(res.activities[2].id, equalTo(ContactGenerator.NEXT_APPT_CONTACT.id))
}

@Test
fun `all person activity is returned`() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ package uk.gov.justice.digital.hmpps.api.controller

import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.tags.Tag
import org.springframework.data.domain.PageRequest
import org.springframework.security.access.prepost.PreAuthorize
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.bind.annotation.*
import uk.gov.justice.digital.hmpps.api.model.activity.PersonActivitySearchRequest
import uk.gov.justice.digital.hmpps.service.ActivityService

@RestController
Expand All @@ -16,6 +15,15 @@ import uk.gov.justice.digital.hmpps.service.ActivityService
class ActivityController(private val activityService: ActivityService) {

@GetMapping
@Operation(summary = "Gets all activity for a person")
@Operation(summary = "Gets all activity for a person")
fun getPersonActivity(@PathVariable crn: String) = activityService.getPersonActivity(crn)
}

@PostMapping
@Operation(summary = "Activity log search for a person")
fun activitySearch(
@PathVariable crn: String,
@RequestBody searchRequest: PersonActivitySearchRequest,
@RequestParam(required = false, defaultValue = "0") page: Int,
@RequestParam(required = false, defaultValue = "10") size: Int
) = activityService.activitySearch(crn, searchRequest, PageRequest.of(page, size))
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,28 @@
package uk.gov.justice.digital.hmpps.api.model.activity

import com.fasterxml.jackson.annotation.JsonFormat
import uk.gov.justice.digital.hmpps.api.model.PersonSummary
import java.time.LocalDate

data class PersonActivity(
val personSummary: PersonSummary,
val activities: List<Activity>
)
)

data class PersonActivitySearchResponse(
val size: Int,
val page: Int,
val totalResults: Long,
val totalPages: Int,
val personSummary: PersonSummary,
val activities: List<Activity>
)

data class PersonActivitySearchRequest(
val keywords: String? = "",
@JsonFormat(pattern = "yyyy-MM-dd")
val dateFrom: LocalDate? = null,
@JsonFormat(pattern = "yyyy-MM-dd")
val dateTo: LocalDate? = null,
val filters: List<String> = emptyList(),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package uk.gov.justice.digital.hmpps.client

import com.fasterxml.jackson.annotation.JsonFormat
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.service.annotation.PostExchange
import java.time.LocalDate

interface ProbationSearchClient {
@PostExchange(url = "/search/activity")
fun contactSearch(
@RequestBody body: ActivitySearchRequest,
@RequestParam page: Int = 0,
@RequestParam size: Int = 10
): ContactSearchResponse
}

data class ActivitySearchRequest(
val crn: String,
val keywords: String? = "",
@JsonFormat(pattern = "yyyy-MM-dd")
val dateFrom: LocalDate? = null,
@JsonFormat(pattern = "yyyy-MM-dd")
val dateTo: LocalDate? = null,
val filters: List<String> = emptyList(),
)

data class ContactSearchResponse(
val size: Int,
val page: Int,
val totalResults: Long,
val totalPages: Int,
val results: List<ContactSearchResult>,
)

data class ContactSearchResult(
val crn: String,
val id: Long,
val highlights: Map<String, List<String>> = mapOf()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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 org.springframework.web.client.RestClient
import uk.gov.justice.digital.hmpps.client.ProbationSearchClient
import uk.gov.justice.digital.hmpps.config.security.createClient

@Configuration
class RestClientConfig(private val oauth2Client: RestClient) {

@Bean
fun probationSearchClient(@Value("\${integrations.probation-search.url}") apiBaseUrl: String): ProbationSearchClient =
createClient(oauth2Client.mutate().baseUrl(apiBaseUrl).build())
}
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,8 @@ interface ContactRepository : JpaRepository<Contact, Long> {
)
fun findByPersonId(personId: Long): List<Contact>

fun findByPersonIdAndIdIn(personId: Long, ids: List<Long>): List<Contact>

fun findByPersonIdAndEventIdIn(personId: Long, eventId: List<Long>): List<Contact>

fun findByPersonIdAndId(personId: Long, id: Long): Contact?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,57 @@
package uk.gov.justice.digital.hmpps.service

import org.springframework.data.domain.Pageable
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import uk.gov.justice.digital.hmpps.api.model.activity.Activity
import uk.gov.justice.digital.hmpps.api.model.activity.PersonActivity
import uk.gov.justice.digital.hmpps.api.model.activity.PersonActivitySearchRequest
import uk.gov.justice.digital.hmpps.api.model.activity.PersonActivitySearchResponse
import uk.gov.justice.digital.hmpps.client.ActivitySearchRequest
import uk.gov.justice.digital.hmpps.client.ProbationSearchClient
import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.ContactRepository
import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.PersonRepository
import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.getSummary

@Service
class ActivityService(
private val personRepository: PersonRepository,
private val contactRepository: ContactRepository
private val contactRepository: ContactRepository,
private val probationSearchClient: ProbationSearchClient
) {

@Transactional
fun activitySearch(
crn: String,
searchRequest: PersonActivitySearchRequest,
pageable: Pageable
): PersonActivitySearchResponse {
val summary = personRepository.getSummary(crn)
val probationSearchRequest = ActivitySearchRequest(
crn = crn,
keywords = searchRequest.keywords,
dateFrom = searchRequest.dateFrom,
dateTo = searchRequest.dateTo,
filters = searchRequest.filters
)
val response =
probationSearchClient.contactSearch(probationSearchRequest, pageable.pageNumber, pageable.pageSize)
val ids = response.results.map { it.id }

val contacts = ids.mapNotNull { contactId ->
contactRepository.findByPersonIdAndIdIn(summary.id, ids).associateBy { it.id }[contactId]
}

return PersonActivitySearchResponse(
size = response.size,
page = response.page,
totalResults = response.totalResults,
totalPages = response.totalPages,
personSummary = summary.toPersonSummary(),
activities = contacts.map { it.toActivity() }
)
}

@Transactional
fun getPersonActivity(crn: String): PersonActivity {
val summary = personRepository.getSummary(crn)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ spring:
query.mutation_strategy.global_temporary:
create_tables: false
drop_tables: false
security.oauth2.client:
registration:
default:
provider: hmpps-auth
authorization-grant-type: client_credentials
client-id: ${oauth2.client-id}
client-secret: ${oauth2.client-secret}
provider:
hmpps-auth:
token-uri: http://localhost:${wiremock.port}/auth/oauth/token
threads.virtual.enabled: true
ldap:
base: ou=Users,dc=moj,dc=com
Expand Down Expand Up @@ -54,6 +64,13 @@ seed.database: true

wiremock.enabled: true

integrations:
probation-search.url: http://localhost:${wiremock.port}/probation-search

oauth2:
client-id: manage-supervision-and-delius
client-secret: manage-supervision-and-delius

logging.level:
uk.gov.justice.digital.hmpps: DEBUG
org.hibernate.tool.schema: ERROR
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.mockito.Mock
import org.mockito.junit.jupiter.MockitoExtension
import org.mockito.kotlin.any
import org.mockito.kotlin.whenever
import uk.gov.justice.digital.hmpps.client.ProbationSearchClient
import uk.gov.justice.digital.hmpps.data.generator.ContactGenerator
import uk.gov.justice.digital.hmpps.data.generator.personalDetails.PersonDetailsGenerator.PERSONAL_DETAILS
import uk.gov.justice.digital.hmpps.integrations.delius.overview.entity.ContactRepository
Expand All @@ -25,6 +26,9 @@ internal class ActivityServiceTest {
@Mock
lateinit var contactRepository: ContactRepository

@Mock
lateinit var probationSearchClient: ProbationSearchClient

@InjectMocks
lateinit var service: ActivityService

Expand Down
Loading