diff --git a/frontend/src/locale/de.json b/frontend/src/locale/de.json
index 50af99e601..6d3cf5237b 100644
--- a/frontend/src/locale/de.json
+++ b/frontend/src/locale/de.json
@@ -511,6 +511,7 @@
"ROW": "Zeile",
"APPLY_TO_ALL_ROWS_NOTE": "+ kennzeichnet, dass die Änderung auf alle Zeilen dieser Spalte angewendet werden kann",
"BULK_IMPORT_DELETE_TASK": "Massenimport-Aufgabe löschen",
+ "BULK_IMPORT_DELETE_TASK_IN_PROGRESS": "Wird gelöscht...",
"BULK_IMPORT_DATA_UPLOADED": "Daten hochgeladen",
"BULK_IMPORT_IMAGE_UPLOADED": "Bild hochgeladen",
"SPREADSHEET_UPLOADED_TITLE": "Tabellen-Datei hochgeladen: {fileName}",
@@ -764,6 +765,7 @@
"INDIVIDUAL_SCORE": "Individueller Score",
"IMAGE_SCORE": "Bild-Score",
"NUMBER_OF_RESULTS": "Anzahl der Ergebnisse",
+ "NUMBER_OF_RESULTS_MAX_HINT": "(max. {max})",
"SELECT_A_PROJECT": "ein Projekt auswählen",
"MATCHED_BASED_ON": "Übereinstimmung basierend auf ",
"POSSIBLE_MATCH": "Mögliche Übereinstimmung",
diff --git a/frontend/src/locale/en.json b/frontend/src/locale/en.json
index e2942a0460..8654c6e0d3 100644
--- a/frontend/src/locale/en.json
+++ b/frontend/src/locale/en.json
@@ -509,6 +509,7 @@
"ROW": "Row",
"APPLY_TO_ALL_ROWS_NOTE": "+ denotes change can apply to all rows for this column",
"BULK_IMPORT_DELETE_TASK": "Delete Import Task",
+ "BULK_IMPORT_DELETE_TASK_IN_PROGRESS": "Deleting...",
"BULK_IMPORT_DATA_UPLOADED": "Data Uploaded",
"BULK_IMPORT_IMAGE_UPLOADED": "Image Uploaded",
"SPREADSHEET_UPLOADED_TITLE": "Spreadsheet Uploaded: {fileName}",
@@ -762,6 +763,7 @@
"INDIVIDUAL_SCORE": "Individual Score",
"IMAGE_SCORE": "Image Score",
"NUMBER_OF_RESULTS": "Number of results",
+ "NUMBER_OF_RESULTS_MAX_HINT": "(max {max})",
"SELECT_A_PROJECT": "select a project",
"MATCHED_BASED_ON": "Matched based on ",
"POSSIBLE_MATCH": "Possible Match",
diff --git a/frontend/src/locale/es.json b/frontend/src/locale/es.json
index 78f88a12d2..bca4a0bc5e 100644
--- a/frontend/src/locale/es.json
+++ b/frontend/src/locale/es.json
@@ -511,6 +511,7 @@
"ROW": "Fila",
"APPLY_TO_ALL_ROWS_NOTE": "+ denota que el cambio se puede aplicar a todas las filas de esta columna",
"BULK_IMPORT_DELETE_TASK": "Eliminar tarea de importación masiva",
+ "BULK_IMPORT_DELETE_TASK_IN_PROGRESS": "Eliminando...",
"BULK_IMPORT_DATA_UPLOADED": "Datos subidos",
"BULK_IMPORT_IMAGE_UPLOADED": "Imagen subida",
"SPREADSHEET_UPLOADED_TITLE": "Hoja de cálculo subida: {fileName}",
@@ -764,6 +765,7 @@
"INDIVIDUAL_SCORE": "Puntuación Individual",
"IMAGE_SCORE": "Puntuación de Imagen",
"NUMBER_OF_RESULTS": "Número de resultados",
+ "NUMBER_OF_RESULTS_MAX_HINT": "(máx. {max})",
"SELECT_A_PROJECT": "seleccionar un proyecto",
"MATCHED_BASED_ON": "Coincidencia basada en ",
"POSSIBLE_MATCH": "Posible Coincidencia",
diff --git a/frontend/src/locale/fr.json b/frontend/src/locale/fr.json
index f6cdb7970b..8adf5a6ab2 100644
--- a/frontend/src/locale/fr.json
+++ b/frontend/src/locale/fr.json
@@ -511,6 +511,7 @@
"ROW": "Ligne",
"APPLY_TO_ALL_ROWS_NOTE": "+ indique que la modification peut s'appliquer à toutes les lignes de cette colonne",
"BULK_IMPORT_DELETE_TASK": "Supprimer la tâche d'importation en masse",
+ "BULK_IMPORT_DELETE_TASK_IN_PROGRESS": "Suppression...",
"BULK_IMPORT_DATA_UPLOADED": "Données téléchargées",
"BULK_IMPORT_IMAGE_UPLOADED": "Image téléchargée",
"SPREADSHEET_UPLOADED_TITLE": "Feuille de calcul téléchargée: {fileName}",
@@ -764,6 +765,7 @@
"INDIVIDUAL_SCORE": "Score Individuel",
"IMAGE_SCORE": "Score d'Image",
"NUMBER_OF_RESULTS": "Nombre de résultats",
+ "NUMBER_OF_RESULTS_MAX_HINT": "(max {max})",
"SELECT_A_PROJECT": "sélectionner un projet",
"MATCHED_BASED_ON": "Correspondance basée sur ",
"POSSIBLE_MATCH": "Correspondance Possible",
diff --git a/frontend/src/locale/it.json b/frontend/src/locale/it.json
index 9afee9807f..60a69d041b 100644
--- a/frontend/src/locale/it.json
+++ b/frontend/src/locale/it.json
@@ -511,6 +511,7 @@
"ROW": "Riga",
"APPLY_TO_ALL_ROWS_NOTE": "+ indica che la modifica può essere applicata a tutte le righe di questa colonna",
"BULK_IMPORT_DELETE_TASK": "Elimina attività di importazione",
+ "BULK_IMPORT_DELETE_TASK_IN_PROGRESS": "Eliminazione in corso...",
"BULK_IMPORT_DATA_UPLOADED": "Dati caricati",
"BULK_IMPORT_IMAGE_UPLOADED": "Immagine caricata",
"SPREADSHEET_UPLOADED_TITLE": "Foglio di calcolo caricato: {fileName}",
@@ -764,6 +765,7 @@
"INDIVIDUAL_SCORE": "Punteggio Individuale",
"IMAGE_SCORE": "Punteggio Immagine",
"NUMBER_OF_RESULTS": "Numero di risultati",
+ "NUMBER_OF_RESULTS_MAX_HINT": "(max {max})",
"SELECT_A_PROJECT": "seleziona un progetto",
"MATCHED_BASED_ON": "Corrispondenza basata su ",
"POSSIBLE_MATCH": "Possibile Corrispondenza",
diff --git a/frontend/src/pages/BulkImport/BulkImportTask.jsx b/frontend/src/pages/BulkImport/BulkImportTask.jsx
index 5ccab73160..6c7fecc0b5 100644
--- a/frontend/src/pages/BulkImport/BulkImportTask.jsx
+++ b/frontend/src/pages/BulkImport/BulkImportTask.jsx
@@ -35,7 +35,9 @@ const BulkImportTask = observer(() => {
const [userRoles, setUserRoles] = useState(null);
const store = useLocalObservable(() => new BulkImportTaskStore());
const [rowsPerPage, setRowsPerPage] = useState(10);
-
+ const [isDeleting, setIsDeleting] = useState(false);
+ const [isSendingToIdentification, setIsSendingToIdentification] =
+ useState(false);
const previousLocationID = task?.matchingLocations || [];
const fetchData = async () => {
@@ -76,12 +78,14 @@ const BulkImportTask = observer(() => {
const deleteTask = async () => {
if (!task?.id) return;
+ if (isDeleting) return;
const confirmed = window.confirm(
intl.formatMessage({ id: "BULK_IMPORT_DELETE_TASK_CONFIRM" }),
);
if (!confirmed) return;
+ setIsDeleting(true);
try {
const res = await fetch(`/api/v3/bulk-import/${task.id}`, {
method: "DELETE",
@@ -102,6 +106,7 @@ const BulkImportTask = observer(() => {
{ error: err.message || "" },
),
);
+ setIsDeleting(false);
}
};
@@ -486,50 +491,50 @@ const BulkImportTask = observer(() => {
{
+ onClick={async () => {
setShowError(false);
- axios
- .get(
+ setIsSendingToIdentification(true);
+
+ try {
+ const response = await axios.get(
`/appadmin/resendBulkImportID.jsp?importIdTask=${taskId}${store.locationIDString}`,
- )
- .then((response) => {
- if (response.status === 200) {
- alert(
- intl.formatMessage({
- id: "BULK_IMPORT_RE_ID_SUCCESS",
- defaultMessage:
- "Re-identification task started successfully.",
- }),
- );
- window.location.reload();
- } else {
- throw new Error(
- intl.formatMessage({
- id: "BULK_IMPORT_RE_ID_ERROR",
- defaultMessage:
- "Failed to start re-identification task.",
- }),
- );
- }
- })
- .catch((error) => {
- console.error(
- "Error starting re-identification task:",
- error,
- );
+ );
+
+ if (response.status === 200) {
alert(
+ intl.formatMessage({
+ id: "BULK_IMPORT_RE_ID_SUCCESS",
+ defaultMessage:
+ "Re-identification task started successfully.",
+ }),
+ );
+ window.location.reload();
+ } else {
+ throw new Error(
intl.formatMessage({
id: "BULK_IMPORT_RE_ID_ERROR",
defaultMessage: "Failed to start re-identification task.",
}),
);
- });
+ }
+ } catch (error) {
+ console.error("Error starting re-identification task:", error);
+ alert(
+ intl.formatMessage({
+ id: "BULK_IMPORT_RE_ID_ERROR",
+ defaultMessage: "Failed to start re-identification task.",
+ }),
+ );
+ } finally {
+ setIsSendingToIdentification(false);
+ }
}}
backgroundColor={theme.wildMeColors.cyan700}
color={theme.defaultColors.white}
@@ -541,6 +546,15 @@ const BulkImportTask = observer(() => {
marginLeft: 0,
}}
>
+ {isSendingToIdentification && (
+
+ )}
{((!userRoles?.includes("admin") &&
@@ -568,6 +582,7 @@ const BulkImportTask = observer(() => {
{
marginLeft: 0,
marginTop: "1rem",
marginBottom: "2rem",
+ opacity: isDeleting ? 0.7 : 1,
+ cursor: isDeleting ? "not-allowed" : "pointer",
}}
>
-
+ {isDeleting ? (
+ <>
+
+
+ >
+ ) : (
+
+ )}
diff --git a/frontend/src/pages/MatchResultsPage/MatchResults.jsx b/frontend/src/pages/MatchResultsPage/MatchResults.jsx
index 2f5eaaf317..34bbf78da2 100644
--- a/frontend/src/pages/MatchResultsPage/MatchResults.jsx
+++ b/frontend/src/pages/MatchResultsPage/MatchResults.jsx
@@ -15,6 +15,7 @@ import FilterIcon from "./icons/FilterIcon";
import MatchCriteriaDrawer from "./components/MatchCriteriaDrawer";
import MultiSelectWithCheckbox from "../../components/MultiSelectWithCheckbox";
import ContainerWithSpinner from "../../components/ContainerWithSpinner";
+import { MAX_NUM_RESULTS } from "./constants";
const MatchResults = observer(() => {
const themeColor = React.useContext(ThemeColorContext);
@@ -234,6 +235,16 @@ const MatchResults = observer(() => {
>
+
+
+
- axios.patch(
- `/api/v3/encounters/${encodeURIComponent(id)}`,
- patchOps,
- {
+ axios
+ .patch(`/api/v3/encounters/${encodeURIComponent(id)}`, patchOps, {
headers: {
"Content-Type": "application/json-patch+json",
Accept: "application/json",
},
- },
- ).then(
- (response) => ({ status: "fulfilled", encounterId: id, response }),
- (error) => ({ status: "rejected", encounterId: id, error }),
- ),
+ })
+ .then(
+ (response) => ({ status: "fulfilled", encounterId: id, response }),
+ (error) => ({ status: "rejected", encounterId: id, error }),
+ ),
);
const results = await Promise.allSettled(patchPromises);
@@ -601,14 +614,17 @@ export default class MatchResultsStore {
ok: false,
error: "CREATE_NEW_INDIVIDUAL_PARTIAL",
successes,
- failures: failures.map((f) => ({ encounterId: f.encounterId, error: f.error?.message || String(f.error) })),
+ failures: failures.map((f) => ({
+ encounterId: f.encounterId,
+ error: f.error?.message || String(f.error),
+ })),
};
}
this.resetSelectionToQuery();
toast.success("New individual created successfully!");
return { ok: true, successes };
- } catch (e) {
+ } catch {
this._matchRequestError = "CREATE_NEW_INDIVIDUAL_FAILED";
toast.error("Failed to create new individual");
return { ok: false, error: "CREATE_NEW_INDIVIDUAL_FAILED" };
@@ -702,7 +718,7 @@ export default class MatchResultsStore {
this.resetSelectionToQuery();
toast.success("Match confirmed successfully!");
return res.data;
- } catch (e) {
+ } catch {
this._matchRequestError = "MATCH_FAILED";
toast.error("Failed to confirm match");
return null;
@@ -751,7 +767,7 @@ export default class MatchResultsStore {
this.resetSelectionToQuery();
toast.success("Merge page opened successfully!");
return { ok: true };
- } catch (e) {
+ } catch {
this._matchRequestError = "MERGE_FAILED";
toast.error("Failed to start merge");
return null;
diff --git a/src/main/java/org/ecocean/Annotation.java b/src/main/java/org/ecocean/Annotation.java
index 577eeaec07..5ed14c612f 100644
--- a/src/main/java/org/ecocean/Annotation.java
+++ b/src/main/java/org/ecocean/Annotation.java
@@ -13,6 +13,8 @@
import org.ecocean.api.ApiException;
import org.ecocean.ia.IA;
import org.ecocean.ia.IAException;
+import org.ecocean.ia.MatchResult;
+import org.ecocean.ia.MatchResultProspect;
import org.ecocean.ia.MLService;
import org.ecocean.ia.Task;
import org.ecocean.identity.IBEISIA;
@@ -1598,7 +1600,6 @@ public static Base createFromApi(JSONObject payload, List
files, Shepherd
foundTrivial + " (and Feature) from " + ma + " and " + enc);
}
}
-
// we queue for embedding extraction so this is done in the background
// and frees up foreground api process to return results to user
// TODO myShepherd commit doesnt happen until we return; potential race condition on IA queue?
@@ -1606,7 +1607,8 @@ public static Base createFromApi(JSONObject payload, List files, Shepherd
task.addObject(ann);
task.setStatusDetailsAddLog("Annotation.createFromApi() embedding extraction on " + ann);
myShepherd.getPM().makePersistent(task);
- System.out.println("[INFO] Annotation.createFromApi(): queueing for embedding extraction with " + task);
+ System.out.println(
+ "[INFO] Annotation.createFromApi(): queueing for embedding extraction with " + task);
ann.queueForEmbeddingExtraction(task, myShepherd);
return ann;
}
@@ -1736,6 +1738,59 @@ public int detachFromTasks(Shepherd myShepherd) {
return tasks.size();
}
+ // we cant just detach the annots from match results, so we need
+ // to kill them off before we can delete an Annotation
+ public long deleteMatchResults(Shepherd myShepherd) {
+ return myShepherd.deleteMatchResults(this);
+ }
+
+ // similar as above for MatchResultProspects
+ public int deleteMatchResultProspects(Shepherd myShepherd) {
+ List mrps = myShepherd.getMatchResultProspects(this);
+ int ct = 0;
+
+ for (MatchResultProspect mrp : mrps) {
+ ct++;
+ System.out.println("[DEBUG] (" + ct + ") ann.deleteMatchResultProspects() on id=" +
+ this.getId() + " deleting " + mrp);
+ myShepherd.getPM().deletePersistent(mrp);
+ }
+ return ct;
+ }
+
+ // when we delete an Annotation, we usually dont want to leave the Embeddings around
+ public int deleteEmbeddings(Shepherd myShepherd) {
+ int rtn = numberEmbeddings();
+
+ if (rtn < 1) return 0;
+ for (Embedding emb : embeddings) {
+ System.out.println("[DEBUG] ann.deleteEmbeddings() on id=" + this.getId() +
+ " deleting " + emb);
+ myShepherd.getPM().deletePersistent(emb);
+ }
+ return rtn;
+ }
+
+ // a convenient method which does a typical set of steps to ready Annotation for deletion from db
+ // if encounter is already known, it can be passed (null will be ignored)
+ public void prepareForDeletion(Shepherd myShepherd, Encounter enc) {
+ int nt = this.detachFromTasks(myShepherd);
+ long t = System.currentTimeMillis();
+
+ if (enc != null) enc.removeAnnotation(this);
+ this.detachFromMediaAsset();
+ long nm = this.deleteMatchResults(myShepherd);
+ int np = this.deleteMatchResultProspects(myShepherd);
+ int ne = this.deleteEmbeddings(myShepherd);
+ System.out.println("[INFO] ann.prepareForDeletion() [" + (System.currentTimeMillis() - t) + "ms]: " + nt + " Tasks, " + nm +
+ " MatchResults, " + np + " MatchResultProspects, " + ne + " Embeddings on " + this);
+ }
+
+ // this version takes no enc, but will attempt to find it
+ public void prepareForDeletion(Shepherd myShepherd) {
+ prepareForDeletion(myShepherd, this.findEncounter(myShepherd));
+ }
+
public static boolean isValidViewpoint(String vp) {
if (vp == null) return true;
return getAllValidViewpoints().contains(vp);
diff --git a/src/main/java/org/ecocean/api/SiteSettings.java b/src/main/java/org/ecocean/api/SiteSettings.java
index aa03a42ee2..3af244e14d 100644
--- a/src/main/java/org/ecocean/api/SiteSettings.java
+++ b/src/main/java/org/ecocean/api/SiteSettings.java
@@ -132,7 +132,9 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response)
for (JSONObject idOpt : iaConfig.identOpts(tx, iaClass)) {
// make a copy so we can safely modify it
JSONObject idOptCopy = new JSONObject(idOpt.toString());
- idOptCopy.remove("api_endpoint"); // dont want this shown
+ // FIXME we need to leave this endpoint in on site-settings for now
+ // as it is needed to kick off the matching from the client
+ // idOptCopy.remove("api_endpoint");
// NOTE: JSONObject.toString() in theory might produce different strings
// for the same object (key ordering different); but in practice seems to
// be consistent within these iterations
diff --git a/src/main/java/org/ecocean/api/patch/EncounterPatchValidator.java b/src/main/java/org/ecocean/api/patch/EncounterPatchValidator.java
index 540226db18..3336192cbd 100644
--- a/src/main/java/org/ecocean/api/patch/EncounterPatchValidator.java
+++ b/src/main/java/org/ecocean/api/patch/EncounterPatchValidator.java
@@ -234,9 +234,7 @@ public static JSONObject applyPatch(Encounter enc, JSONObject patch, User user,
throw new ApiException("no such annotation id=" + value.toString(),
ApiException.ERROR_RETURN_CODE_INVALID);
MediaAsset ma = ann.getMediaAsset();
- ann.detachFromTasks(myShepherd);
- enc.removeAnnotation(ann);
- ann.detachFromMediaAsset();
+ ann.prepareForDeletion(myShepherd, enc);
// "most likely" this encounter is now detached from the asset, but we want them still connected
// TODO parts might be connecting these, but how do we determine if we still need to add the trivial?
if (ma != null) {
@@ -345,8 +343,7 @@ private static JSONObject testJsonValue(Object value, String[] validFields)
}
// should never get called here with null value
- private static MarkedIndividual getOrCreateMarkedIndividual(Object value,
- Shepherd myShepherd)
+ private static MarkedIndividual getOrCreateMarkedIndividual(Object value, Shepherd myShepherd)
throws ApiException {
String idOrName = null;
MarkedIndividual indiv = null;
@@ -356,7 +353,6 @@ private static MarkedIndividual getOrCreateMarkedIndividual(Object value,
// but we ignore that for now :) related, we dont check if this individual
// exists with getMarkedIndividual() first
JSONObject nameData = (JSONObject)value;
-
String type = nameData.optString("type", "NO_TYPE_GIVEN");
// right now we only support type=locationId, but may expand later
if (type.equals("locationId")) {
@@ -365,16 +361,17 @@ private static MarkedIndividual getOrCreateMarkedIndividual(Object value,
idOrName = MarkedIndividual.nextNameByLocationId(locationId);
} catch (IllegalArgumentException ex) {
// can fail for various reasons like invalid locationId or one without a prefix
- throw new ApiException("could not get next individual name for locationId (" + locationId + "): " + ex.getMessage(),
- ApiException.ERROR_RETURN_CODE_INVALID);
+ throw new ApiException("could not get next individual name for locationId (" +
+ locationId + "): " + ex.getMessage(),
+ ApiException.ERROR_RETURN_CODE_INVALID);
}
} else {
throw new ApiException("invalid type passed for new individual creation: " + type,
- ApiException.ERROR_RETURN_CODE_INVALID);
+ ApiException.ERROR_RETURN_CODE_INVALID);
}
// if we fall through to here we should have idOrName to create a new one
- System.out.println("[DEBUG] getOrCreateMarkedIndividual() creating '" + idOrName + "' based on " + nameData);
-
+ System.out.println("[DEBUG] getOrCreateMarkedIndividual() creating '" + idOrName +
+ "' based on " + nameData);
} else { // not json, so must have a name to find/create
idOrName = value.toString();
indiv = myShepherd.getMarkedIndividual(idOrName);
diff --git a/src/main/java/org/ecocean/ia/MatchResult.java b/src/main/java/org/ecocean/ia/MatchResult.java
index c3ca6897ed..ebaec986f9 100644
--- a/src/main/java/org/ecocean/ia/MatchResult.java
+++ b/src/main/java/org/ecocean/ia/MatchResult.java
@@ -38,12 +38,18 @@ public class MatchResult implements java.io.Serializable {
private Set prospects;
private Annotation queryAnnotation;
private int numberCandidates = 0;
+ // we store *actual* count here, but they may not all exist
+ // via .prospects due to MAXIMUM_PROSPECTS_STORED (see below)
+ private int numberProspects = 0;
// not sure we really *need* true fk link to these annots
// they might be gone now and will we ever use this?
// so for now we just populate numberCandidates
private Set candidates;
// fallback number to cutoff number of prospects to return
public static final int DEFAULT_PROSPECTS_CUTOFF = 100;
+ // number of MatchResultProspects [per type] to actually store (hotspotter
+ // results can produce thousands, but storing them all is excessive)
+ public static final int MAXIMUM_PROSPECTS_STORED = 500;
public MatchResult() {
id = Util.generateUUID();
@@ -159,6 +165,7 @@ private int populateProspects(String type, JSONArray annotIds, JSONArray scores,
if (this.prospects == null)
this.prospects = new HashSet();
int num = 0;
+ this.numberProspects += annotIds.length(); // true number of prospects
for (int i = 0; i < annotIds.length(); i++) {
double score = scores.optDouble(i, -Double.MAX_VALUE);
String id = IBEISIA.fromFancyUUID(annotIds.optJSONObject(i));
@@ -174,11 +181,18 @@ private int populateProspects(String type, JSONArray annotIds, JSONArray scores,
ma = createInspectionHeatmapAsset(externRef, id, myShepherd);
this.prospects.add(new MatchResultProspect(ann, score, type, ma));
num++;
+ if (num >= MAXIMUM_PROSPECTS_STORED) {
+ System.out.println("[DEBUG] hit max (" + MAXIMUM_PROSPECTS_STORED +
+ ") number storable prospects on " + this);
+ break;
+ }
}
return num;
}
// we just have a list of annots which matched (e.g. via vectors in opensearch)
+ // NOTE: currently does not check MAXIMUM_PROSPECTS_STORED because vector search
+ // tends to return relatively few prospects. TODO adjust later if this proves untrue.
private int populateProspects(List annots, boolean scoreByIndividual,
Shepherd myShepherd)
throws IOException {
@@ -192,10 +206,12 @@ private int populateProspects(List annots, boolean scoreByIndividual
// these scores are direct from opensearch
for (Annotation ann : annots) {
MediaAsset ma = createInspectionPairxAsset(this.queryAnnotation, ann, myShepherd);
- this.prospects.add(new MatchResultProspect(ann, ann.getOpensearchScore(), "annot", ma));
+ this.prospects.add(new MatchResultProspect(ann, ann.getOpensearchScore(), "annot",
+ ma));
}
}
- return this.prospects.size();
+ this.numberProspects = this.prospects.size();
+ return this.numberProspects;
}
private void _populateProspectsByIndividual(List annots, Shepherd myShepherd) {
@@ -394,7 +410,7 @@ public int numberCandidates() {
}
*/
public int numberProspects() {
- return Util.collectionSize(prospects);
+ return this.numberProspects;
}
public Set prospectScoreTypes() {
diff --git a/src/main/java/org/ecocean/ia/MatchResultProspect.java b/src/main/java/org/ecocean/ia/MatchResultProspect.java
index a10bee63c4..32f6b1b71c 100644
--- a/src/main/java/org/ecocean/ia/MatchResultProspect.java
+++ b/src/main/java/org/ecocean/ia/MatchResultProspect.java
@@ -57,7 +57,7 @@ public boolean isInProjects(Set projectIds, Shepherd myShepherd) {
}
public String toString() {
- return scoreType + ": " + score + " on " + annotation;
+ return scoreType + "=" + score + " on " + annotation + " for " + matchResult;
}
public JSONObject jsonForApiGet(Shepherd myShepherd) {
diff --git a/src/main/java/org/ecocean/servlet/AnnotationEdit.java b/src/main/java/org/ecocean/servlet/AnnotationEdit.java
index 24e327ca8e..d0cacb4f3b 100644
--- a/src/main/java/org/ecocean/servlet/AnnotationEdit.java
+++ b/src/main/java/org/ecocean/servlet/AnnotationEdit.java
@@ -42,7 +42,6 @@ public class AnnotationEdit extends HttpServlet {
myShepherd.beginDBTransaction();
JSONObject jsonIn = ServletUtilities.jsonFromHttpServletRequest(request);
PrintWriter out = response.getWriter();
-
User user = AccessControl.getUser(request, myShepherd);
boolean isAdmin = false;
if (user != null)
@@ -163,6 +162,9 @@ public class AnnotationEdit extends HttpServlet {
myShepherd.getPM().deletePersistent(enc);
rtn.put("encounterDeleted", true);
}
+ annot.deleteMatchResults(myShepherd);
+ annot.deleteMatchResultProspects(myShepherd);
+ annot.deleteEmbeddings(myShepherd);
myShepherd.getPM().deletePersistent(annot);
myShepherd.getPM().deletePersistent(feat);
System.out.println(
diff --git a/src/main/java/org/ecocean/servlet/EncounterDelete.java b/src/main/java/org/ecocean/servlet/EncounterDelete.java
index 8fd28a3959..02309c2123 100644
--- a/src/main/java/org/ecocean/servlet/EncounterDelete.java
+++ b/src/main/java/org/ecocean/servlet/EncounterDelete.java
@@ -20,9 +20,9 @@
// import java.util.Vector;
import java.util.concurrent.ThreadPoolExecutor;
-import org.ecocean.shepherd.core.Shepherd;
-import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
+import org.ecocean.shepherd.core.Shepherd;
public class EncounterDelete extends HttpServlet {
private static final Logger log = LogManager.getLogger(EncounterDelete.class);
@@ -162,15 +162,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response)
ArrayList anns = enc2trash.getAnnotations();
for (Annotation ann : anns) {
myShepherd.beginDBTransaction();
- enc2trash.removeAnnotation(ann);
- myShepherd.updateDBTransaction();
- List iaTasks = Task.getTasksFor(ann, myShepherd);
- if (iaTasks != null && !iaTasks.isEmpty()) {
- for (Task iaTask : iaTasks) {
- iaTask.removeObject(ann);
- myShepherd.updateDBTransaction();
- }
- }
+ ann.prepareForDeletion(myShepherd, enc2trash);
myShepherd.throwAwayAnnotation(ann);
myShepherd.commitDBTransaction();
}
diff --git a/src/main/java/org/ecocean/servlet/EncounterRemoveAnnotation.java b/src/main/java/org/ecocean/servlet/EncounterRemoveAnnotation.java
index 8841fe0e4e..57518f4102 100644
--- a/src/main/java/org/ecocean/servlet/EncounterRemoveAnnotation.java
+++ b/src/main/java/org/ecocean/servlet/EncounterRemoveAnnotation.java
@@ -15,8 +15,8 @@
import java.io.*;
import java.util.List;
-import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.LogManager;
public class EncounterRemoveAnnotation extends HttpServlet {
private static final Logger log = LogManager.getLogger(EncounterRemoveAnnotation.class);
@@ -104,6 +104,9 @@ public void doPost(HttpServletRequest request, HttpServletResponse response)
enc.addComments("Annotation deleted by " + user.getDisplayName() + " on " +
Util.prettyTimeStamp() + "
");
+ ann.deleteMatchResults(myShepherd);
+ ann.deleteMatchResultProspects(myShepherd);
+ ann.deleteEmbeddings(myShepherd);
myShepherd.getPM().deletePersistent(ann);
myShepherd.updateDBTransaction();
res.put("revertToTrivial", true);
@@ -121,6 +124,9 @@ else if (!ann.isTrivial()) {
"\">Annotation deleted by " + user.getDisplayName() + " on " +
Util.prettyTimeStamp() + "");
enc.removeAnnotation(ann);
+ ann.deleteMatchResults(myShepherd);
+ ann.deleteMatchResultProspects(myShepherd);
+ ann.deleteEmbeddings(myShepherd);
myShepherd.getPM().deletePersistent(ann);
myShepherd.commitDBTransaction();
}
diff --git a/src/main/java/org/ecocean/servlet/importer/DeleteImportTask.java b/src/main/java/org/ecocean/servlet/importer/DeleteImportTask.java
index a8e35c819e..56a6950e08 100644
--- a/src/main/java/org/ecocean/servlet/importer/DeleteImportTask.java
+++ b/src/main/java/org/ecocean/servlet/importer/DeleteImportTask.java
@@ -9,8 +9,8 @@
import org.ecocean.Project;
import org.ecocean.security.Collaboration;
import org.ecocean.servlet.ServletUtilities;
-import org.ecocean.social.SocialUnit;
import org.ecocean.shepherd.core.Shepherd;
+import org.ecocean.social.SocialUnit;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
@@ -62,15 +62,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response)
List projects = myShepherd.getProjectsForEncounter(enc);
ArrayList anns = enc.getAnnotations();
for (Annotation ann : anns) {
- enc.removeAnnotation(ann);
- myShepherd.updateDBTransaction();
- List iaTasks = Task.getTasksFor(ann, myShepherd);
- if (iaTasks != null && !iaTasks.isEmpty()) {
- for (Task iaTask : iaTasks) {
- iaTask.removeObject(ann);
- myShepherd.updateDBTransaction();
- }
- }
+ ann.prepareForDeletion(myShepherd, enc);
myShepherd.throwAwayAnnotation(ann);
myShepherd.updateDBTransaction();
}
diff --git a/src/main/java/org/ecocean/servlet/importer/ImportTask.java b/src/main/java/org/ecocean/servlet/importer/ImportTask.java
index 73906e0b10..e8ad049079 100644
--- a/src/main/java/org/ecocean/servlet/importer/ImportTask.java
+++ b/src/main/java/org/ecocean/servlet/importer/ImportTask.java
@@ -403,7 +403,8 @@ public JSONObject statsAnnotations(Shepherd myShepherd) {
// this records only most recent task statuses like: numLatestTask_complete
if (latestTask) {
String latestStatus = "numLatestTask_" + atask.getStatus(myShepherd);
- System.out.println("[DEBUG] (ImportTask " + this.getId() + ") latestStatus for Task " + atask.getId() + ": " + latestStatus);
+ System.out.println("[DEBUG] (ImportTask " + this.getId() +
+ ") latestStatus for Task " + atask.getId() + ": " + latestStatus);
if (sa.has(latestStatus)) {
sa.put(latestStatus, sa.optInt(latestStatus, 0) + 1);
} else {
@@ -509,17 +510,8 @@ public static void deleteWithRelated(String id, User user, Shepherd myShepherd)
List projects = myShepherd.getProjectsForEncounter(enc);
ArrayList anns = enc.getAnnotations();
for (Annotation ann : anns) {
- enc.removeAnnotation(ann);
- // myShepherd.updateDBTransaction();
- List iaTasks = Task.getTasksFor(ann, myShepherd);
- if (iaTasks != null && !iaTasks.isEmpty()) {
- for (Task iaTask : iaTasks) {
- iaTask.removeObject(ann);
- // myShepherd.updateDBTransaction();
- }
- }
+ ann.prepareForDeletion(myShepherd, enc);
myShepherd.throwAwayAnnotation(ann);
- // myShepherd.updateDBTransaction();
}
// handle occurrences
if (occ != null) {
@@ -638,7 +630,9 @@ public JSONObject iaSummaryJson(Shepherd myShepherd) {
pj.put("detectionPercent", 1.0);
pj.put("detectionStatus", "complete");
} else {
- if (numAssets > 0) pj.put("detectionPercent", new Double(numDetectionComplete) / new Double(numAssets));
+ if (numAssets > 0)
+ pj.put("detectionPercent",
+ new Double(numDetectionComplete) / new Double(numAssets));
pj.put("detectionStatus", "sent");
}
if (this.iaTaskRequestedIdentification()) {
@@ -695,11 +689,12 @@ public JSONObject iaSummaryJson(Shepherd myShepherd) {
static Map parseSqlCountResults(Query query) {
Map map = new HashMap<>();
+
try {
List> results = query.executeList();
for (Object row : results) {
- Object[] cols = (Object[]) row;
- map.put((String) cols[0], ((Number) cols[1]).intValue());
+ Object[] cols = (Object[])row;
+ map.put((String)cols[0], ((Number)cols[1]).intValue());
}
} catch (Exception e) {
e.printStackTrace();
@@ -712,28 +707,28 @@ static Map parseSqlCountResults(Query query) {
public static Map getAllEncounterCounts(Shepherd myShepherd) {
Query query = myShepherd.getPM().newQuery("javax.jdo.query.SQL",
"SELECT \"ID_OID\", count(*) FROM \"IMPORTTASK_ENCOUNTERS\" GROUP BY \"ID_OID\"");
+
return parseSqlCountResults(query);
}
public static Map getAllIndividualCounts(Shepherd myShepherd) {
Query query = myShepherd.getPM().newQuery("javax.jdo.query.SQL",
"SELECT ie.\"ID_OID\", count(distinct me.\"INDIVIDUALID_OID\") " +
- "FROM \"IMPORTTASK_ENCOUNTERS\" ie " +
- "JOIN \"MARKEDINDIVIDUAL_ENCOUNTERS\" me " +
- "ON ie.\"CATALOGNUMBER_EID\" = me.\"CATALOGNUMBER_EID\" " +
- "GROUP BY ie.\"ID_OID\"");
+ "FROM \"IMPORTTASK_ENCOUNTERS\" ie " + "JOIN \"MARKEDINDIVIDUAL_ENCOUNTERS\" me " +
+ "ON ie.\"CATALOGNUMBER_EID\" = me.\"CATALOGNUMBER_EID\" " + "GROUP BY ie.\"ID_OID\"");
+
return parseSqlCountResults(query);
}
public static Map getAllMediaAssetCounts(Shepherd myShepherd) {
Query query = myShepherd.getPM().newQuery("javax.jdo.query.SQL",
"SELECT ie.\"ID_OID\", count(distinct mf.\"ID_OID\") " +
- "FROM \"IMPORTTASK_ENCOUNTERS\" ie " +
- "JOIN \"ENCOUNTER_ANNOTATIONS\" ea " +
+ "FROM \"IMPORTTASK_ENCOUNTERS\" ie " + "JOIN \"ENCOUNTER_ANNOTATIONS\" ea " +
"ON ie.\"CATALOGNUMBER_EID\" = ea.\"CATALOGNUMBER_OID\" " +
"JOIN \"ANNOTATION_FEATURES\" af ON ea.\"ID_EID\" = af.\"ID_OID\" " +
"JOIN \"MEDIAASSET_FEATURES\" mf ON af.\"ID_EID\" = mf.\"ID_EID\" " +
"GROUP BY ie.\"ID_OID\"");
+
return parseSqlCountResults(query);
}
}
diff --git a/src/main/java/org/ecocean/shepherd/core/Shepherd.java b/src/main/java/org/ecocean/shepherd/core/Shepherd.java
index 52b5a1fef6..32a4c9582b 100644
--- a/src/main/java/org/ecocean/shepherd/core/Shepherd.java
+++ b/src/main/java/org/ecocean/shepherd/core/Shepherd.java
@@ -17,6 +17,7 @@
import org.ecocean.grid.ScanTask;
import org.ecocean.grid.ScanWorkItem;
import org.ecocean.ia.MatchResult;
+import org.ecocean.ia.MatchResultProspect;
import org.ecocean.ia.Task;
import org.ecocean.media.*;
import org.ecocean.movement.Path;
@@ -2830,6 +2831,46 @@ public List getMatchResults(Task task) {
return all;
}
+ public List getMatchResults(Annotation ann) {
+ List all = new ArrayList();
+
+ if (ann == null) return all;
+ String filter = "SELECT FROM org.ecocean.ia.MatchResult WHERE queryAnnotation.id == '" +
+ ann.getId() + "'";
+ Query query = pm.newQuery(filter);
+ query.setOrdering("created DESC");
+ Collection c = (Collection)query.execute();
+ if (c != null) all = new ArrayList(c);
+ query.closeAll();
+ return all;
+ }
+
+ // faster deletion of all MatchResults associated with Annotation
+ public long deleteMatchResults(Annotation ann) {
+ if (ann == null) return 0l;
+ long t = System.currentTimeMillis();
+ String filter = "SELECT FROM org.ecocean.ia.MatchResult WHERE queryAnnotation.id == '" +
+ ann.getId() + "'";
+ Query query = pm.newQuery(filter);
+ long ct = query.deletePersistentAll();
+ query.closeAll();
+ System.out.println("[DEBUG] deleteMatchResults() deleted " + ct + " [" + (System.currentTimeMillis() - t) + "ms] on " + ann);
+ return ct;
+ }
+
+ public List getMatchResultProspects(Annotation ann) {
+ List all = new ArrayList();
+
+ if (ann == null) return all;
+ String filter = "SELECT FROM org.ecocean.ia.MatchResultProspect WHERE annotation.id == '" +
+ ann.getId() + "'";
+ Query query = pm.newQuery(filter);
+ Collection c = (Collection)query.execute();
+ if (c != null) all = new ArrayList(c);
+ query.closeAll();
+ return all;
+ }
+
public MarkedIndividual getMarkedIndividualQuiet(String name) {
MarkedIndividual indiv = null;
diff --git a/src/main/resources/org/ecocean/ia/package.jdo b/src/main/resources/org/ecocean/ia/package.jdo
index b47b49b153..0e69cd893f 100755
--- a/src/main/resources/org/ecocean/ia/package.jdo
+++ b/src/main/resources/org/ecocean/ia/package.jdo
@@ -86,7 +86,7 @@ alter table "TASK" alter column "PARAMETERS" type text;
-
+