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

give group and submitter information to the preprocessing pipeline #2268

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
Original file line number Diff line number Diff line change
Expand Up @@ -213,10 +213,24 @@ data class SequenceEntryStatus(
val dataUseTerms: DataUseTerms,
) : AccessionVersionInterface

data class EditedSequenceEntryData(
@Schema(example = "LOC_000S01D") override val accession: Accession,
@Schema(example = "1") override val version: Version,
val data: OriginalData<GeneticSequence>,
) : AccessionVersionInterface

data class UnprocessedData(
@Schema(example = "123") override val accession: Accession,
@Schema(example = "LOC_000S01D") override val accession: Accession,
@Schema(example = "1") override val version: Version,
val data: OriginalData<GeneticSequence>,
@Schema(description = "The submission id that was used in the upload to link metadata and sequences")
val submissionId: String,
@Schema(description = "The username of the submitter")
val submitter: String,
@Schema(example = "42", description = "The id of the group that this sequence entry was submitted by")
val groupId: Int,
@Schema(example = "1720304713", description = "Unix timestamp in seconds")
val submittedAt: Long,
) : AccessionVersionInterface

data class OriginalData<SequenceType>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import org.loculus.backend.api.Accessions
import org.loculus.backend.api.CompressionFormat
import org.loculus.backend.api.DataUseTerms
import org.loculus.backend.api.DataUseTermsType
import org.loculus.backend.api.EditedSequenceEntryData
import org.loculus.backend.api.ExternalSubmittedData
import org.loculus.backend.api.GetSequenceResponse
import org.loculus.backend.api.Organism
Expand Down Expand Up @@ -284,8 +285,8 @@ class SubmissionController(
@PathVariable @Valid
organism: Organism,
@HiddenParam authenticatedUser: AuthenticatedUser,
@RequestBody accessionVersion: UnprocessedData,
) = submissionDatabaseService.submitEditedData(authenticatedUser, accessionVersion, organism)
@RequestBody editedSequenceEntryData: EditedSequenceEntryData,
) = submissionDatabaseService.submitEditedData(authenticatedUser, editedSequenceEntryData, organism)

@Operation(description = GET_SEQUENCES_DESCRIPTION)
@GetMapping("/get-sequences", produces = [MediaType.APPLICATION_JSON_VALUE])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ import com.fasterxml.jackson.databind.node.LongNode
import com.fasterxml.jackson.databind.node.NullNode
import com.fasterxml.jackson.databind.node.TextNode
import kotlinx.datetime.Clock
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import mu.KotlinLogging
import org.loculus.backend.api.DataUseTerms
Expand All @@ -21,6 +19,7 @@ import org.loculus.backend.service.submission.RawProcessedData
import org.loculus.backend.service.submission.SubmissionDatabaseService
import org.loculus.backend.utils.Accession
import org.loculus.backend.utils.Version
import org.loculus.backend.utils.toTimestamp
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

Expand Down Expand Up @@ -118,5 +117,3 @@ class ReleasedDataModel(
return SiloVersionStatus.REVISED
}
}

private fun LocalDateTime.toTimestamp() = this.toInstant(TimeZone.UTC).epochSeconds
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import org.loculus.backend.api.ApproveDataScope
import org.loculus.backend.api.DataUseTerms
import org.loculus.backend.api.DataUseTermsType
import org.loculus.backend.api.DeleteSequenceScope
import org.loculus.backend.api.EditedSequenceEntryData
import org.loculus.backend.api.ExternalSubmittedData
import org.loculus.backend.api.GeneticSequence
import org.loculus.backend.api.GetSequenceResponse
Expand All @@ -63,6 +64,7 @@ import org.loculus.backend.service.groupmanagement.GroupManagementDatabaseServic
import org.loculus.backend.service.groupmanagement.GroupManagementPreconditionValidator
import org.loculus.backend.utils.Accession
import org.loculus.backend.utils.Version
import org.loculus.backend.utils.toTimestamp
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
Expand Down Expand Up @@ -126,7 +128,15 @@ class SubmissionDatabaseService(
val preprocessing = SequenceEntriesPreprocessedDataTable

return table
.select(table.accessionColumn, table.versionColumn, table.originalDataColumn)
.select(
table.accessionColumn,
table.versionColumn,
table.originalDataColumn,
table.submissionIdColumn,
table.submitterColumn,
table.groupIdColumn,
table.submittedAtColumn,
)
.where {
table.organismIs(organism) and
not(table.isRevocationColumn) and
Expand All @@ -146,12 +156,16 @@ class SubmissionDatabaseService(
.map { chunk ->
val chunkOfUnprocessedData = chunk.map {
UnprocessedData(
it[table.accessionColumn],
it[table.versionColumn],
compressionService.decompressSequencesInOriginalData(
accession = it[table.accessionColumn],
version = it[table.versionColumn],
data = compressionService.decompressSequencesInOriginalData(
it[table.originalDataColumn]!!,
organism,
),
submissionId = it[table.submissionIdColumn],
submitter = it[table.submitterColumn],
groupId = it[table.groupIdColumn],
submittedAt = it[table.submittedAtColumn].toTimestamp(),
)
}
updateStatusToProcessing(chunkOfUnprocessedData, pipelineVersion)
Expand Down Expand Up @@ -782,35 +796,35 @@ class SubmissionDatabaseService(

fun submitEditedData(
authenticatedUser: AuthenticatedUser,
editedAccessionVersion: UnprocessedData,
editedSequenceEntryData: EditedSequenceEntryData,
organism: Organism,
) {
log.info { "edited sequence entry submitted $editedAccessionVersion" }
log.info { "edited sequence entry submitted $editedSequenceEntryData" }

accessionPreconditionValidator.validate {
thatAccessionVersionExists(editedAccessionVersion)
thatAccessionVersionExists(editedSequenceEntryData)
.andThatUserIsAllowedToEditSequenceEntries(authenticatedUser)
.andThatSequenceEntriesAreInStates(listOf(Status.AWAITING_APPROVAL, Status.HAS_ERRORS))
.andThatOrganismIs(organism)
}

SequenceEntriesTable.update(
where = {
SequenceEntriesTable.accessionVersionIsIn(listOf(editedAccessionVersion))
SequenceEntriesTable.accessionVersionIsIn(listOf(editedSequenceEntryData))
},
) {
it[originalDataColumn] = compressionService
.compressSequencesInOriginalData(editedAccessionVersion.data, organism)
.compressSequencesInOriginalData(editedSequenceEntryData.data, organism)
}

SequenceEntriesPreprocessedDataTable.deleteWhere {
accessionVersionEquals(editedAccessionVersion)
accessionVersionEquals(editedSequenceEntryData)
}

auditLogger.log(
authenticatedUser.username,
"Edited sequence: " +
editedAccessionVersion.displayAccessionVersion(),
editedSequenceEntryData.displayAccessionVersion(),
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.loculus.backend.utils

import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime

fun LocalDateTime.toTimestamp() = this.toInstant(TimeZone.UTC).epochSeconds

fun LocalDateTime.toUtcDateString(): String = this.toInstant(TimeZone.currentSystemDefault())
.toLocalDateTime(TimeZone.UTC)
.date
.toString()
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.CoreMatchers.hasItem
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.containsInAnyOrder
import org.hamcrest.Matchers.empty
import org.hamcrest.Matchers.greaterThan
import org.hamcrest.Matchers.hasProperty
import org.hamcrest.Matchers.hasSize
import org.hamcrest.Matchers.matchesRegex
import org.junit.jupiter.api.Test
import org.loculus.backend.api.Status.IN_PROCESSING
import org.loculus.backend.api.Status.RECEIVED
import org.loculus.backend.api.UnprocessedData
import org.loculus.backend.config.BackendSpringProperty
import org.loculus.backend.controller.DEFAULT_ORGANISM
import org.loculus.backend.controller.DEFAULT_USER_NAME
import org.loculus.backend.controller.EndpointTest
import org.loculus.backend.controller.OTHER_ORGANISM
import org.loculus.backend.controller.assertStatusIs
Expand Down Expand Up @@ -65,15 +69,24 @@ class ExtractUnprocessedDataEndpointTest(

@Test
fun `WHEN extracting unprocessed data THEN only previously not extracted sequence entries are returned`() {
val accessionVersions = convenienceClient.submitDefaultFiles().submissionIdMappings
val submissionResult = convenienceClient.submitDefaultFiles()
val accessionVersions = submissionResult.submissionIdMappings

val result7 = client.extractUnprocessedData(7)
val responseBody7 = result7.expectNdjsonAndGetContent<UnprocessedData>()
assertThat(responseBody7, hasSize(7))
assertThat(
responseBody7,
hasItem(
UnprocessedData(accessionVersions.first().accession, 1, defaultOriginalData),
allOf(
hasProperty<UnprocessedData>("accession", `is`(accessionVersions[0].accession)),
hasProperty("version", `is`(1L)),
hasProperty("data", `is`(defaultOriginalData)),
hasProperty("submissionId", matchesRegex("custom[0-9]")),
hasProperty("submitter", `is`(DEFAULT_USER_NAME)),
hasProperty("groupId", `is`(submissionResult.groupId)),
hasProperty("submittedAt", greaterThan(1_700_000_000L)),
),
),
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package org.loculus.backend.controller.submission
import org.hamcrest.CoreMatchers.containsString
import org.hamcrest.CoreMatchers.hasItem
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.hasProperty
import org.hamcrest.Matchers.hasSize
import org.hamcrest.Matchers.`is`
import org.junit.jupiter.api.Test
Expand Down Expand Up @@ -102,10 +104,9 @@ class ReviseEndpointTest(
assertThat(
responseBody,
hasItem(
UnprocessedData(
accession = accessions.first(),
version = 2,
data = defaultOriginalData,
allOf(
hasProperty<UnprocessedData>("accession", `is`(accessions.first())),
hasProperty("version", `is`(2L)),
),
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ import org.loculus.backend.api.AccessionVersionInterface
import org.loculus.backend.api.ApproveDataScope
import org.loculus.backend.api.DataUseTerms
import org.loculus.backend.api.DeleteSequenceScope
import org.loculus.backend.api.EditedSequenceEntryData
import org.loculus.backend.api.ExternalSubmittedData
import org.loculus.backend.api.Status
import org.loculus.backend.api.SubmittedProcessedData
import org.loculus.backend.api.UnprocessedData
import org.loculus.backend.api.WarningsFilter
import org.loculus.backend.controller.DEFAULT_EXTERNAL_METADATA_UPDATER
import org.loculus.backend.controller.DEFAULT_GROUP_NAME
Expand Down Expand Up @@ -147,7 +147,7 @@ class SubmissionControllerClient(private val mockMvc: MockMvc, private val objec
)

fun submitEditedSequenceEntryVersion(
editedData: UnprocessedData,
editedData: EditedSequenceEntryData,
organism: String = DEFAULT_ORGANISM,
jwt: String? = jwtForDefaultUser,
): ResultActions = mockMvc.perform(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import org.loculus.backend.api.AccessionVersionInterface
import org.loculus.backend.api.AccessionVersionOriginalMetadata
import org.loculus.backend.api.ApproveDataScope
import org.loculus.backend.api.DataUseTerms
import org.loculus.backend.api.EditedSequenceEntryData
import org.loculus.backend.api.GeneticSequence
import org.loculus.backend.api.GetSequenceResponse
import org.loculus.backend.api.Organism
Expand Down Expand Up @@ -284,7 +285,7 @@ class SubmissionConvenienceClient(
fun submitDefaultEditedData(accessions: List<Accession>, userName: String = DEFAULT_USER_NAME) {
accessions.forEach { accession ->
client.submitEditedSequenceEntryVersion(
UnprocessedData(accession, 1L, defaultOriginalData),
EditedSequenceEntryData(accession, 1L, defaultOriginalData),
jwt = generateJwtFor(userName),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers.`is`
import org.hamcrest.Matchers.not
import org.junit.jupiter.api.Test
import org.loculus.backend.api.EditedSequenceEntryData
import org.loculus.backend.api.Status
import org.loculus.backend.api.UnprocessedData
import org.loculus.backend.controller.DEFAULT_USER_NAME
import org.loculus.backend.controller.EndpointTest
import org.loculus.backend.controller.OTHER_ORGANISM
Expand All @@ -29,7 +29,7 @@ class SubmitEditedSequenceEntryVersionEndpointTest(
fun `GIVEN invalid authorization token THEN returns 401 Unauthorized`() {
expectUnauthorizedResponse(isModifyingRequest = true) {
client.submitEditedSequenceEntryVersion(
generateUnprocessedData("1"),
generateEditedData("1"),
jwt = it,
)
}
Expand All @@ -42,7 +42,7 @@ class SubmitEditedSequenceEntryVersionEndpointTest(
convenienceClient.getSequenceEntry(accession = accessions.first(), version = 1)
.assertStatusIs(Status.HAS_ERRORS)

val editedData = generateUnprocessedData(accessions.first())
val editedData = generateEditedData(accessions.first())
client.submitEditedSequenceEntryVersion(editedData)
.andExpect(status().isNoContent)

Expand All @@ -57,7 +57,7 @@ class SubmitEditedSequenceEntryVersionEndpointTest(
convenienceClient.getSequenceEntry(accession = accessions.first(), version = 1)
.assertStatusIs(Status.AWAITING_APPROVAL)

val editedData = generateUnprocessedData(accessions.first())
val editedData = generateEditedData(accessions.first())

client.submitEditedSequenceEntryVersion(editedData)
.andExpect(status().isNoContent)
Expand All @@ -76,7 +76,7 @@ class SubmitEditedSequenceEntryVersionEndpointTest(
.find { it.accession == firstAccession && it.version == 1L }!!
assertThat(entryBeforeEdit.originalMetadata, `is`(not(anEmptyMap())))

val editedData = generateUnprocessedData(firstAccession)
val editedData = generateEditedData(firstAccession)

client.submitEditedSequenceEntryVersion(editedData)
.andExpect(status().isNoContent)
Expand All @@ -93,7 +93,7 @@ class SubmitEditedSequenceEntryVersionEndpointTest(
convenienceClient.getSequenceEntry(accession = accessions.first(), version = 1)
.assertStatusIs(Status.HAS_ERRORS)

val editedDataWithNonExistingVersion = generateUnprocessedData(accessions.first(), version = 2)
val editedDataWithNonExistingVersion = generateEditedData(accessions.first(), version = 2)
val sequenceString = editedDataWithNonExistingVersion.displayAccessionVersion()

client.submitEditedSequenceEntryVersion(editedDataWithNonExistingVersion)
Expand All @@ -113,7 +113,7 @@ class SubmitEditedSequenceEntryVersionEndpointTest(

val nonExistingAccession = "nonExistingAccession"

val editedDataWithNonExistingAccession = generateUnprocessedData(nonExistingAccession)
val editedDataWithNonExistingAccession = generateEditedData(nonExistingAccession)

client.submitEditedSequenceEntryVersion(editedDataWithNonExistingAccession)
.andExpect(status().isUnprocessableEntity)
Expand All @@ -134,7 +134,7 @@ class SubmitEditedSequenceEntryVersionEndpointTest(
convenienceClient.getSequenceEntry(accession = accessions.first(), version = 1)
.assertStatusIs(Status.HAS_ERRORS)

val editedData = generateUnprocessedData(accessions.first())
val editedData = generateEditedData(accessions.first())

client.submitEditedSequenceEntryVersion(editedData, organism = OTHER_ORGANISM)
.andExpect(status().isUnprocessableEntity)
Expand All @@ -156,7 +156,7 @@ class SubmitEditedSequenceEntryVersionEndpointTest(
convenienceClient.getSequenceEntry(accession = accessions.first(), version = 1)
.assertStatusIs(Status.HAS_ERRORS)

val editedDataFromWrongSubmitter = generateUnprocessedData(accessions.first())
val editedDataFromWrongSubmitter = generateEditedData(accessions.first())
val nonExistingUser = "whoseNameMayNotBeMentioned"

client.submitEditedSequenceEntryVersion(editedDataFromWrongSubmitter, jwt = generateJwtFor(nonExistingUser))
Expand All @@ -175,15 +175,15 @@ class SubmitEditedSequenceEntryVersionEndpointTest(
.prepareDataTo(Status.HAS_ERRORS, username = DEFAULT_USER_NAME)
.first()

val editedData = generateUnprocessedData(accessionVersion.accession, accessionVersion.version)
val editedData = generateEditedData(accessionVersion.accession, accessionVersion.version)
client.submitEditedSequenceEntryVersion(editedData, jwt = jwtForSuperUser)
.andExpect(status().isNoContent)

convenienceClient.getSequenceEntry(accession = accessionVersion.accession, version = accessionVersion.version)
.assertStatusIs(Status.RECEIVED)
}

private fun generateUnprocessedData(accession: String, version: Long = 1) = UnprocessedData(
private fun generateEditedData(accession: String, version: Long = 1) = EditedSequenceEntryData(
accession = accession,
version = version,
data = emptyOriginalData,
Expand Down
Loading