Skip to content

Commit

Permalink
Add project and source to Observation objects
Browse files Browse the repository at this point in the history
  • Loading branch information
pvannierop committed May 3, 2024
1 parent 2db3b05 commit a093e74
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 38 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ the data from the RADAR-base kafka service.[]\

Data dashboard applications can use the APIs as follows.

`GET */subject/{subjectId}/topic/{topicId}/observations`
`GET */project/{projectId}/subject/{subjectId}/topic/{topicId}/observations`

## Installation

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ data class ObservationDto(
/** Unique observation ID. */
val id: Long?,

/** Unique identifier of project. */
val project: String?,

/** Unique identifier of study subject. */
val subject: String?,

/** Unique identifier of the data source. */
val source: String?,

/** Unique identifier of the kafka topic. */
val topic: String?,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ class ObservationRepository(
@Context em: Provider<EntityManager>,
) : HibernateRepository(em) {

fun getObservations(topicId: String, subjectId: String): List<Observation> {
logger.debug("Get observations in topic {} of subject {}", topicId, subjectId)
fun getObservations(projectId: String, subjectId: String, topicId: String): List<Observation> {
logger.debug("Get observations in topic {} of subject {} in project {}", topicId, subjectId, projectId)

return transact {
createQuery(
"SELECT o FROM Observation o WHERE o.subject = :subjectId AND o.topic = :topicId ORDER BY o.date DESC",
"SELECT o FROM Observation o WHERE o.project = :projectId AND o.subject = :subjectId AND o.topic = :topicId ORDER BY o.date DESC",
Observation::class.java,
).apply {
setParameter("projectId", projectId)
setParameter("subjectId", subjectId)
setParameter("topicId", topicId)
}.resultList
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import java.time.Duration

fun Observation.toDto(): ObservationDto = ObservationDto(
id = id,
project = project,
subject = subject,
source = source,
topic = topic,
category = category,
date = date?.toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,17 @@ data class Observation(
@Id
val id: Long,

@Column(nullable = false)
@Id
val project: String,

@Column(nullable = false)
@Id
val subject: String,

@Id
val source: String,

@Column(nullable = false)
@Id
val topic: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import org.radarbase.jersey.auth.NeedsPermission
import org.radarbase.jersey.auth.filter.RadarSecurityContext
import org.slf4j.LoggerFactory

@Path("subject/{subjectId}/topic/{topicId}")
@Path("project/{projectId}/subject/{subjectId}/topic/{topicId}")
@Resource
@Produces("application/json")
@Consumes("application/json")
Expand All @@ -43,14 +43,15 @@ class ObservationResource(
@Path("observations")
@NeedsPermission(Permission.MEASUREMENT_READ)
fun getObservations(
@PathParam("projectId") projectId: String,
@PathParam("subjectId") subjectId: String,
@PathParam("topicId") topicId: String
): ObservationListDto {
if (request.securityContext != null && request.securityContext is RadarSecurityContext) {
val userName = (request.securityContext as RadarSecurityContext).userPrincipal
log.info("User $userName is accessing observations for $subjectId")
if (!subjectId.equals(userName)) throw NotFoundException("Subjects can only request their own observations.")
return observationService.getObservations(topicId, subjectId)
return observationService.getObservations(projectId = projectId, subjectId = subjectId, topicId = topicId)
}
return ObservationListDto(emptyList())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ import org.radarbase.datadashboard.api.domain.mapper.toDto
class ObservationService(
@Context private val observationRepository: ObservationRepository
) {
fun getObservations(topicId: String, subjectId: String): ObservationListDto {
val result = this.observationRepository.getObservations(topicId, subjectId)
fun getObservations(projectId: String, subjectId: String, topicId: String): ObservationListDto {
val result = this.observationRepository.getObservations(projectId = projectId, topicId = topicId, subjectId = subjectId)
return ObservationListDto(
result.map { it.toDto() },
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,15 @@
<column name="id" type="bigint" autoIncrement="${autoIncrement}">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="project" type="varchar(255)">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="subject" type="varchar(255)">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="source" type="varchar(255)">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="topic" type="varchar(255)">
<constraints primaryKey="true" nullable="false"/>
</column>
Expand All @@ -47,6 +53,7 @@
</createTable>

<createIndex tableName="observation" indexName="idx_observation_subject_in_topic">
<column name="project"/>
<column name="subject"/>
<column name="topic"/>
</createIndex>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
subject;topic;category;variable;value_numeric;value_textual;date;end_date
sub-1;questionnaire_answer;baseline_questions;Perceived_Pain_Score;5;NULL;2021-02-20 00:00:00;NULL
sub-1;questionnaire_answer;followup_questions;Name_Of_Physician;NULL;Dr.J.Adams;2021-02-20 00:00:00;NULL
sub-2;questionnaire_answer;baseline_questions;Perceived_Pain_Score;2;NULL;2021-02-20 00:00:00;NULL
sub-2;questionnaire_answer;followup_questions;Name_Of_Physician;NULL;Dr.G.Washington;2022-05-20 00:00:00;NULL
sub-1;phone_battery_level;NULL;batteryLevel;5;NULL;2021-02-20 00:00:00;NULL
sub-1;phone_battery_level;NULL;status;NULL;CHARGING;2021-02-20 00:00:00;NULL
sub-2;phone_battery_level;NULL;batteryLevel;10;NULL;2021-02-20 00:00:00;NULL
sub-2;phone_battery_level;NULL;status;NULL;FULL;2021-02-20 00:00:00;NULL
project;subject;source;topic;category;variable;value_numeric;value_textual;date;end_date
project-1;sub-1;source-1;questionnaire_answer;baseline_questions;Perceived_Pain_Score;5;NULL;2021-02-20 00:00:00;NULL
project-1;sub-1;source-1;questionnaire_answer;followup_questions;Name_Of_Physician;NULL;Dr.J.Adams;2021-02-20 00:00:00;NULL
project-1;sub-2;source-1;questionnaire_answer;baseline_questions;Perceived_Pain_Score;2;NULL;2021-02-20 00:00:00;NULL
project-1;sub-2;source-1;questionnaire_answer;followup_questions;Name_Of_Physician;NULL;Dr.G.Washington;2022-05-20 00:00:00;NULL
project-1;sub-1;source-1;phone_battery_level;NULL;batteryLevel;5;NULL;2021-02-20 00:00:00;NULL
project-1;sub-1;source-1;phone_battery_level;NULL;status;NULL;CHARGING;2021-02-20 00:00:00;NULL
project-1;sub-2;source-1;phone_battery_level;NULL;batteryLevel;10;NULL;2021-02-20 00:00:00;NULL
project-1;sub-2;source-1;phone_battery_level;NULL;status;NULL;FULL;2021-02-20 00:00:00;NULL
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ class DashboardIntegrationTest: JerseyTest() {

@Test
fun testGetObservationsNoToken() {
val response = target("subject/sub-1/topic/phone_battery_level/observations").request().get()
val response = target("project/project-1/sub-1/topic/phone_battery_level/observations").request().get()
Assertions.assertEquals(401, response.status)
}

@Test
fun testGetObservationsWithToken() {
val response = target("subject/sub-1/topic/phone_battery_level/observations")
val response = target("project/project-1/subject/sub-1/topic/phone_battery_level/observations")
.request()
.header(HttpHeaders.AUTHORIZATION, "Bearer " + "... encoded token ...")
.get()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ import java.time.ZonedDateTime

class ObservationResourceTest: JerseyTest() {


@Mock
lateinit var observationService: ObservationService

private lateinit var observationListDto: ObservationListDto
private var observationId: Long = 1
private val projectId = "project-1"
private val subjectId = "sub-1"
private val topicId = "topic-1"

Expand All @@ -61,11 +62,7 @@ class ObservationResourceTest: JerseyTest() {
@BeforeEach
fun init() {
// Create some fake observations that are returned by the service.
val obs1 = Observation(id = 1L, subject = subjectId, topic = "topic-1", category = "category-1", variable = "variable-1", date = ZonedDateTime.now(), valueTextual = "value1", valueNumeric = null, endDate = null)
val obs2 = Observation(id = 2L, subject = subjectId, topic = "topic-1", category = "category-1", variable = "variable-1", date = ZonedDateTime.now(), valueTextual = "value1", valueNumeric = null, endDate = null)
val obs3 = Observation(id = 3L, subject = subjectId, topic = "topic-1", category = "category-1", variable = "variable-1", date = ZonedDateTime.now(), valueTextual = "value1", valueNumeric = null, endDate = null)
val obs4 = Observation(id = 4L, subject = subjectId, topic = "topic-1", category = "category-1", variable = "variable-1", date = ZonedDateTime.now(), valueTextual = "value1", valueNumeric = null, endDate = null)
val observations: List<Observation> = listOf(obs1, obs2, obs3, obs4)
val observations: List<Observation> = listOf(createObservation(), createObservation(), createObservation(), createObservation())
// Create Dto that should be returned by the ObservationService.
observationListDto = ObservationListDto(
observations.map { it.toDto() },
Expand All @@ -75,24 +72,46 @@ class ObservationResourceTest: JerseyTest() {
@Test
fun testGetObservations() {
// Instruct the mock to return the fake observations when called.
`when`(observationService.getObservations(subjectId = subjectId, topicId = topicId)).thenReturn(observationListDto)
`when`(observationService.getObservations(projectId = projectId, subjectId = subjectId, topicId = topicId)).thenReturn(observationListDto)
// Make the call to the REST endpoint.
val response = target("subject/sub-1/topic/topic-1/observations").request().get()
val response = target("project/project-1/subject/sub-1/topic/topic-1/observations").request().get()
// Expect the http response to be OK and the same as the expected DTO.
assertEquals(200, response.status)
assertEquals(observationListDto, response.readEntity(ObservationListDto::class.java))
}

@Test
fun testGetObservations_failNoSubjectId() {
val response = target("subject//topic/topic-1/observations").request().get()
val response = target("project/project-1/subject//topic/topic-1/observations").request().get()
assertEquals(404, response.status)
}

@Test
fun testGetObservations_failNoTopicId() {
val response = target("subject/sub-1/topic//observations").request().get()
val response = target("project/project-1/subject/sub-1/topic//observations").request().get()
assertEquals(404, response.status)
}

@Test
fun testGetObservations_failNoProjectId() {
val response = target("project//subject/sub-1/topic/topic-1/observations").request().get()
assertEquals(404, response.status)
}

private fun createObservation(): Observation {
return Observation(
id = observationId,
project = "project-1",
subject = subjectId,
source = "source-1",
topic = "topic-1",
category = "category-1",
variable = "variable-1",
date = ZonedDateTime.now(),
valueTextual = "value1",
valueNumeric = null,
endDate = null
)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@ import java.time.ZonedDateTime

class ObservationServiceTest {

val subjectId = "sub-1"

// Create a Mockito mock of the ObservationRepository. This is instantiated in the init block.
@Mock
private lateinit var observationRepository: ObservationRepository

private var observationId: Long = 1
private val projectId = "project-1"
private val subjectId = "sub-1"
private val topicId = "topic-1"

private val observationService: ObservationService

init {
Expand All @@ -53,17 +56,13 @@ class ObservationServiceTest {

// Create some fake observations that are returned by the repository.
// Each observation is linked to a Variable.
val obs1 = Observation(id = 1L, subject = subjectId, topic = "topic-1", category = "category-1", variable = "variable-1", date = ZonedDateTime.now(), valueTextual = "value1", valueNumeric = null, endDate = null)
val obs2 = Observation(id = 2L, subject = subjectId, topic = "topic-1", category = "category-1", variable = "variable-1", date = ZonedDateTime.now(), valueTextual = "value1", valueNumeric = null, endDate = null)
val obs3 = Observation(id = 3L, subject = subjectId, topic = "topic-1", category = "category-1", variable = "variable-1", date = ZonedDateTime.now(), valueTextual = "value1", valueNumeric = null, endDate = null)
val obs4 = Observation(id = 4L, subject = subjectId, topic = "topic-1", category = "category-1", variable = "variable-1", date = ZonedDateTime.now(), valueTextual = "value1", valueNumeric = null, endDate = null)
val observations = listOf(obs1, obs2, obs3, obs4)
val observations: List<Observation> = listOf(createObservation(), createObservation(), createObservation(), createObservation())

// Mock the repository to return the fake observations.
`when`(observationRepository.getObservations(subjectId = subjectId, topicId = "topic-1")).thenReturn(observations)
`when`(observationRepository.getObservations(projectId = projectId, subjectId = subjectId, topicId = topicId)).thenReturn(observations)

// Call the ObservationService (class under test) to get the observations.
val result = observationService.getObservations(subjectId = subjectId, topicId = "topic-1")
val result = observationService.getObservations(projectId = projectId, subjectId = subjectId, topicId = topicId)

// Check if the result is as expected (observations transformed to ObservationListDto).
val expectedDto = ObservationListDto(
Expand All @@ -72,4 +71,20 @@ class ObservationServiceTest {
assertEquals(expectedDto, result)
}

private fun createObservation(): Observation {
return Observation(
id = observationId,
project = "project-1",
subject = subjectId,
source = "source-1",
topic = "topic-1",
category = "category-1",
variable = "variable-1",
date = ZonedDateTime.now(),
valueTextual = "value1",
valueNumeric = null,
endDate = null
)
}

}

0 comments on commit a093e74

Please sign in to comment.