From 598b05ffa793631e8448c294a1bffd1e435e0673 Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Mon, 4 Mar 2024 15:16:06 +0100 Subject: [PATCH] feat: support order by random #658 --- .../additional-request-properties.mdx | 5 + .../genspectrum/lapis/openApi/OpenApiDocs.kt | 38 ++++--- .../scheduler/DataVersionCacheInvalidator.kt | 7 +- .../org/genspectrum/lapis/silo/SiloClient.kt | 2 +- .../org/genspectrum/lapis/silo/SiloQuery.kt | 78 ++++++++++--- .../genspectrum/lapis/silo/SiloClientTest.kt | 32 ++++++ .../genspectrum/lapis/silo/SiloQueryTest.kt | 105 +++++++++++++++--- siloLapisTests/test/details.spec.ts | 11 ++ 8 files changed, 233 insertions(+), 45 deletions(-) diff --git a/lapis2-docs/src/content/docs/references/additional-request-properties.mdx b/lapis2-docs/src/content/docs/references/additional-request-properties.mdx index e318ac6a..118e90b3 100644 --- a/lapis2-docs/src/content/docs/references/additional-request-properties.mdx +++ b/lapis2-docs/src/content/docs/references/additional-request-properties.mdx @@ -30,6 +30,11 @@ POST /sample/aggregated } ``` +:::note +You can also specify `random` as the field name to get the results in a random order. +It will act as a tiebreaker if it is provided along with other `orderBy` fields. +::: + :::caution LAPIS will throw an error if you try to order by a field that is not in the response. ::: diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/openApi/OpenApiDocs.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/openApi/OpenApiDocs.kt index b6a0e096..3f274573 100644 --- a/lapis2/src/main/kotlin/org/genspectrum/lapis/openApi/OpenApiDocs.kt +++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/openApi/OpenApiDocs.kt @@ -2,6 +2,7 @@ package org.genspectrum.lapis.openApi import io.swagger.v3.oas.models.Components import io.swagger.v3.oas.models.OpenAPI +import io.swagger.v3.oas.models.media.ArraySchema import io.swagger.v3.oas.models.media.BooleanSchema import io.swagger.v3.oas.models.media.IntegerSchema import io.swagger.v3.oas.models.media.Schema @@ -43,6 +44,7 @@ import org.genspectrum.lapis.request.NucleotideInsertion import org.genspectrum.lapis.request.NucleotideMutation import org.genspectrum.lapis.request.OrderByField import org.genspectrum.lapis.response.COUNT_PROPERTY +import org.genspectrum.lapis.silo.ORDER_BY_RANDOM_FIELD_NAME fun buildOpenApiSchema( sequenceFilterFields: SequenceFilterFields, @@ -90,7 +92,7 @@ fun buildOpenApiSchema( getSequenceFiltersWithFormat( databaseConfig, sequenceFilterFields, - fieldsEnum(databaseConfig.schema.metadata), + detailsOrderByFieldsEnum(databaseConfig), ), DETAILS_FIELDS_DESCRIPTION, databaseConfig.schema.metadata, @@ -112,7 +114,7 @@ fun buildOpenApiSchema( getSequenceFilters( databaseConfig, sequenceFilterFields, - aminoAcidSequenceFieldsEnum(referenceGenomeSchema, databaseConfig), + aminoAcidSequenceOrderByFieldsEnum(referenceGenomeSchema, databaseConfig), ), ), ) @@ -122,7 +124,7 @@ fun buildOpenApiSchema( getSequenceFilters( databaseConfig, sequenceFilterFields, - nucleotideSequenceFieldsEnum(referenceGenomeSchema, databaseConfig), + nucleotideSequenceOrderByFieldsEnum(referenceGenomeSchema, databaseConfig), ), ), ) @@ -201,7 +203,7 @@ fun buildOpenApiSchema( AGGREGATED_ORDER_BY_FIELDS_SCHEMA, arraySchema(aggregatedOrderByFieldsEnum(databaseConfig)), ) - .addSchemas(DETAILS_ORDER_BY_FIELDS_SCHEMA, fieldsArray(databaseConfig.schema.metadata)) + .addSchemas(DETAILS_ORDER_BY_FIELDS_SCHEMA, arraySchema(detailsOrderByFieldsEnum(databaseConfig))) .addSchemas( MUTATIONS_ORDER_BY_FIELDS_SCHEMA, arraySchema(mutationsOrderByFieldsEnum()), @@ -212,11 +214,11 @@ fun buildOpenApiSchema( ) .addSchemas( AMINO_ACID_SEQUENCES_ORDER_BY_FIELDS_SCHEMA, - arraySchema(aminoAcidSequenceFieldsEnum(referenceGenomeSchema, databaseConfig)), + arraySchema(aminoAcidSequenceOrderByFieldsEnum(referenceGenomeSchema, databaseConfig)), ) .addSchemas( NUCLEOTIDE_SEQUENCES_ORDER_BY_FIELDS_SCHEMA, - arraySchema(nucleotideSequenceFieldsEnum(referenceGenomeSchema, databaseConfig)), + arraySchema(nucleotideSequenceOrderByFieldsEnum(referenceGenomeSchema, databaseConfig)), ) .addSchemas( SEGMENT_SCHEMA, @@ -500,27 +502,34 @@ private fun fieldsArray( ) = arraySchema(fieldsEnum(databaseConfig, additionalFields)) private fun aggregatedOrderByFieldsEnum(databaseConfig: DatabaseConfig) = - fieldsEnum(databaseConfig.schema.metadata, listOf("count")) + orderByFieldsEnum(databaseConfig.schema.metadata, listOf("count")) -private fun mutationsOrderByFieldsEnum() = fieldsEnum(emptyList(), listOf("mutation", "count", "proportion")) +private fun mutationsOrderByFieldsEnum() = orderByFieldsEnum(emptyList(), listOf("mutation", "count", "proportion")) -private fun insertionsOrderByFieldsEnum() = fieldsEnum(emptyList(), listOf("insertion", "count")) +private fun insertionsOrderByFieldsEnum() = orderByFieldsEnum(emptyList(), listOf("insertion", "count")) -private fun aminoAcidSequenceFieldsEnum( +private fun aminoAcidSequenceOrderByFieldsEnum( referenceGenomeSchema: ReferenceGenomeSchema, databaseConfig: DatabaseConfig, -) = fieldsEnum(emptyList(), referenceGenomeSchema.genes.map { it.name } + databaseConfig.schema.primaryKey) +) = orderByFieldsEnum(emptyList(), referenceGenomeSchema.genes.map { it.name } + databaseConfig.schema.primaryKey) -private fun nucleotideSequenceFieldsEnum( +private fun nucleotideSequenceOrderByFieldsEnum( referenceGenomeSchema: ReferenceGenomeSchema, databaseConfig: DatabaseConfig, -) = fieldsEnum( +) = orderByFieldsEnum( emptyList(), referenceGenomeSchema.nucleotideSequences.map { it.name } + databaseConfig.schema.primaryKey, ) +private fun detailsOrderByFieldsEnum(databaseConfig: DatabaseConfig) = orderByFieldsEnum(databaseConfig.schema.metadata) + +private fun orderByFieldsEnum( + databaseConfig: List = emptyList(), + additionalFields: List = emptyList(), +) = fieldsEnum(databaseConfig, additionalFields + ORDER_BY_RANDOM_FIELD_NAME) + private fun fieldsEnum( databaseConfig: List = emptyList(), additionalFields: List = emptyList(), @@ -529,6 +538,5 @@ private fun fieldsEnum( ._enum(databaseConfig.map { it.name } + additionalFields) private fun arraySchema(schema: Schema) = - Schema() - .type("array") + ArraySchema() .items(schema) diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/scheduler/DataVersionCacheInvalidator.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/scheduler/DataVersionCacheInvalidator.kt index 25daec60..ad9dbcab 100644 --- a/lapis2/src/main/kotlin/org/genspectrum/lapis/scheduler/DataVersionCacheInvalidator.kt +++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/scheduler/DataVersionCacheInvalidator.kt @@ -22,7 +22,12 @@ class DataVersionCacheInvalidator( fun invalidateSiloCache() { log.debug { "checking for data version change" } - val info = cachedSiloClient.callInfo() + val info = try { + cachedSiloClient.callInfo() + } catch (e: Exception) { + log.debug { "Failed to call info: $e" } + return + } if (info.dataVersion != currentlyCachedDataVersion) { log.info { "Invalidating cache, old data version: $currentlyCachedDataVersion, " + diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloClient.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloClient.kt index 06cb72ce..59ef4f22 100644 --- a/lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloClient.kt +++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloClient.kt @@ -51,7 +51,7 @@ class CachedSiloClient( ) { private val httpClient = HttpClient.newHttpClient() - @Cacheable(SILO_QUERY_CACHE_NAME, condition = "#query.action.cacheable") + @Cacheable(SILO_QUERY_CACHE_NAME, condition = "#query.action.cacheable && !#query.action.randomize") fun sendQuery(query: SiloQuery): WithDataVersion { val queryJson = objectMapper.writeValueAsString(query) diff --git a/lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloQuery.kt b/lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloQuery.kt index d98de20a..4f2cceba 100644 --- a/lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloQuery.kt +++ b/lapis2/src/main/kotlin/org/genspectrum/lapis/silo/SiloQuery.kt @@ -30,8 +30,11 @@ interface CommonActionFields { val orderByFields: List val limit: Int? val offset: Int? + val randomize: Boolean? } +const val ORDER_BY_RANDOM_FIELD_NAME = "random" + sealed class SiloAction( @JsonIgnore val typeReference: TypeReference>, @JsonIgnore val cacheable: Boolean, @@ -42,7 +45,14 @@ sealed class SiloAction( orderByFields: List = emptyList(), limit: Int? = null, offset: Int? = null, - ): SiloAction> = AggregatedAction(groupByFields, orderByFields, limit, offset) + ): SiloAction> = + AggregatedAction( + groupByFields = groupByFields, + orderByFields = getNonRandomizedOrderByFields(orderByFields), + limit = limit, + offset = offset, + randomize = getRandomize(orderByFields), + ) fun mutations( minProportion: Double? = null, @@ -51,10 +61,11 @@ sealed class SiloAction( offset: Int? = null, ): SiloAction> = MutationsAction( - minProportion, - orderByFields, - limit, - offset, + minProportion = minProportion, + orderByFields = getNonRandomizedOrderByFields(orderByFields), + limit = limit, + offset = offset, + randomize = getRandomize(orderByFields), ) fun aminoAcidMutations( @@ -64,10 +75,11 @@ sealed class SiloAction( offset: Int? = null, ): SiloAction> = AminoAcidMutationsAction( - minProportion, - orderByFields, - limit, - offset, + minProportion = minProportion, + orderByFields = getNonRandomizedOrderByFields(orderByFields), + limit = limit, + offset = offset, + randomize = getRandomize(orderByFields), ) fun details( @@ -75,19 +87,38 @@ sealed class SiloAction( orderByFields: List = emptyList(), limit: Int? = null, offset: Int? = null, - ): SiloAction> = DetailsAction(fields, orderByFields, limit, offset) + ): SiloAction> = + DetailsAction( + fields = fields, + orderByFields = getNonRandomizedOrderByFields(orderByFields), + limit = limit, + offset = offset, + randomize = getRandomize(orderByFields), + ) fun nucleotideInsertions( orderByFields: List = emptyList(), limit: Int? = null, offset: Int? = null, - ): SiloAction> = NucleotideInsertionsAction(orderByFields, limit, offset) + ): SiloAction> = + NucleotideInsertionsAction( + orderByFields = getNonRandomizedOrderByFields(orderByFields), + limit = limit, + offset = offset, + randomize = getRandomize(orderByFields), + ) fun aminoAcidInsertions( orderByFields: List = emptyList(), limit: Int? = null, offset: Int? = null, - ): SiloAction> = AminoAcidInsertionsAction(orderByFields, limit, offset) + ): SiloAction> = + AminoAcidInsertionsAction( + orderByFields = getNonRandomizedOrderByFields(orderByFields), + limit = limit, + offset = offset, + randomize = getRandomize(orderByFields), + ) fun genomicSequence( type: SequenceType, @@ -95,13 +126,28 @@ sealed class SiloAction( orderByFields: List = emptyList(), limit: Int? = null, offset: Int? = null, - ): SiloAction> = SequenceAction(orderByFields, limit, offset, type, sequenceName) + ): SiloAction> = + SequenceAction( + type = type, + sequenceName = sequenceName, + orderByFields = getNonRandomizedOrderByFields(orderByFields), + limit = limit, + offset = offset, + randomize = getRandomize(orderByFields), + ) + + private fun getRandomize(orderByFields: List) = + orderByFields.any { it.field == ORDER_BY_RANDOM_FIELD_NAME } + + private fun getNonRandomizedOrderByFields(orderByFields: List) = + orderByFields.filter { it.field != ORDER_BY_RANDOM_FIELD_NAME } } @JsonInclude(JsonInclude.Include.NON_EMPTY) private data class AggregatedAction( val groupByFields: List, override val orderByFields: List = emptyList(), + override val randomize: Boolean? = null, override val limit: Int? = null, override val offset: Int? = null, val type: String = "Aggregated", @@ -111,6 +157,7 @@ sealed class SiloAction( private data class MutationsAction( val minProportion: Double?, override val orderByFields: List = emptyList(), + override val randomize: Boolean? = null, override val limit: Int? = null, override val offset: Int? = null, val type: String = "Mutations", @@ -120,6 +167,7 @@ sealed class SiloAction( private data class AminoAcidMutationsAction( val minProportion: Double?, override val orderByFields: List = emptyList(), + override val randomize: Boolean? = null, override val limit: Int? = null, override val offset: Int? = null, val type: String = "AminoAcidMutations", @@ -129,6 +177,7 @@ sealed class SiloAction( private data class DetailsAction( val fields: List = emptyList(), override val orderByFields: List = emptyList(), + override val randomize: Boolean? = null, override val limit: Int? = null, override val offset: Int? = null, val type: String = "Details", @@ -137,6 +186,7 @@ sealed class SiloAction( @JsonInclude(JsonInclude.Include.NON_EMPTY) private data class NucleotideInsertionsAction( override val orderByFields: List = emptyList(), + override val randomize: Boolean? = null, override val limit: Int? = null, override val offset: Int? = null, val type: String = "Insertions", @@ -145,6 +195,7 @@ sealed class SiloAction( @JsonInclude(JsonInclude.Include.NON_EMPTY) private data class AminoAcidInsertionsAction( override val orderByFields: List = emptyList(), + override val randomize: Boolean? = null, override val limit: Int? = null, override val offset: Int? = null, val type: String = "AminoAcidInsertions", @@ -153,6 +204,7 @@ sealed class SiloAction( @JsonInclude(JsonInclude.Include.NON_EMPTY) private data class SequenceAction( override val orderByFields: List = emptyList(), + override val randomize: Boolean? = null, override val limit: Int? = null, override val offset: Int? = null, val type: SequenceType, diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt index ef87009b..030cfcd2 100644 --- a/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt +++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloClientTest.kt @@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.node.DoubleNode import com.fasterxml.jackson.databind.node.IntNode import com.fasterxml.jackson.databind.node.TextNode import org.genspectrum.lapis.logging.RequestIdContext +import org.genspectrum.lapis.request.Order +import org.genspectrum.lapis.request.OrderByField import org.genspectrum.lapis.response.AggregationData import org.genspectrum.lapis.response.DetailsData import org.genspectrum.lapis.response.MutationData @@ -418,6 +420,36 @@ class SiloClientTest( assertThat(dataVersion.dataVersion, `is`(dataVersionValue)) } + @Test + fun `GIVEN a cacheable action with randomize=true THEN is not cached`() { + val errorMessage = "This error should appear" + expectQueryRequestAndRespondWith( + response() + .withStatusCode(200) + .withBody("""{"queryResult": []}"""), + Times.once(), + ) + expectQueryRequestAndRespondWith( + response() + .withStatusCode(500) + .withBody(errorMessage), + Times.exactly(1), + ) + + val orderByRandom = OrderByField( + ORDER_BY_RANDOM_FIELD_NAME, + Order.ASCENDING, + ) + val query = SiloQuery(SiloAction.mutations(orderByFields = listOf(orderByRandom)), True) + assertThat(query.action.cacheable, `is`(true)) + + val result = underTest.sendQuery(query) + assertThat(result, hasSize(0)) + + val exception = assertThrows { underTest.sendQuery(query) } + assertThat(exception.message, containsString(errorMessage)) + } + companion object { @JvmStatic val queriesThatShouldNotBeCached = listOf( diff --git a/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloQueryTest.kt b/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloQueryTest.kt index f61a2656..7712216b 100644 --- a/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloQueryTest.kt +++ b/lapis2/src/test/kotlin/org/genspectrum/lapis/silo/SiloQueryTest.kt @@ -27,7 +27,8 @@ class SiloQueryTest { val expected = """ { "action": { - "type": "Aggregated" + "type": "Aggregated", + "randomize": false }, "filterExpression": { "type": "StringEquals", @@ -69,14 +70,19 @@ class SiloQueryTest { SiloAction.aggregated(), """ { - "type": "Aggregated" + "type": "Aggregated", + "randomize": false } """, ), Arguments.of( SiloAction.aggregated( listOf("field1", "field2"), - listOf(OrderByField("field3", Order.ASCENDING), OrderByField("field4", Order.DESCENDING)), + listOf( + OrderByField("field3", Order.ASCENDING), + OrderByField("field4", Order.DESCENDING), + OrderByField("random", Order.DESCENDING), + ), 100, 50, ), @@ -88,6 +94,7 @@ class SiloQueryTest { {"field": "field3", "order": "ascending"}, {"field": "field4", "order": "descending"} ], + "randomize": true, "limit": 100, "offset": 50 } @@ -97,14 +104,19 @@ class SiloQueryTest { SiloAction.mutations(), """ { - "type": "Mutations" + "type": "Mutations", + "randomize": false } """, ), Arguments.of( SiloAction.mutations( 0.5, - listOf(OrderByField("field3", Order.ASCENDING), OrderByField("field4", Order.DESCENDING)), + listOf( + OrderByField("field3", Order.ASCENDING), + OrderByField("field4", Order.DESCENDING), + OrderByField("random", Order.DESCENDING), + ), 100, 50, ), @@ -116,6 +128,7 @@ class SiloQueryTest { {"field": "field3", "order": "ascending"}, {"field": "field4", "order": "descending"} ], + "randomize": true, "limit": 100, "offset": 50 } @@ -125,14 +138,19 @@ class SiloQueryTest { SiloAction.aminoAcidMutations(), """ { - "type": "AminoAcidMutations" + "type": "AminoAcidMutations", + "randomize": false } """, ), Arguments.of( SiloAction.aminoAcidMutations( 0.5, - listOf(OrderByField("field3", Order.ASCENDING), OrderByField("field4", Order.DESCENDING)), + listOf( + OrderByField("field3", Order.ASCENDING), + OrderByField("field4", Order.DESCENDING), + OrderByField("random", Order.DESCENDING), + ), 100, 50, ), @@ -144,6 +162,7 @@ class SiloQueryTest { {"field": "field3", "order": "ascending"}, {"field": "field4", "order": "descending"} ], + "randomize": true, "limit": 100, "offset": 50 } @@ -153,14 +172,19 @@ class SiloQueryTest { SiloAction.details(), """ { - "type": "Details" + "type": "Details", + "randomize": false } """, ), Arguments.of( SiloAction.details( listOf("age", "pango_lineage"), - listOf(OrderByField("field3", Order.ASCENDING), OrderByField("field4", Order.DESCENDING)), + listOf( + OrderByField("field3", Order.ASCENDING), + OrderByField("field4", Order.DESCENDING), + OrderByField("random", Order.DESCENDING), + ), 100, 50, ), @@ -172,6 +196,7 @@ class SiloQueryTest { {"field": "field3", "order": "ascending"}, {"field": "field4", "order": "descending"} ], + "randomize": true, "limit": 100, "offset": 50 } @@ -181,13 +206,18 @@ class SiloQueryTest { SiloAction.nucleotideInsertions(), """ { - "type": "Insertions" + "type": "Insertions", + "randomize": false } """, ), Arguments.of( SiloAction.nucleotideInsertions( - listOf(OrderByField("field3", Order.ASCENDING), OrderByField("field4", Order.DESCENDING)), + listOf( + OrderByField("field3", Order.ASCENDING), + OrderByField("field4", Order.DESCENDING), + OrderByField("random", Order.DESCENDING), + ), 100, 50, ), @@ -198,6 +228,39 @@ class SiloQueryTest { {"field": "field3", "order": "ascending"}, {"field": "field4", "order": "descending"} ], + "randomize": true, + "limit": 100, + "offset": 50 + } + """, + ), + Arguments.of( + SiloAction.aminoAcidInsertions(), + """ + { + "type": "AminoAcidInsertions", + "randomize": false + } + """, + ), + Arguments.of( + SiloAction.aminoAcidInsertions( + listOf( + OrderByField("field3", Order.ASCENDING), + OrderByField("field4", Order.DESCENDING), + OrderByField("random", Order.DESCENDING), + ), + 100, + 50, + ), + """ + { + "type": "AminoAcidInsertions", + "orderByFields": [ + {"field": "field3", "order": "ascending"}, + {"field": "field4", "order": "descending"} + ], + "randomize": true, "limit": 100, "offset": 50 } @@ -208,7 +271,8 @@ class SiloQueryTest { """ { "type": "FastaAligned", - "sequenceName": "someSequenceName" + "sequenceName": "someSequenceName", + "randomize": false } """, ), @@ -216,7 +280,11 @@ class SiloQueryTest { SiloAction.genomicSequence( SequenceType.ALIGNED, "someSequenceName", - listOf(OrderByField("field3", Order.ASCENDING), OrderByField("field4", Order.DESCENDING)), + listOf( + OrderByField("field3", Order.ASCENDING), + OrderByField("field4", Order.DESCENDING), + OrderByField("random", Order.DESCENDING), + ), 100, 50, ), @@ -228,6 +296,7 @@ class SiloQueryTest { {"field": "field3", "order": "ascending"}, {"field": "field4", "order": "descending"} ], + "randomize": true, "limit": 100, "offset": 50 } @@ -238,7 +307,8 @@ class SiloQueryTest { """ { "type": "Fasta", - "sequenceName": "someSequenceName" + "sequenceName": "someSequenceName", + "randomize": false } """, ), @@ -246,7 +316,11 @@ class SiloQueryTest { SiloAction.genomicSequence( SequenceType.UNALIGNED, "someSequenceName", - listOf(OrderByField("field3", Order.ASCENDING), OrderByField("field4", Order.DESCENDING)), + listOf( + OrderByField("field3", Order.ASCENDING), + OrderByField("field4", Order.DESCENDING), + OrderByField("random", Order.DESCENDING), + ), 100, 50, ), @@ -258,6 +332,7 @@ class SiloQueryTest { {"field": "field3", "order": "ascending"}, {"field": "field4", "order": "descending"} ], + "randomize": true, "limit": 100, "offset": 50 } diff --git a/siloLapisTests/test/details.spec.ts b/siloLapisTests/test/details.spec.ts index 6a35ffb7..7aafdf88 100644 --- a/siloLapisTests/test/details.spec.ts +++ b/siloLapisTests/test/details.spec.ts @@ -201,4 +201,15 @@ key_1002052 `.trim() ); }); + + it('should order by random', async () => { + const result = await lapisClient.postDetails1({ + detailsPostRequest: { + orderBy: [{ field: 'random' }, { field: 'division' }], + fields: ['primaryKey', 'division'], + }, + }); + + expect(result).to.have.nested.property('data[0].division', undefined); + }); });