diff options
Diffstat (limited to 'src')
6 files changed, 351 insertions, 181 deletions
diff --git a/src/main/kotlin/dev/dnpm/etl/processor/config/AppWebConfig.kt b/src/main/kotlin/dev/dnpm/etl/processor/config/AppWebConfig.kt new file mode 100644 index 0000000..3aa50f2 --- /dev/null +++ b/src/main/kotlin/dev/dnpm/etl/processor/config/AppWebConfig.kt @@ -0,0 +1,13 @@ +package dev.dnpm.etl.processor.config + +import org.springframework.boot.convert.ApplicationConversionService +import org.springframework.context.annotation.Configuration +import org.springframework.format.FormatterRegistry +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer + +@Configuration +class AppWebConfig : WebMvcConfigurer { + override fun addFormatters(registry: FormatterRegistry) { + ApplicationConversionService.configure(registry) + } +} diff --git a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestService.kt b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestService.kt index 3a2ea35..7174974 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/services/RequestService.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/services/RequestService.kt @@ -25,6 +25,7 @@ import dev.dnpm.etl.processor.Tan import dev.dnpm.etl.processor.monitoring.* import java.util.* import org.springframework.data.domain.Page +import org.springframework.data.domain.PageImpl import org.springframework.data.domain.Pageable import org.springframework.stereotype.Service @@ -85,4 +86,27 @@ class RequestService(private val requestRepository: RequestRepository) { .maxByOrNull { it.processedAt } ?.type == RequestType.DELETE } + + enum class Filter(val value: String) { + ALL_DIP("all-dip"), + CONFIRMED("confirmed"), + UNCONFIRMED("unconfirmed"); + } +} + +fun Page<Request>.filter(filter: RequestService.Filter): Page<Request> { + val list = + this + .toList() + .filter { + it.type == RequestType.MTB_FILE + && sequenceOf(RequestStatus.SUCCESS, RequestStatus.WARNING).contains(it.status) + } + .filter { + filter == RequestService.Filter.ALL_DIP + || filter == RequestService.Filter.CONFIRMED && it.submissionAccepted + || filter == RequestService.Filter.UNCONFIRMED && !it.submissionAccepted + } + + return PageImpl(list, this.pageable, list.size.toLong()) } diff --git a/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt b/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt index 3092367..35045da 100644 --- a/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt +++ b/src/main/kotlin/dev/dnpm/etl/processor/web/HomeController.kt @@ -26,18 +26,16 @@ import dev.dnpm.etl.processor.Tan import dev.dnpm.etl.processor.config.AppConfigProperties import dev.dnpm.etl.processor.monitoring.ReportService import dev.dnpm.etl.processor.services.RequestService +import dev.dnpm.etl.processor.services.filter +import org.springframework.core.convert.converter.Converter import org.springframework.data.domain.Pageable import org.springframework.data.domain.Sort import org.springframework.data.web.PageableDefault import org.springframework.security.core.context.SecurityContextHolder +import org.springframework.stereotype.Component import org.springframework.stereotype.Controller import org.springframework.ui.Model -import org.springframework.web.bind.annotation.DeleteMapping -import org.springframework.web.bind.annotation.GetMapping -import org.springframework.web.bind.annotation.PathVariable -import org.springframework.web.bind.annotation.PutMapping -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.* @Controller @RequestMapping(path = ["/"]) @@ -49,6 +47,7 @@ class HomeController( @GetMapping fun index( @RequestParam(name = "q", required = false) queryString: String?, + @RequestParam(name = "f", required = false) filter: RequestService.Filter?, @PageableDefault(page = 0, size = 10, sort = ["processedAt"], direction = Sort.Direction.DESC) pageable: Pageable, model: Model, @@ -64,10 +63,20 @@ class HomeController( // Only available for logged-in admins or users if (null != queryString && isAdminOrUser) { model.addAttribute("query", queryString) - requestService.searchRequestLike(PatientPseudonym(queryString), Tan(queryString), pageable) + if (null != filter) { + model.addAttribute("filter", filter.value) + requestService + .searchRequestLike(PatientPseudonym(queryString), Tan(queryString), pageable) + .filter(filter) + } else { + requestService + .searchRequestLike(PatientPseudonym(queryString), Tan(queryString), pageable) + } + } else { requestService.findAll(pageable) } + model.addAttribute("requests", requests) model.addAttribute("postInitialSubmissionBlock", appConfigProperties.postInitialSubmissionBlock) return "index" @@ -129,4 +138,16 @@ class HomeController( return "fragments :: request" } + + @Component + class FilterConverter : Converter<String, RequestService.Filter?> { + override fun convert(source: String): RequestService.Filter? { + return when (source) { + "all-dip" -> RequestService.Filter.ALL_DIP + "confirmed" -> RequestService.Filter.CONFIRMED + "unconfirmed" -> RequestService.Filter.UNCONFIRMED + else -> null + } + } + } } diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index 7245a8b..40fb90a 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -12,12 +12,23 @@ <div class="search-form" sec:authorize="hasRole('USER') or hasRole('ADMIN')"> <form th:action="@{/}" method="get"> <input id="search-input" type="text" name="q" maxlength="64" placeholder="Suche nach Patienten-Pseudonym oder TAN" th:value="${query}"/> + <select name="f" onchange="this.form.submit()"> + <option value="">in allen Anfragen</option> + <option th:selected="${filter == 'all-dip'}" th:value="all-dip">in DNPM:DIP</option> + <option th:if="${postInitialSubmissionBlock}" th:selected="${filter == 'confirmed'}" th:value="confirmed">in DNPM:DIP mit Meldebestätigung</option> + <option th:if="${postInitialSubmissionBlock}" th:selected="${filter == 'unconfirmed'}" th:value="unconfirmed">in DNPM:DIP ohne Meldebestätigung</option> + </select> <a th:href="@{/}">🞫</a> </form> </div> <h1> - Alle Anfragen + <th:block th:switch="${filter}"> + <th:block th:case="null">Alle Anfragen</th:block> + <th:block th:case="'all-dip'">Alle Anfragen in DNPM:DIP</th:block> + <th:block th:case="'confirmed'">Alle Anfragen in DNPM:DIP mit Meldebestätigung</th:block> + <th:block th:case="'unconfirmed'">Alle Anfragen in DNPM:DIP ohne Meldebestätigung</th:block> + </th:block> <a id="reload-notify" class="btn btn-red reload" title="Neue Anfragen laden" th:href="@{/}"> <span>Neue Anfragen laden</span> </a> diff --git a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt index d02a5fe..8b6e5e2 100644 --- a/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt +++ b/src/test/kotlin/dev/dnpm/etl/processor/services/RequestServiceTest.kt @@ -20,237 +20,331 @@ package dev.dnpm.etl.processor.services import dev.dnpm.etl.processor.* -import dev.dnpm.etl.processor.monitoring.Request -import dev.dnpm.etl.processor.monitoring.RequestRepository -import dev.dnpm.etl.processor.monitoring.RequestStatus -import dev.dnpm.etl.processor.monitoring.RequestType -import dev.dnpm.etl.processor.monitoring.SubmissionType -import java.time.Instant +import dev.dnpm.etl.processor.monitoring.* import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource import org.mockito.Mock import org.mockito.Mockito.* import org.mockito.junit.jupiter.MockitoExtension import org.mockito.kotlin.anyValueClass import org.mockito.kotlin.whenever +import org.springframework.data.domain.PageImpl +import org.springframework.data.domain.PageRequest +import java.time.Instant +import java.util.stream.Stream @ExtendWith(MockitoExtension::class) class RequestServiceTest { - private lateinit var requestRepository: RequestRepository - - private lateinit var requestService: RequestService - - private fun anyRequest() = - any(Request::class.java) - ?: Request( - 0L, - randomRequestId(), - PatientPseudonym("TEST_dummy"), - PatientId("PX"), - Fingerprint("dummy"), - RequestType.MTB_FILE, - SubmissionType.TEST, - RequestStatus.SUCCESS, - Tan.empty(), - Instant.parse("2023-08-08T02:00:00Z"), - ) - - @BeforeEach - fun setup(@Mock requestRepository: RequestRepository) { - this.requestRepository = requestRepository - this.requestService = RequestService(requestRepository) - } - - @Test - fun shouldIndicateLastRequestIsDeleteRequest() { - val requests = - listOf( - Request( - 1L, + private lateinit var requestRepository: RequestRepository + + private lateinit var requestService: RequestService + + private fun anyRequest() = + any(Request::class.java) + ?: Request( + 0L, randomRequestId(), - PatientPseudonym("TEST_12345678901"), - PatientId("P1"), - Fingerprint("0123456789abcdef1"), + PatientPseudonym("TEST_dummy"), + PatientId("PX"), + Fingerprint("dummy"), RequestType.MTB_FILE, SubmissionType.TEST, - RequestStatus.WARNING, + RequestStatus.SUCCESS, Tan.empty(), - Instant.parse("2023-07-07T00:00:00Z"), - ), + Instant.parse("2023-08-08T02:00:00Z"), + ) + + @BeforeEach + fun setup(@Mock requestRepository: RequestRepository) { + this.requestRepository = requestRepository + this.requestService = RequestService(requestRepository) + } + + @Test + fun shouldIndicateLastRequestIsDeleteRequest() { + val requests = + listOf( + Request( + 1L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("0123456789abcdef1"), + RequestType.MTB_FILE, + SubmissionType.TEST, + RequestStatus.WARNING, + Tan.empty(), + Instant.parse("2023-07-07T00:00:00Z"), + ), + Request( + 2L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("0123456789abcdefd"), + RequestType.DELETE, + SubmissionType.TEST, + RequestStatus.WARNING, + Tan.empty(), + Instant.parse("2023-07-07T02:00:00Z"), + ), + Request( + 3L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("0123456789abcdef1"), + RequestType.MTB_FILE, + SubmissionType.TEST, + RequestStatus.UNKNOWN, + Tan.empty(), + Instant.parse("2023-08-11T00:00:00Z"), + ), + ) + + val actual = RequestService.isLastRequestWithKnownStatusDeletion(requests) + + assertThat(actual).isTrue() + } + + @Test + fun shouldIndicateLastRequestIsNotDeleteRequest() { + val requests = + listOf( + Request( + 1L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("0123456789abcdef1"), + RequestType.MTB_FILE, + SubmissionType.TEST, + RequestStatus.WARNING, + Tan.empty(), + Instant.parse("2023-07-07T00:00:00Z"), + ), + Request( + 2L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("0123456789abcdef1"), + RequestType.MTB_FILE, + SubmissionType.TEST, + RequestStatus.WARNING, + Tan.empty(), + Instant.parse("2023-07-07T02:00:00Z"), + ), + Request( + 3L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("0123456789abcdef1"), + RequestType.MTB_FILE, + SubmissionType.TEST, + RequestStatus.UNKNOWN, + Tan.empty(), + Instant.parse("2023-08-11T00:00:00Z"), + ), + ) + + val actual = RequestService.isLastRequestWithKnownStatusDeletion(requests) + + assertThat(actual).isFalse() + } + + @Test + fun shouldReturnPatientsLastRequest() { + val requests = + listOf( + Request( + 1L, + randomRequestId(), + PatientPseudonym("TEST_12345678901"), + PatientId("P1"), + Fingerprint("0123456789abcdef1"), + RequestType.DELETE, + SubmissionType.TEST, + RequestStatus.SUCCESS, + Tan.empty(), + Instant.parse("2023-07-07T02:00:00Z"), + ), + Request( + 1L, + randomRequestId(), + PatientPseudonym("TEST_12345678902"), + PatientId("P2"), + Fingerprint("0123456789abcdef2"), + RequestType.MTB_FILE, + SubmissionType.TEST, + RequestStatus.WARNING, + Tan.empty(), + Instant.parse("2023-08-08T00:00:00Z"), + ), + ) + + val actual = RequestService.lastMtbFileRequestForPatientPseudonym(requests) + + assertThat(actual).isInstanceOf(Request::class.java) + assertThat(actual?.fingerprint).isEqualTo(Fingerprint("0123456789abcdef2")) + } + + @Test + fun shouldReturnNullIfNoRequests() { + val requests = listOf<Request>() + + val actual = RequestService.lastMtbFileRequestForPatientPseudonym(requests) + + assertThat(actual).isNull() + } + + @Test + fun saveShouldSaveRequestUsingRepository() { + doAnswer { + val obj = it.arguments[0] as Request + obj.copy(id = 1L) + } + .whenever(requestRepository) + .save(anyRequest()) + + val request = Request( - 2L, randomRequestId(), PatientPseudonym("TEST_12345678901"), PatientId("P1"), - Fingerprint("0123456789abcdefd"), + Fingerprint("0123456789abcdef1"), RequestType.DELETE, SubmissionType.TEST, - RequestStatus.WARNING, + RequestStatus.SUCCESS, Tan.empty(), Instant.parse("2023-07-07T02:00:00Z"), - ), - Request( - 3L, - randomRequestId(), - PatientPseudonym("TEST_12345678901"), - PatientId("P1"), - Fingerprint("0123456789abcdef1"), - RequestType.MTB_FILE, - SubmissionType.TEST, - RequestStatus.UNKNOWN, - Tan.empty(), - Instant.parse("2023-08-11T00:00:00Z"), - ), - ) + ) + + requestService.save(request) + + verify(requestRepository, times(1)).save(anyRequest()) + } + + @Test + fun allRequestsByPatientPseudonymShouldRequestAllRequestsForPatientPseudonym() { + requestService.allRequestsByPatientPseudonym(PatientPseudonym("TEST_12345678901")) - val actual = RequestService.isLastRequestWithKnownStatusDeletion(requests) + verify(requestRepository, times(1)) + .findAllByPatientPseudonymOrderByProcessedAtDesc(anyValueClass()) + } - assertThat(actual).isTrue() - } + @Test + fun lastMtbFileRequestForPatientPseudonymShouldRequestAllRequestsForPatientPseudonym() { + requestService.lastMtbFileRequestForPatientPseudonym(PatientPseudonym("TEST_12345678901")) - @Test - fun shouldIndicateLastRequestIsNotDeleteRequest() { - val requests = - listOf( + verify(requestRepository, times(1)) + .findAllByPatientPseudonymOrderByProcessedAtDesc(anyValueClass()) + } + + @Test + fun isLastRequestDeletionShouldRequestAllRequestsForPatientPseudonym() { + requestService.isLastRequestWithKnownStatusDeletion(PatientPseudonym("TEST_12345678901")) + + verify(requestRepository, times(1)) + .findAllByPatientPseudonymOrderByProcessedAtDesc(anyValueClass()) + } + + @ParameterizedTest + @MethodSource("filterTestData") + fun shouldFilter(filter: RequestService.Filter, expectedIds: Array<RequestId>) { + + val requests = listOf( Request( - 1L, - randomRequestId(), + 1, + RequestId("00000000-0000-0000-0000-000000000001"), PatientPseudonym("TEST_12345678901"), PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.MTB_FILE, - SubmissionType.TEST, - RequestStatus.WARNING, + SubmissionType.INITIAL, + RequestStatus.ERROR, Tan.empty(), - Instant.parse("2023-07-07T00:00:00Z"), + Instant.parse("2026-03-11T11:00:00Z"), ), Request( - 2L, - randomRequestId(), - PatientPseudonym("TEST_12345678901"), + 2, + RequestId("00000000-0000-0000-0000-000000000002"), + PatientPseudonym("TEST_12345678902"), PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.MTB_FILE, - SubmissionType.TEST, + SubmissionType.INITIAL, RequestStatus.WARNING, Tan.empty(), - Instant.parse("2023-07-07T02:00:00Z"), + Instant.parse("2026-03-11T11:00:00Z"), + submissionAccepted = true, ), Request( - 3L, - randomRequestId(), - PatientPseudonym("TEST_12345678901"), + 3, + RequestId("00000000-0000-0000-0000-000000000003"), + PatientPseudonym("TEST_12345678903"), PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.MTB_FILE, - SubmissionType.TEST, - RequestStatus.UNKNOWN, + SubmissionType.ADDITION, + RequestStatus.SUCCESS, Tan.empty(), - Instant.parse("2023-08-11T00:00:00Z"), + Instant.parse("2026-03-11T11:00:00Z"), ), - ) - - val actual = RequestService.isLastRequestWithKnownStatusDeletion(requests) - - assertThat(actual).isFalse() - } - - @Test - fun shouldReturnPatientsLastRequest() { - val requests = - listOf( Request( - 1L, - randomRequestId(), - PatientPseudonym("TEST_12345678901"), + 4, + RequestId("00000000-0000-0000-0000-000000000004"), + PatientPseudonym("TEST_12345678904"), PatientId("P1"), Fingerprint("0123456789abcdef1"), RequestType.DELETE, SubmissionType.TEST, RequestStatus.SUCCESS, Tan.empty(), - Instant.parse("2023-07-07T02:00:00Z"), - ), - Request( - 1L, - randomRequestId(), - PatientPseudonym("TEST_12345678902"), - PatientId("P2"), - Fingerprint("0123456789abcdef2"), - RequestType.MTB_FILE, - SubmissionType.TEST, - RequestStatus.WARNING, - Tan.empty(), - Instant.parse("2023-08-08T00:00:00Z"), - ), + Instant.parse("2026-03-11T11:00:00Z"), + ) ) - val actual = RequestService.lastMtbFileRequestForPatientPseudonym(requests) - - assertThat(actual).isInstanceOf(Request::class.java) - assertThat(actual?.fingerprint).isEqualTo(Fingerprint("0123456789abcdef2")) - } + val actualIds = PageImpl(requests, PageRequest.of(0, 10), requests.size.toLong()) + .filter(filter) + .toList() + .map { it.uuid } - @Test - fun shouldReturnNullIfNoRequests() { - val requests = listOf<Request>() + assertThat(actualIds).containsExactly(*expectedIds) - val actual = RequestService.lastMtbFileRequestForPatientPseudonym(requests) + } - assertThat(actual).isNull() - } - - @Test - fun saveShouldSaveRequestUsingRepository() { - doAnswer { - val obj = it.arguments[0] as Request - obj.copy(id = 1L) + companion object { + @JvmStatic + fun filterTestData(): Stream<Arguments> { + return Stream.of( + Arguments.of( + RequestService.Filter.ALL_DIP, + arrayOf( + RequestId("00000000-0000-0000-0000-000000000002"), + RequestId("00000000-0000-0000-0000-000000000003") + ) + ), + Arguments.of( + RequestService.Filter.CONFIRMED, + arrayOf( + RequestId("00000000-0000-0000-0000-000000000002"), + ) + ), + Arguments.of( + RequestService.Filter.UNCONFIRMED, + arrayOf( + RequestId("00000000-0000-0000-0000-000000000003") + ) + ) + ) } - .whenever(requestRepository) - .save(anyRequest()) - - val request = - Request( - randomRequestId(), - PatientPseudonym("TEST_12345678901"), - PatientId("P1"), - Fingerprint("0123456789abcdef1"), - RequestType.DELETE, - SubmissionType.TEST, - RequestStatus.SUCCESS, - Tan.empty(), - Instant.parse("2023-07-07T02:00:00Z"), - ) - - requestService.save(request) - - verify(requestRepository, times(1)).save(anyRequest()) - } - - @Test - fun allRequestsByPatientPseudonymShouldRequestAllRequestsForPatientPseudonym() { - requestService.allRequestsByPatientPseudonym(PatientPseudonym("TEST_12345678901")) - - verify(requestRepository, times(1)) - .findAllByPatientPseudonymOrderByProcessedAtDesc(anyValueClass()) - } - - @Test - fun lastMtbFileRequestForPatientPseudonymShouldRequestAllRequestsForPatientPseudonym() { - requestService.lastMtbFileRequestForPatientPseudonym(PatientPseudonym("TEST_12345678901")) - - verify(requestRepository, times(1)) - .findAllByPatientPseudonymOrderByProcessedAtDesc(anyValueClass()) - } - - @Test - fun isLastRequestDeletionShouldRequestAllRequestsForPatientPseudonym() { - requestService.isLastRequestWithKnownStatusDeletion(PatientPseudonym("TEST_12345678901")) - - verify(requestRepository, times(1)) - .findAllByPatientPseudonymOrderByProcessedAtDesc(anyValueClass()) - } + } } diff --git a/src/web/style.css b/src/web/style.css index a82e8de..b4ffd3d 100644 --- a/src/web/style.css +++ b/src/web/style.css @@ -286,6 +286,7 @@ section { border: 1px solid var(--table-border); border-radius: 3px; transition: 0.2s; + background: white; } .search-form form:has(input:focus) { @@ -312,7 +313,13 @@ section { text-decoration: none; display: inline-grid; padding: 0; - margin-right: .5rem; + margin: 0 1rem; + vertical-align: text-bottom; +} + +.search-form form select { + border: none; + background: white; } .token-form { |
