Skip to content
New issue

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

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

Already on GitHub? # to your account

Chore/update get clinical trial metadata function #1540

Merged
merged 12 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion dev.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
- https://atlas.qa-mickey.planx-pla.net for Atlas iframe in VHDC qa-mickey
- https://*.quicksight.aws.amazon.com for loading AWS Quicksight dashboards into COVID-19 Home page
-->
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://#.bionimbus.org https://wayf.incommonfederation.org; child-src blob:; connect-src 'self' blob: localhost https://localhost:9443 wss://localhost:9443 https://*.s3.amazonaws.com https://*.mapbox.com https://opendata.datacommons.io https://static.planx-pla.net https://*.logs.datadoghq.com https://classic.clinicaltrials.gov https://*.google-analytics.com https://*.analytics.google.com; img-src 'self' https://opendata.datacommons.io https://static.planx-pla.net https://www.google-analytics.com data: https://*.s3.amazonaws.com https://www.google-analytics.com; script-src 'self' 'unsafe-eval' https://*.google-analytics.com https://www.googletagmanager.com localhost https://localhost:9443; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com localhost https://localhost:9443; object-src 'none'; font-src 'self' data: https://fonts.googleapis.com https://fonts.gstatic.com; frame-src 'self' https://auspice.planx-pla.net https://auspice.pandemicresponsecommons.org https://atlas.qa-mickey.planx-pla.net https://*.quicksight.aws.amazon.com; ">
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://#.bionimbus.org https://wayf.incommonfederation.org; child-src blob:; connect-src 'self' blob: localhost https://localhost:9443 wss://localhost:9443 https://*.s3.amazonaws.com https://*.mapbox.com https://opendata.datacommons.io https://static.planx-pla.net https://*.logs.datadoghq.com https://clinicaltrials.gov https://*.google-analytics.com https://*.analytics.google.com; img-src 'self' https://opendata.datacommons.io https://static.planx-pla.net https://www.google-analytics.com data: https://*.s3.amazonaws.com https://www.google-analytics.com; script-src 'self' 'unsafe-eval' https://*.google-analytics.com https://www.googletagmanager.com localhost https://localhost:9443; worker-src 'self' blob:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com localhost https://localhost:9443; object-src 'none'; font-src 'self' data: https://fonts.googleapis.com https://fonts.gstatic.com; frame-src 'self' https://auspice.planx-pla.net https://auspice.pandemicresponsecommons.org https://atlas.qa-mickey.planx-pla.net https://*.quicksight.aws.amazon.com; ">
<meta name="viewport" content="width=device-width" />
<link href="https://fonts.googleapis.com/icon?family=Source+Sans+Pro" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,700;1,700&display=swap" rel="stylesheet">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const DownloadVariableMetadata = async (
content: (
<React.Fragment>
<p>
Study with name <strong>{resourceInfo.project_title}</strong>
Study with name <strong>{resourceInfo.study_metadata?.minimal_info?.study_name || 'N/A'}</strong>
&nbsp;cannot download data dictionary with name <strong>{key}</strong>.
</p>
<p>Please try again later and contact support.</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ const HeaderButtons = ({ props, setTabActiveKey }) => {
props.modalData[
studyRegistrationConfig.studyRegistrationAccessCheckField
],
props.modalData.project_title,
props.modalData.study_metadata?.minimal_info?.study_name,
props.modalData.project_number,
props.modalData[
studyRegistrationConfig.studyRegistrationUIDField
Expand All @@ -128,7 +128,7 @@ const HeaderButtons = ({ props, setTabActiveKey }) => {
props.modalData[
studyRegistrationConfig.studyRegistrationAccessCheckField
],
props.modalData.project_title,
props.modalData.study_metadata?.minimal_info?.study_name,
props.modalData.project_number,
props.modalData[
studyRegistrationConfig.studyRegistrationUIDField
Expand All @@ -154,7 +154,7 @@ const HeaderButtons = ({ props, setTabActiveKey }) => {
props.modalData[
studyRegistrationConfig.studyRegistrationAccessCheckField
],
props.modalData.project_title,
props.modalData.study_metadata?.minimal_info?.study_name,
props.modalData.project_number,
props.modalData[
studyRegistrationConfig.studyRegistrationUIDField
Expand All @@ -180,7 +180,7 @@ const HeaderButtons = ({ props, setTabActiveKey }) => {
props.modalData[
studyRegistrationConfig.studyRegistrationAccessCheckField
],
props.modalData.project_title,
props.modalData.study_metadata?.minimal_info?.study_name,
props.modalData.project_number,
props.modalData[
studyRegistrationConfig.studyRegistrationUIDField
Expand Down
83 changes: 25 additions & 58 deletions src/StudyRegistration/StudyRegistration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import {
} from '@ant-design/icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Link, useLocation } from 'react-router-dom';

import './StudyRegistration.css';
import { userHasMethodForServiceOnResource } from '../authMappingUtils';
import { useArboristUI, studyRegistrationConfig } from '../localconf';
import loadStudiesFromMDS from '../Discovery/MDSUtils';
import { registerStudyInMDS, preprocessStudyRegistrationMetadata, createCEDARInstance } from './utils';
import Spinner from '../components/Spinner';

const { Option } = Select;
const { Text } = Typography;
Expand Down Expand Up @@ -73,13 +73,13 @@ const handleClinicalTrialIDValidation = async (_, ctID: string): Promise<boolean
if (!ctID) {
return Promise.resolve(true);
}
const resp = await fetch(`https://classic.clinicaltrials.gov/api/query/field_values?expr=${encodeURIComponent(`SEARCH[Study](AREA[NCTId] ${ctID})`)}&field=NCTId&fmt=json`);
const resp = await fetch(`https://clinicaltrials.gov/api/v2/studies/${ctID}?fields=NCTId`);
if (!resp || resp.status !== 200) {
return Promise.reject('Unable to verify ClinicalTrials.gov ID');
}
try {
const respJson = await resp.json();
if (respJson.FieldValuesResponse?.FieldValues?.length === 1 && respJson.FieldValuesResponse.FieldValues[0].FieldValue === ctID) {
if (respJson.protocolSection?.identificationModule?.nctId === ctID) {
return Promise.resolve(true);
}
return Promise.reject('Invalid ClinicalTrials.gov ID');
Expand All @@ -90,55 +90,18 @@ const handleClinicalTrialIDValidation = async (_, ctID: string): Promise<boolean

const getClinicalTrialMetadata = async (ctID: string): Promise<object> => {
const errMsg = 'Unable to fetch study metadata from ClinicalTrials.gov';

// get metadata from the clinicaltrials.gov API
const promiseList: Promise<any>[] = [];
const limit = 20; // the API has a limit of 20 fields
let offset = 0;
const clinicalTrialFieldsToFetch = studyRegistrationConfig.clinicalTrialFields || [];
while (offset < clinicalTrialFieldsToFetch.length) {
const fieldsToFetch = clinicalTrialFieldsToFetch.slice(offset, offset + limit);
offset += limit;
promiseList.push(
fetch(`https://classic.clinicaltrials.gov/api/query/study_fields?expr=${encodeURIComponent(`SEARCH[Study](AREA[NCTId] ${ctID})`)}&fields=${fieldsToFetch.join(',')}&fmt=json`)
.then(
(resp) => {
if (!resp || resp.status !== 200) {
return Promise.reject(errMsg);
}
return resp.json();
},
),
);
// get metadata from the clinicaltrials.gov API
const resp = await fetch(`https://clinicaltrials.gov/api/v2/studies/${ctID}?fields=${clinicalTrialFieldsToFetch.join('|')}`);
if (!resp || resp.status !== 200) {
return Promise.reject('Unable to verify ClinicalTrials.gov ID');
}
try {
const respJson = await resp.json();
return respJson;
} catch {
throw errMsg;
}
const responsesJson = await Promise.all(promiseList);

// add the metadata returned by each call to a single `metadata` object
let metadata = {};
responsesJson.forEach((respJson) => {
// it should return data for a single study
if (respJson.StudyFieldsResponse?.StudyFields?.length !== 1) {
// eslint-disable-next-line no-console
console.error(`${errMsg}; received response:`, respJson);
throw new Error(errMsg);
}
// `respData` looks like this:
// {Rank: value to discard, FieldWithData: [value], FieldWithoutData: []}
const respData = respJson.StudyFieldsResponse.StudyFields[0];
// `partialMetadata` looks like this: (remove Rank and fields without data)
// {FieldWithData: value}
delete respData.Rank;
const partialMetadata = Object.keys(respData).reduce((res, key) => {
if (respData[key].length > 0) {
res[key] = respData[key][0];
}
return res;
}, {});
// add the new key:value pairs to the ones we already have
metadata = { ...metadata, ...partialMetadata };
});

return Promise.resolve(metadata);
};

const isUUID = (input: string) => {
Expand Down Expand Up @@ -272,7 +235,9 @@ const StudyRegistration: React.FunctionComponent<StudyRegistrationProps> = (prop
</div>
);
}

if (!studies) {
return <Spinner text={'Loading study registration form, please wait...'} />;
}
return (
<div className='study-reg-container'>
<div className='study-reg-form-container'>
Expand All @@ -292,7 +257,7 @@ const StudyRegistration: React.FunctionComponent<StudyRegistrationProps> = (prop
key={study[studyRegistrationConfig.studyRegistrationUIDField]}
value={study[studyRegistrationConfig.studyRegistrationUIDField]}
>
{`${study.project_number} : ${study.project_title} : ${study.appl_id}`}
{`${study.project_number || 'N/A'} : ${study.study_metadata?.minimal_info?.study_name || 'N/A'} : ${study.study_metadata?.metadata_location?.nih_application_id || 'N/A'}`}
</Option>
))}
</Select>
Expand Down Expand Up @@ -345,12 +310,14 @@ const StudyRegistration: React.FunctionComponent<StudyRegistrationProps> = (prop
name='repository'
label='Study Data Repository'
hasFeedback
help={(
<React.Fragment> If you have already selected a data repository, indicate it here; otherwise, leave empty.<br />
If you have deposited your data and you have a unique Study ID for the data at the repository, enter it below; otherwise, leave blank.
</React.Fragment>
)}
>
help={(
<React.Fragment> If you have already selected a data repository, indicate it here;
otherwise, leave empty.<br />
If you have deposited your data and you have a unique Study ID for the data at
the repository, enter it below; otherwise, leave blank.
</React.Fragment>
)}
>
<Select placeholder='Select a data repository' showSearch allowClear>
<Option value='BioSystics-AP'>BioSystics-AP</Option>
<Option value='Database of Genotypes and Phenotypes (dbGaP)'>Database of Genotypes and Phenotypes (dbGaP)</Option>
Expand Down
12 changes: 9 additions & 3 deletions src/StudyRegistration/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,15 @@ export const createCEDARInstance = async (cedarUserUUID, metadataToRegister = {
body: JSON.stringify({
cedar_user_uuid: cedarUserUUID,
metadata: {
appl_id: metadataToRegister[STUDY_DATA_FIELD].study_metadata.metadata_location.nih_application_id,
project_title: metadataToRegister[STUDY_DATA_FIELD].study_metadata.minimal_info.study_name,
study_description_summary: metadataToRegister[STUDY_DATA_FIELD].study_metadata.minimal_info.study_description,
study_metadata: {
metadata_location: {
nih_application_id: metadataToRegister[STUDY_DATA_FIELD].study_metadata.metadata_location.nih_application_id,
},
minimal_info: {
study_name: metadataToRegister[STUDY_DATA_FIELD].study_metadata.minimal_info.study_name,
study_description: metadataToRegister[STUDY_DATA_FIELD].study_metadata.minimal_info.study_description,
},
},
clinicaltrials_gov: metadataToRegister.clinicaltrials_gov,
},
}),
Expand Down
2 changes: 1 addition & 1 deletion webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ if (configFile.featureFlags && configFile.featureFlags.discoveryUseAggMDS) {
connectSrcURLs.push('https://dataguids.org');
}
if (configFile.featureFlags && configFile.featureFlags.studyRegistration) {
connectSrcURLs.push('https://classic.clinicaltrials.gov');
connectSrcURLs.push('https://clinicaltrials.gov');
}
if (process.env.DATADOG_APPLICATION_ID && process.env.DATADOG_CLIENT_TOKEN) {
connectSrcURLs.push('https://*.logs.datadoghq.com');
Expand Down
Loading