Skip to content
Draft
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
62 changes: 62 additions & 0 deletions website/src/components/LoadingListener.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
interface Props {
count: number;
}

const { count } = Astro.props;
---

<div id='loading-listener'>
<div class='loading-element'>
<div class='flex flex-col gap-2'>
<div>Loading data</div>
<progress class='progress progress-primary w-56' value='0' max={count}></progress>
</div>
</div>
<div class='loading-content hidden'>
<slot />
</div>
</div>

<script define:vars={{ count }} is:inline>
let received = 0;
const element = document.getElementById('loading-listener');
element?.scrollIntoView({ behavior: 'smooth' }); // prevent default scrolling to the wrong position of the anker
const loadingElement = element.querySelector('.loading-element');
const loadingContent = element.querySelector('.loading-content');
const progressBar = element.querySelector('.progress');

function handleFinishedLoading() {
received++;
progressBar.value = received;

setTimeout(() => {
// Also show finished loading
if (received === count) {
const hash = window.location.hash.substring(1);

if (hash) {
const target = document.getElementById(hash);

loadingElement.classList.add('hidden');
loadingContent.classList.remove('hidden');
setTimeout(() => {
// wait for rendering
target?.scrollIntoView({ behavior: 'smooth' });
}, 100);

if (element) {
element.removeEventListener('gs-component-finished-loading', handleFinishedLoading);
}
} else {
loadingElement.classList.add('hidden');
loadingContent.classList.remove('hidden');
}
}
}, 1000);
}

if (element) {
element.addEventListener('gs-component-finished-loading', handleFinishedLoading);
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { type OrganismViewKey, type OrganismWithViewKey } from '../../../views/r
import { ServerSide } from '../../../views/serverSideRouting';
import { singleVariantViewKey } from '../../../views/viewKeys';
import ComponentsGrid from '../../ComponentsGrid.astro';
import LoadingListener from '../../LoadingListener.astro';
import GsAggregate from '../../genspectrum/GsAggregate.astro';
import GsMutations from '../../genspectrum/GsMutations.astro';
import GsMutationsOverTime from '../../genspectrum/GsMutationsOverTime.astro';
Expand Down Expand Up @@ -58,6 +59,9 @@ const downloadLinks = noVariantSelected
downloadFileBasename: `${organism}_${sanitizeForFilename(displayName)}_accessions`,
},
];

const count =
view.organismConstants.aggregatedVisualizations.singleVariant.length + 6 + (subdivisionField !== undefined ? 1 : 0);
---

<SingleVariantOrganismPageLayout view={view} downloadLinks={downloadLinks}>
Expand All @@ -71,75 +75,77 @@ const downloadLinks = noVariantSelected
<AnalyzeSingleVariantSelectorFallback slot='fallback' />
</SingleVariantPageStateSelector>

<div class='mx-[8px] flex flex-col gap-y-6' slot='dataDisplay'>
{
noVariantSelected && (
<SelectVariant>
<QuickstartLinks view={view} />
</SelectVariant>
)
}
<GsStatistics numeratorFilter={variantLapisFilter} denominatorFilter={datasetLapisFilter} />
<ComponentsGrid>
<GsPrevalenceOverTime
numeratorFilters={[
{
displayName: '',
lapisFilter: variantLapisFilter,
},
]}
denominatorFilter={datasetLapisFilter}
lapisDateField={view.organismConstants.mainDateField}
granularity={timeGranularity}
pageSize={10}
/>
<GsMutations
<LoadingListener count={count} slot='dataDisplay'>
<div class='mx-[8px] flex flex-col gap-y-6'>
{
noVariantSelected && (
<SelectVariant>
<QuickstartLinks view={view} />
</SelectVariant>
)
}
<GsStatistics numeratorFilter={variantLapisFilter} denominatorFilter={datasetLapisFilter} />
<ComponentsGrid>
<GsPrevalenceOverTime
numeratorFilters={[
{
displayName: '',
lapisFilter: variantLapisFilter,
},
]}
denominatorFilter={datasetLapisFilter}
lapisDateField={view.organismConstants.mainDateField}
granularity={timeGranularity}
pageSize={10}
/>
<GsMutations
lapisFilter={variantLapisFilter}
baselineLapisFilter={datasetLapisFilter}
sequenceType='nucleotide'
pageSize={10}
/>
<GsMutations
lapisFilter={variantLapisFilter}
baselineLapisFilter={datasetLapisFilter}
sequenceType='amino acid'
pageSize={10}
/>
{
subdivisionField !== undefined && (
<GsAggregate
title={subdivisionLabel}
fields={[subdivisionField]}
lapisFilter={variantLapisFilter}
views={[views.table, views.bar]}
pageSize={10}
/>
)
}

{
view.organismConstants.aggregatedVisualizations.singleVariant.map(({ label, fields, views }) => (
<GsAggregate
title={label}
fields={fields}
lapisFilter={variantLapisFilter}
views={views}
pageSize={10}
/>
))
}
</ComponentsGrid>
<GsMutationsOverTime
lapisFilter={variantLapisFilter}
baselineLapisFilter={datasetLapisFilter}
sequenceType='nucleotide'
pageSize={10}
granularity={timeGranularity}
lapisDateField={view.organismConstants.mainDateField}
/>
<GsMutations
<GsMutationsOverTime
lapisFilter={variantLapisFilter}
baselineLapisFilter={datasetLapisFilter}
sequenceType='amino acid'
pageSize={10}
granularity={timeGranularity}
lapisDateField={view.organismConstants.mainDateField}
/>
{
subdivisionField !== undefined && (
<GsAggregate
title={subdivisionLabel}
fields={[subdivisionField]}
lapisFilter={variantLapisFilter}
views={[views.table, views.bar]}
pageSize={10}
/>
)
}

{
view.organismConstants.aggregatedVisualizations.singleVariant.map(({ label, fields, views }) => (
<GsAggregate
title={label}
fields={fields}
lapisFilter={variantLapisFilter}
views={views}
pageSize={10}
/>
))
}
</ComponentsGrid>
<GsMutationsOverTime
lapisFilter={variantLapisFilter}
sequenceType='nucleotide'
granularity={timeGranularity}
lapisDateField={view.organismConstants.mainDateField}
/>
<GsMutationsOverTime
lapisFilter={variantLapisFilter}
sequenceType='amino acid'
granularity={timeGranularity}
lapisDateField={view.organismConstants.mainDateField}
/>
</div>
</div>
</LoadingListener>
</SingleVariantOrganismPageLayout>
4 changes: 4 additions & 0 deletions website/tests/ViewPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ export abstract class ViewPage {
public async selectDateRange(dateRangeOption: string) {
await this.page.locator('gs-date-range-filter').getByRole('combobox').first().selectOption(dateRangeOption);
}

public async waitForLoading() {
await this.page.waitForSelector('[aria-label="Loading"]', { state: 'detached' });
}
}
15 changes: 11 additions & 4 deletions website/tests/mainPage.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ test.describe('Main page', () => {
});

allOrganisms.forEach((organism) => {
test(`should navigate to all views for ${organism}`, async ({ page }) => {
for (const { viewKey, linkName, title, expectedHeadline } of views) {
views.forEach((view) => {
const { viewKey, linkName, title, expectedHeadline } = view;
test(`should navigate to views ${title} for ${organism}`, async ({ page }) => {
if (!ServerSide.routing.isOrganismWithViewKey(organism, viewKey)) {
continue;
test.skip();
return;
}

const organismName = organismConfig[organism].label;
Expand All @@ -68,11 +70,16 @@ test.describe('Main page', () => {
.locator('..')
.getByText(linkName, { exact: true })
.click();
// wait for the page to load
await page.waitForSelector('[aria-label="Loading"]', { state: 'detached' });
await page.waitForSelector('text="Loading data"', { state: 'hidden' });
await page.waitForTimeout(10);

await expect(page.locator('text=Error -')).not.toBeVisible();
await expect(page.locator('text=Something went wrong')).not.toBeVisible();
await expect(page).toHaveTitle(`${title} | ${organismName} | GenSpectrum`);
await expect(page.getByRole('heading', { name: expectedHeadline }).first()).toBeVisible();
}
});
});
});
});
Loading