Skip to content

Conversation

@Aiga115
Copy link

@Aiga115 Aiga115 commented Sep 9, 2025

Issue #ILEX-136
Problem: replace elasticsearch with entity-check
Solution:

  1. Call entity-check endpoint to find potential match
  2. If no potential match we call elastic search function and place terms on sidebar
  3. Make term items on sidebar clickable and when clicking on one of the terms start edit term flow
  4. Change button title and function call according to isEditing state

Note: We don't have api call for editing so now editTerm function just logs the message. Has to be connected later

Result:

Recording.2025-09-09.141349.mp4
Recording.2025-09-09.141502.mp4

@Aiga115 Aiga115 requested a review from ddelpiano September 9, 2025 12:16
@ddelpiano ddelpiano requested a review from Copilot September 15, 2025 17:02
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR replaces the elasticsearch-based term matching system with a new entity-check API to identify potential term matches. When no exact matches are found through entity-check, the system falls back to elasticsearch for searching terms that can be selected for editing.

  • Replace elasticsearch search with entity-check API for finding potential matches
  • Enable term editing flow by making sidebar term items clickable
  • Add fallback to elasticsearch when no exact matches found via entity-check

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/constants/types.js Adds term type constants and exports for OWL classes and properties
src/config.js Adds CHECK_ENTITY endpoint configuration for the new API
src/components/TermEditor/newTerm/SecondStepContent.jsx Updates component to accept searchTerm prop and use it in predicate initialization
src/components/TermEditor/newTerm/FirstStepContent.jsx Major refactor to use entity-check API with elasticsearch fallback and enable editing mode
src/components/TermEditor/newTerm/AddNewTermDialog.jsx Adds editing state management and switches between create/edit modes
src/components/TermEditor/NewTermSidebar.jsx Improves loading states with skeletons and makes non-exact match items clickable
src/api/endpoints/apiService.ts Adds checkPotentialMatches function and refactors createNewEntity to handle redirects properly

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

const matchList = Array.isArray(matches) ? matches : [matches];
return matchList.map(match => ({
ilx: termUri.split('/').pop(),
label: match.object,
Copy link

Copilot AI Sep 15, 2025

Choose a reason for hiding this comment

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

The code assumes match has an object property when creating the label field, but this property might not exist. This could result in undefined labels for exact matches.

Suggested change
label: match.object,
label: match.object ?? termUri.split('/').pop() ?? "Unknown",

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

@Aiga115 take this into account as well, thanks!

'&:hover': { backgroundColor: "transparent" }
}}
onClick={() => onResultAction(result)}
onClick={() => window.location.href = `/${user.groupname}/${result.ilx}`}
Copy link

Copilot AI Sep 15, 2025

Choose a reason for hiding this comment

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

Using window.location.href for navigation breaks the React Router pattern and causes a full page reload. Consider using useNavigate hook from React Router instead.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

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

@Aiga115 I agree with copilot suggestion here, thanks!

Comment on lines 140 to 217

return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();

xhr.open('POST', endpoint, true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.withCredentials = true;

xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
if (xhr.status === 303) {
const redirectUrl = xhr.getResponseHeader('x-redirect-location') || xhr.getResponseHeader('Location');
if (redirectUrl) {
const tmpMatch = redirectUrl.match(/tmp_\d{9}/);
console.log("Found tmp ID:", tmpMatch?.[0]);

if (tmpMatch) {
resolve({
term: {
id: tmpMatch[0]
}
});
return;
}
}
}
}
}

// Otherwise, return response as-is
return response;
} catch (error) {
if (error?.response.status === 409) {
const match = error?.response?.data?.existing?.[0];
if (match) {
return {
term: {
id: `${match}`,
},
raw: error?.response,
status: error?.response?.status,
};
if (xhr.readyState === XMLHttpRequest.DONE) {
if (xhr.status === 0) {
// Try to find tmp ID in the response text
const tmpMatch = xhr.responseText?.match(/tmp_\d{9}/);
if (tmpMatch) {
resolve({
term: {
id: tmpMatch[0]
},
raw: xhr.responseText,
status: 303
});
return;
}
}

// Handle 409 Conflict
if (xhr.status === 409) {
try {
const responseData = JSON.parse(xhr.responseText);
const match = responseData?.existing?.[0];
if (match) {
resolve({
term: {
id: match
},
raw: responseData,
status: xhr.status
});
return;
}
} catch (e) {
console.error("Error parsing 409 response:", e);
}
}

resolve({
raw: xhr.responseText,
status: xhr.status
});
}
}
};

return {
raw: error?.response,
status: error?.response?.status,
xhr.onerror = function() {
console.error("XHR Error:", xhr.status, xhr.statusText);
reject(new Error('Network request failed'));
};
}

};
xhr.send(JSON.stringify(data));
});
Copy link

Copilot AI Sep 15, 2025

Choose a reason for hiding this comment

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

Using XMLHttpRequest directly instead of the existing createPostRequest function breaks consistency with the rest of the codebase. Consider modifying createPostRequest to handle redirects instead of implementing a custom XHR solution.

Copilot uses AI. Check for mistakes.
const navigate = useNavigate();

const isCreateButtonDisabled = hasExactMatch || termValue === "";
const isCreateButtonDisabled = hasExactMatch || termValue === "" || (isEditing && termValue === selectedTermValue);
Copy link

Copilot AI Sep 15, 2025

Choose a reason for hiding this comment

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

[nitpick] The button disabled logic is complex and could be extracted into a separate function or useMemo for better readability and testability.

Copilot uses AI. Check for mistakes.
const endpoint = `/${group}${API_CONFIG.REAL_API.CREATE_NEW_ENTITY}`;

return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
Copy link
Member

Choose a reason for hiding this comment

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

@Aiga115 why do not parametrise the content-type of createPostRequest but re-implement the entire request inside this function?


xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.HEADERS_RECEIVED) {
if (xhr.status === 303) {
Copy link
Member

Choose a reason for hiding this comment

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

Following the comment above, we should be able to handle these cases as part of the response of the createPostRequest rather than re-implementing ex novo the request on a case basis, so that all the code in common across requests will be handled by a single function and we avoid to duplicate code.

};

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.

👍🏽

'&:hover': { backgroundColor: "transparent" }
}}
onClick={() => onResultAction(result)}
onClick={() => window.location.href = `/${user.groupname}/${result.ilx}`}
Copy link
Member

Choose a reason for hiding this comment

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

@Aiga115 I agree with copilot suggestion here, thanks!

const matchList = Array.isArray(matches) ? matches : [matches];
return matchList.map(match => ({
ilx: termUri.split('/').pop(),
label: match.object,
Copy link
Member

Choose a reason for hiding this comment

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

@Aiga115 take this into account as well, thanks!

@ddelpiano
Copy link
Member

Interlex.webm

@Aiga115 when selecting a potential match from the sidebar the sidebar itself disappear (check the recording), this is not expected, the user might also have selected the wrong item, the sidebar needs to stay there and visible.

@ddelpiano
Copy link
Member

@Aiga115 upon triggering the debounce, the elasticsearch endpoint is hit a second time that is unnecessary since the query term requested is the same, please fix this as well. Thanks

Screencast.from.15-09-25.23.09.57.webm

@Aiga115 Aiga115 marked this pull request as draft September 16, 2025 14:07
@Aiga115 Aiga115 marked this pull request as ready for review September 17, 2025 10:27
@Aiga115 Aiga115 requested a review from ddelpiano September 17, 2025 10:27
});

// Handle 303 redirect
if (response.status === 303) {
Copy link
Member

Choose a reason for hiding this comment

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

This logic should be injected from whatever component calls the createPostRequest and wants to handle this differently, modifying this into this call implies that all the components in the application that are using the createPostRequest will have to behave the same way the add new term component needs to. Please fix this.


// Handle 409 Conflict
if (response.status === 409) {
const responseData = await response.json();
Copy link
Member

Choose a reason for hiding this comment

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

same here

@ddelpiano ddelpiano merged commit 54e35e4 into devel Sep 22, 2025
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants