Skip to content

Commit 0973b71

Browse files
committed
refactor collection fetching
1 parent 22109f5 commit 0973b71

1 file changed

Lines changed: 86 additions & 116 deletions

File tree

website/src/components/views/wasap/useWasapPageData.ts

Lines changed: 86 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { CustomColumn, LapisFilter } from '@genspectrum/dashboard-components/util';
1+
import type { CountCoverageQuery, CustomColumn, LapisFilter } from '@genspectrum/dashboard-components/util';
22
import { useQuery } from '@tanstack/react-query';
33
import dayjs from 'dayjs';
44

@@ -15,11 +15,12 @@ import type {
1515
} from './wasapPageConfig';
1616
import { getBackendServiceForClientside } from '../../../backendApi/backendService';
1717
import { getCollection } from '../../../covspectrum/getCollection';
18+
import type { CollectionVariant } from '../../../covspectrum/types';
1819
import { detailedMutationsToQuery } from '../../../covspectrum/variantConversionUtil';
1920
import { getCladeLineages } from '../../../lapis/getCladeLineages';
2021
import { getJaccardForMutations, getMutations, getMutationsForVariant } from '../../../lapis/getMutations';
2122
import { parseQuery } from '../../../lapis/parseQuery';
22-
import type { FilterObject } from '../../../types/Collection';
23+
import type { FilterObject, Variant } from '../../../types/Collection';
2324
import { 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

415385
function 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

Comments
 (0)