mirror of
https://github.com/inaturalist/iNaturalistReactNative.git
synced 2026-05-08 23:53:05 -04:00
Handle modified but remotely deleted obs in upload process (#1628)
* Map realm observations to smaller objects for displaying in MyObservations * Handle obs remotely deleted in upload UI and process; closes #1579 * Multiple modified and remotely deleted observations handled in upload process * Revert to using full Realm objects in FlashList
This commit is contained in:
committed by
GitHub
parent
dacaec4bbd
commit
34f18dc6bc
@@ -3,8 +3,8 @@ import { INatApiError } from "api/error";
|
||||
import { deleteRemoteObservation } from "api/observations";
|
||||
import { RealmContext } from "providers/contexts";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import Observation from "realmModels/Observation";
|
||||
import { log } from "sharedHelpers/logger";
|
||||
import safeRealmWrite from "sharedHelpers/safeRealmWrite";
|
||||
import { useAuthenticatedMutation } from "sharedHooks";
|
||||
import useStore from "stores/useStore";
|
||||
|
||||
@@ -37,15 +37,8 @@ const useDeleteObservations = ( canBeginDeletions, myObservationsDispatch ): Obj
|
||||
&& !deletionsInProgress
|
||||
&& !deletionsComplete;
|
||||
|
||||
const deleteLocalObservation = useCallback( ( ) => {
|
||||
const realmObservation = realm?.objectForPrimaryKey( "Observation", uuid );
|
||||
logger.info( "Local observation to delete: ", realmObservation?.uuid );
|
||||
if ( realmObservation ) {
|
||||
safeRealmWrite( realm, ( ) => {
|
||||
realm?.delete( realmObservation );
|
||||
}, `deleting local observation ${realmObservation.uuid} in useDeleteObservations` );
|
||||
logger.info( "Local observation deleted" );
|
||||
}
|
||||
const deleteLocalObservation = useCallback( async ( ) => {
|
||||
await Observation.deleteLocalObservation( realm, uuid );
|
||||
return true;
|
||||
}, [realm, uuid] );
|
||||
|
||||
@@ -176,7 +169,9 @@ const useDeleteObservations = ( canBeginDeletions, myObservationsDispatch ): Obj
|
||||
};
|
||||
}, [deletionsComplete, error, resetDeleteObservationsSlice] );
|
||||
|
||||
return null;
|
||||
return {
|
||||
deleteLocalObservation
|
||||
};
|
||||
};
|
||||
|
||||
export default useDeleteObservations;
|
||||
|
||||
@@ -30,6 +30,9 @@ export default useUploadObservations = ( ): Object => {
|
||||
const setCurrentUpload = useStore( state => state.setCurrentUpload );
|
||||
const currentUpload = useStore( state => state.currentUpload );
|
||||
const setNumUnuploadedObservations = useStore( state => state.setNumUnuploadedObservations );
|
||||
const removeDeletedObsFromUploadQueue = useStore(
|
||||
state => state.removeDeletedObsFromUploadQueue
|
||||
);
|
||||
|
||||
// The existing abortController lets you abort...
|
||||
const abortController = useStore( storeState => storeState.abortController );
|
||||
@@ -69,6 +72,7 @@ export default useUploadObservations = ( ): Object => {
|
||||
] );
|
||||
|
||||
const uploadObservationAndCatchError = useCallback( async observation => {
|
||||
const { uuid } = observation;
|
||||
setCurrentUpload( observation );
|
||||
try {
|
||||
await uploadObservation( observation, realm, { signal: newAbortController( ).signal } );
|
||||
@@ -80,8 +84,18 @@ export default useUploadObservations = ( ): Object => {
|
||||
completeUploads( );
|
||||
}
|
||||
} catch ( uploadError ) {
|
||||
console.log( uploadError, "upload error" );
|
||||
const message = handleUploadError( uploadError, t );
|
||||
addUploadError( message, observation.uuid );
|
||||
if ( message?.match( /That observation no longer exists./ ) ) {
|
||||
// 20240531 amanda - it seems like we have to update the UI
|
||||
// for the progress bar before actually deleting the observation
|
||||
// locally, otherwise Realm will throw an error while trying
|
||||
// to load the individual progress for a deleted observation
|
||||
removeDeletedObsFromUploadQueue( uuid );
|
||||
await Observation.deleteLocalObservation( realm, uuid );
|
||||
} else {
|
||||
addUploadError( message, uuid );
|
||||
}
|
||||
}
|
||||
}, [
|
||||
addUploadError,
|
||||
@@ -89,6 +103,7 @@ export default useUploadObservations = ( ): Object => {
|
||||
currentUpload,
|
||||
newAbortController,
|
||||
realm,
|
||||
removeDeletedObsFromUploadQueue,
|
||||
removeFromUploadQueue,
|
||||
setCurrentUpload,
|
||||
t,
|
||||
|
||||
@@ -288,6 +288,36 @@ class Observation extends Realm.Object {
|
||||
};
|
||||
}
|
||||
|
||||
// static mapObservationForFlashList( obs ) {
|
||||
// return {
|
||||
// _created_at: obs._created_at,
|
||||
// _deleted_at: obs._deleted_at,
|
||||
// _synced_at: obs._synced_at,
|
||||
// _updated_at: obs._updated_at,
|
||||
// uuid: obs.uuid,
|
||||
// comments: obs.comments,
|
||||
// description: obs.description,
|
||||
// geoprivacy: obs.geoprivacy,
|
||||
// id: obs.id,
|
||||
// identifications: obs.identifications,
|
||||
// latitude: obs.latitude,
|
||||
// longitude: obs.longitude,
|
||||
// observationPhotos: obs.observationPhotos,
|
||||
// observationSounds: obs.observationSounds,
|
||||
// observed_on_string: obs.observed_on_string,
|
||||
// obscured: obs.obscured,
|
||||
// place_guess: obs.place_guess,
|
||||
// positional_accuracy: obs.positional_accuracy,
|
||||
// quality_grade: obs.quality_grade,
|
||||
// taxon: obs.taxon,
|
||||
// time_observed_at: obs.time_observed_at,
|
||||
// comments_viewed: obs.comments_viewed,
|
||||
// identifications_viewed: obs.identifications_viewed,
|
||||
// privateLatitude: obs.privateLatitude,
|
||||
// privateLongitude: obs.privateLongitude
|
||||
// };
|
||||
// }
|
||||
|
||||
static projectUri = obs => {
|
||||
const photo = obs?.observation_photos?.[0];
|
||||
if ( !photo ) { return null; }
|
||||
@@ -384,6 +414,17 @@ class Observation extends Realm.Object {
|
||||
return updatedObs;
|
||||
};
|
||||
|
||||
static deleteLocalObservation = async ( realm, uuidToDelete ) => {
|
||||
const observation = realm?.objectForPrimaryKey( "Observation", uuidToDelete );
|
||||
if ( observation ) {
|
||||
await safeRealmWrite( realm, ( ) => {
|
||||
realm?.delete( observation );
|
||||
}, `deleting local observation ${uuidToDelete} in deleteLocalObservation` );
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
static schema = {
|
||||
name: "Observation",
|
||||
primaryKey: "uuid",
|
||||
|
||||
@@ -284,7 +284,8 @@ export function handleUploadError( uploadError: Error | INatApiError, t: Functio
|
||||
if ( e.message?.errors ) {
|
||||
return e.message.errors.flat( ).join( ", " );
|
||||
}
|
||||
return e.message;
|
||||
// 410 error for observations previously deleted uses e.message?.error format
|
||||
return e.message?.error || e.message;
|
||||
} ).join( ", " );
|
||||
} else if ( uploadError.message?.match( /Network request failed/ ) ) {
|
||||
message = t( "Connection-problem-Please-try-again-later" );
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
useEffect, useRef,
|
||||
useState
|
||||
} from "react";
|
||||
// import Observation from "realmModels/Observation";
|
||||
|
||||
const { useRealm } = RealmContext;
|
||||
|
||||
@@ -31,6 +32,12 @@ const useLocalObservations = ( ): Object => {
|
||||
|
||||
if ( isFocused ) {
|
||||
setObservationList( stagedObservationList.current );
|
||||
// 20240530 amanda - we only need about half of the keys in an Observation object to
|
||||
// display to the user on MyObservations, so I think passing around smaller objects
|
||||
// will improve render time here. if it causes problems, we can remove and pass around
|
||||
// the full realm object
|
||||
// const mappedObservations = stagedObservationList.current
|
||||
// .map( o => Observation.mapObservationForFlashList( o ) );
|
||||
}
|
||||
} );
|
||||
// eslint-disable-next-line consistent-return
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import _ from "lodash";
|
||||
import { RealmObservation } from "realmModels/types.d.ts";
|
||||
|
||||
const DEFAULT_STATE = {
|
||||
@@ -76,6 +77,12 @@ const calculateTotalToolbarIncrements = uploads => countMappedIncrements(
|
||||
uploads.map( u => countTotalIncrements( u ) )
|
||||
);
|
||||
|
||||
const setTotalToolbarProgress = ( totalToolbarIncrements, totalUploadProgress ) => (
|
||||
totalToolbarIncrements > 0
|
||||
? setCurrentToolbarIncrements( totalUploadProgress ) / totalToolbarIncrements
|
||||
: 0
|
||||
);
|
||||
|
||||
const createUploadObservationsSlice: StateCreator<UploadObservationsSlice> = set => ( {
|
||||
...DEFAULT_STATE,
|
||||
resetUploadObservationsSlice: ( ) => set( DEFAULT_STATE ),
|
||||
@@ -121,9 +128,7 @@ const createUploadObservationsSlice: StateCreator<UploadObservationsSlice> = set
|
||||
= observation.currentIncrements / observation.totalIncrements;
|
||||
return ( {
|
||||
totalUploadProgress,
|
||||
totalToolbarProgress: totalToolbarIncrements > 0
|
||||
? setCurrentToolbarIncrements( totalUploadProgress ) / totalToolbarIncrements
|
||||
: 0
|
||||
totalToolbarProgress: setTotalToolbarProgress( totalToolbarIncrements, totalUploadProgress )
|
||||
} );
|
||||
} ),
|
||||
setUploadStatus: uploadStatus => set( ( ) => ( {
|
||||
@@ -171,7 +176,39 @@ const createUploadObservationsSlice: StateCreator<UploadObservationsSlice> = set
|
||||
} ),
|
||||
setNumUnuploadedObservations: numUnuploadedObservations => set( ( ) => ( {
|
||||
numUnuploadedObservations
|
||||
} ) )
|
||||
} ) ),
|
||||
removeDeletedObsFromUploadQueue: uuid => set( state => {
|
||||
const {
|
||||
numObservationsInQueue,
|
||||
numUploadsAttempted,
|
||||
totalToolbarIncrements,
|
||||
totalUploadProgress: existingTotalUploadProgress,
|
||||
uploadQueue
|
||||
} = state;
|
||||
// Zustand does *not* make deep copies when making supposedly immutable
|
||||
// state changes, so for nested objects like this, we need to create a
|
||||
// new object explicitly.
|
||||
// https://github.com/pmndrs/zustand/blob/main/docs/guides/immutable-state-and-merging.md#nested-objects
|
||||
const totalUploadProgress = existingTotalUploadProgress
|
||||
? [...existingTotalUploadProgress]
|
||||
: [];
|
||||
const observation = totalUploadProgress.find( o => o.uuid === uuid );
|
||||
observation.totalProgress = observation.totalIncrements;
|
||||
observation.currentIncrements = observation.totalIncrements;
|
||||
|
||||
// return the new queue without the uuid of the object already deleted remotely
|
||||
const queueWithDeleted = _.remove( uploadQueue, uuidInQueue => uuidInQueue !== uuid );
|
||||
|
||||
return ( {
|
||||
uploadQueue: queueWithDeleted,
|
||||
currentUpload: null,
|
||||
totalUploadProgress,
|
||||
totalToolbarProgress: setTotalToolbarProgress( totalToolbarIncrements, totalUploadProgress ),
|
||||
uploadStatus: numUploadsAttempted === numObservationsInQueue
|
||||
? "complete"
|
||||
: "uploadInProgress"
|
||||
} );
|
||||
} )
|
||||
} );
|
||||
|
||||
export default createUploadObservationsSlice;
|
||||
|
||||
Reference in New Issue
Block a user