diff --git a/backend/docs/plantuml/sequenceInitialSubmission.svg b/backend/docs/plantuml/sequenceInitialSubmission.svg index 2ff8ce7601..afb0563ede 100644 --- a/backend/docs/plantuml/sequenceInitialSubmission.svg +++ b/backend/docs/plantuml/sequenceInitialSubmission.svg @@ -1 +1 @@ -Loculus Frontend / UserLoculus Frontend / User(Pre-)Processing Pipeline(Pre-)Processing PipelineLoculus BackendLoculus BackendDatabaseDatabasesubmit initial sequence dataalt[Sequence and Metadata Files are valid]response with accessionsStore initial sequence dataset status to 'RECEIVED'[Sequence and Metadata Files are invalid]response with errorIn the following we assume initial raw sequence data are validloop[until no processing errors]Query initial/edited sequence dataQuery initial/edited sequence dataSet status from 'RECEIVED'/'HAS_ERRORS' to 'IN_PROCESSING'initial/edited sequence datainitial/edited sequence datasubmit processed sequence dataalt[validation errors occur]response with validation errorsValidation error are probably only redeemable by changing the pre-processing pipelineIn the following we assume that no validation error occurredalt[processed sequence data contain processing errors]Store processed sequence data with errorsset status to 'HAS_ERRORS'Store processed sequence data without errorsset status to 'AWAITING_APPROVAL'query processed sequence data with errorsaddress errors and edit sequence datasubmit edited sequence datastore edited sequence dataset status to 'RECEIVED'Query processed sequence dataQuery processed sequence dataprocessed sequence dataprocessed sequence dataapprove processed sequence dataset status from 'AWAITING_APPROVAL' to 'APPROVED_FOR_RELEASE' \ No newline at end of file +Loculus Frontend / UserLoculus Frontend / User(Pre-)Processing Pipeline(Pre-)Processing PipelineLoculus BackendLoculus BackendDatabaseDatabasesubmit initial sequence dataalt[Sequence and Metadata Files are valid]response with accessionsStore initial sequence dataset status to 'RECEIVED'[Sequence and Metadata Files are invalid]response with errorIn the following we assume initial raw sequence data are validloop[until no processing errors]Query initial/edited sequence dataQuery initial/edited sequence dataSet status from 'RECEIVED'/'HAS_ERRORS' to 'IN_PROCESSING'initial/edited sequence datainitial/edited sequence datasubmit processed sequence dataalt[validation errors occur]response with validation errorsValidation error are probably only redeemable by changing the pre-processing pipelineIn the following we assume that no validation error occurredalt[processed sequence data contain processing errors]Store processed sequence data with errorsset status to 'HAS_ERRORS'Store processed sequence data without errorsset status to 'AWAITING_APPROVAL'query processed sequence data with errorsaddress errors and edit sequence datasubmit edited sequence datastore edited sequence dataset status to 'RECEIVED'Query processed sequence dataQuery processed sequence dataprocessed sequence dataprocessed sequence dataapprove processed sequence dataset status from 'AWAITING_APPROVAL' to 'APPROVED_FOR_RELEASE' \ No newline at end of file diff --git a/backend/docs/plantuml/sequencePersistingData.svg b/backend/docs/plantuml/sequencePersistingData.svg index 0c6d2440ce..4f3b85e3ba 100644 --- a/backend/docs/plantuml/sequencePersistingData.svg +++ b/backend/docs/plantuml/sequencePersistingData.svg @@ -1 +1 @@ -Loculus Frontend / UserLoculus Frontend / UserLoculus BackendLoculus BackendSequenceentry tableSequenceentry tableAuxiliarymetadata tableAuxiliarymetadata tableAuxiliarysequence tableAuxiliarysequence tablesubmit initial/revised sequence dataStore metadata in auxiliary tableSend list of submissionIDs of submitted metadataStore sequence data in auxiliary tableSend list of submissionIDs of submitted sequence dataalt[for revisions additionally:]Send list of new versions by matching given accessionsCheck if all sequence data and metadata are consistentalt[data consistent*]Merge and Copy to prodDBMerge metadata and sequence dataCopy merged data to production DBSend list of accession versions of new sequence entriesSend list of accession versions of new sequence entries[data inconsistent*]Send error messageDelete temporary metadataDelete temporary sequence data(*) data consistency:- each metadata entry has a unique submission ID- each metadata entry has at least one corresponding sequence entry- each sequence entry has exactly one corresponding metadata entry- (for revision) submitted accession versions exists \ No newline at end of file +Loculus Frontend / UserLoculus Frontend / UserLoculus BackendLoculus BackendSequenceentry tableSequenceentry tableAuxiliarymetadata tableAuxiliarymetadata tableAuxiliarysequence tableAuxiliarysequence tablesubmit initial/revised sequence dataStore metadata in auxiliary tableSend list of submissionIDs of submitted metadataStore sequence data in auxiliary tableSend list of submissionIDs of submitted sequence dataalt[for revisions additionally:]Send list of new versions by matching given accessionsCheck if all sequence data and metadata are consistentalt[data consistent*]Merge and Copy to prodDBMerge metadata and sequence dataCopy merged data to production DBSend list of accession versions of new sequence entriesSend list of accession versions of new sequence entries[data inconsistent*]Send error messageDelete temporary metadataDelete temporary sequence data(*) data consistency:- each metadata entry has a unique submission ID- each metadata entry has at least one corresponding sequence entry- each sequence entry has exactly one corresponding metadata entry- (for revision) submitted accession versions exists \ No newline at end of file diff --git a/backend/docs/plantuml/sequenceRevision.svg b/backend/docs/plantuml/sequenceRevision.svg index ac289146a0..ca31dcecf0 100644 --- a/backend/docs/plantuml/sequenceRevision.svg +++ b/backend/docs/plantuml/sequenceRevision.svg @@ -1 +1 @@ -Loculus Frontend / UserLoculus Frontend / UserLoculus BackendLoculus BackendDatabaseDatabasesubmit corrected data for existing accessionsalt[accession exists and highest version is 'APPROVED_FOR_RELEASE']insert new version in status 'RECEIVED'response with accession + new version numberresponse with error messageAt this point the same flow applies as if this was an initial submission \ No newline at end of file +Loculus Frontend / UserLoculus Frontend / UserLoculus BackendLoculus BackendDatabaseDatabasesubmit corrected data for existing accessionsalt[accession exists and highest version is 'APPROVED_FOR_RELEASE']insert new version in status 'RECEIVED'response with accession + new version numberresponse with error messageAt this point the same flow applies as if this was an initial submission \ No newline at end of file diff --git a/backend/docs/plantuml/sequenceRevocation.puml b/backend/docs/plantuml/sequenceRevocation.puml index 0ef07abb2d..598755526c 100644 --- a/backend/docs/plantuml/sequenceRevocation.puml +++ b/backend/docs/plantuml/sequenceRevocation.puml @@ -6,7 +6,7 @@ frontend -> backend: wants to revoke already processed sequence alt accession exists and highest version is 'APPROVED_FOR_RELEASE' - backend -> DB: insert new version\nwith status 'AWAITING_APPROVAL_FOR_REVOCATION' \nand ' is_revocation = true' + backend -> DB: insert new version\nwith status 'AWAITING_APPROVAL' \nand ' is_revocation = true' backend -> frontend: response with accession + new version number else backend -> frontend: response with error message @@ -17,6 +17,6 @@ backend -> DB: set status to 'APPROVED_FOR_RELEASE' else frontend -> backend: cancels revocation - backend -> DB: delete latest version \nof the sequence and Status \n'AWAITING_APPROVAL_FOR_REVOCATION' + backend -> DB: delete latest version \nof the sequence and Status \n'AWAITING_APPROVAL' end @enduml diff --git a/backend/docs/plantuml/sequenceRevocation.svg b/backend/docs/plantuml/sequenceRevocation.svg index 71426b5a56..e3f08dbf1f 100644 --- a/backend/docs/plantuml/sequenceRevocation.svg +++ b/backend/docs/plantuml/sequenceRevocation.svg @@ -1 +1 @@ -Loculus Frontend / UserLoculus Frontend / UserLoculus BackendLoculus BackendDatabaseDatabasewants to revoke already processed sequencealt[accession exists and highest version is 'APPROVED_FOR_RELEASE']insert new versionwith status 'AWAITING_APPROVAL_FOR_REVOCATION'and ' is_revocation = true'response with accession + new version numberresponse with error messagealtconfirms revocationset status to 'APPROVED_FOR_RELEASE'cancels revocationdelete latest versionof the sequence and Status'AWAITING_APPROVAL_FOR_REVOCATION' \ No newline at end of file +Loculus Frontend / UserLoculus Frontend / UserLoculus BackendLoculus BackendDatabaseDatabasewants to revoke already processed sequencealt[accession exists and highest version is 'APPROVED_FOR_RELEASE']insert new versionwith status 'AWAITING_APPROVAL'and ' is_revocation = true'response with accession + new version numberresponse with error messagealtconfirms revocationset status to 'APPROVED_FOR_RELEASE'cancels revocationdelete latest versionof the sequence and Status'AWAITING_APPROVAL' \ No newline at end of file diff --git a/backend/docs/plantuml/statusChange.puml b/backend/docs/plantuml/statusChange.puml index bda1d0ddc5..99e9ded444 100644 --- a/backend/docs/plantuml/statusChange.puml +++ b/backend/docs/plantuml/statusChange.puml @@ -16,7 +16,6 @@ } ' status states - state AWAITING_APPROVAL_FOR_REVOCATION state RECEIVED state IN_PROCESSING state HAS_ERRORS @@ -39,8 +38,7 @@ CREATING_REVISE --> RECEIVED REVOKED_DATA --> CREATING_REVOKE : user initiates revocation - CREATING_REVOKE --> AWAITING_APPROVAL_FOR_REVOCATION - AWAITING_APPROVAL_FOR_REVOCATION --> is_approved + CREATING_REVOKE --> AWAITING_APPROVAL RECEIVED --> IN_PROCESSING : preprocessing starts IN_PROCESSING --> is_error diff --git a/backend/docs/plantuml/statusChange.svg b/backend/docs/plantuml/statusChange.svg index d30d19ecc1..63e24620ac 100644 --- a/backend/docs/plantuml/statusChange.svg +++ b/backend/docs/plantuml/statusChange.svg @@ -1 +1 @@ -initial datanew unprocessed datarevision dataalready existing sequencewith status APPROVED_FOR_RELEASE+ new unprocessed datarevocation dataalready existing sequencewith status APPROVED_FOR_RELEASEAWAITING_APPROVAL_FOR_REVOCATIONRECEIVEDIN_PROCESSINGHAS_ERRORSAPPROVED_FOR_RELEASEAWAITING_APPROVALentry with new accessionwill be createdentry with existing accessionand incremented version numberwill be createdentry with existing accession,incremented version number,and is_revocation=true will be createdentry will be deletedinitial submissionuser initiates revisionuser initiates revocationpreprocessing startsdata contain error(s)user edits datauser edits datapreprocessing successfuluser approvesor automatic approvalvia 'release_directly'user rejects \ No newline at end of file +initial datanew unprocessed datarevision dataalready existing sequencewith status APPROVED_FOR_RELEASE+ new unprocessed datarevocation dataalready existing sequencewith status APPROVED_FOR_RELEASERECEIVEDIN_PROCESSINGHAS_ERRORSAPPROVED_FOR_RELEASEAWAITING_APPROVALentry with new accessionwill be createdentry with existing accessionand incremented version numberwill be createdentry with existing accession,incremented version number,and is_revocation=true will be createdentry will be deletedinitial submissionuser initiates revisionuser initiates revocationpreprocessing startsdata contain error(s)user edits datauser edits datapreprocessing successfuluser approvesor automatic approvalvia 'release_directly'user rejects \ No newline at end of file diff --git a/backend/docs/runtime_view.md b/backend/docs/runtime_view.md index f771b48943..f9992de8ef 100644 --- a/backend/docs/runtime_view.md +++ b/backend/docs/runtime_view.md @@ -42,9 +42,10 @@ When submitting a sequence entry, it goes through a series of statuses: **Has_errors**: The sequence entry contains errors that prevent a release. It must be edited and resubmitted by the submitter. -**Awaiting_approval**: The sequence entry was successfully processed by the preprocessing pipeline and can be released. The submitter has to approve the release. - -**Awaiting_approval_for_revocation**: The revocation request was received. The submitter has to approve the revocation. +**Awaiting_approval**: +The sequence entry was successfully processed by the preprocessing pipeline and can be released +or a revocation was submitted. +The submitter has to approve the release. **Approved_for_release**: The sequence entry was approved for release. It is or will shortly be released. @@ -92,10 +93,10 @@ In following, the changes of the databases are shown given a series of example e **Event 2:** The preprocessing pipeline processes the two sequence entries and found no errors. -| accession | version | submitter | submitted_at | started_processing_at | finished_processing_at | approved_at | status | is_revocation | original_data | processed_data | errors | warnings | -|-----------|---------|-----------|--------------|-----------------------|------------------------|-------------|---------|---------------|---------------|----------------|--------|----------| -| 1 | 1 | user1 | t1 | t2 | t3 | | STAGING | false | d1 | ... | [] | [] | -| 2 | 1 | user1 | t1 | t2 | t3 | | STAGING | false | d2 | ... | [] | [] | +| accession | version | submitter | submitted_at | started_processing_at | finished_processing_at | approved_at | status | is_revocation | original_data | processed_data | errors | warnings | +|-----------|---------|-----------|--------------|-----------------------|------------------------|-------------|-------------------|---------------|---------------|----------------|--------|----------| +| 1 | 1 | user1 | t1 | t2 | t3 | | AWAITING_APPROVAL | false | d1 | ... | [] | [] | +| 2 | 1 | user1 | t1 | t2 | t3 | | AWAITING_APPROVAL | false | d2 | ... | [] | [] | **Event 3:** The user approves accession 1 and rejects accession 2. @@ -118,7 +119,7 @@ In following, the changes of the databases are shown given a series of example e | accession | version | submitter | submitted_at | started_processing_at | finished_processing_at | approved_at | status | is_revocation | original_data | processed_data | errors | warnings | |-----------|---------|-----------|--------------|-----------------------|------------------------|-------------|----------------------|---------------|---------------|----------------|--------|----------| | 1 | 1 | user1 | t1 | t2 | t3 | t4 | APPROVED_FOR_RELEASE | false | d1 | ... | [] | [] | -| 1 | 2 | user1 | t5 | t6 | t7 | | STAGING | false | d3 | ... | [] | [] | +| 1 | 2 | user1 | t5 | t6 | t7 | | AWAITING_APPROVAL | false | d3 | ... | [] | [] | **Event 6:** The user approves the revision. @@ -129,11 +130,11 @@ In following, the changes of the databases are shown given a series of example e **Event 7:** The user revokes accession 1. -| accession | version | submitter | submitted_at | started_processing_at | finished_processing_at | approved_at | status | is_revocation | original_data | processed_data | errors | warnings | -|-----------|---------|-----------|--------------|-----------------------|------------------------|-------------|----------------------------------|---------------|---------------|----------------|--------|----------| -| 1 | 1 | user1 | t1 | t2 | t3 | t4 | APPROVED_FOR_RELEASE | false | d1 | ... | [] | [] | -| 1 | 2 | user1 | t5 | t6 | t7 | t8 | APPROVED_FOR_RELEASE | false | d3 | ... | [] | [] | -| 1 | 3 | user1 | t9 | | | | AWAITING_APPROVAL_FOR_REVOCATION | true | | | | | +| accession | version | submitter | submitted_at | started_processing_at | finished_processing_at | approved_at | status | is_revocation | original_data | processed_data | errors | warnings | +|-----------|---------|-----------|--------------|-----------------------|------------------------|-------------|----------------------|---------------|---------------|----------------|--------|----------| +| 1 | 1 | user1 | t1 | t2 | t3 | t4 | APPROVED_FOR_RELEASE | false | d1 | ... | [] | [] | +| 1 | 2 | user1 | t5 | t6 | t7 | t8 | APPROVED_FOR_RELEASE | false | d3 | ... | [] | [] | +| 1 | 3 | user1 | t9 | | | | AWAITING_APPROVAL | true | | | | | **Event 8:** The user rejects the revocation of accession 1. @@ -144,11 +145,11 @@ In following, the changes of the databases are shown given a series of example e **Event 9:** The user revokes accession 1 again. -| accession | version | submitter | submitted_at | started_processing_at | finished_processing_at | approved_at | status | is_revocation | original_data | processed_data | errors | warnings | -|-----------|---------|-----------|--------------|-----------------------|------------------------|-------------|----------------------------------|---------------|---------------|----------------|--------|----------| -| 1 | 1 | user1 | t1 | t2 | t3 | t4 | APPROVED_FOR_RELEASE | false | d1 | ... | [] | [] | -| 1 | 2 | user1 | t5 | t6 | t7 | t8 | APPROVED_FOR_RELEASE | false | d3 | ... | [] | [] | -| 1 | 3 | user1 | t10 | | | | AWAITING_APPROVAL_FOR_REVOCATION | true | | | | | +| accession | version | submitter | submitted_at | started_processing_at | finished_processing_at | approved_at | status | is_revocation | original_data | processed_data | errors | warnings | +|-----------|---------|-----------|--------------|-----------------------|------------------------|-------------|----------------------|---------------|---------------|----------------|--------|----------| +| 1 | 1 | user1 | t1 | t2 | t3 | t4 | APPROVED_FOR_RELEASE | false | d1 | ... | [] | [] | +| 1 | 2 | user1 | t5 | t6 | t7 | t8 | APPROVED_FOR_RELEASE | false | d3 | ... | [] | [] | +| 1 | 3 | user1 | t10 | | | | AWAITING_APPROVAL | true | | | | | **Event 10:** The user approves the revocation. diff --git a/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt b/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt index 106f550640..5bb873a178 100644 --- a/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt +++ b/backend/src/main/kotlin/org/loculus/backend/api/SubmissionTypes.kt @@ -250,10 +250,6 @@ enum class Status { @JsonProperty("APPROVED_FOR_RELEASE") APPROVED_FOR_RELEASE, - - @JsonProperty("AWAITING_APPROVAL_FOR_REVOCATION") - AWAITING_APPROVAL_FOR_REVOCATION, - ; companion object { diff --git a/backend/src/main/kotlin/org/loculus/backend/controller/ExceptionHandler.kt b/backend/src/main/kotlin/org/loculus/backend/controller/ExceptionHandler.kt index 942e144acb..56ea2ba2d4 100644 --- a/backend/src/main/kotlin/org/loculus/backend/controller/ExceptionHandler.kt +++ b/backend/src/main/kotlin/org/loculus/backend/controller/ExceptionHandler.kt @@ -37,7 +37,7 @@ class ExceptionHandler : ResponseEntityExceptionHandler() { @ExceptionHandler(ConstraintViolationException::class, BadRequestException::class) @ResponseStatus(HttpStatus.BAD_REQUEST) fun handleBadRequestException(e: Exception): ResponseEntity { - log.warn(e) { "Caught ${e.javaClass}: ${e.message}" } + log.info { "Caught ${e.javaClass}: ${e.message}" } return responseEntity( HttpStatus.BAD_REQUEST, @@ -57,7 +57,7 @@ class ExceptionHandler : ResponseEntityExceptionHandler() { ], ) fun handleUnauthorizedException(e: Exception): ResponseEntity { - log.warn(e) { "Caught ${e.javaClass}: ${e.message}" } + log.info { "Caught ${e.javaClass}: ${e.message}" } return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build() } @@ -69,7 +69,7 @@ class ExceptionHandler : ResponseEntityExceptionHandler() { ) @ResponseStatus(HttpStatus.UNPROCESSABLE_ENTITY) fun handleUnprocessableEntityException(e: Exception): ResponseEntity { - log.warn(e) { "Caught unprocessable entity exception: ${e.message}" } + log.info { "Caught unprocessable entity exception: ${e.message}" } return responseEntity( HttpStatus.UNPROCESSABLE_ENTITY, @@ -80,7 +80,7 @@ class ExceptionHandler : ResponseEntityExceptionHandler() { @ExceptionHandler(ConflictException::class) @ResponseStatus(HttpStatus.CONFLICT) fun handleConflictException(e: Exception): ResponseEntity { - log.warn(e) { "Caught conflict exception: ${e.message}" } + log.info { "Caught conflict exception: ${e.message}" } return responseEntity( HttpStatus.CONFLICT, @@ -91,7 +91,7 @@ class ExceptionHandler : ResponseEntityExceptionHandler() { @ExceptionHandler(NotFoundException::class) @ResponseStatus(HttpStatus.NOT_FOUND) fun handleNotFoundException(e: NotFoundException): ResponseEntity { - log.warn(e) { "Caught not found exception: ${e.message}" } + log.info { "Caught not found exception: ${e.message}" } return responseEntity( HttpStatus.NOT_FOUND, @@ -102,7 +102,7 @@ class ExceptionHandler : ResponseEntityExceptionHandler() { @ExceptionHandler(ForbiddenException::class) @ResponseStatus(HttpStatus.FORBIDDEN) fun handleForbiddenException(e: ForbiddenException): ResponseEntity { - log.warn(e) { "Caught forbidden exception: ${e.message}" } + log.info { "Caught forbidden exception: ${e.message}" } return responseEntity( HttpStatus.FORBIDDEN, diff --git a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionController.kt b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionController.kt index f4c2ca1341..5724bcd294 100644 --- a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionController.kt +++ b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionController.kt @@ -11,7 +11,6 @@ import jakarta.validation.Valid import jakarta.validation.constraints.Max import mu.KotlinLogging import org.loculus.backend.api.AccessionVersion -import org.loculus.backend.api.AccessionVersions import org.loculus.backend.api.AccessionVersionsFilterWithApprovalScope import org.loculus.backend.api.AccessionVersionsFilterWithDeletionScope import org.loculus.backend.api.Accessions @@ -281,16 +280,6 @@ class SubmissionController( @UsernameFromJwt username: String, ): List = submissionDatabaseService.revoke(body.accessions, username, organism) - @Operation(description = CONFIRM_REVOCATION_DESCRIPTION) - @ResponseStatus(HttpStatus.NO_CONTENT) - @PostMapping("/confirm-revocation") - fun confirmRevocation( - @PathVariable @Valid - organism: Organism, - @UsernameFromJwt username: String, - @RequestBody body: AccessionVersions, - ) = submissionDatabaseService.confirmRevocation(body.accessionVersions, username, organism) - @Operation(description = DELETE_SEQUENCES_DESCRIPTION) @ResponseStatus(HttpStatus.OK) @DeleteMapping( diff --git a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt index 379b4c3874..149fa31000 100644 --- a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt +++ b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt @@ -72,7 +72,7 @@ If a filter is applied for a group the user is not a member of, the endpoint wil const val APPROVE_PROCESSED_DATA_DESCRIPTION = """ Approve processed accession versions and set the status to 'APPROVED_FOR_RELEASE'. -This can only be done for accession versions in status 'AWAITING_APPROVAL' or 'AWAITING_APPROVAL_FOR_REVOCATION' that the user submitted themselves. +This can only be done for accession versions in status 'AWAITING_APPROVAL' that the user is allowed to edit. """ const val REVOKE_DESCRIPTION = """ @@ -83,14 +83,6 @@ If any of the given sequence entries do not exist, or do not have the latest ver or the given user has no right to the sequence entry, this will return an error and roll back the whole transaction. """ -const val CONFIRM_REVOCATION_DESCRIPTION = """ -Confirm revocation of existing sequence entries. -This will set the status 'AWAITING_APPROVAL_FOR_REVOCATION' of the revocation version to -'APPROVED_FOR_RELEASE'. If any of the given accession versions do not exist, or do not have the latest version in status -'AWAITING_APPROVAL_FOR_REVOCATION', or the given user has no right to the sequence entry, this will return an error and roll back the -whole transaction. -""" - const val REVISE_RESPONSE_DESCRIPTION = """ Returns a list of accessions, versions and submissionIds of the submitted revised data. The version will increase by one in respect to the original accession version. diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt index c602d36872..95a68f3667 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/SubmissionDatabaseService.kt @@ -244,7 +244,7 @@ class SubmissionDatabaseService( accessionPreconditionValidator.validateAccessionVersions( submitter, accessionVersionsFilter, - listOf(Status.AWAITING_APPROVAL, Status.AWAITING_APPROVAL_FOR_REVOCATION), + listOf(Status.AWAITING_APPROVAL), organism, ) } @@ -262,7 +262,7 @@ class SubmissionDatabaseService( }, ).select { val statusCondition = view.statusIsOneOf( - listOf(Status.AWAITING_APPROVAL, Status.AWAITING_APPROVAL_FOR_REVOCATION), + listOf(Status.AWAITING_APPROVAL), ) val accessionCondition = if (accessionVersionsFilter !== null) { @@ -521,7 +521,7 @@ class SubmissionDatabaseService( where = { (view.accessionColumn inList accessions) and view.isMaxVersion and - view.statusIs(Status.AWAITING_APPROVAL_FOR_REVOCATION) + view.statusIs(Status.AWAITING_APPROVAL) }, ).map { SubmissionIdMapping( @@ -533,29 +533,6 @@ class SubmissionDatabaseService( } } - fun confirmRevocation(accessionVersions: List, username: String, organism: Organism) { - log.info { "Confirming revocation for ${accessionVersions.size} sequence entries" } - - accessionPreconditionValidator.validateAccessionVersions( - username, - accessionVersions, - listOf(Status.AWAITING_APPROVAL_FOR_REVOCATION), - organism, - ) - - val now = Clock.System.now().toLocalDateTime(TimeZone.UTC) - - entriesTableProvider.get(organism).let { table -> - table.update( - where = { - table.accessionVersionIsIn(accessionVersions) - }, - ) { - it[releasedAtColumn] = now - } - } - } - fun deleteSequenceEntryVersions( accessionVersionsFilter: List?, submitter: String, @@ -572,7 +549,6 @@ class SubmissionDatabaseService( Status.RECEIVED, Status.AWAITING_APPROVAL, Status.HAS_ERRORS, - Status.AWAITING_APPROVAL_FOR_REVOCATION, ) if (accessionVersionsFilter != null) { @@ -669,6 +645,7 @@ class SubmissionDatabaseService( view.originalDataColumn, view.errorsColumn, view.warningsColumn, + view.isRevocationColumn, ) .select( where = { @@ -677,6 +654,12 @@ class SubmissionDatabaseService( ) return selectedSequenceEntries.first().let { + if (it[view.isRevocationColumn]) { + throw UnprocessableEntityException( + "Accession version ${accessionVersion.displayAccessionVersion()} is a revocation.", + ) + } + SequenceEntryVersionToEdit( it[view.accessionColumn], it[view.versionColumn], diff --git a/backend/src/main/resources/db/migration/V1__init.sql b/backend/src/main/resources/db/migration/V1__init.sql index 8abcfbaf11..14c23c1b87 100644 --- a/backend/src/main/resources/db/migration/V1__init.sql +++ b/backend/src/main/resources/db/migration/V1__init.sql @@ -52,7 +52,7 @@ select sepd.warnings, case when se.released_at is not null then 'APPROVED_FOR_RELEASE' - when se.is_revocation then 'AWAITING_APPROVAL_FOR_REVOCATION' + when se.is_revocation then 'AWAITING_APPROVAL' when sepd.processing_status = 'IN_PROCESSING' then 'IN_PROCESSING' when sepd.processing_status = 'HAS_ERRORS' then 'HAS_ERRORS' when sepd.processing_status = 'FINISHED' then 'AWAITING_APPROVAL' diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ApproveProcessedDataEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ApproveProcessedDataEndpointTest.kt index b8c0b10574..289017f1c4 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ApproveProcessedDataEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ApproveProcessedDataEndpointTest.kt @@ -9,7 +9,6 @@ import org.loculus.backend.api.AccessionVersion import org.loculus.backend.api.ApproveDataScope import org.loculus.backend.api.Status.APPROVED_FOR_RELEASE import org.loculus.backend.api.Status.AWAITING_APPROVAL -import org.loculus.backend.api.Status.AWAITING_APPROVAL_FOR_REVOCATION import org.loculus.backend.api.Status.IN_PROCESSING import org.loculus.backend.controller.DEFAULT_ORGANISM import org.loculus.backend.controller.EndpointTest @@ -56,6 +55,21 @@ class ApproveProcessedDataEndpointTest( ) } + @Test + fun `GIVEN revoked sequence entries awaiting approval THEN their status should be APPROVED_FOR_RELEASE`() { + val accessionVersions = convenienceClient.prepareDefaultSequenceEntriesToAwaitingApprovalForRevocation() + + client.approveProcessedSequenceEntries(accessionVersions) + .andExpect(status().isOk) + .andExpect(content().contentType(MediaType.APPLICATION_JSON)) + .andExpect(jsonPath("\$[*].accession").value(accessionVersions.map { it.accession })) + + assertThat( + convenienceClient.getSequenceEntries().statusCounts[APPROVED_FOR_RELEASE], + `is`(2 * NUMBER_OF_SEQUENCES), + ) + } + @Test fun `WHEN I approve without accession filter or with full scope THEN all data is approved`() { val approvableSequences = convenienceClient.prepareDataTo(AWAITING_APPROVAL).map { it.accession } @@ -158,7 +172,7 @@ class ApproveProcessedDataEndpointTest( "$.detail", containsString( "Accession versions are in not in one of the states " + - "[$AWAITING_APPROVAL, $AWAITING_APPROVAL_FOR_REVOCATION]: " + + "[$AWAITING_APPROVAL]: " + "${accessionVersionNotInCorrectState.first().displayAccessionVersion()} - $IN_PROCESSING", ), ), diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ConfirmRevocationEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ConfirmRevocationEndpointTest.kt deleted file mode 100644 index c70007f634..0000000000 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ConfirmRevocationEndpointTest.kt +++ /dev/null @@ -1,119 +0,0 @@ -package org.loculus.backend.controller.submission - -import org.hamcrest.Matchers.containsString -import org.junit.jupiter.api.Test -import org.loculus.backend.api.AccessionVersion -import org.loculus.backend.api.Status.APPROVED_FOR_RELEASE -import org.loculus.backend.api.Status.AWAITING_APPROVAL -import org.loculus.backend.api.Status.AWAITING_APPROVAL_FOR_REVOCATION -import org.loculus.backend.controller.DEFAULT_ORGANISM -import org.loculus.backend.controller.EndpointTest -import org.loculus.backend.controller.OTHER_ORGANISM -import org.loculus.backend.controller.assertStatusIs -import org.loculus.backend.controller.expectUnauthorizedResponse -import org.loculus.backend.controller.generateJwtFor -import org.loculus.backend.controller.getAccessionVersions -import org.loculus.backend.controller.toAccessionVersion -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.MediaType -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath -import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status - -@EndpointTest -class ConfirmRevocationEndpointTest( - @Autowired val client: SubmissionControllerClient, - @Autowired val convenienceClient: SubmissionConvenienceClient, -) { - - @Test - fun `GIVEN invalid authorization token THEN returns 401 Unauthorized`() { - expectUnauthorizedResponse(isModifyingRequest = true) { - client.confirmRevocation( - emptyList(), - jwt = it, - ) - } - } - - @Test - fun `GIVEN sequence entries with status 'FOR_REVOCATION' THEN the status changes to 'APPROVED_FOR_RELEASE'`() { - val accessionVersions = convenienceClient.prepareDataTo(AWAITING_APPROVAL_FOR_REVOCATION).getAccessionVersions() - - client.confirmRevocation(accessionVersions) - .andExpect(status().isNoContent) - - convenienceClient.getSequenceEntryOfUser(accession = accessionVersions.first().accession, version = 2) - .assertStatusIs(APPROVED_FOR_RELEASE) - } - - @Test - fun `WHEN confirming revocation of non-existing accessionVersions THEN throws an unprocessableEntity error`() { - convenienceClient.prepareDataTo(AWAITING_APPROVAL_FOR_REVOCATION) - - val nonExistingAccession = AccessionVersion("123", 2) - val nonExistingVersion = AccessionVersion("1", 123) - - client.confirmRevocation(listOf(nonExistingAccession, nonExistingVersion)) - .andExpect(status().isUnprocessableEntity) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect( - jsonPath("\$.detail", containsString("Accession versions 1.123, 123.2 do not exist")), - ) - } - - @Test - fun `WHEN confirming revocation of other organism THEN throws an unprocessableEntity error`() { - val revokedAccessionVersion = - convenienceClient.prepareDataTo(AWAITING_APPROVAL_FOR_REVOCATION).first().toAccessionVersion() - - client.confirmRevocation( - listOf(revokedAccessionVersion), - organism = OTHER_ORGANISM, - ) - .andExpect(status().isUnprocessableEntity) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect( - jsonPath("\$.detail", containsString("accession versions are not of organism $OTHER_ORGANISM:")), - ) - } - - @Test - fun `WHEN confirming revocation for accessionVersions not from the submitter THEN throws forbidden error`() { - val accessionVersions = convenienceClient - .prepareDataTo(AWAITING_APPROVAL_FOR_REVOCATION, organism = DEFAULT_ORGANISM).getAccessionVersions() - - val notSubmitter = "notTheSubmitter" - client.confirmRevocation(accessionVersions, jwt = generateJwtFor(notSubmitter)) - .andExpect(status().isForbidden) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect( - jsonPath("\$.detail", containsString("is not a member of group")), - ) - } - - @Test - fun `WHEN I confirm a revocation versions with latest version not 'APPROVED_FOR_RELEASE' THEN throws an error`() { - val accessionVersions = convenienceClient - .prepareDataTo(AWAITING_APPROVAL) - .getAccessionVersions() - - val revocationAccessionVersions = convenienceClient - .prepareDataTo(AWAITING_APPROVAL_FOR_REVOCATION) - .getAccessionVersions() - - client.confirmRevocation(accessionVersions + revocationAccessionVersions) - .andExpect(status().isUnprocessableEntity) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) - .andExpect( - jsonPath( - "\$.detail", - containsString( - "Accession versions are in not in one of the states [" + - "${AWAITING_APPROVAL_FOR_REVOCATION.name}]: " + - "${accessionVersions.first().displayAccessionVersion()} - ${AWAITING_APPROVAL.name}", - ), - ), - ) - } -} diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/DeleteSequencesEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/DeleteSequencesEndpointTest.kt index 18429212d5..fdae1877f7 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/DeleteSequencesEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/DeleteSequencesEndpointTest.kt @@ -5,7 +5,6 @@ import org.hamcrest.CoreMatchers.hasItem import org.hamcrest.CoreMatchers.`is` import org.hamcrest.CoreMatchers.not import org.hamcrest.MatcherAssert.assertThat -import org.hamcrest.Matchers import org.hamcrest.Matchers.hasProperty import org.hamcrest.Matchers.hasSize import org.junit.jupiter.api.Test @@ -53,6 +52,9 @@ class DeleteSequencesEndpointTest( testScenario: TestScenario, ) { convenienceClient.prepareDataTo(testScenario.statusAfterPreparation) + if (testScenario.statusAfterPreparation == Status.AWAITING_APPROVAL) { + convenienceClient.prepareDefaultSequenceEntriesToAwaitingApprovalForRevocation() + } val accessionVersionsToDelete = convenienceClient.getSequenceEntriesOfUserInState( status = testScenario.statusAfterPreparation, @@ -98,8 +100,7 @@ class DeleteSequencesEndpointTest( }, ) - val listOfAllowedStatuses = "[${Status.RECEIVED}, ${Status.AWAITING_APPROVAL}, " + - "${Status.HAS_ERRORS}, ${Status.AWAITING_APPROVAL_FOR_REVOCATION}]" + val listOfAllowedStatuses = "[${Status.RECEIVED}, ${Status.AWAITING_APPROVAL}, ${Status.HAS_ERRORS}]" val errorString = "Accession versions are in not in one of the states $listOfAllowedStatuses: " + accessionVersionsToDelete.sortedWith(AccessionVersionComparator).joinToString(", ") { "${it.accession}.${it.version} - ${it.status}" @@ -144,7 +145,7 @@ class DeleteSequencesEndpointTest( assertThat( convenienceClient.getSequenceEntries().sequenceEntries, - Matchers.hasSize(erroneousSequences.size + approvableSequences.size), + hasSize(erroneousSequences.size + approvableSequences.size), ) client.deleteSequenceEntries(scope = DeleteSequenceScope.ALL) @@ -290,10 +291,6 @@ class DeleteSequencesEndpointTest( Status.AWAITING_APPROVAL, true, ), - TestScenario( - Status.AWAITING_APPROVAL_FOR_REVOCATION, - true, - ), ) @JvmStatic diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetDataToEditEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetDataToEditEndpointTest.kt index 690d9a420f..d339a4b5fa 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetDataToEditEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetDataToEditEndpointTest.kt @@ -26,7 +26,7 @@ class GetDataToEditEndpointTest( @Test fun `GIVEN invalid authorization token THEN returns 401 Unauthorized`() { expectUnauthorizedResponse { - client.getSequenceEntryThatHasErrors( + client.getSequenceEntryToEdit( "ShouldNotMatterAtAll", 1, jwt = it, @@ -43,7 +43,7 @@ class GetDataToEditEndpointTest( convenienceClient.getSequenceEntryOfUser(accession = firstAccession, version = 1) .assertStatusIs(Status.HAS_ERRORS) - val editedData = convenienceClient.getSequenceEntryThatHasErrors( + val editedData = convenienceClient.getSequenceEntryToEdit( accession = firstAccession, version = 1, ) @@ -57,7 +57,7 @@ class GetDataToEditEndpointTest( fun `WHEN I query data for a non-existent accession THEN refuses request with not found`() { val nonExistentAccession = "DefinitelyNotExisting" - client.getSequenceEntryThatHasErrors(nonExistentAccession, 1) + client.getSequenceEntryToEdit(nonExistentAccession, 1) .andExpect(status().isUnprocessableEntity) .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect( @@ -74,9 +74,9 @@ class GetDataToEditEndpointTest( organism = DEFAULT_ORGANISM, ).first().accession - client.getSequenceEntryThatHasErrors(firstAccession, 1, organism = DEFAULT_ORGANISM) + client.getSequenceEntryToEdit(firstAccession, 1, organism = DEFAULT_ORGANISM) .andExpect(status().isOk) - client.getSequenceEntryThatHasErrors(firstAccession, 1, organism = OTHER_ORGANISM) + client.getSequenceEntryToEdit(firstAccession, 1, organism = OTHER_ORGANISM) .andExpect(status().isUnprocessableEntity) .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect( @@ -94,7 +94,7 @@ class GetDataToEditEndpointTest( convenienceClient.prepareDataTo(Status.HAS_ERRORS) - client.getSequenceEntryThatHasErrors("1", nonExistentAccessionVersion) + client.getSequenceEntryToEdit("1", nonExistentAccessionVersion) .andExpect(status().isUnprocessableEntity) .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) .andExpect( @@ -108,7 +108,7 @@ class GetDataToEditEndpointTest( fun `WHEN I query a sequence entry that has a wrong state THEN refuses request with unprocessable entity`() { val firstAccession = convenienceClient.prepareDataTo(Status.IN_PROCESSING).first().accession - client.getSequenceEntryThatHasErrors( + client.getSequenceEntryToEdit( accession = firstAccession, version = 1, ) @@ -127,7 +127,7 @@ class GetDataToEditEndpointTest( val firstAccession = convenienceClient.prepareDataTo(Status.HAS_ERRORS).first().accession val userNameThatDoesNotHavePermissionToQuery = "theOneWhoMustNotBeNamed" - client.getSequenceEntryThatHasErrors( + client.getSequenceEntryToEdit( accession = firstAccession, version = 1, jwt = generateJwtFor(userNameThatDoesNotHavePermissionToQuery), @@ -138,4 +138,14 @@ class GetDataToEditEndpointTest( jsonPath("\$.detail", containsString("is not a member of group")), ) } + + @Test + fun `GIVEN revocation version awaiting approval THEN throws unprocessable entity error`() { + val accessionVersion = convenienceClient.prepareDefaultSequenceEntriesToAwaitingApprovalForRevocation() + .first() + + client.getSequenceEntryToEdit(accessionVersion.accession, accessionVersion.version) + .andExpect(status().isUnprocessableEntity) + .andExpect(jsonPath("\$.detail", containsString("is a revocation"))) + } } diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetReleasedDataEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetReleasedDataEndpointTest.kt index ea253faa2b..34158879a9 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetReleasedDataEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetReleasedDataEndpointTest.kt @@ -220,7 +220,7 @@ class GetReleasedDataEndpointTest( convenienceClient.reviseAndProcessDefaultSequenceEntries(preparedSubmissions.map { it.accession }) val revokedSequences = convenienceClient.revokeSequenceEntries(preparedSubmissions.map { it.accession }) - convenienceClient.confirmRevocation(revokedSequences) + convenienceClient.approveProcessedSequenceEntries(revokedSequences) convenienceClient.reviseAndProcessDefaultSequenceEntries(revokedSequences.map { it.accession }) diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetSequencesEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetSequencesEndpointTest.kt index 4667f8a9f6..7eeda44dcc 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetSequencesEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetSequencesEndpointTest.kt @@ -10,11 +10,9 @@ import org.hamcrest.Matchers.`is` import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest import org.junit.jupiter.params.provider.MethodSource -import org.loculus.backend.api.AccessionVersion import org.loculus.backend.api.Status import org.loculus.backend.api.Status.APPROVED_FOR_RELEASE import org.loculus.backend.api.Status.AWAITING_APPROVAL -import org.loculus.backend.api.Status.AWAITING_APPROVAL_FOR_REVOCATION import org.loculus.backend.api.Status.HAS_ERRORS import org.loculus.backend.api.Status.IN_PROCESSING import org.loculus.backend.api.Status.RECEIVED @@ -197,7 +195,6 @@ class GetSequencesEndpointTest( AWAITING_APPROVAL to 10, HAS_ERRORS to 10, APPROVED_FOR_RELEASE to 0, - AWAITING_APPROVAL_FOR_REVOCATION to 0, ), ), ) @@ -241,45 +238,31 @@ class GetSequencesEndpointTest( ), Scenario( setupDescription = "I submitted sequence entries that have been successfully processed", - prepareDatabase = { - it.prepareDataTo(AWAITING_APPROVAL).map { entry -> entry.accession } - }, + prepareDatabase = { it.prepareDataTo(AWAITING_APPROVAL).map { entry -> entry.accession } }, expectedStatus = AWAITING_APPROVAL, expectedIsRevocation = false, ), Scenario( setupDescription = "I submitted, processed and approved sequence entries", - prepareDatabase = { - val accessionVersions = it.prepareDataTo(AWAITING_APPROVAL) - it.approveProcessedSequenceEntries(listOf(accessionVersions.first())) - accessionVersions.map { entry -> entry.accession } - }, + prepareDatabase = { it.prepareDataTo(APPROVED_FOR_RELEASE).map { entry -> entry.accession } }, expectedStatus = APPROVED_FOR_RELEASE, expectedIsRevocation = false, ), Scenario( setupDescription = "I submitted a revocation", prepareDatabase = { - val accessionVersions = it.prepareDataTo(AWAITING_APPROVAL) - it.approveProcessedSequenceEntries(listOf(accessionVersions.first())) + val accessionVersions = it.prepareDataTo(APPROVED_FOR_RELEASE) val accessions = accessionVersions.map { entry -> entry.accession } - it.revokeSequenceEntries(listOf(accessions.first())) + it.revokeSequenceEntries(accessions) accessions }, - expectedStatus = AWAITING_APPROVAL_FOR_REVOCATION, + expectedStatus = AWAITING_APPROVAL, expectedIsRevocation = true, expectedVersion = 2, ), Scenario( setupDescription = "I approved a revocation", - prepareDatabase = { - val accessionVersions = it.prepareDataTo(AWAITING_APPROVAL) - it.approveProcessedSequenceEntries(listOf(accessionVersions.first())) - val accessions = accessionVersions.map { entry -> entry.accession } - it.revokeSequenceEntries(listOf(accessions.first())) - it.confirmRevocation(listOf(AccessionVersion(accessions.first(), 2))) - accessions - }, + prepareDatabase = { it.prepareRevokedSequenceEntries().map { entry -> entry.accession } }, expectedStatus = APPROVED_FOR_RELEASE, expectedIsRevocation = true, expectedVersion = 2, diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/RevokeEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/RevokeEndpointTest.kt index 072de7affb..91e9211003 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/RevokeEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/RevokeEndpointTest.kt @@ -3,7 +3,7 @@ package org.loculus.backend.controller.submission import org.hamcrest.Matchers.containsString import org.junit.jupiter.api.Test import org.loculus.backend.api.Status -import org.loculus.backend.api.Status.AWAITING_APPROVAL_FOR_REVOCATION +import org.loculus.backend.api.Status.AWAITING_APPROVAL import org.loculus.backend.controller.DEFAULT_ORGANISM import org.loculus.backend.controller.EndpointTest import org.loculus.backend.controller.OTHER_ORGANISM @@ -34,7 +34,7 @@ class RevokeEndpointTest( } @Test - fun `GIVEN entries with 'APPROVED_FOR_RELEASE' THEN the status changes to 'AWAITING_APPROVAL_FOR_REVOCATION'`() { + fun `GIVEN entries with 'APPROVED_FOR_RELEASE' THEN returns revocation version in status AWAITING_APPROVAL`() { val accessions = convenienceClient.prepareDefaultSequenceEntriesToApprovedForRelease().map { it.accession } client.revokeSequenceEntries(accessions) @@ -45,7 +45,7 @@ class RevokeEndpointTest( .andExpect(jsonPath("\$[0].version").value(2)) convenienceClient.getSequenceEntryOfUser(accession = accessions.first(), version = 2) - .assertStatusIs(AWAITING_APPROVAL_FOR_REVOCATION) + .assertStatusIs(AWAITING_APPROVAL) } @Test diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionControllerClient.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionControllerClient.kt index e2a4959af1..1a78616c43 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionControllerClient.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionControllerClient.kt @@ -2,6 +2,7 @@ package org.loculus.backend.controller.submission import com.fasterxml.jackson.databind.ObjectMapper import org.loculus.backend.api.AccessionVersion +import org.loculus.backend.api.AccessionVersionInterface import org.loculus.backend.api.ApproveDataScope import org.loculus.backend.api.DataUseTerms import org.loculus.backend.api.DeleteSequenceScope @@ -27,6 +28,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multi import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post const val DEFAULT_USER_NAME = "testuser" + class SubmissionControllerClient(private val mockMvc: MockMvc, private val objectMapper: ObjectMapper) { fun submit( metadataFile: MockMultipartFile, @@ -102,7 +104,7 @@ class SubmissionControllerClient(private val mockMvc: MockMvc, private val objec ) } - fun getSequenceEntryThatHasErrors( + fun getSequenceEntryToEdit( accession: Accession, version: Long, organism: String = DEFAULT_ORGANISM, @@ -128,7 +130,7 @@ class SubmissionControllerClient(private val mockMvc: MockMvc, private val objec } fun approveProcessedSequenceEntries( - listOfSequencesToApprove: List? = null, + listOfSequencesToApprove: List? = null, organism: String = DEFAULT_ORGANISM, scope: ApproveDataScope = ApproveDataScope.ALL, jwt: String? = jwtForDefaultUser, @@ -136,7 +138,8 @@ class SubmissionControllerClient(private val mockMvc: MockMvc, private val objec post(addOrganismToPath("/approve-processed-data", organism = organism)) .contentType(MediaType.APPLICATION_JSON) .content( - """{"accessionVersionsFilter": ${createAccessionVersionsFilterBodyString(listOfSequencesToApprove)}, + """{ + "accessionVersionsFilter": ${serialize(listOfSequencesToApprove)}, "scope": "$scope" }""", ) @@ -154,17 +157,6 @@ class SubmissionControllerClient(private val mockMvc: MockMvc, private val objec .withAuth(jwt), ) - fun confirmRevocation( - listOfSequencesToConfirm: List, - organism: String = DEFAULT_ORGANISM, - jwt: String? = jwtForDefaultUser, - ): ResultActions = mockMvc.perform( - post(addOrganismToPath("/confirm-revocation", organism = organism)) - .contentType(MediaType.APPLICATION_JSON) - .content("""{"accessionVersions":${objectMapper.writeValueAsString(listOfSequencesToConfirm)}}""") - .withAuth(jwt), - ) - fun getReleasedData(organism: String = DEFAULT_ORGANISM, jwt: String? = jwtForGetReleasedData): ResultActions = mockMvc.perform( get(addOrganismToPath("/get-released-data", organism = organism)) @@ -181,10 +173,11 @@ class SubmissionControllerClient(private val mockMvc: MockMvc, private val objec .withAuth(jwt) .contentType(MediaType.APPLICATION_JSON) .content( - """{"accessionVersionsFilter":${createAccessionVersionsFilterBodyString( - listOfAccessionVersionsToDelete, - )}, - "scope": "$scope"} + """ + { + "accessionVersionsFilter": ${serialize(listOfAccessionVersionsToDelete)}, + "scope": "$scope" + } """.trimMargin(), ), ) @@ -201,12 +194,10 @@ class SubmissionControllerClient(private val mockMvc: MockMvc, private val objec .withAuth(jwt), ) - private fun createAccessionVersionsFilterBodyString( - listOfSequencesToApprove: List? = null, - ): String { + private fun serialize(listOfSequencesToApprove: List? = null): String { return if (listOfSequencesToApprove != null) { objectMapper.writeValueAsString( - listOfSequencesToApprove, + listOfSequencesToApprove.map { AccessionVersion(it.accession, it.version) }, ) } else { "null" diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionConvenienceClient.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionConvenienceClient.kt index 0a4ecb218d..2ff3edb6d7 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionConvenienceClient.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionConvenienceClient.kt @@ -146,9 +146,9 @@ class SubmissionConvenienceClient( } fun prepareRevokedSequenceEntries(organism: String = DEFAULT_ORGANISM): List { - val accessionVersions = prepareDataTo(Status.AWAITING_APPROVAL_FOR_REVOCATION, organism = organism) - confirmRevocation(accessionVersions, organism = organism) - return accessionVersions + val accessionVersions = prepareDataTo(Status.APPROVED_FOR_RELEASE, organism = organism) + val revocationVersions = revokeSequenceEntries(accessionVersions.map { it.accession }, organism = organism) + return approveProcessedSequenceEntries(revocationVersions, organism = organism) } fun extractUnprocessedData( @@ -205,12 +205,12 @@ class SubmissionConvenienceClient( ?: error("Did not find $accession.$version for $userName") } - fun getSequenceEntryThatHasErrors( + fun getSequenceEntryToEdit( accession: Accession, version: Long, userName: String = DEFAULT_USER_NAME, ): SequenceEntryVersionToEdit = deserializeJsonResponse( - client.getSequenceEntryThatHasErrors( + client.getSequenceEntryToEdit( accession = accession, version = version, jwt = generateJwtFor(userName), @@ -244,17 +244,15 @@ class SubmissionConvenienceClient( fun approveProcessedSequenceEntries( listOfSequencesToApprove: List, organism: String = DEFAULT_ORGANISM, - ) { - client.approveProcessedSequenceEntries( - listOfSequencesToApprove.map { - AccessionVersion( - it.accession, - it.version, + ): List { + return deserializeJsonResponse( + client + .approveProcessedSequenceEntries( + listOfSequencesToApprove, + organism = organism, ) - }, - organism = organism, + .andExpect(status().isOk), ) - .andExpect(status().isOk) } fun reviseDefaultProcessedSequenceEntries( @@ -276,14 +274,6 @@ class SubmissionConvenienceClient( ): List = deserializeJsonResponse(client.revokeSequenceEntries(listOfAccessionsToRevoke, organism = organism)) - fun confirmRevocation( - listOfSequencesToConfirm: List, - organism: String = DEFAULT_ORGANISM, - ) { - client.confirmRevocation(listOfSequencesToConfirm.map { AccessionVersion(it.accession, it.version) }, organism) - .andExpect(status().isNoContent) - } - fun prepareDataTo(status: Status, organism: String = DEFAULT_ORGANISM): List { return when (status) { Status.RECEIVED -> submitDefaultFiles(organism = organism) @@ -291,9 +281,7 @@ class SubmissionConvenienceClient( Status.HAS_ERRORS -> prepareDefaultSequenceEntriesToHasErrors(organism = organism) Status.AWAITING_APPROVAL -> prepareDefaultSequenceEntriesToAwaitingApproval(organism = organism) Status.APPROVED_FOR_RELEASE -> prepareDefaultSequenceEntriesToApprovedForRelease(organism = organism) - Status.AWAITING_APPROVAL_FOR_REVOCATION -> prepareDefaultSequenceEntriesToAwaitingApprovalForRevocation( - organism = organism, - ) + else -> throw Exception("Test issue: No data preparation defined for status $status") } } diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitProcessedDataEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitProcessedDataEndpointTest.kt index b3c183c117..0cb9e1eafc 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitProcessedDataEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitProcessedDataEndpointTest.kt @@ -138,7 +138,7 @@ class SubmitProcessedDataEndpointTest( Status.AWAITING_APPROVAL, ) - submissionControllerClient.getSequenceEntryThatHasErrors( + submissionControllerClient.getSequenceEntryToEdit( accession = accessions.first(), version = 1, organism = OTHER_ORGANISM, @@ -180,7 +180,7 @@ class SubmitProcessedDataEndpointTest( Status.AWAITING_APPROVAL, ) - submissionControllerClient.getSequenceEntryThatHasErrors( + submissionControllerClient.getSequenceEntryToEdit( accession = accessions.first(), version = 1, ) diff --git a/website/src/components/ReviewPage/ReviewCard.tsx b/website/src/components/ReviewPage/ReviewCard.tsx index f48ba64f9d..0c612c02e0 100644 --- a/website/src/components/ReviewPage/ReviewCard.tsx +++ b/website/src/components/ReviewPage/ReviewCard.tsx @@ -4,7 +4,6 @@ import { Tooltip } from 'react-tooltip'; import { backendClientHooks } from '../../services/serviceHooks.ts'; import { awaitingApprovalStatus, - awaitingApprovalForRevocationStatus, type DataUseTerms, hasErrorsStatus, inProcessingStatus, @@ -66,7 +65,7 @@ export const ReviewCard: FC = ({ value={sequenceEntryStatus.submissionId} /> {data !== undefined && } - {sequenceEntryStatus.status === awaitingApprovalForRevocationStatus && ( + {sequenceEntryStatus.isRevocation && ( = ({ return (
- {sequenceEntryStatus.status !== awaitingApprovalForRevocationStatus && ( + {!sequenceEntryStatus.isRevocation && ( )}
diff --git a/website/src/hooks/useSubmissionOperations.ts b/website/src/hooks/useSubmissionOperations.ts index 2bed5c4240..6db71234fa 100644 --- a/website/src/hooks/useSubmissionOperations.ts +++ b/website/src/hooks/useSubmissionOperations.ts @@ -10,7 +10,6 @@ import { inProcessingStatus, type PageQuery, receivedStatus, - awaitingApprovalForRevocationStatus, } from '../types/backend.ts'; import type { ClientConfig } from '../types/runtimeConfig.ts'; import { createAuthorizationHeader } from '../utils/createAuthorizationHeader.ts'; @@ -24,13 +23,7 @@ export function useSubmissionOperations( pageQuery: PageQuery, ) { const hooks = useMemo(() => backendClientHooks(clientConfig), [clientConfig]); - const allRelevantStatuses = [ - receivedStatus, - inProcessingStatus, - hasErrorsStatus, - awaitingApprovalStatus, - awaitingApprovalForRevocationStatus, - ]; + const allRelevantStatuses = [receivedStatus, inProcessingStatus, hasErrorsStatus, awaitingApprovalStatus]; const [includedStatuses, setIncludedStatuses] = useState(allRelevantStatuses); const useGetSequences = hooks.useGetSequences( { diff --git a/website/src/services/backendApi.ts b/website/src/services/backendApi.ts index c1a454e49f..8c6ce2862e 100644 --- a/website/src/services/backendApi.ts +++ b/website/src/services/backendApi.ts @@ -7,7 +7,6 @@ import { accessionVersion, accessionVersionsFilterWithApprovalScope, accessionVersionsFilterWithDeletionScope, - accessionVersionsObject, dataUseTermsHistoryEntry, getSequencesResponse, problemDetail, @@ -166,22 +165,6 @@ const deleteSequencesEndpoint = makeEndpoint({ errors: [{ status: 'default', schema: problemDetail }, notAuthorizedError], }); -const confirmRevocationEndpoint = makeEndpoint({ - method: 'post', - path: withOrganismPathSegment('/confirm-revocation'), - alias: 'confirmRevocation', - parameters: [ - authorizationHeader, - { - name: 'accessionVersions', - type: 'Body', - schema: accessionVersionsObject, - }, - ], - response: z.never(), - errors: [{ status: 'default', schema: problemDetail }, { status: 422, schema: problemDetail }, notAuthorizedError], -}); - const extractUnprocessedDataEndpoint = makeEndpoint({ method: 'post', path: withOrganismPathSegment('/extract-unprocessed-data'), @@ -234,7 +217,6 @@ export const backendApi = makeApi([ getSequencesEndpoint, approveProcessedDataEndpoint, deleteSequencesEndpoint, - confirmRevocationEndpoint, extractUnprocessedDataEndpoint, submitProcessedDataEndpoint, getDataUseTermsHistoryEndpoint, diff --git a/website/src/types/backend.ts b/website/src/types/backend.ts index b86655cecb..6d2db4defc 100644 --- a/website/src/types/backend.ts +++ b/website/src/types/backend.ts @@ -5,7 +5,6 @@ export const inProcessingStatus = 'IN_PROCESSING'; export const hasErrorsStatus = 'HAS_ERRORS'; export const awaitingApprovalStatus = 'AWAITING_APPROVAL'; export const approvedForReleaseStatus = 'APPROVED_FOR_RELEASE'; -export const awaitingApprovalForRevocationStatus = 'AWAITING_APPROVAL_FOR_REVOCATION'; export const sequenceEntryStatusNames = z.union([ z.literal(receivedStatus), @@ -13,7 +12,6 @@ export const sequenceEntryStatusNames = z.union([ z.literal(hasErrorsStatus), z.literal(awaitingApprovalStatus), z.literal(approvedForReleaseStatus), - z.literal(awaitingApprovalForRevocationStatus), ]); export type SequenceEntryStatusNames = z.infer; const statusThatAllowsEditing = z.union([z.literal(hasErrorsStatus), z.literal(awaitingApprovalStatus)]); @@ -54,10 +52,6 @@ export const accessionVersion = z.object({ }); export type AccessionVersion = z.infer; -export const accessionVersionsObject = z.object({ - accessionVersions: z.array(accessionVersion), -}); - export const accessionVersionsFilter = z.object({ accessionVersionsFilter: z.array(accessionVersion).optional(), }); diff --git a/website/tests/util/backendCalls.ts b/website/tests/util/backendCalls.ts index d35349dbf4..a99cbad783 100644 --- a/website/tests/util/backendCalls.ts +++ b/website/tests/util/backendCalls.ts @@ -92,8 +92,8 @@ export const revokeReleasedData = async (accessions: Accession[], token: string) ); const confirmationResponse = await backendClient.call( - 'confirmRevocation', - { accessionVersions }, + 'approveProcessedData', + { scope: 'ALL', accessionVersionsFilter: accessionVersions }, { params: { organism: dummyOrganism.key }, headers: createAuthorizationHeader(token),