diff --git a/frontend/src/locale/de.json b/frontend/src/locale/de.json index e56a3e7a8e..b4a529312e 100644 --- a/frontend/src/locale/de.json +++ b/frontend/src/locale/de.json @@ -454,6 +454,8 @@ "BULK_IMPORT_INSTRUCTIONS_NEED_HELP_DOCS": "Sieh dir die {wildbookDocsLink} an oder kontaktiere den Support", "BULK_IMPORT_INSTRUCTIONS_WILDBOOK_DOCS": "Wildbook-Dokumentation", "BULK_IMPORT_INSTRUCTIONS_CLOSE_BUTTON": "Schließen", + "BULKIMPORT_ERROR_REQUIRE_PREFIX": "Erforderliches Projekt-ID-Präfix fehlt", + "BULKIMPORT_ERROR_REQUIRE_NAME": "Erforderlicher Forschungsprojektname fehlt", "PHOTOS_UPLOADED_TITLE": "hochgeladene Fotos: {count}", "PHOTOS_UPLOADED": "Fotos hochgeladen", "PHOTOS_FAILED": "fehlgeschlagene Fotos", diff --git a/frontend/src/locale/en.json b/frontend/src/locale/en.json index e418cdab76..e99c73c379 100644 --- a/frontend/src/locale/en.json +++ b/frontend/src/locale/en.json @@ -453,6 +453,8 @@ "BULK_IMPORT_INSTRUCTIONS_NEED_HELP_DOCS": "Check the {wildbookDocsLink} for troubleshooting or contact support", "BULK_IMPORT_INSTRUCTIONS_WILDBOOK_DOCS": "Wildbook Docs", "BULK_IMPORT_INSTRUCTIONS_CLOSE_BUTTON": "Close", + "BULKIMPORT_ERROR_REQUIRE_PREFIX": "missing required project ID prefix", + "BULKIMPORT_ERROR_REQUIRE_NAME": "missing required research project name", "PHOTOS_UPLOADED_TITLE": "photos uploaded: {count}", "PHOTOS_UPLOADED": "photos uploaded", "PHOTOS_FAILED": "photos failed", diff --git a/frontend/src/locale/es.json b/frontend/src/locale/es.json index 49a06d6626..1bf05f3e57 100644 --- a/frontend/src/locale/es.json +++ b/frontend/src/locale/es.json @@ -452,6 +452,8 @@ "BULK_IMPORT_INSTRUCTIONS_NEED_HELP_DOCS": "Consulta {wildbookDocsLink} para resolución de problemas o contacto con soporte", "BULK_IMPORT_INSTRUCTIONS_WILDBOOK_DOCS": "Documentación de Wildbook", "BULK_IMPORT_INSTRUCTIONS_CLOSE_BUTTON": "Cerrar", + "BULKIMPORT_ERROR_REQUIRE_PREFIX": "falta el prefijo de ID del proyecto obligatorio", + "BULKIMPORT_ERROR_REQUIRE_NAME": "falta el nombre del proyecto de investigación obligatorio", "PHOTOS_UPLOADED_TITLE": "fotos subidas: {count}", "PHOTOS_UPLOADED": "fotos subidas", "PHOTOS_FAILED": "fotos fallidas", diff --git a/frontend/src/locale/fr.json b/frontend/src/locale/fr.json index 9ddfda35c8..613b513a3f 100644 --- a/frontend/src/locale/fr.json +++ b/frontend/src/locale/fr.json @@ -452,6 +452,8 @@ "BULK_IMPORT_INSTRUCTIONS_NEED_HELP_DOCS": "Consultez {wildbookDocsLink} pour le dépannage ou contactez le support", "BULK_IMPORT_INSTRUCTIONS_WILDBOOK_DOCS": "Docs Wildbook", "BULK_IMPORT_INSTRUCTIONS_CLOSE_BUTTON": "Fermer", + "BULKIMPORT_ERROR_REQUIRE_PREFIX": "préfixe d’identifiant de projet requis manquant", + "BULKIMPORT_ERROR_REQUIRE_NAME": "nom du projet de recherche requis manquant", "PHOTOS_UPLOADED_TITLE": "photos téléchargées : {count}", "PHOTOS_UPLOADED": "photos téléchargées", "PHOTOS_FAILED": "échecs photos", diff --git a/frontend/src/locale/it.json b/frontend/src/locale/it.json index 53e4f93f2b..8c017c9635 100644 --- a/frontend/src/locale/it.json +++ b/frontend/src/locale/it.json @@ -453,6 +453,8 @@ "BULK_IMPORT_INSTRUCTIONS_NEED_HELP_DOCS": "Consulta {wildbookDocsLink} per risoluzione problemi o contatta il supporto", "BULK_IMPORT_INSTRUCTIONS_WILDBOOK_DOCS": "Documentazione Wildbook", "BULK_IMPORT_INSTRUCTIONS_CLOSE_BUTTON": "Chiudi", + "BULKIMPORT_ERROR_REQUIRE_PREFIX": "manca il prefisso ID progetto obbligatorio", + "BULKIMPORT_ERROR_REQUIRE_NAME": "manca il nome del progetto di ricerca obbligatorio", "PHOTOS_UPLOADED_TITLE": "foto caricate: {count}", "PHOTOS_UPLOADED": "foto caricate", "PHOTOS_FAILED": "foto non riuscite", diff --git a/frontend/src/pages/BulkImport/BulkImportErrorSummaryBar.jsx b/frontend/src/pages/BulkImport/BulkImportErrorSummaryBar.jsx index e70b54932f..e33c24f4f1 100644 --- a/frontend/src/pages/BulkImport/BulkImportErrorSummaryBar.jsx +++ b/frontend/src/pages/BulkImport/BulkImportErrorSummaryBar.jsx @@ -11,7 +11,7 @@ const ErrorSummaryBar = observer(({ store }) => { Object.values(errors).forEach((rowErrors) => { Object.values(rowErrors).forEach((errMsg) => { - if (/required/i.test(JSON.stringify(errMsg))) { + if (/require/i.test(JSON.stringify(errMsg))) { missingField += 1; } else if ( /invalid/i.test(JSON.stringify(errMsg)) || diff --git a/frontend/src/pages/BulkImport/BulkImportStore.js b/frontend/src/pages/BulkImport/BulkImportStore.js index eda3804fb0..6dd89af030 100644 --- a/frontend/src/pages/BulkImport/BulkImportStore.js +++ b/frontend/src/pages/BulkImport/BulkImportStore.js @@ -1669,6 +1669,77 @@ export class BulkImportStore { } }); }); + + const prefixRe = /^Encounter\.project(\d+)\.projectIdPrefix$/; + const nameRe = /^Encounter\.project(\d+)\.researchProjectName$/; + + const prefixIdx = new Set(); + const nameIdx = new Set(); + + this._columnsDef.forEach((col) => { + let m = prefixRe.exec(col); + if (m) prefixIdx.add(m[1]); + + m = nameRe.exec(col); + if (m) nameIdx.add(m[1]); + }); + + const allIdx = new Set([...prefixIdx, ...nameIdx]); + + const ensureRowErrorMap = (rowIndex) => { + if (!errors[rowIndex]) errors[rowIndex] = {}; + return errors[rowIndex]; + }; + + allIdx.forEach((idx) => { + const hasPrefixCol = prefixIdx.has(idx); + const hasNameCol = nameIdx.has(idx); + + if (hasPrefixCol === hasNameCol) return; + + const prefixKey = `Encounter.project${idx}.projectIdPrefix`; + const nameKey = `Encounter.project${idx}.researchProjectName`; + + const row0 = ensureRowErrorMap(0); + + if (!hasPrefixCol && hasNameCol) { + row0[nameKey] = "BULKIMPORT_ERROR_REQUIRE_PREFIX"; + } + + if (hasPrefixCol && !hasNameCol) { + row0[prefixKey] = "BULKIMPORT_ERROR_REQUIRE_NAME"; + } + }); + + this._spreadsheetData.forEach((row, rowIndex) => { + allIdx.forEach((idx) => { + if (!prefixIdx.has(idx) || !nameIdx.has(idx)) return; + + const prefixKey = `Encounter.project${idx}.projectIdPrefix`; + const nameKey = `Encounter.project${idx}.researchProjectName`; + + const norm = (v) => String(v ?? "").trim(); + + const prefixVal = norm(row[prefixKey]); + const nameVal = norm(row[nameKey]); + + const hasPrefixVal = prefixVal !== ""; + const hasNameVal = nameVal !== ""; + + if (hasPrefixVal !== hasNameVal) { + const rowErr = ensureRowErrorMap(rowIndex); + + if (!hasPrefixVal && hasNameVal) { + rowErr[nameKey] = "BULKIMPORT_ERROR_REQUIRE_PREFIX"; + } + + if (hasPrefixVal && !hasNameVal) { + rowErr[prefixKey] = "BULKIMPORT_ERROR_REQUIRE_NAME"; + } + } + }); + }); + this._cachedValidation = { errors, warnings }; return this._cachedValidation; } diff --git a/frontend/src/pages/BulkImport/EditableDataTable.jsx b/frontend/src/pages/BulkImport/EditableDataTable.jsx index 9b1f29b78b..99cdc97c58 100644 --- a/frontend/src/pages/BulkImport/EditableDataTable.jsx +++ b/frontend/src/pages/BulkImport/EditableDataTable.jsx @@ -23,7 +23,14 @@ const EditableCell = observer( return store.getOptionsForSelectCell(columnId); }, [columnId, store]); const selectValue = useMemo(() => { - return value ? { value, label: value } : null; + if ( + value === null || + value === undefined || + String(value).trim() === "" + ) { + return null; + } + return { value, label: String(value) }; }, [value]); const [showDetail, setShowDetail] = useState(false); @@ -62,7 +69,7 @@ const EditableCell = observer( { if (e.key === "Enter") { diff --git a/src/main/java/org/ecocean/api/bulk/BulkImportUtil.java b/src/main/java/org/ecocean/api/bulk/BulkImportUtil.java index 3e651c8b94..0a51e910cc 100644 --- a/src/main/java/org/ecocean/api/bulk/BulkImportUtil.java +++ b/src/main/java/org/ecocean/api/bulk/BulkImportUtil.java @@ -139,6 +139,29 @@ public static Map validateRow(JSONObject row, Shepherd myShepher ApiException.ERROR_RETURN_CODE_INVALID)); } } + + // 1314 introduces catching incomplete project name/prefix combos + List projectPrefixes = findIndexedFieldNames(fieldNames, "Encounter.project.projectIdPrefix"); + List projectNames = findIndexedFieldNames(fieldNames, "Encounter.project.researchProjectName"); + // we use the bigger of these + int pmax = Integer.max(projectPrefixes.size(), projectNames.size()); + for (int i = 0; i < pmax; i++) { + String pnameKey = "Encounter.project" + i + ".researchProjectName"; + String pprefixKey = "Encounter.project" + i + ".projectIdPrefix"; + Object pname = getValue(rtn, pnameKey); + Object pprefix = getValue(rtn, pprefixKey); + if ((pname == null) && (pprefix == null)) continue; + if ((pname != null) && (pprefix != null)) continue; + if (pname == null) + rtn.put(pnameKey, + new BulkValidatorException("must have researchProjectName if given projectIdPrefix", + ApiException.ERROR_RETURN_CODE_REQUIRED)); + if (pprefix == null) + rtn.put(pprefixKey, + new BulkValidatorException("must have projectIdPrefix if given researchProjectName", + ApiException.ERROR_RETURN_CODE_REQUIRED)); + } + return rtn; }