Skip to content

Commit f7830ae

Browse files
committed
feat(website): add file uploads to revisions
1 parent efb7303 commit f7830ae

11 files changed

Lines changed: 268 additions & 81 deletions

File tree

integration-tests/tests/pages/edit.page.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import { expect, Page } from '@playwright/test';
22
import { ReviewPage } from './review.page';
3+
import { prepareTmpDirForSingleUpload, uploadFilesFromTmpDir } from '../utils/file-upload-helpers';
34

45
export class EditPage {
56
constructor(private page: Page) {}
67

8+
async goto(organism: string, accession: string, version: number) {
9+
await this.page.goto(`/${organism}/submission/edit/${accession}/${version}`);
10+
}
11+
712
async discardSequenceFile() {
813
await this.page.getByRole('button', { name: 'Discard file' }).click();
914
}
@@ -31,4 +36,14 @@ export class EditPage {
3136
await this.page.waitForURL('**/review', { timeout: 15_000 });
3237
return new ReviewPage(this.page);
3338
}
39+
40+
async uploadExternalFiles(
41+
fileId: string,
42+
fileContents: Record<string, string>,
43+
tmpDir: string,
44+
) {
45+
await prepareTmpDirForSingleUpload(fileContents, tmpDir);
46+
const fileCount = Object.keys(fileContents).length;
47+
await uploadFilesFromTmpDir(this.page, fileId, tmpDir, fileCount);
48+
}
3449
}

integration-tests/tests/pages/revision.page.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { expect, type Page } from '@playwright/test';
2+
import { prepareTmpDirForBulkUpload, uploadFilesFromTmpDir } from '../utils/file-upload-helpers';
23

34
/**
45
* Page object for the sequence revision page.
@@ -121,7 +122,6 @@ export class RevisionPage {
121122
async submitRevision() {
122123
await this.acceptTerms();
123124
await this.clickSubmit();
124-
await this.clickConfirm();
125125
}
126126

127127
/**
@@ -187,4 +187,17 @@ export class RevisionPage {
187187

188188
await this.submitRevision();
189189
}
190+
191+
async uploadExternalFiles(
192+
fileId: string,
193+
fileContents: Record<string, Record<string, string>>,
194+
tmpDir: string,
195+
) {
196+
await prepareTmpDirForBulkUpload(fileContents, tmpDir);
197+
const fileCount = Object.values(fileContents).reduce(
198+
(total, files) => total + Object.keys(files).length,
199+
0,
200+
);
201+
await uploadFilesFromTmpDir(this.page, fileId, tmpDir, fileCount);
202+
}
190203
}

integration-tests/tests/pages/submission.page.ts

Lines changed: 9 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { Page } from '@playwright/test';
22
import { ReviewPage } from './review.page';
3-
import fs from 'fs';
4-
import path from 'path';
53
import Papa from 'papaparse';
64
import { NavigationPage } from './navigation.page';
7-
import { clearTmpDir } from '../utils/tmpdir';
5+
import {
6+
prepareTmpDirForBulkUpload,
7+
prepareTmpDirForSingleUpload,
8+
uploadFilesFromTmpDir,
9+
} from '../utils/file-upload-helpers';
810

911
class SubmissionPage {
1012
protected page: Page;
@@ -68,17 +70,6 @@ class SubmissionPage {
6870
await reviewPage.waitForZeroProcessing();
6971
return reviewPage;
7072
}
71-
72-
protected async _uploadFilesFromTmpDir(testId: string, tmpDir: string, fileCount: number) {
73-
await this.page.getByRole('heading', { name: 'Extra files' }).scrollIntoViewIfNeeded();
74-
// Trigger file upload (don't await) and wait for checkmarks to appear (indicates success)
75-
void this.page.getByTestId(testId).setInputFiles(tmpDir);
76-
return Promise.all(
77-
Array.from({ length: fileCount }, (_, i) =>
78-
this.page.getByText('✓').nth(i).waitFor({ state: 'visible' }),
79-
),
80-
);
81-
}
8273
}
8374

8475
export class SingleSequenceSubmissionPage extends SubmissionPage {
@@ -140,19 +131,9 @@ export class SingleSequenceSubmissionPage extends SubmissionPage {
140131
fileContents: Record<string, string>,
141132
tmpDir: string,
142133
) {
143-
await this._prepareTmpDirWithFiles(fileContents, tmpDir);
134+
await prepareTmpDirForSingleUpload(fileContents, tmpDir);
144135
const fileCount = Object.keys(fileContents).length;
145-
await this._uploadFilesFromTmpDir(fileId, tmpDir, fileCount);
146-
}
147-
148-
private async _prepareTmpDirWithFiles(fileContents: Record<string, string>, tmpDir: string) {
149-
await clearTmpDir(tmpDir);
150-
151-
await Promise.all(
152-
Object.entries(fileContents).map(([fileName, fileContent]) =>
153-
fs.promises.writeFile(path.join(tmpDir, fileName), fileContent),
154-
),
155-
);
136+
await uploadFilesFromTmpDir(this.page, fileId, tmpDir, fileCount);
156137
}
157138

158139
async completeSubmission(
@@ -229,43 +210,16 @@ export class BulkSubmissionPage extends SubmissionPage {
229210
});
230211
}
231212

232-
/**
233-
* The given file contents will be stored in a temp dir and then submitted
234-
* for the given file ID.
235-
* @param fileId For which file ID to upload the files.
236-
* @param fileContents A struct: submissionID -> filename -> filecontent.
237-
* @param tmpDir The temporary directory to use for storing files.
238-
*/
239213
async uploadExternalFiles(
240214
fileId: string,
241215
fileContents: Record<string, Record<string, string>>,
242216
tmpDir: string,
243217
) {
244-
await this._prepareTmpDirWithFiles(fileContents, tmpDir);
218+
await prepareTmpDirForBulkUpload(fileContents, tmpDir);
245219
const fileCount = Object.values(fileContents).reduce(
246220
(total, files) => total + Object.keys(files).length,
247221
0,
248222
);
249-
await this._uploadFilesFromTmpDir(fileId, tmpDir, fileCount);
250-
}
251-
252-
private async _prepareTmpDirWithFiles(
253-
fileContents: Record<string, Record<string, string>>,
254-
tmpDir: string,
255-
) {
256-
await clearTmpDir(tmpDir);
257-
258-
// Create submission directories and write files
259-
const submissionIds = Object.keys(fileContents);
260-
await Promise.all(
261-
submissionIds.map((submissionId) => fs.promises.mkdir(path.join(tmpDir, submissionId))),
262-
);
263-
await Promise.all(
264-
Object.entries(fileContents).flatMap(([submissionId, files]) => {
265-
return Object.entries(files).map(([fileName, fileContent]) =>
266-
fs.promises.writeFile(path.join(tmpDir, submissionId, fileName), fileContent),
267-
);
268-
}),
269-
);
223+
await uploadFilesFromTmpDir(this.page, fileId, tmpDir, fileCount);
270224
}
271225
}

integration-tests/tests/specs/features/file-sharing.spec.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
import { expect } from '@playwright/test';
12
import { test } from '../../fixtures/tmpdir.fixture';
3+
import { EditPage } from '../../pages/edit.page';
4+
import { ReviewPage } from '../../pages/review.page';
5+
import { RevisionPage } from '../../pages/revision.page';
6+
import { SearchPage } from '../../pages/search.page';
27
import { BulkSubmissionPage, SingleSequenceSubmissionPage } from '../../pages/submission.page';
38

49
const ORGANISM_NAME = 'Test organism (with files)';
10+
const ORGANISM_URL_NAME = 'dummy-organism-with-files';
511
const RAW_READS = 'raw_reads';
612
const METADATA_HEADERS = ['submissionId', 'country', 'date'];
713
const COUNTRY_1 = 'Norway';
@@ -88,3 +94,116 @@ test('bulk submit 1 seq with a 35 MB file', async ({ page, groupId, tmpDir }) =>
8894
const searchPage = await reviewPage.releaseAndGoToReleasedSequences();
8995
await searchPage.checkFileContentInModal('cell', COUNTRY_1, LARGE_FILE);
9096
});
97+
98+
const REVISION_METADATA_HEADERS = ['accession', 'submissionId', 'country', 'date'];
99+
const REVISION_FILES = { 'revised_file.txt': 'This is a revised file.' };
100+
const REVISION_FILES_2 = { 'another_file.txt': 'Another revised file content.' };
101+
102+
test('bulk revise 2 seqs with files', async ({ page, groupId, tmpDir }) => {
103+
test.setTimeout(300_000);
104+
105+
const timestamp = Date.now();
106+
const id1 = `bulk-rev-1-${timestamp}`;
107+
const id2 = `bulk-rev-2-${timestamp}`;
108+
const revId1 = `bulk-rev-updated-1-${timestamp}`;
109+
const revId2 = `bulk-rev-updated-2-${timestamp}`;
110+
111+
// Step 1: Submit and release 2 sequences
112+
const submissionPage = new BulkSubmissionPage(page);
113+
await submissionPage.navigateToSubmissionPage(ORGANISM_NAME);
114+
await submissionPage.uploadMetadataFile(METADATA_HEADERS, [
115+
[id1, COUNTRY_1, '2022-01-01'],
116+
[id2, COUNTRY_2, '2022-01-02'],
117+
]);
118+
const reviewPage = await submissionPage.submitAndWaitForProcessingDone();
119+
const searchPage = await reviewPage.releaseAndGoToReleasedSequences();
120+
121+
// Get the accessions of the released sequences
122+
const accessionVersions = await searchPage.waitForSequencesInSearch(2);
123+
const accession1 = accessionVersions.find((av) => av.version === 1)?.accession;
124+
const accession2 = accessionVersions.find(
125+
(av) => av.version === 1 && av.accession !== accession1,
126+
)?.accession;
127+
expect(accession1).toBeDefined();
128+
expect(accession2).toBeDefined();
129+
130+
// Step 2: Bulk revise with files
131+
const revisionPage = new RevisionPage(page);
132+
await revisionPage.goto(ORGANISM_URL_NAME, groupId);
133+
134+
// Upload revision metadata (with accession column)
135+
const revisionMetadata = [
136+
[accession1, revId1, COUNTRY_1, '2022-02-01'],
137+
[accession2, revId2, COUNTRY_2, '2022-02-02'],
138+
];
139+
await page.getByTestId('metadata_file').setInputFiles({
140+
name: 'revision_metadata.tsv',
141+
mimeType: 'text/plain',
142+
buffer: Buffer.from(
143+
[
144+
REVISION_METADATA_HEADERS.join('\t'),
145+
...revisionMetadata.map((r) => r.join('\t')),
146+
].join('\n'),
147+
),
148+
});
149+
150+
// Upload files for each revision
151+
await revisionPage.uploadExternalFiles(
152+
RAW_READS,
153+
{ [revId1]: REVISION_FILES, [revId2]: REVISION_FILES_2 },
154+
tmpDir,
155+
);
156+
await revisionPage.submitRevision();
157+
158+
// Step 3: Verify in review page and release
159+
const reviewPage2 = new ReviewPage(page);
160+
await reviewPage2.waitForZeroProcessing();
161+
await reviewPage2.releaseValidSequences();
162+
163+
const searchPage2 = new SearchPage(page);
164+
await searchPage2.goToReleasedSequences(ORGANISM_URL_NAME, groupId);
165+
await page.goto(page.url() + '?column_submissionId=true');
166+
167+
// Check that revised sequences have the files
168+
await searchPage2.checkFileContentInModal('cell', revId1, REVISION_FILES);
169+
await searchPage2.checkFileContentInModal('cell', revId2, REVISION_FILES_2);
170+
});
171+
172+
test('single revise seq with files via edit page', async ({ page, groupId, tmpDir }) => {
173+
test.setTimeout(300_000);
174+
175+
// Step 1: Submit and release a sequence
176+
const submissionPage = new SingleSequenceSubmissionPage(page);
177+
await submissionPage.navigateToSubmissionPage(ORGANISM_NAME);
178+
await submissionPage.fillSubmissionFormDummyOrganism({
179+
submissionId: 'single-rev',
180+
country: COUNTRY_1,
181+
date: '2023-01-01',
182+
});
183+
const reviewPage = await submissionPage.submitAndWaitForProcessingDone();
184+
const searchPage = await reviewPage.releaseAndGoToReleasedSequences();
185+
186+
// Step 2: Wait until sequence is available and directly go to revise/edit page
187+
const accessionVersions = await searchPage.waitForSequencesInSearch(1);
188+
const editPage = new EditPage(page);
189+
await editPage.goto(
190+
ORGANISM_URL_NAME,
191+
accessionVersions[0].accession,
192+
accessionVersions[0].version,
193+
);
194+
195+
// Step 3: Upload files in the edit page
196+
await editPage.uploadExternalFiles(RAW_READS, REVISION_FILES, tmpDir);
197+
const reviewPage2 = await editPage.submitChanges();
198+
await reviewPage2.waitForZeroProcessing();
199+
await reviewPage2.releaseValidSequences();
200+
201+
// Step 4: Release and verify files
202+
const searchPage2 = new SearchPage(page);
203+
await searchPage2.goToReleasedSequences(ORGANISM_URL_NAME, groupId);
204+
await searchPage2.checkFileContentInModal(
205+
'link',
206+
`${accessionVersions[0].accession}.${accessionVersions[0].version + 1}`,
207+
REVISION_FILES,
208+
);
209+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import { Page } from '@playwright/test';
2+
import fs from 'fs';
3+
import path from 'path';
4+
import { clearTmpDir } from './tmpdir';
5+
6+
/**
7+
* @param fileContents A struct: submissionID -> filename -> filecontent
8+
* @param tmpDir The temporary directory to use for storing files
9+
*/
10+
export async function prepareTmpDirForBulkUpload(
11+
fileContents: Record<string, Record<string, string>>,
12+
tmpDir: string,
13+
) {
14+
await clearTmpDir(tmpDir);
15+
16+
// Create submission directories and write files
17+
const submissionIds = Object.keys(fileContents);
18+
await Promise.all(
19+
submissionIds.map((submissionId) => fs.promises.mkdir(path.join(tmpDir, submissionId))),
20+
);
21+
await Promise.all(
22+
Object.entries(fileContents).flatMap(([submissionId, files]) => {
23+
return Object.entries(files).map(([fileName, fileContent]) =>
24+
fs.promises.writeFile(path.join(tmpDir, submissionId, fileName), fileContent),
25+
);
26+
}),
27+
);
28+
}
29+
30+
/**
31+
* @param fileContents A struct: filename -> filecontent
32+
* @param tmpDir The temporary directory to use for storing files
33+
*/
34+
export async function prepareTmpDirForSingleUpload(
35+
fileContents: Record<string, string>,
36+
tmpDir: string,
37+
) {
38+
await clearTmpDir(tmpDir);
39+
40+
await Promise.all(
41+
Object.entries(fileContents).map(([fileName, fileContent]) =>
42+
fs.promises.writeFile(path.join(tmpDir, fileName), fileContent),
43+
),
44+
);
45+
}
46+
47+
export async function uploadFilesFromTmpDir(
48+
page: Page,
49+
testId: string,
50+
tmpDir: string,
51+
fileCount: number,
52+
) {
53+
await page.getByRole('heading', { name: 'Extra files' }).scrollIntoViewIfNeeded();
54+
// Trigger file upload (don't await) and wait for checkmarks to appear (indicates success)
55+
void page.getByTestId(testId).setInputFiles(tmpDir);
56+
return Promise.all(
57+
Array.from({ length: fileCount }, (_, i) =>
58+
page.getByText('✓').nth(i).waitFor({ state: 'visible' }),
59+
),
60+
);
61+
}

0 commit comments

Comments
 (0)