diff --git a/backend/src/main/kotlin/org/loculus/backend/config/BackendSpringConfig.kt b/backend/src/main/kotlin/org/loculus/backend/config/BackendSpringConfig.kt index 119665129b..6439ed30e4 100644 --- a/backend/src/main/kotlin/org/loculus/backend/config/BackendSpringConfig.kt +++ b/backend/src/main/kotlin/org/loculus/backend/config/BackendSpringConfig.kt @@ -1,5 +1,6 @@ package org.loculus.backend.config +import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.readValue import io.swagger.v3.oas.models.headers.Header @@ -146,7 +147,9 @@ internal fun validateEarliestReleaseDateFields(config: BackendConfig): List(File(configPath)) + val config = objectMapper + .enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES) + .readValue(File(configPath)) logger.info { "Loaded backend config from $configPath" } logger.info { "Config: $config" } val validationErrors = validateEarliestReleaseDateFields(config) 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 a56907619b..3d84ba284a 100644 --- a/backend/src/main/kotlin/org/loculus/backend/config/Config.kt +++ b/backend/src/main/kotlin/org/loculus/backend/config/Config.kt @@ -7,13 +7,15 @@ import org.loculus.backend.api.Organism data class BackendConfig( val organisms: Map, val accessionPrefix: String, - val dataUseTermsUrls: DataUseTermsUrls?, + val dataUseTerms: DataUseTerms, ) { fun getInstanceConfig(organism: Organism) = organisms[organism.name] ?: throw IllegalArgumentException( "Organism: ${organism.name} not found in backend config. Available organisms: ${organisms.keys}", ) } +data class DataUseTerms(val enabled: Boolean, val urls: DataUseTermsUrls?) + data class DataUseTermsUrls(val open: String, val restricted: String) data class InstanceConfig(val schema: Schema, val referenceGenomes: ReferenceGenome) 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 88ba74b6ad..f1f00ab502 100644 --- a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionController.kt +++ b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionController.kt @@ -33,6 +33,7 @@ import org.loculus.backend.api.SubmittedProcessedData import org.loculus.backend.api.UnprocessedData import org.loculus.backend.auth.AuthenticatedUser import org.loculus.backend.auth.HiddenParam +import org.loculus.backend.config.BackendConfig import org.loculus.backend.controller.LoculusCustomHeaders.X_TOTAL_RECORDS import org.loculus.backend.log.REQUEST_ID_MDC_KEY import org.loculus.backend.log.RequestIdContext @@ -76,10 +77,11 @@ open class SubmissionController( private val submissionDatabaseService: SubmissionDatabaseService, private val iteratorStreamer: IteratorStreamer, private val requestIdContext: RequestIdContext, + private val backendConfig: BackendConfig, ) { - @Operation(description = SUBMIT_DESCRIPTION) @ApiResponse(responseCode = "200", description = SUBMIT_RESPONSE_DESCRIPTION) + @ApiResponse(responseCode = "400", description = SUBMIT_ERROR_RESPONSE) @PostMapping("/submit", consumes = ["multipart/form-data"]) fun submit( @PathVariable @Valid organism: Organism, @@ -87,8 +89,10 @@ open class SubmissionController( @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 = "Data Use terms under which data is released.") @RequestParam dataUseTermsType: - DataUseTermsType, + @Parameter( + description = + "Data Use terms under which data is released. Mandatory when data use terms are enabled for this Instance.", + ) @RequestParam dataUseTermsType: DataUseTermsType?, @Parameter( description = "Mandatory when data use terms are set to 'RESTRICTED'." + @@ -96,13 +100,22 @@ open class SubmissionController( " Format: YYYY-MM-DD", ) @RequestParam restrictedUntil: String?, ): List { + var innerDataUseTermsType = DataUseTermsType.OPEN + if (backendConfig.dataUseTerms.enabled) { + if (dataUseTermsType == null) { + throw BadRequestException("the 'dataUseTermsType' needs to be provided.") + } else { + innerDataUseTermsType = dataUseTermsType + } + } + val params = SubmissionParams.OriginalSubmissionParams( organism, authenticatedUser, metadataFile, sequenceFile, groupId, - DataUseTerms.fromParameters(dataUseTermsType, restrictedUntil), + DataUseTerms.fromParameters(innerDataUseTermsType, restrictedUntil), ) return submitModel.processSubmissions(UUID.randomUUID().toString(), params) } diff --git a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt index 66a7711bb1..babbe5c2d8 100644 --- a/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt +++ b/backend/src/main/kotlin/org/loculus/backend/controller/SubmissionControllerDescriptions.kt @@ -8,6 +8,10 @@ The accession is the (globally unique) id that the system assigned to the sequen You can use this response to associate the user provided submissionId with the system assigned accession. """ +const val SUBMIT_ERROR_RESPONSE = """ +The data use terms type have not been provided, even though they are enabled for this Loculus instance. +""" + const val METADATA_FILE_DESCRIPTION = """ A TSV (tab separated values) file containing the metadata of the submitted sequence entries. The file may be compressed with zstd, xz, zip, gzip, lzma, bzip2 (with common extensions). diff --git a/backend/src/main/kotlin/org/loculus/backend/model/ReleasedDataModel.kt b/backend/src/main/kotlin/org/loculus/backend/model/ReleasedDataModel.kt index 3cccc63b84..619bcc0cf7 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/ReleasedDataModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/ReleasedDataModel.kt @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.node.TextNode import mu.KotlinLogging import org.loculus.backend.api.DataUseTerms import org.loculus.backend.api.GeneticSequence +import org.loculus.backend.api.MetadataMap import org.loculus.backend.api.Organism import org.loculus.backend.api.ProcessedData import org.loculus.backend.api.VersionStatus @@ -94,6 +95,9 @@ open class ReleasedDataModel( return "\"$lastUpdateTime\"" // ETag must be enclosed in double quotes } + private fun conditionalMetadata(condition: Boolean, values: () -> MetadataMap): MetadataMap = + if (condition) values() else emptyMap() + private fun computeAdditionalMetadataFields( rawProcessedData: RawProcessedData, latestVersions: Map, @@ -111,6 +115,13 @@ open class ReleasedDataModel( val earliestReleaseDate = earliestReleaseDateFinder?.calculateEarliestReleaseDate(rawProcessedData) + val dataUseTermsUrl: String? = backendConfig.dataUseTerms.urls?.let { urls -> + when (currentDataUseTerms) { + DataUseTerms.Open -> urls.open + is DataUseTerms.Restricted -> urls.restricted + } + } + var metadata = rawProcessedData.processedData.metadata + mapOf( ("accession" to TextNode(rawProcessedData.accession)), @@ -126,31 +137,41 @@ open class ReleasedDataModel( ("releasedAtTimestamp" to LongNode(rawProcessedData.releasedAtTimestamp.toTimestamp())), ("releasedDate" to TextNode(rawProcessedData.releasedAtTimestamp.toUtcDateString())), ("versionStatus" to TextNode(versionStatus.name)), - ("dataUseTerms" to TextNode(currentDataUseTerms.type.name)), - ("dataUseTermsRestrictedUntil" to restrictedDataUseTermsUntil), ("pipelineVersion" to LongNode(rawProcessedData.pipelineVersion)), ) + - if (rawProcessedData.isRevocation) { - mapOf("versionComment" to TextNode(rawProcessedData.versionComment)) - } else { - emptyMap() - }.let { - when (backendConfig.dataUseTermsUrls) { - null -> it - else -> { - val url = when (currentDataUseTerms) { - DataUseTerms.Open -> backendConfig.dataUseTermsUrls.open - is DataUseTerms.Restricted -> backendConfig.dataUseTermsUrls.restricted - } - it + ("dataUseTermsUrl" to TextNode(url)) - } - } - } + - if (earliestReleaseDate != null) { - mapOf("earliestReleaseDate" to TextNode(earliestReleaseDate.toUtcDateString())) - } else { - emptyMap() - } + conditionalMetadata( + backendConfig.dataUseTerms.enabled, + { + mapOf( + "dataUseTerms" to TextNode(currentDataUseTerms.type.name), + "dataUseTermsRestrictedUntil" to restrictedDataUseTermsUntil, + ) + }, + ) + + conditionalMetadata( + rawProcessedData.isRevocation, + { + mapOf( + "versionComment" to TextNode(rawProcessedData.versionComment), + ) + }, + ) + + conditionalMetadata( + earliestReleaseDate != null, + { + mapOf( + "earliestReleaseDate" to TextNode(earliestReleaseDate!!.toUtcDateString()), + ) + }, + ) + + conditionalMetadata( + dataUseTermsUrl != null, + { + mapOf( + "dataUseTermsUrl" to TextNode(dataUseTermsUrl!!), + ) + }, + ) return ProcessedData( metadata = metadata, diff --git a/backend/src/test/kotlin/org/loculus/backend/config/BackendSpringConfigTest.kt b/backend/src/test/kotlin/org/loculus/backend/config/BackendSpringConfigTest.kt index 8979cbb94b..f516000913 100644 --- a/backend/src/test/kotlin/org/loculus/backend/config/BackendSpringConfigTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/config/BackendSpringConfigTest.kt @@ -70,5 +70,5 @@ fun backendConfig(metadataList: List, earliestReleaseDate: EarliestRel ), ), accessionPrefix = "FOO_", - dataUseTermsUrls = null, + dataUseTerms = DataUseTerms(true, null), ) diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/EndpointTestExtension.kt b/backend/src/test/kotlin/org/loculus/backend/controller/EndpointTestExtension.kt index 5a5833b741..8275b90b3a 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/EndpointTestExtension.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/EndpointTestExtension.kt @@ -31,6 +31,12 @@ import org.springframework.test.annotation.DirtiesContext import org.springframework.test.context.ActiveProfiles import org.testcontainers.containers.PostgreSQLContainer +/** + * The main annotation for tests. It also loads the [EndpointTestExtension], which initializes + * a PostgreSQL test container. + * You can set additional properties to - for example - override the backend config file, like in + * [org.loculus.backend.controller.submission.GetReleasedDataDataUseTermsDisabledEndpointTest]. + */ @Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) @AutoConfigureMockMvc @@ -48,6 +54,10 @@ import org.testcontainers.containers.PostgreSQLContainer ) annotation class EndpointTest(@get:AliasFor(annotation = SpringBootTest::class) val properties: Array = []) +const val SINGLE_SEGMENTED_REFERENCE_GENOME = "src/test/resources/backend_config_single_segment.json" + +const val DATA_USE_TERMS_DISABLED_CONFIG = "src/test/resources/backend_config_data_use_terms_disabled.json" + private const val SPRING_DATASOURCE_URL = "spring.datasource.url" private const val SPRING_DATASOURCE_USERNAME = "spring.datasource.username" private const val SPRING_DATASOURCE_PASSWORD = "spring.datasource.password" diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetReleasedDataDataUseTermsDisabledEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetReleasedDataDataUseTermsDisabledEndpointTest.kt new file mode 100644 index 0000000000..aacfc67f3c --- /dev/null +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetReleasedDataDataUseTermsDisabledEndpointTest.kt @@ -0,0 +1,141 @@ +package org.loculus.backend.controller.submission + +import com.fasterxml.jackson.databind.node.BooleanNode +import com.fasterxml.jackson.databind.node.IntNode +import com.fasterxml.jackson.databind.node.TextNode +import com.ninjasquad.springmockk.MockkBean +import io.mockk.every +import kotlinx.datetime.Clock +import kotlinx.datetime.toLocalDateTime +import org.hamcrest.CoreMatchers.hasItem +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.CoreMatchers.not +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.matchesPattern +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.keycloak.representations.idm.UserRepresentation +import org.loculus.backend.api.GeneticSequence +import org.loculus.backend.api.ProcessedData +import org.loculus.backend.config.BackendConfig +import org.loculus.backend.config.BackendSpringProperty +import org.loculus.backend.controller.DATA_USE_TERMS_DISABLED_CONFIG +import org.loculus.backend.controller.DEFAULT_GROUP +import org.loculus.backend.controller.DEFAULT_GROUP_CHANGED +import org.loculus.backend.controller.DEFAULT_GROUP_NAME_CHANGED +import org.loculus.backend.controller.DEFAULT_PIPELINE_VERSION +import org.loculus.backend.controller.DEFAULT_USER_NAME +import org.loculus.backend.controller.EndpointTest +import org.loculus.backend.controller.expectNdjsonAndGetContent +import org.loculus.backend.controller.groupmanagement.GroupManagementControllerClient +import org.loculus.backend.controller.groupmanagement.andGetGroupId +import org.loculus.backend.controller.jwtForDefaultUser +import org.loculus.backend.controller.submission.SubmitFiles.DefaultFiles.NUMBER_OF_SEQUENCES +import org.loculus.backend.service.KeycloakAdapter +import org.loculus.backend.utils.DateProvider +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.header +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +@EndpointTest( + properties = ["${BackendSpringProperty.BACKEND_CONFIG_PATH}=$DATA_USE_TERMS_DISABLED_CONFIG"], +) +class GetReleasedDataDataUseTermsDisabledEndpointTest( + @Autowired private val convenienceClient: SubmissionConvenienceClient, + @Autowired private val submissionControllerClient: SubmissionControllerClient, + @Autowired private val groupClient: GroupManagementControllerClient, + @Autowired private val backendConfig: BackendConfig, +) { + private val currentDate = Clock.System.now().toLocalDateTime(DateProvider.timeZone).date.toString() + + @MockkBean + lateinit var keycloakAdapter: KeycloakAdapter + + @BeforeEach + fun setup() { + every { keycloakAdapter.getUsersWithName(any()) } returns listOf(UserRepresentation()) + } + + @Test + fun `config has been read and data use terms are configured to be off`() { + assertThat(backendConfig.dataUseTerms.enabled, `is`(false)) + } + + @Test + fun `GIVEN released data exists THEN NOT returns data use terms properties`() { + val groupId = groupClient.createNewGroup(group = DEFAULT_GROUP, jwt = jwtForDefaultUser) + .andExpect(status().isOk) + .andGetGroupId() + + convenienceClient.prepareDefaultSequenceEntriesToApprovedForRelease(groupId = groupId) + + val response = submissionControllerClient.getReleasedData() + + val responseBody = response.expectNdjsonAndGetContent>() + + responseBody.forEach { + assertThat(it.metadata.keys, not(hasItem("dataUseTerms"))) + assertThat(it.metadata.keys, not(hasItem("dataUseTermsRestrictedUntil"))) + } + } + + @Test + fun `GIVEN released data exists THEN returns with additional metadata fields & no data use terms properties`() { + val groupId = groupClient.createNewGroup(group = DEFAULT_GROUP, jwt = jwtForDefaultUser) + .andExpect(status().isOk) + .andGetGroupId() + + convenienceClient.prepareDefaultSequenceEntriesToApprovedForRelease(groupId = groupId) + + groupClient.updateGroup( + groupId = groupId, + group = DEFAULT_GROUP_CHANGED, + jwt = jwtForDefaultUser, + ).andExpect(status().isOk) + + val response = submissionControllerClient.getReleasedData() + + val responseBody = response.expectNdjsonAndGetContent>() + + assertThat(responseBody.size, `is`(NUMBER_OF_SEQUENCES)) + + response.andExpect(header().string("x-total-records", NUMBER_OF_SEQUENCES.toString())) + + responseBody.forEach { + val id = it.metadata["accession"]!!.asText() + val version = it.metadata["version"]!!.asLong() + assertThat(version, `is`(1)) + + val expectedMetadata = defaultProcessedData.metadata + mapOf( + "accession" to TextNode(id), + "version" to IntNode(version.toInt()), + "accessionVersion" to TextNode("$id.$version"), + "isRevocation" to BooleanNode.FALSE, + "submitter" to TextNode(DEFAULT_USER_NAME), + "groupName" to TextNode(DEFAULT_GROUP_NAME_CHANGED), + "versionStatus" to TextNode("LATEST_VERSION"), + "releasedDate" to TextNode(currentDate), + "submittedDate" to TextNode(currentDate), + "pipelineVersion" to IntNode(DEFAULT_PIPELINE_VERSION.toInt()), + ) + + for ((key, value) in it.metadata) { + when (key) { + "submittedAtTimestamp" -> expectIsTimestampWithCurrentYear(value) + "releasedAtTimestamp" -> expectIsTimestampWithCurrentYear(value) + "submissionId" -> assertThat(value.textValue(), matchesPattern("^custom\\d$")) + "groupId" -> assertThat(value.intValue(), `is`(groupId)) + else -> { + assertThat(expectedMetadata.keys, hasItem(key)) + assertThat(value, `is`(expectedMetadata[key])) + } + } + } + assertThat(it.alignedNucleotideSequences, `is`(defaultProcessedData.alignedNucleotideSequences)) + assertThat(it.unalignedNucleotideSequences, `is`(defaultProcessedData.unalignedNucleotideSequences)) + assertThat(it.alignedAminoAcidSequences, `is`(defaultProcessedData.alignedAminoAcidSequences)) + assertThat(it.nucleotideInsertions, `is`(defaultProcessedData.nucleotideInsertions)) + assertThat(it.aminoAcidInsertions, `is`(defaultProcessedData.aminoAcidInsertions)) + } + } +} diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetReleasedDataEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetReleasedDataEndpointTest.kt index a6b7cabc21..6e2c41f875 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetReleasedDataEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/GetReleasedDataEndpointTest.kt @@ -97,7 +97,6 @@ class GetReleasedDataEndpointTest( @Autowired private val groupClient: GroupManagementControllerClient, @Autowired private val dataUseTermsClient: DataUseTermsControllerClient, ) { - private val currentYear = Clock.System.now().toLocalDateTime(DateProvider.timeZone).year private val currentDate = Clock.System.now().toLocalDateTime(DateProvider.timeZone).date.toString() @MockkBean @@ -420,11 +419,12 @@ class GetReleasedDataEndpointTest( latestVersion5 = 5L, ) } +} - private fun expectIsTimestampWithCurrentYear(value: JsonNode) { - val dateTime = Instant.fromEpochSeconds(value.asLong()).toLocalDateTime(DateProvider.timeZone) - assertThat(dateTime.year, `is`(currentYear)) - } +fun expectIsTimestampWithCurrentYear(value: JsonNode) { + val currentYear = Clock.System.now().toLocalDateTime(DateProvider.timeZone).year + val dateTime = Instant.fromEpochSeconds(value.asLong()).toLocalDateTime(DateProvider.timeZone) + assertThat(dateTime.year, `is`(currentYear)) } private const val OPEN_DATA_USE_TERMS_URL = "openUrl" @@ -555,11 +555,12 @@ class GetReleasedDataEndpointWithDataUseTermsUrlTest( @Value("\${${BackendSpringProperty.BACKEND_CONFIG_PATH}}") configPath: String, ): BackendConfig { val originalConfig = readBackendConfig(objectMapper = objectMapper, configPath = configPath) - return originalConfig.copy( - dataUseTermsUrls = DataUseTermsUrls( - open = OPEN_DATA_USE_TERMS_URL, - restricted = RESTRICTED_DATA_USE_TERMS_URL, + dataUseTerms = originalConfig.dataUseTerms.copy( + urls = DataUseTermsUrls( + open = OPEN_DATA_USE_TERMS_URL, + restricted = RESTRICTED_DATA_USE_TERMS_URL, + ), ), ) } 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 eba76f6dd8..7b6f7daa3d 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 @@ -56,6 +56,22 @@ class SubmissionControllerClient(private val mockMvc: MockMvc, private val objec .withAuth(jwt), ) + fun submitWithoutDataUseTerms( + metadataFile: MockMultipartFile, + sequencesFile: MockMultipartFile? = null, + organism: String = DEFAULT_ORGANISM, + groupId: Int, + jwt: String? = jwtForDefaultUser, + ): ResultActions = mockMvc.perform( + multipart(addOrganismToPath("/submit", organism = organism)) + .apply { + sequencesFile?.let { file(sequencesFile) } + } + .file(metadataFile) + .param("groupId", groupId.toString()) + .withAuth(jwt), + ) + fun extractUnprocessedData( numberOfSequenceEntries: Int, organism: String = DEFAULT_ORGANISM, diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointDataUseTermsDisabledTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointDataUseTermsDisabledTest.kt new file mode 100644 index 0000000000..89ae7972ab --- /dev/null +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointDataUseTermsDisabledTest.kt @@ -0,0 +1,58 @@ +package org.loculus.backend.controller.submission + +import org.hamcrest.CoreMatchers.`is` +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.containsString +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.loculus.backend.config.BackendConfig +import org.loculus.backend.config.BackendSpringProperty +import org.loculus.backend.controller.DATA_USE_TERMS_DISABLED_CONFIG +import org.loculus.backend.controller.DEFAULT_ORGANISM +import org.loculus.backend.controller.EndpointTest +import org.loculus.backend.controller.groupmanagement.GroupManagementControllerClient +import org.loculus.backend.controller.groupmanagement.andGetGroupId +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.MediaType.APPLICATION_JSON_VALUE +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath +import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status + +@EndpointTest( + properties = ["${BackendSpringProperty.BACKEND_CONFIG_PATH}=$DATA_USE_TERMS_DISABLED_CONFIG"], +) +class SubmitEndpointDataUseTermsDisabledTest( + @Autowired val submissionControllerClient: SubmissionControllerClient, + @Autowired val backendConfig: BackendConfig, + @Autowired val groupManagementClient: GroupManagementControllerClient, +) { + var groupId: Int = 0 + + @BeforeEach + fun prepareNewGroup() { + groupId = groupManagementClient.createNewGroup().andGetGroupId() + } + + @Test + fun `config has been read and data use terms are configured to be off`() { + assertThat(backendConfig.dataUseTerms.enabled, `is`(false)) + } + + @Test + fun `GIVEN valid input multi segment data THEN returns mapping of provided custom ids to generated ids`() { + submissionControllerClient.submitWithoutDataUseTerms( + DefaultFiles.metadataFile, + DefaultFiles.sequencesFile, + organism = DEFAULT_ORGANISM, + 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)) + } +} diff --git a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SingleSegmentedSubmitEndpointTest.kt b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointSingleSegmentedTest.kt similarity index 96% rename from backend/src/test/kotlin/org/loculus/backend/controller/submission/SingleSegmentedSubmitEndpointTest.kt rename to backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointSingleSegmentedTest.kt index 80713614f6..3b083abe9e 100644 --- a/backend/src/test/kotlin/org/loculus/backend/controller/submission/SingleSegmentedSubmitEndpointTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/controller/submission/SubmitEndpointSingleSegmentedTest.kt @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test import org.loculus.backend.config.BackendConfig import org.loculus.backend.config.BackendSpringProperty import org.loculus.backend.controller.EndpointTest +import org.loculus.backend.controller.SINGLE_SEGMENTED_REFERENCE_GENOME import org.loculus.backend.controller.groupmanagement.GroupManagementControllerClient import org.loculus.backend.controller.groupmanagement.andGetGroupId import org.springframework.beans.factory.annotation.Autowired @@ -15,14 +16,12 @@ import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status -const val SINGLE_SEGMENTED_REFERENCE_GENOME = "src/test/resources/backend_config_single_segment.json" - private const val DEFAULT_SEQUENCE_NAME = "main" @EndpointTest( properties = ["${BackendSpringProperty.BACKEND_CONFIG_PATH}=$SINGLE_SEGMENTED_REFERENCE_GENOME"], ) -class SingleSegmentedSubmitEndpointTest( +class SubmitEndpointSingleSegmentedTest( @Autowired val submissionControllerClient: SubmissionControllerClient, @Autowired val convenienceClient: SubmissionConvenienceClient, @Autowired val backendConfig: BackendConfig, 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 57cbddab60..f9d8a63196 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 @@ -149,6 +149,17 @@ class SubmitEndpointTest( .andExpect(jsonPath("\$[0].version").value(1)) } + @Test + fun `GIVEN submission without data use terms THEN returns an error`() { + submissionControllerClient.submitWithoutDataUseTerms( + DefaultFiles.metadataFile, + DefaultFiles.sequencesFileMultiSegmented, + organism = OTHER_ORGANISM, + groupId = groupId, + ) + .andExpect(status().isBadRequest) + } + @Test fun `GIVEN fasta data with unknown segment THEN data is accepted to let the preprocessing pipeline verify it`() { submissionControllerClient.submit( diff --git a/backend/src/test/kotlin/org/loculus/backend/service/GenerateAccessionFromNumberServiceTest.kt b/backend/src/test/kotlin/org/loculus/backend/service/GenerateAccessionFromNumberServiceTest.kt index ad3b53fa80..d8d1387e88 100644 --- a/backend/src/test/kotlin/org/loculus/backend/service/GenerateAccessionFromNumberServiceTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/service/GenerateAccessionFromNumberServiceTest.kt @@ -4,6 +4,7 @@ import org.hamcrest.CoreMatchers.`is` import org.hamcrest.MatcherAssert.assertThat import org.junit.jupiter.api.Test import org.loculus.backend.config.BackendConfig +import org.loculus.backend.config.DataUseTerms import java.lang.Math.random import kotlin.math.pow @@ -11,7 +12,11 @@ const val PREFIX = "LOC_" class GenerateAccessionFromNumberServiceTest { private val accessionFromNumberService = GenerateAccessionFromNumberService( - BackendConfig(accessionPrefix = PREFIX, organisms = emptyMap(), dataUseTermsUrls = null), + BackendConfig( + accessionPrefix = PREFIX, + organisms = emptyMap(), + dataUseTerms = DataUseTerms(true, null), + ), ) @Test diff --git a/backend/src/test/kotlin/org/loculus/backend/service/submission/EmptyProcessedDataProviderTest.kt b/backend/src/test/kotlin/org/loculus/backend/service/submission/EmptyProcessedDataProviderTest.kt index 96a0dea239..8b679138d4 100644 --- a/backend/src/test/kotlin/org/loculus/backend/service/submission/EmptyProcessedDataProviderTest.kt +++ b/backend/src/test/kotlin/org/loculus/backend/service/submission/EmptyProcessedDataProviderTest.kt @@ -6,6 +6,7 @@ import org.hamcrest.MatcherAssert.assertThat import org.junit.jupiter.api.Test import org.loculus.backend.api.Organism import org.loculus.backend.config.BackendConfig +import org.loculus.backend.config.DataUseTerms import org.loculus.backend.config.InstanceConfig import org.loculus.backend.config.Metadata import org.loculus.backend.config.MetadataType @@ -46,7 +47,7 @@ class EmptyProcessedDataProviderTest { ), ), ), - dataUseTermsUrls = null, + dataUseTerms = DataUseTerms(true, null), ), ) diff --git a/backend/src/test/resources/backend_config.json b/backend/src/test/resources/backend_config.json index 3729e7a6bf..128c6016fb 100644 --- a/backend/src/test/resources/backend_config.json +++ b/backend/src/test/resources/backend_config.json @@ -225,5 +225,8 @@ ] } } + }, + "dataUseTerms": { + "enabled": true } } diff --git a/backend/src/test/resources/backend_config_data_use_terms_disabled.json b/backend/src/test/resources/backend_config_data_use_terms_disabled.json new file mode 100644 index 0000000000..d73ddd2d78 --- /dev/null +++ b/backend/src/test/resources/backend_config_data_use_terms_disabled.json @@ -0,0 +1,107 @@ +{ + "accessionPrefix": "LOC_", + "organisms": { + "dummyOrganism": { + "referenceGenomes": { + "nucleotideSequences": [ + { + "name": "main", + "sequence": "ATTAAAGGTTTATACCTTCCCAGGTAACAAACCAACCAACTTTCGATCT" + } + ], + "genes": [ + { + "name": "someLongGene", + "sequence": "AAAAAAAAAAAAAAAAAAAAAAAAA" + }, + { + "name": "someShortGene", + "sequence": "MADS" + } + ] + }, + "schema": { + "organismName": "Test", + "allowSubmissionOfConsensusSequences": true, + "metadata": [ + { + "name": "date", + "type": "date", + "required": true + }, + { + "name": "dateSubmitted", + "type": "date" + }, + { + "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 + }, + { + "name": "age", + "type": "int" + }, + { + "name": "sex", + "type": "string", + "autocomplete": true + }, + { + "name": "pangoLineage", + "type": "string", + "autocomplete": true + }, + { + "name": "qc", + "type": "float" + }, + { + "name": "booleanColumn", + "type": "boolean" + }, + { + "name": "insdcAccessionFull", + "type": "string" + }, + { + "name": "other_db_accession", + "type": "string" + } + ], + "externalMetadata": [ + { + "name": "insdcAccessionFull", + "type": "string", + "externalMetadataUpdater": "ena" + }, + { + "name": "other_db_accession", + "type": "string", + "externalMetadataUpdater": "other_db" + } + ] + } + } + }, + "dataUseTerms": { + "enabled": false + } +} diff --git a/backend/src/test/resources/backend_config_single_segment.json b/backend/src/test/resources/backend_config_single_segment.json index e61b1c5c72..00cb719276 100644 --- a/backend/src/test/resources/backend_config_single_segment.json +++ b/backend/src/test/resources/backend_config_single_segment.json @@ -1,5 +1,8 @@ { "accessionPrefix": "LOC_", + "dataUseTerms": { + "enabled": true + }, "organisms": { "dummyOrganism": { "referenceGenomes": { diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 6b631f90b7..86d85cf33a 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -73,6 +73,7 @@ export default defineConfig({ label: 'Build new preprocessing pipeline', link: '/for-administrators/build-new-preprocessing-pipeline/', }, + { label: 'Data use terms', link: '/for-administrators/data-use-terms/' }, { label: 'User administration', link: '/for-administrators/user-administration/' }, ], }, diff --git a/docs/src/content/docs/for-administrators/data-use-terms.md b/docs/src/content/docs/for-administrators/data-use-terms.md new file mode 100644 index 0000000000..655c310355 --- /dev/null +++ b/docs/src/content/docs/for-administrators/data-use-terms.md @@ -0,0 +1,33 @@ +--- +title: Data use terms +description: What's the data use terms concept of Loculus and how to configure it. +--- + +Loculus comes with built-in handling of data use terms for submitted data, which means that data can either be _open_ or _restricted_. You can define, what restricted means yourself. Users can submit data as restricted, but they have to give a date at which point the sequences become open, this date can at most be one year from the submission date. + +When data use terms are enabled, you can also filter for only open or only restricted data, and when downloading you will have to accept the data use terms. The same applies to API usage. +You can then enable data use terms like this: + +```yaml +dataUseTerms: + enabled: true +``` + +Optionally, you can provide links to pages where the data use terms are descripted: + +```yaml +dataUseTerms: + enabled: true + urls: + open: https://example.org/open + restricted: https://example.org/restricted +``` + +## Disabling data use terms + +To disable data use terms, set `enabled` to `false`: + +```yaml +dataUseTerms: + enabled: true +``` diff --git a/docs/src/content/docs/for-users/submit-sequences.md b/docs/src/content/docs/for-users/submit-sequences.md index 2e68e128a6..284ec65a1e 100644 --- a/docs/src/content/docs/for-users/submit-sequences.md +++ b/docs/src/content/docs/for-users/submit-sequences.md @@ -24,7 +24,7 @@ Uploading sequences via the website is a easy way to submit sequences without ha 1. Log into your account, and then click 'Submit' in the top-right corner of the website 2. Select the organism that you'd like to submit sequences for 3. Drag-and-drop a `fasta` file with the sequences and a metadata file with the associated metadata into the box on the website, or click the 'Upload a file' link within the boxes to open a file-selection box -4. Select the Terms of Use that you would like for your data +4. If Terms of Use are enabled for your Loculus instance: Select the Terms of Use that you would like for your data 5. Select 'Submit sequences' at the bottom of the page The data will now be processed, and you will have to approve your submission before it is finalized. You can see how to do this [here](../approve-submissions/). @@ -39,13 +39,15 @@ To upload sequences through the HTTP API you will need to: 2. Retrieve an authentication JSON web token: see the [Authenticating via API guide](../authenticate-via-api/). 3. Identify the Group ID of your group: you can find it on the page of your group. 4. Send a POST request: - - To upload sequences with the **open use terms**: `//submit?groupId=&dataUseTermsType=OPEN` + - The API path to use is: `//submit` + - Add your group ID to the query parameters: `?groupId=` + - If Data use Terms are configured for your Loculus instance, add `&dataUseTermsType=OPEN` for _open_ data use terms or `&dataUseTermsType=RESTRICTED&restrictedUntil=YYYY-MM-DD` (where `YYYY-MM-DD` refers to a date like 2025-01-31) for _restricted_ data use terms - with a date until when this restriction will be in place. If your Loculus instance doesn't use Data use Terms, you can leave out these settings. - The header should contain - `Authorization: Bearer ` - `Content-Type: multipart/form-data` - The request body should contain the FASTA and metadata TSV files with the keys `sequenceFile` and `metadataFile` -With cURL, the corresponding command for sending the POST request can be: +Below you can see an example of submitting to the API with cURL (with open data use terms): ``` curl -X 'POST' \ diff --git a/docs/src/content/docs/reference/helm-chart-config.mdx b/docs/src/content/docs/reference/helm-chart-config.mdx index 2bfbf416b8..13385ebe7e 100644 --- a/docs/src/content/docs/reference/helm-chart-config.mdx +++ b/docs/src/content/docs/reference/helm-chart-config.mdx @@ -160,6 +160,24 @@ The configuration for the Helm chart is provided as a YAML file. It has the foll "https://github.com/loculus-project/loculus" The link that the GitHub icon in the footer points to + + `dataUseTerms.enabled` + Boolean + true + Whether this Loculus instance handles data use terms. + + + `dataUseTerms.urls.open` + String + + A URL describing the open data use terms. + + + `dataUseTerms.urls.restricted` + String + + A URL describing the restricted data use terms. + diff --git a/kubernetes/loculus/templates/_common-metadata.tpl b/kubernetes/loculus/templates/_common-metadata.tpl index 44ae6d38f0..289b052e3c 100644 --- a/kubernetes/loculus/templates/_common-metadata.tpl +++ b/kubernetes/loculus/templates/_common-metadata.tpl @@ -75,6 +75,7 @@ fields: autocomplete: true displayName: Date released (exact) columnWidth: 100 + {{- if $.Values.dataUseTerms.enabled }} - name: dataUseTerms type: string generateIndex: true @@ -89,7 +90,7 @@ fields: displayName: Data use terms restricted until hideOnSequenceDetailsPage: true header: Data use terms - {{- if $.Values.dataUseTermsUrls }} + {{- if $.Values.dataUseTerms.urls }} - name: dataUseTermsUrl displayName: Data use terms URL type: string @@ -99,6 +100,7 @@ fields: type: link url: "__value__" {{- end}} + {{- end}} - name: versionStatus type: string notSearchable: true @@ -160,6 +162,7 @@ enableLoginNavigationItem: {{ $.Values.website.websiteConfig.enableLoginNavigati enableSubmissionNavigationItem: {{ $.Values.website.websiteConfig.enableSubmissionNavigationItem }} enableSubmissionPages: {{ $.Values.website.websiteConfig.enableSubmissionPages }} enableSeqSets: {{ $.Values.seqSets.enabled }} +enableDataUseTerms: {{ $.Values.dataUseTerms.enabled }} accessionPrefix: {{ quote $.Values.accessionPrefix }} {{- $commonMetadata := (include "loculus.commonMetadata" . | fromYaml).fields }} organisms: @@ -289,8 +292,8 @@ fields: {{- define "loculus.generateBackendConfig" }} accessionPrefix: {{ quote $.Values.accessionPrefix }} name: {{ quote $.Values.name }} -dataUseTermsUrls: - {{$.Values.dataUseTermsUrls | toYaml | nindent 2}} +dataUseTerms: + {{$.Values.dataUseTerms | toYaml | nindent 2}} organisms: {{- range $key, $instance := (.Values.organisms | default .Values.defaultOrganisms) }} {{ $key }}: diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index 026ce4092b..07d956f41a 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -20,9 +20,11 @@ ingestLimitSeconds: 1800 getSubmissionListLimitSeconds: 600 preprocessingTimeout: 600 accessionPrefix: "LOC_" -dataUseTermsUrls: - open: https://#TODO-MVP/open - restricted: https://#TODO-MVP/restricted +dataUseTerms: + enabled: true + urls: + open: https://#TODO-MVP/open + restricted: https://#TODO-MVP/restricted name: "Loculus" logo: url: "/favicon.svg" diff --git a/website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx b/website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx index 923517349a..ee48794204 100644 --- a/website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx +++ b/website/src/components/SearchPage/DownloadDialog/DownloadDialog.spec.tsx @@ -24,16 +24,19 @@ const defaultOrganism = 'ebola'; async function renderDialog({ downloadParams = new SelectFilter(new Set()), allowSubmissionOfConsensusSequences = true, + dataUseTermsEnabled = true, }: { downloadParams?: SequenceFilter; allowSubmissionOfConsensusSequences?: boolean; + dataUseTermsEnabled?: boolean; } = {}) { render( , ); @@ -158,6 +161,27 @@ describe('DownloadDialog', () => { expect(query).toMatch(/field2=/); expect(query).not.toMatch(/field1=/); }); + + describe('DataUseTerms disabled', () => { + test('download button activated by default', async () => { + await renderDialog({ dataUseTermsEnabled: false }); + + const downloadButton = screen.getByRole('link', { name: 'Download' }); + expect(downloadButton).not.toHaveClass('btn-disabled'); + }); + + test('checkbox not in the document', async () => { + await renderDialog({ dataUseTermsEnabled: false }); + + expect(screen.queryByLabelText('I agree to the data use terms.')).not.toBeInTheDocument(); + }); + + test('restricted data switch is not in the document', async () => { + await renderDialog({ dataUseTermsEnabled: false }); + + expect(screen.queryByLabelText(/restricted data/)).not.toBeInTheDocument(); + }); + }); }); async function checkAgreement() { diff --git a/website/src/components/SearchPage/DownloadDialog/DownloadDialog.tsx b/website/src/components/SearchPage/DownloadDialog/DownloadDialog.tsx index 9987795cc5..bcc356ff28 100644 --- a/website/src/components/SearchPage/DownloadDialog/DownloadDialog.tsx +++ b/website/src/components/SearchPage/DownloadDialog/DownloadDialog.tsx @@ -15,6 +15,7 @@ type DownloadDialogProps = { sequenceFilter: SequenceFilter; referenceGenomesSequenceNames: ReferenceGenomesSequenceNames; allowSubmissionOfConsensusSequences: boolean; + dataUseTermsEnabled: boolean; }; export const DownloadDialog: FC = ({ @@ -22,6 +23,7 @@ export const DownloadDialog: FC = ({ sequenceFilter, referenceGenomesSequenceNames, allowSubmissionOfConsensusSequences, + dataUseTermsEnabled, }) => { const [isOpen, setIsOpen] = useState(false); @@ -29,42 +31,45 @@ export const DownloadDialog: FC = ({ const closeDialog = () => setIsOpen(false); const [downloadOption, setDownloadOption] = useState(); - const [agreedToDataUseTerms, setAgreedToDataUseTerms] = useState(false); + const [agreedToDataUseTerms, setAgreedToDataUseTerms] = useState(dataUseTermsEnabled ? false : true); return ( <> - +
-
- -
+ {dataUseTermsEnabled && ( +
+ +
+ )} void; allowSubmissionOfConsensusSequences: boolean; + dataUseTermsEnabled: boolean; }; export const DownloadForm: FC = ({ referenceGenomesSequenceNames, onChange, allowSubmissionOfConsensusSequences, + dataUseTermsEnabled, }) => { const [includeRestricted, setIncludeRestricted] = useState(0); const [includeOldData, setIncludeOldData] = useState(0); @@ -136,27 +138,29 @@ export const DownloadForm: FC = ({ return (
- No, only download open data }, - { - label: ( - <> - Yes, include restricted data -
({/* TODO(862) */} - - What does it mean? - - ) - - ), - }, - ]} - selected={includeRestricted} - onSelect={setIncludeRestricted} - /> + {dataUseTermsEnabled && ( + No, only download open data }, + { + label: ( + <> + Yes, include restricted data +
({/* TODO(862) */} + + What does it mean? + + ) + + ), + }, + ]} + selected={includeRestricted} + onSelect={setIncludeRestricted} + /> + )} { if (!hiddenFieldValues) { hiddenFieldValues = {}; @@ -188,13 +190,13 @@ export const InnerSearchFullUI = ({ }; useEffect(() => { - if (showEditDataUseTermsControls) { + if (showEditDataUseTermsControls && dataUseTermsEnabled) { setAColumnVisibility(DATA_USE_TERMS_FIELD, true); } }, []); const lapisUrl = getLapisUrl(clientConfig, organism); - const downloadUrlGenerator = new DownloadUrlGenerator(organism, lapisUrl); + const downloadUrlGenerator = new DownloadUrlGenerator(organism, lapisUrl, dataUseTermsEnabled); const hooks = lapisClientHooks(lapisUrl).zodiosHooks; const aggregatedHook = hooks.useAggregated({}, {}); @@ -353,7 +355,7 @@ export const InnerSearchFullUI = ({
- {showEditDataUseTermsControls && ( + {showEditDataUseTermsControls && dataUseTermsEnabled && (
diff --git a/website/src/components/Submission/DataUploadForm.tsx b/website/src/components/Submission/DataUploadForm.tsx index 46528ed0be..881882b970 100644 --- a/website/src/components/Submission/DataUploadForm.tsx +++ b/website/src/components/Submission/DataUploadForm.tsx @@ -43,6 +43,7 @@ type DataUploadFormProps = { onSuccess: () => void; onError: (message: string) => void; submissionDataTypes: SubmissionDataTypes; + dataUseTermsEnabled: boolean; }; const logger = getClientLogger('DataUploadForm'); @@ -129,6 +130,7 @@ const InnerDataUploadForm = ({ referenceGenomeSequenceNames, metadataTemplateFields, submissionDataTypes, + dataUseTermsEnabled, }: DataUploadFormProps) => { const [metadataFile, setMetadataFile] = useState(undefined); // The columnMapping can be null; if null -> don't apply mapping. @@ -163,12 +165,12 @@ const InnerDataUploadForm = ({ const handleSubmit = async (event: FormEvent) => { event.preventDefault(); - if (!agreedToINSDCUploadTerms) { + if (dataUseTermsEnabled && !agreedToINSDCUploadTerms) { onError('Please tick the box to agree that you will not independently submit these sequences to INSDC'); return; } - if (!confirmedNoPII) { + if (dataUseTermsEnabled && !confirmedNoPII) { onError( 'Please confirm the data you submitted does not include restricted or personally identifiable information.', ); @@ -328,86 +330,87 @@ const InnerDataUploadForm = ({ - {action !== 'revise' && ( + {action === 'submit' && dataUseTermsEnabled && ( )} -
-
-

Acknowledgement

-

Acknowledge submission terms

-
-
-
- {dataUseTermsType === restrictedDataUseTermsOption && ( -

- Your data will be available on Pathoplexus, under the restricted use terms until{' '} - {restrictedUntil.toFormat('yyyy-MM-dd')}. After the restricted period your data will - additionally be made publicly available through the{' '} - - INSDC - {' '} - databases (ENA, DDBJ, NCBI). -

- )} - {dataUseTermsType === openDataUseTermsOption && ( -

- Your data will be available on Pathoplexus under the open use terms. It will - additionally be made publicly available through the{' '} - - INSDC - {' '} - databases (ENA, DDBJ, NCBI). -

- )} -
- -
-
- + {dataUseTermsEnabled && ( +
+
+

Acknowledgement

+

Acknowledge submission terms

+
+
+
+ {dataUseTermsType === restrictedDataUseTermsOption && ( +

+ Your data will be available on Pathoplexus, under the restricted use terms until{' '} + {restrictedUntil.toFormat('yyyy-MM-dd')}. After the restricted period your data + will additionally be made publicly available through the{' '} + + INSDC + {' '} + databases (ENA, DDBJ, NCBI). +

+ )} + {dataUseTermsType === openDataUseTermsOption && ( +

+ Your data will be available on Pathoplexus under the open use terms. It will + additionally be made publicly available through the{' '} + + INSDC + {' '} + databases (ENA, DDBJ, NCBI). +

+ )} +
+ +
+
+ +
-
- -
+ )} +
diff --git a/website/src/types/config.ts b/website/src/types/config.ts index dda387617a..f2a2d4a8c1 100644 --- a/website/src/types/config.ts +++ b/website/src/types/config.ts @@ -144,6 +144,7 @@ export const websiteConfig = z.object({ enableLoginNavigationItem: z.boolean(), enableSubmissionNavigationItem: z.boolean(), enableSubmissionPages: z.boolean(), + enableDataUseTerms: z.boolean(), }); export type WebsiteConfig = z.infer;