Skip to content

Commit ca70a75

Browse files
committed
edit bulk terms completed
1 parent bcee16e commit ca70a75

File tree

9 files changed

+301
-36
lines changed

9 files changed

+301
-36
lines changed

nginx/default.conf

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,34 @@ server {
5757
add_header Access-Control-Allow-Credentials true always;
5858
}
5959

60+
# Handle PATCH/PUT/POST requests to ilx_ endpoints (for bulk term editing)
61+
location ~ ^/[^/]+/ilx_[^/]+$ {
62+
proxy_pass https://uri.olympiangods.org;
63+
proxy_set_header Host uri.olympiangods.org;
64+
proxy_set_header X-Real-IP $remote_addr;
65+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
66+
proxy_set_header X-Forwarded-Proto $scheme;
67+
proxy_set_header Content-Type application/json;
68+
69+
# Pass through auth/cookies if present
70+
proxy_set_header Authorization $http_authorization;
71+
proxy_set_header Cookie $http_cookie;
72+
73+
proxy_ssl_verify off;
74+
75+
# CORS headers for all methods including preflight
76+
add_header Access-Control-Allow-Origin $http_origin always;
77+
add_header Access-Control-Allow-Credentials true always;
78+
add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" always;
79+
add_header Access-Control-Allow-Headers "Content-Type, Authorization, Cookie, X-Requested-With" always;
80+
add_header Access-Control-Expose-Headers "X-Redirect-Location" always;
81+
82+
# Handle preflight OPTIONS requests
83+
if ($request_method = OPTIONS) {
84+
return 204;
85+
}
86+
}
87+
6088
location ~ ^/[^/]+/ontologies/uris/.*\.(html|jsonld)$ {
6189
proxy_pass https://uri.olympiangods.org;
6290
proxy_set_header Host uri.olympiangods.org;

src/api/endpoints/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ export const patchTerm = async (group, termID, term) => {
243243
const {patchEndpointsIlx} = useApi();
244244

245245
/** Call Endpoint */
246-
return patchEndpointsIlx(group, termID, term).then((data: any) => {
246+
return patchEndpointsIlx(group, termID, { data: term }).then((data: any) => {
247247
let termParsed = getTerm(data.data);
248248
let response = {
249249
status : data.status,

src/api/endpoints/interLexURIStructureAPI.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9794,7 +9794,7 @@ export const patchEndpointsIlx = (
97949794

97959795

97969796
return customInstance<void>(
9797-
{url: `${API_CONFIG.OLYMPIAN_GODS}/${group}/${fragPrefId}`, method: 'PATCH'
9797+
{url: `${group}/${fragPrefId}`, method: 'PATCH'
97989798
},
97999799
options);
98009800
}

src/components/Dashboard/EditBulkTerms/EditBulkTermsDialog.jsx

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ import CustomizedDialog from "../../common/CustomizedDialog";
1111
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
1212
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
1313
import SearchTermsData from "../../../static/SearchTermsData.json";
14+
import { patchTerm } from "../../../api/endpoints";
1415

1516
const initialSearchConditions = { attribute: '', value: '', condition: 'where', relation: SearchTermsData.objectOptions[0].value }
1617

17-
const HeaderRightSideContent = ({ handleClose, activeStep, handleNext, handleBack, setActiveStep, isAllFieldsFilled, selectedOntology }) => {
18+
const HeaderRightSideContent = ({ handleClose, activeStep, handleNext, handleBack, setActiveStep, isAllFieldsFilled, selectedOntology, isUpdating }) => {
1819
return (
1920
<Box display='flex' alignItems='center' gap='.75rem'>
2021
<MobileStepper
@@ -41,8 +42,14 @@ const HeaderRightSideContent = ({ handleClose, activeStep, handleNext, handleBac
4142
Previous
4243
</Button>
4344
}
44-
<Button endIcon={<ArrowForwardIcon />} variant='contained' color='primary' onClick={handleNext} disabled={activeStep === 0 && !isAllFieldsFilled && !selectedOntology}>
45-
Continue
45+
<Button
46+
endIcon={!isUpdating && <ArrowForwardIcon />}
47+
variant='contained'
48+
color='primary'
49+
onClick={handleNext}
50+
disabled={(activeStep === 0 && !isAllFieldsFilled && !selectedOntology) || isUpdating}
51+
>
52+
{isUpdating ? 'Saving Changes...' : 'Continue'}
4653
</Button>
4754
</>
4855
}
@@ -65,7 +72,117 @@ const EditBulkTermsDialog = ({ open, handleClose, activeStep, setActiveStep }) =
6572
const [ontologyTerms, setOntologyTerms] = useState([]);
6673
const [ontologyAttributes, setOntologyAttributes] = useState([]);
6774
const [selectedOntology, setSelectedOntology] = useState(null);
68-
const handleNext = () => {
75+
const [originalTerms, setOriginalTerms] = useState([]);
76+
const [batchUpdateResults, setBatchUpdateResults] = useState(null);
77+
const [isUpdating, setIsUpdating] = useState(false);
78+
const performBatchUpdate = async (termsToUpdate = null) => {
79+
setIsUpdating(true);
80+
81+
// Use provided terms or all ontology terms
82+
const terms = termsToUpdate || ontologyTerms;
83+
84+
const results = {
85+
successful: [],
86+
failed: [],
87+
total: terms.length
88+
};
89+
90+
try {
91+
// Store original terms before starting updates (only if not retrying)
92+
if (!termsToUpdate) {
93+
setOriginalTerms([...ontologyTerms]);
94+
}
95+
96+
// Process each term
97+
for (const term of terms) {
98+
try {
99+
// Extract the group and term ID from the term
100+
let termId = term.id || term['@id'];
101+
102+
// If termId is in full URI format, extract just the ID part
103+
if (termId && termId.includes('/')) {
104+
termId = termId.split('/').pop();
105+
}
106+
107+
const group = 'base'; // Default group, can be made configurable
108+
109+
// Create the JSON-LD payload with the current term data
110+
const jsonLdPayload = {
111+
"@context": term["@context"] || {
112+
"@vocab": "http://uri.interlex.org/base/",
113+
"owl": "http://www.w3.org/2002/07/owl#",
114+
"rdfs": "http://www.w3.org/2000/01/rdf-schema#"
115+
},
116+
"@id": term['@id'] || termId,
117+
...term
118+
};
119+
120+
// Send PATCH request
121+
const response = await patchTerm(group, termId, jsonLdPayload);
122+
123+
if (response.status === 200 || response.status === 201) {
124+
results.successful.push({
125+
termId,
126+
term: response.term,
127+
status: response.status
128+
});
129+
} else {
130+
results.failed.push({
131+
termId,
132+
error: `HTTP ${response.status}`,
133+
term
134+
});
135+
}
136+
} catch (error) {
137+
console.error(`Failed to update term ${term.id}:`, error);
138+
results.failed.push({
139+
termId: term.id,
140+
error: error.message || 'Unknown error',
141+
term
142+
});
143+
}
144+
}
145+
146+
// If retrying, merge with existing results
147+
if (termsToUpdate && batchUpdateResults) {
148+
setBatchUpdateResults({
149+
successful: [...batchUpdateResults.successful, ...results.successful],
150+
failed: results.failed, // Replace failed list with new attempt results
151+
total: batchUpdateResults.total
152+
});
153+
} else {
154+
setBatchUpdateResults(results);
155+
}
156+
} catch (error) {
157+
console.error('Batch update failed:', error);
158+
const failureResults = {
159+
successful: termsToUpdate && batchUpdateResults ? batchUpdateResults.successful : [],
160+
failed: terms.map(term => ({
161+
termId: term.id,
162+
error: error.message || 'Batch operation failed',
163+
term
164+
})),
165+
total: termsToUpdate && batchUpdateResults ? batchUpdateResults.total : terms.length
166+
};
167+
setBatchUpdateResults(failureResults);
168+
} finally {
169+
setIsUpdating(false);
170+
}
171+
};
172+
173+
const handleTryAgain = async () => {
174+
if (batchUpdateResults && batchUpdateResults.failed.length > 0) {
175+
// Extract failed terms for retry
176+
const failedTerms = batchUpdateResults.failed.map(failedItem => failedItem.term);
177+
await performBatchUpdate(failedTerms);
178+
}
179+
};
180+
181+
const handleNext = async () => {
182+
// If we're moving from step 1 (EditTerms) to step 2 (Status), perform batch update
183+
if (activeStep === 1) {
184+
await performBatchUpdate();
185+
}
69186
setActiveStep((prevActiveStep) => prevActiveStep + 1);
70187
};
71188

@@ -83,7 +200,7 @@ const EditBulkTermsDialog = ({ open, handleClose, activeStep, setActiveStep }) =
83200
};
84201

85202
// put success by default, should be changed later
86-
const statusProps = getStatusProps({ success: true });
203+
87204

88205
return (
89206
<CustomizedDialog
@@ -99,6 +216,7 @@ const EditBulkTermsDialog = ({ open, handleClose, activeStep, setActiveStep }) =
99216
setActiveStep={setActiveStep}
100217
isAllFieldsFilled={isAllFieldsFilled(searchConditions)}
101218
selectedOntology={selectedOntology}
219+
isUpdating={isUpdating}
102220
/>
103221
}
104222
sx={{
@@ -119,6 +237,7 @@ const EditBulkTermsDialog = ({ open, handleClose, activeStep, setActiveStep }) =
119237
setOntologyAttributes={setOntologyAttributes}
120238
selectedOntology={selectedOntology}
121239
setSelectedOntology={setSelectedOntology}
240+
setOriginalTerms={setOriginalTerms}
122241
/>
123242
}
124243
{
@@ -130,7 +249,12 @@ const EditBulkTermsDialog = ({ open, handleClose, activeStep, setActiveStep }) =
130249
/>
131250
}
132251
{
133-
activeStep === 2 && <StatusStep statusProps={statusProps} onAction={() => setActiveStep(0)} actionButtonStartIcon={<EditOutlinedIcon />} />
252+
activeStep === 2 && <StatusStep
253+
statusProps={getStatusProps(batchUpdateResults, isUpdating)}
254+
onAction={() => setActiveStep(0)}
255+
actionButtonStartIcon={<EditOutlinedIcon />}
256+
onTryAgain={handleTryAgain}
257+
/>
134258
}
135259
</>
136260
</CustomizedDialog>
@@ -143,7 +267,9 @@ HeaderRightSideContent.propTypes = {
143267
handleNext: PropTypes.func.isRequired,
144268
handleBack: PropTypes.func.isRequired,
145269
setActiveStep: PropTypes.func.isRequired,
146-
isAllFieldsFilled: PropTypes.func.isRequired,
270+
isAllFieldsFilled: PropTypes.bool.isRequired,
271+
selectedOntology: PropTypes.object,
272+
isUpdating: PropTypes.bool.isRequired,
147273
};
148274

149275
EditBulkTermsDialog.propTypes = {

src/components/Dashboard/EditBulkTerms/SearchTerms.jsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ const styles = {
4848
},
4949
}
5050

51-
const SearchTerms = ({ searchConditions, setSearchConditions, initialSearchConditions, ontologyTerms, setOntologyTerms, ontologyAttributes, setOntologyAttributes, selectedOntology, setSelectedOntology }) => {
51+
const SearchTerms = ({ searchConditions, setSearchConditions, initialSearchConditions, ontologyTerms, setOntologyTerms, ontologyAttributes, setOntologyAttributes, selectedOntology, setSelectedOntology, setOriginalTerms }) => {
5252
const [ontologyEditOption, setOntologyEditOption] = useState(Confirmation.Yes);
5353
const [attributesLoading, setAttributesLoading] = useState(false);
5454
const { user } = useContext(GlobalDataContext);
@@ -232,14 +232,15 @@ const SearchTerms = ({ searchConditions, setSearchConditions, initialSearchCondi
232232
}
233233

234234
setOntologyTerms(termsData);
235+
setOriginalTerms([...termsData]); // Store original terms for undo functionality
235236
} catch (error) {
236237
console.error('Error fetching ontology attributes:', error);
237238
setOntologyAttributes([]);
238239
setOntologyTerms([]);
239240
} finally {
240241
setAttributesLoading(false);
241242
}
242-
}, [setOntologyAttributes, setOntologyTerms, setSelectedOntology]);
243+
}, [setOntologyAttributes, setOntologyTerms, setSelectedOntology, setOriginalTerms]);
243244

244245
const updatedColumnsArray = useMemo(() => {
245246
// If an ontology is selected and we have attributes, use those
@@ -432,6 +433,7 @@ SearchTerms.propTypes = {
432433
setOntologyAttributes: PropTypes.func.isRequired,
433434
selectedOntology: PropTypes.object,
434435
setSelectedOntology: PropTypes.func.isRequired,
436+
setOriginalTerms: PropTypes.func.isRequired,
435437
};
436438

437439
export default SearchTerms;
Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,50 @@
1-
export const getStatusProps = (responseStatus) => ({
2-
statusResponse: responseStatus,
3-
successMessage: "Bulk terms edit successful",
4-
failureMessage: "Unable to create the bulk term",
5-
successDescription: `Your changes has been applied. Click finish to close the modal, or restart editing.`,
6-
failureDescription: `Your changes hasn't been applied. Click finish to close the modal, or restart editing.`,
7-
actionButtonMessage: responseStatus?.success ? "Edit bulk terms" : null,
8-
isTryButtonVisible: responseStatus?.success ? false : true,
9-
});
1+
export const getStatusProps = (batchResults, isUpdating = false) => {
2+
if (!batchResults) {
3+
return {
4+
statusResponse: { success: false },
5+
successMessage: "Bulk terms edit in progress",
6+
failureMessage: "Bulk terms edit in progress",
7+
successDescription: "Please wait while changes are being applied...",
8+
failureDescription: "Please wait while changes are being applied...",
9+
actionButtonMessage: null,
10+
isTryButtonVisible: false,
11+
};
12+
}
13+
14+
const isSuccess = batchResults.failed.length === 0;
15+
const successCount = batchResults.successful.length;
16+
const failureCount = batchResults.failed.length;
17+
const totalCount = batchResults.total;
18+
19+
// If currently updating (retry in progress), show appropriate messages
20+
if (isUpdating) {
21+
return {
22+
statusResponse: { success: false },
23+
successMessage: "Retrying failed updates...",
24+
failureMessage: "Retrying failed updates...",
25+
successDescription: `Retrying ${failureCount} failed term updates. Please wait...`,
26+
failureDescription: `Retrying ${failureCount} failed term updates. Please wait...`,
27+
actionButtonMessage: null,
28+
isTryButtonVisible: false,
29+
};
30+
}
31+
32+
return {
33+
statusResponse: { success: isSuccess },
34+
successMessage: isSuccess
35+
? `Bulk terms edit successful - ${successCount}/${totalCount} terms updated`
36+
: `Bulk terms edit partially successful - ${successCount}/${totalCount} terms updated`,
37+
failureMessage: `Bulk terms edit failed - ${failureCount}/${totalCount} terms failed to update`,
38+
successDescription: isSuccess
39+
? `All ${successCount} term changes have been successfully applied. Click finish to close the modal, or restart editing.`
40+
: `${successCount} terms were updated successfully, but ${failureCount} failed. You can try again for the failed terms. Click finish to close the modal, or restart editing.`,
41+
failureDescription: `${failureCount} out of ${totalCount} term updates failed. Check the error details and try again for the failed terms. Click finish to close the modal, or restart editing.`,
42+
actionButtonMessage: "Edit bulk terms",
43+
isTryButtonVisible: !isSuccess && failureCount > 0,
44+
batchDetails: {
45+
successful: batchResults.successful,
46+
failed: batchResults.failed,
47+
total: totalCount
48+
}
49+
};
50+
};

0 commit comments

Comments
 (0)