1
0
mirror of https://github.com/pcvolkmer/etl-processor.git synced 2025-07-01 22:22:53 +00:00

19 Commits

Author SHA1 Message Date
e3d0471ca9 Release 0.9.5 2024-05-31 13:21:59 +02:00
8b194e7212 chore: update kotlin version 2024-05-31 12:55:32 +02:00
070100eba0 chore: update spring dependency-management plugin 2024-05-31 12:54:28 +02:00
ce1489d9a1 chore: update spring boot dependencies 2024-05-31 12:54:23 +02:00
ca1e73a0b5 chore: update bwhc-dto-java dependency 2024-05-31 12:54:18 +02:00
041bf459ef fix: add missing 'fatal' severity 2024-05-31 12:52:22 +02:00
c922e27758 fix: handle null values in MtbFile
This should not occur but if, it should not result in NPE except for

* Patient
* Consent
* Episode
2024-05-31 12:51:40 +02:00
4d5c0ce1fb chore: remove println 2024-05-31 12:51:36 +02:00
bb0bbf5a28 chore: update webjars-locator dependency 2024-05-31 12:49:51 +02:00
1b4585d601 docs: fix CVE number in dependency comment 2024-05-31 12:44:23 +02:00
dad3ea80ee chore: update integration test dependency
This mitigates CVE-204-26308 and CVE-2024-25710
2024-05-31 12:44:19 +02:00
01446bdece chore: update GitHub workflow actions 2024-05-31 12:34:57 +02:00
43660a4dcb chore: mark as snapshot version 2024-05-31 12:22:08 +02:00
8313420de5 chore: bump version for new release 2024-04-19 09:42:19 +02:00
1651f446fe chore: update spring boot and other dependencies 2024-04-19 09:41:17 +02:00
056a087065 chore: update spring boot dependencies 2024-03-25 16:12:20 +01:00
a730ce2a53 fix: update spring security due to CVE-2024-22257 2024-03-19 16:40:32 +01:00
12eb1feea6 fix: assign new value from scope function 2024-03-12 18:29:42 +01:00
af714f7b64 fix: ignore possible null values in mtb files 2024-03-12 17:56:25 +01:00
9 changed files with 215 additions and 146 deletions

View File

@ -21,7 +21,7 @@ jobs:
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}

View File

@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-java@v3 - uses: actions/setup-java@v4
with: with:
java-version: '21' java-version: '21'
distribution: 'temurin' distribution: 'temurin'
@ -27,7 +27,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: actions/setup-java@v3 - uses: actions/setup-java@v4
with: with:
java-version: '21' java-version: '21'
distribution: 'temurin' distribution: 'temurin'

View File

@ -4,23 +4,23 @@ import org.springframework.boot.gradle.tasks.bundling.BootBuildImage
plugins { plugins {
war war
id("org.springframework.boot") version "3.2.3" id("org.springframework.boot") version "3.2.6"
id("io.spring.dependency-management") version "1.1.4" id("io.spring.dependency-management") version "1.1.5"
kotlin("jvm") version "1.9.22" kotlin("jvm") version "1.9.24"
kotlin("plugin.spring") version "1.9.22" kotlin("plugin.spring") version "1.9.24"
} }
group = "de.ukw.ccc" group = "de.ukw.ccc"
version = "0.9.0" version = "0.9.5"
var versions = mapOf( var versions = mapOf(
"bwhc-dto-java" to "0.2.0", "bwhc-dto-java" to "0.3.0",
"hapi-fhir" to "6.10.2", "hapi-fhir" to "6.10.5",
"httpclient5" to "5.2.1", "httpclient5" to "5.2.3",
"mockito-kotlin" to "5.2.1", "mockito-kotlin" to "5.3.1",
// Webjars // Webjars
"echarts" to "5.4.3", "echarts" to "5.4.3",
"htmx.org" to "1.9.10" "htmx.org" to "1.9.11"
) )
java { java {
@ -70,7 +70,7 @@ dependencies {
implementation("ca.uhn.hapi.fhir:hapi-fhir-structures-r4:${versions["hapi-fhir"]}") implementation("ca.uhn.hapi.fhir:hapi-fhir-structures-r4:${versions["hapi-fhir"]}")
implementation("org.apache.httpcomponents.client5:httpclient5:${versions["httpclient5"]}") implementation("org.apache.httpcomponents.client5:httpclient5:${versions["httpclient5"]}")
implementation("com.jayway.jsonpath:json-path") implementation("com.jayway.jsonpath:json-path")
implementation("org.webjars:webjars-locator:0.50") implementation("org.webjars:webjars-locator:0.52")
implementation("org.webjars.npm:echarts:${versions["echarts"]}") implementation("org.webjars.npm:echarts:${versions["echarts"]}")
implementation("org.webjars.npm:htmx.org:${versions["htmx.org"]}") implementation("org.webjars.npm:htmx.org:${versions["htmx.org"]}")
runtimeOnly("org.mariadb.jdbc:mariadb-java-client") runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
@ -85,6 +85,8 @@ dependencies {
testImplementation("org.mockito.kotlin:mockito-kotlin:${versions["mockito-kotlin"]}") testImplementation("org.mockito.kotlin:mockito-kotlin:${versions["mockito-kotlin"]}")
integrationTestImplementation("org.testcontainers:junit-jupiter") integrationTestImplementation("org.testcontainers:junit-jupiter")
integrationTestImplementation("org.testcontainers:postgresql") integrationTestImplementation("org.testcontainers:postgresql")
// Override dependency version from org.testcontainers:junit-jupiter - CVE-2024-26308, CVE-2024-25710
integrationTestImplementation("org.apache.commons:commons-compress:1.26.1")
} }
tasks.withType<KotlinCompile> { tasks.withType<KotlinCompile> {

View File

@ -57,6 +57,7 @@ class ReportService(
data class Issue(val severity: Severity, val message: String) data class Issue(val severity: Severity, val message: String)
enum class Severity(@JsonValue val value: String) { enum class Severity(@JsonValue val value: String) {
FATAL("fatal"),
ERROR("error"), ERROR("error"),
WARNING("warning"), WARNING("warning"),
INFO("info") INFO("info")

View File

@ -1,7 +1,7 @@
/* /*
* This file is part of ETL-Processor * This file is part of ETL-Processor
* *
* Copyright (c) 2023 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors * Copyright (c) 2024 Comprehensive Cancer Center Mainfranken, Datenintegrationszentrum Philipps-Universität Marburg and Contributors
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published * it under the terms of the GNU Affero General Public License as published
@ -31,31 +31,31 @@ import org.apache.commons.codec.digest.DigestUtils
infix fun MtbFile.pseudonymizeWith(pseudonymizeService: PseudonymizeService) { infix fun MtbFile.pseudonymizeWith(pseudonymizeService: PseudonymizeService) {
val patientPseudonym = pseudonymizeService.patientPseudonym(this.patient.id) val patientPseudonym = pseudonymizeService.patientPseudonym(this.patient.id)
this.episode.patient = patientPseudonym this.episode?.patient = patientPseudonym
this.carePlans.forEach { it.patient = patientPseudonym } this.carePlans?.forEach { it.patient = patientPseudonym }
this.patient.id = patientPseudonym this.patient.id = patientPseudonym
this.claims.forEach { it.patient = patientPseudonym } this.claims?.forEach { it.patient = patientPseudonym }
this.consent.patient = patientPseudonym this.consent?.patient = patientPseudonym
this.claimResponses.forEach { it.patient = patientPseudonym } this.claimResponses?.forEach { it.patient = patientPseudonym }
this.diagnoses.forEach { it.patient = patientPseudonym } this.diagnoses?.forEach { it.patient = patientPseudonym }
this.ecogStatus.forEach { it.patient = patientPseudonym } this.ecogStatus?.forEach { it.patient = patientPseudonym }
this.familyMemberDiagnoses.forEach { it.patient = patientPseudonym } this.familyMemberDiagnoses?.forEach { it.patient = patientPseudonym }
this.geneticCounsellingRequests.forEach { it.patient = patientPseudonym } this.geneticCounsellingRequests?.forEach { it.patient = patientPseudonym }
this.histologyReevaluationRequests.forEach { it.patient = patientPseudonym } this.histologyReevaluationRequests?.forEach { it.patient = patientPseudonym }
this.histologyReports.forEach { this.histologyReports?.forEach {
it.patient = patientPseudonym it.patient = patientPseudonym
it.tumorMorphology.patient = patientPseudonym it.tumorMorphology?.patient = patientPseudonym
} }
this.lastGuidelineTherapies.forEach { it.patient = patientPseudonym } this.lastGuidelineTherapies?.forEach { it.patient = patientPseudonym }
this.molecularPathologyFindings.forEach { it.patient = patientPseudonym } this.molecularPathologyFindings?.forEach { it.patient = patientPseudonym }
this.molecularTherapies.forEach { molecularTherapy -> molecularTherapy.history.forEach { it.patient = patientPseudonym } } this.molecularTherapies?.forEach { molecularTherapy -> molecularTherapy.history.forEach { it.patient = patientPseudonym } }
this.ngsReports.forEach { it.patient = patientPseudonym } this.ngsReports?.forEach { it.patient = patientPseudonym }
this.previousGuidelineTherapies.forEach { it.patient = patientPseudonym } this.previousGuidelineTherapies?.forEach { it.patient = patientPseudonym }
this.rebiopsyRequests.forEach { it.patient = patientPseudonym } this.rebiopsyRequests?.forEach { it.patient = patientPseudonym }
this.recommendations.forEach { it.patient = patientPseudonym } this.recommendations?.forEach { it.patient = patientPseudonym }
this.responses.forEach { it.patient = patientPseudonym } this.responses?.forEach { it.patient = patientPseudonym }
this.studyInclusionRequests.forEach { it.patient = patientPseudonym } this.studyInclusionRequests?.forEach { it.patient = patientPseudonym }
this.specimens.forEach { it.patient = patientPseudonym } this.specimens?.forEach { it.patient = patientPseudonym }
} }
/** /**
@ -73,151 +73,153 @@ infix fun MtbFile.anonymizeContentWith(pseudonymizeService: PseudonymizeService)
return "$prefix$hash" return "$prefix$hash"
} }
this.episode.apply { this.episode?.apply {
id = anonymize(id) id = id?.let {
} anonymize(it)
this.carePlans.onEach { carePlan ->
carePlan.apply {
id = anonymize(id)
diagnosis = anonymize(diagnosis)
geneticCounsellingRequest = anonymize(geneticCounsellingRequest)
rebiopsyRequests = rebiopsyRequests.map { anonymize(it) }
recommendations = recommendations.map { anonymize(it) }
studyInclusionRequests = studyInclusionRequests.map { anonymize(it) }
} }
} }
this.claims.onEach { claim -> this.carePlans?.onEach { carePlan ->
claim.apply { carePlan?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
therapy = anonymize(therapy) diagnosis = diagnosis?.let { anonymize(it) }
geneticCounsellingRequest = geneticCounsellingRequest?.let { anonymize(it) }
rebiopsyRequests = rebiopsyRequests.map { it?.let { anonymize(it) } }
recommendations = recommendations.map { it?.let { anonymize(it) } }
studyInclusionRequests = studyInclusionRequests.map { it?.let { anonymize(it) } }
} }
} }
this.claimResponses.onEach { claimResponse -> this.claims?.onEach { claim ->
claimResponse.apply { claim?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
claim = anonymize(claim) therapy = therapy?.let { anonymize(it) }
} }
} }
this.consent.apply { this.claimResponses?.onEach { claimResponse ->
id = anonymize(id) claimResponse?.apply {
} id = id?.let { anonymize(it) }
this.diagnoses.onEach { diagnosis -> claim = claim?.let { anonymize(it) }
diagnosis.apply {
id = anonymize(id)
histologyResults = histologyResults.map { anonymize(it) }
} }
} }
this.ecogStatus.onEach { ecogStatus -> this.consent?.apply {
ecogStatus.apply { id = id?.let { anonymize(it) }
id = anonymize(id) }
this.diagnoses?.onEach { diagnosis ->
diagnosis?.apply {
id = id?.let { anonymize(it) }
histologyResults = histologyResults?.map { it?.let { anonymize(it) } }
} }
} }
this.familyMemberDiagnoses.onEach { familyMemberDiagnosis -> this.ecogStatus?.onEach { ecogStatus ->
familyMemberDiagnosis.apply { ecogStatus?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
} }
} }
this.geneticCounsellingRequests.onEach { geneticCounsellingRequest -> this.familyMemberDiagnoses?.onEach { familyMemberDiagnosis ->
geneticCounsellingRequest.apply { familyMemberDiagnosis?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
} }
} }
this.histologyReevaluationRequests.onEach { histologyReevaluationRequest -> this.geneticCounsellingRequests?.onEach { geneticCounsellingRequest ->
histologyReevaluationRequest.apply { geneticCounsellingRequest?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
specimen = anonymize(specimen)
} }
} }
this.histologyReports.onEach { histologyReport -> this.histologyReevaluationRequests?.onEach { histologyReevaluationRequest ->
histologyReport.apply { histologyReevaluationRequest?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
specimen = anonymize(specimen) specimen = specimen?.let { anonymize(it) }
tumorMorphology.apply { }
id = anonymize(id) }
specimen = anonymize(specimen) this.histologyReports?.onEach { histologyReport ->
histologyReport?.apply {
id = id?.let { anonymize(it) }
specimen = specimen?.let { anonymize(it) }
tumorMorphology?.apply {
id = id?.let { anonymize(it) }
specimen = specimen?.let { anonymize(it) }
} }
tumorCellContent.apply { tumorCellContent?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
specimen = anonymize(specimen) specimen = specimen?.let { anonymize(it) }
} }
} }
} }
this.lastGuidelineTherapies.onEach { lastGuidelineTherapy -> this.lastGuidelineTherapies?.onEach { lastGuidelineTherapy ->
lastGuidelineTherapy.apply { lastGuidelineTherapy?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
diagnosis = anonymize(diagnosis) diagnosis = diagnosis?.let { anonymize(it) }
} }
} }
this.molecularPathologyFindings.onEach { molecularPathologyFinding -> this.molecularPathologyFindings?.onEach { molecularPathologyFinding ->
molecularPathologyFinding.apply { molecularPathologyFinding?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
specimen = anonymize(specimen) specimen = specimen?.let { anonymize(it) }
} }
} }
this.molecularTherapies.onEach { molecularTherapy -> this.molecularTherapies?.onEach { molecularTherapy ->
molecularTherapy.apply { molecularTherapy?.apply {
history.onEach { history -> history?.onEach { history ->
history.apply { history?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
basedOn = anonymize(basedOn) basedOn = basedOn?.let { anonymize(it) }
} }
} }
} }
} }
this.ngsReports.onEach { ngsReport -> this.ngsReports?.onEach { ngsReport ->
ngsReport.apply { ngsReport?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
specimen = anonymize(specimen) specimen = specimen?.let { anonymize(it) }
tumorCellContent.apply { tumorCellContent?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
specimen = anonymize(specimen) specimen = specimen?.let { anonymize(it) }
} }
simpleVariants.onEach { simpleVariant -> simpleVariants?.onEach { simpleVariant ->
simpleVariant.apply { simpleVariant?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
} }
} }
} }
} }
this.previousGuidelineTherapies.onEach { previousGuidelineTherapy -> this.previousGuidelineTherapies?.onEach { previousGuidelineTherapy ->
previousGuidelineTherapy.apply { previousGuidelineTherapy?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
diagnosis = anonymize(diagnosis) diagnosis = diagnosis?.let { anonymize(it) }
this.medication.forEach { medication -> medication.forEach { medication ->
medication.apply { medication?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
} }
} }
} }
} }
this.rebiopsyRequests.onEach { rebiopsyRequest -> this.rebiopsyRequests?.onEach { rebiopsyRequest ->
rebiopsyRequest.apply { rebiopsyRequest?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
specimen = anonymize(specimen) specimen = specimen?.let { anonymize(it) }
} }
} }
this.recommendations.onEach { recommendation -> this.recommendations?.onEach { recommendation ->
recommendation.apply { recommendation?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
diagnosis = anonymize(diagnosis) diagnosis = diagnosis?.let { anonymize(it) }
ngsReport = anonymize(ngsReport) ngsReport = ngsReport?.let { anonymize(it) }
} }
} }
this.responses.onEach { response -> this.responses?.onEach { response ->
response.apply { response?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
therapy = anonymize(therapy) therapy = therapy?.let { anonymize(it) }
} }
} }
this.studyInclusionRequests.onEach { studyInclusionRequest -> this.studyInclusionRequests?.onEach { studyInclusionRequest ->
studyInclusionRequest.apply { studyInclusionRequest?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
reason = anonymize(reason) reason = reason?.let { anonymize(it) }
} }
} }
this.specimens.onEach { specimen -> this.specimens?.onEach { specimen ->
specimen.apply { specimen?.apply {
id = anonymize(id) id = id?.let { anonymize(it) }
} }
} }
} }

View File

@ -134,7 +134,6 @@ class StatisticsRestController(
@GetMapping(path = ["events"], produces = [MediaType.TEXT_EVENT_STREAM_VALUE]) @GetMapping(path = ["events"], produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun updater(): Flux<ServerSentEvent<Any>> { fun updater(): Flux<ServerSentEvent<Any>> {
return statisticsUpdateProducer.asFlux().flatMap { return statisticsUpdateProducer.asFlux().flatMap {
println(it)
Flux.fromIterable( Flux.fromIterable(
listOf( listOf(
ServerSentEvent.builder<Any>() ServerSentEvent.builder<Any>()

View File

@ -55,6 +55,7 @@
<td th:if="${issue.severity.value == 'info'}" class="bg-blue"><small>[[ ${issue.severity} ]]</small></td> <td th:if="${issue.severity.value == 'info'}" class="bg-blue"><small>[[ ${issue.severity} ]]</small></td>
<td th:if="${issue.severity.value == 'warning'}" class="bg-yellow"><small>[[ ${issue.severity} ]]</small></td> <td th:if="${issue.severity.value == 'warning'}" class="bg-yellow"><small>[[ ${issue.severity} ]]</small></td>
<td th:if="${issue.severity.value == 'error'}" class="bg-red"><small>[[ ${issue.severity} ]]</small></td> <td th:if="${issue.severity.value == 'error'}" class="bg-red"><small>[[ ${issue.severity} ]]</small></td>
<td th:if="${issue.severity.value == 'fatal'}" class="bg-red"><small>[[ ${issue.severity} ]]</small></td>
<td>[[ ${issue.message} ]]</td> <td>[[ ${issue.message} ]]</td>
</tr> </tr>
</tbody> </tbody>

View File

@ -134,4 +134,65 @@ class ExtensionsTest {
.isEqualTo("TESTDOMAIN44e20a53bbbf9f3ae39626d05df7014dcd77d6098") .isEqualTo("TESTDOMAIN44e20a53bbbf9f3ae39626d05df7014dcd77d6098")
} }
@Test
fun shouldNotThrowExceptionOnNullValues(@Mock pseudonymizeService: PseudonymizeService) {
doAnswer {
it.arguments[0]
"PSEUDO-ID"
}.whenever(pseudonymizeService).patientPseudonym(ArgumentMatchers.anyString())
doAnswer {
"TESTDOMAIN"
}.whenever(pseudonymizeService).prefix()
val mtbFile = MtbFile.builder()
.withPatient(
Patient.builder()
.withId("1")
.withBirthDate("2000-08-08")
.withGender(Patient.Gender.MALE)
.build()
)
.withConsent(
Consent.builder()
.withId("1")
.withStatus(Consent.Status.ACTIVE)
.withPatient("123")
.build()
)
.withEpisode(
Episode.builder()
.withId("1")
.withPatient("1")
.withPeriod(PeriodStart("2023-08-08"))
.build()
)
.withClaims(null)
.withDiagnoses(null)
.withCarePlans(null)
.withClaimResponses(null)
.withEcogStatus(null)
.withFamilyMemberDiagnoses(null)
.withGeneticCounsellingRequests(null)
.withHistologyReevaluationRequests(null)
.withHistologyReports(null)
.withLastGuidelineTherapies(null)
.withMolecularPathologyFindings(null)
.withMolecularTherapies(null)
.withNgsReports(null)
.withPreviousGuidelineTherapies(null)
.withRebiopsyRequests(null)
.withRecommendations(null)
.withResponses(null)
.withStudyInclusionRequests(null)
.withSpecimens(null)
.build()
mtbFile.pseudonymizeWith(pseudonymizeService)
mtbFile.anonymizeContentWith(pseudonymizeService)
assertThat(mtbFile.episode.id).isNotNull()
}
} }

View File

@ -43,20 +43,23 @@ class ReportServiceTest {
"issues": [ "issues": [
{ "severity": "info", "message": "Info Message" }, { "severity": "info", "message": "Info Message" },
{ "severity": "warning", "message": "Warning Message" }, { "severity": "warning", "message": "Warning Message" },
{ "severity": "error", "message": "Error Message" } { "severity": "error", "message": "Error Message" },
{ "severity": "fatal", "message": "Fatal Message" }
] ]
} }
""".trimIndent() """.trimIndent()
val actual = this.reportService.deserialize(json) val actual = this.reportService.deserialize(json)
assertThat(actual).hasSize(3) assertThat(actual).hasSize(4)
assertThat(actual[0].severity).isEqualTo(ReportService.Severity.ERROR) assertThat(actual[0].severity).isEqualTo(ReportService.Severity.FATAL)
assertThat(actual[0].message).isEqualTo("Error Message") assertThat(actual[0].message).isEqualTo("Fatal Message")
assertThat(actual[1].severity).isEqualTo(ReportService.Severity.WARNING) assertThat(actual[1].severity).isEqualTo(ReportService.Severity.ERROR)
assertThat(actual[1].message).isEqualTo("Warning Message") assertThat(actual[1].message).isEqualTo("Error Message")
assertThat(actual[2].severity).isEqualTo(ReportService.Severity.INFO) assertThat(actual[2].severity).isEqualTo(ReportService.Severity.WARNING)
assertThat(actual[2].message).isEqualTo("Info Message") assertThat(actual[2].message).isEqualTo("Warning Message")
assertThat(actual[3].severity).isEqualTo(ReportService.Severity.INFO)
assertThat(actual[3].message).isEqualTo("Info Message")
} }
@Test @Test