Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Waste water data submission #448

Merged
merged 11 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions .env.schema
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ NEXT_PUBLIC_EGO_API_URL=
NEXT_PUBLIC_EGO_CLIENT_ID=
NEXT_PUBLIC_KEYCLOAK_API_URL=

# ###### Environmental submission
NEXT_PUBLIC_ENVIRONMENTAL_SUBMISSION_API_URL=
NEXT_PUBLIC_ENVIRONMENTAL_SUBMISSION_CATEGORY_ID=
Comment on lines +32 to +33
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

environment variables to set up Submission service

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what's a category id in this context?

Copy link

@joneubank joneubank Jan 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lyric data category ID. Lyric is built to manage data of multiple data-dictionaries and multiple submitting projects, so when submitting we need to identify both which data category (data-dictionary) we are submitting for, and what our project ID is. Not sure if we have a separate env var for the project ID or not...


# ######## Muse
# for dev work, remember to add "http://localhost:3000" to Muse's CORS allowed domains (i.e. )
NEXT_PUBLIC_MUSE_API_URL=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import { css, useTheme } from '@emotion/react';
import Router from 'next/router';
import { ReactElement, useEffect, useReducer, useState } from 'react';
import urlJoin from 'url-join';

import { ButtonElement as Button } from '@/components/Button';
import ErrorNotification from '@/components/ErrorNotification';
Expand Down Expand Up @@ -80,7 +81,11 @@ const NewSubmissions = (): ReactElement => {

default: {
response.submissionId
? Router.push(getInternalLink({ path: `submission/${response.submissionId}` }))
? Router.push(
getInternalLink({
path: urlJoin('submission', 'clinical', response.submissionId),
}),
)
: console.log('Unhandled response:', response);
return Promise.resolve();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { css } from '@emotion/react';
import { format } from 'date-fns';
import { ReactElement } from 'react';
import { Column } from 'react-table';
import urljoin from 'url-join';

import { numberSort, uuidSort } from '@/components/GenericTable/helpers';
import StyledLink from '@/components/Link';
Expand All @@ -32,7 +33,9 @@ const columnData: Column<Record<string, unknown>>[] = [
{
accessor: 'submissionId',
Cell: ({ value }: { value: unknown }) => (
<StyledLink href={getInternalLink({ path: `/submission/${value}` })}>
<StyledLink
href={getInternalLink({ path: urljoin('submission', 'clinical', String(value)) })}
>
{value as string}
</StyledLink>
),
Expand Down
40 changes: 9 additions & 31 deletions components/pages/submission/Clinical/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright (c) 2021 The Ontario Institute for Cancer Research. All rights reserved
* Copyright (c) 2024 The Ontario Institute for Cancer Research. All rights reserved
*
* This program and the accompanying materials are made available under the terms of
* the GNU Affero General Public License v3.0. You should have received a copy of the
Expand All @@ -19,38 +19,16 @@
*
*/

import { css, useTheme } from '@emotion/react';
import { ReactElement } from 'react';

import defaultTheme from '@/components/theme';
import PageLayout from '@/components/PageLayout';

import NewSubmissions from './NewSubmissions';
import PreviousSubmissions from './PreviousSubmissions';
import PageContent from './PageContent';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we name this component? It is an extremely vague component name.


const EnvironmentalDataSubmissionPage = (): ReactElement => {
const theme: typeof defaultTheme = useTheme();
const ClinicalDataSubmissionPage = (): ReactElement => (
<PageLayout subtitle="Submission Dashboard">
<PageContent />
</PageLayout>
);

return (
<>
<h1 className="view-title">Clinical Case Submissions</h1>

<section
css={css`
display: flex;
padding: 40px 0 calc(${theme.dimensions.footer.height}px + 30px);
position: relative;

> * {
flex-basis: 50%;
padding: 0 30px;
}
`}
>
<PreviousSubmissions />
<NewSubmissions />
</section>
</>
);
};

export default EnvironmentalDataSubmissionPage;
export default ClinicalDataSubmissionPage;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright (c) 2021 The Ontario Institute for Cancer Research. All rights reserved
* Copyright (c) 2024 The Ontario Institute for Cancer Research. All rights reserved
*
* This program and the accompanying materials are made available under the terms of
* the GNU Affero General Public License v3.0. You should have received a copy of the
Expand Down Expand Up @@ -48,7 +48,7 @@ const DropZone = ({
isDragActive,
// isFileTooLarge,
} = useDropzone({
accept: '.fa,.gz,.fasta,.tsv,text/tab-separated-values',
accept: '.csv',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be valid/desirable to also accept tsv?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean to convert it to .csv for them? Or is this expecting that they may submit .tsv in the future and we should accept that?

disabled,
onDrop: useCallback(
(acceptedFiles: File[]) =>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright (c) 2021 The Ontario Institute for Cancer Research. All rights reserved
* Copyright (c) 2024 The Ontario Institute for Cancer Research. All rights reserved
*
* This program and the accompanying materials are made available under the terms of
* the GNU Affero General Public License v3.0. You should have received a copy of the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright (c) 2021 The Ontario Institute for Cancer Research. All rights reserved
* Copyright (c) 2024 The Ontario Institute for Cancer Research. All rights reserved
*
* This program and the accompanying materials are made available under the terms of
* the GNU Affero General Public License v3.0. You should have received a copy of the
Expand Down
111 changes: 42 additions & 69 deletions components/pages/submission/Environmental/NewSubmissions/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright (c) 2021 The Ontario Institute for Cancer Research. All rights reserved
* Copyright (c) 2024 The Ontario Institute for Cancer Research. All rights reserved
*
* This program and the accompanying materials are made available under the terms of
* the GNU Affero General Public License v3.0. You should have received a copy of the
Expand All @@ -22,14 +22,15 @@
import { css, useTheme } from '@emotion/react';
import Router from 'next/router';
import { ReactElement, useEffect, useReducer, useState } from 'react';
import urlJoin from 'url-join';

import { ButtonElement as Button } from '@/components/Button';
import ErrorNotification from '@/components/ErrorNotification';
import StyledLink from '@/components/Link';
import { LoaderWrapper } from '@/components/Loader';
import defaultTheme from '@/components/theme';
import useAuthContext from '@/global/hooks/useAuthContext';
import useMuseData from '@/global/hooks/useMuseData';
import useEnvironmentalData from '@/global/hooks/useEnvironmentalData';
import getInternalLink from '@/global/utils/getInternalLink';

import DropZone from './DropZone';
Expand All @@ -46,44 +47,55 @@ const NewSubmissions = (): ReactElement => {
const [thereAreFiles, setThereAreFiles] = useState(false);
const [uploadError, setUploadError] = useState(noUploadError);
const [validationState, validationDispatch] = useReducer(validationReducer, validationParameters);
const { oneTSV, oneOrMoreFasta, readyToUpload } = validationState;
const { oneOrMoreCsv, readyToUpload } = validationState;

const { awaitingResponse, fetchMuseData } = useMuseData('NewSubmissions');
const { awaitingResponse, submitData } = useEnvironmentalData('NewSubmissions');

const handleSubmit = () => {
if (thereAreFiles && token && userHasWriteScopes) {
const formData = new FormData();

// if many TSV are available, submit only the first one along with all fastas
const selectedTSV = oneTSV.slice(-1)[0];
formData.append('files', selectedTSV, selectedTSV.name);
oneOrMoreFasta.forEach((fasta) => formData.append('files', fasta, fasta.name));
oneOrMoreCsv.forEach((csvFile) => {
// TODO: Fine grained permissions. Check if user has permission to upload Environmental data to this organization
// Taking the organization name from the filename
const organizationName = csvFile.name.split('.')[0].toUpperCase();
formData.append('organization', organizationName);
// Submission service expects a file with the name of the schema it represents
formData.append('files', csvFile, 'sample.csv');
});

return fetchMuseData('submissions', { body: formData, method: 'POST' }).then((response) => {
return submitData({ body: formData }).then((response) => {
switch (response.status) {
case 'BAD_REQUEST': {
case 'INVALID_FILE_EXTENSION':
case 'FILE_READ_ERROR':
case 'UNRECOGNIZED_HEADER':
case 'MISSING_REQUIRED_HEADER': {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we communicate the specific error?

setUploadError({
...response,
status: 'Your submission has errors and cannot be processed.',
});
return Promise.resolve();
}

case 'INTERNAL_SERVER_ERROR': {
case 'PROCESSING': {
response.submissionId
? Router.push(
getInternalLink({
path: urlJoin('submission', 'environmental', response.submissionId.toString()),
}),
)
: console.log('Unhandled response:', response);
return Promise.resolve();
}

default: {
console.error(response);
setUploadError({
status: 'Internal server error',
message: 'Your upload request has failed. Please try again later.',
});
return Promise.resolve();
}

default: {
response.submissionId
? Router.push(getInternalLink({ path: `submission/${response.submissionId}` }))
: console.log('Unhandled response:', response);
return Promise.resolve();
}
}
});
}
Expand All @@ -93,9 +105,7 @@ const NewSubmissions = (): ReactElement => {

useEffect(() => {
setUploadError(noUploadError);
setThereAreFiles(
validationState.oneTSV.length > 0 || validationState.oneOrMoreFasta.length > 0,
);
setThereAreFiles(validationState.oneOrMoreCsv.length > 0);
}, [validationState]);

const handleClearAll = () => {
Expand Down Expand Up @@ -136,14 +146,11 @@ const NewSubmissions = (): ReactElement => {
<h1 className="view-title">Start a New Submission</h1>

<p>
Virus metadata is submitted as a <span className="code">.tsv</span> file. Viral genome data
must be submitted as a <span className="code">.fasta</span> file. Up to 5000 samples can be
submitted in a single submission, but note that the larger the file the longer the
submission will take. FASTA files are accepted individually, or as a single concatenated
FASTA containing all samples in one file.
Waste water metadata is submitted as a <span className="code">.csv</span> file .The file
name must match the Study name for the Submission. Multiple files are accepted.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good opportunity for an example valid file name?

</p>

<h2>To format your viral sequence metadata:</h2>
<h2>To format your waste water sequence metadata:</h2>

<ol>
<li>
Expand All @@ -153,17 +160,8 @@ const NewSubmissions = (): ReactElement => {
rel="noopener noreferrer"
target="_blank"
>
metadata TSV Template
metadata CSV Template
</StyledLink>{' '}
for the viral sequence metadata and populate it with accepted values for each field. A
reference of the accepted values can be found{' '}
<StyledLink
href="https://github.com/Public-Health-Bioinformatics/DataHarmonizer/blob/master/template/canada_covid19/SOP.pdf"
rel="noopener noreferrer"
target="_blank"
>
in this resource
</StyledLink>
.
</li>
<li>
Expand All @@ -175,30 +173,14 @@ const NewSubmissions = (): ReactElement => {
DataHarmonizer
</StyledLink>{' '}
is a tool that can be used to help validate the accepted values for each field in your
metadata TSV locally before submitting. Download the tool and follow the instructions on
metadata CSV locally before submitting. Download the tool and follow the instructions on
the Github repository to pre-validate each field in your metadata before submission.
</li>
<li>
If you are using Excel or Google sheets, make sure all characters are UTF-8 encoded.
</li>
</ol>

<h2>To format your viral sequence files:</h2>

<ol>
<li>
Make sure they have the file extension <span className="code">.fasta</span>,{' '}
<span className="code">.fa</span>, or zipped fastas in <span className="code">.gz</span>{' '}
format.
</li>
<li>
Each sequence must be preceded be a description line, beginning with a &gt;. The
description line should include &gt;hCoV-19/<span className="code">country</span>/
<span className="code">identifier</span>/<span className="code">year</span> sequenced.
This identifier must match exactly the "fasta header name" column in the TSV file.
</li>
</ol>

<DropZone
disabled={!userHasWriteScopes}
validationState={validationState}
Expand Down Expand Up @@ -254,7 +236,7 @@ const NewSubmissions = (): ReactElement => {
loading={awaitingResponse}
message={
<>
Currently validating metadata and sequencing files.
Currently validating metadata files.
<br />
Do not navigate away from this browser window.
</>
Expand Down Expand Up @@ -331,21 +313,12 @@ const NewSubmissions = (): ReactElement => {
<tbody>
{thereAreFiles ? (
<>
{oneTSV.map((tsv, index) => (
// when more than one, all but the last one will get crossed out on render
<FileRow
active={index === oneTSV.length - 1}
file={tsv}
key={tsv.name}
handleRemove={handleRemoveThis(tsv)}
/>
))}
{oneOrMoreFasta.map((fasta: File) => (
{oneOrMoreCsv.map((csvFile: File) => (
<FileRow
active={true}
file={fasta}
key={fasta.name}
handleRemove={handleRemoveThis(fasta)}
file={csvFile}
key={csvFile.name}
handleRemove={handleRemoveThis(csvFile)}
/>
))}
</>
Expand Down Expand Up @@ -377,7 +350,7 @@ const NewSubmissions = (): ReactElement => {
margin-left: 10px;
`}
>
You must submit only one metadata TSV file and at least one FASTA file.
You must submit at least one CSV file.
</p>
)}
</td>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright (c) 2021 The Ontario Institute for Cancer Research. All rights reserved
* Copyright (c) 2024 The Ontario Institute for Cancer Research. All rights reserved
*
* This program and the accompanying materials are made available under the terms of
* the GNU Affero General Public License v3.0. You should have received a copy of the
Expand Down Expand Up @@ -50,19 +50,18 @@ export type ReaderCallbackType = (result: string | ArrayBuffer | null) => void;

export type ValidationActionType =
| {
type: 'add fasta' | 'add tsv';
type: 'add csv';
file: File;
}
| {
type: 'remove fasta' | 'remove tsv';
type: 'remove csv';
file: string;
}
| {
type: 'clear all' | 'is ready' | 'not ready';
};

export type ValidationParametersType = {
oneTSV: File[];
oneOrMoreFasta: File[];
oneOrMoreCsv: File[];
readyToUpload: boolean;
};
Loading