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-579/583: Updated Animal Workflow (Capture, Mortality, Measurements, Profile) #1291

Merged
merged 79 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
fa86500
make install
mauberti-bc Apr 25, 2024
4817fff
animal profile page & context
mauberti-bc Apr 25, 2024
694b7d6
Merge branch 'dev' of github.com:bcgov/biohubbc into manage-animals
mauberti-bc Apr 26, 2024
e7b6898
wip: animal profile page
mauberti-bc Apr 26, 2024
ddc1433
wip: add capture event
mauberti-bc Apr 27, 2024
66b287a
release location
mauberti-bc Apr 27, 2024
cc41193
edit animal
mauberti-bc Apr 28, 2024
7ef059a
update context to set selectedanimal from survey critter id
mauberti-bc Apr 28, 2024
a7b3d97
animal list
mauberti-bc Apr 29, 2024
08c0076
wip: report mortality
mauberti-bc Apr 29, 2024
002ebc4
edit animal page loading spinner
mauberti-bc Apr 29, 2024
1e7db0e
no captures placeholder
mauberti-bc Apr 29, 2024
da05fae
fix objectives in openapi project update request
mauberti-bc Apr 29, 2024
afd23b2
update edit capture page
mauberti-bc Apr 29, 2024
f4ceec6
wip: formik validation
mauberti-bc Apr 30, 2024
95f0b79
initial formik validation on capture form
mauberti-bc May 1, 2024
67b67f6
jsdocs
mauberti-bc May 1, 2024
ad59545
Merge branch 'dev' into manage-animals
mauberti-bc May 2, 2024
09e759c
animal profile improvements
mauberti-bc May 3, 2024
0cbc5bd
profile
mauberti-bc May 3, 2024
29e42f3
merge latest
mauberti-bc May 7, 2024
2c4fcfb
wip: collection units
mauberti-bc May 7, 2024
0b90546
ecological units
mauberti-bc May 7, 2024
092bc2c
update population units in edit critter
mauberti-bc May 8, 2024
77fdf08
wip: markings
mauberti-bc May 8, 2024
34dc51d
eco units
mauberti-bc May 8, 2024
1fd1102
wip: capture form
mauberti-bc May 9, 2024
8e5ac1f
merge latest
mauberti-bc May 9, 2024
9fba5a3
merge in dev
mauberti-bc May 9, 2024
9bf129c
broken: ecological units data loader
mauberti-bc May 10, 2024
606c9ad
Merge remote-tracking branch 'origin/dev' into manage-animals
NickPhura May 13, 2024
ca3a2b8
Get Animal form control interaction working.
NickPhura May 14, 2024
b658ce5
make animal profile components dumber
mauberti-bc May 15, 2024
8b62e8c
wip: delete critters from survey
mauberti-bc May 15, 2024
e84621b
Add isEdit mode support for create/edit animal page
NickPhura May 15, 2024
4fc4592
markings card on capture form
mauberti-bc May 15, 2024
d600e4b
Merge branch 'manage-animals' of github.com:bcgov/biohubbc into manag…
mauberti-bc May 15, 2024
b8904a1
wip: adding markings to captures
mauberti-bc May 16, 2024
16a8907
delete markings linked to capture before deleting capture
mauberti-bc May 16, 2024
189af53
Update animal components
NickPhura May 16, 2024
a19cb12
Merge branch 'manage-animals' of https://github.com/bcgov/biohubbc in…
NickPhura May 16, 2024
3e5f17f
Tweaks
NickPhura May 16, 2024
a3662ab
add and edit measurements on capture page
mauberti-bc May 17, 2024
309079c
wip: fixing measurement types
mauberti-bc May 17, 2024
1ad065b
add and edit markings on capture
mauberti-bc May 17, 2024
709919a
fix marking update
mauberti-bc May 17, 2024
83993de
merge manage-animals branch
mauberti-bc May 18, 2024
5f0fbc9
initial report mortality workflow
mauberti-bc May 18, 2024
69450a8
mortality section of animal profile
mauberti-bc May 19, 2024
88a215e
add measurements and markings to capture card on animal profile
mauberti-bc May 21, 2024
e372240
fix app index file
mauberti-bc May 23, 2024
b5ce5a6
Merge remote-tracking branch 'origin/dev' into manage-animals
NickPhura May 23, 2024
f530ae9
Minor cleanup and organization changes. Wrap some dataloaders in useE…
NickPhura May 23, 2024
f638580
Wrap load() calls in useEffect
NickPhura May 23, 2024
ebbd37a
Fix edit capture requests (was sending measurements/markings from oth…
NickPhura May 24, 2024
820aa80
Merge remote-tracking branch 'origin/dev' into manage-animals
NickPhura May 24, 2024
aeae822
Cleanup, Fix required marking identifier
NickPhura May 24, 2024
8e10a25
Merge remote-tracking branch 'origin/manage-animals' into animal-mort…
NickPhura May 24, 2024
d611938
Merge in latest animal-capture branch, fix merge issues.
NickPhura May 24, 2024
9af13b5
Misc
NickPhura May 27, 2024
82510dc
Fix
NickPhura May 27, 2024
587a56c
WIP
NickPhura May 30, 2024
cd115b2
Merge remote-tracking branch 'origin/dev' into animal-mortality
NickPhura Jun 1, 2024
e89f9c1
Merge remote-tracking branch 'origin/dev' into animal-mortality
NickPhura Jun 3, 2024
9703450
Updates
NickPhura Jun 3, 2024
2b19c8c
Update mortality CRUD
NickPhura Jun 4, 2024
a7b1d78
fix capture_release data in critterbase payload & styling
mauberti-bc Jun 4, 2024
b7f6112
Remove datagrid hover css.
NickPhura Jun 4, 2024
3681852
docs: updated env.docker to include linux container hosts
MacQSL Jun 7, 2024
d58ca70
Minor fixes
NickPhura Jun 10, 2024
a07261e
Updates
NickPhura Jun 10, 2024
c21c390
Updates
NickPhura Jun 10, 2024
c86bd45
Fix/improve capture/mortality create/edit measurements
NickPhura Jun 11, 2024
c3cd06a
Fix code smells
NickPhura Jun 11, 2024
4e01f51
Fix test
NickPhura Jun 11, 2024
2a7519e
Fix collapse UI behaviour. Minor Tweaks.
NickPhura Jun 11, 2024
c4c9d4f
Cleanup
NickPhura Jun 11, 2024
d9c17d3
Fix typings
NickPhura Jun 11, 2024
d32c456
Add start case to measurement/marking labels.
NickPhura Jun 12, 2024
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
24 changes: 0 additions & 24 deletions api/src/openapi/schemas/critter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,30 +204,6 @@ const quantitativeMeasurmentSchema: OpenAPIV3.SchemaObject = {
}
};

export const critterCreateRequestObject: OpenAPIV3.SchemaObject = {
title: 'Create critter request object',
type: 'object',
properties: {
critter_id: {
type: 'string',
format: 'uuid'
},
animal_id: {
type: 'string'
},
wlh_id: {
type: 'string'
},
itis_tsn: {
type: 'integer'
},
sex: {
type: 'string'
}
},
additionalProperties: false
};

export const critterBulkRequestObject: OpenAPIV3.SchemaObject = {
title: 'Bulk post request object',
type: 'object',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import Ajv from 'ajv';
import { expect } from 'chai';
import sinon from 'sinon';
import * as db from '../../../../../../database/db';
import { SurveyCritterService } from '../../../../../../services/survey-critter-service';
import { getMockDBConnection, getRequestHandlerMocks } from '../../../../../../__mocks__/db';
import { POST, removeCrittersFromSurvey } from './delete';

describe('critterId openapi schema', () => {
const ajv = new Ajv();

it('DELETE is valid openapi v3 schema', () => {
expect(ajv.validateSchema(POST.apiDoc as unknown as object)).to.be.true;
});
});

describe('removeCrittersFromSurvey', () => {
afterEach(() => {
sinon.restore();
});

const mockDBConnection = getMockDBConnection({ release: sinon.stub() });

it('removes critter from survey', async () => {
sinon.stub(db, 'getDBConnection').returns(mockDBConnection);

const removeCrittersFromSurveyStub = sinon
.stub(SurveyCritterService.prototype, 'removeCrittersFromSurvey')
.resolves();

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.body = {
critterIds: [2, 3]
};
mockReq.params = {
surveyId: '1'
};

const requestHandler = removeCrittersFromSurvey();

await requestHandler(mockReq, mockRes, mockNext);

expect(mockRes.statusValue).to.equal(200);
expect(removeCrittersFromSurveyStub).to.have.been.calledOnceWith(1, [2, 3]);
});

it('catches and re-throws errors', async () => {
sinon.stub(db, 'getDBConnection').returns(mockDBConnection);

const mockError = new Error('a test error');

sinon.stub(SurveyCritterService.prototype, 'removeCrittersFromSurvey').rejects(mockError);

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.body = {
critterIds: [2, 3]
};
mockReq.params = {
surveyId: '1'
};

const requestHandler = removeCrittersFromSurvey();

try {
await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect(actualError).to.equal(mockError);
}
});
});
116 changes: 116 additions & 0 deletions api/src/paths/project/{projectId}/survey/{surveyId}/critters/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { RequestHandler } from 'express';
import { Operation } from 'express-openapi';
import { PROJECT_PERMISSION, SYSTEM_ROLE } from '../../../../../../constants/roles';
import { getDBConnection } from '../../../../../../database/db';
import { authorizeRequestHandler } from '../../../../../../request-handlers/security/authorization';
import { SurveyCritterService } from '../../../../../../services/survey-critter-service';
import { getLogger } from '../../../../../../utils/logger';

const defaultLog = getLogger('paths/project/{projectId}/survey/{surveyId}/critters/delete');

export const POST: Operation = [
authorizeRequestHandler((req) => {
return {

Check warning on line 13 in api/src/paths/project/{projectId}/survey/{surveyId}/critters/delete.ts

View check run for this annotation

Codecov / codecov/patch

api/src/paths/project/{projectId}/survey/{surveyId}/critters/delete.ts#L13

Added line #L13 was not covered by tests
or: [
{
validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR],
surveyId: Number(req.params.surveyId),
discriminator: 'ProjectPermission'
},
{
validSystemRoles: [SYSTEM_ROLE.DATA_ADMINISTRATOR],
discriminator: 'SystemRole'
}
]
};
}),
removeCrittersFromSurvey()
];

POST.apiDoc = {
description: 'Removes association of critters from this survey.',
tags: ['critterbase'],
security: [
{
Bearer: []
}
],
parameters: [
{
in: 'path',
name: 'surveyId',
schema: {
type: 'number'
},
required: true
}
],
requestBody: {
description: 'Array of survey critter Ids to be deleted.',
content: {
'application/json': {
schema: {
type: 'object',
additionalProperties: false,
required: ['critterIds'],
properties: {
critterIds: {
type: 'array',
items: {
type: 'integer',
minimum: 1,
description: 'Survey critter Id.'
}
}
}
}
}
}
},
responses: {
200: {
description: 'Critter was removed from survey'
},
400: {
$ref: '#/components/responses/400'
},
401: {
$ref: '#/components/responses/401'
},
403: {
$ref: '#/components/responses/403'
},
500: {
$ref: '#/components/responses/500'
},
default: {
$ref: '#/components/responses/default'
}
}
};

export function removeCrittersFromSurvey(): RequestHandler {
return async (req, res) => {
const critterIds = req.body.critterIds;
const surveyId = Number(req.params.surveyId);

const connection = getDBConnection(req['keycloak_token']);

try {
await connection.open();

const surveyCritterService = new SurveyCritterService(connection);
const result = await surveyCritterService.removeCrittersFromSurvey(surveyId, critterIds);

await connection.commit();

return res.status(200).json(result);
} catch (error) {
defaultLog.error({ label: 'removeCritterFromSurvey', message: 'error', error });
await connection.rollback();
throw error;
} finally {
connection.release();
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,24 @@ describe('getCrittersFromSurvey', () => {
sinon.restore();
});

const mockDBConnection = getMockDBConnection({ release: sinon.stub() });
const mockSurveyCritter = { critter_id: 123, survey_id: 123, critterbase_critter_id: 'critterbase1' };
const mockCBCritter = { critter_id: 'critterbase1' };

it('returns critters from survey', async () => {
const mockDBConnection = getMockDBConnection({ release: sinon.stub() });

const mockSurveyCritter = { critter_id: 123, survey_id: 123, critterbase_critter_id: 'critterbase1' };
const mockCBCritter = { critter_id: 'critterbase1' };

const mockGetDBConnection = sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
const mockGetCrittersInSurvey = sinon
.stub(SurveyCritterService.prototype, 'getCrittersInSurvey')
.resolves([mockSurveyCritter]);
const mockGetMultipleCrittersByIds = sinon
.stub(CritterbaseService.prototype, 'getMultipleCrittersByIds')
.resolves([mockCBCritter]);

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

const requestHandler = getCrittersFromSurvey();

await requestHandler(mockReq, mockRes, mockNext);

expect(mockGetDBConnection.calledOnce).to.be.true;
Expand All @@ -37,6 +40,8 @@ describe('getCrittersFromSurvey', () => {
});

it('returns empty array if no critters in survey', async () => {
const mockDBConnection = getMockDBConnection({ release: sinon.stub() });

const mockGetDBConnection = sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
const mockGetCrittersInSurvey = sinon.stub(SurveyCritterService.prototype, 'getCrittersInSurvey').resolves([]);

Expand All @@ -51,12 +56,16 @@ describe('getCrittersFromSurvey', () => {
});

it('catches and re-throws errors', async () => {
const mockDBConnection = getMockDBConnection({ release: sinon.stub() });

const mockError = new Error('a test error');
const mockGetDBConnection = sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
const mockGetCrittersInSurvey = sinon
.stub(SurveyCritterService.prototype, 'getCrittersInSurvey')
.rejects(mockError);

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

const requestHandler = getCrittersFromSurvey();

try {
Expand All @@ -76,30 +85,69 @@ describe('addCritterToSurvey', () => {
sinon.restore();
});

const mockDBConnection = getMockDBConnection({ release: sinon.stub() });
const mockCBCritter = { critter_id: 'critterbase1' };
it('does not create a new critter', async () => {
const mockDBConnection = getMockDBConnection({ release: sinon.stub() });

const mockSurveyCritter = { survey_critter_id: 123, critterbase_critter_id: 'critterbase1' };
const mockCBCritter = { critter_id: 'critterbase1' };

const mockGetDBConnection = sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
const mockAddCritterToSurvey = sinon
.stub(SurveyCritterService.prototype, 'addCritterToSurvey')
.resolves(mockSurveyCritter.survey_critter_id);
const mockCreateCritter = sinon.stub(CritterbaseService.prototype, 'createCritter').resolves();

const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();
mockReq.body = mockCBCritter;

const requestHandler = addCritterToSurvey();

await requestHandler(mockReq, mockRes, mockNext);

expect(mockGetDBConnection.calledOnce).to.be.true;
expect(mockAddCritterToSurvey.calledOnce).to.be.true;
expect(mockCreateCritter.notCalled).to.be.true;
expect(mockRes.status).to.have.been.calledWith(201);
expect(mockRes.json).to.have.been.calledWith(mockSurveyCritter);
});

it('returns critters from survey', async () => {
const mockDBConnection = getMockDBConnection({ release: sinon.stub() });

const mockSurveyCritter = { survey_critter_id: 123, critterbase_critter_id: 'critterbase1' };
const mockCBCritter = { critter_id: 'critterbase1' };

const mockGetDBConnection = sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
const mockAddCritterToSurvey = sinon.stub(SurveyCritterService.prototype, 'addCritterToSurvey').resolves();
const mockAddCritterToSurvey = sinon
.stub(SurveyCritterService.prototype, 'addCritterToSurvey')
.resolves(mockSurveyCritter.survey_critter_id);
const mockCreateCritter = sinon.stub(CritterbaseService.prototype, 'createCritter').resolves(mockCBCritter);

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

const requestHandler = addCritterToSurvey();

await requestHandler(mockReq, mockRes, mockNext);

expect(mockGetDBConnection.calledOnce).to.be.true;
expect(mockAddCritterToSurvey.calledOnce).to.be.true;
expect(mockCreateCritter.calledOnce).to.be.true;
expect(mockRes.status).to.have.been.calledWith(201);
expect(mockRes.json).to.have.been.calledWith(mockCBCritter);
expect(mockRes.json).to.have.been.calledWith(mockSurveyCritter);
});

it('catches and re-throws errors', async () => {
const mockDBConnection = getMockDBConnection({ release: sinon.stub() });

const mockCBCritter = { critter_id: 'critterbase1' };

const mockError = new Error('a test error');
const mockGetDBConnection = sinon.stub(db, 'getDBConnection').returns(mockDBConnection);
const mockAddCritterToSurvey = sinon.stub(SurveyCritterService.prototype, 'addCritterToSurvey').rejects(mockError);
const mockCreateCritter = sinon.stub(CritterbaseService.prototype, 'createCritter').resolves(mockCBCritter);

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

const requestHandler = addCritterToSurvey();

try {
Expand All @@ -108,6 +156,7 @@ describe('addCritterToSurvey', () => {
} catch (actualError) {
expect(actualError).to.equal(mockError);
expect(mockAddCritterToSurvey.calledOnce).to.be.true;
expect(mockCreateCritter.calledOnce).to.be.true;
expect(mockGetDBConnection.calledOnce).to.be.true;
expect(mockDBConnection.release).to.have.been.called;
}
Expand Down
Loading