Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SIMSBIOHUB-286: Upload Telemetry Files on Device Creation #1102

Merged
merged 62 commits into from
Sep 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
78d0095
Added method to BctwService for keyX file uploads
JeremyQuartech Sep 12, 2023
063af6e
Merge branch 'dev' into SIMSBIOHUB-263
JeremyQuartech Sep 13, 2023
ce9c9c0
WIP: Attachment service and repo functions for keyx files
JeremyQuartech Sep 14, 2023
e46e75b
Added survey_attachment_keyx table
JeremyQuartech Sep 14, 2023
22b4323
Added attachment-repo methods to insert and update keyx references
JeremyQuartech Sep 14, 2023
be69edf
Added attachment-keyx-service and modified file object type in BctwSe…
JeremyQuartech Sep 14, 2023
daafac9
Updated api routes for file processing
JeremyQuartech Sep 14, 2023
cc7b09e
Various bug fixes, implemented ui changes for uploads
JeremyQuartech Sep 15, 2023
9f1320a
Added notes where improvements can likely be made still.
JeremyQuartech Sep 15, 2023
79b0b12
Merge branch 'dev' into SIMSBIOHUB-263
JeremyQuartech Sep 15, 2023
b524858
Renamed process endpoint and added stub schema
JeremyQuartech Sep 18, 2023
57e56d5
renamed process endpoint to /processKeyx
JeremyQuartech Sep 18, 2023
02f37dd
revert name changes
JeremyQuartech Sep 19, 2023
e451dac
Addressed various PR comments
JeremyQuartech Sep 19, 2023
9fb59f9
Extracted keyx detection to file-utils to reduce complexity of upload()
JeremyQuartech Sep 19, 2023
fdd8b45
Add unit tests for /survey/process
JeremyQuartech Sep 19, 2023
b40fe5c
Added basic keyx specific upload dialog to survey documents
JeremyQuartech Sep 19, 2023
ef710d1
updated Bctw Service unit tests
JeremyQuartech Sep 19, 2023
c2bd027
Removed /process endpoint
JeremyQuartech Sep 19, 2023
573312e
Added keyx specific upload endpoint
JeremyQuartech Sep 19, 2023
0a9f0e0
Added keyx response schema and error handling to bcwt service
JeremyQuartech Sep 19, 2023
72e531c
Removed keyx attachment table migration
JeremyQuartech Sep 19, 2023
9946b6a
Added zip file validation
JeremyQuartech Sep 19, 2023
5397ee6
WIP: displaying errors in keyx uploader
JeremyQuartech Sep 20, 2023
4a73169
Added error handling for BCTW api connection refusal
JeremyQuartech Sep 20, 2023
f663c77
Disabled error details by default
JeremyQuartech Sep 20, 2023
98323d7
Reverted attachment-repo changes
JeremyQuartech Sep 20, 2023
57cb457
Removed dead cod
JeremyQuartech Sep 20, 2023
645660d
Merge branch 'dev' into SIMSBIOHUB-263
JeremyQuartech Sep 20, 2023
fa75116
Updated BctwService tests
JeremyQuartech Sep 21, 2023
8dd0643
Removed console log and added keyxResults to upload response
JeremyQuartech Sep 21, 2023
a29f3a1
Added request timeout to BCTW service
JeremyQuartech Sep 21, 2023
0d75a74
Added keyx results to openapi schema
JeremyQuartech Sep 21, 2023
9027969
Removed console logs
JeremyQuartech Sep 21, 2023
4d3266a
Fixed code smells and addressed PR comments
JeremyQuartech Sep 21, 2023
4f4b571
fixed code smells
JeremyQuartech Sep 21, 2023
171d01c
Moved keyx detection to media-utils and updated unit tests
JeremyQuartech Sep 21, 2023
3887a50
Code duplication fixes
JeremyQuartech Sep 21, 2023
6e964f8
Code duplication fix
JeremyQuartech Sep 21, 2023
e2da28c
Merge branch 'dev' into SIMSBIOHUB-263
GrahamS-Quartech Sep 21, 2023
9d5f0c4
WIP: Attachment section inside add device form
JeremyQuartech Sep 21, 2023
7c2fe68
Added basic file upload component to Telemetry device form
JeremyQuartech Sep 22, 2023
d0d7764
Changed file uploads on device add to use staged mode instead of dire…
JeremyQuartech Sep 25, 2023
6416d4d
Merge branch 'dev' into SIMSBIOHUB-286
JeremyQuartech Sep 25, 2023
7c5583f
Made uploadHandler prop optional
JeremyQuartech Sep 25, 2023
3a7f330
Refactored TelemetryFileUpload to reduce complexity
JeremyQuartech Sep 25, 2023
5444d0d
Separated handleSaveTelemetry in a function for both edit and add tel…
JeremyQuartech Sep 26, 2023
942497a
Fixed logic bug on upload rendering
JeremyQuartech Sep 26, 2023
5fcf548
Removed 'any' casting on error catch
JeremyQuartech Sep 26, 2023
1239b30
Refactored handleEditTelemetry to split up device and deployment spec…
JeremyQuartech Sep 26, 2023
19d5dc1
WIP: Telemetry upload tests
JeremyQuartech Sep 27, 2023
19a64f0
Merge branch 'dev' into SIMSBIOHUB-286
JeremyQuartech Sep 27, 2023
6a40c87
Added KeyX file registration status detection to deviceId endpoint
JeremyQuartech Sep 27, 2023
7215e62
Updated logic in add telemetry form to only allow uploads if keyx doe…
JeremyQuartech Sep 27, 2023
5a2c026
Added support to upload keyx and cfg files during telemetry device edit
JeremyQuartech Sep 27, 2023
8426bba
Minor tweaks to AttchmentFormSection of TelemetryDeviceForm
JeremyQuartech Sep 27, 2023
428a5b4
Fixed broken device detail tests.
JeremyQuartech Sep 28, 2023
769ea35
Added test coverage for getKeyXDetails
JeremyQuartech Sep 28, 2023
14e5bd5
updated useDeviceApi tests
JeremyQuartech Sep 28, 2023
39f42da
added uploadSurveyKeyx test
JeremyQuartech Sep 28, 2023
d35402a
Adds test of telemetry device add
JeremyQuartech Sep 28, 2023
9afedbc
Added basic test to TelemetryFileUpload component
JeremyQuartech Sep 28, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions api/src/paths/telemetry/device/{deviceId}.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('getDeviceDetails', () => {
it('gets device details', async () => {
const mockGetDeviceDetails = sinon.stub(BctwService.prototype, 'getDeviceDetails').resolves([]);
const mockGetDeployments = sinon.stub(BctwService.prototype, 'getDeviceDeployments').resolves([]);
const mockGetKeyXDetails = sinon.stub(BctwService.prototype, 'getKeyXDetails').resolves([]);

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
const requestHandler = getDeviceDetails();
Expand All @@ -30,5 +31,23 @@ describe('getDeviceDetails', () => {
expect(mockRes.statusValue).to.equal(200);
expect(mockGetDeviceDetails).to.have.been.calledOnce;
expect(mockGetDeployments).to.have.been.calledOnce;
expect(mockGetKeyXDetails).to.have.been.calledOnce;
});

it('catches and re-throws errors', async () => {
const mockError = new Error('test error');
const mockGetDeviceDetails = sinon.stub(BctwService.prototype, 'getDeviceDetails').rejects(mockError);

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
const requestHandler = getDeviceDetails();

try {
await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (error) {
expect(error).to.equal(mockError);
expect(mockGetDeviceDetails).to.have.been.calledOnce;
expect(mockNext).not.to.have.been.called;
}
});
});
3 changes: 3 additions & 0 deletions api/src/paths/telemetry/device/{deviceId}.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,11 @@ export function getDeviceDetails(): RequestHandler {
try {
const results = await bctwService.getDeviceDetails(deviceId);
const deployments = await bctwService.getDeviceDeployments(deviceId);
const keyXResult = await bctwService.getKeyXDetails([deviceId]);
const keyXStatus = keyXResult?.[0]?.keyx?.idcollar === deviceId;
const retObj = {
device: results?.[0],
keyXStatus: keyXStatus,
deployments: deployments
};
return res.status(200).json(retObj);
Expand Down
11 changes: 11 additions & 0 deletions api/src/services/bctw-service.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
GET_DEPLOYMENTS_BY_DEVICE_ENDPOINT,
GET_DEPLOYMENTS_ENDPOINT,
GET_DEVICE_DETAILS,
GET_KEYX_STATUS_ENDPOINT,
HEALTH_ENDPOINT,
IDeployDevice,
IDeploymentUpdate,
Expand Down Expand Up @@ -238,6 +239,16 @@ describe('BctwService', () => {
});
});

describe('getKeyXDetails', () => {
it('should send a get request', async () => {
const mockGetRequest = sinon.stub(bctwService, '_makeGetRequest');

await bctwService.getKeyXDetails([123]);

expect(mockGetRequest).to.have.been.calledOnceWith(GET_KEYX_STATUS_ENDPOINT, { device_ids: ['123'] });
});
});

describe('updateDevice', () => {
it('should send a post request', async () => {
const mockAxios = sinon.stub(bctwService.axiosInstance, 'post').resolves({ data: { results: [], errors: [] } });
Expand Down
20 changes: 20 additions & 0 deletions api/src/services/bctw-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ export const IUploadKeyxResponse = z.object({

export type IUploadKeyxResponse = z.infer<typeof IUploadKeyxResponse>;

export const IKeyXDetails = z.object({
device_id: z.number(),
keyx: z
.object({
idcom: z.string(),
comtype: z.string(),
idcollar: z.number(),
collarkey: z.string(),
collartype: z.number()
})
.nullable()
});

export type IKeyXDetails = z.infer<typeof IKeyXDetails>;

export const IBctwUser = z.object({
keycloak_guid: z.string(),
username: z.string()
Expand Down Expand Up @@ -97,6 +112,7 @@ export const HEALTH_ENDPOINT = '/health';
export const GET_CODE_ENDPOINT = '/get-code';
export const GET_DEVICE_DETAILS = '/get-collar-history-by-device/';
export const UPLOAD_KEYX_ENDPOINT = '/import-xml';
export const GET_KEYX_STATUS_ENDPOINT = '/get-collars-keyx';

export class BctwService {
user: IBctwUser;
Expand Down Expand Up @@ -330,6 +346,10 @@ export class BctwService {
};
}

async getKeyXDetails(deviceIds: number[]): Promise<IKeyXDetails[]> {
JeremyQuartech marked this conversation as resolved.
Show resolved Hide resolved
return this._makeGetRequest(GET_KEYX_STATUS_ENDPOINT, { device_ids: deviceIds.map((id) => String(id)) });
}

/**
* Get a list of all BCTW codes with a given header name.
*
Expand Down
2 changes: 1 addition & 1 deletion app/src/components/file-upload/FileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface IFileUploadProps {
* @type {IUploadHandler}
* @memberof IFileUploadProps
*/
uploadHandler: IUploadHandler;
uploadHandler?: IUploadHandler;
/**
* Callback fired for each accepted file in the list (that do not have any `DropZone` errors (size, count, extension).
*
Expand Down
26 changes: 14 additions & 12 deletions app/src/components/file-upload/FileUploadItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@

export interface IFileUploadItemProps {
/**
* A file upload callback fired for each file.
* An optional file upload callback fired for each file.
*
* @type {IUploadHandler}
* @memberof IFileUploadItemProps
*/
uploadHandler: IUploadHandler;
uploadHandler?: IUploadHandler;
/**
* An optional callback fired if the file upload is successful.
*
Expand Down Expand Up @@ -223,16 +223,18 @@
onSuccess?.(response);
};

uploadHandler(file, cancelToken, handleFileUploadProgress)
.then(handleFileUploadSuccess, (error: APIError) => {
setError(error?.message);
setErrorDetails(
error.errors?.map((error) => {
return { _id: v4(), message: String(error) };
})
);
})
.catch();
if (uploadHandler) {
uploadHandler(file, cancelToken, handleFileUploadProgress)
.then(handleFileUploadSuccess, (error: APIError) => {
setError(error?.message);
setErrorDetails(
error?.errors?.map((e) => {
return { _id: v4(), message: e?.toString() };

Check warning on line 232 in app/src/components/file-upload/FileUploadItem.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/components/file-upload/FileUploadItem.tsx#L232

Added line #L232 was not covered by tests
})
);
})
.catch();
}

setStatus(UploadFileStatus.UPLOADING);
}, [
Expand Down
1 change: 1 addition & 0 deletions app/src/constants/attachments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export enum ProjectSurveyAttachmentValidExtensions {
DATA = '.txt, .xls, .xlsx, .xlsm, .xlsb, .accdb, .mdb, .ods, .csv',
IMAGE = '.gif, .png, .jpg, .jpeg, .svg, .tiff, .bmp, .tif',
KEYX = '.keyx, .zip',
CONFIG = '.cfg',
REPORT = '.doc, .docx, .pdf',
SPATIAL = '.kml, .gpx, .zip',
VIDEO = '.mp4, .mov, .wmv, .ave',
Expand Down
5 changes: 3 additions & 2 deletions app/src/features/surveys/view/SurveyAnimals.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,9 @@ describe('SurveyAnimals', () => {
fireEvent.click(getByTestId('animal actions'));
fireEvent.click(getByTestId('animal-table-row-edit-timespan'));
fireEvent.click(getByText('Save Changes'));
//fireEvent.click(getByText('Import Animals'));
//fireEvent.click(getByText('Save Changes'));
fireEvent.click(getByTestId('animal actions'));
fireEvent.click(getByTestId('animal-table-row-add-device'));
fireEvent.click(getByText('Save Changes'));
});
});
});
126 changes: 91 additions & 35 deletions app/src/features/surveys/view/SurveyAnimals.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import HelpButtonTooltip from 'components/buttons/HelpButtonTooltip';
import EditDialog from 'components/dialog/EditDialog';
import { H2ButtonToolbar } from 'components/toolbar/ActionToolbars';
import { AttachmentType } from 'constants/attachments';
import { SurveyAnimalsI18N } from 'constants/i18n';
import { DialogContext } from 'contexts/dialogContext';
import { SurveyContext } from 'contexts/surveyContext';
Expand All @@ -16,10 +17,18 @@
import yup from 'utils/YupSchema';
import NoSurveySectionData from '../components/NoSurveySectionData';
import { AnimalSchema, AnimalSex, Critter, IAnimal } from './survey-animals/animal';
import { AnimalTelemetryDeviceSchema, Device, IAnimalTelemetryDevice } from './survey-animals/device';
import {
AnimalTelemetryDeviceSchema,
Device,
IAnimalTelemetryDevice,
IDeploymentTimespan
} from './survey-animals/device';
import IndividualAnimalForm from './survey-animals/IndividualAnimalForm';
import { SurveyAnimalsTable } from './survey-animals/SurveyAnimalsTable';
import TelemetryDeviceForm, { TELEMETRY_DEVICE_FORM_MODE } from './survey-animals/TelemetryDeviceForm';
import TelemetryDeviceForm, {
IAnimalTelemetryDeviceFile,
TELEMETRY_DEVICE_FORM_MODE
} from './survey-animals/TelemetryDeviceForm';

const SurveyAnimals: React.FC = () => {
const bhApi = useBiohubApi();
Expand Down Expand Up @@ -151,44 +160,91 @@
}
};

const handleTelemetrySave = async (survey_critter_id: number, data: IAnimalTelemetryDevice[]) => {
const uploadAttachment = async (file?: File, attachmentType?: AttachmentType) => {
try {

Check warning on line 164 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L164

Added line #L164 was not covered by tests
if (file && attachmentType === AttachmentType.KEYX) {
await bhApi.survey.uploadSurveyKeyx(projectId, surveyId, file);

Check warning on line 166 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L166

Added line #L166 was not covered by tests
} else if (file && attachmentType === AttachmentType.OTHER) {
await bhApi.survey.uploadSurveyAttachments(projectId, surveyId, file);

Check warning on line 168 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L168

Added line #L168 was not covered by tests
}
} catch (error) {
throw new Error(`Failed to upload attachment ${file?.name}`);

Check warning on line 171 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L171

Added line #L171 was not covered by tests
JeremyQuartech marked this conversation as resolved.
Show resolved Hide resolved
}
};

const handleAddTelemetry = async (survey_critter_id: number, data: IAnimalTelemetryDeviceFile[]) => {
const critter = critterData?.find((a) => a.survey_critter_id === survey_critter_id);
const critterTelemetryDevice = { ...data[0], critter_id: critter?.critter_id ?? '' };
if (telemetryFormMode === TELEMETRY_DEVICE_FORM_MODE.ADD) {
try {
await bhApi.survey.addDeployment(projectId, surveyId, survey_critter_id, critterTelemetryDevice);
setPopup('Successfully added deployment.');
} catch (e) {
const { attachmentFile, attachmentType, ...critterTelemetryDevice } = {

Check warning on line 177 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L177

Added line #L177 was not covered by tests
...data[0],
critter_id: critter?.critter_id ?? ''
};
try {

Check warning on line 181 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L181

Added line #L181 was not covered by tests
// Upload attachment if there is one
await uploadAttachment(attachmentFile, attachmentType);

Check warning on line 183 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L183

Added line #L183 was not covered by tests
// create new deployment record
await bhApi.survey.addDeployment(projectId, surveyId, survey_critter_id, critterTelemetryDevice);
setPopup('Successfully added deployment.');
surveyContext.artifactDataLoader.refresh(projectId, surveyId);

Check warning on line 187 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L185-L187

Added lines #L185 - L187 were not covered by tests
} catch (error: unknown) {
if (error instanceof Error) {
setPopup('Failed to add deployment' + (error?.message ? `: ${error.message}` : '.'));
} else {
setPopup('Failed to add deployment.');
}
} else if (telemetryFormMode === TELEMETRY_DEVICE_FORM_MODE.EDIT) {
for (const formValues of data) {
const existingDevice = deploymentData?.find((a) => a.device_id === formValues.device_id);
const formDevice = new Device({ collar_id: existingDevice?.collar_id, ...formValues });
if (existingDevice && !_deepEquals(new Device(existingDevice), formDevice)) {
//Verify whether the data entered in the form changed from the device metadata we already have.
try {
await telemetryApi.devices.upsertCollar(formDevice); //If it's different, upsert. Note that this alone does not touch a deployment.
} catch (e) {
setPopup(`Failed to update device ${formDevice.device_id}`);
}
}
for (const formDeployment of formValues.deployments ?? []) {
//Iterate over the deployments under this device.
const existingDeployment = deploymentData?.find((a) => a.deployment_id === formDeployment.deployment_id); //Find the deployment info we already have.
if (
!datesSameNullable(formDeployment?.attachment_start, existingDeployment?.attachment_start) ||
!datesSameNullable(formDeployment?.attachment_end, existingDeployment?.attachment_end) //Helper function necessary for this date comparison since moment(null) !== moment(null) normally.
) {
try {
await bhApi.survey.updateDeployment(projectId, surveyId, survey_critter_id, formDeployment);
} catch (e) {
setPopup(`Failed to update deployment ${formDeployment.deployment_id}`);
}
}
}
};

const updateDevice = async (formValues: IAnimalTelemetryDevice) => {
const existingDevice = deploymentData?.find((deployment) => deployment.device_id === formValues.device_id);
const formDevice = new Device({ collar_id: existingDevice?.collar_id, ...formValues });

Check warning on line 199 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L198-L199

Added lines #L198 - L199 were not covered by tests
if (existingDevice && !_deepEquals(new Device(existingDevice), formDevice)) {
try {
await telemetryApi.devices.upsertCollar(formDevice);

Check warning on line 202 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L201-L202

Added lines #L201 - L202 were not covered by tests
} catch (error) {
throw new Error(`Failed to update collar ${formDevice.collar_id}`);

Check warning on line 204 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L204

Added line #L204 was not covered by tests
}
}
};

const updateDeployments = async (formDeployments: IDeploymentTimespan[], survey_critter_id: number) => {
for (const formDeployment of formDeployments ?? []) {
const existingDeployment = deploymentData?.find(
(animalDeployment) => animalDeployment.deployment_id === formDeployment.deployment_id

Check warning on line 212 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L211-L212

Added lines #L211 - L212 were not covered by tests
);
if (
!datesSameNullable(formDeployment?.attachment_start, existingDeployment?.attachment_start) ||
!datesSameNullable(formDeployment?.attachment_end, existingDeployment?.attachment_end)
) {
try {
await bhApi.survey.updateDeployment(projectId, surveyId, survey_critter_id, formDeployment);

Check warning on line 219 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L218-L219

Added lines #L218 - L219 were not covered by tests
} catch (error) {
throw new Error(`Failed to update deployment ${formDeployment.deployment_id}`);

Check warning on line 221 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L221

Added line #L221 was not covered by tests
}
}
setPopup('Updated deployment and device data successfully.');
}
};

const handleEditTelemetry = async (survey_critter_id: number, data: IAnimalTelemetryDeviceFile[]) => {
const errors: string[] = [];
for (const { attachmentFile, attachmentType, ...formValues } of data) {
try {
await uploadAttachment(attachmentFile, attachmentType);
await updateDevice(formValues);

Check warning on line 232 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L228-L232

Added lines #L228 - L232 were not covered by tests
await updateDeployments(formValues.deployments ?? [], survey_critter_id);
} catch (error) {
errors.push(`Device ${formValues.device_id} - ` + (error instanceof Error ? error.message : 'Unknown error'));
}
}
errors.length
? setPopup('Failed to save some data: ' + errors.join(', '))
: setPopup('Updated deployment and device data successfully.');
};

const handleTelemetrySave = async (survey_critter_id: number, data: IAnimalTelemetryDeviceFile[]) => {
if (telemetryFormMode === TELEMETRY_DEVICE_FORM_MODE.ADD) {
await handleAddTelemetry(survey_critter_id, data);

Check warning on line 245 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L245

Added line #L245 was not covered by tests
} else if (telemetryFormMode === TELEMETRY_DEVICE_FORM_MODE.EDIT) {
await handleEditTelemetry(survey_critter_id, data);

Check warning on line 247 in app/src/features/surveys/view/SurveyAnimals.tsx

View check run for this annotation

Codecov / codecov/patch

app/src/features/surveys/view/SurveyAnimals.tsx#L247

Added line #L247 was not covered by tests
}

setOpenDeviceDialog(false);
Expand Down
Loading