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
37 changes: 35 additions & 2 deletions src/api/endpoints/apiActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,41 @@ import { customInstance } from '../../../mock/mutator/customClient';

type SecondParameter<T extends (...args: any) => any> = Parameters<T>[1];

export const createPostRequest = <T = any, D = any>(endpoint: string, headers : object) => {
return (data?: D, options?: SecondParameter<typeof customInstance>) => {
interface CustomRequestConfig extends AxiosRequestConfig {
handleRedirect?: boolean;
}

export const createPostRequest = <T = any, D = any>(endpoint: string, headers: object) => {
return async (data?: D, options?: CustomRequestConfig) => {
// Use fetch for handling redirect responses
if (options?.handleRedirect) {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
...headers,
},
body: JSON.stringify(data),
credentials: 'include',
redirect: 'manual'
});

// Default response handling
const text = await response.text();
try {
const json = JSON.parse(text);
return {
raw: json,
status: response.status
};
} catch {
return {
raw: text,
status: response.status
};
}
}

// Default axios behavior for normal requests
return customInstance<T>(
{
url: endpoint,
Expand Down
69 changes: 16 additions & 53 deletions src/api/endpoints/apiService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,9 @@ export const getSelectedTermLabel = async (searchTerm: string, group: string = '
return label?.['@value'] || '';
};

return {
label: label ? getLabelValue(label) : undefined,
actualGroup: group
return {
label: label ? getLabelValue(label) : undefined,
actualGroup: group
};
} catch (err: any) {
console.error(err.message);
Expand All @@ -110,7 +110,7 @@ export const getSelectedTermLabel = async (searchTerm: string, group: string = '
try {
const fallbackResponse = await createGetRequest<JsonLdResponse, any>(`/base/${searchTerm}.jsonld`)();
const fallbackLabel = fallbackResponse['@graph']?.[0]?.['rdfs:label'];

const getLabelValue = (label: LabelType): string => {
if (typeof label === 'string') return label;
if (Array.isArray(label)) {
Expand All @@ -122,9 +122,9 @@ export const getSelectedTermLabel = async (searchTerm: string, group: string = '
return label?.['@value'] || '';
};

return {
label: fallbackLabel ? getLabelValue(fallbackLabel) : undefined,
actualGroup: 'base'
return {
label: fallbackLabel ? getLabelValue(fallbackLabel) : undefined,
actualGroup: 'base'
};
} catch (fallbackErr: any) {
console.error('Fallback request also failed:', fallbackErr.message);
Expand All @@ -136,50 +136,9 @@ export const getSelectedTermLabel = async (searchTerm: string, group: 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>(
endpoint,
{ "Content-Type": "application/x-www-form-urlencoded" }
)(data);

// If the response is HTML (a string), extract TMP ID
if (typeof response === "string") {
const match = response.match(/TMP:\d{9}/);
if (match) {
return {
term: {
id: `${match[0]}`,
},
raw: response,
status: 200,
};
}
}

// Otherwise, return response as-is
return response;
} catch (error) {
if (error && typeof error === 'object' && 'response' in error && (error as any).response?.status === 409) {
const match = (error as any)?.response?.data?.existing?.[0];
if (match) {
return {
term: {
id: `${match}`,
},
raw: (error as any)?.response,
status: (error as any)?.response?.status,
};
}
}

return {
raw: (error as any)?.response,
status: (error as any)?.response?.status,
};
}

};
const endpoint = `/${group}${API_CONFIG.REAL_API.CREATE_NEW_ENTITY}`;
return createPostRequest(endpoint, { 'Content-Type': 'application/json' })(data, { handleRedirect: true });
}

export const createNewOntology = async ({
groupname,
Expand Down Expand Up @@ -334,10 +293,10 @@ export const getTermDiscussions = async (group: string, variantID: string) => {
};

export const getVariant = (group: string, term: string) => {
return createGetRequest<any, any>(`/${group}/variant/${term}`, "application/json")();
return createGetRequest<any, any>(`/${group}/variant/${term}`, "application/json")();
};

export const getTermPredicates = async ({
export const getTermPredicates = async ({
groupname,
termId,
objToSub = true,
Expand Down Expand Up @@ -398,3 +357,7 @@ export const getTermHierarchies = async ({

return { triples };
};

export const checkPotentialMatches = async (group: string, data: any) => {
return createPostRequest<any, any>(`/${group}${API_CONFIG.REAL_API.CHECK_ENTITY}`, { "Content-Type": "application/json" })(data);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏽

};
65 changes: 45 additions & 20 deletions src/components/TermEditor/NewTermSidebar.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import PropTypes from 'prop-types';
import { useNavigate } from 'react-router-dom';
import { StartIcon, JoinRightIcon } from '../../Icons';
import ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';
import ArrowOutwardIcon from '@mui/icons-material/ArrowOutward';
Expand All @@ -8,9 +9,9 @@ import {
IconButton,
Tooltip,
Stack,
CircularProgress,
Chip,
Button
Button,
Skeleton
} from '@mui/material';
import { vars } from '../../theme/variables';

Expand Down Expand Up @@ -49,17 +50,6 @@ const HOVER_INDICATOR_STYLES = {
background: brand600
};

const LoadingSpinner = () => (
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: '32rem'
}}>
<CircularProgress />
</Box>
);

const ExpandedHeader = ({ onToggle }) => (
<Box
width={1}
Expand Down Expand Up @@ -127,15 +117,43 @@ const EmptyState = () => (
</Box>
);

const ResultItem = ({ result, searchValue, onResultAction }) => {
const ResultItemSkeleton = () => (
<Box
width={1}
display="flex"
flexDirection="column"
px={1}
py={1.5}
gap={1}
sx={{ borderBottom: `1px solid ${gray200}` }}
>
<Stack direction="column" spacing={1} sx={{ justifyContent: "space-between", alignItems: "flex-start" }}>
<Stack direction="row" spacing={0.5}>
<Skeleton variant="rounded" width={150} height={24} />
<Skeleton variant="rounded" width={150} height={24} />
</Stack>
<Skeleton variant="rounded" width="100%" height={36} />
<Stack direction="row" spacing={1}>
<Skeleton variant="rounded" width={24} height={20} />
<Skeleton variant="rounded" width={150} height={20} />
</Stack>
</Stack>
</Box>
);

const ResultItem = ({ result, searchValue, onResultAction, user }) => {
const isExactMatch = result.label.toLowerCase() === searchValue?.toLowerCase();
const navigate = useNavigate();

const getItemStyles = () => ({
borderBottom: `1px solid ${gray200}`,
position: 'relative',
borderRadius: '0.5rem',
cursor: isExactMatch ? 'default' : 'pointer',
...(isExactMatch && EXACT_MATCH_STYLES),
'&:hover': {
...(!isExactMatch && {
backgroundColor: gray200,
'&:before': HOVER_INDICATOR_STYLES
})
}
Expand All @@ -150,6 +168,7 @@ const ResultItem = ({ result, searchValue, onResultAction }) => {
px={1}
py={1.5}
gap={1}
onClick={() => !isExactMatch && onResultAction(result)}
sx={getItemStyles()}
>
<Stack
Expand Down Expand Up @@ -180,7 +199,7 @@ const ResultItem = ({ result, searchValue, onResultAction }) => {
height: "auto",
'&:hover': { backgroundColor: "transparent" }
}}
onClick={() => onResultAction(result)}
onClick={() => navigate(`/${user.groupname}/${result.ilx}`)}
>
Go to term
</Button>
Expand Down Expand Up @@ -211,18 +230,17 @@ export default function NewTermSidebar({
results,
isResultsEmpty,
searchValue,
onResultAction
onResultAction,
user
}) {
if (loading) {
return <LoadingSpinner />;
}

const sidebarStyles = {
display: 'flex',
flexDirection: 'column',
borderLeft: `1px solid ${gray200}`,
transition: SIDEBAR_STYLES.transition,
p: 3,
minWidth: open ? SIDEBAR_STYLES.expanded : SIDEBAR_STYLES.collapsed,
maxWidth: open ? SIDEBAR_STYLES.expanded : SIDEBAR_STYLES.collapsed,
overflowY: 'auto',
'::-webkit-scrollbar': {
Expand All @@ -249,7 +267,11 @@ export default function NewTermSidebar({
alignItems="center"
gap={1}
>
{isResultsEmpty ? (
{loading ? (
Array.from(new Array(10)).map((_, index) => (
<ResultItemSkeleton key={index} />
))
) : isResultsEmpty ? (
<EmptyState />
) : (
<>
Expand All @@ -259,6 +281,7 @@ export default function NewTermSidebar({
result={result}
searchValue={searchValue}
onResultAction={onResultAction}
user={user}
/>
))}
</>
Expand All @@ -282,6 +305,7 @@ ResultItem.propTypes = {
result: PropTypes.object.isRequired,
searchValue: PropTypes.string,
onResultAction: PropTypes.func,
user: PropTypes.object,
};

NewTermSidebar.propTypes = {
Expand All @@ -292,4 +316,5 @@ NewTermSidebar.propTypes = {
isResultsEmpty: PropTypes.bool.isRequired,
searchValue: PropTypes.string.isRequired,
onResultAction: PropTypes.func,
user: PropTypes.object,
};
Loading