1- import type { CustomColumn , LapisFilter } from '@genspectrum/dashboard-components/util' ;
1+ import type { CountCoverageQuery , CustomColumn , LapisFilter } from '@genspectrum/dashboard-components/util' ;
22import { useQuery } from '@tanstack/react-query' ;
33import dayjs from 'dayjs' ;
44
@@ -15,11 +15,12 @@ import type {
1515} from './wasapPageConfig' ;
1616import { getBackendServiceForClientside } from '../../../backendApi/backendService' ;
1717import { getCollection } from '../../../covspectrum/getCollection' ;
18+ import type { CollectionVariant } from '../../../covspectrum/types' ;
1819import { detailedMutationsToQuery } from '../../../covspectrum/variantConversionUtil' ;
1920import { getCladeLineages } from '../../../lapis/getCladeLineages' ;
2021import { getJaccardForMutations , getMutations , getMutationsForVariant } from '../../../lapis/getMutations' ;
2122import { parseQuery } from '../../../lapis/parseQuery' ;
22- import type { FilterObject } from '../../../types/Collection' ;
23+ import type { FilterObject , Variant } from '../../../types/Collection' ;
2324import { validateGenomeOnly } from '../../../util/siloExpressionUtils' ;
2425
2526/**
@@ -245,95 +246,21 @@ async function fetchCovSpectrumCollectionModeData(
245246 }
246247 const collection = await getCollection ( config . collectionsApiBaseUrl , analysis . collectionId ) ;
247248
248- const variantData : {
249- name : string ;
250- queryString : string ;
251- description ?: string ;
252- } [ ] = [ ] ;
253-
254- const invalidVariants : {
255- name : string ;
256- error : string ;
257- } [ ] = [ ] ;
258-
259- for ( const variant of collection . variants ) {
260- let queryString : string ;
261- switch ( variant . query . type ) {
262- case 'variantQuery' : {
263- queryString = variant . query . variantQuery ;
264- break ;
265- }
266- case 'detailedMutations' : {
267- queryString = detailedMutationsToQuery ( variant . query ) ;
268- break ;
269- }
270- }
271- if ( queryString === '' ) {
272- invalidVariants . push ( {
273- name : variant . name ,
274- error : 'Variant is empty.' ,
275- } ) ;
276- continue ;
277- }
278- variantData . push ( {
279- name : variant . name ,
280- queryString : queryString ,
281- description : variant . description !== '' ? variant . description : undefined ,
282- } ) ;
283- }
284-
285- // Parse all variant queries through LAPIS
286- const parseResults = await parseQuery ( config . lapisBaseUrl , { queries : variantData . map ( ( vd ) => vd . queryString ) } ) ;
287-
288- // Process results and validate
289- const queries : {
290- displayLabel : string ;
291- description ?: string ;
292- countQuery : string ;
293- coverageQuery : string ;
294- } [ ] = [ ] ;
295-
296- variantData . forEach ( ( { name, queryString, description } , index ) => {
297- const parseResult = parseResults [ index ] ;
298-
299- // Check if parsing failed
300- if ( parseResult . type === 'failure' ) {
301- invalidVariants . push ( {
302- name : name ,
303- error : `Parse error: ${ parseResult . error } ` ,
304- } ) ;
305- return ;
306- }
307-
308- // Validate that the parsed query only contains genome checks
309- const validationResult = validateGenomeOnly ( parseResult . filter ) ;
310- if ( ! validationResult . isGenomeOnly ) {
311- invalidVariants . push ( {
312- name : name ,
313- error : validationResult . error ,
314- } ) ;
315- return ;
316- }
317-
318- // Query is valid - add to queries array
319- // coverage query can be calculated as below, see https://github.com/GenSpectrum/LAPIS/pull/1558
320- const coverageQuery = `(${ queryString } ) or (not maybe(${ queryString } ))` ;
321- queries . push ( {
322- displayLabel : name ,
323- description,
324- countQuery : queryString ,
325- coverageQuery,
326- } ) ;
327- } ) ;
249+ const { variantData, invalidVariants } = extractCovSpectrumVariantData ( collection . variants ) ;
250+ const { queries, invalidVariants : parseInvalidVariants } = await parseAndBuildQueries (
251+ config . lapisBaseUrl ,
252+ variantData ,
253+ ) ;
254+ const allInvalidVariants = [ ...invalidVariants , ...parseInvalidVariants ] ;
328255
329256 return {
330257 type : 'collection' ,
331258 collection : {
332259 id : collection . id ,
333260 title : collection . title ,
334- queries,
261+ queries : deduplicateLabels ( queries ) ,
335262 } ,
336- ...( invalidVariants . length > 0 && { invalidVariants } ) ,
263+ ...( allInvalidVariants . length > 0 && { invalidVariants : allInvalidVariants } ) ,
337264 } ;
338265}
339266
@@ -349,18 +276,56 @@ async function fetchCollectionModeData(
349276 }
350277 const collection = await getBackendServiceForClientside ( ) . getCollection ( { id : String ( analysis . collectionId ) } ) ;
351278
352- const variantData : {
353- name : string ;
354- queryString : string ;
355- description ?: string ;
356- } [ ] = [ ] ;
279+ const { variantData, invalidVariants } = extractBackendVariantData ( collection . variants ) ;
280+ const { queries, invalidVariants : parseInvalidVariants } = await parseAndBuildQueries (
281+ config . lapisBaseUrl ,
282+ variantData ,
283+ ) ;
284+ const allInvalidVariants = [ ...invalidVariants , ...parseInvalidVariants ] ;
357285
358- const invalidVariants : {
359- name : string ;
360- error : string ;
361- } [ ] = [ ] ;
286+ return {
287+ type : 'collection' ,
288+ collection : { id : collection . id , title : collection . name , queries : deduplicateLabels ( queries ) } ,
289+ ...( allInvalidVariants . length > 0 && { invalidVariants : allInvalidVariants } ) ,
290+ } ;
291+ }
292+
293+ type VariantQueryInput = { name : string ; queryString : string ; description ?: string } ;
294+ type VariantExtractionResult = { variantData : VariantQueryInput [ ] ; invalidVariants : InvalidVariantInfo [ ] } ;
295+
296+ function extractCovSpectrumVariantData ( variants : CollectionVariant [ ] ) : VariantExtractionResult {
297+ const variantData : VariantQueryInput [ ] = [ ] ;
298+ const invalidVariants : InvalidVariantInfo [ ] = [ ] ;
299+
300+ for ( const variant of variants ) {
301+ let queryString : string ;
302+ switch ( variant . query . type ) {
303+ case 'variantQuery' :
304+ queryString = variant . query . variantQuery ;
305+ break ;
306+ case 'detailedMutations' :
307+ queryString = detailedMutationsToQuery ( variant . query ) ;
308+ break ;
309+ }
310+ if ( queryString === '' ) {
311+ invalidVariants . push ( { name : variant . name , error : 'Variant is empty.' } ) ;
312+ continue ;
313+ }
314+ variantData . push ( {
315+ name : variant . name ,
316+ queryString,
317+ description : variant . description !== '' ? variant . description : undefined ,
318+ } ) ;
319+ }
320+
321+ return { variantData, invalidVariants } ;
322+ }
323+
324+ function extractBackendVariantData ( variants : Variant [ ] ) : VariantExtractionResult {
325+ const variantData : VariantQueryInput [ ] = [ ] ;
326+ const invalidVariants : InvalidVariantInfo [ ] = [ ] ;
362327
363- for ( const variant of collection . variants ) {
328+ for ( const variant of variants ) {
364329 let queryString : string ;
365330 switch ( variant . type ) {
366331 case 'query' :
@@ -381,14 +346,22 @@ async function fetchCollectionModeData(
381346 } ) ;
382347 }
383348
384- const parseResults = await parseQuery ( config . lapisBaseUrl , { queries : variantData . map ( ( vd ) => vd . queryString ) } ) ;
349+ return { variantData, invalidVariants } ;
350+ }
385351
386- const queries : {
387- displayLabel : string ;
388- description ?: string ;
389- countQuery : string ;
390- coverageQuery : string ;
391- } [ ] = [ ] ;
352+ /**
353+ * Takes a list of variant queries (from a collection) and validates them all against a LAPIS.
354+ * For valid variant queries, it constructs a `CountCoverageQuery` to use with the `GsQueriesOverTime`
355+ * component. For invalid queries, an `InvalidVariantInfo` is returned.
356+ */
357+ async function parseAndBuildQueries (
358+ lapisBaseUrl : string ,
359+ variantData : VariantQueryInput [ ] ,
360+ ) : Promise < { queries : CountCoverageQuery [ ] ; invalidVariants : InvalidVariantInfo [ ] } > {
361+ const parseResults = await parseQuery ( lapisBaseUrl , { queries : variantData . map ( ( vd ) => vd . queryString ) } ) ;
362+
363+ const queries : CountCoverageQuery [ ] = [ ] ;
364+ const invalidVariants : InvalidVariantInfo [ ] = [ ] ;
392365
393366 variantData . forEach ( ( { name, queryString, description } , index ) => {
394367 const parseResult = parseResults [ index ] ;
@@ -401,15 +374,12 @@ async function fetchCollectionModeData(
401374 invalidVariants . push ( { name, error : validationResult . error } ) ;
402375 return ;
403376 }
377+ // coverage query formula: https://github.com/GenSpectrum/LAPIS/pull/1558
404378 const coverageQuery = `(${ queryString } ) or (not maybe(${ queryString } ))` ;
405379 queries . push ( { displayLabel : name , description, countQuery : queryString , coverageQuery } ) ;
406380 } ) ;
407381
408- return {
409- type : 'collection' ,
410- collection : { id : collection . id , title : collection . name , queries : deduplicateLabels ( queries ) } ,
411- ...( invalidVariants . length > 0 && { invalidVariants } ) ,
412- } ;
382+ return { queries, invalidVariants } ;
413383}
414384
415385function deduplicateLabels < T extends { displayLabel : string } > ( queries : T [ ] ) : T [ ] {
@@ -469,6 +439,14 @@ export type WasapMutationsData = {
469439 lineageForJaccard ?: string ;
470440} ;
471441
442+ /**
443+ * Name of the invalid variant, and the error (why it's not valid).
444+ */
445+ type InvalidVariantInfo = {
446+ name : string ;
447+ error : string ;
448+ } ;
449+
472450/**
473451 * Collection data consists of a collection with its variants.
474452 */
@@ -477,15 +455,7 @@ export type WasapCollectionData = {
477455 collection : {
478456 id : number ;
479457 title : string ;
480- queries : {
481- displayLabel : string ;
482- countQuery : string ;
483- coverageQuery : string ;
484- description ?: string ;
485- } [ ] ;
458+ queries : CountCoverageQuery [ ] ;
486459 } ;
487- invalidVariants ?: {
488- name : string ;
489- error : string ;
490- } [ ] ;
460+ invalidVariants ?: InvalidVariantInfo [ ] ;
491461} ;
0 commit comments