diff --git a/src/App.jsx b/src/App.jsx
index 6363339..a1db92e 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -37,6 +37,7 @@ function App() {
const [selectedKey, setSelectedKey] = useState(null);
const [user, setUser] = useState(null);
const [ontologyForPagination, setOntologyForPagination] = useState([]);
+ const [ucumCodes, setUcumCodes] = useState([]);
message.config({
top: '25vh',
@@ -79,6 +80,8 @@ function App() {
setImportState,
ontologyForPagination,
setOntologyForPagination,
+ ucumCodes,
+ setUcumCodes,
}}
>
diff --git a/src/components/Manager/FetchManager.jsx b/src/components/Manager/FetchManager.jsx
index 374d1cd..ff6bdfc 100644
--- a/src/components/Manager/FetchManager.jsx
+++ b/src/components/Manager/FetchManager.jsx
@@ -4,6 +4,7 @@ import { ontologyReducer } from './Utilitiy';
export const getAll = (vocabUrl, name, navigate) => {
return fetch(`${vocabUrl}/${name}`, {
method: 'GET',
+ credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
diff --git a/src/components/Manager/MappingsFunctions/MappingReset.jsx b/src/components/Manager/MappingsFunctions/MappingReset.jsx
deleted file mode 100644
index f93ab9f..0000000
--- a/src/components/Manager/MappingsFunctions/MappingReset.jsx
+++ /dev/null
@@ -1,647 +0,0 @@
-import { Checkbox, Form, Input, notification, Tooltip } from 'antd';
-import { useContext, useEffect, useRef, useState } from 'react';
-import { myContext } from '../../../App';
-import { ellipsisString, ontologyReducer, systemsMatch } from '../Utilitiy';
-import { ModalSpinner } from '../Spinner';
-import { MappingContext } from '../../../Contexts/MappingContext';
-import { getFiltersByCode, olsFilterOntologiesSearch } from '../FetchManager';
-import { SearchContext } from '../../../Contexts/SearchContext';
-import { OntologyCheckboxes } from './OntologyCheckboxes';
-import { MappingRelationship } from './MappingRelationship';
-
-export const MappingReset = ({
- searchProp,
- mappingDesc,
- setEditMappings,
- form,
- onClose,
- mappingProp,
- table,
- terminology,
-}) => {
- const { searchUrl, vocabUrl } = useContext(myContext);
- const {
- apiPreferences,
- defaultOntologies,
- setFacetCounts,
- setApiPreferencesCode,
- apiPreferencesCode,
- setUnformattedPref,
- ontologyApis,
- prefTerminologies,
- } = useContext(SearchContext);
- const [page, setPage] = useState(0);
- const entriesPerPage = 1000;
- const [loading, setLoading] = useState(true);
- const [results, setResults] = useState([]);
- const [totalCount, setTotalCount] = useState();
- const [resultsCount, setResultsCount] = useState(); //
- const [lastCount, setLastCount] = useState(0); //save last count as count of the results before you fetch data again
- const [filteredResultsCount, setFilteredResultsCount] = useState(0);
- const [inputValue, setInputValue] = useState(searchProp); //Sets the value of the search bar
- const [currentSearchProp, setCurrentSearchProp] = useState(searchProp);
- const [terminologiesToMap, setTerminologiesToMap] = useState([]);
- const [active, setActive] = useState(null);
- const [allCheckboxes, setAllCheckboxes] = useState([]);
-
- const {
- setSelectedMappings,
- displaySelectedMappings,
- setDisplaySelectedMappings,
- selectedBoxes,
- setSelectedBoxes,
- } = useContext(MappingContext);
- let ref = useRef();
- const { Search } = Input;
-
- const fetchTerminologies = () => {
- setLoading(true);
- const fetchPromises = prefTerminologies?.map(pref =>
- fetch(`${vocabUrl}/${pref?.reference}`).then(response => response.json())
- );
-
- Promise.all(fetchPromises)
- .then(results => {
- // Once all fetch calls are resolved, set the combined data
- setTerminologiesToMap(results);
- })
- .catch(error => {
- notification.error({
- message: 'Error',
- description: 'An error occurred. Please try again.',
- });
- })
- .finally(() => setLoading(false));
- };
-
- // since the code is passed through searchProp, the '!!' forces it to be evaluated as a boolean.
- // if there is a searchProp being passed, it evaluates to true and runs the search function.
- // inputValue and currentSearchProp for the search bar is set to the passed searchProp.
- // The function is run when the code changes.
- useEffect(() => {
- setInputValue(searchProp);
- setCurrentSearchProp(searchProp);
- setPage(0);
- if (!!searchProp) {
- getFiltersByCode(
- vocabUrl,
- mappingProp,
- setApiPreferencesCode,
- notification,
- setUnformattedPref,
- table,
- terminology,
- setLoading
- );
- }
- }, [searchProp]);
-
- useEffect(() => {
- if (apiPreferencesCode !== undefined) {
- fetchResults(0, searchProp);
- }
- }, [searchProp]);
-
- useEffect(() => {
- if (apiPreferencesCode !== undefined) {
- fetchResults(page, currentSearchProp);
- }
- }, [page]);
-
- useEffect(() => {
- if (prefTerminologies.length > 0) {
- fetchTerminologies();
- }
- }, []);
-
- useEffect(() => {
- setAllCheckboxes(
- terminologiesToMap.find(term => term.id === active)?.codes ?? []
- );
- }, [active]);
-
- useEffect(() => {
- setActive(terminologiesToMap?.[0]?.id);
- }, [terminologiesToMap]);
-
- // The '!!' forces currentSearchProp to be evaluated as a boolean.
- // If there is a currentSearchProp in the search bar, it evaluates to true and runs the search function.
- // The function is run when the query changes and when the preferred ontology changes.
- // If there are preferred terminologies, it runs when the OLS search bar is clicked (i.e. active)
- useEffect(() => {
- if (
- prefTerminologies.length > 0 &&
- active === 'search' &&
- !!currentSearchProp &&
- apiPreferencesCode !== undefined
- ) {
- fetchResults(page, currentSearchProp);
- } else if (
- prefTerminologies.length === 0 &&
- !!currentSearchProp &&
- apiPreferencesCode !== undefined
- ) {
- fetchResults(page, currentSearchProp);
- }
- }, [currentSearchProp, apiPreferencesCode, active]);
-
- /* Pagination is handled via a "View More" link at the bottom of the page.
- Each click on the "View More" link makes an API call to fetch the next 15 results.
- This useEffect moves the scroll bar on the modal to the first index of the new batch of results.
- Because the content is in a modal and not the window, the closest class name to the modal is used for the location of the ref. */
- useEffect(() => {
- if (results?.length > 0 && page > 0 && ref.current) {
- const container = ref.current.closest('.ant-modal-body');
- const scrollTop = ref.current.offsetTop - container.offsetTop;
- container.scrollTop = scrollTop;
- }
- }, [results]);
-
- // Sets the value of the selected_mappings in the form to the checkboxes that are selected
- useEffect(() => {
- form.setFieldsValue({
- selected_mappings: selectedBoxes,
- });
- }, [selectedBoxes, form]);
-
- // sets the code to null on dismount.
- useEffect(
- () => () => {
- onClose();
- setEditMappings(null);
- setSelectedMappings([]);
- setDisplaySelectedMappings([]);
- setSelectedBoxes([]);
- },
- []
- );
-
- // Sets currentSearchProp to the value of the search bar and sets page to 0.
- const handleSearch = query => {
- setCurrentSearchProp(query);
- setPage(0);
- };
-
- // The function that makes the API call to search for the passed code.
- const fetchResults = (page, query) => {
- if (!!!query) {
- return undefined;
- }
- setLoading(true);
-
- /* The OLS API returns 10 results by default unless specified otherwise. The fetch call includes a specified
- number of results to return per page (entriesPerPage) and a calculation of the first index to start the results
- on each new batch of results (pageStart, calculated as the number of the page * the number of entries per page */
- const pageStart = page * entriesPerPage;
- if (
- //If there are api preferences and one of them is OLS, it gets the preferred ontologies
- apiPreferences?.self?.api_preference &&
- 'ols' in apiPreferences?.self?.api_preference
- ) {
- const apiPreferenceOntologies = () => {
- if (apiPreferences?.self?.api_preference?.ols) {
- return apiPreferences.self.api_preference.ols.join(',');
- } else {
- // else if there are no preferred ontologies, it uses the default ontologies
- return defaultOntologies;
- }
- };
- //fetch call to search OLS with either preferred or default ontologies
- return olsFilterOntologiesSearch(
- searchUrl,
- query,
- apiPreferencesCode?.length > 0
- ? apiPreferencesCode
- : apiPreferenceOntologies(),
- page,
- entriesPerPage,
- pageStart,
- selectedBoxes,
- setTotalCount,
- setResults,
- setFilteredResultsCount,
- setResultsCount,
- setLoading,
- results,
- setFacetCounts
- );
- } else
- return olsFilterOntologiesSearch(
- searchUrl,
- query,
- apiPreferencesCode?.length > 0 ? apiPreferencesCode : defaultOntologies,
- page,
- entriesPerPage,
- pageStart,
- selectedBoxes,
- setTotalCount,
- setResults,
- setFilteredResultsCount,
- setResultsCount,
- setLoading,
- results,
- setFacetCounts
- );
- };
-
- // the 'View More' pagination onClick increments the page. The search function is triggered to run on page change in the useEffect.
- const handleViewMore = e => {
- e.preventDefault();
- setPage(prevPage => prevPage + 1);
- };
-
- // The display for the checkboxes. The index is set to the count of the results before you fetch the new batch of results
- // again + 1, to move the scrollbar to the first result of the new batch.
- const checkBoxDisplay = (item, index) => {
- return (
- <>
-
- >
- );
- };
-
- const handleChange = e => {
- setInputValue(e.target.value);
- };
-
- // The display for the checkboxes. The index is set to the count of the results before you fetch the new batch of results
- // again + 1, to move the scrollbar to the first result of the new batch.
- const newSearchDisplay = (d, index) => {
- index === lastCount + 1;
- return (
- <>
-
- >
- );
- };
- // If the checkbox is checked, it adds the object to the selectedBoxes array
- // If it is unchecked, it filters it out of the selectedBoxes array.
- const onCheckboxChange = (event, code) => {
- if (event.target.checked) {
- setSelectedBoxes(prevState => [...prevState, code]);
- } else {
- setSelectedBoxes(prevState => prevState.filter(val => val !== code));
- }
- };
-
- const onSelectedChange = checkedValues => {
- const selected = JSON.parse(checkedValues?.[checkedValues.length - 1]);
-
- // Adds the selectedMappings to the selectedBoxes to ensure they are checked
- setSelectedBoxes(prevState => {
- const updated = [...prevState, selected];
- // Sets the values for the form to the selectedMappings checkboxes that are checked
- form.setFieldsValue({ selected_mappings: updated });
- return updated;
- });
-
- setDisplaySelectedMappings(prevState => [...prevState, selected]);
- };
-
- // Creates a Set that excludes the mappings that have already been selected.
- // Then filteres the existing mappings out of the results to only display results that have not yet been selected.
- const getFilteredResults = () => {
- const codesToExclude = new Set([
- ...displaySelectedMappings?.map(m => m?.code),
- ]);
- return results.filter(r => !codesToExclude?.has(r.obo_id));
- };
-
- const filteredResultsArray = getFilteredResults();
- // Peforms search on Tab key press
- const searchOnTab = e => {
- if (e.key === 'Tab') {
- e.preventDefault();
- handleSearch(e.target.value);
- }
- };
- return (
- <>
-
- <>
- {loading === false ? (
- <>
-
-
-
{searchProp}
-
- {!prefTerminologies.length > 0 && (
-
- )}
-
- {mappingDesc}
-
- {/* ant.design form displaying the checkboxes with the search results. */}
-
- {/* 'View More' pagination displaying the number of results being displayed
- out of the total number of results. Because of the filter to filter out the duplicates,
- there is a tooltip informing the user that redundant entries have been removed to explain any
- inconsistencies in results numbers per page. */}
-
- Displaying {resultsCount}
- of {totalCount}
-
- {resultsCount < totalCount - filteredResultsCount && (
- {
- handleViewMore(e);
- setLastCount(resultsCount);
- }}
- >
- View More
-
- )}
-
- )}
-
-
- >
- ) : (
-
-
-
- )}
- >
-
- >
- );
-};
diff --git a/src/components/Projects/Studies/StudyList.jsx b/src/components/Projects/Studies/StudyList.jsx
index ffd7fdf..4a76215 100644
--- a/src/components/Projects/Studies/StudyList.jsx
+++ b/src/components/Projects/Studies/StudyList.jsx
@@ -32,7 +32,7 @@ export const StudyList = () => {
if (error) {
notification.error({
message: 'Error',
- description: 'An error occurred. Please try again.',
+ description: 'An error occurred loading studies.',
});
}
return error;
diff --git a/src/components/Projects/Tables/AddVariable.jsx b/src/components/Projects/Tables/AddVariable.jsx
index 8b22e62..54cf7db 100644
--- a/src/components/Projects/Tables/AddVariable.jsx
+++ b/src/components/Projects/Tables/AddVariable.jsx
@@ -89,6 +89,8 @@ export const AddVariable = ({ table, setTable }) => {
}}
maskClosable={false}
closeIcon={false}
+ cancelButtonProps={{ disabled: loading }}
+ okButtonProps={{ disabled: loading }}
>
{loading ? (
diff --git a/src/components/Projects/Tables/DataTypeNumerical.jsx b/src/components/Projects/Tables/DataTypeNumerical.jsx
index 4836348..88e6ec2 100644
--- a/src/components/Projects/Tables/DataTypeNumerical.jsx
+++ b/src/components/Projects/Tables/DataTypeNumerical.jsx
@@ -1,8 +1,20 @@
-import { Form, Input, InputNumber, Space } from 'antd';
+import { Form, Input, InputNumber, Select, Space } from 'antd';
+import { getById } from '../../Manager/FetchManager';
+import { useContext, useEffect, useState } from 'react';
+import { myContext } from '../../../App';
export const DataTypeNumerical = ({ form, type }) => {
- // Validation function to ensure values are numbers and min is less than max
+ const { ucumCodes } = useContext(myContext);
+
+ const options = ucumCodes.map((uc, i) => {
+ return {
+ key: i,
+ value: `ucum:${uc.code}`,
+ label: uc.display,
+ };
+ });
+ // Validation function to ensure values are numbers and min is less than max
const validateMinMax = () => {
const min = parseFloat(form.getFieldValue('min'));
const max = parseFloat(form.getFieldValue('max'));
@@ -75,11 +87,24 @@ export const DataTypeNumerical = ({ form, type }) => {
/>
- {
+ const labelMatch = (option?.label ?? '')
+ .toLowerCase()
+ .includes(input.toLowerCase());
+ const valueMatch = (option?.value ?? '')
+ .toLowerCase()
+ .includes(input.toLowerCase());
+ return labelMatch || valueMatch;
}}
- placeholder="Units"
+ options={options}
/>
diff --git a/src/components/Projects/Tables/EditDataTypeNumerical.jsx b/src/components/Projects/Tables/EditDataTypeNumerical.jsx
index 1c88578..c619f6a 100644
--- a/src/components/Projects/Tables/EditDataTypeNumerical.jsx
+++ b/src/components/Projects/Tables/EditDataTypeNumerical.jsx
@@ -1,6 +1,18 @@
-import { Form, Input, InputNumber, Space } from 'antd';
+import { Form, InputNumber, Select, Space } from 'antd';
+import { useContext } from 'react';
+import { myContext } from '../../../App';
+
+export const EditDataTypeNumerical = ({ type, form, tableData }) => {
+ const { ucumCodes } = useContext(myContext);
+
+ const options = ucumCodes.map((uc, i) => {
+ return {
+ key: i,
+ value: `ucum:${uc.code}`,
+ label: uc.display,
+ };
+ });
-export const EditDataTypeNumerical = ({ type, form }) => {
// Validation function to ensure values are numbers and min is less than max
const validateMinMax = () => {
const min = parseFloat(form.getFieldValue('min'));
@@ -82,11 +94,25 @@ export const EditDataTypeNumerical = ({ type, form }) => {
/>
- {
+ const labelMatch = (option?.label ?? '')
+ .toLowerCase()
+ .includes(input.toLowerCase());
+ const valueMatch = (option?.value ?? '')
+ .toLowerCase()
+ .includes(input.toLowerCase());
+ return labelMatch || valueMatch;
}}
- placeholder="Units"
+ options={options}
/>
diff --git a/src/components/Projects/Tables/EditDataTypeSubForm.jsx b/src/components/Projects/Tables/EditDataTypeSubForm.jsx
index 3b61b49..d07b141 100644
--- a/src/components/Projects/Tables/EditDataTypeSubForm.jsx
+++ b/src/components/Projects/Tables/EditDataTypeSubForm.jsx
@@ -41,7 +41,7 @@ function EditDataTypeSubForm({ type, form, editRow, tableData }) {
return (
<>
{type === 'INTEGER' || type === 'QUANTITY' ? (
-
+
) : (
type === 'ENUMERATION' &&
(!terminologyLoading ? (
diff --git a/src/components/Projects/Tables/TableDetails.jsx b/src/components/Projects/Tables/TableDetails.jsx
index fdaf8ec..d6e57f2 100644
--- a/src/components/Projects/Tables/TableDetails.jsx
+++ b/src/components/Projects/Tables/TableDetails.jsx
@@ -36,8 +36,16 @@ import { ellipsisString, mappingTooltip } from '../../Manager/Utilitiy';
export const TableDetails = () => {
const [form] = Form.useForm();
- const { vocabUrl, edit, setEdit, table, setTable, user } =
- useContext(myContext);
+ const {
+ vocabUrl,
+ edit,
+ setEdit,
+ table,
+ setTable,
+ user,
+ ucumCodes,
+ setUcumCodes,
+ } = useContext(myContext);
const { apiPreferences, setApiPreferences } = useContext(SearchContext);
const {
getMappings,
@@ -75,7 +83,7 @@ export const TableDetails = () => {
}, [table, mapping, pageSize]);
const updateMappings = (mapArr, mappingCode) => {
- // setLoading(true);
+ setLoading(true);
const mappingsDTO = {
mappings: mapArr,
editor: user.email,
@@ -117,6 +125,10 @@ export const TableDetails = () => {
// fetches the table and sets 'table' to the response
useEffect(() => {
+ tableApiCalls();
+ }, []);
+
+ const tableApiCalls = async () => {
setLoading(true);
getById(vocabUrl, 'Table', tableId)
.then(data => {
@@ -162,7 +174,8 @@ export const TableDetails = () => {
})
.then(data => {
setApiPreferences(data);
- });
+ })
+ .finally(() => setLoading(false));
} else {
setLoading(false);
}
@@ -172,13 +185,26 @@ export const TableDetails = () => {
if (error) {
notification.error({
message: 'Error',
- description: 'An error occurred loading the ontology preferences.',
+ description: 'An error occurred loading table details.',
});
}
return error;
})
.finally(() => setLoading(false));
- }, []);
+ };
+
+ useEffect(() => {
+ table && tableTypes();
+ }, [table]);
+
+ const tableTypes = () => {
+ const varTypes = table?.variables?.map(tv => tv.data_type);
+ if (varTypes?.includes('INTEGER' || 'QUANTITY')) {
+ getById(vocabUrl, 'Terminology', 'ucum-common').then(data =>
+ setUcumCodes(data.codes)
+ );
+ }
+ };
// sets table to an empty object on dismount
useEffect(
@@ -296,6 +322,11 @@ It then shows the mappings as table data and alows the user to delete a mapping
updateMappings(variableMappings?.mappings, variableMappings?.code);
};
+ const findType = variable => {
+ const unit = variable?.units?.split(':')[1];
+ const foundType = ucumCodes.filter(item => item.code === unit);
+ return foundType.length > 0 ? foundType[0].display : variable?.units;
+ };
// data for the table columns. Each table has an array of variables. Each variable has a name, description, and data type.
// The integer and quantity data types include additional details.
// The enumeration data type includes a reference to a terminology, which includes further codes with the capability to match the
@@ -311,7 +342,7 @@ It then shows the mappings as table data and alows the user to delete a mapping
data_type: variable.data_type,
min: variable.min,
max: variable.max,
- units: variable.units,
+ units: findType(variable),
enumeration: variable.data_type === 'ENUMERATION' && (
);
};
+
+// const tableApiCalls = async () => {
+// const tableData = await getById(vocabUrl, 'Table', tableId);
+
+// if (!tableData) {
+// navigate('/404');
+// } else {
+// setTable(tableData);
+
+// const [tableMappings, tableFilters, mappingRelationships] =
+// await Promise.all([
+// getById(vocabUrl, 'Table', `${tableId}/mapping`),
+// fetch(`${vocabUrl}/Table/${tableId}/filter/self`, {
+// method: 'GET',
+// headers: {
+// 'Content-Type': 'application/json',
+// },
+// }),
+// getById(vocabUrl, 'Terminology', 'ftd-concept-map-relationship'),
+// ]);
+
+// if (tableMappings) {
+// setMapping(tableMappings.codes);
+// }
+
+// if (tableFilters.ok) {
+// setApiPreferences(tableFilters.json());
+// } else {
+// throw new Error('An unknown error occurred.');
+// }
+// if (mappingRelationships) {
+// setRelationshipOptions(mappingRelationships.codes);
+// }
+// }
+// setLoading(false);
+// };
diff --git a/src/components/Projects/Terminologies/TerminologyList.jsx b/src/components/Projects/Terminologies/TerminologyList.jsx
index 743d806..8b1309e 100644
--- a/src/components/Projects/Terminologies/TerminologyList.jsx
+++ b/src/components/Projects/Terminologies/TerminologyList.jsx
@@ -1,4 +1,4 @@
-import { Button, Input, Space, Table } from 'antd';
+import { Button, Input, notification, Space, Table } from 'antd';
import { SearchOutlined } from '@ant-design/icons';
import { useContext, useEffect, useRef, useState } from 'react';
import { myContext } from '../../../App';
@@ -33,6 +33,15 @@ export const TerminologyList = () => {
.then(data => {
setTerms(data);
})
+ .catch(error => {
+ if (error) {
+ notification.error({
+ message: 'Error',
+ description: 'An error occurred loading terminologies.',
+ });
+ }
+ return error;
+ })
.finally(() => setLoading(false));
localStorage.setItem('pageSize', pageSize);
}, [pageSize]);