Files
iNaturalistReactNative/src/api/observations.js
Johannes Klein 929652fe14 Add some context to 429 errors (#2861)
* Add context to announcements API calls handleError

* Add context to handleError for calls in comments

* Add context to handleError for calls in computerVision

* Add context to handleError for calls in flags

* Add context to handleError for calls in identifications

* Add context to handleError for calls in messages

* Add context to handleError for calls in observations

* Add context to handleError for calls in observationSounds

* Add context to handleError for calls in places

* Add context to handleError for calls in projects

* Add context to handleError for calls in qualityMetrics

* Add context to handleError for calls in relationships

* Add context to handleError for calls in taxa

* Add context to handleError for calls in translations

* Add context to handleError for calls in users

* Move creation of error context to the handleError function

Also spread any other context that was passed into this function in the new context.

* Move logger for 429 errors to the handleError function

There are two codepaths that an error of 429 can follow here. Errors thrown from reactQueryRetry have a .status field whereas errors thrown from inaturalistjs have a .response.status field (and not the other in both cases).
I don't want to update the handleError function for now but instead keep its codepaths the same, so I added logging for both paths.

* Remove nullish values (null or undefined) from context
2025-04-30 11:11:23 +02:00

272 lines
7.5 KiB
JavaScript

// @flow
import inatjs from "inaturalistjs";
import handleError from "./error";
// I tried doing this in Observaiton.js but got mysterious Realm errors. More
// could be here, but this solves an immediate problem with schema mismatch
function mapObsPhotoToLocalSchema( obsPhoto ) {
obsPhoto.photo.licenseCode = obsPhoto.photo.licenseCode
|| obsPhoto.photo.license_code;
return obsPhoto;
}
function mapToLocalSchema( observation ) {
observation.observationPhotos = observation?.observationPhotos?.map( mapObsPhotoToLocalSchema );
observation.observation_photos = observation?.observation_photos?.map( mapObsPhotoToLocalSchema );
return observation;
}
const searchObservations = async ( params: Object = {}, opts: Object = {} ): Promise<Object> => {
try {
const response = await inatjs.observations.search( params, opts );
response.results = response.results.map( mapToLocalSchema );
return response;
} catch ( e ) {
return handleError( e, { context: { functionName: "searchObservations", opts } } );
}
};
const faveObservation = async ( params: Object = {}, opts: Object = {} ): Promise<?number> => {
try {
return await inatjs.observations.fave( params, opts );
} catch ( e ) {
return handleError( e, { context: { functionName: "faveObservation", opts } } );
}
};
const unfaveObservation = async ( params: Object = {}, opts: Object = {} ): Promise<?number> => {
try {
return await inatjs.observations.unfave( params, opts );
} catch ( e ) {
return handleError( e, { context: { functionName: "unfaveObservation", opts } } );
}
};
const fetchRemoteObservation = async (
uuid: string,
params: Object = {},
opts: Object = {}
): Promise<?number> => {
try {
const response = await inatjs.observations.fetch(
uuid,
params,
opts
);
if ( !response ) { return null; }
const { results } = response;
if ( results?.length > 0 ) {
return mapToLocalSchema( results[0] );
}
return null;
} catch ( e ) {
return handleError( e, { context: { functionName: "fetchRemoteObservation", uuid, opts } } );
}
};
const fetchRemoteObservations = async (
uuids: Array<string>,
params: Object = {},
opts: Object = {}
): Promise<?Array<Object>> => {
try {
const response = await inatjs.observations.fetch(
uuids,
params,
opts
);
if ( !response ) { return null; }
const { results } = response;
if ( results?.length > 0 ) {
return results.map( mapToLocalSchema );
}
return null;
} catch ( e ) {
return handleError( e, { context: { functionName: "fetchRemoteObservations", uuids, opts } } );
}
};
const markAsReviewed = async ( params: Object = {}, opts: Object = {} ): Promise<?number> => {
try {
return await inatjs.observations.review( params, opts );
} catch ( e ) {
return handleError( e, { context: { functionName: "markAsReviewed", opts } } );
}
};
const markObservationUpdatesViewed = async (
params: Object = {},
opts: Object = {}
): Promise<?Object> => {
try {
return await inatjs.observations.viewedUpdates( params, opts );
} catch ( e ) {
return handleError( e, { context: { functionName: "markObservationUpdatesViewed", opts } } );
}
};
const createObservation = async (
params: Object = {},
opts: Object = {}
): Promise<?Object> => {
try {
return await inatjs.observations.create( params, opts );
} catch ( e ) {
return handleError( e, { context: { functionName: "createObservation", opts } } );
}
};
const updateObservation = async (
params: Object = {},
opts: Object = {}
): Promise<?Object> => {
try {
return await inatjs.observations.update( params, opts );
} catch ( e ) {
return handleError( e, { context: { functionName: "updateObservation", opts } } );
}
};
// TODO: replace this. It doesn't do anything specific to creating or updateing
// evidence, it just wraps an API call, so it could be renamed
// to "callEndpoint", or maybe we should preserve abstraction from inatjs and
// not accept an inatjs endpoint and replace this with several functions
const createOrUpdateEvidence = async (
apiEndpoint: Function,
params: Object = {},
opts: Object = {}
): Promise<?Object> => {
try {
return await apiEndpoint( params, opts );
} catch ( e ) {
return handleError( e, { context: { functionName: "createOrUpdateEvidence", opts } } );
}
};
const fetchObservationUpdates = async (
params: Object = {},
opts: Object = {}
): Promise<?Object> => {
try {
const { results } = await inatjs.observations.updates( params, opts );
return results;
} catch ( e ) {
return handleError( e, { context: { functionName: "fetchObservationUpdates", opts } } );
}
};
const fetchUnviewedObservationUpdatesCount = async (
params: Object = {},
opts: Object = {}
): Promise<number> => {
try {
const { total_results: updatesCount } = await inatjs.observations.updates( {
...params,
viewed: false,
per_page: 0
}, opts );
return updatesCount;
} catch ( e ) {
return handleError( e, {
context: {
functionName: "fetchUnviewedObservationUpdatesCount",
opts
}
} );
}
};
const deleteRemoteObservation = async (
params: Object = {},
opts: Object = {}
) : Promise<?Object> => {
try {
return await inatjs.observations.delete( params, opts );
} catch ( e ) {
return handleError( e, { context: { functionName: "deleteRemoteObservation", opts } } );
}
};
const fetchObservers = async ( params: Object = {} ) : Promise<?Object> => {
try {
return inatjs.observations.observers( params );
} catch ( e ) {
return handleError( e, { context: { functionName: "fetchObservers" }, throw: true } );
}
};
const fetchIdentifiers = async ( params: Object = {} ) : Promise<?Object> => {
try {
return await inatjs.observations.identifiers( params );
} catch ( e ) {
return handleError( e, { context: { functionName: "fetchIdentifiers" } } );
}
};
const fetchSpeciesCounts = async (
params: Object = {},
opts: Object = {}
) : Promise<?Object> => {
try {
return inatjs.observations.speciesCounts( params, opts );
} catch ( e ) {
return handleError( e, { context: { functionName: "fetchSpeciesCounts", opts } } );
}
};
const checkForDeletedObservations = async (
params: Object = {},
opts: Object = {}
) : Promise<?Object> => {
try {
return await inatjs.observations.deleted( params, opts );
} catch ( e ) {
return handleError( e, { context: { functionName: "checkForDeletedObservations", opts } } );
}
};
const fetchSubscriptions = async (
params: Object = {},
opts: Object = {}
) : Promise<?Object> => {
try {
return inatjs.observations.subscriptions( params, opts );
} catch ( e ) {
return handleError( e, { context: { functionName: "fetchSubscriptions", opts } } );
}
};
const createSubscription = async (
params: Object = {},
opts: Object = {}
) : Promise<?Object> => {
try {
return inatjs.observations.subscribe( params, opts );
} catch ( e ) {
return handleError( e, { context: { functionName: "createSubscription", opts } } );
}
};
export {
checkForDeletedObservations,
createObservation,
createOrUpdateEvidence,
createSubscription,
deleteRemoteObservation,
faveObservation,
fetchIdentifiers,
fetchObservationUpdates,
fetchObservers,
fetchRemoteObservation,
fetchRemoteObservations,
fetchSpeciesCounts,
fetchSubscriptions,
fetchUnviewedObservationUpdatesCount,
markAsReviewed,
markObservationUpdatesViewed,
searchObservations,
unfaveObservation,
updateObservation
};