-
Notifications
You must be signed in to change notification settings - Fork 0
feat(backend): add backend API support for generalized collections #1076
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
7038c6f
Add tables
fhennig f1bca94
add a 'create collection' endpoint
fhennig 1fc3d40
Add 'GET' for collections; add tests
fhennig 70b1194
Add setup to spin up local postgres instance for testing
fhennig c205631
Add 'GET' for collections by ID
fhennig 2fb9f00
foo
fhennig b9049d3
change mutation list defintion
fhennig 649d910
Add delete implementation
fhennig 1894e8b
put pt1
fhennig 0d3f253
dedicated Variant.kt api file
fhennig 28743cd
Change collection & variant IDs from UUID to Long
fhennig fbc5f44
review
fhennig f2ec21a
Consolidate DB setup: use root docker-compose for local dev
fhennig ccef31c
Move validateIsValidOrganism into DashboardsConfig class
fhennig fc75a8d
Fix variant deletion in putCollection and clean up imports
fhennig 47a7c19
Extract duplicated lineage filter validation into a single method
fhennig 1dccf7f
optimize collection loading
fhennig 96d1228
better MutationListDefinition
fhennig 81ad926
Update backend/src/test/kotlin/org/genspectrum/dashboardsbackend/cont…
fhennig 836d747
some progress, but failing tests
fhennig 9629cdd
simplify Mutationlist
fhennig 40c7702
Use import for MutationListDefinition in VariantTable
fhennig 99cd270
Add doc comment to validateLineageFilters
fhennig 4a8185c
Reduce duplication by routing createCollection through createVariantE…
fhennig fb205e1
Use when expression to capture variantId in putCollection
fhennig 51152bb
Split CollectionsControllerTest by HTTP verb
fhennig 13880e3
Rename test to follow GIVEN/WHEN/THEN convention
fhennig a2eea38
Use imports instead of fully-qualified names in CollectionsPostTest
fhennig d86c272
Add test asserting type field is present on variants in GET response
fhennig 513ad8c
Add type field to VariantRequest subclasses for consistency with Variant
fhennig d550451
Revert "Add type field to VariantRequest subclasses for consistency w…
fhennig 29061e3
format
fhennig aae53d6
docs: make the correct type of `type` appear in OpenAPI (#1088)
fengelniederhammer 65512bf
Add test
fhennig 1d19b67
Remove unused function
fhennig 3f03d59
remove explicit spring JDBC import
fhennig 2cba7bb
remove unused exception handler
fhennig File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/Collection.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| package org.genspectrum.dashboardsbackend.api | ||
|
|
||
| import io.swagger.v3.oas.annotations.media.Schema | ||
|
|
||
| @Schema( | ||
| description = "A collection of variants", | ||
| example = """ | ||
| { | ||
| "id": 1, | ||
| "name": "My Collection", | ||
| "ownedBy": "user123", | ||
| "organism": "covid", | ||
| "description": "A collection of interesting variants", | ||
| "variants": [] | ||
| } | ||
| """, | ||
| ) | ||
| data class Collection( | ||
| val id: Long, | ||
| val name: String, | ||
| val ownedBy: String, | ||
| val organism: String, | ||
| val description: String?, | ||
| val variants: List<Variant>, | ||
| ) | ||
|
|
||
| @Schema( | ||
| description = "Request to create a collection", | ||
| example = """ | ||
| { | ||
| "name": "My Collection", | ||
| "organism": "covid", | ||
| "description": "A collection of interesting variants", | ||
| "variants": [ | ||
| { | ||
| "type": "query", | ||
| "name": "BA.2 in USA", | ||
| "description": "BA.2 lineage cases in USA", | ||
| "countQuery": "country='USA' & lineage='BA.2'", | ||
| "coverageQuery": "country='USA'" | ||
| } | ||
| ] | ||
| } | ||
| """, | ||
| ) | ||
| data class CollectionRequest( | ||
| val name: String, | ||
| val organism: String, | ||
| val description: String? = null, | ||
| val variants: List<VariantRequest>, | ||
| ) | ||
|
|
||
| @Schema( | ||
| description = "Request to update a collection", | ||
| example = """ | ||
| { | ||
| "name": "Updated Collection Name", | ||
| "description": "Updated description", | ||
| "variants": [ | ||
| { | ||
| "type": "query", | ||
| "id": 1, | ||
| "name": "BA.2 in USA", | ||
| "description": "BA.2 lineage cases in USA", | ||
| "countQuery": "country='USA' & lineage='BA.2'", | ||
| "coverageQuery": "country='USA'" | ||
| }, | ||
| { | ||
| "type": "query", | ||
| "name": "New Variant Without ID", | ||
| "countQuery": "country='Germany'" | ||
| } | ||
| ] | ||
| } | ||
| """, | ||
| ) | ||
| data class CollectionUpdate( | ||
| val name: String? = null, | ||
| val description: String? = null, | ||
| val variants: List<VariantUpdate>? = null, | ||
| ) |
16 changes: 16 additions & 0 deletions
16
backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/MutationListDefinition.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package org.genspectrum.dashboardsbackend.api | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonInclude | ||
|
|
||
| /** | ||
| * A JSON object with mutation lists (keys: aaMutations, nucMutations, ...) | ||
| * as well as lineage filtering under the "filters" key | ||
| */ | ||
| @JsonInclude(JsonInclude.Include.NON_NULL) | ||
| data class MutationListDefinition( | ||
| val aaMutations: List<String>? = null, | ||
| val nucMutations: List<String>? = null, | ||
| val aaInsertions: List<String>? = null, | ||
| val nucInsertions: List<String>? = null, | ||
| val filters: Map<String, String>? = null, | ||
| ) |
222 changes: 222 additions & 0 deletions
222
backend/src/main/kotlin/org/genspectrum/dashboardsbackend/api/Variant.kt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,222 @@ | ||
| package org.genspectrum.dashboardsbackend.api | ||
|
|
||
| import com.fasterxml.jackson.annotation.JsonCreator | ||
| import com.fasterxml.jackson.annotation.JsonProperty | ||
| import com.fasterxml.jackson.annotation.JsonSubTypes | ||
| import com.fasterxml.jackson.annotation.JsonTypeInfo | ||
| import io.swagger.v3.oas.annotations.media.Schema | ||
| import org.genspectrum.dashboardsbackend.api.Variant.MutationListVariant | ||
| import org.genspectrum.dashboardsbackend.api.Variant.QueryVariant | ||
|
|
||
| enum class QueryVariantType { | ||
| @JsonProperty("query") | ||
| QUERY, | ||
| } | ||
|
|
||
| enum class MutationListVariantType { | ||
| @JsonProperty("mutationList") | ||
| MUTATION_LIST, | ||
| } | ||
|
|
||
| @JsonTypeInfo( | ||
| use = JsonTypeInfo.Id.NAME, | ||
| include = JsonTypeInfo.As.PROPERTY, | ||
| property = "type", | ||
| ) | ||
| @JsonSubTypes( | ||
| JsonSubTypes.Type(value = QueryVariant::class, name = "query"), | ||
| JsonSubTypes.Type(value = MutationListVariant::class, name = "mutationList"), | ||
| ) | ||
| @Schema( | ||
| description = "Base interface for different variant types", | ||
| ) | ||
| sealed interface Variant { | ||
| val id: Long | ||
| val collectionId: Long | ||
|
|
||
| @Schema( | ||
| description = "A variant defined by LAPIS queries", | ||
| example = """ | ||
| { | ||
| "type": "query", | ||
| "id": 1, | ||
| "collectionId": 2, | ||
| "name": "BA.2 in USA", | ||
| "description": "BA.2 lineage cases in USA", | ||
| "countQuery": "country='USA' & lineage='BA.2'", | ||
| "coverageQuery": "country='USA'" | ||
| } | ||
| """, | ||
| ) | ||
| data class QueryVariant @JsonCreator constructor( | ||
| override val id: Long, | ||
| override val collectionId: Long, | ||
| val name: String, | ||
| val description: String?, | ||
| val countQuery: String, | ||
| val coverageQuery: String? = null, | ||
| ) : Variant { | ||
| val type: QueryVariantType = QueryVariantType.QUERY | ||
| } | ||
|
|
||
| @Schema( | ||
| description = "A variant defined by a list of mutations", | ||
| example = """ | ||
| { | ||
| "type": "mutationList", | ||
| "id": 1, | ||
| "collectionId": 2, | ||
| "name": "Omicron mutations", | ||
| "description": "Key mutations for Omicron", | ||
| "mutationList": { | ||
| "aaMutations": ["S:N501Y", "S:E484K", "S:K417N"] | ||
| } | ||
| } | ||
| """, | ||
| ) | ||
| data class MutationListVariant @JsonCreator constructor( | ||
| override val id: Long, | ||
| override val collectionId: Long, | ||
| val name: String, | ||
| val description: String?, | ||
| val mutationList: MutationListDefinition, | ||
| ) : Variant { | ||
| val type: MutationListVariantType = MutationListVariantType.MUTATION_LIST | ||
| } | ||
| } | ||
|
|
||
| @JsonTypeInfo( | ||
| use = JsonTypeInfo.Id.NAME, | ||
| include = JsonTypeInfo.As.PROPERTY, | ||
| property = "type", | ||
| ) | ||
| @JsonSubTypes( | ||
| JsonSubTypes.Type(value = VariantRequest.QueryVariantRequest::class, name = "query"), | ||
| JsonSubTypes.Type(value = VariantRequest.MutationListVariantRequest::class, name = "mutationList"), | ||
| ) | ||
| @Schema( | ||
| description = "Request to create a variant", | ||
| ) | ||
| sealed interface VariantRequest { | ||
| @Schema( | ||
| description = "Request to create a query variant", | ||
| example = """ | ||
| { | ||
| "type": "query", | ||
| "name": "BA.2 in USA", | ||
| "description": "BA.2 lineage cases in USA", | ||
| "countQuery": "country='USA' & lineage='BA.2'", | ||
| "coverageQuery": "country='USA'" | ||
| } | ||
| """, | ||
| ) | ||
| data class QueryVariantRequest( | ||
| val name: String, | ||
| val description: String? = null, | ||
| val countQuery: String, | ||
| val coverageQuery: String? = null, | ||
| ) : VariantRequest { | ||
| val type: QueryVariantType = QueryVariantType.QUERY | ||
| } | ||
|
|
||
| @Schema( | ||
| description = "Request to create a mutation list variant", | ||
| example = """ | ||
| { | ||
| "type": "mutationList", | ||
| "name": "Omicron mutations", | ||
| "description": "Key mutations for Omicron", | ||
| "mutationList": { | ||
| "aaMutations": ["S:N501Y", "S:E484K", "S:K417N"] | ||
| } | ||
| } | ||
| """, | ||
| ) | ||
| data class MutationListVariantRequest( | ||
| val name: String, | ||
| val description: String? = null, | ||
| val mutationList: MutationListDefinition, | ||
| ) : VariantRequest { | ||
| val type: MutationListVariantType = MutationListVariantType.MUTATION_LIST | ||
| } | ||
| } | ||
|
|
||
| @JsonTypeInfo( | ||
| use = JsonTypeInfo.Id.NAME, | ||
| include = JsonTypeInfo.As.PROPERTY, | ||
| property = "type", | ||
| ) | ||
| @JsonSubTypes( | ||
| JsonSubTypes.Type(value = VariantUpdate.QueryVariantUpdate::class, name = "query"), | ||
| JsonSubTypes.Type(value = VariantUpdate.MutationListVariantUpdate::class, name = "mutationList"), | ||
| ) | ||
| @Schema( | ||
| description = "Request to update or create a variant", | ||
| ) | ||
| sealed interface VariantUpdate { | ||
| val id: Long? | ||
|
|
||
| @Schema( | ||
| description = "Request to update or create a query variant", | ||
| example = """ | ||
| { | ||
| "type": "query", | ||
| "id": 1, | ||
| "name": "BA.2 in USA", | ||
| "description": "BA.2 lineage cases in USA", | ||
| "countQuery": "country='USA' & lineage='BA.2'", | ||
| "coverageQuery": "country='USA'" | ||
| } | ||
| """, | ||
| ) | ||
| data class QueryVariantUpdate( | ||
| override val id: Long? = null, | ||
| val name: String, | ||
| val description: String? = null, | ||
| val countQuery: String, | ||
| val coverageQuery: String? = null, | ||
| ) : VariantUpdate { | ||
| val type: QueryVariantType = QueryVariantType.QUERY | ||
| } | ||
|
|
||
| @Schema( | ||
| description = "Request to update or create a mutation list variant", | ||
| example = """ | ||
| { | ||
| "type": "mutationList", | ||
| "id": 1, | ||
| "name": "Omicron mutations", | ||
| "description": "Key mutations for Omicron", | ||
| "mutationList": { | ||
| "aaMutations": ["S:N501Y", "S:E484K", "S:K417N"] | ||
| } | ||
| } | ||
| """, | ||
| ) | ||
| data class MutationListVariantUpdate( | ||
| override val id: Long? = null, | ||
| val name: String, | ||
| val description: String? = null, | ||
| val mutationList: MutationListDefinition, | ||
| ) : VariantUpdate { | ||
| val type: MutationListVariantType = MutationListVariantType.MUTATION_LIST | ||
| } | ||
|
|
||
| fun toVariantRequest(): VariantRequest { | ||
| require(id == null) { "Cannot convert a VariantUpdate with an existing id to a VariantRequest: $id" } | ||
| return when (this) { | ||
| is QueryVariantUpdate -> VariantRequest.QueryVariantRequest( | ||
| name = name, | ||
| description = description, | ||
| countQuery = countQuery, | ||
| coverageQuery = coverageQuery, | ||
| ) | ||
|
|
||
| is MutationListVariantUpdate -> VariantRequest.MutationListVariantRequest( | ||
| name = name, | ||
| description = description, | ||
| mutationList = mutationList, | ||
| ) | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
typedoesn't tell which value is allowed here. Can we copy the magic that we did in LAPIS over here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I just saw that it looks good for the GET request, but not in the POST.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't find it anymore how we did it in LAPIS, can you link it? IMO it's a shortcoming of the generator that this is missing and I personally don't mind it much, I'd rather just solve this with a doc string and move on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I made it work: #1088