From 43ca5e8a610c6f5979827af071ef86ddd17eb780 Mon Sep 17 00:00:00 2001 From: Felix Hennig Date: Wed, 11 Dec 2024 14:25:19 +0100 Subject: [PATCH] feat: earliest release date (#3380) * Add config setting * WIP * Implement something * Add caching * Support the field in values.yaml * default to false * Be explicit about using ISO format * test enabling it for west-nile * Add documentation * Add metadata field * generate default * Add it for Ebola Sudan * Config fix? * Parse only date * Now fix? * Update docs * Catch IllegalArgumentException and log and ignore * Validate settings file * Write test to check for sorted * Move test * Factor out finder into a utility * Simplify; document * Add test stub * Add 'createDummyRow' function for testing * Add EarliestDateFinder Test * Add comment to new test for ordering * Add assertion * format * explain the field * explain the field * Add initial empty test * Add positive test * Add more tests * make code more compact * Update backend/src/test/kotlin/org/loculus/backend/controller/submission/GetReleasedDataEndpointTest.kt Co-authored-by: Chaoran Chen * Make test naming more specific * Improve error emssage and raise log level --------- Co-authored-by: Chaoran Chen --- .../backend/config/BackendSpringConfig.kt | 38 ++++++++- .../org/loculus/backend/config/Config.kt | 3 + .../backend/model/ReleasedDataModel.kt | 25 +++++- .../utils/EarliestReleaseDateFinder.kt | 74 ++++++++++++++++++ .../backend/config/BackendSpringConfigTest.kt | 74 ++++++++++++++++++ .../submission/GetReleasedDataEndpointTest.kt | 53 +++++++++++++ .../utils/EarliestReleaseDateFinderTest.kt | 77 +++++++++++++++++++ .../docs/reference/helm-chart-config.mdx | 29 +++++++ .../loculus/templates/_common-metadata.tpl | 7 ++ kubernetes/loculus/values.yaml | 10 +++ 10 files changed, 388 insertions(+), 2 deletions(-) create mode 100644 backend/src/main/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinder.kt create mode 100644 backend/src/test/kotlin/org/loculus/backend/config/BackendSpringConfigTest.kt create mode 100644 backend/src/test/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinderTest.kt 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 7f11e6f16c..119665129b 100644 --- a/backend/src/main/kotlin/org/loculus/backend/config/BackendSpringConfig.kt +++ b/backend/src/main/kotlin/org/loculus/backend/config/BackendSpringConfig.kt @@ -116,9 +116,45 @@ class BackendSpringConfig { } } +/** + * Check whether configured metadatafields for earliestReleaseDate are actually fields and are of type date. + * Returns a non-empty list of errors if validation errors were found. + */ +internal fun validateEarliestReleaseDateFields(config: BackendConfig): List { + val errors = mutableListOf() + config.organisms.values.forEach { + val organism = it.schema.organismName + val allFields = it.schema.metadata.map { it.name }.toSet() + val dateFields = it.schema.metadata.filter { it.type == MetadataType.DATE }.map { it.name }.toSet() + it.schema.earliestReleaseDate.externalFields.forEach { + if (!allFields.contains(it)) { + errors.add( + "Error on organism $organism in earliestReleaseDate.externalFields: " + + "Field $it does not exist.", + ) + } else { + if (!dateFields.contains(it)) { + errors.add( + "Error on organism $organism in earliestReleaseDate.externalFields: " + + "Field $it is not of type ${MetadataType.DATE}.", + ) + } + } + } + } + return errors +} + fun readBackendConfig(objectMapper: ObjectMapper, configPath: String): BackendConfig { val config = objectMapper.readValue(File(configPath)) logger.info { "Loaded backend config from $configPath" } logger.info { "Config: $config" } - return objectMapper.readValue(File(configPath)) + val validationErrors = validateEarliestReleaseDateFields(config) + if (validationErrors.isNotEmpty()) { + throw IllegalArgumentException( + "The configuration file at $configPath is invalid: " + + validationErrors.joinToString(" "), + ) + } + return 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 61ba114025..7205e1924e 100644 --- a/backend/src/main/kotlin/org/loculus/backend/config/Config.kt +++ b/backend/src/main/kotlin/org/loculus/backend/config/Config.kt @@ -22,6 +22,7 @@ data class Schema( val organismName: String, val metadata: List, val externalMetadata: List = emptyList(), + val earliestReleaseDate: EarliestReleaseDate = EarliestReleaseDate(false, emptyList()), ) // The Json property names need to be kept in sync with website config enum `metadataPossibleTypes` in `config.ts` @@ -76,3 +77,5 @@ data class ExternalMetadata( override val type: MetadataType, override val required: Boolean = false, ) : BaseMetadata() + +data class EarliestReleaseDate(val enabled: Boolean = false, val externalFields: List) 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 af84d15c22..591a63f386 100644 --- a/backend/src/main/kotlin/org/loculus/backend/model/ReleasedDataModel.kt +++ b/backend/src/main/kotlin/org/loculus/backend/model/ReleasedDataModel.kt @@ -25,6 +25,7 @@ import org.loculus.backend.service.submission.SubmissionDatabaseService import org.loculus.backend.service.submission.UpdateTrackerTable import org.loculus.backend.utils.Accession import org.loculus.backend.utils.DateProvider +import org.loculus.backend.utils.EarliestReleaseDateFinder import org.loculus.backend.utils.Version import org.loculus.backend.utils.toTimestamp import org.loculus.backend.utils.toUtcDateString @@ -58,8 +59,22 @@ open class ReleasedDataModel( val latestVersions = submissionDatabaseService.getLatestVersions(organism) val latestRevocationVersions = submissionDatabaseService.getLatestRevocationVersions(organism) + val earliestReleaseDateConfig = backendConfig.getInstanceConfig(organism).schema.earliestReleaseDate + val finder = if (earliestReleaseDateConfig.enabled) { + EarliestReleaseDateFinder(earliestReleaseDateConfig.externalFields) + } else { + null + } + return submissionDatabaseService.streamReleasedSubmissions(organism) - .map { computeAdditionalMetadataFields(it, latestVersions, latestRevocationVersions) } + .map { + computeAdditionalMetadataFields( + it, + latestVersions, + latestRevocationVersions, + finder, + ) + } } @Transactional(readOnly = true) @@ -83,6 +98,7 @@ open class ReleasedDataModel( rawProcessedData: RawProcessedData, latestVersions: Map, latestRevocationVersions: Map, + earliestReleaseDateFinder: EarliestReleaseDateFinder?, ): ProcessedData { val versionStatus = computeVersionStatus(rawProcessedData, latestVersions, latestRevocationVersions) @@ -93,6 +109,8 @@ open class ReleasedDataModel( NullNode.getInstance() } + val earliestReleaseDate = earliestReleaseDateFinder?.calculateEarliestReleaseDate(rawProcessedData) + var metadata = rawProcessedData.processedData.metadata + mapOf( ("accession" to TextNode(rawProcessedData.accession)), @@ -126,6 +144,11 @@ open class ReleasedDataModel( it + ("dataUseTermsUrl" to TextNode(url)) } } + } + + if (earliestReleaseDate != null) { + mapOf("earliestReleaseDate" to TextNode(earliestReleaseDate.toUtcDateString())) + } else { + emptyMap() } return ProcessedData( diff --git a/backend/src/main/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinder.kt b/backend/src/main/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinder.kt new file mode 100644 index 0000000000..d34e7419c3 --- /dev/null +++ b/backend/src/main/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinder.kt @@ -0,0 +1,74 @@ +package org.loculus.backend.utils + +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.LocalTime +import mu.KotlinLogging +import org.loculus.backend.service.submission.RawProcessedData + +private val log = KotlinLogging.logger { } + +/** + * Calculate the earliest release date for rows of sequence entries given to it one by one. + * Assumes that rows are sorted: all accession entries are given in a block, and with ascending versions. + * + * The earliest release date of a sequence is the earliest date of: + * - the internal release date + * - any date from a given list of fields + * - the earliest release date from the previous version (if it exists) + */ +class EarliestReleaseDateFinder(private val fields: List) { + private val earliestReleaseDateCache = mutableMapOf() + private var previousRawProcessedData: RawProcessedData? = null + + fun calculateEarliestReleaseDate(rawProcessedData: RawProcessedData): LocalDateTime { + assert( + previousRawProcessedData == null || + rawProcessedData.accession > previousRawProcessedData!!.accession || + ( + rawProcessedData.accession == previousRawProcessedData!!.accession && + rawProcessedData.version > previousRawProcessedData!!.version + ), + ) { + "Input is not ordered. Current: ${rawProcessedData.accession}.${rawProcessedData.version}, " + + "Previous: ${previousRawProcessedData!!.accession}.${previousRawProcessedData!!.version}" + } + + var earliestReleaseDate = rawProcessedData.releasedAtTimestamp + + fields.forEach { field -> + rawProcessedData.processedData.metadata[field]?.textValue()?.let { dateText -> + val date = try { + LocalDateTime(LocalDate.parse(dateText), LocalTime.fromSecondOfDay(0)) + } catch (e: IllegalArgumentException) { + log.error { + "Unexpected error: Incorrectly formatted date on ${rawProcessedData.accession}." + + "${rawProcessedData.version} on field $field: $dateText " + + "Something is wrong with this instance: it might be a configuration error or a bug of " + + "the software. Please feel free to reach out to the developers if you need advice " + + "(https://github.com/loculus-project/loculus/issues/)." + } + null + } + if (date != null) { + earliestReleaseDate = if (date < earliestReleaseDate) date else earliestReleaseDate + } + } + } + + earliestReleaseDateCache[rawProcessedData.accession]?.let { cached -> + if (cached < earliestReleaseDate) { + earliestReleaseDate = cached + } else { + earliestReleaseDateCache[rawProcessedData.accession] = earliestReleaseDate + } + } ?: run { + earliestReleaseDateCache.clear() // Inputs are ordered; no need for previous values + earliestReleaseDateCache[rawProcessedData.accession] = earliestReleaseDate + } + + previousRawProcessedData = rawProcessedData + + return earliestReleaseDate + } +} diff --git a/backend/src/test/kotlin/org/loculus/backend/config/BackendSpringConfigTest.kt b/backend/src/test/kotlin/org/loculus/backend/config/BackendSpringConfigTest.kt new file mode 100644 index 0000000000..8979cbb94b --- /dev/null +++ b/backend/src/test/kotlin/org/loculus/backend/config/BackendSpringConfigTest.kt @@ -0,0 +1,74 @@ +package org.loculus.backend.config + +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.`is` +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.Test +import org.loculus.backend.controller.DEFAULT_ORGANISM + +class BackendSpringConfigTest { + + @Test + fun `GIVEN an empty config THEN the it is valid`() { + val conf = backendConfig(emptyList(), EarliestReleaseDate(false, emptyList())) + + val errors = validateEarliestReleaseDateFields(conf) + + assertTrue(errors.isEmpty()) + } + + @Test + fun `GIVEN a config with earliestReleaseDate configured with existing date fields THEN it is valid`() { + val conf = backendConfig( + listOf( + Metadata("foo", MetadataType.DATE), + Metadata("bar", MetadataType.DATE), + ), + EarliestReleaseDate(true, listOf("foo", "bar")), + ) + + val errors = validateEarliestReleaseDateFields(conf) + + assertTrue(errors.isEmpty()) + } + + @Test + fun `GIVEN a config with a missing external field in earliestReleaseDate THEN it is invalid`() { + val conf = backendConfig( + listOf( + Metadata("foo", MetadataType.DATE), + ), + EarliestReleaseDate(true, listOf("foo", "bar")), + ) + + val errors = validateEarliestReleaseDateFields(conf) + + assertThat(errors.size, `is`(1)) + } + + @Test + fun `GIVEN a config with an external field with incorrect type in earliestReleaseDate THEN it is invalid`() { + val conf = backendConfig( + listOf( + Metadata("foo", MetadataType.DATE), + Metadata("bar", MetadataType.STRING), + ), + EarliestReleaseDate(true, listOf("foo", "bar")), + ) + + val errors = validateEarliestReleaseDateFields(conf) + + assertThat(errors.size, `is`(1)) + } +} + +fun backendConfig(metadataList: List, earliestReleaseDate: EarliestReleaseDate) = BackendConfig( + organisms = mapOf( + DEFAULT_ORGANISM to InstanceConfig( + Schema(DEFAULT_ORGANISM, metadataList, earliestReleaseDate = earliestReleaseDate), + ReferenceGenome(emptyList(), emptyList()), + ), + ), + accessionPrefix = "FOO_", + dataUseTermsUrls = null, +) 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 981bda7281..4009545386 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 @@ -16,16 +16,21 @@ import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime +import kotlinx.datetime.TimeZone import kotlinx.datetime.plus import kotlinx.datetime.toInstant import kotlinx.datetime.toLocalDateTime import org.hamcrest.CoreMatchers.`is` import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers +import org.hamcrest.Matchers.equalTo import org.hamcrest.Matchers.greaterThan import org.hamcrest.Matchers.hasSize import org.hamcrest.Matchers.matchesPattern import org.hamcrest.Matchers.not import org.hamcrest.Matchers.notNullValue +import org.jetbrains.exposed.sql.batchInsert +import org.jetbrains.exposed.sql.transactions.transaction import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.Test import org.keycloak.representations.idm.UserRepresentation @@ -44,6 +49,7 @@ import org.loculus.backend.controller.DEFAULT_GROUP import org.loculus.backend.controller.DEFAULT_GROUP_CHANGED import org.loculus.backend.controller.DEFAULT_GROUP_NAME import org.loculus.backend.controller.DEFAULT_GROUP_NAME_CHANGED +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.datauseterms.DataUseTermsControllerClient @@ -56,6 +62,7 @@ import org.loculus.backend.controller.jwtForDefaultUser import org.loculus.backend.controller.submission.GetReleasedDataEndpointWithDataUseTermsUrlTest.ConfigWithModifiedDataUseTermsUrlSpringConfig import org.loculus.backend.controller.submission.SubmitFiles.DefaultFiles.NUMBER_OF_SEQUENCES import org.loculus.backend.service.KeycloakAdapter +import org.loculus.backend.service.submission.SequenceEntriesTable import org.loculus.backend.utils.Accession import org.loculus.backend.utils.DateProvider import org.loculus.backend.utils.Version @@ -87,6 +94,7 @@ class GetReleasedDataEndpointTest( @Autowired private val convenienceClient: SubmissionConvenienceClient, @Autowired private val submissionControllerClient: SubmissionControllerClient, @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() @@ -343,6 +351,51 @@ class GetReleasedDataEndpointTest( assertThat(data[0].metadata, `is`(not(emptyMap()))) } + /** + * This test ist relevant for EarliestReleaseDateFinder which relies on this particular ordering to be returned. + */ + @Test + fun `GIVEN multiple accessions with multiple versions THEN results are ordered by accession and version`() { + val now = Clock.System.now().toLocalDateTime(TimeZone.UTC) + val accessions = listOf("SEQ1", "SEQ2", "SEQ3", "SEQ4") + val versions = listOf(1L, 2L, 3L) + val accessionVersions = accessions.flatMap { versions.map(it::to) } + + transaction { + val submittingGroupId = groupClient.createNewGroup() + .andExpect(status().isOk) + .andGetGroupId() + + SequenceEntriesTable.batchInsert(accessionVersions.shuffled()) { (accession, version) -> + this[SequenceEntriesTable.accessionColumn] = accession + this[SequenceEntriesTable.versionColumn] = version + this[SequenceEntriesTable.groupIdColumn] = submittingGroupId + this[SequenceEntriesTable.submittedAtTimestampColumn] = now + this[SequenceEntriesTable.releasedAtTimestampColumn] = now + this[SequenceEntriesTable.organismColumn] = DEFAULT_ORGANISM + this[SequenceEntriesTable.submissionIdColumn] = "foo" + this[SequenceEntriesTable.submitterColumn] = "bar" + this[SequenceEntriesTable.approverColumn] = "baz" + } + + dataUseTermsClient.changeDataUseTerms(DataUseTermsChangeRequest(accessions, DataUseTerms.Open)) + } + + val data = convenienceClient.getReleasedData(DEFAULT_ORGANISM) + + // assert that the accessions are sorted + assertThat(data.size, Matchers.`is`(12)) + val actualAccessionOrder = data.map { it.metadata["accession"]!!.asText() } + assertThat(actualAccessionOrder, equalTo(actualAccessionOrder.sorted())) + + // assert that _within_ each accession block, it's sorted by version + val accessionChunks = data.groupBy { it.metadata["accession"]!!.asText() } + assertThat(accessionChunks.size, Matchers.`is`(accessions.size)) + accessionChunks.values + .map { chunk -> chunk.map { it.metadata["version"]!!.asLong() } } + .forEach { assertThat(it, equalTo(it.sorted())) } + } + private fun prepareRevokedAndRevocationAndRevisedVersions(): PreparedVersions { val preparedSubmissions = convenienceClient.prepareDataTo(Status.APPROVED_FOR_RELEASE) convenienceClient.reviseAndProcessDefaultSequenceEntries(preparedSubmissions.map { it.accession }) diff --git a/backend/src/test/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinderTest.kt b/backend/src/test/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinderTest.kt new file mode 100644 index 0000000000..79d020f2b1 --- /dev/null +++ b/backend/src/test/kotlin/org/loculus/backend/utils/EarliestReleaseDateFinderTest.kt @@ -0,0 +1,77 @@ +package org.loculus.backend.utils + +import com.fasterxml.jackson.databind.node.NullNode +import com.fasterxml.jackson.databind.node.TextNode +import kotlinx.datetime.LocalDate +import kotlinx.datetime.LocalDateTime +import kotlinx.datetime.format +import org.hamcrest.MatcherAssert.assertThat +import org.hamcrest.Matchers.equalTo +import org.junit.jupiter.api.Test +import org.loculus.backend.api.DataUseTerms +import org.loculus.backend.api.ProcessedData +import org.loculus.backend.service.submission.RawProcessedData + +class EarliestReleaseDateFinderTest { + + @Test + fun `GIVEN multiple versions THEN the release date of the first version is used for all versions`() { + val finder = EarliestReleaseDateFinder(emptyList()) + + assert(finder, row("A", 1L, monthDay(1, 1)), monthDay(1, 1)) + assert(finder, row("A", 2L, monthDay(2, 1)), monthDay(1, 1)) + + assert(finder, row("B", 1L, monthDay(2, 1)), monthDay(2, 1)) + assert(finder, row("B", 2L, monthDay(3, 1)), monthDay(2, 1)) + } + + @Test + fun `GIVEN an external field is earlier THEN the external field is used`() { + val finder = EarliestReleaseDateFinder(listOf("foo")) + + assert(finder, row("A", 1L, monthDay(10, 1), mapOf("foo" to monthDay(1, 1))), monthDay(1, 1)) + assert(finder, row("A", 2L, monthDay(10, 2), mapOf("foo" to monthDay(1, 2))), monthDay(1, 1)) + + assert(finder, row("B", 1L, monthDay(10, 1), mapOf("foo" to null)), monthDay(10, 1)) + assert(finder, row("B", 2L, monthDay(10, 2), mapOf("foo" to monthDay(1, 2))), monthDay(1, 2)) + } +} + +fun assert(finder: EarliestReleaseDateFinder, row: RawProcessedData, expected: LocalDateTime) { + assertThat(finder.calculateEarliestReleaseDate(row), equalTo(expected)) +} + +fun monthDay(month: Int, day: Int) = LocalDateTime(2024, month, day, 0, 0, 0) + +fun row( + accession: String, + version: Long, + releasedAt: LocalDateTime, + fieldValues: Map = emptyMap(), +) = RawProcessedData( + accession = accession, + version = version, + releasedAtTimestamp = releasedAt, + processedData = ProcessedData( + metadata = fieldValues.map { (field, date) -> + field to + if (date != null) TextNode(date.date.format(LocalDate.Formats.ISO)) else NullNode.getInstance() + }.toMap(), + unalignedNucleotideSequences = emptyMap(), + alignedNucleotideSequences = emptyMap(), + nucleotideInsertions = emptyMap(), + alignedAminoAcidSequences = emptyMap(), + aminoAcidInsertions = emptyMap(), + ), + isRevocation = false, + versionComment = null, + submitter = "foo", + submissionId = "foo", + submittedAtTimestamp = releasedAt, + groupId = 0, + groupName = "foo", + dataUseTerms = DataUseTerms.Open, +) + +// Notes: +// - What about revocations? diff --git a/docs/src/content/docs/reference/helm-chart-config.mdx b/docs/src/content/docs/reference/helm-chart-config.mdx index 1673a41090..2d53f374db 100644 --- a/docs/src/content/docs/reference/helm-chart-config.mdx +++ b/docs/src/content/docs/reference/helm-chart-config.mdx @@ -535,6 +535,35 @@ Each organism object has the following fields: Metadata fields associated with the organism. + + `earliestReleaseDate` + Object + + + Configuration object for enabling and configuring the `earliestReleaseDate` metadata field. + For each version of an accession, the `earliestReleaseDate` is calculated as the earliest date + of the internal release date, the dates in the configured `externalFields` and the value from the + previous version of the accession (if there is one). + This can be used when having a mix of sequences imported from other databases, as well as sequences + released first in this Loculus instance, to have a field that shows the earliest release date regardless + of where the sequence was first released. + + + + `earliestReleaseDate.enabled` + boolean + + Whether to enable the `earliestReleaseDate` metadata field. + + + `earliestReleaseDate.externalFields` + Array of strings + + + Field names to use when calculating the earliest release date. + The fields need to be nullable strings formated with `yyyy-mm-dd`. + + `website` Object diff --git a/kubernetes/loculus/templates/_common-metadata.tpl b/kubernetes/loculus/templates/_common-metadata.tpl index 8fea84d3f1..6889c8e281 100644 --- a/kubernetes/loculus/templates/_common-metadata.tpl +++ b/kubernetes/loculus/templates/_common-metadata.tpl @@ -285,6 +285,13 @@ organisms: {{- $args := dict "metadata" (include "loculus.patchMetadataSchema" . | fromYaml).metadata "nucleotideSequences" $nucleotideSequences}} {{ $metadata := include "loculus.generateBackendExternalMetadata" $args | fromYaml }} {{ $metadata.fields | default list | toYaml | nindent 8 }} + earliestReleaseDate: + {{- if .earliestReleaseDate }} + {{ .earliestReleaseDate | toYaml | nindent 8 }} + {{- else }} + enabled: false + externalFields: [] + {{- end }} {{- end }} referenceGenomes: {{ $referenceGenomes:= include "loculus.generateReferenceGenome" $instance.referenceGenomes | fromYaml }} diff --git a/kubernetes/loculus/values.yaml b/kubernetes/loculus/values.yaml index 26e7122331..2bdbc790fc 100644 --- a/kubernetes/loculus/values.yaml +++ b/kubernetes/loculus/values.yaml @@ -33,6 +33,10 @@ defaultOrganismConfig: &defaultOrganismConfig loadSequencesAutomatically: true organismName: "Ebola Sudan" image: "/images/organisms/ebolasudan_small.jpg" + earliestReleaseDate: + enabled: true + externalFields: + - ncbiReleaseDate ### Field list ## General fields # name: Key used across app to refer to this field (required) @@ -131,6 +135,12 @@ defaultOrganismConfig: &defaultOrganismConfig timestamp: ncbiReleaseDate noInput: true columnWidth: 100 + - name: earliestReleaseDate + displayName: Earliest release date + header: Sample details + type: date + rangeSearch: true + noInput: true - name: ncbiUpdateDate type: date displayName: NCBI update date