Skip to content

Commit

Permalink
change gql mutation uploads to use fetch (#146)
Browse files Browse the repository at this point in the history
* make reusuable program path func

* add form helper

* abstract fetch with options

* use react query fetch instead of apollo gql

* account for FileList

* remove credentials include

* add auth and form data creation fix

---------

Co-authored-by: Ciaran Schutte <[email protected]>
  • Loading branch information
ciaranschutte and Ciaran Schutte authored Jan 13, 2024
1 parent d350459 commit 0e0892f
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 46 deletions.
14 changes: 6 additions & 8 deletions src/app/(post-login)/submission/components/ProgramMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,8 @@ import {
PROGRAM_DASHBOARD_PATH,
PROGRAM_MANAGE_PATH,
PROGRAM_SAMPLE_REGISTRATION_PATH,
PROGRAM_SHORT_NAME_PATH,
} from '@/global/constants';
import { notNull } from '@/global/utils';
import { getProgramPath, notNull } from '@/global/utils';
import { css } from '@/lib/emotion';
import { Icon, MenuItem } from '@icgc-argo/uikit';
import orderBy from 'lodash/orderBy';
Expand Down Expand Up @@ -246,7 +245,6 @@ const MenuContent = ({ programName }: { programName: string }) => {
const { egoJwt } = useAuthContext();
const pathname = usePathname();
const pathnameLastSegment = pathname.split('/').at(-1);
const getProgramPath = (path: string) => path.replace(PROGRAM_SHORT_NAME_PATH, programName);

const { isDisabled: isSubmissionSystemDisabled } = useSubmissionSystemStatus();

Expand Down Expand Up @@ -279,15 +277,15 @@ const MenuContent = ({ programName }: { programName: string }) => {
return (
<>
{/** Dashboard */}
<Link href={getProgramPath(PROGRAM_DASHBOARD_PATH)}>
<Link href={getProgramPath(PROGRAM_DASHBOARD_PATH, programName)}>
<MenuItem level={3} content="Dashboard" selected={pathnameLastSegment === 'dashboard'} />
</Link>

{/** Register Samples */}
{userCanSubmitData &&
renderDataSubmissionLinks(
getProgramPath(PROGRAM_SAMPLE_REGISTRATION_PATH),
getProgramPath(PROGRAM_CLINICAL_SUBMISSION_PATH),
getProgramPath(PROGRAM_SAMPLE_REGISTRATION_PATH, programName),
getProgramPath(PROGRAM_CLINICAL_SUBMISSION_PATH, programName),
registrationStatusIcon,
isSubmissionSystemDisabled,
pathnameLastSegment,
Expand All @@ -296,7 +294,7 @@ const MenuContent = ({ programName }: { programName: string }) => {

{/** Submitted Data */}
{userCanViewData && (
<Link href={getProgramPath(PROGRAM_CLINICAL_DATA_PATH)}>
<Link href={getProgramPath(PROGRAM_CLINICAL_DATA_PATH, programName)}>
<MenuItem
level={3}
content={
Expand All @@ -312,7 +310,7 @@ const MenuContent = ({ programName }: { programName: string }) => {

{/** Manage Program */}
{userCanManageProgram && (
<Link href={getProgramPath(PROGRAM_MANAGE_PATH)}>
<Link href={getProgramPath(PROGRAM_MANAGE_PATH, programName)}>
<MenuItem
level={3}
content="Manage Program"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,24 @@ import CLEAR_CLINICAL_SUBMISSION from '@/app/gql/clinical/CLEAR_CLINICAL_SUBMISS
import CLINICAL_SUBMISSION_QUERY from '@/app/gql/clinical/CLINICAL_SUBMISSION_QUERY';
import SIGN_OFF_SUBMISSION_MUTATION from '@/app/gql/clinical/SIGN_OFF_SUBMISSION_MUTATION';
import VALIDATE_SUBMISSION_MUTATION from '@/app/gql/clinical/VALIDATE_SUBMISSION_MUTATION';
import UPLOAD_CLINICAL_SUBMISSION_MUTATION from '@/app/gql/gateway/UPLOAD_CLINICAL_SUBMISSION_MUTATION';
import { useAppConfigContext } from '@/app/hooks/AppProvider';
import { useAuthContext } from '@/app/hooks/AuthProvider';
import { useGlobalLoader } from '@/app/hooks/GlobalLoaderProvider';
import { useToaster } from '@/app/hooks/ToastProvider';
import { useClinicalQuery } from '@/app/hooks/useApolloQuery';
import useCommonToasters from '@/app/hooks/useCommonToasters';
import { useSubmissionSystemStatus } from '@/app/hooks/useSubmissionSystemStatus';
import useUrlQueryState from '@/app/hooks/useURLQueryState';
import useUserConfirmationModalState from '@/app/hooks/useUserConfirmationModalState';
import { PROGRAM_DASHBOARD_PATH, PROGRAM_SHORT_NAME_PATH } from '@/global/constants';
import { displayDateAndTime, sleep, toDisplayError } from '@/global/utils';
import {
PROGRAM_DASHBOARD_PATH,
PROGRAM_SHORT_NAME_PATH,
UPLOAD_CLINICAL_DATA,
} from '@/global/constants';
import { displayDateAndTime, getProgramPath, sleep, toDisplayError } from '@/global/utils';
import { createFileFormData, uploadFileRequest } from '@/global/utils/form';
import { css } from '@/lib/emotion';
import { useMutation } from '@apollo/client';
import { useMutation as useGQLMutation } from '@apollo/client';
import {
ColumnDef,
NOTIFICATION_VARIANTS,
Expand All @@ -53,6 +59,8 @@ import {
} from '@icgc-argo/uikit';
import { usePathname, useRouter } from 'next/navigation';
import { useEffect, useMemo, useState } from 'react';
import { useMutation } from 'react-query';
import urlJoin from 'url-join';
import FileError from '../../../../../components/FileError';
import FilesNavigator from './components/FilesNavigator';
import Header from './components/Header';
Expand All @@ -76,6 +84,8 @@ const ClinicalSubmission = ({ shortName }: { shortName: string }) => {
const pathname = usePathname();
const { setGlobalLoading } = useGlobalLoader();
const toaster = useToaster();
const { CLINICAL_API_ROOT } = useAppConfigContext();
const { egoJwt } = useAuthContext();

useEffect(() => {
const defaultQuery = '?tab=donor';
Expand All @@ -100,28 +110,32 @@ const ClinicalSubmission = ({ shortName }: { shortName: string }) => {
});

// mutations
const [clearClinicalSubmission] = useMutation(CLEAR_CLINICAL_SUBMISSION);
const [uploadClinicalSubmission] = useMutation(UPLOAD_CLINICAL_SUBMISSION_MUTATION, {
onError: () => {
commonToaster.unknownError();
},
});
const [clearClinicalSubmission] = useGQLMutation(CLEAR_CLINICAL_SUBMISSION);

const handleSubmissionFilesUpload = (files: FileList) =>
uploadClinicalSubmission({
variables: {
programShortName: shortName,
files,
const uploadClinicalSubmission = useMutation(
(formData) => {
const url = urlJoin(CLINICAL_API_ROOT, getProgramPath(UPLOAD_CLINICAL_DATA, shortName));
return uploadFileRequest(url, formData, egoJwt);
},
{
onError: () => {
commonToaster.unknownError();
},
});
},
);

const handleSubmissionFilesUpload = (files: FileList) => {
const fileFormData = createFileFormData(files, 'clinicalFiles');
return uploadClinicalSubmission.mutate(fileFormData);
};

const [validateSubmission] = useMutation(VALIDATE_SUBMISSION_MUTATION, {
const [validateSubmission] = useGQLMutation(VALIDATE_SUBMISSION_MUTATION, {
onCompleted: () => {
//setSelectedClinicalEntityType(defaultClinicalEntityType);
},
});

const [signOffSubmission] = useMutation(SIGN_OFF_SUBMISSION_MUTATION);
const [signOffSubmission] = useGQLMutation(SIGN_OFF_SUBMISSION_MUTATION);

const { isDisabled: isSubmissionSystemDisabled } = useSubmissionSystemStatus();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,17 @@ import { BreadcrumbTitle, HelpLink, PageHeader } from '@/app/components/PageHead
import CLEAR_CLINICAL_REGISTRATION_MUTATION from '@/app/gql/clinical/CLEAR_CLINICAL_REGISTRATION_MUTATION';
import CLINICAL_SCHEMA_VERSION from '@/app/gql/clinical/CLINICAL_SCHEMA_VERSION';
import GET_REGISTRATION_QUERY from '@/app/gql/clinical/GET_REGISTRATION_QUERY';
import UPLOAD_REGISTRATION_MUTATION from '@/app/gql/gateway/UPLOAD_REGISTRATION_MUTATION';
import { useAppConfigContext } from '@/app/hooks/AppProvider';
import { useAuthContext } from '@/app/hooks/AuthProvider';
import { useToaster } from '@/app/hooks/ToastProvider';
import { useClinicalQuery } from '@/app/hooks/useApolloQuery';
import useCommonToasters from '@/app/hooks/useCommonToasters';
import { useSubmissionSystemStatus } from '@/app/hooks/useSubmissionSystemStatus';
import { notNull } from '@/global/utils';
import { UPLOAD_REGISTRATION } from '@/global/constants';
import { getProgramPath, notNull } from '@/global/utils';
import { createFileFormData, uploadFileRequest } from '@/global/utils/form';
import { css } from '@/lib/emotion';
import { useMutation, useQuery } from '@apollo/client';
import { useMutation as useGQLMutation, useQuery } from '@apollo/client';
import {
BUTTON_SIZES,
BUTTON_VARIANTS,
Expand All @@ -47,6 +49,7 @@ import {
} from '@icgc-argo/uikit';
import { get } from 'lodash';
import { useState } from 'react';
import { useMutation } from 'react-query';
import urlJoin from 'url-join';
import FileError from '../../../../../components/FileError';
import FilePreview from './components/FilePreview';
Expand All @@ -62,6 +65,8 @@ const Register = ({ shortName }: { shortName: string }) => {
variables: { shortName },
});

const { egoJwt } = useAuthContext();

// get dictionary version
const latestDictionaryResponse = useClinicalQuery(CLINICAL_SCHEMA_VERSION);
const { loading: isLoadingDictVersion, data: dictData } = latestDictionaryResponse;
Expand All @@ -73,7 +78,7 @@ const Register = ({ shortName }: { shortName: string }) => {
const commonToaster = useCommonToasters();

// docs url
const { DOCS_URL_ROOT } = useAppConfigContext();
const { DOCS_URL_ROOT, CLINICAL_API_ROOT } = useAppConfigContext();
const helpUrl = urlJoin(DOCS_URL_ROOT, '/docs/submission/registering-samples');

// modal state
Expand All @@ -96,33 +101,29 @@ const Register = ({ shortName }: { shortName: string }) => {
const registrationId = get(clinicalRegistration, 'id', '') || '';

// handlers
const [uploadFile, { loading: isUploading }] = useMutation(
UPLOAD_REGISTRATION_MUTATION,

const uploadFile = useMutation(
(formData) => {
const url = urlJoin(CLINICAL_API_ROOT, getProgramPath(UPLOAD_REGISTRATION, shortName));
return uploadFileRequest(url, formData, egoJwt);
},
{
onError: (e) => {
onError: () => {
commonToaster.unknownError();
},
},
);

const [uploadClinicalSubmission, mutationStatus] = useMutation(UPLOAD_REGISTRATION_MUTATION, {
onError: (e) => {
commonToaster.unknownError();
},
});

const handleUpload = (file: File) =>
uploadClinicalSubmission({
variables: { shortName, registrationFile: file },
});
const handleUpload = (file: File) => {
const fileFormData = createFileFormData(file, 'registrationFile');
return uploadFile.mutate(fileFormData);
};

const handleRegister = () => {
setShowModal((state) => !state);
};

// file preview clear
const [clearRegistration] = useMutation(CLEAR_CLINICAL_REGISTRATION_MUTATION);
const [clearRegistration] = useGQLMutation(CLEAR_CLINICAL_REGISTRATION_MUTATION);
const handleClearClick = async () => {
if (clinicalRegistration?.id == null) {
refetch();
Expand Down Expand Up @@ -192,7 +193,7 @@ const Register = ({ shortName }: { shortName: string }) => {
<Instructions
dictionaryVersion={dictionaryVersion}
handleUpload={handleUpload}
isUploading={isUploading}
isUploading={uploadFile.isLoading}
handleRegister={handleRegister}
flags={instructionFlags}
/>
Expand Down
4 changes: 4 additions & 0 deletions src/global/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ export const PROGRAM_CLINICAL_SUBMISSION_PATH = `${SUBMISSION_PATH}/program/${PR
export const PROGRAM_CLINICAL_DATA_PATH = `${SUBMISSION_PATH}/program/${PROGRAM_SHORT_NAME_PATH}/clinical-data`;
export const CREATE_PROGRAM_PAGE_PATH = `${SUBMISSION_PATH}/program/create`;

// Upload paths
export const UPLOAD_REGISTRATION = `/clinical/api/submission/program/${PROGRAM_SHORT_NAME_PATH}/registration`;
export const UPLOAD_CLINICAL_DATA = `/clinical/api/submission/program/${PROGRAM_SHORT_NAME_PATH}/clinical/submissionUpload`;

export const CLINICAL_TEMPLATE_PATH = '/clinical/proxy/template';
37 changes: 37 additions & 0 deletions src/global/utils/form.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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
* GNU Affero General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

export const createFileFormData = (upload, uploadName) => {
const formData = new FormData();

const filesToAdd = !Array.isArray(upload) ? [upload] : upload;
for (const [i, file] of filesToAdd.entries()) {
formData.append(uploadName || `file_${i}`, file);
}
return formData;
};

export const uploadFileRequest = (url, body, jwt) => {
const options: RequestInit = {
headers: { accept: '*/*', authorization: `Bearer ${jwt}` },
method: 'POST',
body,
};
return fetch(url, options);
};
1 change: 1 addition & 0 deletions src/global/utils/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
export * from './clinical';
export * from './misc';
export * from './types';
export * from './url';
23 changes: 23 additions & 0 deletions src/global/utils/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* 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
* GNU Affero General Public License along with this program.
* If not, see <http://www.gnu.org/licenses/>.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
* SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
* IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

import { PROGRAM_SHORT_NAME_PATH } from '../constants';

export const getProgramPath = (path: string, programName: string) =>
path.replace(PROGRAM_SHORT_NAME_PATH, programName);

0 comments on commit 0e0892f

Please sign in to comment.