Skip to content

Commit bdf1d13

Browse files
authored
Merge pull request #9 from OpenConceptLab/feature/load-custom-match-algo-details-by-get-request
�feat(map-projects): enrich custom match algorithm form�
2 parents 3f2aa96 + e3ce97a commit bdf1d13

1 file changed

Lines changed: 116 additions & 21 deletions

File tree

src/components/map-projects/MultiAlgoSelector.jsx

Lines changed: 116 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import orderBy from 'lodash/orderBy'
3434

3535

3636
import ConceptIcon from '../concepts/ConceptIcon'
37+
import APIService from '../../services/APIService';
3738

3839
/**
3940
* MultiAlgoSelector (MUI5)
@@ -78,6 +79,7 @@ export default function MultiAlgoSelector({
7879
const { t } = useTranslation()
7980
const [expanded, setExpanded] = useState(() => new Map());
8081
const [errors, setErrors] = React.useState({})
82+
const [customAlgoMeta, setCustomAlgoMeta] = React.useState({})
8183

8284
const normalizedValue = useMemo(() => {
8385
let changed = false;
@@ -152,6 +154,7 @@ export default function MultiAlgoSelector({
152154
const removeSelected = (key) => {
153155
const next = (value || []).filter((v) => v.__key !== key);
154156
onChange(next);
157+
setCustomAlgoMeta(prev => omit(prev, [key]));
155158

156159
setExpanded((prev) => {
157160
const n = new Map(prev);
@@ -219,6 +222,68 @@ export default function MultiAlgoSelector({
219222
return <TuneRoundedIcon sx={{fontSize: '1.5rem', color: 'warning.main'}} />
220223

221224
};
225+
226+
const setCustomAlgoState = (key, patch) => {
227+
setCustomAlgoMeta(prev => ({...prev, [key]: {...prev[key], ...patch}}));
228+
};
229+
230+
const verifyCustomAlgorithm = async (key, tokenOverride) => {
231+
const selected = normalizedValue.find(v => v.__key === key);
232+
if(!selected?.url)
233+
return;
234+
235+
const token = tokenOverride ?? selected?.token;
236+
setCustomAlgoState(key, {isLoading: true, requestError: '', requestSuccess: '', tokenRequired: false});
237+
238+
const service = APIService.new();
239+
service.URL = selected.url;
240+
241+
const response = await service.get(token || false, {}, undefined, true);
242+
const status = response?.response?.status || response?.status;
243+
244+
if([401, 403].includes(status)) {
245+
setCustomAlgoState(key, {
246+
isLoading: false,
247+
tokenRequired: true,
248+
requestError: '',
249+
requestSuccess: '',
250+
});
251+
return;
252+
}
253+
254+
if(response?.response || response?.detail || response?.message === 'Network Error' || typeof response === 'string') {
255+
setCustomAlgoState(key, {
256+
isLoading: false,
257+
tokenRequired: false,
258+
requestError: response?.response?.data?.detail || response?.detail || response?.message || response || t('unknown_error'),
259+
requestSuccess: '',
260+
});
261+
return;
262+
}
263+
264+
const data = response?.data || response;
265+
if(!data?.name || !(data?.ID || data?.id)) {
266+
setCustomAlgoState(key, {
267+
isLoading: false,
268+
tokenRequired: false,
269+
requestError: t('unknown_error'),
270+
requestSuccess: '',
271+
});
272+
return;
273+
}
274+
275+
updateSelected(key, {
276+
id: data.ID || data.id || '',
277+
name: data.name || '',
278+
description: data.description || '',
279+
});
280+
setCustomAlgoState(key, {
281+
isLoading: false,
282+
tokenRequired: false,
283+
requestError: '',
284+
requestSuccess: t('common.saved'),
285+
});
286+
};
222287
return (
223288
<Box sx={{ width: "100%" }}>
224289
<Stack spacing={1.5}>
@@ -228,6 +293,7 @@ export default function MultiAlgoSelector({
228293

229294
const isOpen = expanded.get(sel.__key) ?? false;
230295
const hasErrors = errors[sel.__key]?.id || errors[sel.__key]?.name
296+
const customMeta = customAlgoMeta[sel.__key] || {}
231297

232298
return (
233299
<Paper
@@ -320,6 +386,51 @@ export default function MultiAlgoSelector({
320386
borderLeftStyle: "solid",
321387
}}
322388
>
389+
<Stack spacing={1.5}>
390+
<TextField
391+
fullWidth
392+
required
393+
label={t('map_project.api_url')}
394+
value={sel.url || ""}
395+
onChange={(e) => {
396+
updateSelected(sel.__key, { url: e.target.value });
397+
if(customMeta.requestError || customMeta.requestSuccess || customMeta.tokenRequired) {
398+
setCustomAlgoState(sel.__key, {
399+
requestError: '',
400+
requestSuccess: '',
401+
tokenRequired: false,
402+
});
403+
}
404+
}}
405+
onBlur={() => verifyCustomAlgorithm(sel.__key)}
406+
placeholder="https://example.com/match"
407+
error={Boolean(customMeta.requestError)}
408+
helperText={customMeta.requestError || customMeta.requestSuccess || ''}
409+
/>
410+
<TextField
411+
fullWidth
412+
required={Boolean(customMeta.tokenRequired)}
413+
type='password'
414+
label={t('map_project.api_token')}
415+
value={sel.token || ""}
416+
onChange={(e) => {
417+
updateSelected(sel.__key, { token: e.target.value });
418+
if(customMeta.requestError || customMeta.requestSuccess) {
419+
setCustomAlgoState(sel.__key, {
420+
requestError: '',
421+
requestSuccess: '',
422+
});
423+
}
424+
}}
425+
onBlur={() => {
426+
if(sel.url && eHasValue(sel.token || '')) {
427+
verifyCustomAlgorithm(sel.__key, sel.token);
428+
}
429+
}}
430+
placeholder="••••••••"
431+
error={Boolean(customMeta.tokenRequired && !sel.token)}
432+
helperText={customMeta.tokenRequired && !sel.token ? 'Authentication is required for this algorithm.' : ''}
433+
/>
323434
<Stack direction={{ xs: "column", sm: "row" }} spacing={1.5}>
324435
<TextField
325436
sx={{width: '50%'}}
@@ -346,34 +457,14 @@ export default function MultiAlgoSelector({
346457
helperText={errors[sel.__key]?.name ? t('map_project.algo_conflicting_name') : ''}
347458
/>
348459
</Stack>
349-
<Stack direction={{ xs: "column", sm: "row" }} sx={{marginTop: '12px'}} spacing={1.5}>
350460
<TextField
351461
fullWidth
352462
label={t('common.description')}
353463
value={sel.description || ''}
354464
onChange={(e) =>
355-
updateSelected(sel.__key, { id: e.target.value || '' })
465+
updateSelected(sel.__key, { description: e.target.value || '' })
356466
}
357467
/>
358-
</Stack>
359-
<Stack spacing={1.5} sx={{marginTop: '12px'}}>
360-
<TextField
361-
fullWidth
362-
required
363-
label={t('map_project.api_url')}
364-
value={sel.url || ""}
365-
onChange={(e) => updateSelected(sel.__key, { url: e.target.value })}
366-
placeholder="https://example.com/match"
367-
/>
368-
<TextField
369-
fullWidth
370-
type='password'
371-
label={t('map_project.api_token')}
372-
value={sel.token || ""}
373-
onChange={(e) => updateSelected(sel.__key, { token: e.target.value })}
374-
placeholder="••••••••"
375-
/>
376-
377468
<Stack direction={{ xs: "column", sm: "row" }} spacing={1.5}>
378469
<TextField
379470
label={t('map_project.batch_size')}
@@ -511,3 +602,7 @@ function clampInt(value, min, max) {
511602
if (Number.isNaN(n)) return min;
512603
return Math.max(min, Math.min(max, n));
513604
}
605+
606+
function eHasValue(value) {
607+
return Boolean(value && String(value).trim());
608+
}

0 commit comments

Comments
 (0)