Skip to content
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
77 changes: 75 additions & 2 deletions src/api/endpoints/apiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ export const getSelectedTermLabel = async (searchTerm: string): Promise<string |
}
};

export const createNewEntity = async ({group,data,session}: { group: string; data: any; session: string }) => {
export const createNewEntity = async ({ group, data, session }: { group: string; data: any; session: string }) => {
try {
const endpoint = `/${group}${API_CONFIG.REAL_API.CREATE_NEW_ENTITY}`;
const response = await createPostRequest<any, any>(
Expand Down Expand Up @@ -122,5 +122,78 @@ export const createNewEntity = async ({group,data,session}: { group: string; dat
status: error?.response?.status,
};
}


};

export const createNewOntology = async ({
groupname,
token,
ontologyName,
title,
subjects,
}: {
groupname: string;
token: string;
ontologyName: string;
title: string;
subjects: string[];
}) => {
const endpoint = `/${groupname}/ontologies/uris/${ontologyName}/spec`;

const data = {
title : title,
subjects : subjects,
};

const headers = {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
};

try {
const postResponse = await createPostRequest<any, any>(endpoint, headers)(data);

// If the POST creates a new location, try fetching it (simulate follow-up GETs from the test)
if (postResponse?.location) {
const getResponse = await fetch(postResponse.location, {
headers: {
Accept: 'application/json',
},
});

// Optionally fetch HTML if needed (like the .html equivalent in the Python test)
const htmlResponse = await fetch(endpoint, {
headers: {
Accept: 'text/html',
},
});

return {
created: true,
data: postResponse,
jsonResponse: await getResponse.json(),
htmlAvailable: htmlResponse.ok,
};
}

return {
created: true,
data: postResponse,
};
} catch (error: any) {
return {
created: false,
error: error?.response?.data || error.message,
};
}
};

export const getNewTokenApi = ({ groupname, data }: { groupname: string, data: any }) => {
const endpoint = `/${groupname}${API_CONFIG.REAL_API.API_NEW_TOKEN}`;
return createPostRequest<any, any>(endpoint, { "Content-Type" : "application/json" })(data);
};

export const retrieveTokenApi = ({ groupname }: { groupname: string }) => {
const endpoint = `/${groupname}${API_CONFIG.REAL_API.API_RETRIEVE_TOKEN}`;
return createGetRequest<any, any>(endpoint, "application/json")();
};
133 changes: 122 additions & 11 deletions src/components/SingleOrganization/AddNewOntologyDialog.jsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import * as React from "react";
import PropTypes from "prop-types";
import AddIcon from '@mui/icons-material/Add';
import Checkbox from "../common/CustomCheckbox";
import StatusDialog from "../common/StatusDialog";
import CustomInputBox from "../common/CustomInputBox";
import { Stack, Button, Grid, Box } from "@mui/material";
import CustomizedDialog from "../common/CustomizedDialog";
import ImportFileTab from "./../TermEditor/ImportFileTab";
import BasicTabs from "../common/CustomTabs";
import { useState } from "react";
import { createNewOntology, getNewTokenApi, retrieveTokenApi } from "../../api/endpoints/apiService";
import { GlobalDataContext } from "../../contexts/DataContext";
import { useContext } from "react";

const HeaderRightSideContent = ({ handleClose, onAddNewOntology }) => {
return (
Expand All @@ -25,14 +30,66 @@ HeaderRightSideContent.propTypes = {
}

const AddNewOntologyDialog = ({ open, handleClose }) => {
const [openStatusDialog, setOpenStatusDialog] = React.useState(false);
const [newOntology, setNewOntology] = React.useState({
const [openStatusDialog, setOpenStatusDialog] = useState(false);
const [newOntology, setNewOntology] = useState({
title: "",
description: ""
});
const [newOntologyResponse, setNewOntologyResponse] = useState({
title: "",
description: "",
created : false,
message : "Your ontology “Nervous system” has been added. Click 'Go to Ontology' to go see the result, or add a new ontology."
});
const [files, setFiles] = useState([]);
const [url, setUrl] = useState('');
const [tabValue, setTabValue] = useState(0);
const { user } = useContext(GlobalDataContext);

const handleSubmit = async() => {
const groupname = user?.groupname

const handleSubmit = () => {
console.log("Submit new ontology data!");
const retrieved_tokens = await retrieveTokenApi({groupname})
let token = null;
if ( retrieved_tokens?.length > 0 ){
token = retrieved_tokens?.[retrieved_tokens?.length - 1]?.key;
}

if ( token === undefined || token === null) {
const newToken = await getNewTokenApi({groupname});
token = newToken?.key;
}
const ontologyName = newOntology?.title + "_" + Math.random().toString(36).substring(2, 10);
const title = newOntology?.title;
const subjects = files?.[0]?.data?.subjects;

const result = await createNewOntology({
groupname,
token,
ontologyName,
title,
subjects,
});

let ontologyResponseMessage = "Ontology created successfully!"

if (result.created) {
console.log('Ontology details:', result.data);

if (result.jsonResponse) {
console.log('Retrieved JSON:', result.jsonResponse);
}

if (result.htmlAvailable !== undefined) {
console.log('HTML version available:', result.htmlAvailable);
}
} else {
ontologyResponseMessage = "Failed to create ontology"
console.error('❌ Failed to create ontology:', result.error);
}

setOpenStatusDialog(true);
setNewOntologyResponse({title : newOntology?.title, description : ontologyResponseMessage, message : ontologyResponseMessage, created : result.created})
}

const handleNewOntologyChange = (e) => {
Expand All @@ -45,8 +102,6 @@ const AddNewOntologyDialog = ({ open, handleClose }) => {

const handleAddNewOntology = () => {
handleSubmit();
setOpenStatusDialog(true);
setNewOntology({ title: "", description: "" })
};

const handleCloseStatusDialog = () => {
Expand All @@ -56,9 +111,56 @@ const AddNewOntologyDialog = ({ open, handleClose }) => {
const handleFinishButtonClick = () => {
handleClose();
setOpenStatusDialog(false);
setNewOntology({ title: "", description: "" })
}

const handleChangeUrl = (event) => {
setUrl(event.target.value);
}

const handleFilesSelected = async (newFiles) => {
const fileArray = Array.from(newFiles);

const readFileContents = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();

reader.onload = () => {
let content = reader.result;

// Try parsing JSON if it's a JSON file
if (file.name.endsWith('.json')) {
try {
content = JSON.parse(content);
} catch (e) {
console.error(`Invalid JSON in file ${file.name}`, e);
content = null;
}
}

resolve({
name: file.name,
size: (file.size / 1024).toFixed(2),
progress: 100,
data: content
});
};

reader.onerror = () => reject(reader.error);
reader.readAsText(file);
});
};

const updatedFiles = await Promise.all(fileArray.map(readFileContents));

setFiles(prevFiles => {
const prevString = JSON.stringify(prevFiles);
const newString = JSON.stringify(updatedFiles);
return prevString !== newString ? updatedFiles : prevFiles;
});
};

const handleChangeTabs = (_, newValue) => setTabValue(newValue);

return (
<>
<CustomizedDialog
Expand All @@ -72,6 +174,10 @@ const AddNewOntologyDialog = ({ open, handleClose }) => {
/>
}
>
<Box display="flex" height={1}>
<Box sx={{ px: '3.25rem', pt: '1.75rem', pb: '2.5rem', flex: 1, overflowY: 'auto' }}>
<BasicTabs tabValue={tabValue} handleChange={handleChangeTabs} tabs={["Manually", "Import"]} />
{tabValue === 0 && (
<Grid container spacing={5.5}>
<Grid item xs={12} lg={12}>
<Stack direction="column" mb={1}>
Expand Down Expand Up @@ -101,16 +207,21 @@ const AddNewOntologyDialog = ({ open, handleClose }) => {
/>
</Grid>
</Grid>
)}
{tabValue === 1 && <ImportFileTab files={files} url={url} onFilesChange={handleFilesSelected} onChangeUrl={handleChangeUrl} />}
</Box>
</Box>
</CustomizedDialog>
<StatusDialog
title={"Add a new ontology"}
message={"Ontology successfully created"}
subMessage={"Your ontology “Nervous system” has been added. Click finish to go see the result, or add a new ontology."}
title={newOntologyResponse?.title}
message={newOntologyResponse?.description}
subMessage={newOntologyResponse?.message}
addButtonTitle={"Add a new ontology"}
finishButtonTitle={"Go to ontology"}
open={openStatusDialog}
handleClose={handleCloseStatusDialog}
handleCloseandAdd={handleFinishButtonClick}
errored={!newOntologyResponse.created}
/>
</>
)
Expand Down
27 changes: 11 additions & 16 deletions src/components/SingleOrganization/CreateForkDialog.jsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import * as React from "react";
import { useContext } from "react";
import { debounce } from 'lodash';
import PropTypes from "prop-types";
import termParser from "../../parsers/termParser";
import CustomInputBox from "../common/CustomInputBox";
import { getOrganizations } from "../../api/endpoints";
import CustomSelectBox from "../common/CustomSelectBox";
import { useState, useEffect, useCallback } from "react";
import CustomizedDialog from "../common/CustomizedDialog";
import ForkRightIcon from '@mui/icons-material/ForkRight';
import CustomAutocompleteBox from "../common/CustomAutocompleteBox";
import { Stack, Button, Grid, Box, Typography } from "@mui/material";
import { GlobalDataContext } from "../../contexts/DataContext";
import * as mockApi from "../../api/endpoints/swaggerMockMissingEndpoints";
import { useOrganizations } from "../../helpers/useOrganizations";

import { vars } from "../../theme/variables";
const { gray800, gray500, gray600 } = vars;
Expand Down Expand Up @@ -39,32 +40,27 @@ const CreateForkDialog = ({ open, handleClose, onSubmit }) => {
const { getMatchTerms } = useMockApi();
// eslint-disable-next-line no-unused-vars
const [loading, setLoading] = useState(true);
const [termResults, setTermResults] = useState([]);
const [organizations, setOrganizations] = useState([]);
const [termResults] = useState([]);
const [newFork, setNewFork] = React.useState({
term: null,
owner: "",
name: ""
});
const { user } = useContext(GlobalDataContext);
const groupname = user?.groupname || "base";
const {
organizations,
} = useOrganizations(groupname);

// eslint-disable-next-line react-hooks/exhaustive-deps
const fetchTerms = useCallback(
debounce((term) => {
debounce(() => {
setLoading(true);
getMatchTerms("base", "i", { filter: "", value: "" }).then(data => {
const parsedData = termParser(data, term);
setTermResults(parsedData.results);
});

}, 300),
[getMatchTerms]
);

const fetchOrganizations = async () => {
const organizations = await getOrganizations("base")
setOrganizations(organizations);
setLoading(false)
}

const handleCreateFork = () => {
onSubmit(newFork);
};
Expand All @@ -90,7 +86,6 @@ const CreateForkDialog = ({ open, handleClose, onSubmit }) => {

useEffect(() => {
setLoading(true)
fetchOrganizations();
}, []);


Expand Down
8 changes: 4 additions & 4 deletions src/components/SingleOrganization/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,9 @@ const useOrganizationData = (id) => {
setLoading(false);
}
};

fetchData();
if ( id ) {
fetchData();
}
}, [id]);

return { organization, organizationCuries, organizationTerms, organizationOntologies, loading };
Expand All @@ -93,9 +94,8 @@ const SingleOrganization = () => {
const [ontologiesPageOptions, setOntologiesPageOptions] = useState([]);

const navigate = useNavigate();
const id = "1"; // Hardcoded for now

const { organization, organizationTerms, organizationOntologies, loading } = useOrganizationData(id);
const { organization, organizationTerms, organizationOntologies, loading } = useOrganizationData();

useEffect(() => {
if (organizationTerms.length > 0) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/TermEditor/ImportFile.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const ImportFile = ({ onFilesSelected }) => {
hidden
id="browse"
onChange={handleFileChange}
accept=".csv"
accept=".csv,.json"
multiple
/>
<Box display="flex" sx={styles.uploadLabel} gap={0.50}>
Expand Down
Loading