Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lapis-e2e/test/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import {
InfoControllerApi,
LapisControllerApi,
Middleware,
QueriesOverTimeControllerApi,
SingleSegmentedSequenceControllerApi,
} from './lapisClient';
import { MutationsOverTimeControllerApi } from './lapisClient/index';
import {
LapisControllerApi as LapisControllerApiMultiSegmented,
MultiSegmentedSequenceControllerApi,
Expand Down Expand Up @@ -44,7 +44,7 @@ export const lapisInfoClient = new InfoControllerApi(new Configuration({ basePat
middleware
);
export const actuatorClient = new ActuatorApi(new Configuration({ basePath })).withMiddleware(middleware);
export const mutOverTimeClient = new MutationsOverTimeControllerApi(
export const queriesOverTimeClient = new QueriesOverTimeControllerApi(
new Configuration({ basePath })
).withMiddleware(middleware);
export const lapisClientMultiSegmented = new LapisControllerApiMultiSegmented(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,51 @@
import { expect } from 'chai';
import { mutOverTimeClient } from './common';
import { queriesOverTimeClient } from './common';

describe('The /mutationsOverTime endpoint', () => {
it('should return a correct queriesOverTime response', async () => {
const result = await queriesOverTimeClient.postQueriesOverTime({
queriesOverTimeRequest: {
filters: {
country: 'Switzerland',
},
queries: [
{ countQuery: 'C27972T | C26735T', coverageQuery: '!27972N & !26735N', displayLabel: 'my label' },
{ countQuery: 'C27972T & C26735T', coverageQuery: '!27972N & !26735N' },
],
dateRanges: [
{ dateFrom: '2020-01-01', dateTo: '2020-12-31' },
{ dateFrom: '2021-01-01', dateTo: '2021-12-31' },
{ dateFrom: '2022-01-01', dateTo: '2022-12-31' },
],
dateField: 'date',
},
});

expect(result.data).to.deep.equal({
queries: ['my label', 'C27972T & C26735T'],
dateRanges: [
{ dateFrom: new Date('2020-01-01'), dateTo: new Date('2020-12-31') },
{ dateFrom: new Date('2021-01-01'), dateTo: new Date('2021-12-31') },
{ dateFrom: new Date('2022-01-01'), dateTo: new Date('2022-12-31') },
],
data: [
[
{ count: 6, coverage: 22 },
{ count: 52, coverage: 73 },
{ count: 0, coverage: 0 },
],
[
{ count: 0, coverage: 22 },
{ count: 1, coverage: 73 },
{ count: 0, coverage: 0 },
],
],
totalCountsByDateRange: [22, 77, 0],
});
});

it('returns a response with the correct dimensions etc.', async () => {
const result = await mutOverTimeClient.postNucleotideMutationsOverTime({
const result = await queriesOverTimeClient.postNucleotideMutationsOverTime({
mutationsOverTimeRequest: {
filters: {
country: 'Switzerland',
Expand Down Expand Up @@ -40,7 +82,7 @@ describe('The /mutationsOverTime endpoint', () => {
});

it('returns an empty response if no mutations are given', async () => {
const result = await mutOverTimeClient.postNucleotideMutationsOverTime({
const result = await queriesOverTimeClient.postNucleotideMutationsOverTime({
mutationsOverTimeRequest: {
filters: {
country: 'Switzerland',
Expand Down Expand Up @@ -69,7 +111,7 @@ describe('The /mutationsOverTime endpoint', () => {
});

it('returns an empty response if no date ranges are given', async () => {
const result = await mutOverTimeClient.postNucleotideMutationsOverTime({
const result = await queriesOverTimeClient.postNucleotideMutationsOverTime({
mutationsOverTimeRequest: {
filters: {
country: 'Switzerland',
Expand All @@ -86,7 +128,7 @@ describe('The /mutationsOverTime endpoint', () => {
});

it('if downloadAsFile is true, the content disposition is set to attachment', async () => {
const result = await mutOverTimeClient.postNucleotideMutationsOverTimeRaw({
const result = await queriesOverTimeClient.postNucleotideMutationsOverTimeRaw({
mutationsOverTimeRequest: {
filters: {
country: 'Switzerland',
Expand All @@ -102,7 +144,7 @@ describe('The /mutationsOverTime endpoint', () => {
});

it('if downloadFileBasename is set, it is present in the headers', async () => {
const result = await mutOverTimeClient.postNucleotideMutationsOverTimeRaw({
const result = await queriesOverTimeClient.postNucleotideMutationsOverTimeRaw({
mutationsOverTimeRequest: {
filters: {
country: 'Switzerland',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ const val ALL_UNALIGNED_MULTI_SEGMENTED_NUCLEOTIDE_SEQUENCE_ENDPOINT_DESCRIPTION
""""Returns the unaligned nucleotide sequences of all requested segments that match the given filter criteria."""
const val MUTATIONS_OVER_TIME_ENDPOINT_DESCRIPTION =
"""Returns the number of sequences containing the specified mutations within the requested date ranges, along
with the corresponding coverage. The order of the mutations and date ranges is preserved."""
with the corresponding coverage in a tabular format. The order of the mutations and date ranges is preserved."""
const val QUERIES_OVER_TIME_ENDPOINT_DESCRIPTION = """
Returns the number of sequences matching the specified queries within the requested date ranges, along
with the corresponding coverage in a tabular format. The order of the queries and date ranges is preserved.

This endpoint is a generalization of the "mutations over time" endpoints. It allows for more complex queries,
such as combinations of mutations ("mutation 1 or mutation 2") and other filters. Since it's not obvious what
"coverage" means for arbitrary queries, the user always has to specify a coverage query separately.
"""

const val AGGREGATED_GROUP_BY_FIELDS_DESCRIPTION =
"""The fields to stratify by.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@ import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.media.Schema
import org.genspectrum.lapis.controller.LapisHeaders.LAPIS_DATA_VERSION
import org.genspectrum.lapis.model.mutationsOverTime.MutationsOverTimeModel
import org.genspectrum.lapis.model.mutationsOverTime.MutationsOverTimeResult
import org.genspectrum.lapis.model.mutationsOverTime.QueriesOverTimeModel
import org.genspectrum.lapis.model.mutationsOverTime.QueriesOverTimeResult
import org.genspectrum.lapis.openApi.MUTATIONS_OVER_TIME_REQUEST_SCHEMA
import org.genspectrum.lapis.openApi.QUERIES_OVER_TIME_REQUEST_SCHEMA
import org.genspectrum.lapis.request.AminoAcidMutationsOverTimeRequest
import org.genspectrum.lapis.request.NucleotideMutationsOverTimeRequest
import org.genspectrum.lapis.request.QueriesOverTimeRequest
import org.genspectrum.lapis.response.LapisInfoFactory
import org.genspectrum.lapis.response.MutationsOverTimeResponse
import org.genspectrum.lapis.response.QueriesOverTimeResponse
import org.genspectrum.lapis.silo.DataVersion
import org.springframework.http.MediaType
import org.springframework.http.ResponseEntity
Expand All @@ -21,11 +24,12 @@ import org.springframework.web.bind.annotation.RestController

const val NUCLEOTIDE_MUTATIONS_OVER_TIME_ROUTE = "/nucleotideMutationsOverTime"
const val AMINO_ACID_MUTATIONS_OVER_TIME_ROUTE = "/aminoAcidMutationsOverTime"
const val QUERIES_OVER_TIME_ROUTE = "/queriesOverTime"

@RestController
@RequestMapping("/component")
class MutationsOverTimeController(
val mutationsOverTimeModel: MutationsOverTimeModel,
class QueriesOverTimeController(
val queriesOverTimeModel: QueriesOverTimeModel,
val lapisInfoFactory: LapisInfoFactory,
val dataVersion: DataVersion,
) {
Expand All @@ -39,14 +43,14 @@ class MutationsOverTimeController(
@Parameter(schema = Schema(ref = "#/components/schemas/$MUTATIONS_OVER_TIME_REQUEST_SCHEMA"))
@RequestBody
request: NucleotideMutationsOverTimeRequest,
): ResponseEntity<MutationsOverTimeResponse> {
val data = mutationsOverTimeModel.evaluateNucleotideMutations(
): ResponseEntity<QueriesOverTimeResponse<MutationsOverTimeResult>> {
val data = queriesOverTimeModel.evaluateNucleotideMutations(
request.includeMutations,
request.dateRanges,
request.filters,
request.dateField,
)
return createMutationsOverTimeResponse(data)
return createResponse(data)
}

@PostMapping(
Expand All @@ -59,21 +63,39 @@ class MutationsOverTimeController(
@Parameter(schema = Schema(ref = "#/components/schemas/$MUTATIONS_OVER_TIME_REQUEST_SCHEMA"))
@RequestBody
request: AminoAcidMutationsOverTimeRequest,
): ResponseEntity<MutationsOverTimeResponse> {
val data = mutationsOverTimeModel.evaluateAminoAcidMutations(
): ResponseEntity<QueriesOverTimeResponse<MutationsOverTimeResult>> {
val data = queriesOverTimeModel.evaluateAminoAcidMutations(
request.includeMutations,
request.dateRanges,
request.filters,
request.dateField,
)
return createMutationsOverTimeResponse(data)
return createResponse(data)
}

private fun createMutationsOverTimeResponse(
resultData: MutationsOverTimeResult,
): ResponseEntity<MutationsOverTimeResponse> =
@PostMapping(
QUERIES_OVER_TIME_ROUTE,
produces = [MediaType.APPLICATION_JSON_VALUE],
consumes = [MediaType.APPLICATION_JSON_VALUE],
)
@Operation(description = QUERIES_OVER_TIME_ENDPOINT_DESCRIPTION)
fun postQueriesOverTime(
@Parameter(schema = Schema(ref = "#/components/schemas/$QUERIES_OVER_TIME_REQUEST_SCHEMA"))
@RequestBody
request: QueriesOverTimeRequest,
): ResponseEntity<QueriesOverTimeResponse<QueriesOverTimeResult>> {
val data = queriesOverTimeModel.evaluateQueriesOverTime(
request.queries,
request.dateRanges,
request.filters,
request.dateField,
)
return createResponse(data)
}

private fun <Result> createResponse(resultData: Result) =
ResponseEntity
.ok()
.header(LAPIS_DATA_VERSION, dataVersion.dataVersion)
.body(MutationsOverTimeResponse(resultData, lapisInfoFactory.create()))
.body(QueriesOverTimeResponse(resultData, lapisInfoFactory.create()))
}
Loading
Loading