From 8210ba7a3c8a67a8658e802a46075dc500f15736 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Thu, 16 Jan 2025 13:16:05 +0100 Subject: [PATCH 01/14] feat(kubernetes): add `allowSubmissionOfConsensusSequences` to the config --- .../org/loculus/backend/config/Config.kt | 1 + .../templates/_boolean-with-default-true.tpl | 4 + .../loculus/templates/_common-metadata.tpl | 6 + .../loculus-preprocessing-config.yaml | 9 +- kubernetes/loculus/values.yaml | 10 +- .../DownloadDialog/DownloadDialog.tsx | 3 + .../DownloadDialog/DownloadForm.tsx | 114 ++++++++++-------- .../components/SearchPage/SearchFullUI.tsx | 1 + website/src/types/config.ts | 1 + website/src/types/referencesGenomes.ts | 4 +- 10 files changed, 89 insertions(+), 64 deletions(-) create mode 100644 kubernetes/loculus/templates/_boolean-with-default-true.tpl diff --git a/backend/src/main/kotlin/org/loculus/backend/config/Config.kt b/backend/src/main/kotlin/org/loculus/backend/config/Config.kt index a2ee9d98f4..74edbdaee6 100644 --- a/backend/src/main/kotlin/org/loculus/backend/config/Config.kt +++ b/backend/src/main/kotlin/org/loculus/backend/config/Config.kt @@ -23,6 +23,7 @@ data class Schema( val metadata: List, val externalMetadata: List = emptyList(), val earliestReleaseDate: EarliestReleaseDate = EarliestReleaseDate(false, emptyList()), + val allowSubmissionOfConsensusSequences: Boolean ) // The Json property names need to be kept in sync with website config enum `metadataPossibleTypes` in `config.ts` diff --git a/kubernetes/loculus/templates/_boolean-with-default-true.tpl b/kubernetes/loculus/templates/_boolean-with-default-true.tpl new file mode 100644 index 0000000000..4f059021aa --- /dev/null +++ b/kubernetes/loculus/templates/_boolean-with-default-true.tpl @@ -0,0 +1,4 @@ +{{/* "default true" can't be used, because "false | default true" will be true (but we would need the result false) */}} +{{- define "loculus.booleanWithDefaultTrue" -}} +{{ eq . false | ternary false true }} +{{- end -}} diff --git a/kubernetes/loculus/templates/_common-metadata.tpl b/kubernetes/loculus/templates/_common-metadata.tpl index f0c99b2081..470331992b 100644 --- a/kubernetes/loculus/templates/_common-metadata.tpl +++ b/kubernetes/loculus/templates/_common-metadata.tpl @@ -169,6 +169,7 @@ organisms: {{- with ($instance.schema | include "loculus.patchMetadataSchema" | fromYaml) }} organismName: {{ quote .organismName }} loadSequencesAutomatically: {{ .loadSequencesAutomatically | default false }} + allowSubmissionOfConsensusSequences: {{ include "loculus.booleanWithDefaultTrue" .allowSubmissionOfConsensusSequences }} {{- $nucleotideSequences := .nucleotideSequences | default (list "main")}} {{ if .image }} image: {{ .image }} @@ -294,6 +295,7 @@ organisms: {{- with $instance.schema }} {{- $nucleotideSequences := .nucleotideSequences | default (list "main")}} organismName: {{ quote .organismName }} + allowSubmissionOfConsensusSequences: {{ include "loculus.booleanWithDefaultTrue" .allowSubmissionOfConsensusSequences }} metadata: {{- $args := dict "metadata" (include "loculus.patchMetadataSchema" . | fromYaml).metadata "nucleotideSequences" $nucleotideSequences}} {{ $metadata := include "loculus.generateBackendMetadata" $args | fromYaml }} @@ -317,9 +319,13 @@ organisms: {{- end }} {{- define "loculus.generateReferenceGenome" }} +{{ if .nucleotideSequences }} nucleotideSequences: {{ $nucleotideSequences := include "loculus.generateSequences" .nucleotideSequences | fromYaml }} {{ $nucleotideSequences.fields | toYaml | nindent 8 }} +{{ else }} +nucleotideSequences: [] +{{ end }} {{ if .genes }} genes: {{ $genes := include "loculus.generateSequences" .genes | fromYaml }} diff --git a/kubernetes/loculus/templates/loculus-preprocessing-config.yaml b/kubernetes/loculus/templates/loculus-preprocessing-config.yaml index f53745448e..423bd64155 100644 --- a/kubernetes/loculus/templates/loculus-preprocessing-config.yaml +++ b/kubernetes/loculus/templates/loculus-preprocessing-config.yaml @@ -1,7 +1,8 @@ {{- range $organism, $organismConfig := (.Values.organisms | default .Values.defaultOrganisms) }} {{- $metadata := ($organismConfig.schema | include "loculus.patchMetadataSchema" | fromYaml).metadata }} -{{- $nucleotideSequences := (($organismConfig.schema | include "loculus.patchMetadataSchema" | fromYaml).nucleotideSequences | default "" ) }} -{{- $nucleotideSequencesList := ($organismConfig.schema | include "loculus.patchMetadataSchema" | fromYaml).nucleotideSequences | default (list "main")}} +{{- $rawNucleotideSequences := (($organismConfig.schema | include "loculus.patchMetadataSchema" | fromYaml).nucleotideSequences) }} +{{- $nucleotideSequences := ($rawNucleotideSequences | default "" ) }} +{{- $nucleotideSequencesList := (eq $rawNucleotideSequences nil | ternary (list "main") $rawNucleotideSequences) }} {{- range $processingIndex, $processingConfig := $organismConfig.preprocessing }} {{- if $processingConfig.configFile }} --- @@ -13,7 +14,7 @@ data: preprocessing-config.yaml: | organism: {{ $organism }} {{- $processingConfig.configFile | toYaml | nindent 4 }} - nucleotideSequences: {{- $nucleotideSequencesList | toYaml | nindent 4 }} + {{- (dict "nucleotideSequences" $nucleotideSequencesList) | toYaml | nindent 4 }} processing_spec: {{- $args := dict "metadata" $metadata "nucleotideSequences" $nucleotideSequences }} {{- include "loculus.preprocessingSpecs" $args | nindent 6 }} @@ -24,4 +25,4 @@ data: args: {{- end }} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index 7246bf9407..3236b09459 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -1377,10 +1377,12 @@ defaultOrganisms: sequence: "[[URL:https://corneliusroemer.github.io/seqs/artefacts/sars-cov-2/ORF9b.fasta]]" - name: "S" sequence: "[[URL:https://corneliusroemer.github.io/seqs/artefacts/sars-cov-2/S.fasta]]" - not-aligned-organism: + dummy-organism-without-seqs: schema: image: "https://cdn.who.int/media/images/default-source/mca/mca-covid-19/coronavirus-2.tmb-1920v.jpg?sfvrsn=4dba955c_19" - organismName: "Test organism (without alignment)" + organismName: "Test organism (without consensus sequences)" + nucleotideSequences: [] + allowSubmissionOfConsensusSequences: false metadata: - name: date type: date @@ -1433,9 +1435,7 @@ defaultOrganisms: genes: [] batch_size: 100 referenceGenomes: - nucleotideSequences: - - name: "main" - sequence: "[[URL:https://corneliusroemer.github.io/seqs/artefacts/sars-cov-2/reference.fasta]]" + nucleotideSequences: [] genes: [] cchf: <<: *defaultOrganismConfig diff --git a/website/src/components/SearchPage/DownloadDialog/DownloadDialog.tsx b/website/src/components/SearchPage/DownloadDialog/DownloadDialog.tsx index 6af4911d02..9987795cc5 100644 --- a/website/src/components/SearchPage/DownloadDialog/DownloadDialog.tsx +++ b/website/src/components/SearchPage/DownloadDialog/DownloadDialog.tsx @@ -14,12 +14,14 @@ type DownloadDialogProps = { downloadUrlGenerator: DownloadUrlGenerator; sequenceFilter: SequenceFilter; referenceGenomesSequenceNames: ReferenceGenomesSequenceNames; + allowSubmissionOfConsensusSequences: boolean; }; export const DownloadDialog: FC = ({ downloadUrlGenerator, sequenceFilter, referenceGenomesSequenceNames, + allowSubmissionOfConsensusSequences, }) => { const [isOpen, setIsOpen] = useState(false); @@ -38,6 +40,7 @@ export const DownloadDialog: FC = ({
diff --git a/website/src/types/config.ts b/website/src/types/config.ts index 3f6846ab6b..ba7524a30c 100644 --- a/website/src/types/config.ts +++ b/website/src/types/config.ts @@ -107,6 +107,7 @@ export const schema = z.object({ primaryKey: z.string(), defaultOrderBy: z.string(), defaultOrder: orderByType, + allowSubmissionOfConsensusSequences: z.boolean(), loadSequencesAutomatically: z.boolean().optional(), }); export type Schema = z.infer; diff --git a/website/src/types/referencesGenomes.ts b/website/src/types/referencesGenomes.ts index 855e324251..1fcbbe4cdf 100644 --- a/website/src/types/referencesGenomes.ts +++ b/website/src/types/referencesGenomes.ts @@ -13,9 +13,7 @@ const namedSequence = z.object({ export type NamedSequence = z.infer; export const referenceGenomes = z.object({ - nucleotideSequences: z.array(namedSequence).refine((data) => data.length > 0, { - message: 'Array must have at least one entry', - }), + nucleotideSequences: z.array(namedSequence), genes: z.array(namedSequence), }); export type ReferenceGenomes = z.infer; From 4b962185a7545bc8063ee9cec923e1a35253d95b Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Thu, 16 Jan 2025 16:01:23 +0100 Subject: [PATCH 02/14] feat(backend): allow organisms without sequences --- .../org/loculus/backend/config/Config.kt | 2 +- .../controller/SubmissionController.kt | 8 +- .../org/loculus/backend/model/SubmitModel.kt | 57 +++++++++----- .../submission/UploadDatabaseService.kt | 11 ++- .../loculus/backend/controller/TestHelpers.kt | 1 + .../ExtractUnprocessedDataEndpointTest.kt | 38 +++++++++- .../submission/PreparedProcessedData.kt | 16 ++++ .../submission/ReviseEndpointTest.kt | 75 +++++++++++++++++-- .../submission/SubmissionControllerClient.kt | 20 +++-- .../submission/SubmissionConvenienceClient.kt | 40 ++++++++-- .../submission/SubmissionJourneyTest.kt | 72 ++++++++++++++++++ .../submission/SubmitEndpointTest.kt | 51 ++++++++++++- .../src/test/resources/backend_config.json | 41 ++++++++++ .../backend_config_single_segment.json | 2 +- 14 files changed, 386 insertions(+), 48 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/config/Config.kt b/backend/src/main/kotlin/org/loculus/backend/config/Config.kt index 74edbdaee6..9e05330f6c 100644 --- a/backend/src/main/kotlin/org/loculus/backend/config/Config.kt +++ b/backend/src/main/kotlin/org/loculus/backend/config/Config.kt @@ -23,7 +23,7 @@ data class Schema( val metadata: List, val externalMetadata: List = emptyList(), val earliestReleaseDate: EarliestReleaseDate = EarliestReleaseDate(false, emptyList()), - val allowSubmissionOfConsensusSequences: Boolean + val allowSubmissionOfConsensusSequences: Boolean = true, ) // The Json property names need to be kept in sync with website config enum `metadataPossibleTypes` in `config.ts` 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 7420815501..88ba74b6ad 100644 --- a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionController.kt +++ b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionController.kt @@ -86,7 +86,7 @@ open class SubmissionController( @HiddenParam authenticatedUser: AuthenticatedUser, @Parameter(description = GROUP_ID_DESCRIPTION) @RequestParam groupId: Int, @Parameter(description = METADATA_FILE_DESCRIPTION) @RequestParam metadataFile: MultipartFile, - @Parameter(description = SEQUENCE_FILE_DESCRIPTION) @RequestParam sequenceFile: MultipartFile, + @Parameter(description = SEQUENCE_FILE_DESCRIPTION) @RequestParam sequenceFile: MultipartFile?, @Parameter(description = "Data Use terms under which data is released.") @RequestParam dataUseTermsType: DataUseTermsType, @Parameter( @@ -118,7 +118,7 @@ open class SubmissionController( ) @RequestParam metadataFile: MultipartFile, @Parameter( description = SEQUENCE_FILE_DESCRIPTION, - ) @RequestParam sequenceFile: MultipartFile, + ) @RequestParam sequenceFile: MultipartFile?, ): List { val params = SubmissionParams.RevisionSubmissionParams( organism, @@ -172,7 +172,9 @@ open class SubmissionController( } val lastDatabaseWriteETag = releasedDataModel.getLastDatabaseWriteETag() - if (ifNoneMatch == lastDatabaseWriteETag) return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build() + if (ifNoneMatch == lastDatabaseWriteETag) { + return ResponseEntity.status(HttpStatus.NOT_MODIFIED).build() + } val headers = HttpHeaders() headers.contentType = MediaType.parseMediaType(MediaType.APPLICATION_NDJSON_VALUE) diff --git a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt index 3c17f0157e..1f26b9de95 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt @@ -8,6 +8,7 @@ import org.loculus.backend.api.DataUseTerms import org.loculus.backend.api.Organism import org.loculus.backend.api.SubmissionIdMapping import org.loculus.backend.auth.AuthenticatedUser +import org.loculus.backend.config.BackendConfig import org.loculus.backend.controller.BadRequestException import org.loculus.backend.controller.DuplicateKeyException import org.loculus.backend.controller.UnprocessableEntityException @@ -41,14 +42,14 @@ interface SubmissionParams { val organism: Organism val authenticatedUser: AuthenticatedUser val metadataFile: MultipartFile - val sequenceFile: MultipartFile + val sequenceFile: MultipartFile? val uploadType: UploadType data class OriginalSubmissionParams( override val organism: Organism, override val authenticatedUser: AuthenticatedUser, override val metadataFile: MultipartFile, - override val sequenceFile: MultipartFile, + override val sequenceFile: MultipartFile?, val groupId: Int, val dataUseTerms: DataUseTerms, ) : SubmissionParams { @@ -59,7 +60,7 @@ interface SubmissionParams { override val organism: Organism, override val authenticatedUser: AuthenticatedUser, override val metadataFile: MultipartFile, - override val sequenceFile: MultipartFile, + override val sequenceFile: MultipartFile?, ) : SubmissionParams { override val uploadType: UploadType = UploadType.REVISION } @@ -76,6 +77,7 @@ class SubmitModel( private val groupManagementPreconditionValidator: GroupManagementPreconditionValidator, private val dataUseTermsPreconditionValidator: DataUseTermsPreconditionValidator, private val dateProvider: DateProvider, + private val backendConfig: BackendConfig, ) { companion object AcceptedFileTypes { @@ -106,9 +108,11 @@ class SubmitModel( batchSize, ) - log.debug { "Validating submission with uploadId $uploadId" } - val (metadataSubmissionIds, sequencesSubmissionIds) = uploadDatabaseService.getUploadSubmissionIds(uploadId) - validateSubmissionIdSets(metadataSubmissionIds.toSet(), sequencesSubmissionIds.toSet()) + if (requiresSequenceFile(submissionParams.organism)) { + log.debug { "Validating submission with uploadId $uploadId" } + val (metadataSubmissionIds, sequencesSubmissionIds) = uploadDatabaseService.getUploadSubmissionIds(uploadId) + validateSubmissionIdSets(metadataSubmissionIds.toSet(), sequencesSubmissionIds.toSet()) + } if (submissionParams is SubmissionParams.RevisionSubmissionParams) { log.info { "Associating uploaded sequence data with existing sequence entries with uploadId $uploadId" } @@ -150,17 +154,32 @@ class SubmitModel( metadataTempFileToDelete.delete() } - val sequenceTempFileToDelete = MaybeFile() - try { - val sequenceStream = getStreamFromFile( - submissionParams.sequenceFile, - uploadId, - sequenceFileTypes, - sequenceTempFileToDelete, - ) - uploadSequences(uploadId, sequenceStream, batchSize, submissionParams.organism) - } finally { - sequenceTempFileToDelete.delete() + val sequenceFile = submissionParams.sequenceFile + if (sequenceFile == null) { + if (requiresSequenceFile(submissionParams.organism)) { + throw BadRequestException( + "Submissions for organism ${submissionParams.organism.name} require a sequence file.", + ) + } + } else { + if (!requiresSequenceFile(submissionParams.organism)) { + throw BadRequestException( + "Sequence uploads are not allowed for organism ${submissionParams.organism.name}.", + ) + } + + val sequenceTempFileToDelete = MaybeFile() + try { + val sequenceStream = getStreamFromFile( + sequenceFile, + uploadId, + sequenceFileTypes, + sequenceTempFileToDelete, + ) + uploadSequences(uploadId, sequenceStream, batchSize, submissionParams.organism) + } finally { + sequenceTempFileToDelete.delete() + } } } @@ -324,4 +343,8 @@ class SubmitModel( SequenceUploadAuxTable.select(SequenceUploadAuxTable.sequenceSubmissionIdColumn).count() > 0 return metadataInAuxTable || sequencesInAuxTable } + + private fun requiresSequenceFile(organism: Organism) = backendConfig.getInstanceConfig(organism) + .schema + .allowSubmissionOfConsensusSequences } diff --git a/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt b/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt index 17cff685b4..900ce64ead 100644 --- a/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt +++ b/backend/src/main/kotlin/org/loculus/backend/service/submission/UploadDatabaseService.kt @@ -149,14 +149,17 @@ class UploadDatabaseService( jsonb_build_object( 'metadata', metadata_upload_aux_table.metadata, 'unalignedNucleotideSequences', - jsonb_object_agg( - sequence_upload_aux_table.segment_name, - sequence_upload_aux_table.compressed_sequence_data::jsonb + COALESCE( + jsonb_object_agg( + sequence_upload_aux_table.segment_name, + sequence_upload_aux_table.compressed_sequence_data::jsonb + ) FILTER (WHERE sequence_upload_aux_table.segment_name IS NOT NULL), + '{}'::jsonb ) ) FROM metadata_upload_aux_table - JOIN + LEFT JOIN sequence_upload_aux_table ON metadata_upload_aux_table.upload_id = sequence_upload_aux_table.upload_id AND metadata_upload_aux_table.submission_id = sequence_upload_aux_table.submission_id diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/TestHelpers.kt b/backend/src/test/kotlin/org/loculus/backend/controller/TestHelpers.kt index 6cf573c562..d28c7ed1f9 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/TestHelpers.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/TestHelpers.kt @@ -31,6 +31,7 @@ import org.testcontainers.shaded.org.awaitility.Awaitility.await const val DEFAULT_ORGANISM = "dummyOrganism" const val OTHER_ORGANISM = "otherOrganism" +const val ORGANISM_WITHOUT_CONSENSUS_SEQUENCES = "dummyOrganismWithoutConsensusSequences" const val DEFAULT_PIPELINE_VERSION = 1L const val DEFAULT_EXTERNAL_METADATA_UPDATER = "ena" diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ExtractUnprocessedDataEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ExtractUnprocessedDataEndpointTest.kt index 52bbfa62ce..85a1470306 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ExtractUnprocessedDataEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ExtractUnprocessedDataEndpointTest.kt @@ -5,6 +5,7 @@ import org.hamcrest.CoreMatchers.hasItem import org.hamcrest.CoreMatchers.`is` import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers.allOf +import org.hamcrest.Matchers.anEmptyMap import org.hamcrest.Matchers.containsInAnyOrder import org.hamcrest.Matchers.empty import org.hamcrest.Matchers.greaterThan @@ -12,6 +13,8 @@ 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.GeneticSequence +import org.loculus.backend.api.OriginalData import org.loculus.backend.api.Status.IN_PROCESSING import org.loculus.backend.api.Status.RECEIVED import org.loculus.backend.api.UnprocessedData @@ -19,6 +22,7 @@ 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.ORGANISM_WITHOUT_CONSENSUS_SEQUENCES import org.loculus.backend.controller.OTHER_ORGANISM import org.loculus.backend.controller.assertStatusIs import org.loculus.backend.controller.expectForbiddenResponse @@ -27,7 +31,6 @@ import org.loculus.backend.controller.expectUnauthorizedResponse import org.loculus.backend.controller.getAccessionVersions import org.loculus.backend.controller.jwtForDefaultUser import org.loculus.backend.controller.submission.SubmitFiles.DefaultFiles -import org.loculus.backend.controller.submission.SubmitFiles.DefaultFiles.NUMBER_OF_SEQUENCES import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.HttpHeaders.ETAG import org.springframework.test.web.servlet.result.MockMvcResultMatchers.header @@ -181,4 +184,37 @@ class ExtractUnprocessedDataEndpointTest( `is`(empty()), ) } + + @Test + fun `GIVEN entries for organism without consensus sequences THEN only returns metadata`() { + val submissionResult = convenienceClient.submitDefaultFiles(organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES) + val accessionVersions = submissionResult.submissionIdMappings + + val result = client.extractUnprocessedData( + numberOfSequenceEntries = DefaultFiles.NUMBER_OF_SEQUENCES, + organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES, + ) + val responseBody = result.expectNdjsonAndGetContent() + assertThat(responseBody, hasSize(DefaultFiles.NUMBER_OF_SEQUENCES)) + assertThat( + responseBody, + hasItem( + allOf( + hasProperty("accession", `is`(accessionVersions[0].accession)), + hasProperty("version", `is`(1L)), + hasProperty( + "data", + allOf( + hasProperty>("metadata", `is`(defaultOriginalData.metadata)), + hasProperty("unalignedNucleotideSequences", `is`(anEmptyMap())), + ), + ), + hasProperty("submissionId", matchesRegex("custom[0-9]")), + hasProperty("submitter", `is`(DEFAULT_USER_NAME)), + hasProperty("groupId", `is`(submissionResult.groupId)), + hasProperty("submittedAt", greaterThan(1_700_000_000L)), + ), + ), + ) + } } diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedProcessedData.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedProcessedData.kt index 7f4a57a931..0939aafcaf 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedProcessedData.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/PreparedProcessedData.kt @@ -6,6 +6,7 @@ import com.fasterxml.jackson.databind.node.IntNode import com.fasterxml.jackson.databind.node.NullNode import com.fasterxml.jackson.databind.node.TextNode import org.loculus.backend.api.GeneName +import org.loculus.backend.api.GeneticSequence import org.loculus.backend.api.Insertion import org.loculus.backend.api.PreprocessingAnnotation import org.loculus.backend.api.PreprocessingAnnotationSource @@ -99,6 +100,21 @@ val defaultProcessedDataMultiSegmented = ProcessedData( ), ) +val defaultProcessedDataWithoutSequences = ProcessedData( + metadata = mapOf( + "date" to TextNode("2002-12-15"), + "host" to TextNode("google.com"), + "region" to TextNode("Europe"), + "country" to TextNode("Spain"), + "division" to NullNode.instance, + ), + unalignedNucleotideSequences = emptyMap(), + alignedNucleotideSequences = emptyMap(), + nucleotideInsertions = emptyMap(), + alignedAminoAcidSequences = emptyMap(), + aminoAcidInsertions = emptyMap(), +) + private val defaultSuccessfulSubmittedData = SubmittedProcessedData( accession = "If a test result shows this, processed data was not prepared correctly.", version = 1, diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt index 4d9c361c3d..a689e65fa3 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/ReviseEndpointTest.kt @@ -18,6 +18,7 @@ import org.loculus.backend.api.UnprocessedData 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.ORGANISM_WITHOUT_CONSENSUS_SEQUENCES import org.loculus.backend.controller.OTHER_ORGANISM import org.loculus.backend.controller.SUPER_USER_NAME import org.loculus.backend.controller.assertStatusIs @@ -29,7 +30,7 @@ import org.loculus.backend.controller.groupmanagement.andGetGroupId import org.loculus.backend.controller.jwtForSuperUser import org.loculus.backend.controller.submission.SubmitFiles.DefaultFiles import org.springframework.beans.factory.annotation.Autowired -import org.springframework.http.MediaType +import org.springframework.http.MediaType.APPLICATION_JSON_VALUE import org.springframework.mock.web.MockMultipartFile import org.springframework.test.web.servlet.ResultMatcher import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content @@ -67,7 +68,7 @@ class ReviseEndpointTest( jwt = jwtForSuperUser, ) .andExpect(status().isOk) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) .andExpect(jsonPath("\$.length()").value(DefaultFiles.NUMBER_OF_SEQUENCES)) .andExpect(jsonPath("\$[0].submissionId").value("custom0")) .andExpect(jsonPath("\$[0].accession").value(accessions.first())) @@ -86,7 +87,7 @@ class ReviseEndpointTest( DefaultFiles.sequencesFile, ) .andExpect(status().isOk) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) .andExpect(jsonPath("\$.length()").value(DefaultFiles.NUMBER_OF_SEQUENCES)) .andExpect(jsonPath("\$[0].submissionId").value("custom0")) .andExpect(jsonPath("\$[0].accession").value(accessions.first())) @@ -129,7 +130,7 @@ class ReviseEndpointTest( ), SubmitFiles.sequenceFileWith(), ).andExpect(status().isUnprocessableEntity) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) .andExpect( jsonPath("\$.detail").value( "Accessions 123 do not exist", @@ -149,7 +150,7 @@ class ReviseEndpointTest( organism = OTHER_ORGANISM, ) .andExpect(status().isUnprocessableEntity) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) .andExpect( jsonPath("\$.detail").value( containsString("accession versions are not of organism otherOrganism:"), @@ -168,7 +169,7 @@ class ReviseEndpointTest( jwt = generateJwtFor(notSubmitter), ) .andExpect(status().isForbidden) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) .andExpect( jsonPath( "\$.detail", @@ -186,7 +187,7 @@ class ReviseEndpointTest( DefaultFiles.sequencesFile, ) .andExpect(status().isUnprocessableEntity) - .andExpect(content().contentType(MediaType.APPLICATION_JSON_VALUE)) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) .andExpect( jsonPath( "\$.detail", @@ -198,6 +199,64 @@ class ReviseEndpointTest( ) } + @Test + fun `GIVEN no consensus sequences file for organism that requires one THEN throws bad request error`() { + val accessions = convenienceClient.prepareDataTo(APPROVED_FOR_RELEASE).map { it.accession } + + client.reviseSequenceEntries( + metadataFile = DefaultFiles.getRevisedMetadataFile(accessions), + sequencesFile = null, + ) + .andExpect(status().isBadRequest) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect( + jsonPath("\$.detail").value("Submissions for organism $DEFAULT_ORGANISM require a sequence file."), + ) + } + + @Test + fun `GIVEN sequence file for organism without consensus sequences THEN returns bad request`() { + val accessions = convenienceClient.prepareDataTo( + status = APPROVED_FOR_RELEASE, + organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES, + ) + .map { it.accession } + + client.reviseSequenceEntries( + DefaultFiles.getRevisedMetadataFile(accessions), + DefaultFiles.sequencesFile, + organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES, + ) + .andExpect(status().isBadRequest) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect( + jsonPath( + "\$.detail", + ).value("Sequence uploads are not allowed for organism $ORGANISM_WITHOUT_CONSENSUS_SEQUENCES."), + ) + } + + @Test + fun `GIVEN no sequence file for organism without consensus sequences THEN data is accepted`() { + val accessions = convenienceClient.prepareDataTo( + status = APPROVED_FOR_RELEASE, + organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES, + ) + .map { it.accession } + + client.reviseSequenceEntries( + metadataFile = DefaultFiles.getRevisedMetadataFile(accessions), + sequencesFile = null, + organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES, + ) + .andExpect(status().isOk) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("\$.length()").value(DefaultFiles.NUMBER_OF_SEQUENCES)) + .andExpect(jsonPath("\$[0].submissionId").value("custom0")) + .andExpect(jsonPath("\$[0].accession").value(accessions.first())) + .andExpect(jsonPath("\$[0].version").value(2)) + } + @ParameterizedTest(name = "GIVEN {0} THEN throws error \"{5}\"") @MethodSource("badRequestForRevision") fun `GIVEN invalid data THEN throws bad request`( @@ -231,7 +290,7 @@ class ReviseEndpointTest( SubmitFiles.sequenceFileWith(name = "notSequencesFile"), status().isBadRequest, "Bad Request", - "Required part 'sequenceFile' is not present.", + "Submissions for organism $DEFAULT_ORGANISM require a sequence file.", ), Arguments.of( "wrong extension for metadata file", 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 cc97aa4fcc..eba76f6dd8 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 @@ -33,14 +33,16 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post class SubmissionControllerClient(private val mockMvc: MockMvc, private val objectMapper: ObjectMapper) { fun submit( metadataFile: MockMultipartFile, - sequencesFile: MockMultipartFile, + sequencesFile: MockMultipartFile? = null, organism: String = DEFAULT_ORGANISM, groupId: Int, dataUseTerm: DataUseTerms = DataUseTerms.Open, jwt: String? = jwtForDefaultUser, ): ResultActions = mockMvc.perform( multipart(addOrganismToPath("/submit", organism = organism)) - .file(sequencesFile) + .apply { + sequencesFile?.let { file(sequencesFile) } + } .file(metadataFile) .param("groupId", groupId.toString()) .param("dataUseTermsType", dataUseTerm.type.name) @@ -175,9 +177,11 @@ class SubmissionControllerClient(private val mockMvc: MockMvc, private val objec .content( """{ "accessionVersionsFilter": ${serialize(accessionVersionsFilter)}, - ${submitterNamesFilter?.let { - """"submitterNamesFilter": [${it.joinToString(",") { name -> "\"$name\"" }}],""" - } ?: ""} + ${ + submitterNamesFilter?.let { + """"submitterNamesFilter": [${it.joinToString(",") { name -> "\"$name\"" }}],""" + } ?: "" + } "scope": "$scope" }""", ) @@ -243,12 +247,14 @@ class SubmissionControllerClient(private val mockMvc: MockMvc, private val objec fun reviseSequenceEntries( metadataFile: MockMultipartFile, - sequencesFile: MockMultipartFile, + sequencesFile: MockMultipartFile?, organism: String = DEFAULT_ORGANISM, jwt: String? = jwtForDefaultUser, ): ResultActions = mockMvc.perform( multipart(addOrganismToPath("/revise", organism = organism)) - .file(sequencesFile) + .apply { + sequencesFile?.let { file(sequencesFile) } + } .file(metadataFile) .withAuth(jwt), ) 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 1cc5ab2299..a3e313beae 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 @@ -11,6 +11,7 @@ import org.loculus.backend.api.EditedSequenceEntryData import org.loculus.backend.api.GeneticSequence import org.loculus.backend.api.GetSequenceResponse import org.loculus.backend.api.Organism +import org.loculus.backend.api.OriginalData import org.loculus.backend.api.ProcessedData import org.loculus.backend.api.ProcessingResult import org.loculus.backend.api.SequenceEntryStatus @@ -24,6 +25,7 @@ import org.loculus.backend.controller.DEFAULT_GROUP import org.loculus.backend.controller.DEFAULT_ORGANISM import org.loculus.backend.controller.DEFAULT_PIPELINE_VERSION import org.loculus.backend.controller.DEFAULT_USER_NAME +import org.loculus.backend.controller.ORGANISM_WITHOUT_CONSENSUS_SEQUENCES import org.loculus.backend.controller.OTHER_ORGANISM import org.loculus.backend.controller.expectNdjsonAndGetContent import org.loculus.backend.controller.generateJwtFor @@ -57,14 +59,19 @@ class SubmissionConvenienceClient( .createNewGroup(group = DEFAULT_GROUP, jwt = generateJwtFor(username)) .andGetGroupId() - val isMultiSegmented = backendConfig - .getInstanceConfig(Organism(organism)) + val instanceConfig = backendConfig.getInstanceConfig(Organism(organism)) + + val isMultiSegmented = instanceConfig .referenceGenomes .nucleotideSequences.size > 1 + val doesNotAllowSequenceFile = !instanceConfig.schema.allowSubmissionOfConsensusSequences + val submit = client.submit( DefaultFiles.metadataFile, - if (isMultiSegmented) { + if (doesNotAllowSequenceFile) { + null + } else if (isMultiSegmented) { DefaultFiles.sequencesFileMultiSegmented } else { DefaultFiles.sequencesFile @@ -153,6 +160,11 @@ class SubmissionConvenienceClient( accession = it.accession, ) + ORGANISM_WITHOUT_CONSENSUS_SEQUENCES -> PreparedProcessedData.successfullyProcessed( + accession = it.accession, + ) + .copy(data = defaultProcessedDataWithoutSequences) + else -> throw Exception("Test issue: There is no mapping of processed data for organism $organism") } }.toTypedArray(), @@ -303,15 +315,33 @@ class SubmissionConvenienceClient( ), ).processingResultCounts[processingResult]!!.toInt() - fun submitDefaultEditedData(accessions: List, userName: String = DEFAULT_USER_NAME) { + fun submitEditedData( + accessions: List, + organism: String = DEFAULT_ORGANISM, + userName: String = DEFAULT_USER_NAME, + editedData: OriginalData, + ) { accessions.forEach { accession -> client.submitEditedSequenceEntryVersion( - EditedSequenceEntryData(accession, 1L, defaultOriginalData), + EditedSequenceEntryData(accession, 1L, editedData), jwt = generateJwtFor(userName), + organism = organism, ) + .andExpect(status().isNoContent) } } + fun submitDefaultEditedData( + accessions: List, + organism: String = DEFAULT_ORGANISM, + userName: String = DEFAULT_USER_NAME, + ) = submitEditedData( + accessions, + organism = organism, + userName = userName, + editedData = defaultOriginalData, + ) + fun approveProcessedSequenceEntries( accessionVersionsFilter: List, organism: String = DEFAULT_ORGANISM, diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionJourneyTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionJourneyTest.kt index aa501cf1f8..7ef673d7f2 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionJourneyTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmissionJourneyTest.kt @@ -1,6 +1,7 @@ package org.loculus.backend.controller.submission import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.anEmptyMap import org.hamcrest.Matchers.containsInAnyOrder import org.hamcrest.Matchers.empty import org.hamcrest.Matchers.`is` @@ -8,6 +9,7 @@ import org.junit.jupiter.api.Test import org.loculus.backend.api.AccessionVersion import org.loculus.backend.api.AccessionVersionInterface import org.loculus.backend.api.GeneticSequence +import org.loculus.backend.api.OriginalData import org.loculus.backend.api.ProcessedData import org.loculus.backend.api.Status.APPROVED_FOR_RELEASE import org.loculus.backend.api.Status.IN_PROCESSING @@ -15,6 +17,7 @@ import org.loculus.backend.api.Status.PROCESSED import org.loculus.backend.api.Status.RECEIVED import org.loculus.backend.controller.DEFAULT_ORGANISM import org.loculus.backend.controller.EndpointTest +import org.loculus.backend.controller.ORGANISM_WITHOUT_CONSENSUS_SEQUENCES import org.loculus.backend.controller.OTHER_ORGANISM import org.loculus.backend.controller.assertHasError import org.loculus.backend.controller.assertStatusIs @@ -170,6 +173,75 @@ class SubmissionJourneyTest(@Autowired val convenienceClient: SubmissionConvenie ) } + @Test + fun `Entries without consensus sequences - submission, edit, approval`() { + val accessions = convenienceClient.submitDefaultFiles(organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES) + .submissionIdMappings + .map { it.accession } + + val getSequenceEntry = { + convenienceClient.getSequenceEntry( + accession = accessions.first(), + version = 1, + organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES, + ) + } + + getSequenceEntry().assertStatusIs(RECEIVED) + + convenienceClient.extractUnprocessedData(organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES) + convenienceClient.submitProcessedData( + accessions.map { + PreparedProcessedData.withErrors(accession = it) + .copy(data = defaultProcessedDataWithoutSequences) + }, + organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES, + ) + + getSequenceEntry().assertStatusIs(PROCESSED) + .assertHasError(true) + + convenienceClient.submitEditedData( + accessions, + organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES, + editedData = OriginalData( + metadata = defaultOriginalData.metadata, + unalignedNucleotideSequences = emptyMap(), + ), + ) + getSequenceEntry().assertStatusIs(RECEIVED) + + convenienceClient.extractUnprocessedData(organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES) + getSequenceEntry().assertStatusIs(IN_PROCESSING) + + convenienceClient.submitProcessedData( + accessions.map { + PreparedProcessedData.successfullyProcessed(accession = it) + .copy(data = defaultProcessedDataWithoutSequences) + }, + organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES, + ) + getSequenceEntry().assertStatusIs(PROCESSED) + .assertHasError(false) + + convenienceClient.approveProcessedSequenceEntries( + accessions.map { + AccessionVersion(it, 1) + }, + organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES, + ) + getSequenceEntry().assertStatusIs(APPROVED_FOR_RELEASE) + + val releasedData = convenienceClient.getReleasedData(organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES) + assertThat(releasedData.size, `is`(DefaultFiles.NUMBER_OF_SEQUENCES)) + val releasedDatum = releasedData.first() + assertThat(releasedDatum.unalignedNucleotideSequences, `is`(anEmptyMap())) + assertThat(releasedDatum.alignedNucleotideSequences, `is`(anEmptyMap())) + assertThat(releasedDatum.alignedAminoAcidSequences, `is`(anEmptyMap())) + assertThat(releasedDatum.nucleotideInsertions, `is`(anEmptyMap())) + assertThat(releasedDatum.aminoAcidInsertions, `is`(anEmptyMap())) + } + private fun getAccessionVersionsOfProcessedData(processedData: List>) = processedData .map { it.metadata } .map { it["accessionVersion"]!!.asText() } diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt index ecc5b55c2c..57cbddab60 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointTest.kt @@ -17,6 +17,7 @@ import org.loculus.backend.api.Organism import org.loculus.backend.config.BackendConfig import org.loculus.backend.controller.DEFAULT_ORGANISM import org.loculus.backend.controller.EndpointTest +import org.loculus.backend.controller.ORGANISM_WITHOUT_CONSENSUS_SEQUENCES import org.loculus.backend.controller.OTHER_ORGANISM import org.loculus.backend.controller.expectUnauthorizedResponse import org.loculus.backend.controller.generateJwtFor @@ -195,6 +196,54 @@ class SubmitEndpointTest( .andExpect(jsonPath("\$.detail", containsString(expectedMessage))) } + @Test + fun `GIVEN no sequence file for organism that requires one THEN returns bad request`() { + submissionControllerClient.submit( + metadataFile = DefaultFiles.metadataFile, + sequencesFile = null, + organism = DEFAULT_ORGANISM, + groupId = groupId, + ) + .andExpect(status().isBadRequest) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect( + jsonPath("\$.detail").value("Submissions for organism $DEFAULT_ORGANISM require a sequence file."), + ) + } + + @Test + fun `GIVEN sequence file for organism without consensus sequences THEN returns bad request`() { + submissionControllerClient.submit( + metadataFile = DefaultFiles.metadataFile, + sequencesFile = DefaultFiles.sequencesFile, + organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES, + groupId = groupId, + ) + .andExpect(status().isBadRequest) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect( + jsonPath( + "\$.detail", + ).value("Sequence uploads are not allowed for organism $ORGANISM_WITHOUT_CONSENSUS_SEQUENCES."), + ) + } + + @Test + fun `GIVEN no sequence file for organism without consensus sequences THEN data is accepted`() { + submissionControllerClient.submit( + metadataFile = DefaultFiles.metadataFile, + sequencesFile = null, + organism = ORGANISM_WITHOUT_CONSENSUS_SEQUENCES, + groupId = groupId, + ) + .andExpect(status().isOk) + .andExpect(content().contentType(APPLICATION_JSON_VALUE)) + .andExpect(jsonPath("\$.length()").value(NUMBER_OF_SEQUENCES)) + .andExpect(jsonPath("\$[0].submissionId").value("custom0")) + .andExpect(jsonPath("\$[0].accession", containsString(backendConfig.accessionPrefix))) + .andExpect(jsonPath("\$[0].version").value(1)) + } + companion object { @JvmStatic @@ -244,7 +293,7 @@ class SubmitEndpointTest( SubmitFiles.sequenceFileWith(name = "notSequencesFile"), status().isBadRequest, "Bad Request", - "Required part 'sequenceFile' is not present.", + "Submissions for organism $DEFAULT_ORGANISM require a sequence file.", DEFAULT_ORGANISM, DataUseTerms.Open, ), diff --git a/backend/src/test/resources/backend_config.json b/backend/src/test/resources/backend_config.json index 9f8c3a59d5..878c02484a 100644 --- a/backend/src/test/resources/backend_config.json +++ b/backend/src/test/resources/backend_config.json @@ -22,6 +22,7 @@ }, "schema": { "organismName": "Test", + "allowSubmissionOfConsensusSequences": true, "metadata": [ { "name": "date", @@ -124,6 +125,7 @@ }, "schema": { "organismName": "Test", + "allowSubmissionOfConsensusSequences": true, "metadata": [ { "name": "date", @@ -181,6 +183,45 @@ } ] } + }, + "dummyOrganismWithoutConsensusSequences": { + "referenceGenomes": { + "nucleotideSequences": [], + "genes": [] + }, + "schema": { + "organismName": "Test without consensus sequences", + "allowSubmissionOfConsensusSequences": false, + "metadata": [ + { + "name": "date", + "type": "date", + "required": true + }, + { + "name": "region", + "type": "string", + "autocomplete": true, + "required": true + }, + { + "name": "country", + "type": "string", + "autocomplete": true, + "required": true + }, + { + "name": "division", + "type": "string", + "autocomplete": true + }, + { + "name": "host", + "type": "string", + "autocomplete": true + } + ] + } } } } diff --git a/backend/src/test/resources/backend_config_single_segment.json b/backend/src/test/resources/backend_config_single_segment.json index f1d2c5dd6d..e61b1c5c72 100644 --- a/backend/src/test/resources/backend_config_single_segment.json +++ b/backend/src/test/resources/backend_config_single_segment.json @@ -76,4 +76,4 @@ } } } -} \ No newline at end of file +} From 0c0096f2ab2160498b70f55281891b229eb5361d Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Fri, 17 Jan 2025 10:11:07 +0100 Subject: [PATCH 03/14] feat(website): allow organisms without sequences --- website/src/components/Edit/EditPage.spec.tsx | 16 ++++- website/src/components/Edit/EditPage.tsx | 64 ++++++++++------- .../DownloadDialog/DownloadDialog.spec.tsx | 30 +++++++- .../components/SearchPage/SearchForm.spec.tsx | 1 + .../src/components/SearchPage/SearchForm.tsx | 24 ++++--- .../components/SearchPage/SearchFullUI.tsx | 1 + .../SearchPage/fields/MutationField.tsx | 2 +- .../SequenceDetailsPage/SequenceDataUI.tsx | 23 +++--- .../SequenceDetailsPage/getTableData.spec.ts | 52 ++++++++++---- .../SequenceDetailsPage/getTableData.ts | 17 +++-- .../components/Submission/DataUploadForm.tsx | 72 ++++++++++--------- .../Submission/FileUpload/UploadComponent.tsx | 4 +- .../components/Submission/RevisionForm.tsx | 3 + .../Submission/SubmissionForm.spec.tsx | 32 ++++++++- .../components/Submission/SubmissionForm.tsx | 3 + .../submission/[groupId]/revise.astro | 4 +- .../submission/[groupId]/submit.astro | 4 +- .../edit/[accession]/[version].astro | 2 + website/src/types/backend.ts | 2 +- website/tests/playwrightSetup.ts | 1 + 20 files changed, 247 insertions(+), 110 deletions(-) diff --git a/website/src/components/Edit/EditPage.spec.tsx b/website/src/components/Edit/EditPage.spec.tsx index 97ae43f265..65e2fa8f10 100644 --- a/website/src/components/Edit/EditPage.spec.tsx +++ b/website/src/components/Edit/EditPage.spec.tsx @@ -6,7 +6,7 @@ import { beforeEach, describe, expect, test } from 'vitest'; import { EditPage } from './EditPage.tsx'; import { defaultReviewData, editableEntry, metadataKey, testAccessToken, testOrganism } from '../../../vitest.setup.ts'; -import type { SequenceEntryToEdit, UnprocessedMetadataRecord } from '../../types/backend.ts'; +import type { UnprocessedMetadataRecord } from '../../types/backend.ts'; import type { ClientConfig } from '../../types/runtimeConfig.ts'; const queryClient = new QueryClient(); @@ -19,7 +19,11 @@ const inputFields = [ }, ]; -function renderEditPage(editedData: SequenceEntryToEdit = defaultReviewData, clientConfig: ClientConfig = dummyConfig) { +function renderEditPage({ + editedData = defaultReviewData, + clientConfig = dummyConfig, + allowSubmissionOfConsensusSequences = true, +} = {}) { render( , ); @@ -51,6 +56,13 @@ describe('EditPage', () => { await userEvent.click(submitButton); }); + test('should render without allowed submission of consensus sequences', () => { + renderEditPage({ allowSubmissionOfConsensusSequences: false }); + + expect(screen.getByText(/Original Data/i)).toBeInTheDocument(); + expectTextInSequenceData.originalMetadata(defaultReviewData.originalData.metadata); + }); + test('should show original data and processed data', () => { renderEditPage(); diff --git a/website/src/components/Edit/EditPage.tsx b/website/src/components/Edit/EditPage.tsx index 991996e78b..94bf2c6157 100644 --- a/website/src/components/Edit/EditPage.tsx +++ b/website/src/components/Edit/EditPage.tsx @@ -28,6 +28,7 @@ type EditPageProps = { dataToEdit: SequenceEntryToEdit; accessToken: string; inputFields: InputField[]; + allowSubmissionOfConsensusSequences: boolean; }; const logger = getClientLogger('EditPage'); @@ -75,7 +76,8 @@ const InnerEditPage: FC = ({ clientConfig, accessToken, inputFields, -}: EditPageProps) => { + allowSubmissionOfConsensusSequences, +}) => { const [editedMetadata, setEditedMetadata] = useState(mapMetadataToRow(dataToEdit)); const [editedSequences, setEditedSequences] = useState(mapSequencesToRow(dataToEdit)); const [processedSequenceTab, setProcessedSequenceTab] = useState(0); @@ -102,7 +104,9 @@ const InnerEditPage: FC = ({ if (isCreatingRevision) { submitRevision({ metadataFile: createMetadataTsv(editedMetadata, dataToEdit.submissionId, dataToEdit.accession), - sequenceFile: createSequenceFasta(editedSequences, dataToEdit.submissionId), + sequenceFile: allowSubmissionOfConsensusSequences + ? createSequenceFasta(editedSequences, dataToEdit.submissionId) + : undefined, }); } else { submitEdit({ @@ -141,25 +145,31 @@ const InnerEditPage: FC = ({ setEditedMetadata={setEditedMetadata} inputFields={inputFields} /> - + {allowSubmissionOfConsensusSequences && ( + + )} - - - + {allowSubmissionOfConsensusSequences && ( + <> + + + + + )} - {processedSequences.length > 0 && ( + {allowSubmissionOfConsensusSequences && processedSequences.length > 0 && (
{processedSequences.map(({ label }, i) => ( @@ -199,16 +209,18 @@ const InnerEditPage: FC = ({ Submit - + {allowSubmissionOfConsensusSequences && ( + + )}
); diff --git a/website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx b/website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx index c05c37219f..1cfe86aa85 100644 --- a/website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx +++ b/website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx @@ -21,12 +21,19 @@ const defaultReferenceGenome: ReferenceGenomesSequenceNames = { const defaultLapisUrl = 'https://lapis'; const defaultOrganism = 'ebola'; -async function renderDialog(downloadParams: SequenceFilter = new SelectFilter(new Set())) { +async function renderDialog({ + downloadParams = new SelectFilter(new Set()), + allowSubmissionOfConsensusSequences = true, +}: { + downloadParams?: SequenceFilter; + allowSubmissionOfConsensusSequences?: boolean; +} = {}) { render( , ); @@ -49,7 +56,16 @@ describe('DownloadDialog', () => { }); test('should generate the right download link from filters', async () => { - await renderDialog(new FieldFilter({ accession: ['accession1', 'accession2'], field1: 'value1' }, {}, [])); + await renderDialog({ + downloadParams: new FieldFilter( + { + accession: ['accession1', 'accession2'], + field1: 'value1', + }, + {}, + [], + ), + }); await checkAgreement(); let [path, query] = getDownloadHref()?.split('?') ?? []; @@ -79,7 +95,7 @@ describe('DownloadDialog', () => { }); test('should generate the right download link from selected sequences', async () => { - await renderDialog(new SelectFilter(new Set(['SEQID1', 'SEQID2']))); + await renderDialog({ downloadParams: new SelectFilter(new Set(['SEQID1', 'SEQID2'])) }); await checkAgreement(); let [path, query] = getDownloadHref()?.split('?') ?? []; @@ -107,6 +123,14 @@ describe('DownloadDialog', () => { /downloadAsFile=true&downloadFileBasename=ebola_nuc_\d{4}-\d{2}-\d{2}T\d{4}&compression=zstd&accessionVersion=SEQID1&accessionVersion=SEQID2/, ); }); + + test('should render with allowSubmissionOfConsensusSequences = false', async () => { + await renderDialog({ allowSubmissionOfConsensusSequences: false }); + await checkAgreement(); + + const [path] = getDownloadHref()?.split('?') ?? []; + expect(path).toBe(`${defaultLapisUrl}/sample/details`); + }); }); async function checkAgreement() { diff --git a/website/src/components/SearchPage/SearchForm.spec.tsx b/website/src/components/SearchPage/SearchForm.spec.tsx index 5517a682f4..ee46215394 100644 --- a/website/src/components/SearchPage/SearchForm.spec.tsx +++ b/website/src/components/SearchPage/SearchForm.spec.tsx @@ -69,6 +69,7 @@ const renderSearchForm = ({ setASearchVisibility, referenceGenomesSequenceNames, lapisSearchParameters, + showMutationSearch: true, }; render( diff --git a/website/src/components/SearchPage/SearchForm.tsx b/website/src/components/SearchPage/SearchForm.tsx index 71eabdfc1d..ffe7cd0771 100644 --- a/website/src/components/SearchPage/SearchForm.tsx +++ b/website/src/components/SearchPage/SearchForm.tsx @@ -33,6 +33,7 @@ interface SearchFormProps { setASearchVisibility: (fieldName: string, value: boolean) => void; referenceGenomesSequenceNames: ReferenceGenomesSequenceNames; lapisSearchParameters: Record; // eslint-disable-line @typescript-eslint/no-explicit-any -- TODO(#3451) use `unknown`or proper types + showMutationSearch: boolean; } export const SearchForm = ({ @@ -44,6 +45,7 @@ export const SearchForm = ({ setASearchVisibility, referenceGenomesSequenceNames, lapisSearchParameters, + showMutationSearch, }: SearchFormProps) => { const visibleFields = consolidatedMetadataSchema.filter((field) => searchVisibilities.get(field.name)); @@ -103,16 +105,20 @@ export const SearchForm = ({ )} />
- setSomeFieldValues(['accession', value])} - /> +
+ setSomeFieldValues(['accession', value])} + /> +
- setSomeFieldValues(['mutation', value])} - /> + {showMutationSearch && ( + setSomeFieldValues(['mutation', value])} + /> + )} {visibleFields.map((filter) => (
diff --git a/website/src/components/SearchPage/fields/MutationField.tsx b/website/src/components/SearchPage/fields/MutationField.tsx index ecc07e57fc..0a6f57bf77 100644 --- a/website/src/components/SearchPage/fields/MutationField.tsx +++ b/website/src/components/SearchPage/fields/MutationField.tsx @@ -71,7 +71,7 @@ export const MutationField: FC = ({ referenceGenomesSequence }; return ( -
+
= ({ )} -
- -
+ {schema.allowSubmissionOfConsensusSequences && ( +
+ +
+ )} {isMyGroup && accessToken !== undefined && (

@@ -96,6 +98,7 @@ export const SequenceDataUI: FC = ({ /> )} + {/* TODO ! */} { beforeEach(() => { mockRequest.lapis.details(200, { info, data: [toLapisEntry({ dummyField: 'dummyValue' })] }); @@ -124,7 +128,7 @@ describe('getTableData', () => { label: 'Substitutions', name: 'nucleotideSubstitutions', value: '', - header: 'Nucleotide mutations', + header: nucleotideMutationsHeader, customDisplay: { type: 'badge', value: [ @@ -159,14 +163,14 @@ describe('getTableData', () => { label: 'Deletions', name: 'nucleotideDeletions', value: '20, 21, 39-45, 400', - header: 'Nucleotide mutations', + header: nucleotideMutationsHeader, type: { kind: 'mutation' }, }); expect(data).toContainEqual({ label: 'Substitutions', name: 'aminoAcidSubstitutions', value: '', - header: 'Amino acid mutations', + header: aminoAcidMutationsHeader, customDisplay: { type: 'badge', value: [ @@ -201,7 +205,7 @@ describe('getTableData', () => { label: 'Deletions', name: 'aminoAcidDeletions', value: 'gene1:20-23, gene1:40', - header: 'Amino acid mutations', + header: aminoAcidMutationsHeader, type: { kind: 'mutation' }, }); }); @@ -217,14 +221,14 @@ describe('getTableData', () => { label: 'Insertions', name: 'nucleotideInsertions', value: 'nucleotideInsertion1, nucleotideInsertion2', - header: 'Nucleotide mutations', + header: nucleotideMutationsHeader, type: { kind: 'mutation' }, }); expect(data).toContainEqual({ label: 'Insertions', name: 'aminoAcidInsertions', value: 'aminoAcidInsertion1, aminoAcidInsertion2', - header: 'Amino acid mutations', + header: aminoAcidMutationsHeader, type: { kind: 'mutation' }, }); }); @@ -255,6 +259,30 @@ describe('getTableData', () => { expect(isRevocation).toBe(expectedIsRevocation); } }); + + test('should not return mutation data when allowSubmissionOfConsensusSequences = false', async () => { + mockRequest.lapis.nucleotideMutations(200, { info, data: nucleotideMutations }); + mockRequest.lapis.aminoAcidMutations(200, { info, data: aminoAcidMutations }); + mockRequest.lapis.details(200, { info, data: [{ timestampField: 1706194761 }] }); + + const result = await getTableData( + accessionVersion, + { + ...schema, + allowSubmissionOfConsensusSequences: false, + }, + lapisClient, + ); + + const data = result._unsafeUnwrap().data; + + const mutationTableEntries = data.filter((entry) => + [nucleotideMutationsHeader, aminoAcidMutationsHeader].includes(entry.header), + ); + + expect(data.length).greaterThanOrEqual(1, 'data.length'); + expect(mutationTableEntries).toStrictEqual([]); + }); }); function toLapisEntry(entry: Record, isRevocation = false) { @@ -454,7 +482,7 @@ const defaultMutationsInsertionsDeletionsList: TableDataEntry[] = [ label: 'Substitutions', name: 'nucleotideSubstitutions', value: '', - header: 'Nucleotide mutations', + header: nucleotideMutationsHeader, customDisplay: { type: 'badge', value: [], @@ -465,21 +493,21 @@ const defaultMutationsInsertionsDeletionsList: TableDataEntry[] = [ label: 'Deletions', name: 'nucleotideDeletions', value: '', - header: 'Nucleotide mutations', + header: nucleotideMutationsHeader, type: { kind: 'mutation' }, }, { label: 'Insertions', name: 'nucleotideInsertions', value: '', - header: 'Nucleotide mutations', + header: nucleotideMutationsHeader, type: { kind: 'mutation' }, }, { label: 'Substitutions', name: 'aminoAcidSubstitutions', value: '', - header: 'Amino acid mutations', + header: aminoAcidMutationsHeader, customDisplay: { type: 'badge', value: [], @@ -490,14 +518,14 @@ const defaultMutationsInsertionsDeletionsList: TableDataEntry[] = [ label: 'Deletions', name: 'aminoAcidDeletions', value: '', - header: 'Amino acid mutations', + header: aminoAcidMutationsHeader, type: { kind: 'mutation' }, }, { label: 'Insertions', name: 'aminoAcidInsertions', value: '', - header: 'Amino acid mutations', + header: aminoAcidMutationsHeader, type: { kind: 'mutation' }, }, ]; diff --git a/website/src/components/SequenceDetailsPage/getTableData.ts b/website/src/components/SequenceDetailsPage/getTableData.ts index 3bba7d87b9..0809c9e341 100644 --- a/website/src/components/SequenceDetailsPage/getTableData.ts +++ b/website/src/components/SequenceDetailsPage/getTableData.ts @@ -169,13 +169,16 @@ function toTableData(config: Schema) { header: metadata.header ?? '', type: { kind: 'metadata', metadataType: metadata.type }, })); - const mutations = mutationDetails( - nucleotideMutations, - aminoAcidMutations, - nucleotideInsertions, - aminoAcidInsertions, - ); - data.push(...mutations); + + if (config.allowSubmissionOfConsensusSequences) { + const mutations = mutationDetails( + nucleotideMutations, + aminoAcidMutations, + nucleotideInsertions, + aminoAcidInsertions, + ); + data.push(...mutations); + } return data; }; diff --git a/website/src/components/Submission/DataUploadForm.tsx b/website/src/components/Submission/DataUploadForm.tsx index 0acd00cb7b..336901a6c3 100644 --- a/website/src/components/Submission/DataUploadForm.tsx +++ b/website/src/components/Submission/DataUploadForm.tsx @@ -36,6 +36,7 @@ type DataUploadFormProps = { referenceGenomeSequenceNames: ReferenceGenomesSequenceNames; onSuccess: () => void; onError: (message: string) => void; + allowSubmissionOfConsensusSequences: boolean; }; const logger = getClientLogger('DataUploadForm'); @@ -84,15 +85,13 @@ const DataUseTerms = ({ const DevExampleData = ({ setExampleEntries, exampleEntries, - metadataFile, - sequenceFile, handleLoadExampleData, + dataIsLoaded, }: { setExampleEntries: (entries: number) => void; exampleEntries: number | undefined; - metadataFile: File | null; - sequenceFile: File | null; handleLoadExampleData: () => void; + dataIsLoaded: boolean; }) => { return (

@@ -108,7 +107,7 @@ const DevExampleData = ({ Load Example Data {' '}
- {metadataFile && sequenceFile && Example data loaded} + {dataIsLoaded && Example data loaded}

); }; @@ -122,9 +121,10 @@ const InnerDataUploadForm = ({ onError, group, referenceGenomeSequenceNames, + allowSubmissionOfConsensusSequences, }: DataUploadFormProps) => { - const [metadataFile, setMetadataFile] = useState(null); - const [sequenceFile, setSequenceFile] = useState(null); + const [metadataFile, setMetadataFile] = useState(undefined); + const [sequenceFile, setSequenceFile] = useState(undefined); const [exampleEntries, setExampleEntries] = useState(10); const { submit, revise, isLoading } = useSubmitFiles(accessToken, organism, clientConfig, onSuccess, onError); @@ -143,10 +143,12 @@ const InnerDataUploadForm = ({ const exampleMetadataContent = action === `submit` ? metadataFileContent : revisedMetadataFileContent; const metadataFile = createTempFile(exampleMetadataContent, 'text/tab-separated-values', 'metadata.tsv'); - const sequenceFile = createTempFile(sequenceFileContent, 'application/octet-stream', 'sequences.fasta'); - setMetadataFile(metadataFile); - setSequenceFile(sequenceFile); + + if (allowSubmissionOfConsensusSequences) { + const sequenceFile = createTempFile(sequenceFileContent, 'application/octet-stream', 'sequences.fasta'); + setSequenceFile(sequenceFile); + } }; const handleSubmit = (event: FormEvent) => { @@ -168,7 +170,7 @@ const InnerDataUploadForm = ({ onError('Please select metadata file'); return; } - if (!sequenceFile) { + if (!sequenceFile && allowSubmissionOfConsensusSequences) { onError('Please select a sequences file'); return; } @@ -201,8 +203,12 @@ const InnerDataUploadForm = ({
-

Sequences and metadata

-

Select your sequence data and metadata files

+

+ {allowSubmissionOfConsensusSequences ? 'Sequences and metadata' : 'Metadata'} +

+

+ Select your {allowSubmissionOfConsensusSequences && 'sequence data and'}metadata files +

{action === 'revise' && ( @@ -250,28 +256,30 @@ const InnerDataUploadForm = ({ .

- {(organism.startsWith('not-aligned-organism') || organism.startsWith('dummy-organism')) && - action === 'submit' && ( - - )} + {organism.startsWith('dummy-organism') && action === 'submit' && ( + + )}
-
- - -
+ {allowSubmissionOfConsensusSequences && ( +
+ + +
+ )}
void; + setFile: (file: File | undefined) => void; name: string; ariaLabel: string; fileKind: FileKind; @@ -37,7 +37,7 @@ export const UploadComponent = ({ }, ); } - setFile(processedFile !== null ? processedFile.inner() : null); + setFile(processedFile !== null ? processedFile.inner() : undefined); rawSetMyFile(processedFile); }, [setFile, rawSetMyFile], diff --git a/website/src/components/Submission/RevisionForm.tsx b/website/src/components/Submission/RevisionForm.tsx index 27dc7277a6..320828c219 100644 --- a/website/src/components/Submission/RevisionForm.tsx +++ b/website/src/components/Submission/RevisionForm.tsx @@ -13,6 +13,7 @@ type RevisionFormProps = { clientConfig: ClientConfig; group: Group; referenceGenomeSequenceNames: ReferenceGenomesSequenceNames; + allowSubmissionOfConsensusSequences: boolean; }; export const RevisionForm: FC = ({ @@ -21,6 +22,7 @@ export const RevisionForm: FC = ({ clientConfig, group, referenceGenomeSequenceNames, + allowSubmissionOfConsensusSequences, }) => { return (
@@ -35,6 +37,7 @@ export const RevisionForm: FC = ({ onSuccess={() => { window.location.href = routes.userSequenceReviewPage(organism, group.groupId); }} + allowSubmissionOfConsensusSequences={allowSubmissionOfConsensusSequences} />
); diff --git a/website/src/components/Submission/SubmissionForm.spec.tsx b/website/src/components/Submission/SubmissionForm.spec.tsx index ff6b041bc4..b9ae70671c 100644 --- a/website/src/components/Submission/SubmissionForm.spec.tsx +++ b/website/src/components/Submission/SubmissionForm.spec.tsx @@ -1,12 +1,12 @@ import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { toast } from 'react-toastify'; -import { describe, expect, test, vi } from 'vitest'; +import { afterEach, describe, expect, test, vi } from 'vitest'; import { SubmissionForm } from './SubmissionForm'; import { mockRequest, testAccessToken, testConfig, testGroups, testOrganism } from '../../../vitest.setup.ts'; import type { Group, ProblemDetail, SubmissionIdMapping } from '../../types/backend.ts'; -import type { ReferenceGenomesSequenceNames, ReferenceAccession } from '../../types/referencesGenomes.ts'; +import type { ReferenceAccession, ReferenceGenomesSequenceNames } from '../../types/referencesGenomes.ts'; vi.mock('../../api', () => ({ getClientLogger: () => ({ @@ -48,7 +48,7 @@ const defaultReferenceGenomesSequenceNames: ReferenceGenomesSequenceNames = { insdcAccessionFull: [defaultAccession], }; -function renderSubmissionForm() { +function renderSubmissionForm({ allowSubmissionOfConsensusSequences = true } = {}) { return render( , ); } @@ -69,6 +70,10 @@ const testResponse: SubmissionIdMapping[] = [ ]; describe('SubmitForm', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + test('should handle file upload and server response', async () => { mockRequest.backend.submit(200, testResponse); mockRequest.backend.getGroupsOfUser(); @@ -180,4 +185,25 @@ describe('SubmitForm', () => { }); }); } + + test('should accept submission without sequence file for organism that does not allow consensus sequences', async () => { + mockRequest.backend.submit(200, testResponse); + mockRequest.backend.getGroupsOfUser(); + + const { getByLabelText } = renderSubmissionForm({ + allowSubmissionOfConsensusSequences: false, + }); + + await userEvent.upload(getByLabelText(/Metadata File/i), metadataFile); + await userEvent.click( + getByLabelText(/I confirm I have not and will not submit this data independently to INSDC/i), + ); + await userEvent.click( + getByLabelText(/I confirm that the data submitted is not sensitive or human-identifiable/i), + ); + + await waitFor(() => { + expect(toast.error).not.toHaveBeenCalled(); + }); + }); }); diff --git a/website/src/components/Submission/SubmissionForm.tsx b/website/src/components/Submission/SubmissionForm.tsx index bc10c357b2..47a44f25e5 100644 --- a/website/src/components/Submission/SubmissionForm.tsx +++ b/website/src/components/Submission/SubmissionForm.tsx @@ -13,6 +13,7 @@ type SubmissionFormProps = { clientConfig: ClientConfig; group: Group; referenceGenomeSequenceNames: ReferenceGenomesSequenceNames; + allowSubmissionOfConsensusSequences: boolean; }; export const SubmissionForm: FC = ({ @@ -21,6 +22,7 @@ export const SubmissionForm: FC = ({ clientConfig, group, referenceGenomeSequenceNames, + allowSubmissionOfConsensusSequences, }) => { return (
@@ -35,6 +37,7 @@ export const SubmissionForm: FC = ({ onSuccess={() => { window.location.href = routes.userSequenceReviewPage(organism, group.groupId); }} + allowSubmissionOfConsensusSequences={allowSubmissionOfConsensusSequences} />
); diff --git a/website/src/pages/[organism]/submission/[groupId]/revise.astro b/website/src/pages/[organism]/submission/[groupId]/revise.astro index 7f34789237..181c024392 100644 --- a/website/src/pages/[organism]/submission/[groupId]/revise.astro +++ b/website/src/pages/[organism]/submission/[groupId]/revise.astro @@ -2,7 +2,7 @@ import { cleanOrganism } from '../../../../components/Navigation/cleanOrganism'; import { RevisionForm } from '../../../../components/Submission/RevisionForm'; import SubmissionPageWrapper from '../../../../components/Submission/SubmissionPageWrapper.astro'; -import { getRuntimeConfig } from '../../../../config'; +import { getRuntimeConfig, getSchema } from '../../../../config'; import { getAccessToken } from '../../../../utils/getAccessToken'; import { getReferenceGenomesSequenceNames } from '../../../../utils/search'; import { getGroupsAndCurrentGroup } from '../../../../utils/submissionPages'; @@ -17,6 +17,7 @@ if (!cleanedOrganism) { }; } const referenceGenomeSequenceNames = getReferenceGenomesSequenceNames(cleanedOrganism.key); +const schema = getSchema(cleanedOrganism.key); const groupsResult = await getGroupsAndCurrentGroup(Astro.params, Astro.locals.session); const clientConfig = getRuntimeConfig().public; @@ -32,6 +33,7 @@ const clientConfig = getRuntimeConfig().public; referenceGenomeSequenceNames={referenceGenomeSequenceNames} clientConfig={clientConfig} group={group} + allowSubmissionOfConsensusSequences={schema.allowSubmissionOfConsensusSequences} client:load /> ), diff --git a/website/src/pages/[organism]/submission/[groupId]/submit.astro b/website/src/pages/[organism]/submission/[groupId]/submit.astro index 2b6d10819b..1bc6588b75 100644 --- a/website/src/pages/[organism]/submission/[groupId]/submit.astro +++ b/website/src/pages/[organism]/submission/[groupId]/submit.astro @@ -2,7 +2,7 @@ import { cleanOrganism } from '../../../../components/Navigation/cleanOrganism'; import { SubmissionForm } from '../../../../components/Submission/SubmissionForm'; import SubmissionPageWrapper from '../../../../components/Submission/SubmissionPageWrapper.astro'; -import { getRuntimeConfig } from '../../../../config'; +import { getRuntimeConfig, getSchema } from '../../../../config'; import { getAccessToken } from '../../../../utils/getAccessToken'; import { getReferenceGenomesSequenceNames } from '../../../../utils/search'; import { getGroupsAndCurrentGroup } from '../../../../utils/submissionPages'; @@ -18,6 +18,7 @@ if (!cleanedOrganism) { }; } const referenceGenomeSequenceNames = getReferenceGenomesSequenceNames(cleanedOrganism.key); +const schema = getSchema(cleanedOrganism.key); const groupsResult = await getGroupsAndCurrentGroup(Astro.params, Astro.locals.session); @@ -39,6 +40,7 @@ Astro.response.headers.append('Expires', '0'); organism={organism} clientConfig={clientConfig} group={group} + allowSubmissionOfConsensusSequences={schema.allowSubmissionOfConsensusSequences} client:load /> ), diff --git a/website/src/pages/[organism]/submission/edit/[accession]/[version].astro b/website/src/pages/[organism]/submission/edit/[accession]/[version].astro index 3bacccb847..821162c33c 100644 --- a/website/src/pages/[organism]/submission/edit/[accession]/[version].astro +++ b/website/src/pages/[organism]/submission/edit/[accession]/[version].astro @@ -13,6 +13,7 @@ const { inputFields } = getSchema(organism); const accessToken = getAccessToken(Astro.locals.session)!; const clientConfig = getRuntimeConfig().public; +const schema = getSchema(organism); const dataToEdit = await BackendClient.create().getDataToEdit(organism, accessToken, accession, version); --- @@ -27,6 +28,7 @@ const dataToEdit = await BackendClient.create().getDataToEdit(organism, accessTo dataToEdit={dataToEdit} clientConfig={clientConfig} inputFields={inputFields} + allowSubmissionOfConsensusSequences={schema.allowSubmissionOfConsensusSequences} client:load /> ), diff --git a/website/src/types/backend.ts b/website/src/types/backend.ts index c21e1b27ec..ec4062667f 100644 --- a/website/src/types/backend.ts +++ b/website/src/types/backend.ts @@ -228,7 +228,7 @@ export type SequenceEntryToEdit = z.infer; export const uploadFiles = z.object({ metadataFile: z.instanceof(File), - sequenceFile: z.instanceof(File), + sequenceFile: z.instanceof(File).optional(), }); export const submitFiles = uploadFiles.merge( diff --git a/website/tests/playwrightSetup.ts b/website/tests/playwrightSetup.ts index 2c650ef9ba..5234cf1719 100644 --- a/website/tests/playwrightSetup.ts +++ b/website/tests/playwrightSetup.ts @@ -43,6 +43,7 @@ export default async function globalSetupForPlaywright() { defaultOrder: 'ascending', tableColumns: [], inputFields: [], + allowSubmissionOfConsensusSequences: true, }, e2eLogger, ); From c2ea82b31cf4c8f6b868a74129542d64da660ab8 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Fri, 17 Jan 2025 15:33:59 +0100 Subject: [PATCH 04/14] test(preprocessing): make sure that it works without sequences --- .../tests/test_processing_functions.py | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/preprocessing/nextclade/tests/test_processing_functions.py b/preprocessing/nextclade/tests/test_processing_functions.py index 853d9c891a..cb2ec6edca 100644 --- a/preprocessing/nextclade/tests/test_processing_functions.py +++ b/preprocessing/nextclade/tests/test_processing_functions.py @@ -10,7 +10,7 @@ ) from loculus_preprocessing.config import Config, get_config -from loculus_preprocessing.datatypes import ProcessedEntry, ProcessingAnnotation +from loculus_preprocessing.datatypes import ProcessedEntry, ProcessingAnnotation, UnprocessedData, UnprocessedEntry from loculus_preprocessing.prepro import process_all from loculus_preprocessing.processing_functions import ( ProcessingFunctions, @@ -452,6 +452,36 @@ def test_preprocessing(test_case_def: Case, config: Config, factory_custom: Proc verify_processed_entry(processed_entry, test_case.expected_output, test_case.name) +def test_preprocessing_without_consensus_sequences(): + sequence_name = "entry without sequences" + sequence_entery_data = UnprocessedEntry( + accessionVersion=f"LOC_01.1", + data=UnprocessedData( + submitter="test_submitter", + metadata={ + "ncbi_required_collection_date": "2024-01-01", + "name_required": sequence_name + }, + unalignedNucleotideSequences={}, + ), + ) + + config = get_config(test_config_file) + config.nucleotideSequences = [] + + result = process_all([sequence_entery_data], "temp_dataset_dir", config) + processed_entry = result[0] + + assert processed_entry.errors == [] + assert processed_entry.warnings == [] + assert processed_entry.data.metadata["name_required"] == sequence_name + assert processed_entry.data.unalignedNucleotideSequences == {} + assert processed_entry.data.alignedNucleotideSequences == {} + assert processed_entry.data.nucleotideInsertions == {} + assert processed_entry.data.alignedAminoAcidSequences == {} + assert processed_entry.data.aminoAcidInsertions == {} + + def test_format_frameshift(): # Test case 1: Empty input assert not format_frameshift("[]") From 255061d08d65e1c1f17aec56ad10c6251cb7100a Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Fri, 17 Jan 2025 16:43:05 +0100 Subject: [PATCH 05/14] docs: add `allowSubmissionOfConsensusSequences` to Helm chart config reference --- .../docs/reference/helm-chart-config.mdx | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/src/content/docs/reference/helm-chart-config.mdx b/docs/src/content/docs/reference/helm-chart-config.mdx index 842441a25a..f1bf8ec1bf 100644 --- a/docs/src/content/docs/reference/helm-chart-config.mdx +++ b/docs/src/content/docs/reference/helm-chart-config.mdx @@ -205,7 +205,10 @@ The configuration for the Helm chart is provided as a YAML file. It has the foll `website.websiteConfig.enableSubmissionPages` Boolean true - Whether to completely disable submission related pages. Setting this to false is useful when hosting Loculus for analysis-only purposes. + + Whether to completely disable submission related pages. Setting this to false is useful when hosting + Loculus for analysis-only purposes. + `website.runtimeConfig.public` @@ -229,7 +232,10 @@ The configuration for the Helm chart is provided as a YAML file. It has the foll `website.runtimeConfig.public.lapisUrlTemplate` String true - Overwrite the URLs where the client-side website code expects the LAPIS instances. Must contain `%organism%` as a placeholder. + + Overwrite the URLs where the client-side website code expects the LAPIS instances. Must contain + `%organism%` as a placeholder. + @@ -568,6 +574,15 @@ Each organism object has the following fields: press a button to do so. (For small genomes, this should probably be true.) + + `allowSubmissionOfConsensusSequences` + Boolean + true + + If `false`, the submission form will not allow submission of consensus sequences (i.e. the sequences + file must be omitted). All consensus sequence related parts on the website will be hidden. + + `description` String @@ -584,7 +599,9 @@ Each organism object has the following fields: `metadataTemplate` Array of String - Which input fields to add to the downloadable metadata template on the submission and revision page. + + Which input fields to add to the downloadable metadata template on the submission and revision page. + `earliestReleaseDate` From 385fa7f96e1fa6259d91b04890863ab7a00ccfd0 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Mon, 20 Jan 2025 08:35:55 +0100 Subject: [PATCH 06/14] fixup! feat(website): allow organisms without sequences --- website/src/components/SequenceDetailsPage/SequenceDataUI.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/website/src/components/SequenceDetailsPage/SequenceDataUI.tsx b/website/src/components/SequenceDetailsPage/SequenceDataUI.tsx index 2a4834a122..45c9ffd05b 100644 --- a/website/src/components/SequenceDetailsPage/SequenceDataUI.tsx +++ b/website/src/components/SequenceDetailsPage/SequenceDataUI.tsx @@ -98,7 +98,6 @@ export const SequenceDataUI: FC = ({ /> )} - {/* TODO ! */}
Date: Mon, 20 Jan 2025 09:02:39 +0100 Subject: [PATCH 07/14] fixup! feat(website): allow organisms without sequences --- .../components/Submission/DataUploadForm.tsx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/website/src/components/Submission/DataUploadForm.tsx b/website/src/components/Submission/DataUploadForm.tsx index 336901a6c3..1b3f9ad8c9 100644 --- a/website/src/components/Submission/DataUploadForm.tsx +++ b/website/src/components/Submission/DataUploadForm.tsx @@ -256,16 +256,17 @@ const InnerDataUploadForm = ({ .

- {organism.startsWith('dummy-organism') && action === 'submit' && ( - - )} + {(organism.startsWith('not-aligned-organism') || organism.startsWith('dummy-organism')) && + action === 'submit' && ( + + )}
From 0c08be4138ecc0e4b030087fc18a898450c961b7 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Mon, 20 Jan 2025 09:02:47 +0100 Subject: [PATCH 08/14] fixup! feat(kubernetes): add `allowSubmissionOfConsensusSequences` to the config --- kubernetes/loculus/values.yaml | 60 ++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index 3236b09459..209d065c20 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -1437,6 +1437,66 @@ defaultOrganisms: referenceGenomes: nucleotideSequences: [] genes: [] + not-aligned-organism: + schema: + image: "https://cdn.who.int/media/images/default-source/mca/mca-covid-19/coronavirus-2.tmb-1920v.jpg?sfvrsn=4dba955c_19" + organismName: "Test organism (without alignment)" + metadata: + - name: date + type: date + initiallyVisible: true + header: "Collection Details" + required: true + preprocessing: + function: parse_and_assert_past_date + inputs: + date: date + - name: region + type: string + initiallyVisible: true + generateIndex: true + autocomplete: true + header: "Collection Details" + - name: country + initiallyVisible: true + type: string + generateIndex: true + autocomplete: true + header: "Collection Details" + - name: division + initiallyVisible: true + type: string + generateIndex: true + autocomplete: true + header: "Collection Details" + - name: host + initiallyVisible: true + type: string + autocomplete: true + header: "Collection Details" + website: + tableColumns: + - country + - division + - date + defaultOrder: descending + defaultOrderBy: date + silo: + dateToSortBy: date + preprocessing: + - version: 1 + image: ghcr.io/loculus-project/preprocessing-nextclade + args: + - "prepro" + configFile: + log_level: DEBUG + genes: [] + batch_size: 100 + referenceGenomes: + nucleotideSequences: + - name: "main" + sequence: "[[URL:https://corneliusroemer.github.io/seqs/artefacts/sars-cov-2/reference.fasta]]" + genes: [] cchf: <<: *defaultOrganismConfig schema: From 7ab509ba6594a678aef3dc11e4491c284e2b9151 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Wed, 22 Jan 2025 08:45:23 +0100 Subject: [PATCH 09/14] minor text changes --- website/src/components/Submission/DataUploadForm.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/src/components/Submission/DataUploadForm.tsx b/website/src/components/Submission/DataUploadForm.tsx index 1b3f9ad8c9..acda38db51 100644 --- a/website/src/components/Submission/DataUploadForm.tsx +++ b/website/src/components/Submission/DataUploadForm.tsx @@ -107,7 +107,7 @@ const DevExampleData = ({ Load Example Data {' '}
- {dataIsLoaded && Example data loaded} + {dataIsLoaded && Data loaded}

); }; @@ -207,7 +207,7 @@ const InnerDataUploadForm = ({ {allowSubmissionOfConsensusSequences ? 'Sequences and metadata' : 'Metadata'}

- Select your {allowSubmissionOfConsensusSequences && 'sequence data and'}metadata files + Select your {allowSubmissionOfConsensusSequences && 'sequence data and '}metadata files

{action === 'revise' && ( From 9bdd0c3be3efcb8e936dd5af8848f3d86a73555e Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Wed, 22 Jan 2025 09:04:52 +0100 Subject: [PATCH 10/14] don't use a complicated looking template --- .../loculus/templates/_boolean-with-default-true.tpl | 4 ---- kubernetes/loculus/templates/_common-metadata.tpl | 12 ++++++++++-- 2 files changed, 10 insertions(+), 6 deletions(-) delete mode 100644 kubernetes/loculus/templates/_boolean-with-default-true.tpl diff --git a/kubernetes/loculus/templates/_boolean-with-default-true.tpl b/kubernetes/loculus/templates/_boolean-with-default-true.tpl deleted file mode 100644 index 4f059021aa..0000000000 --- a/kubernetes/loculus/templates/_boolean-with-default-true.tpl +++ /dev/null @@ -1,4 +0,0 @@ -{{/* "default true" can't be used, because "false | default true" will be true (but we would need the result false) */}} -{{- define "loculus.booleanWithDefaultTrue" -}} -{{ eq . false | ternary false true }} -{{- end -}} diff --git a/kubernetes/loculus/templates/_common-metadata.tpl b/kubernetes/loculus/templates/_common-metadata.tpl index 470331992b..812c85ee0f 100644 --- a/kubernetes/loculus/templates/_common-metadata.tpl +++ b/kubernetes/loculus/templates/_common-metadata.tpl @@ -169,7 +169,11 @@ organisms: {{- with ($instance.schema | include "loculus.patchMetadataSchema" | fromYaml) }} organismName: {{ quote .organismName }} loadSequencesAutomatically: {{ .loadSequencesAutomatically | default false }} - allowSubmissionOfConsensusSequences: {{ include "loculus.booleanWithDefaultTrue" .allowSubmissionOfConsensusSequences }} + {{- if (hasKey . "allowSubmissionOfConsensusSequences") }} + allowSubmissionOfConsensusSequences: {{ .allowSubmissionOfConsensusSequences }} + {{- else }} + allowSubmissionOfConsensusSequences: true + {{- end }} {{- $nucleotideSequences := .nucleotideSequences | default (list "main")}} {{ if .image }} image: {{ .image }} @@ -295,7 +299,11 @@ organisms: {{- with $instance.schema }} {{- $nucleotideSequences := .nucleotideSequences | default (list "main")}} organismName: {{ quote .organismName }} - allowSubmissionOfConsensusSequences: {{ include "loculus.booleanWithDefaultTrue" .allowSubmissionOfConsensusSequences }} + {{- if (hasKey . "allowSubmissionOfConsensusSequences") }} + allowSubmissionOfConsensusSequences: {{ .allowSubmissionOfConsensusSequences }} + {{- else }} + allowSubmissionOfConsensusSequences: true + {{- end }} metadata: {{- $args := dict "metadata" (include "loculus.patchMetadataSchema" . | fromYaml).metadata "nucleotideSequences" $nucleotideSequences}} {{ $metadata := include "loculus.generateBackendMetadata" $args | fromYaml }} From a45dce4d8c58c0b405a61d929373dd7ade8cd683 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Wed, 22 Jan 2025 09:22:04 +0100 Subject: [PATCH 11/14] add test that sequences download isn't present anymore --- .../DownloadDialog/DownloadDialog.spec.tsx | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx b/website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx index 1cfe86aa85..e94b28eeaf 100644 --- a/website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx +++ b/website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx @@ -55,6 +55,10 @@ describe('DownloadDialog', () => { expect(getDownloadHref()).toMatch(new RegExp(`^${defaultLapisUrl}`)); }); + const olderVersionsLabel = /Yes, include older versions/; + const rawNucleotideSequencesLabel = /Raw nucleotide sequences/; + const gzipCompressionLabel = /Gzip/; + test('should generate the right download link from filters', async () => { await renderDialog({ downloadParams: new FieldFilter( @@ -74,9 +78,9 @@ describe('DownloadDialog', () => { /downloadAsFile=true&downloadFileBasename=ebola_metadata_\d{4}-\d{2}-\d{2}T\d{4}&versionStatus=LATEST_VERSION&isRevocation=false&dataUseTerms=OPEN&dataFormat=tsv&accession=accession1&accession=accession2&field1=value1/, ); - await userEvent.click(screen.getByLabelText(/Yes, include older versions/)); - await userEvent.click(screen.getByLabelText(/Raw nucleotide sequences/)); - await userEvent.click(screen.getByLabelText(/Gzip/)); + await userEvent.click(screen.getByLabelText(olderVersionsLabel)); + await userEvent.click(screen.getByLabelText(rawNucleotideSequencesLabel)); + await userEvent.click(screen.getByLabelText(gzipCompressionLabel)); [path, query] = getDownloadHref()?.split('?') ?? []; expect(path).toBe(`${defaultLapisUrl}/sample/unalignedNucleotideSequences`); @@ -104,9 +108,9 @@ describe('DownloadDialog', () => { /downloadAsFile=true&downloadFileBasename=ebola_metadata_\d{4}-\d{2}-\d{2}T\d{4}&versionStatus=LATEST_VERSION&isRevocation=false&dataUseTerms=OPEN&dataFormat=tsv&accessionVersion=SEQID1&accessionVersion=SEQID2/, ); - await userEvent.click(screen.getByLabelText(/Yes, include older versions/)); - await userEvent.click(screen.getByLabelText(/Raw nucleotide sequences/)); - await userEvent.click(screen.getByLabelText(/Gzip/)); + await userEvent.click(screen.getByLabelText(olderVersionsLabel)); + await userEvent.click(screen.getByLabelText(rawNucleotideSequencesLabel)); + await userEvent.click(screen.getByLabelText(gzipCompressionLabel)); [path, query] = getDownloadHref()?.split('?') ?? []; expect(path).toBe(`${defaultLapisUrl}/sample/unalignedNucleotideSequences`); @@ -130,6 +134,10 @@ describe('DownloadDialog', () => { const [path] = getDownloadHref()?.split('?') ?? []; expect(path).toBe(`${defaultLapisUrl}/sample/details`); + + expect(screen.queryByLabelText(rawNucleotideSequencesLabel)).not.toBeInTheDocument(); + expect(screen.getByLabelText(olderVersionsLabel)).toBeInTheDocument(); + expect(screen.getByLabelText(gzipCompressionLabel)).toBeInTheDocument(); }); }); From db7d4119aa496b001528ad1d62d115f31c8e3a05 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Wed, 22 Jan 2025 11:09:01 +0100 Subject: [PATCH 12/14] refactor(backend): rename stuff --- .../main/kotlin/org/loculus/backend/model/SubmitModel.kt | 8 ++++---- .../controller/submission/SubmissionConvenienceClient.kt | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt index 1f26b9de95..1cdf5af555 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt @@ -108,7 +108,7 @@ class SubmitModel( batchSize, ) - if (requiresSequenceFile(submissionParams.organism)) { + if (requiresConsensusSequenceFile(submissionParams.organism)) { log.debug { "Validating submission with uploadId $uploadId" } val (metadataSubmissionIds, sequencesSubmissionIds) = uploadDatabaseService.getUploadSubmissionIds(uploadId) validateSubmissionIdSets(metadataSubmissionIds.toSet(), sequencesSubmissionIds.toSet()) @@ -156,13 +156,13 @@ class SubmitModel( val sequenceFile = submissionParams.sequenceFile if (sequenceFile == null) { - if (requiresSequenceFile(submissionParams.organism)) { + if (requiresConsensusSequenceFile(submissionParams.organism)) { throw BadRequestException( "Submissions for organism ${submissionParams.organism.name} require a sequence file.", ) } } else { - if (!requiresSequenceFile(submissionParams.organism)) { + if (!requiresConsensusSequenceFile(submissionParams.organism)) { throw BadRequestException( "Sequence uploads are not allowed for organism ${submissionParams.organism.name}.", ) @@ -344,7 +344,7 @@ class SubmitModel( return metadataInAuxTable || sequencesInAuxTable } - private fun requiresSequenceFile(organism: Organism) = backendConfig.getInstanceConfig(organism) + private fun requiresConsensusSequenceFile(organism: Organism) = backendConfig.getInstanceConfig(organism) .schema .allowSubmissionOfConsensusSequences } 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 a3e313beae..3fd889c135 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 @@ -65,11 +65,11 @@ class SubmissionConvenienceClient( .referenceGenomes .nucleotideSequences.size > 1 - val doesNotAllowSequenceFile = !instanceConfig.schema.allowSubmissionOfConsensusSequences + val doesNotAllowConsensusSequenceFile = !instanceConfig.schema.allowSubmissionOfConsensusSequences val submit = client.submit( DefaultFiles.metadataFile, - if (doesNotAllowSequenceFile) { + if (doesNotAllowConsensusSequenceFile) { null } else if (isMultiSegmented) { DefaultFiles.sequencesFileMultiSegmented From 4531b93dfabdc0b273fedbf181ae895ae6afa620 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Wed, 22 Jan 2025 11:57:43 +0100 Subject: [PATCH 13/14] rename config value --- .../kotlin/org/loculus/backend/config/Config.kt | 6 +++++- .../org/loculus/backend/model/SubmitModel.kt | 3 ++- .../submission/SubmissionConvenienceClient.kt | 4 +++- backend/src/test/resources/backend_config.json | 4 +++- .../docs/reference/helm-chart-config.mdx | 2 +- .../loculus/templates/_common-metadata.tpl | 12 ++---------- .../templates/_submission-data-types.tpl | 10 ++++++++++ kubernetes/loculus/values.yaml | 3 ++- website/src/components/Edit/EditPage.spec.tsx | 2 +- website/src/components/Edit/EditPage.tsx | 16 ++++++++-------- .../src/components/SearchPage/SearchFullUI.tsx | 4 ++-- .../SequenceDetailsPage/SequenceDataUI.tsx | 2 +- .../SequenceDetailsPage/getTableData.spec.ts | 8 ++++++-- .../SequenceDetailsPage/getTableData.ts | 2 +- .../components/Submission/DataUploadForm.tsx | 17 +++++++++-------- .../src/components/Submission/RevisionForm.tsx | 7 ++++--- .../Submission/SubmissionForm.spec.tsx | 2 +- .../components/Submission/SubmissionForm.tsx | 7 ++++--- .../submission/[groupId]/revise.astro | 2 +- .../submission/[groupId]/submit.astro | 2 +- .../submission/edit/[accession]/[version].astro | 2 +- website/src/types/config.ts | 7 ++++++- website/tests/playwrightSetup.ts | 4 +++- 23 files changed, 77 insertions(+), 51 deletions(-) create mode 100644 kubernetes/loculus/templates/_submission-data-types.tpl diff --git a/backend/src/main/kotlin/org/loculus/backend/config/Config.kt b/backend/src/main/kotlin/org/loculus/backend/config/Config.kt index 9e05330f6c..e258e2787c 100644 --- a/backend/src/main/kotlin/org/loculus/backend/config/Config.kt +++ b/backend/src/main/kotlin/org/loculus/backend/config/Config.kt @@ -23,7 +23,11 @@ data class Schema( val metadata: List, val externalMetadata: List = emptyList(), val earliestReleaseDate: EarliestReleaseDate = EarliestReleaseDate(false, emptyList()), - val allowSubmissionOfConsensusSequences: Boolean = true, + val submissionDataTypes: SubmissionDataTypes = SubmissionDataTypes(), +) + +data class SubmissionDataTypes( + val consensusSequences: Boolean = true, ) // The Json property names need to be kept in sync with website config enum `metadataPossibleTypes` in `config.ts` diff --git a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt index 1cdf5af555..b2e1fcfb28 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/SubmitModel.kt @@ -346,5 +346,6 @@ class SubmitModel( private fun requiresConsensusSequenceFile(organism: Organism) = backendConfig.getInstanceConfig(organism) .schema - .allowSubmissionOfConsensusSequences + .submissionDataTypes + .consensusSequences } 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 3fd889c135..61062f479c 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 @@ -65,7 +65,9 @@ class SubmissionConvenienceClient( .referenceGenomes .nucleotideSequences.size > 1 - val doesNotAllowConsensusSequenceFile = !instanceConfig.schema.allowSubmissionOfConsensusSequences + val doesNotAllowConsensusSequenceFile = !instanceConfig.schema + .submissionDataTypes + .consensusSequences val submit = client.submit( DefaultFiles.metadataFile, diff --git a/backend/src/test/resources/backend_config.json b/backend/src/test/resources/backend_config.json index 878c02484a..3729e7a6bf 100644 --- a/backend/src/test/resources/backend_config.json +++ b/backend/src/test/resources/backend_config.json @@ -191,7 +191,9 @@ }, "schema": { "organismName": "Test without consensus sequences", - "allowSubmissionOfConsensusSequences": false, + "submissionDataTypes": { + "consensusSequences": false + }, "metadata": [ { "name": "date", diff --git a/docs/src/content/docs/reference/helm-chart-config.mdx b/docs/src/content/docs/reference/helm-chart-config.mdx index f1bf8ec1bf..2bfbf416b8 100644 --- a/docs/src/content/docs/reference/helm-chart-config.mdx +++ b/docs/src/content/docs/reference/helm-chart-config.mdx @@ -575,7 +575,7 @@ Each organism object has the following fields: - `allowSubmissionOfConsensusSequences` + `submissionDataTypes.consensusSequences` Boolean true diff --git a/kubernetes/loculus/templates/_common-metadata.tpl b/kubernetes/loculus/templates/_common-metadata.tpl index 812c85ee0f..efe49d4de6 100644 --- a/kubernetes/loculus/templates/_common-metadata.tpl +++ b/kubernetes/loculus/templates/_common-metadata.tpl @@ -169,11 +169,7 @@ organisms: {{- with ($instance.schema | include "loculus.patchMetadataSchema" | fromYaml) }} organismName: {{ quote .organismName }} loadSequencesAutomatically: {{ .loadSequencesAutomatically | default false }} - {{- if (hasKey . "allowSubmissionOfConsensusSequences") }} - allowSubmissionOfConsensusSequences: {{ .allowSubmissionOfConsensusSequences }} - {{- else }} - allowSubmissionOfConsensusSequences: true - {{- end }} + {{- include "loculus.submissionDataTypes" . | nindent 6 }} {{- $nucleotideSequences := .nucleotideSequences | default (list "main")}} {{ if .image }} image: {{ .image }} @@ -299,11 +295,7 @@ organisms: {{- with $instance.schema }} {{- $nucleotideSequences := .nucleotideSequences | default (list "main")}} organismName: {{ quote .organismName }} - {{- if (hasKey . "allowSubmissionOfConsensusSequences") }} - allowSubmissionOfConsensusSequences: {{ .allowSubmissionOfConsensusSequences }} - {{- else }} - allowSubmissionOfConsensusSequences: true - {{- end }} + {{- include "loculus.submissionDataTypes" . | nindent 6 }} metadata: {{- $args := dict "metadata" (include "loculus.patchMetadataSchema" . | fromYaml).metadata "nucleotideSequences" $nucleotideSequences}} {{ $metadata := include "loculus.generateBackendMetadata" $args | fromYaml }} diff --git a/kubernetes/loculus/templates/_submission-data-types.tpl b/kubernetes/loculus/templates/_submission-data-types.tpl new file mode 100644 index 0000000000..64094b336d --- /dev/null +++ b/kubernetes/loculus/templates/_submission-data-types.tpl @@ -0,0 +1,10 @@ +{{- define "loculus.submissionDataTypes" -}} +submissionDataTypes: + {{- if (hasKey . "submissionDataTypes") }} + {{- with .submissionDataTypes }} + consensusSequences: {{ (hasKey . "consensusSequences") | ternary .consensusSequences "true" }} + {{- end }} + {{- else }} + consensusSequences: true + {{- end}} +{{- end }} diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index 209d065c20..6f1967291c 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -1382,7 +1382,8 @@ defaultOrganisms: image: "https://cdn.who.int/media/images/default-source/mca/mca-covid-19/coronavirus-2.tmb-1920v.jpg?sfvrsn=4dba955c_19" organismName: "Test organism (without consensus sequences)" nucleotideSequences: [] - allowSubmissionOfConsensusSequences: false + submissionDataTypes: + consensusSequences: false metadata: - name: date type: date diff --git a/website/src/components/Edit/EditPage.spec.tsx b/website/src/components/Edit/EditPage.spec.tsx index 65e2fa8f10..2a5c1f7d06 100644 --- a/website/src/components/Edit/EditPage.spec.tsx +++ b/website/src/components/Edit/EditPage.spec.tsx @@ -32,7 +32,7 @@ function renderEditPage({ clientConfig={clientConfig} accessToken={testAccessToken} inputFields={inputFields} - allowSubmissionOfConsensusSequences={allowSubmissionOfConsensusSequences} + submissionDataTypes={{ consensusSequences: allowSubmissionOfConsensusSequences }} /> , ); diff --git a/website/src/components/Edit/EditPage.tsx b/website/src/components/Edit/EditPage.tsx index 94bf2c6157..c4c80636c2 100644 --- a/website/src/components/Edit/EditPage.tsx +++ b/website/src/components/Edit/EditPage.tsx @@ -13,7 +13,7 @@ import { type SequenceEntryToEdit, approvedForReleaseStatus, } from '../../types/backend.ts'; -import { type InputField } from '../../types/config.ts'; +import { type InputField, type SubmissionDataTypes } from '../../types/config.ts'; import type { ClientConfig } from '../../types/runtimeConfig.ts'; import { createAuthorizationHeader } from '../../utils/createAuthorizationHeader.ts'; import { getAccessionVersionString } from '../../utils/extractAccessionVersion.ts'; @@ -28,7 +28,7 @@ type EditPageProps = { dataToEdit: SequenceEntryToEdit; accessToken: string; inputFields: InputField[]; - allowSubmissionOfConsensusSequences: boolean; + submissionDataTypes: SubmissionDataTypes; }; const logger = getClientLogger('EditPage'); @@ -76,7 +76,7 @@ const InnerEditPage: FC = ({ clientConfig, accessToken, inputFields, - allowSubmissionOfConsensusSequences, + submissionDataTypes, }) => { const [editedMetadata, setEditedMetadata] = useState(mapMetadataToRow(dataToEdit)); const [editedSequences, setEditedSequences] = useState(mapSequencesToRow(dataToEdit)); @@ -104,7 +104,7 @@ const InnerEditPage: FC = ({ if (isCreatingRevision) { submitRevision({ metadataFile: createMetadataTsv(editedMetadata, dataToEdit.submissionId, dataToEdit.accession), - sequenceFile: allowSubmissionOfConsensusSequences + sequenceFile: submissionDataTypes.consensusSequences ? createSequenceFasta(editedSequences, dataToEdit.submissionId) : undefined, }); @@ -145,7 +145,7 @@ const InnerEditPage: FC = ({ setEditedMetadata={setEditedMetadata} inputFields={inputFields} /> - {allowSubmissionOfConsensusSequences && ( + {submissionDataTypes.consensusSequences && ( = ({ )} - {allowSubmissionOfConsensusSequences && ( + {submissionDataTypes.consensusSequences && ( <> = ({ - {allowSubmissionOfConsensusSequences && processedSequences.length > 0 && ( + {submissionDataTypes.consensusSequences && processedSequences.length > 0 && (

{processedSequences.map(({ label }, i) => ( @@ -209,7 +209,7 @@ const InnerEditPage: FC = ({ Submit - {allowSubmissionOfConsensusSequences && ( + {submissionDataTypes.consensusSequences && (
@@ -377,7 +377,7 @@ export const InnerSearchFullUI = ({ downloadUrlGenerator={downloadUrlGenerator} sequenceFilter={sequencesFilter} referenceGenomesSequenceNames={referenceGenomesSequenceNames} - allowSubmissionOfConsensusSequences={schema.allowSubmissionOfConsensusSequences} + allowSubmissionOfConsensusSequences={schema.submissionDataTypes.consensusSequences} />
diff --git a/website/src/components/SequenceDetailsPage/SequenceDataUI.tsx b/website/src/components/SequenceDetailsPage/SequenceDataUI.tsx index 45c9ffd05b..05b121c2ac 100644 --- a/website/src/components/SequenceDetailsPage/SequenceDataUI.tsx +++ b/website/src/components/SequenceDetailsPage/SequenceDataUI.tsx @@ -68,7 +68,7 @@ export const SequenceDataUI: FC = ({ )} - {schema.allowSubmissionOfConsensusSequences && ( + {schema.submissionDataTypes.consensusSequences && (
{ accessionVersion, { ...schema, - allowSubmissionOfConsensusSequences: false, + submissionDataTypes: { + consensusSequences: false, + }, }, lapisClient, ); diff --git a/website/src/components/SequenceDetailsPage/getTableData.ts b/website/src/components/SequenceDetailsPage/getTableData.ts index 0809c9e341..5bb4d30ff3 100644 --- a/website/src/components/SequenceDetailsPage/getTableData.ts +++ b/website/src/components/SequenceDetailsPage/getTableData.ts @@ -170,7 +170,7 @@ function toTableData(config: Schema) { type: { kind: 'metadata', metadataType: metadata.type }, })); - if (config.allowSubmissionOfConsensusSequences) { + if (config.submissionDataTypes.consensusSequences) { const mutations = mutationDetails( nucleotideMutations, aminoAcidMutations, diff --git a/website/src/components/Submission/DataUploadForm.tsx b/website/src/components/Submission/DataUploadForm.tsx index acda38db51..16afa38b8a 100644 --- a/website/src/components/Submission/DataUploadForm.tsx +++ b/website/src/components/Submission/DataUploadForm.tsx @@ -24,6 +24,7 @@ import { createAuthorizationHeader } from '../../utils/createAuthorizationHeader import { stringifyMaybeAxiosError } from '../../utils/stringifyMaybeAxiosError.ts'; import { withQueryProvider } from '../common/withQueryProvider.tsx'; import { FASTA_FILE_KIND, METADATA_FILE_KIND } from './FileUpload/fileProcessing.ts'; +import type { SubmissionDataTypes } from '../../types/config.ts'; export type UploadAction = 'submit' | 'revise'; @@ -36,7 +37,7 @@ type DataUploadFormProps = { referenceGenomeSequenceNames: ReferenceGenomesSequenceNames; onSuccess: () => void; onError: (message: string) => void; - allowSubmissionOfConsensusSequences: boolean; + submissionDataTypes: SubmissionDataTypes; }; const logger = getClientLogger('DataUploadForm'); @@ -121,7 +122,7 @@ const InnerDataUploadForm = ({ onError, group, referenceGenomeSequenceNames, - allowSubmissionOfConsensusSequences, + submissionDataTypes, }: DataUploadFormProps) => { const [metadataFile, setMetadataFile] = useState(undefined); const [sequenceFile, setSequenceFile] = useState(undefined); @@ -145,7 +146,7 @@ const InnerDataUploadForm = ({ const metadataFile = createTempFile(exampleMetadataContent, 'text/tab-separated-values', 'metadata.tsv'); setMetadataFile(metadataFile); - if (allowSubmissionOfConsensusSequences) { + if (submissionDataTypes.consensusSequences) { const sequenceFile = createTempFile(sequenceFileContent, 'application/octet-stream', 'sequences.fasta'); setSequenceFile(sequenceFile); } @@ -170,7 +171,7 @@ const InnerDataUploadForm = ({ onError('Please select metadata file'); return; } - if (!sequenceFile && allowSubmissionOfConsensusSequences) { + if (!sequenceFile && submissionDataTypes.consensusSequences) { onError('Please select a sequences file'); return; } @@ -204,10 +205,10 @@ const InnerDataUploadForm = ({

- {allowSubmissionOfConsensusSequences ? 'Sequences and metadata' : 'Metadata'} + {submissionDataTypes.consensusSequences ? 'Sequences and metadata' : 'Metadata'}

- Select your {allowSubmissionOfConsensusSequences && 'sequence data and '}metadata files + Select your {submissionDataTypes.consensusSequences && 'sequence data and '}metadata files

{action === 'revise' && ( @@ -263,14 +264,14 @@ const InnerDataUploadForm = ({ exampleEntries={exampleEntries} handleLoadExampleData={handleLoadExampleData} dataIsLoaded={ - !!metadataFile && (!allowSubmissionOfConsensusSequences || !!sequenceFile) + !!metadataFile && (!submissionDataTypes.consensusSequences || !!sequenceFile) } /> )}

- {allowSubmissionOfConsensusSequences && ( + {submissionDataTypes.consensusSequences && (
= ({ @@ -22,7 +23,7 @@ export const RevisionForm: FC = ({ clientConfig, group, referenceGenomeSequenceNames, - allowSubmissionOfConsensusSequences, + submissionDataTypes, }) => { return (
@@ -37,7 +38,7 @@ export const RevisionForm: FC = ({ onSuccess={() => { window.location.href = routes.userSequenceReviewPage(organism, group.groupId); }} - allowSubmissionOfConsensusSequences={allowSubmissionOfConsensusSequences} + submissionDataTypes={submissionDataTypes} />
); diff --git a/website/src/components/Submission/SubmissionForm.spec.tsx b/website/src/components/Submission/SubmissionForm.spec.tsx index b9ae70671c..c51de981d5 100644 --- a/website/src/components/Submission/SubmissionForm.spec.tsx +++ b/website/src/components/Submission/SubmissionForm.spec.tsx @@ -56,7 +56,7 @@ function renderSubmissionForm({ allowSubmissionOfConsensusSequences = true } = { organism={testOrganism} clientConfig={testConfig.public} group={group} - allowSubmissionOfConsensusSequences={allowSubmissionOfConsensusSequences} + submissionDataTypes={{ consensusSequences: allowSubmissionOfConsensusSequences }} />, ); } diff --git a/website/src/components/Submission/SubmissionForm.tsx b/website/src/components/Submission/SubmissionForm.tsx index 47a44f25e5..acb05094ee 100644 --- a/website/src/components/Submission/SubmissionForm.tsx +++ b/website/src/components/Submission/SubmissionForm.tsx @@ -6,6 +6,7 @@ import { routes } from '../../routes/routes.ts'; import { type Group } from '../../types/backend.ts'; import type { ReferenceGenomesSequenceNames } from '../../types/referencesGenomes'; import type { ClientConfig } from '../../types/runtimeConfig.ts'; +import type { SubmissionDataTypes } from '../../types/config.ts'; type SubmissionFormProps = { accessToken: string; @@ -13,7 +14,7 @@ type SubmissionFormProps = { clientConfig: ClientConfig; group: Group; referenceGenomeSequenceNames: ReferenceGenomesSequenceNames; - allowSubmissionOfConsensusSequences: boolean; + submissionDataTypes: SubmissionDataTypes; }; export const SubmissionForm: FC = ({ @@ -22,7 +23,7 @@ export const SubmissionForm: FC = ({ clientConfig, group, referenceGenomeSequenceNames, - allowSubmissionOfConsensusSequences, + submissionDataTypes, }) => { return (
@@ -37,7 +38,7 @@ export const SubmissionForm: FC = ({ onSuccess={() => { window.location.href = routes.userSequenceReviewPage(organism, group.groupId); }} - allowSubmissionOfConsensusSequences={allowSubmissionOfConsensusSequences} + submissionDataTypes={submissionDataTypes} />
); diff --git a/website/src/pages/[organism]/submission/[groupId]/revise.astro b/website/src/pages/[organism]/submission/[groupId]/revise.astro index 181c024392..49fe93058a 100644 --- a/website/src/pages/[organism]/submission/[groupId]/revise.astro +++ b/website/src/pages/[organism]/submission/[groupId]/revise.astro @@ -33,7 +33,7 @@ const clientConfig = getRuntimeConfig().public; referenceGenomeSequenceNames={referenceGenomeSequenceNames} clientConfig={clientConfig} group={group} - allowSubmissionOfConsensusSequences={schema.allowSubmissionOfConsensusSequences} + submissionDataTypes={schema.submissionDataTypes} client:load /> ), diff --git a/website/src/pages/[organism]/submission/[groupId]/submit.astro b/website/src/pages/[organism]/submission/[groupId]/submit.astro index 1bc6588b75..b51442eddf 100644 --- a/website/src/pages/[organism]/submission/[groupId]/submit.astro +++ b/website/src/pages/[organism]/submission/[groupId]/submit.astro @@ -40,7 +40,7 @@ Astro.response.headers.append('Expires', '0'); organism={organism} clientConfig={clientConfig} group={group} - allowSubmissionOfConsensusSequences={schema.allowSubmissionOfConsensusSequences} + submissionDataTypes={schema.submissionDataTypes} client:load /> ), diff --git a/website/src/pages/[organism]/submission/edit/[accession]/[version].astro b/website/src/pages/[organism]/submission/edit/[accession]/[version].astro index 821162c33c..50cafe4abb 100644 --- a/website/src/pages/[organism]/submission/edit/[accession]/[version].astro +++ b/website/src/pages/[organism]/submission/edit/[accession]/[version].astro @@ -28,7 +28,7 @@ const dataToEdit = await BackendClient.create().getDataToEdit(organism, accessTo dataToEdit={dataToEdit} clientConfig={clientConfig} inputFields={inputFields} - allowSubmissionOfConsensusSequences={schema.allowSubmissionOfConsensusSequences} + submissionDataTypes={schema.submissionDataTypes} client:load /> ), diff --git a/website/src/types/config.ts b/website/src/types/config.ts index ba7524a30c..b4fbbe1b34 100644 --- a/website/src/types/config.ts +++ b/website/src/types/config.ts @@ -96,6 +96,11 @@ export type GroupedMetadataFilter = { initiallyVisible?: boolean; }; +export const submissionDataTypesSchema = z.object({ + consensusSequences: z.boolean(), +}); +export type SubmissionDataTypes = z.infer; + export const schema = z.object({ organismName: z.string(), image: z.string().optional(), @@ -107,7 +112,7 @@ export const schema = z.object({ primaryKey: z.string(), defaultOrderBy: z.string(), defaultOrder: orderByType, - allowSubmissionOfConsensusSequences: z.boolean(), + submissionDataTypes: submissionDataTypesSchema, loadSequencesAutomatically: z.boolean().optional(), }); export type Schema = z.infer; diff --git a/website/tests/playwrightSetup.ts b/website/tests/playwrightSetup.ts index 5234cf1719..ba8bc1f97b 100644 --- a/website/tests/playwrightSetup.ts +++ b/website/tests/playwrightSetup.ts @@ -43,7 +43,9 @@ export default async function globalSetupForPlaywright() { defaultOrder: 'ascending', tableColumns: [], inputFields: [], - allowSubmissionOfConsensusSequences: true, + submissionDataTypes: { + consensusSequences: true, + }, }, e2eLogger, ); From c1284249710cebd5159f29ab0879cca6aa1931a7 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Wed, 22 Jan 2025 12:21:15 +0100 Subject: [PATCH 14/14] fix tests and format --- .../main/kotlin/org/loculus/backend/config/Config.kt | 4 +--- .../src/components/SearchPage/SearchFullUI.spec.tsx | 12 +++++++----- website/src/components/SearchPage/SearchFullUI.tsx | 2 +- website/src/components/Submission/RevisionForm.tsx | 2 +- website/src/components/Submission/SubmissionForm.tsx | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/backend/src/main/kotlin/org/loculus/backend/config/Config.kt b/backend/src/main/kotlin/org/loculus/backend/config/Config.kt index e258e2787c..a56907619b 100644 --- a/backend/src/main/kotlin/org/loculus/backend/config/Config.kt +++ b/backend/src/main/kotlin/org/loculus/backend/config/Config.kt @@ -26,9 +26,7 @@ data class Schema( val submissionDataTypes: SubmissionDataTypes = SubmissionDataTypes(), ) -data class SubmissionDataTypes( - val consensusSequences: Boolean = true, -) +data class SubmissionDataTypes(val consensusSequences: Boolean = true) // The Json property names need to be kept in sync with website config enum `metadataPossibleTypes` in `config.ts` // They also need to be in sync with SILO database config, as the Loculus config is a sort of superset of it diff --git a/website/src/components/SearchPage/SearchFullUI.spec.tsx b/website/src/components/SearchPage/SearchFullUI.spec.tsx index 0d6c256007..7e77bb21e4 100644 --- a/website/src/components/SearchPage/SearchFullUI.spec.tsx +++ b/website/src/components/SearchPage/SearchFullUI.spec.tsx @@ -3,11 +3,11 @@ import { render, screen, waitFor, waitForElementToBeRemoved } from '@testing-lib import userEvent from '@testing-library/user-event'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { SearchFullUI } from './SearchFullUI'; -import { testConfig, testOrganism, testSiteName } from '../../../vitest.setup.ts'; +import { type InnerSearchFullUIProps, SearchFullUI } from './SearchFullUI'; +import { testConfig, testOrganism } from '../../../vitest.setup.ts'; import { lapisClientHooks } from '../../services/serviceHooks.ts'; import type { MetadataFilter, Schema } from '../../types/config.ts'; -import type { ReferenceGenomesSequenceNames, ReferenceAccession } from '../../types/referencesGenomes.ts'; +import type { ReferenceAccession, ReferenceGenomesSequenceNames } from '../../types/referencesGenomes.ts'; global.ResizeObserver = class FakeResizeObserver { observe() {} @@ -89,18 +89,20 @@ function renderSearchFullUI({ accessToken: 'dummyAccessToken', referenceGenomesSequenceNames, myGroups: [], - websiteName: testSiteName, organism: testOrganism, clientConfig, schema: { metadata: metadataSchema, tableColumns: ['field1', 'field3'], primaryKey: 'accession', + submissionDataTypes: { + consensusSequences: true, + }, } as Schema, initialData: [], initialCount: 0, initialQueryDict: {}, - }; + } satisfies InnerSearchFullUIProps; render( diff --git a/website/src/components/SearchPage/SearchFullUI.tsx b/website/src/components/SearchPage/SearchFullUI.tsx index 43f12af615..e712981927 100644 --- a/website/src/components/SearchPage/SearchFullUI.tsx +++ b/website/src/components/SearchPage/SearchFullUI.tsx @@ -34,7 +34,7 @@ import { import { EditDataUseTermsModal } from '../DataUseTerms/EditDataUseTermsModal.tsx'; import ErrorBox from '../common/ErrorBox.tsx'; -interface InnerSearchFullUIProps { +export interface InnerSearchFullUIProps { accessToken?: string; referenceGenomesSequenceNames: ReferenceGenomesSequenceNames; myGroups: Group[]; diff --git a/website/src/components/Submission/RevisionForm.tsx b/website/src/components/Submission/RevisionForm.tsx index 5287ca8ec4..8666cd249c 100644 --- a/website/src/components/Submission/RevisionForm.tsx +++ b/website/src/components/Submission/RevisionForm.tsx @@ -5,9 +5,9 @@ import { DataUploadForm } from './DataUploadForm.tsx'; import { routes } from '../../routes/routes.ts'; import { type Group } from '../../types/backend.ts'; import type { InputField } from '../../types/config.ts'; +import type { SubmissionDataTypes } from '../../types/config.ts'; import type { ReferenceGenomesSequenceNames } from '../../types/referencesGenomes'; import type { ClientConfig } from '../../types/runtimeConfig.ts'; -import type { SubmissionDataTypes } from '../../types/config.ts'; type RevisionFormProps = { accessToken: string; diff --git a/website/src/components/Submission/SubmissionForm.tsx b/website/src/components/Submission/SubmissionForm.tsx index e711bd7ae6..9096b1308a 100644 --- a/website/src/components/Submission/SubmissionForm.tsx +++ b/website/src/components/Submission/SubmissionForm.tsx @@ -5,9 +5,9 @@ import { DataUploadForm } from './DataUploadForm.tsx'; import { routes } from '../../routes/routes.ts'; import { type Group } from '../../types/backend.ts'; import type { InputField } from '../../types/config.ts'; +import type { SubmissionDataTypes } from '../../types/config.ts'; import type { ReferenceGenomesSequenceNames } from '../../types/referencesGenomes'; import type { ClientConfig } from '../../types/runtimeConfig.ts'; -import type { SubmissionDataTypes } from '../../types/config.ts'; type SubmissionFormProps = { accessToken: string;