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-569/589: Add Method Techniques and Manage Sampling Information Page #1303

Merged
merged 75 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
4808c1a
wip: migration to add techinques
mauberti-bc May 28, 2024
b2ce234
wip
mauberti-bc May 28, 2024
6f487f7
insert lookup values for techniques
mauberti-bc May 28, 2024
c54dd40
wip: technique service and repo
mauberti-bc May 29, 2024
e12214d
view techniques for survey
mauberti-bc May 29, 2024
f7f9a02
added values into existing structure, new enum type needs checking bu…
LouisThedroux May 29, 2024
9da8e18
more technique attributes added
LouisThedroux May 30, 2024
1333193
method technique attribute select: broken
mauberti-bc May 30, 2024
062cfb9
adding more options, editing syntax and formatting
LouisThedroux May 31, 2024
0a56423
wip: rework sampling site page and adding methods to sites
mauberti-bc Jun 3, 2024
438d73e
finished adding core quant and qual attributes. Starting on adding op…
LouisThedroux Jun 3, 2024
5ef630a
Merge branch 'method-technique' of https://github.com/bcgov/biohubbc …
LouisThedroux Jun 3, 2024
8c16389
more attribute options
LouisThedroux Jun 3, 2024
72c2a23
formik errors on sampling site page for sample periods
mauberti-bc Jun 3, 2024
1351a60
sampling method techniques on edit sample site page
mauberti-bc Jun 4, 2024
08ba332
wip: replace method_technique_id with technique object in sample meth…
mauberti-bc Jun 4, 2024
788dc87
merge in dev
mauberti-bc Jun 4, 2024
e53d5fa
fix types: include technique object in sample location get request bu…
mauberti-bc Jun 4, 2024
9dbc75d
update observation table to include technique name
mauberti-bc Jun 4, 2024
34204ba
wip: accordion card on manage sampling site page
mauberti-bc Jun 4, 2024
dc0b175
merge latest
mauberti-bc Jun 4, 2024
d7c0f80
added method lookup attributes
LouisThedroux Jun 4, 2024
a72bd90
techniqueAttributeSelect is causing erroneous rerenders on value change
mauberti-bc Jun 4, 2024
ec426fe
Update sampling method skeletons
NickPhura Jun 4, 2024
36a4a81
Merge branch 'method-technique' of https://github.com/bcgov/biohubbc …
NickPhura Jun 4, 2024
498ee21
Remove duplicate file
NickPhura Jun 4, 2024
031ed12
Fix technique attributes form controls
NickPhura Jun 5, 2024
bdda115
Run formatter
NickPhura Jun 5, 2024
56602b3
added qualitative options
LouisThedroux Jun 5, 2024
129e1d9
Update technique attributes utils
NickPhura Jun 6, 2024
aade4ca
wip: edit technique page
mauberti-bc Jun 6, 2024
62687e3
merge latest
mauberti-bc Jun 6, 2024
fce15ce
Added more attractants
LouisThedroux Jun 6, 2024
58c73ca
added comment for file explanation
LouisThedroux Jun 6, 2024
a2b40b2
Merge remote-tracking branch 'origin/method-technique' into method-te…
LouisThedroux Jun 6, 2024
e11bdf8
wip: resolve rerendering issues with technique attributes
mauberti-bc Jun 6, 2024
6dc8966
merge latest
mauberti-bc Jun 6, 2024
c1319e6
move units from technique_attribute_quantitative to method_tehcnique_…
mauberti-bc Jun 6, 2024
64f8bee
wip: delete technique
mauberti-bc Jun 10, 2024
9b09f9f
added min, max, unit to method_lookup_attribute_quantitative
LouisThedroux Jun 11, 2024
5440cdc
added more options and cleaned up some other formatting
LouisThedroux Jun 12, 2024
7db2e46
add, edit, and delete techniques quant and qual attributes and attrac…
mauberti-bc Jun 12, 2024
a1b2a07
Merge branch 'method-technique' of github.com:bcgov/biohubbc into met…
mauberti-bc Jun 12, 2024
6a0f49d
wip: fix openapi spec for techniques
mauberti-bc Jun 12, 2024
1acd1f1
db triggers for validating technique attributes & openapi schemas
mauberti-bc Jun 12, 2024
fcaf29c
validate incoming attributes for techniques
mauberti-bc Jun 13, 2024
77278cd
Merge remote-tracking branch 'origin/dev' into method-technique
NickPhura Jun 24, 2024
1073c82
Re-arrange components
NickPhura Jun 25, 2024
f6050ea
Merge remote-tracking branch 'origin/dev' into method-technique
NickPhura Jun 25, 2024
cf23762
Misc tweaks
NickPhura Jun 26, 2024
2cfb272
Misc updates
NickPhura Jun 26, 2024
6d45491
Misc updates
NickPhura Jun 26, 2024
59f4f8c
Misc updates
NickPhura Jun 26, 2024
1e5fb42
Misc updates
NickPhura Jun 26, 2024
e46133c
Merge branch 'method-technique' of https://github.com/bcgov/biohubbc …
NickPhura Jun 26, 2024
6372639
Updates
NickPhura Jun 28, 2024
f719634
Remove temp file
NickPhura Jun 28, 2024
667bf01
More updates
NickPhura Jun 29, 2024
4431426
Merge remote-tracking branch 'origin/dev' into method-technique
NickPhura Jul 5, 2024
6ce0aa9
fix merge issues
NickPhura Jul 5, 2024
faf1683
Merge remote-tracking branch 'origin/dev' into method-technique
NickPhura Jul 5, 2024
31b5355
fix bulk technique menu
NickPhura Jul 5, 2024
fcfa986
Merge remote-tracking branch 'origin/dev' into method-technique
NickPhura Jul 9, 2024
5eb623d
WIP
NickPhura Jul 12, 2024
dedeaf4
Fix code after removing experimental react-query changes for sampling…
NickPhura Jul 12, 2024
01a8ff8
Fix linting, formatting, tests.
NickPhura Jul 15, 2024
dfd0a7b
fix: updated some nit picks
MacQSL Jul 17, 2024
c5bba81
- Fix missing project permission on sampling endpoints
NickPhura Jul 17, 2024
d54b8c3
Merge branch 'method-technique' of https://github.com/bcgov/biohubbc …
NickPhura Jul 17, 2024
ba5f3b0
Merge branch 'dev' into method-technique
NickPhura Jul 17, 2024
3213256
Add backend unit tests for technique related code.
NickPhura Jul 18, 2024
9b4e5eb
Fix dialog width
NickPhura Jul 18, 2024
30ea96f
Merge branch 'dev' into method-technique
NickPhura Jul 18, 2024
afaf570
ignore-skip
NickPhura Jul 19, 2024
18b640f
ignore-skip
NickPhura Jul 19, 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
1 change: 1 addition & 0 deletions .github/workflows/addComments.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
pull_request:
types: [opened, ready_for_review]
branches-ignore:
- test
- prod

jobs:
Expand Down
3 changes: 2 additions & 1 deletion api/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "esbenp.prettier-vscode",
"typescript.preferences.importModuleSpecifier": "non-relative"
}
18 changes: 14 additions & 4 deletions api/src/database/db-utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DatabaseError } from 'pg';
import { z } from 'zod';
import { SYSTEM_IDENTITY_SOURCE } from '../constants/database';
import { ApiExecuteSQLError } from '../errors/api-error';
Expand Down Expand Up @@ -59,6 +60,8 @@ export const syncErrorWrapper =
/**
* This function parses the passed in error and translates them into a human readable error
*
* @see https://www.postgresql.org/docs/current/errcodes-appendix.html for postgres error codes
*
* @param error error to be parsed
* @returns an error to throw
*/
Expand All @@ -67,10 +70,17 @@ const parseError = (error: any) => {
throw new ApiExecuteSQLError('SQL response failed schema check', [error]);
}

if (error.message === 'CONCURRENCY_EXCEPTION') {
// error thrown by DB trigger based on revision_count
// will be thrown if two updates to the same record are made concurrently
throw new ApiExecuteSQLError('Failed to update stale data', [error]);
if (error instanceof DatabaseError) {
if (error.message === 'CONCURRENCY_EXCEPTION') {
// error thrown by DB trigger based on revision_count
// will be thrown if two updates to the same record are made concurrently
throw new ApiExecuteSQLError('Failed to update stale data', [error]);
}

if (error.code === '23503') {
// error thrown by DB when query fails due to foreign key constraint
throw new ApiExecuteSQLError('Failed to delete record due to foreign key constraint', [error]);
}
}

// Generic error thrown if not captured above
Expand Down
154 changes: 154 additions & 0 deletions api/src/openapi/schemas/technique.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { OpenAPIV3 } from 'openapi-types';

const techniqueAttractantsSchema: OpenAPIV3.SchemaObject = {
type: 'array',
description: 'Attractants used to lure species during the technique.',
items: {
type: 'object',
required: ['attractant_lookup_id'],
additionalProperties: false,
properties: {
attractant_lookup_id: {
type: 'integer',
description: 'The ID of a known attractant type.'
}
}
}
};

const techniqueAttributesSchema: OpenAPIV3.SchemaObject = {
type: 'object',
description: 'Attributes of the technique.',
required: ['qualitative_attributes', 'quantitative_attributes'],
additionalProperties: false,
properties: {
quantitative_attributes: {
type: 'array',
items: {
type: 'object',
required: ['method_technique_attribute_quantitative_id', 'method_lookup_attribute_quantitative_id', 'value'],
additionalProperties: false,
properties: {
method_technique_attribute_quantitative_id: {
type: 'integer',
description: 'Primary key of the attribute.',
nullable: true
},
method_lookup_attribute_quantitative_id: {
type: 'string',
format: 'uuid',
description: 'The ID of a known quantitative attribute.'
},
value: {
type: 'number',
description: 'The value of the quantitative attribute.'
}
}
}
},
qualitative_attributes: {
type: 'array',
items: {
type: 'object',
required: [
'method_technique_attribute_qualitative_id',
'method_lookup_attribute_qualitative_id',
'method_lookup_attribute_qualitative_option_id'
],
additionalProperties: false,
properties: {
method_technique_attribute_qualitative_id: {
type: 'integer',
description: 'Primary key of the attribute',
nullable: true
},
method_lookup_attribute_qualitative_id: {
type: 'string',
format: 'uuid',
description: 'The ID of a known qualitative attribute.'
},
method_lookup_attribute_qualitative_option_id: {
type: 'string',
format: 'uuid',
description: 'The ID of a known qualitative attribute option.'
}
}
}
}
}
};

export const techniqueSimpleViewSchema: OpenAPIV3.SchemaObject = {
type: 'object',
required: ['method_technique_id', 'name', 'description', 'attractants'],
additionalProperties: false,
properties: {
method_technique_id: {
type: 'integer',
description: 'Primary key of the technique'
},
name: {
type: 'string',
description: 'Name of the technique.'
},
description: {
type: 'string',
description: 'Description of the technique.',
nullable: true
},
attractants: techniqueAttractantsSchema
}
};

export const techniqueCreateSchema: OpenAPIV3.SchemaObject = {
type: 'object',
required: ['name', 'description', 'method_lookup_id', 'distance_threshold', 'attractants', 'attributes'],
additionalProperties: false,
properties: {
name: {
type: 'string',
description: 'Name of the technique.'
},
description: {
type: 'string',
description: 'Description of the technique.',
nullable: true
},
method_lookup_id: {
type: 'integer',
description: 'The ID of a known method type.',
minimum: 1
},
distance_threshold: {
type: 'number',
description: 'Maximum detection distance (meters).',
nullable: true
},
attractants: techniqueAttractantsSchema,
attributes: techniqueAttributesSchema
}
};

export const techniqueUpdateSchema: OpenAPIV3.SchemaObject = {
...techniqueCreateSchema,
required: [...(techniqueCreateSchema.required ?? []), 'method_technique_id'],
properties: {
...techniqueCreateSchema.properties,
method_technique_id: {
type: 'number',
description: 'Primary key for the technique.'
}
}
};

export const techniqueViewSchema: OpenAPIV3.SchemaObject = {
...techniqueCreateSchema,
required: [...(techniqueCreateSchema.required ?? []), 'method_technique_id'],
properties: {
...techniqueCreateSchema.properties,
method_technique_id: {
type: 'number',
description: 'Primary key for the technique.'
}
}
};
54 changes: 26 additions & 28 deletions api/src/paths/codes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,45 +5,31 @@ import sinonChai from 'sinon-chai';
import * as db from '../database/db';
import { HTTPError } from '../errors/http-error';
import { CodeService } from '../services/code-service';
import { getMockDBConnection } from '../__mocks__/db';
import { getMockDBConnection, getRequestHandlerMocks } from '../__mocks__/db';
import * as codes from './codes';

chai.use(sinonChai);

describe('codes', () => {
const dbConnectionObj = getMockDBConnection();

const sampleReq = {
keycloak_token: {}
} as any;

let actualResult = {
management_action_type: null
};

const sampleRes = {
status: () => {
return {
json: (result: any) => {
actualResult = result;
}
};
}
};

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

it('should throw a 500 error when fails to fetch codes', async () => {
const dbConnectionObj = getMockDBConnection();
sinon.stub(db, 'getAPIUserDBConnection').returns(dbConnectionObj);

sinon.stub(CodeService.prototype, 'getAllCodeSets').resolves(undefined);

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

mockReq.keycloak_token = {};

try {
const result = codes.getAllCodes();
const requestHandler = codes.getAllCodes();

await result(sampleReq, null as unknown as any, null as unknown as any);
await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).status).to.equal(500);
Expand All @@ -52,28 +38,40 @@ describe('codes', () => {
});

it('should return the fetched codes on success', async () => {
const dbConnectionObj = getMockDBConnection();
sinon.stub(db, 'getAPIUserDBConnection').returns(dbConnectionObj);

sinon.stub(CodeService.prototype, 'getAllCodeSets').resolves({
management_action_type: { id: 1, name: 'management action type' }
} as any);

const result = codes.getAllCodes();
const { mockReq, mockRes, mockNext } = getRequestHandlerMocks();

await result(sampleReq, sampleRes as any, null as unknown as any);
mockReq.keycloak_token = {};

expect(actualResult.management_action_type).to.eql({ id: 1, name: 'management action type' });
const requestHandler = codes.getAllCodes();

await requestHandler(mockReq, mockRes, mockNext);

expect(mockRes.jsonValue.management_action_type).to.eql({ id: 1, name: 'management action type' });
});

it('should throw an error when a failure occurs', async () => {
const expectedError = new Error('cannot process request');

const dbConnectionObj = getMockDBConnection();
sinon.stub(db, 'getAPIUserDBConnection').returns(dbConnectionObj);

sinon.stub(CodeService.prototype, 'getAllCodeSets').rejects(expectedError);

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

mockReq.keycloak_token = {};

try {
const result = codes.getAllCodes();
const requestHandler = codes.getAllCodes();

await result(sampleReq, sampleRes as any, null as unknown as any);
await requestHandler(mockReq, mockRes, mockNext);
expect.fail();
} catch (actualError) {
expect((actualError as HTTPError).message).to.equal(expectedError.message);
Expand Down
36 changes: 33 additions & 3 deletions api/src/paths/codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ GET.apiDoc = {
'vantage_codes',
'site_selection_strategies',
'survey_progress',
'method_response_metrics'
'method_response_metrics',
'attractants'
],
properties: {
management_action_type: {
Expand Down Expand Up @@ -316,11 +317,13 @@ GET.apiDoc = {
},
survey_progress: {
type: 'array',
description: 'Indicates the progress of a survey (e.g. planned, in progress, completed).',
items: {
type: 'object',
properties: {
id: {
type: 'integer'
type: 'integer',
minimum: 1
},
name: {
type: 'string'
Expand All @@ -333,13 +336,37 @@ GET.apiDoc = {
},
method_response_metrics: {
type: 'array',
description:
'Indicates the measurement type of a sampling method (e.g. count, precent cover, biomass, etc).',
items: {
type: 'object',
additionalProperties: false,
required: ['id', 'name', 'description'],
properties: {
id: {
type: 'integer'
type: 'integer',
minimum: 1
},
name: {
type: 'string'
},
description: {
type: 'string'
}
}
}
},
attractants: {
NickPhura marked this conversation as resolved.
Show resolved Hide resolved
type: 'array',
description: 'Describes the attractants that can be used by a sampling technique.',
items: {
type: 'object',
additionalProperties: false,
required: ['id', 'name', 'description'],
properties: {
id: {
NickPhura marked this conversation as resolved.
Show resolved Hide resolved
type: 'integer',
minimum: 1
},
name: {
type: 'string'
Expand Down Expand Up @@ -395,6 +422,9 @@ export function getAllCodes(): RequestHandler {
throw new HTTP500('Failed to fetch codes');
}

// Allow browsers to cache this response for 300 seconds (5 minutes)
res.setHeader('Cache-Control', 'private, max-age=300');

return res.status(200).json(allCodeSets);
} catch (error) {
defaultLog.error({ label: 'getAllCodes', message: 'error', error });
Expand Down
2 changes: 1 addition & 1 deletion api/src/paths/project/{projectId}/delete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const DELETE: Operation = [
return {
or: [
{
validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR],
validProjectPermissions: [PROJECT_PERMISSION.COORDINATOR, PROJECT_PERMISSION.COLLABORATOR],
projectId: Number(req.params.projectId),
discriminator: 'ProjectPermission'
},
Expand Down
Loading
Loading