Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { type FC, useMemo } from 'react';

import type { OrganismsConfig } from '../../../config.ts';
import { chooseGranularityBasedOnDateRange } from '../../../util/chooseGranularityBasedOnDateRange.ts';
import { ComponentHeight } from '../../../views/OrganismConstants.ts';
import type { DatasetAndVariantData } from '../../../views/View.ts';
import { toLapisFilterWithoutVariant } from '../../../views/pageStateHandlers/toLapisFilterWithoutVariant.ts';
import { type OrganismWithViewKey, Routing } from '../../../views/routing.ts';
import { compareSideBySideViewKey } from '../../../views/viewKeys.ts';
import { GsAggregate } from '../../genspectrum/GsAggregate.tsx';
import { GsMutations } from '../../genspectrum/GsMutations.tsx';
import { GsPrevalenceOverTime } from '../../genspectrum/GsPrevalenceOverTime.tsx';

export type GenericCompareSideBySideDataDisplayProps = {
organismViewKey: `${OrganismWithViewKey<typeof compareSideBySideViewKey>}.${typeof compareSideBySideViewKey}`;
organismsConfig: OrganismsConfig;
datasetAndVariantData: DatasetAndVariantData;
hideMutationComponents?: boolean;
};

export const GenericCompareSideBySideDataDisplay: FC<GenericCompareSideBySideDataDisplayProps> = ({
organismViewKey,
organismsConfig,
datasetAndVariantData,
hideMutationComponents,
}) => {
const view = useMemo(
() => new Routing(organismsConfig).getOrganismView(organismViewKey),
[organismsConfig, organismViewKey],
);

const { datasetFilter, variantFilter } = datasetAndVariantData;

const datasetLapisFilter = toLapisFilterWithoutVariant(datasetFilter, view.organismConstants.additionalFilters);
const timeGranularity = chooseGranularityBasedOnDateRange({
earliestDate: new Date(view.organismConstants.earliestDate),
dateRange: datasetFilter.dateFilters[view.organismConstants.mainDateField],
});
const numeratorFilter = view.pageStateHandler.variantFilterToLapisFilter(datasetFilter, variantFilter);

return (
<>
<GsPrevalenceOverTime
numeratorFilters={[
{
displayName: '',
lapisFilter: numeratorFilter,
},
]}
denominatorFilter={datasetLapisFilter}
lapisDateField={view.organismConstants.mainDateField}
granularity={timeGranularity}
height={ComponentHeight.large}
pageSize={10}
/>
{hideMutationComponents !== true && (
<>
<GsMutations
lapisFilter={numeratorFilter}
baselineLapisFilter={datasetLapisFilter}
sequenceType='nucleotide'
pageSize={10}
/>
<GsMutations
lapisFilter={numeratorFilter}
baselineLapisFilter={datasetLapisFilter}
sequenceType='amino acid'
pageSize={10}
/>
</>
)}

{view.organismConstants.aggregatedVisualizations.compareSideBySide.map(({ label, fields, views }) => (
<GsAggregate
key={label}
title={label}
fields={fields}
lapisFilter={numeratorFilter}
views={views}
pageSize={10}
/>
))}
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
---
import { GenericCompareSideBySideDataDisplay } from './GenericCompareSideBySideDataDisplay.tsx';
import { toDownloadLink } from './toDownloadLink';
import { isStaging, getDashboardsConfig, getLapisUrl } from '../../../config';
import OrganismViewPageLayout from '../../../layouts/OrganismPage/OrganismViewPageLayout.astro';
import { chooseGranularityBasedOnDateRange } from '../../../util/chooseGranularityBasedOnDateRange';
import { ComponentHeight } from '../../../views/OrganismConstants';
import { toLapisFilterWithoutVariant } from '../../../views/pageStateHandlers/toLapisFilterWithoutVariant';
import { type OrganismViewKey, type OrganismWithViewKey } from '../../../views/routing';
import { ServerSide } from '../../../views/serverSideRouting';
import { compareSideBySideViewKey } from '../../../views/viewKeys';
import GsAggregate from '../../genspectrum/GsAggregate.astro';
import GsMutations from '../../genspectrum/GsMutations.astro';
import GsPrevalenceOverTime from '../../genspectrum/GsPrevalenceOverTime.astro';
import { CompareSideBySidePageStateSelector } from '../../pageStateSelectors/CompareSideBySidePageStateSelector';
import { CompareSideBySideSelectorFallback } from '../../pageStateSelectors/FallbackElement';

Expand All @@ -26,27 +21,16 @@ const view = ServerSide.routing.getOrganismView(organismViewKey);

const pageState = view.pageStateHandler.parsePageStateFromUrl(Astro.url);

const organismConfig = getDashboardsConfig().dashboards.organisms;

const downloadLinks = [...pageState.filters.entries()].map(toDownloadLink(view.pageStateHandler, organism));
---

<OrganismViewPageLayout view={view} downloadLinks={downloadLinks}>
<gs-app lapis={getLapisUrl(view.organismConstants.organism)}>
<div class='flex overflow-x-auto'>
{
Array.from(pageState.filters).map(([id, { variantFilter, datasetFilter }]) => {
const datasetLapisFilter = toLapisFilterWithoutVariant(
datasetFilter,
view.organismConstants.additionalFilters,
);
const timeGranularity = chooseGranularityBasedOnDateRange({
earliestDate: new Date(view.organismConstants.earliestDate),
dateRange: datasetFilter.dateFilters[view.organismConstants.mainDateField],
});
const numeratorFilter = view.pageStateHandler.variantFilterToLapisFilter(
datasetFilter,
variantFilter,
);

Array.from(pageState.filters).map(([id, datasetAndVariantData]) => {
return (
<div class='flex min-w-[500px] flex-1 flex-col gap-4 border-r-2 border-gray-200 px-2'>
<div class='mb-4'>
Expand All @@ -63,7 +47,7 @@ const downloadLinks = [...pageState.filters.entries()].map(toDownloadLink(view.p
<CompareSideBySidePageStateSelector
filterId={id}
initialPageState={pageState}
organismsConfig={getDashboardsConfig().dashboards.organisms}
organismsConfig={organismConfig}
organismViewKey={organismViewKey}
enableAdvancedQueryFilter={isStaging()}
client:only='react'
Expand All @@ -75,47 +59,13 @@ const downloadLinks = [...pageState.filters.entries()].map(toDownloadLink(view.p
</CompareSideBySidePageStateSelector>
</div>

<GsPrevalenceOverTime
numeratorFilters={[
{
displayName: '',
lapisFilter: numeratorFilter,
},
]}
denominatorFilter={datasetLapisFilter}
lapisDateField={view.organismConstants.mainDateField}
granularity={timeGranularity}
height={ComponentHeight.large}
pageSize={10}
<GenericCompareSideBySideDataDisplay
organismViewKey={organismViewKey}
organismsConfig={organismConfig}
datasetAndVariantData={datasetAndVariantData}
hideMutationComponents={hideMutationComponents}
client:load
/>
{!hideMutationComponents && (
<>
<GsMutations
lapisFilter={numeratorFilter}
baselineLapisFilter={datasetLapisFilter}
sequenceType='nucleotide'
pageSize={10}
/>
<GsMutations
lapisFilter={numeratorFilter}
baselineLapisFilter={datasetLapisFilter}
sequenceType='amino acid'
pageSize={10}
/>
</>
)}

{view.organismConstants.aggregatedVisualizations.compareSideBySide.map(
({ label, fields, views }) => (
<GsAggregate
title={label}
fields={fields}
lapisFilter={numeratorFilter}
views={views}
pageSize={10}
/>
),
)}
</div>
);
})
Expand Down
50 changes: 50 additions & 0 deletions website/tests/CompareSideBySidePage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { expect } from '@playwright/test';

import { ViewPage } from './ViewPage.ts';
import { paths } from '../src/types/Organism.ts';
import { type OrganismWithViewKey } from '../src/views/routing';
import { compareSideBySideViewKey } from '../src/views/viewKeys';

export class CompareSideBySidePage extends ViewPage {
public readonly removeColumnButton = this.page.getByRole('link', { name: 'Remove column' });
public readonly addColumnButton = this.page.getByRole('link', { name: 'Add column' });
public readonly mutationField = this.page.getByRole('combobox', { name: 'Enter a mutation', exact: false });

public async goto(organism: OrganismWithViewKey<typeof compareSideBySideViewKey>) {
await this.page.goto(`${paths[organism].basePath}/compare-side-by-side`);
}

public async addColumn(options: {
dateRangeOption: string;
expectedColumnCount: number;
lineage?: string;
lineageFieldPlaceholder?: string;
mutation?: string;
}) {
await expect(this.addColumnButton).toBeVisible();

await this.addColumnButton.click();
await expect(this.mutationField).toHaveCount(options.expectedColumnCount);

await this.page
.locator('gs-date-range-filter')
.getByRole('combobox')
.last()
.selectOption(options.dateRangeOption);

if (options.lineage && options.lineageFieldPlaceholder) {
const { lineage, lineageFieldPlaceholder } = options;

const lineageFieldLocator = this.page.getByPlaceholder(lineageFieldPlaceholder);
await expect(lineageFieldLocator).toHaveCount(options.expectedColumnCount);

await this.fillLineageField(lineageFieldLocator.last(), lineage);
}

if (options.mutation) {
await this.fillMutationField(this.mutationField.last(), options.mutation);
}

await this.page.getByRole('button', { name: 'Apply filters' }).last().click();
}
}
8 changes: 2 additions & 6 deletions website/tests/CompareToBaselinePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,11 @@ export class CompareToBaselinePage extends CompareVariantsPage {
const { lineage, lineageFieldPlaceholder } = options;

const lineageFieldLocator = this.page.getByPlaceholder(lineageFieldPlaceholder);
await lineageFieldLocator.first().fill(lineage);

const selectedLineage = this.page.getByRole('option', { name: lineage, exact: false });
await selectedLineage.first().click();
await this.fillLineageField(lineageFieldLocator.first(), lineage);
}

if (options.mutation) {
await this.mutationField.first().fill(options.mutation);
await this.mutationField.first().press('Enter');
await this.fillMutationField(this.mutationField.first(), options.mutation);
}
}
}
8 changes: 2 additions & 6 deletions website/tests/CompareVariantsPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,11 @@ export class CompareVariantsPage extends ViewPage {
const lineageFieldLocator = this.page.getByPlaceholder(lineageFieldPlaceholder);
await expect(lineageFieldLocator).toHaveCount(numberMutationFields + 1);

await lineageFieldLocator.last().fill(lineage);

const selectedLineage = this.page.getByRole('option', { name: lineage, exact: false });
await selectedLineage.first().click();
await this.fillLineageField(lineageFieldLocator.last(), lineage);
}

if (options.mutation) {
await this.mutationField.last().fill(options.mutation);
await this.mutationField.last().press('Enter');
await this.fillMutationField(this.mutationField.last(), options.mutation);
}
}
}
13 changes: 12 additions & 1 deletion website/tests/ViewPage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect, type Page } from '@playwright/test';
import { expect, type Locator, type Page } from '@playwright/test';

import { type Organism } from '../src/types/Organism.ts';

Expand All @@ -23,4 +23,15 @@ export abstract class ViewPage {
public async selectDateRange(dateRangeOption: string) {
await this.page.locator('gs-date-range-filter').getByRole('combobox').first().selectOption(dateRangeOption);
}

public async fillLineageField(locator: Locator, lineage: string) {
await locator.fill(lineage);
const selectedLineage = this.page.getByRole('option', { name: lineage, exact: false });
await selectedLineage.first().click();
}

public async fillMutationField(locator: Locator, mutation: string) {
await locator.first().fill(mutation);
await locator.first().press('Enter');
}
}
38 changes: 38 additions & 0 deletions website/tests/compareSideBySide.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { expect } from '@playwright/test';

import { test } from './e2e.fixture.ts';
import { organismOptions, organismsWithView } from './helpers.ts';
import { compareSideBySideViewKey } from '../src/views/viewKeys';

test.describe('The Compare Side-By-Side page', () => {
for (const organism of organismsWithView(compareSideBySideViewKey)) {
test(`should show diagrams after selecting a variant in both columns - ${organism}`, async ({
compareSideBySidePage,
}) => {
const options = organismOptions[organism];

await compareSideBySidePage.goto(organism);
await expect(compareSideBySidePage.removeColumnButton).not.toBeVisible();
await expect(compareSideBySidePage.addColumnButton).toBeVisible();

await expect(compareSideBySidePage.diagramTitle('Prevalence Over Time')).not.toBeVisible();

await compareSideBySidePage.addColumn({
dateRangeOption: 'All times',
expectedColumnCount: 1,
...options,
});
await expect(compareSideBySidePage.removeColumnButton).not.toBeVisible();

await compareSideBySidePage.addColumn({
dateRangeOption: 'All times',
expectedColumnCount: 2,
...options,
});
await expect(compareSideBySidePage.removeColumnButton).toHaveCount(2);

await expect(compareSideBySidePage.diagramTitle('Prevalence Over Time')).toHaveCount(2);
await compareSideBySidePage.expectToSeeNoComponentErrors();
});
}
});
5 changes: 5 additions & 0 deletions website/tests/e2e.fixture.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { test as base } from '@playwright/test';

import { CompareSideBySidePage } from './CompareSideBySidePage.ts';
import { CompareToBaselinePage } from './CompareToBaselinePage.ts';
import { CompareVariantsPage } from './CompareVariantsPage.ts';
import { SequencingEffortsPage } from './SequencingEffortsPage.ts';
Expand All @@ -8,6 +9,7 @@ type E2EFixture = {
compareVariantsPage: CompareVariantsPage;
sequencingEffortsPage: SequencingEffortsPage;
compareToBaselinePage: CompareToBaselinePage;
compareSideBySidePage: CompareSideBySidePage;
};

export const test = base.extend<E2EFixture>({
Expand All @@ -20,4 +22,7 @@ export const test = base.extend<E2EFixture>({
compareToBaselinePage: async ({ page }, use) => {
await use(new CompareToBaselinePage(page));
},
compareSideBySidePage: async ({ page }, use) => {
await use(new CompareSideBySidePage(page));
},
});
2 changes: 2 additions & 0 deletions website/tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ export function organismsWithView<ViewKey extends string>(viewKey: ViewKey): Org

export const organismOptions = {
[Organisms.covid]: { lineage: 'JN.1*', lineageFieldPlaceholder: 'Nextclade pango lineage' },
[Organisms.influenzaA]: { lineage: 'H1', lineageFieldPlaceholder: 'HA subtype' },
[Organisms.h5n1]: { lineage: '2.3.4.4b', lineageFieldPlaceholder: 'Clade' },
[Organisms.h1n1pdm]: { lineage: '6B.1A.5a.2a.1', lineageFieldPlaceholder: 'Clade HA' },
[Organisms.h3n2]: { lineage: '3C.2a1b', lineageFieldPlaceholder: 'Clade HA' },
[Organisms.influenzaB]: { lineage: 'vic', lineageFieldPlaceholder: 'Lineage' },
[Organisms.victoria]: { lineage: 'V1A.3a.2', lineageFieldPlaceholder: 'Clade HA' },
[Organisms.westNile]: { lineage: '1A', lineageFieldPlaceholder: 'Lineage' },
[Organisms.rsvA]: { lineage: 'A.D.5.2', lineageFieldPlaceholder: 'Lineage' },
Expand Down