From 1fd29053177b0d5ba110ef9bb17bb744ccc441db Mon Sep 17 00:00:00 2001 From: Fabian Engelniederhammer Date: Fri, 17 Jan 2025 10:11:07 +0100 Subject: [PATCH] feat(website): allow organisms without sequences --- website/src/components/Edit/EditPage.spec.tsx | 16 ++++- website/src/components/Edit/EditPage.tsx | 60 +++++++++------- .../SequenceDetailsPage/SequenceDataUI.tsx | 1 + .../components/Submission/DataUploadForm.tsx | 72 ++++++++++--------- .../Submission/FileUpload/UploadComponent.tsx | 4 +- .../Submission/SubmissionForm.spec.tsx | 32 ++++++++- .../components/Submission/SubmissionForm.tsx | 3 + .../submission/[groupId]/submit.astro | 4 +- .../edit/[accession]/[version].astro | 2 + website/src/types/backend.ts | 2 +- 10 files changed, 130 insertions(+), 66 deletions(-) diff --git a/website/src/components/Edit/EditPage.spec.tsx b/website/src/components/Edit/EditPage.spec.tsx index 97ae43f265..65e2fa8f10 100644 --- a/website/src/components/Edit/EditPage.spec.tsx +++ b/website/src/components/Edit/EditPage.spec.tsx @@ -6,7 +6,7 @@ import { beforeEach, describe, expect, test } from 'vitest'; import { EditPage } from './EditPage.tsx'; import { defaultReviewData, editableEntry, metadataKey, testAccessToken, testOrganism } from '../../../vitest.setup.ts'; -import type { SequenceEntryToEdit, UnprocessedMetadataRecord } from '../../types/backend.ts'; +import type { UnprocessedMetadataRecord } from '../../types/backend.ts'; import type { ClientConfig } from '../../types/runtimeConfig.ts'; const queryClient = new QueryClient(); @@ -19,7 +19,11 @@ const inputFields = [ }, ]; -function renderEditPage(editedData: SequenceEntryToEdit = defaultReviewData, clientConfig: ClientConfig = dummyConfig) { +function renderEditPage({ + editedData = defaultReviewData, + clientConfig = dummyConfig, + allowSubmissionOfConsensusSequences = true, +} = {}) { render( , ); @@ -51,6 +56,13 @@ describe('EditPage', () => { await userEvent.click(submitButton); }); + test('should render without allowed submission of consensus sequences', () => { + renderEditPage({ allowSubmissionOfConsensusSequences: false }); + + expect(screen.getByText(/Original Data/i)).toBeInTheDocument(); + expectTextInSequenceData.originalMetadata(defaultReviewData.originalData.metadata); + }); + test('should show original data and processed data', () => { renderEditPage(); diff --git a/website/src/components/Edit/EditPage.tsx b/website/src/components/Edit/EditPage.tsx index 991996e78b..f4e61df7a8 100644 --- a/website/src/components/Edit/EditPage.tsx +++ b/website/src/components/Edit/EditPage.tsx @@ -28,6 +28,7 @@ type EditPageProps = { dataToEdit: SequenceEntryToEdit; accessToken: string; inputFields: InputField[]; + allowSubmissionOfConsensusSequences: boolean; }; const logger = getClientLogger('EditPage'); @@ -75,7 +76,8 @@ const InnerEditPage: FC = ({ clientConfig, accessToken, inputFields, -}: EditPageProps) => { + allowSubmissionOfConsensusSequences, +}) => { const [editedMetadata, setEditedMetadata] = useState(mapMetadataToRow(dataToEdit)); const [editedSequences, setEditedSequences] = useState(mapSequencesToRow(dataToEdit)); const [processedSequenceTab, setProcessedSequenceTab] = useState(0); @@ -141,25 +143,31 @@ const InnerEditPage: FC = ({ setEditedMetadata={setEditedMetadata} inputFields={inputFields} /> - + {allowSubmissionOfConsensusSequences && ( + + )} - - - + {allowSubmissionOfConsensusSequences && ( + <> + + + + + )} - {processedSequences.length > 0 && ( + {allowSubmissionOfConsensusSequences && processedSequences.length > 0 && (
{processedSequences.map(({ label }, i) => ( @@ -199,16 +207,18 @@ const InnerEditPage: FC = ({ Submit - + {allowSubmissionOfConsensusSequences && ( + + )}
); diff --git a/website/src/components/SequenceDetailsPage/SequenceDataUI.tsx b/website/src/components/SequenceDetailsPage/SequenceDataUI.tsx index 67b0f9b43d..aa0c90a044 100644 --- a/website/src/components/SequenceDetailsPage/SequenceDataUI.tsx +++ b/website/src/components/SequenceDetailsPage/SequenceDataUI.tsx @@ -96,6 +96,7 @@ export const SequenceDataUI: FC = ({ /> )} + {/* TODO ! */} void; onError: (message: string) => void; + allowSubmissionOfConsensusSequences: boolean; }; const logger = getClientLogger('DataUploadForm'); @@ -84,15 +85,13 @@ const DataUseTerms = ({ const DevExampleData = ({ setExampleEntries, exampleEntries, - metadataFile, - sequenceFile, handleLoadExampleData, + dataIsLoaded, }: { setExampleEntries: (entries: number) => void; exampleEntries: number | undefined; - metadataFile: File | null; - sequenceFile: File | null; handleLoadExampleData: () => void; + dataIsLoaded: boolean; }) => { return (

@@ -108,7 +107,7 @@ const DevExampleData = ({ Load Example Data {' '}
- {metadataFile && sequenceFile && Example data loaded} + {dataIsLoaded && Example data loaded}

); }; @@ -122,9 +121,10 @@ const InnerDataUploadForm = ({ onError, group, referenceGenomeSequenceNames, + allowSubmissionOfConsensusSequences, }: DataUploadFormProps) => { - const [metadataFile, setMetadataFile] = useState(null); - const [sequenceFile, setSequenceFile] = useState(null); + const [metadataFile, setMetadataFile] = useState(undefined); + const [sequenceFile, setSequenceFile] = useState(undefined); const [exampleEntries, setExampleEntries] = useState(10); const { submit, revise, isLoading } = useSubmitFiles(accessToken, organism, clientConfig, onSuccess, onError); @@ -143,10 +143,12 @@ const InnerDataUploadForm = ({ const exampleMetadataContent = action === `submit` ? metadataFileContent : revisedMetadataFileContent; const metadataFile = createTempFile(exampleMetadataContent, 'text/tab-separated-values', 'metadata.tsv'); - const sequenceFile = createTempFile(sequenceFileContent, 'application/octet-stream', 'sequences.fasta'); - setMetadataFile(metadataFile); - setSequenceFile(sequenceFile); + + if (allowSubmissionOfConsensusSequences) { + const sequenceFile = createTempFile(sequenceFileContent, 'application/octet-stream', 'sequences.fasta'); + setSequenceFile(sequenceFile); + } }; const handleSubmit = (event: FormEvent) => { @@ -168,7 +170,7 @@ const InnerDataUploadForm = ({ onError('Please select metadata file'); return; } - if (!sequenceFile) { + if (!sequenceFile && allowSubmissionOfConsensusSequences) { onError('Please select a sequences file'); return; } @@ -201,8 +203,12 @@ const InnerDataUploadForm = ({
-

Sequences and metadata

-

Select your sequence data and metadata files

+

+ {allowSubmissionOfConsensusSequences ? 'Sequences and metadata' : 'Metadata'} +

+

+ Select your {allowSubmissionOfConsensusSequences && 'sequence data and'}metadata files +

{action === 'revise' && ( @@ -250,28 +256,30 @@ const InnerDataUploadForm = ({ .

- {(organism.startsWith('not-aligned-organism') || organism.startsWith('dummy-organism')) && - action === 'submit' && ( - - )} + {organism.startsWith('dummy-organism') && action === 'submit' && ( + + )}
-
- - -
+ {allowSubmissionOfConsensusSequences && ( +
+ + +
+ )}
void; + setFile: (file: File | undefined) => void; name: string; ariaLabel: string; fileKind: FileKind; @@ -37,7 +37,7 @@ export const UploadComponent = ({ }, ); } - setFile(processedFile !== null ? processedFile.inner() : null); + setFile(processedFile !== null ? processedFile.inner() : undefined); rawSetMyFile(processedFile); }, [setFile, rawSetMyFile], diff --git a/website/src/components/Submission/SubmissionForm.spec.tsx b/website/src/components/Submission/SubmissionForm.spec.tsx index ff6b041bc4..b9ae70671c 100644 --- a/website/src/components/Submission/SubmissionForm.spec.tsx +++ b/website/src/components/Submission/SubmissionForm.spec.tsx @@ -1,12 +1,12 @@ import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { toast } from 'react-toastify'; -import { describe, expect, test, vi } from 'vitest'; +import { afterEach, describe, expect, test, vi } from 'vitest'; import { SubmissionForm } from './SubmissionForm'; import { mockRequest, testAccessToken, testConfig, testGroups, testOrganism } from '../../../vitest.setup.ts'; import type { Group, ProblemDetail, SubmissionIdMapping } from '../../types/backend.ts'; -import type { ReferenceGenomesSequenceNames, ReferenceAccession } from '../../types/referencesGenomes.ts'; +import type { ReferenceAccession, ReferenceGenomesSequenceNames } from '../../types/referencesGenomes.ts'; vi.mock('../../api', () => ({ getClientLogger: () => ({ @@ -48,7 +48,7 @@ const defaultReferenceGenomesSequenceNames: ReferenceGenomesSequenceNames = { insdcAccessionFull: [defaultAccession], }; -function renderSubmissionForm() { +function renderSubmissionForm({ allowSubmissionOfConsensusSequences = true } = {}) { return render( , ); } @@ -69,6 +70,10 @@ const testResponse: SubmissionIdMapping[] = [ ]; describe('SubmitForm', () => { + afterEach(() => { + vi.clearAllMocks(); + }); + test('should handle file upload and server response', async () => { mockRequest.backend.submit(200, testResponse); mockRequest.backend.getGroupsOfUser(); @@ -180,4 +185,25 @@ describe('SubmitForm', () => { }); }); } + + test('should accept submission without sequence file for organism that does not allow consensus sequences', async () => { + mockRequest.backend.submit(200, testResponse); + mockRequest.backend.getGroupsOfUser(); + + const { getByLabelText } = renderSubmissionForm({ + allowSubmissionOfConsensusSequences: false, + }); + + await userEvent.upload(getByLabelText(/Metadata File/i), metadataFile); + await userEvent.click( + getByLabelText(/I confirm I have not and will not submit this data independently to INSDC/i), + ); + await userEvent.click( + getByLabelText(/I confirm that the data submitted is not sensitive or human-identifiable/i), + ); + + await waitFor(() => { + expect(toast.error).not.toHaveBeenCalled(); + }); + }); }); diff --git a/website/src/components/Submission/SubmissionForm.tsx b/website/src/components/Submission/SubmissionForm.tsx index bc10c357b2..47a44f25e5 100644 --- a/website/src/components/Submission/SubmissionForm.tsx +++ b/website/src/components/Submission/SubmissionForm.tsx @@ -13,6 +13,7 @@ type SubmissionFormProps = { clientConfig: ClientConfig; group: Group; referenceGenomeSequenceNames: ReferenceGenomesSequenceNames; + allowSubmissionOfConsensusSequences: boolean; }; export const SubmissionForm: FC = ({ @@ -21,6 +22,7 @@ export const SubmissionForm: FC = ({ clientConfig, group, referenceGenomeSequenceNames, + allowSubmissionOfConsensusSequences, }) => { return (
@@ -35,6 +37,7 @@ export const SubmissionForm: FC = ({ onSuccess={() => { window.location.href = routes.userSequenceReviewPage(organism, group.groupId); }} + allowSubmissionOfConsensusSequences={allowSubmissionOfConsensusSequences} />
); diff --git a/website/src/pages/[organism]/submission/[groupId]/submit.astro b/website/src/pages/[organism]/submission/[groupId]/submit.astro index 2b6d10819b..1bc6588b75 100644 --- a/website/src/pages/[organism]/submission/[groupId]/submit.astro +++ b/website/src/pages/[organism]/submission/[groupId]/submit.astro @@ -2,7 +2,7 @@ import { cleanOrganism } from '../../../../components/Navigation/cleanOrganism'; import { SubmissionForm } from '../../../../components/Submission/SubmissionForm'; import SubmissionPageWrapper from '../../../../components/Submission/SubmissionPageWrapper.astro'; -import { getRuntimeConfig } from '../../../../config'; +import { getRuntimeConfig, getSchema } from '../../../../config'; import { getAccessToken } from '../../../../utils/getAccessToken'; import { getReferenceGenomesSequenceNames } from '../../../../utils/search'; import { getGroupsAndCurrentGroup } from '../../../../utils/submissionPages'; @@ -18,6 +18,7 @@ if (!cleanedOrganism) { }; } const referenceGenomeSequenceNames = getReferenceGenomesSequenceNames(cleanedOrganism.key); +const schema = getSchema(cleanedOrganism.key); const groupsResult = await getGroupsAndCurrentGroup(Astro.params, Astro.locals.session); @@ -39,6 +40,7 @@ Astro.response.headers.append('Expires', '0'); organism={organism} clientConfig={clientConfig} group={group} + allowSubmissionOfConsensusSequences={schema.allowSubmissionOfConsensusSequences} client:load /> ), diff --git a/website/src/pages/[organism]/submission/edit/[accession]/[version].astro b/website/src/pages/[organism]/submission/edit/[accession]/[version].astro index 3bacccb847..821162c33c 100644 --- a/website/src/pages/[organism]/submission/edit/[accession]/[version].astro +++ b/website/src/pages/[organism]/submission/edit/[accession]/[version].astro @@ -13,6 +13,7 @@ const { inputFields } = getSchema(organism); const accessToken = getAccessToken(Astro.locals.session)!; const clientConfig = getRuntimeConfig().public; +const schema = getSchema(organism); const dataToEdit = await BackendClient.create().getDataToEdit(organism, accessToken, accession, version); --- @@ -27,6 +28,7 @@ const dataToEdit = await BackendClient.create().getDataToEdit(organism, accessTo dataToEdit={dataToEdit} clientConfig={clientConfig} inputFields={inputFields} + allowSubmissionOfConsensusSequences={schema.allowSubmissionOfConsensusSequences} client:load /> ), diff --git a/website/src/types/backend.ts b/website/src/types/backend.ts index c21e1b27ec..ec4062667f 100644 --- a/website/src/types/backend.ts +++ b/website/src/types/backend.ts @@ -228,7 +228,7 @@ export type SequenceEntryToEdit = z.infer; export const uploadFiles = z.object({ metadataFile: z.instanceof(File), - sequenceFile: z.instanceof(File), + sequenceFile: z.instanceof(File).optional(), }); export const submitFiles = uploadFiles.merge(