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

Edit sampling site fixes #1279

Merged
merged 70 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
2eba603
Initial working observation import against sampling period
NickPhura Apr 5, 2024
b1b0079
Add better loading/disabled handlers
NickPhura Apr 8, 2024
f9a4999
Merge remote-tracking branch 'origin/dev' into SIMSBIOHUB-501
NickPhura Apr 8, 2024
5545b77
Add Knip and SWC
NickPhura Apr 9, 2024
bf0c063
Update import observations
NickPhura Apr 9, 2024
f3572c7
Remove console logs
NickPhura Apr 9, 2024
3ffe5b5
Remove swc from API to fix unit tests
NickPhura Apr 9, 2024
16d7f10
Remove knip
NickPhura Apr 9, 2024
303288d
Remove gulp
NickPhura Apr 9, 2024
fe9538c
Update lock
NickPhura Apr 9, 2024
b88fe37
Merge remote-tracking branch 'origin/dev' into SIMSBIOHUB-501
NickPhura Apr 9, 2024
7b66813
standards page and api service
mauberti-bc Apr 9, 2024
72b5c65
Merge branch 'dev' into SIMSBIOHUB-501
NickPhura Apr 10, 2024
8da65db
Merge branch 'SIMSBIOHUB-501' of https://github.com/bcgov/biohubbc in…
NickPhura Apr 10, 2024
e384884
Add unit tests. fix spelling
NickPhura Apr 10, 2024
84dc2e0
Add tests
NickPhura Apr 10, 2024
a270322
update standards page
mauberti-bc Apr 11, 2024
83853b3
standards endpoint and service tests
mauberti-bc Apr 11, 2024
17538dd
finish unit tests
mauberti-bc Apr 11, 2024
df37d81
linter
mauberti-bc Apr 11, 2024
0049685
add standards to hamburger menu for smaller screens
mauberti-bc Apr 11, 2024
4611339
fix openapi spec
mauberti-bc Apr 12, 2024
3770df6
linter
mauberti-bc Apr 12, 2024
33fd406
Fix prettier (bump eslint/prettier versions)
NickPhura Apr 12, 2024
87cfc71
Update .eslintrc
NickPhura Apr 12, 2024
485745e
Update .eslintrc
NickPhura Apr 12, 2024
1da9a3f
re-run install
NickPhura Apr 12, 2024
9ceba80
Merge branch 'dev' into npTechDebt
NickPhura Apr 12, 2024
00bb321
Merge remote-tracking branch 'origin/npTechDebt' into taxon-standards
mauberti-bc Apr 12, 2024
58dfa30
prettier changes & node update
mauberti-bc Apr 13, 2024
bbb31d6
Merge branch 'taxon-standards' of github.com:bcgov/biohubbc into taxo…
mauberti-bc Apr 13, 2024
716bdcc
linter
mauberti-bc Apr 13, 2024
3f992cf
Merge branch 'dev' of github.com:bcgov/biohubbc into taxon-standards
mauberti-bc Apr 13, 2024
f2b5586
attempt to solve prettier issues
mauberti-bc Apr 13, 2024
398558c
merge in dev
mauberti-bc Apr 14, 2024
d03c8aa
basic configure table modal
mauberti-bc Apr 15, 2024
70bb5d4
update file structure
mauberti-bc Apr 15, 2024
f578f4e
refactor sampling site page
mauberti-bc Apr 20, 2024
087dde9
fix measurement issue
mauberti-bc Apr 20, 2024
7c66f62
merge in dev
mauberti-bc Apr 20, 2024
1368a42
organize sample site create and edit flows
mauberti-bc Apr 20, 2024
7f5fba9
linter
mauberti-bc Apr 22, 2024
22aad7a
linter
mauberti-bc Apr 22, 2024
05c8c26
fix tests
mauberti-bc Apr 22, 2024
26156e2
fix formik validation
mauberti-bc Apr 22, 2024
d7c0179
fix edit survey types
mauberti-bc Apr 23, 2024
c9ccbec
cleanup
mauberti-bc Apr 24, 2024
75a55ec
fix test
mauberti-bc Apr 24, 2024
285757d
jsdoc and cleanup
mauberti-bc Apr 25, 2024
fceabc6
missing dependency
mauberti-bc Apr 25, 2024
3393e58
fix duplicate keys & code smells
mauberti-bc Apr 25, 2024
74c5dea
refactor survey folder subdirectories
mauberti-bc Apr 25, 2024
5969b81
prettier
mauberti-bc Apr 25, 2024
555703e
fix file paths in tests
mauberti-bc Apr 25, 2024
ad2c5b2
merge in dev
mauberti-bc Apr 29, 2024
24a6d72
replace sample_blocks and sample_stratums with blocks and stratums, r…
mauberti-bc Apr 30, 2024
3d1dded
remove extra fields from project details response
mauberti-bc Apr 30, 2024
0c541ab
prettier
mauberti-bc Apr 30, 2024
26d3f3c
re-remove surveyed_areas_all
mauberti-bc Apr 30, 2024
2e32c81
prettier
mauberti-bc Apr 30, 2024
faa2c80
merge in remote
mauberti-bc Apr 30, 2024
7b7282e
update openapi sec
mauberti-bc Apr 30, 2024
1f08446
update tests
mauberti-bc Apr 30, 2024
cd6f3bc
fix sonarcloud alert
mauberti-bc Apr 30, 2024
e58362f
additionalproperties: false
mauberti-bc Apr 30, 2024
5a6498f
Merge remote-tracking branch 'origin/dev' into sample-site-req
NickPhura May 2, 2024
06f3792
kml support
mauberti-bc May 2, 2024
bdbc304
Refactor database zod schema validation. Now strict.
NickPhura May 4, 2024
ee622ca
Fix strict zod issues.
NickPhura May 7, 2024
d4cf6ae
Fix test
NickPhura May 7, 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
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,17 @@ pipeline-install: ## Runs `npm install` for all projects
## Run `docker logs <container> -f` commands for all projects
## - You can include additional parameters by appaending an `args` param
## - Ex: `make log-app args="--tail 0"`
## Note: The default args, if not provided, are `--tail 2000`
## ------------------------------------------------------------------------------

args ?= --tail 2000 ## Default args if none are provided

log: ## Runs `docker-compose logs -f` for all containers
@echo "==============================================="
@echo "Running docker logs for the app container"
@echo "==============================================="
@docker-compose logs -f $(args)

log-app: ## Runs `docker logs <container> -f` for the app container
@echo "==============================================="
@echo "Running docker logs for the app container"
Expand Down
8 changes: 5 additions & 3 deletions api/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,12 +187,12 @@
return json.apply(res, args);
}

const body = args[0];
const reqBody = args[0];

Check warning on line 190 in api/src/app.ts

View check run for this annotation

Codecov / codecov/patch

api/src/app.ts#L190

Added line #L190 was not covered by tests

// Run openapi response validation function
const validationResult: { message: any; errors: any[] } | undefined = res['validateResponse'](
res.statusCode,
body
reqBody
);

let validationMessage = '';
Expand All @@ -215,7 +215,9 @@
message: validationMessage,
error: errorList,
req_url: `${req.method} ${req.url}`,
res_body: body
req_params: req.params,
req_body: req.body,
res_body: reqBody
});

throw new HTTP500(validationMessage, errorList);
Expand Down
56 changes: 1 addition & 55 deletions api/src/database/db-utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,13 @@
import { expect } from 'chai';
import { QueryResult } from 'pg';
import sinon from 'sinon';
import { z } from 'zod';
import { SYSTEM_IDENTITY_SOURCE } from '../constants/database';
import {
BceidBasicUserInformation,
BceidBusinessUserInformation,
DatabaseUserInformation,
IdirUserInformation
} from '../utils/keycloak-utils';
import { getGenericizedKeycloakUserInformation, getZodQueryResult } from './db-utils';

/**
* Enforces that a zod schema satisfies an existing type definition.
*
* Code copied from: https://github.com/colinhacks/zod/issues/372#issuecomment-1280054492
* An unresolved feature request was opened as well: https://github.com/colinhacks/zod/issues/2084
*
* Note: This may not be sufficient to cover ALL possible scenarios.
*
* @example
* const myZodSchema = z.object({...});
* // Compile error if `myZodSchema` doesn't satisfy `TypeDefinitionToCompareTo`
* zodImplements<TypeDefinitionToCompareTo>().with(myZodSchema.shape);
*
* @template Model
* @return {*}
*/
function zodImplements<Model = never>() {
type ZodImplements<Model> = {
[key in keyof Model]-?: undefined extends Model[key]
? null extends Model[key]
? z.ZodNullableType<z.ZodOptionalType<z.ZodType<Model[key]>>>
: z.ZodOptionalType<z.ZodType<Model[key]>>
: null extends Model[key]
? z.ZodNullableType<z.ZodType<Model[key]>>
: z.ZodType<Model[key]>;
};

return {
with: <
Schema extends ZodImplements<Model> & {
[unknownKey in Exclude<keyof Schema, keyof Model>]: never;
}
>(
schema: Schema
) => z.object(schema)
};
}

describe('getZodQueryResult', () => {
it('defines a zod schema that conforms to the real pg `QueryResult` type', () => {
const zodQueryResultRow = z.object({});

const zodQueryResult = getZodQueryResult(zodQueryResultRow);

// Not a traditional test: will just cause a compile error if the zod schema doesn't satisfy the `QueryResult` type
zodImplements<QueryResult>().with(zodQueryResult.shape);

// Dummy assertion to satisfy linter
expect(true).to.be.true;
});
});
import { getGenericizedKeycloakUserInformation } from './db-utils';

describe('getGenericizedKeycloakUserInformation', () => {
afterEach(() => {
Expand Down
28 changes: 0 additions & 28 deletions api/src/database/db-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,34 +77,6 @@ const parseError = (error: any) => {
throw new ApiExecuteSQLError('Failed to execute SQL', [error]);
};

/**
* A re-definition of the pg `QueryResult` type using Zod.
*
* @template T
* @param {T} zodQueryResultRow A zod schema, used to define the `rows` field of the response. In pg, this would
* be the `QueryResultRow` type.
*/
export const getZodQueryResult = <T extends z.Schema>(zodQueryResultRow: T) =>
z.object({
rows: z.array(zodQueryResultRow),
command: z.string(),
rowCount: z.number().nullable(),
// Using `coerce` as a workaround for an issue with the QueryResult type definition: it specifies oid is always a
// number, but in reality it can return `null`.
oid: z.coerce.number(),
fields: z.array(
z.object({
name: z.string(),
tableID: z.number(),
columnID: z.number(),
dataTypeID: z.number(),
dataTypeSize: z.number(),
dataTypeModifier: z.number(),
format: z.string()
})
)
});

/**
* Converts a type specific keycloak user information object with type specific properties into a new object with
* generic properties.
Expand Down
125 changes: 82 additions & 43 deletions api/src/database/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,7 @@
ServiceClientUserInformation
} from '../utils/keycloak-utils';
import { getLogger } from '../utils/logger';
import {
asyncErrorWrapper,
getGenericizedKeycloakUserInformation,
getZodQueryResult,
syncErrorWrapper
} from './db-utils';
import { asyncErrorWrapper, getGenericizedKeycloakUserInformation, syncErrorWrapper } from './db-utils';

const defaultLog = getLogger('database/db');

Expand Down Expand Up @@ -152,33 +147,37 @@
* Performs a query against this connection, returning the results.
*
* @example
* // Create a basic SQLStatement object
* const sqlStatement = SQL`select * from table where name = ${name}`;
* const response = await connection.sql(sqlStatement, ZodSchema);
*
* @param {SQLStatement} sqlStatement SQL statement object
* @param {z.Schema<T, any, any>} zodSchema An optional zod schema
* @return {*} {(Promise<QueryResult<any>>)}
* @param {z.ZodSchema<T, any, any>} [ZodSchema] An optional zod schema that defines the expected shape of a `row`.
* @return {*} {(Promise<QueryResult<T>>)}
* @throws If the connection is not open.
* @memberof IDBConnection
*/
sql: <T extends pg.QueryResultRow = any>(
sqlStatement: SQLStatement,
zodSchema?: z.Schema<T, any, any>
ZodSchema?: z.ZodSchema<T, any, any>
) => Promise<pg.QueryResult<T>>;
/**
* Performs a query against this connection, returning the results.
*
* @example
* const queryBuilder = getKnex().select().from('table').where('name', name);
* const response = await connection.knex(queryBuilder, ZodSchema);
*
* @see {@link getKnex} to get a knex instance.
*
* @param {Knex.QueryBuilder} queryBuilder Knex query builder object
* @param {z.Schema<T, any, any>} zodSchema An optional zod schema
* @return {*} {(Promise<QueryResult<any>>)}
* @param {z.ZodSchema<T, any, any>} [ZodSchema] An optional zod schema that defines the expected shape of a `row`.
* @return {*} {(Promise<QueryResult<T>>)}
* @throws If the connection is not open.
* @memberof IDBConnection
*/
knex: <T extends pg.QueryResultRow = any>(
queryBuilder: Knex.QueryBuilder,
zodSchema?: z.Schema<T, any, any>
ZodSchema?: z.ZodSchema<T, any, any>
) => Promise<pg.QueryResult<T>>;
/**
* Get the ID of the system user in context.
Expand Down Expand Up @@ -341,85 +340,125 @@
*
* @template T
* @param {SQLStatement} sqlStatement SQL statement object
* @param {z.Schema<T, any, any>} zodSchema An optional zod schema
* @param {z.ZodSchema<T, any, any>} [ZodSchema] An optional zod schema that defines the expected shape of a `row`.
* @throws {Error} if the connection is not open
* @return {*} {Promise<pg.QueryResult<T>>}
*/
const _sql = async <T extends pg.QueryResultRow = any>(
sqlStatement: SQLStatement,
zodSchema?: z.Schema<T, any, any>
ZodSchema?: z.ZodSchema<T, any, any>
): Promise<pg.QueryResult<T>> => {
if (process.env.DATABASE_RESPONSE_VALIDATION_ENABLED !== 'true') {
// Don't validate database responses against provided zod schema
return _query(sqlStatement.text, sqlStatement.values);
}

const queryStart = Date.now();

const response = await _query(sqlStatement.text, sqlStatement.values);

const queryEnd = Date.now();

if (!zodSchema) {
defaultLog.silly({ label: '_sql', message: sqlStatement.text, queryExecutionTime: queryEnd - queryStart });
// No zod schema provided
defaultLog.silly({
label: '_sql',
message: 'Sql performance',
sql: { sql: sqlStatement.text, bindings: sqlStatement.values },
queryExecutionTime: queryEnd - queryStart
});

if (!ZodSchema || process.env.DATABASE_RESPONSE_VALIDATION_ENABLED !== 'true') {
// No zod schema provided, or database response validation is disabled
return response;
}

// Validate the response against the zod schema
// Validate the response rows against the zod schema
const zodStart = Date.now();
const validatedResponse = getZodQueryResult(zodSchema).parseAsync(response);

const zodResponse =
ZodSchema instanceof z.ZodObject
? z.strictObject({ rows: z.array(ZodSchema.strict()) }).safeParse({ rows: response.rows })
: z.strictObject({ rows: z.array(ZodSchema) }).safeParse({ rows: response.rows });

const zodEnd = Date.now();

defaultLog.silly({
label: '_sql + zod',
message: sqlStatement.text,
label: '_sql',
message: 'Zod performance',
sql: { sql: sqlStatement.text, bindings: sqlStatement.values },
queryExecutionTime: queryEnd - queryStart,
zodExecutionTime: zodEnd - zodStart
});
return validatedResponse;

if (!zodResponse.success) {
defaultLog.debug({

Check warning on line 388 in api/src/database/db.ts

View check run for this annotation

Codecov / codecov/patch

api/src/database/db.ts#L388

Added line #L388 was not covered by tests
label: '_sql',
message: 'zodResponse',
zodResponse
});

throw new ApiExecuteSQLError('Failed to validate database response', zodResponse.error.errors);

Check warning on line 394 in api/src/database/db.ts

View check run for this annotation

Codecov / codecov/patch

api/src/database/db.ts#L394

Added line #L394 was not covered by tests
}

return response;

Check warning on line 397 in api/src/database/db.ts

View check run for this annotation

Codecov / codecov/patch

api/src/database/db.ts#L397

Added line #L397 was not covered by tests
};

/**
* Performs a query against this connection, returning the results.
*
* @template T
* @param {Knex.QueryBuilder} queryBuilder Knex query builder object
* @param {z.Schema<T, any, any>} zodSchema An optional zod schema
* @param {z.ZodSchema<T, any, any>} [ZodSchema] An optional zod schema that defines the expected shape of a `row`.
* @throws {Error} if the connection is not open
* @return {*} {Promise<pg.QueryResult<T>>}
*/
const _knex = async <T extends pg.QueryResultRow = any>(
queryBuilder: Knex.QueryBuilder,
zodSchema?: z.Schema<T, any, any>
ZodSchema?: z.ZodSchema<T, any, any>
) => {
const { sql, bindings } = queryBuilder.toSQL().toNative();

if (process.env.DATABASE_RESPONSE_VALIDATION_ENABLED !== 'true') {
// Don't validate database responses against provided zod schema
return _query(sql, bindings as any[]);
}

const queryStart = Date.now();

const response = await _query(sql, bindings as any[]);

const queryEnd = Date.now();

if (!zodSchema) {
defaultLog.silly({ label: '_knex', message: sql, queryExecutionTime: queryEnd - queryStart });
// No zod schema provided
defaultLog.silly({

Check warning on line 421 in api/src/database/db.ts

View check run for this annotation

Codecov / codecov/patch

api/src/database/db.ts#L421

Added line #L421 was not covered by tests
label: '_knex',
message: 'Sql performance',
sql: { sql, bindings },
queryExecutionTime: queryEnd - queryStart
});

if (!ZodSchema || process.env.DATABASE_RESPONSE_VALIDATION_ENABLED !== 'true') {
// No zod schema provided, or database response validation is disabled
return response;
}

// Validate the response against the zod schema
// Validate the response rows against the zod schema
const zodStart = Date.now();
const validatedResponse = getZodQueryResult(zodSchema).parseAsync(response);

const zodResponse =
ZodSchema instanceof z.ZodObject
? z.strictObject({ rows: z.array(ZodSchema.strict()) }).safeParse({ rows: response.rows })
: z.object({ rows: z.array(ZodSchema) }).safeParse({ rows: response.rows });

const zodEnd = Date.now();

defaultLog.silly({
label: '_knex + zod',
message: sql,
label: '_knex',
message: 'Zod performance',
sql: { sql, bindings },
queryExecutionTime: queryEnd - queryStart,
zodExecutionTime: zodEnd - zodStart
});
return validatedResponse;

if (!zodResponse.success) {
defaultLog.debug({

Check warning on line 452 in api/src/database/db.ts

View check run for this annotation

Codecov / codecov/patch

api/src/database/db.ts#L452

Added line #L452 was not covered by tests
label: '_knex',
message: 'zodResponse',
zodResponse
});

throw new ApiExecuteSQLError('Failed to validate database response', zodResponse.error.errors);

Check warning on line 458 in api/src/database/db.ts

View check run for this annotation

Codecov / codecov/patch

api/src/database/db.ts#L458

Added line #L458 was not covered by tests
}

return response;

Check warning on line 461 in api/src/database/db.ts

View check run for this annotation

Codecov / codecov/patch

api/src/database/db.ts#L461

Added line #L461 was not covered by tests
};

/**
Expand Down
2 changes: 2 additions & 0 deletions api/src/models/biohub-create.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe('PostSurveyObservationToBiohubObject', () => {
const obj = {
survey_observation_id: 1,
survey_id: 1,
wldtaxonomic_units_id: 1,
survey_sample_site_id: 1,
survey_sample_method_id: 1,
survey_sample_period_id: 1,
Expand Down Expand Up @@ -158,6 +159,7 @@ describe('PostSurveySubmissionToBioHubObject', () => {
{
survey_observation_id: 1,
survey_id: 1,
wldtaxonomic_units_id: 1,
survey_sample_site_id: 1,
survey_sample_method_id: 1,
survey_sample_period_id: 1,
Expand Down
Loading
Loading