Skip to content

Commit 044766a

Browse files
Chore/update get clinical trial metadata function (#1540)
* chore(updateGetClinicalTrialMetadataFunction): Updated exceptions for remote API calls, updated endpoint for verifying NCT ID * chore(updateGetClinicalTrialMetadataFunction): Created working prototype * chore(updateGetClinicalTrialMetadataFunction): Fixed eslint errors * chore(updateGetClinicalTrialMetadataFunction): Removed unneeded comments and line changes * chore(updateGetClinicalTrialMetadataFunction): Added spinner until studies are available * chore(updateGetClinicalTrialMetadataFunction): Formatted code for readability * update metadata format * use fields from new metadata format --------- Co-authored-by: Mingfei Shao <[email protected]> Co-authored-by: Mingfei Shao <[email protected]>
1 parent 90b4763 commit 044766a

File tree

6 files changed

+41
-68
lines changed

6 files changed

+41
-68
lines changed

dev.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
- https://atlas.qa-mickey.planx-pla.net for Atlas iframe in VHDC qa-mickey
1212
- https://*.quicksight.aws.amazon.com for loading AWS Quicksight dashboards into COVID-19 Home page
1313
-->
14-
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://login.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; ">
14+
<meta http-equiv="Content-Security-Policy" content="default-src 'self' https://login.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; ">
1515
<meta name="viewport" content="width=device-width" />
1616
<link href="https://fonts.googleapis.com/icon?family=Source+Sans+Pro" rel="stylesheet">
1717
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,700;1,700&display=swap" rel="stylesheet">

src/Discovery/DiscoveryDetails/Components/FieldGrouping/TabField/DataDownloadList/ActionButtons/DownloadUtils/DownloadVariableMetadata/DownloadVariableMetadata.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ const DownloadVariableMetadata = async (
2424
content: (
2525
<React.Fragment>
2626
<p>
27-
Study with name <strong>{resourceInfo.project_title}</strong>
27+
Study with name <strong>{resourceInfo.study_metadata?.minimal_info?.study_name || 'N/A'}</strong>
2828
&nbsp;cannot download data dictionary with name <strong>{key}</strong>.
2929
</p>
3030
<p>Please try again later and contact support.</p>

src/Discovery/DiscoveryDetails/Components/HeaderButtons/HeaderButtons.tsx

+4-4
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ const HeaderButtons = ({ props, setTabActiveKey }) => {
116116
props.modalData[
117117
studyRegistrationConfig.studyRegistrationAccessCheckField
118118
],
119-
props.modalData.project_title,
119+
props.modalData.study_metadata?.minimal_info?.study_name,
120120
props.modalData.project_number,
121121
props.modalData[
122122
studyRegistrationConfig.studyRegistrationUIDField
@@ -128,7 +128,7 @@ const HeaderButtons = ({ props, setTabActiveKey }) => {
128128
props.modalData[
129129
studyRegistrationConfig.studyRegistrationAccessCheckField
130130
],
131-
props.modalData.project_title,
131+
props.modalData.study_metadata?.minimal_info?.study_name,
132132
props.modalData.project_number,
133133
props.modalData[
134134
studyRegistrationConfig.studyRegistrationUIDField
@@ -154,7 +154,7 @@ const HeaderButtons = ({ props, setTabActiveKey }) => {
154154
props.modalData[
155155
studyRegistrationConfig.studyRegistrationAccessCheckField
156156
],
157-
props.modalData.project_title,
157+
props.modalData.study_metadata?.minimal_info?.study_name,
158158
props.modalData.project_number,
159159
props.modalData[
160160
studyRegistrationConfig.studyRegistrationUIDField
@@ -180,7 +180,7 @@ const HeaderButtons = ({ props, setTabActiveKey }) => {
180180
props.modalData[
181181
studyRegistrationConfig.studyRegistrationAccessCheckField
182182
],
183-
props.modalData.project_title,
183+
props.modalData.study_metadata?.minimal_info?.study_name,
184184
props.modalData.project_number,
185185
props.modalData[
186186
studyRegistrationConfig.studyRegistrationUIDField

src/StudyRegistration/StudyRegistration.tsx

+25-58
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ import {
1818
} from '@ant-design/icons';
1919
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
2020
import { Link, useLocation } from 'react-router-dom';
21-
2221
import './StudyRegistration.css';
2322
import { userHasMethodForServiceOnResource } from '../authMappingUtils';
2423
import { useArboristUI, studyRegistrationConfig } from '../localconf';
2524
import loadStudiesFromMDS from '../Discovery/MDSUtils';
2625
import { registerStudyInMDS, preprocessStudyRegistrationMetadata, createCEDARInstance } from './utils';
26+
import Spinner from '../components/Spinner';
2727

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

9191
const getClinicalTrialMetadata = async (ctID: string): Promise<object> => {
9292
const errMsg = 'Unable to fetch study metadata from ClinicalTrials.gov';
93-
94-
// get metadata from the clinicaltrials.gov API
95-
const promiseList: Promise<any>[] = [];
96-
const limit = 20; // the API has a limit of 20 fields
97-
let offset = 0;
9893
const clinicalTrialFieldsToFetch = studyRegistrationConfig.clinicalTrialFields || [];
99-
while (offset < clinicalTrialFieldsToFetch.length) {
100-
const fieldsToFetch = clinicalTrialFieldsToFetch.slice(offset, offset + limit);
101-
offset += limit;
102-
promiseList.push(
103-
fetch(`https://classic.clinicaltrials.gov/api/query/study_fields?expr=${encodeURIComponent(`SEARCH[Study](AREA[NCTId] ${ctID})`)}&fields=${fieldsToFetch.join(',')}&fmt=json`)
104-
.then(
105-
(resp) => {
106-
if (!resp || resp.status !== 200) {
107-
return Promise.reject(errMsg);
108-
}
109-
return resp.json();
110-
},
111-
),
112-
);
94+
// get metadata from the clinicaltrials.gov API
95+
const resp = await fetch(`https://clinicaltrials.gov/api/v2/studies/${ctID}?fields=${clinicalTrialFieldsToFetch.join('|')}`);
96+
if (!resp || resp.status !== 200) {
97+
return Promise.reject('Unable to verify ClinicalTrials.gov ID');
98+
}
99+
try {
100+
const respJson = await resp.json();
101+
return respJson;
102+
} catch {
103+
throw errMsg;
113104
}
114-
const responsesJson = await Promise.all(promiseList);
115-
116-
// add the metadata returned by each call to a single `metadata` object
117-
let metadata = {};
118-
responsesJson.forEach((respJson) => {
119-
// it should return data for a single study
120-
if (respJson.StudyFieldsResponse?.StudyFields?.length !== 1) {
121-
// eslint-disable-next-line no-console
122-
console.error(`${errMsg}; received response:`, respJson);
123-
throw new Error(errMsg);
124-
}
125-
// `respData` looks like this:
126-
// {Rank: value to discard, FieldWithData: [value], FieldWithoutData: []}
127-
const respData = respJson.StudyFieldsResponse.StudyFields[0];
128-
// `partialMetadata` looks like this: (remove Rank and fields without data)
129-
// {FieldWithData: value}
130-
delete respData.Rank;
131-
const partialMetadata = Object.keys(respData).reduce((res, key) => {
132-
if (respData[key].length > 0) {
133-
res[key] = respData[key][0];
134-
}
135-
return res;
136-
}, {});
137-
// add the new key:value pairs to the ones we already have
138-
metadata = { ...metadata, ...partialMetadata };
139-
});
140-
141-
return Promise.resolve(metadata);
142105
};
143106

144107
const isUUID = (input: string) => {
@@ -272,7 +235,9 @@ const StudyRegistration: React.FunctionComponent<StudyRegistrationProps> = (prop
272235
</div>
273236
);
274237
}
275-
238+
if (!studies) {
239+
return <Spinner text={'Loading study registration form, please wait...'} />;
240+
}
276241
return (
277242
<div className='study-reg-container'>
278243
<div className='study-reg-form-container'>
@@ -292,7 +257,7 @@ const StudyRegistration: React.FunctionComponent<StudyRegistrationProps> = (prop
292257
key={study[studyRegistrationConfig.studyRegistrationUIDField]}
293258
value={study[studyRegistrationConfig.studyRegistrationUIDField]}
294259
>
295-
{`${study.project_number} : ${study.project_title} : ${study.appl_id}`}
260+
{`${study.project_number || 'N/A'} : ${study.study_metadata?.minimal_info?.study_name || 'N/A'} : ${study.study_metadata?.metadata_location?.nih_application_id || 'N/A'}`}
296261
</Option>
297262
))}
298263
</Select>
@@ -345,12 +310,14 @@ const StudyRegistration: React.FunctionComponent<StudyRegistrationProps> = (prop
345310
name='repository'
346311
label='Study Data Repository'
347312
hasFeedback
348-
help={(
349-
<React.Fragment> If you have already selected a data repository, indicate it here; otherwise, leave empty.<br />
350-
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.
351-
</React.Fragment>
352-
)}
353-
>
313+
help={(
314+
<React.Fragment> If you have already selected a data repository, indicate it here;
315+
otherwise, leave empty.<br />
316+
If you have deposited your data and you have a unique Study ID for the data at
317+
the repository, enter it below; otherwise, leave blank.
318+
</React.Fragment>
319+
)}
320+
>
354321
<Select placeholder='Select a data repository' showSearch allowClear>
355322
<Option value='BioSystics-AP'>BioSystics-AP</Option>
356323
<Option value='Database of Genotypes and Phenotypes (dbGaP)'>Database of Genotypes and Phenotypes (dbGaP)</Option>

src/StudyRegistration/utils.tsx

+9-3
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,15 @@ export const createCEDARInstance = async (cedarUserUUID, metadataToRegister = {
7373
body: JSON.stringify({
7474
cedar_user_uuid: cedarUserUUID,
7575
metadata: {
76-
appl_id: metadataToRegister[STUDY_DATA_FIELD].study_metadata.metadata_location.nih_application_id,
77-
project_title: metadataToRegister[STUDY_DATA_FIELD].study_metadata.minimal_info.study_name,
78-
study_description_summary: metadataToRegister[STUDY_DATA_FIELD].study_metadata.minimal_info.study_description,
76+
study_metadata: {
77+
metadata_location: {
78+
nih_application_id: metadataToRegister[STUDY_DATA_FIELD].study_metadata.metadata_location.nih_application_id,
79+
},
80+
minimal_info: {
81+
study_name: metadataToRegister[STUDY_DATA_FIELD].study_metadata.minimal_info.study_name,
82+
study_description: metadataToRegister[STUDY_DATA_FIELD].study_metadata.minimal_info.study_description,
83+
},
84+
},
7985
clinicaltrials_gov: metadataToRegister.clinicaltrials_gov,
8086
},
8187
}),

webpack.config.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ if (configFile.featureFlags && configFile.featureFlags.discoveryUseAggMDS) {
3737
connectSrcURLs.push('https://dataguids.org');
3838
}
3939
if (configFile.featureFlags && configFile.featureFlags.studyRegistration) {
40-
connectSrcURLs.push('https://classic.clinicaltrials.gov');
40+
connectSrcURLs.push('https://clinicaltrials.gov');
4141
}
4242
if (process.env.DATADOG_APPLICATION_ID && process.env.DATADOG_CLIENT_TOKEN) {
4343
connectSrcURLs.push('https://*.logs.datadoghq.com');

0 commit comments

Comments
 (0)