Skip to content

Commit

Permalink
feat(website): add revise form (#3111)
Browse files Browse the repository at this point in the history
  • Loading branch information
theosanderson authored Oct 30, 2024
1 parent 1b7ce06 commit f4e9dc9
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 24 deletions.
125 changes: 106 additions & 19 deletions website/src/components/Edit/EditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import type { Row } from './InputField.tsx';
import { getClientLogger } from '../../clientLogger.ts';
import { routes } from '../../routes/routes.ts';
import { backendClientHooks } from '../../services/serviceHooks.ts';
import { ACCESSION_FIELD } from '../../settings.ts';
import type { MetadataRecord, ProcessingAnnotationSourceType, SequenceEntryToEdit } from '../../types/backend.ts';
import { ACCESSION_FIELD, SUBMISSION_ID_FIELD } from '../../settings.ts';
import {
type MetadataRecord,
type ProcessingAnnotationSourceType,
type SequenceEntryToEdit,
approvedForReleaseStatus,
} from '../../types/backend.ts';
import { type InputField } from '../../types/config.ts';
import type { ClientConfig } from '../../types/runtimeConfig.ts';
import { createAuthorizationHeader } from '../../utils/createAuthorizationHeader.ts';
Expand Down Expand Up @@ -41,6 +46,31 @@ const SubmissionIdRow: FC<SubmissionProps> = ({ submissionId }) => (
</tr>
);

function createMetadataTsv(metadata: Row[], submissionId: string, accession: string): File {
const tableVals = [
...metadata,
{ key: SUBMISSION_ID_FIELD, value: submissionId },
{ key: ACCESSION_FIELD, value: accession },
];

const header = tableVals.map((row) => row.key).join('\t');

const values = tableVals.map((row) => row.value).join('\t');

const tsvContent = `${header}\n${values}`;

return new File([tsvContent], 'metadata.tsv', { type: 'text/tab-separated-values' });
}

function createSequenceFasta(sequences: Row[], submissionId: string): File {
const fastaContent =
sequences.length === 1
? `>${submissionId}\n${sequences[0].value}`
: sequences.map((sequence) => `>${submissionId}_${sequence.key}\n${sequence.value}`).join('\n');

return new File([fastaContent], 'sequences.fasta', { type: 'text/plain' });
}

const InnerEditPage: FC<EditPageProps> = ({
organism,
dataToEdit,
Expand All @@ -53,10 +83,19 @@ const InnerEditPage: FC<EditPageProps> = ({
const [processedSequenceTab, setProcessedSequenceTab] = useState(0);

const { errorMessage, isErrorOpen, openErrorFeedback, closeErrorFeedback } = useErrorFeedbackState();

const dialogRef = useRef<HTMLDialogElement>(null);

const { mutate: submitEditedSequence } = useSubmitEditedSequence(
const isCreatingRevision = dataToEdit.status === approvedForReleaseStatus;

const { mutate: submitRevision, isLoading: isRevisionLoading } = useSubmitRevision(
organism,
clientConfig,
accessToken,
dataToEdit,
openErrorFeedback,
);

const { mutate: submitEdit, isLoading: isEditLoading } = useSubmitEdit(
organism,
clientConfig,
accessToken,
Expand All @@ -71,29 +110,43 @@ const InnerEditPage: FC<EditPageProps> = ({
};

const submitEditedDataForAccessionVersion = async () => {
const data = {
accession: dataToEdit.accession,
version: dataToEdit.version,
data: {
metadata: editedMetadata.reduce((prev, row) => ({ ...prev, [row.key]: row.value }), {}),
unalignedNucleotideSequences: editedSequences.reduce(
(prev, row) => ({ ...prev, [row.key]: row.value }),
{},
),
},
};
submitEditedSequence(data);
if (isCreatingRevision) {
submitRevision({
metadataFile: createMetadataTsv(editedMetadata, dataToEdit.submissionId, dataToEdit.accession),
sequenceFile: createSequenceFasta(editedSequences, dataToEdit.submissionId),
});
} else {
submitEdit({
accession: dataToEdit.accession,
version: dataToEdit.version,
data: {
metadata: editedMetadata.reduce((prev, row) => ({ ...prev, [row.key]: row.value }), {}),
unalignedNucleotideSequences: editedSequences.reduce(
(prev, row) => ({ ...prev, [row.key]: row.value }),
{},
),
},
});
}
};

const isLoading = isRevisionLoading || isEditLoading;
const processedSequences = useMemo(() => extractProcessedSequences(dataToEdit), [dataToEdit]);
const processedInsertions = useMemo(() => extractInsertions(dataToEdit), [dataToEdit]);

return (
<>
<ManagedErrorFeedback message={errorMessage} open={isErrorOpen} onClose={closeErrorFeedback} />
<div className='flex items-center mb-4'>
<h1 className='title'>
{isCreatingRevision ? 'Create new revision from' : 'Edit'} {dataToEdit.accession}.
{dataToEdit.version}
</h1>
</div>

<div className='flex items-center gap-4'>
<button className='btn normal-case' onClick={handleOpenConfirmationDialog}>
<button className='btn normal-case' onClick={handleOpenConfirmationDialog} disabled={isLoading}>
{isLoading && <span className='loading loading-spinner loading-sm mr-2' />}
Submit
</button>

Expand All @@ -103,6 +156,7 @@ const InnerEditPage: FC<EditPageProps> = ({
title={`Download the original, unaligned sequence${
editedSequences.length > 1 ? 's' : ''
} as provided by the submitter`}
disabled={isLoading}
>
Download Sequence{editedSequences.length > 1 ? 's' : ''}
</button>
Expand All @@ -114,6 +168,7 @@ const InnerEditPage: FC<EditPageProps> = ({
onConfirmation={submitEditedDataForAccessionVersion}
/>
</dialog>

<table className='customTable'>
<tbody className='w-full'>
<Subtitle title='Original Data' bold />
Expand Down Expand Up @@ -141,6 +196,7 @@ const InnerEditPage: FC<EditPageProps> = ({
<Subtitle title='Sequences' />
</tbody>
</table>

{processedSequences.length > 0 && (
<div>
<BoxWithTabsTabBar>
Expand Down Expand Up @@ -171,15 +227,46 @@ const InnerEditPage: FC<EditPageProps> = ({

export const EditPage = withQueryProvider(InnerEditPage);

function useSubmitEditedSequence(
function useSubmitRevision(
organism: string,
clientConfig: ClientConfig,
accessToken: string,
reviewData: SequenceEntryToEdit,
openErrorFeedback: (message: string) => void,
) {
return backendClientHooks(clientConfig).useRevise(
{
params: { organism },
headers: createAuthorizationHeader(accessToken),
},
{
onSuccess: async () => {
await logger.info('Successfully submitted revision for ' + getAccessionVersionString(reviewData));
location.href = routes.userSequenceReviewPage(organism, reviewData.groupId);
},
onError: async (error) => {
const message = `Failed to submit revision for ${getAccessionVersionString(
reviewData,
)} with error '${JSON.stringify(error)})}'`;
await logger.info(message);
openErrorFeedback(message);
},
},
);
}

function useSubmitEdit(
organism: string,
clientConfig: ClientConfig,
accessToken: string,
reviewData: SequenceEntryToEdit,
openErrorFeedback: (message: string) => void,
) {
return backendClientHooks(clientConfig).useSubmitReviewedSequence(
{ headers: createAuthorizationHeader(accessToken), params: { organism } },
{
headers: createAuthorizationHeader(accessToken),
params: { organism },
},
{
onSuccess: async () => {
await logger.info('Successfully submitted edited data ' + getAccessionVersionString(reviewData));
Expand Down
9 changes: 9 additions & 0 deletions website/src/components/SequenceDetailsPage/SequenceDataUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ export const SequenceDataUI: FC<Props> = ({
/>
)}

<a
href={routes.editPage(organism, {
accession: accessionVersion.split('.')[0],
version: parseInt(accessionVersion.split('.')[1], 10),
})}
className='btn btn-sm mr-3'
>
Revise this sequence
</a>
<RevokeButton
organism={organism}
clientConfig={clientConfig}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@ const dataToEdit = await BackendClient.create().getDataToEdit(organism, accessTo
---

<BaseLayout title={`Edit ${accession}.${version}`}>
<div class='flex items-center mb-4'>
<h1 class='title'>
Edit {accession}.{version}
</h1>
</div>
{
dataToEdit.match(
(dataToEdit) => (
Expand Down

0 comments on commit f4e9dc9

Please sign in to comment.