diff --git a/api/README.md b/api/README.md index e0a7c303ce..ee143762c1 100644 --- a/api/README.md +++ b/api/README.md @@ -4,9 +4,8 @@ | Technology | Version | Website | Description | | ---------- | ------- | ------------------------------------ | -------------------- | -| node | 10.x.x | https://nodejs.org/en/ | JavaScript Runtime | +| node | 14.x.x | https://nodejs.org/en/ | JavaScript Runtime | | npm | 6.x.x | https://www.npmjs.com/ | Node Package Manager | -| PostgreSQL | 9.6 | https://www.postgresql.org/download/ | PSQL database | @@ -14,7 +13,9 @@ The root API schema is defined in `./src/openapi/api.ts`. -If this project is running in docker you can view the api docs at: `http://localhost:6100/api/api-docs/`. +If this project is running in docker you can view the beautified api docs at: `http://localhost:6100/api-docs/`. + +- The raw api-docs are available at: `http://localhost:6100/raw-api-docs/`. This project uses npm package `express-openapi` via `./app.ts` to automatically generate the express server and its routes, based on the contents of the `./src/openapi/api.ts` and the `./src/path/` content. diff --git a/api/src/models/survey-create.test.ts b/api/src/models/survey-create.test.ts index 8b28733e5a..5adfcd7e2a 100644 --- a/api/src/models/survey-create.test.ts +++ b/api/src/models/survey-create.test.ts @@ -14,10 +14,6 @@ describe('PostSurveyObject', () => { expect(data.survey_name).to.equal(null); }); - it('sets survey_purpose', () => { - expect(data.survey_purpose).to.equal(null); - }); - it('sets focal_species', () => { expect(data.focal_species).to.eql([]); }); @@ -26,10 +22,6 @@ describe('PostSurveyObject', () => { expect(data.ancillary_species).to.eql([]); }); - it('sets common survey methodology id', () => { - expect(data.common_survey_methodology_id).to.equal(null); - }); - it('sets start_date', () => { expect(data.start_date).to.equal(null); }); @@ -116,10 +108,6 @@ describe('PostSurveyObject', () => { expect(data.survey_name).to.equal(surveyObj.survey_name); }); - it('sets survey_purpose', () => { - expect(data.survey_purpose).to.equal(surveyObj.survey_purpose); - }); - it('sets focal_species', () => { expect(data.focal_species).to.eql(surveyObj.focal_species); }); @@ -128,10 +116,6 @@ describe('PostSurveyObject', () => { expect(data.ancillary_species).to.eql(surveyObj.ancillary_species); }); - it('sets common_survey_methodology_id', () => { - expect(data.common_survey_methodology_id).to.eql(surveyObj.common_survey_methodology_id); - }); - it('sets start_date', () => { expect(data.start_date).to.equal(surveyObj.start_date); }); @@ -205,10 +189,6 @@ describe('PostSurveyObject', () => { expect(data.survey_name).to.equal(surveyObj.survey_name); }); - it('sets survey_purpose', () => { - expect(data.survey_purpose).to.equal(surveyObj.survey_purpose); - }); - it('sets focal_species', () => { expect(data.focal_species).to.eql(surveyObj.focal_species); }); @@ -217,10 +197,6 @@ describe('PostSurveyObject', () => { expect(data.ancillary_species).to.eql(surveyObj.ancillary_species); }); - it('sets common_survey_methodology_id', () => { - expect(data.common_survey_methodology_id).to.eql(surveyObj.common_survey_methodology_id); - }); - it('sets start_date', () => { expect(data.start_date).to.equal(surveyObj.start_date); }); diff --git a/api/src/models/survey-create.ts b/api/src/models/survey-create.ts index 0b30d644ae..5638a4d80d 100644 --- a/api/src/models/survey-create.ts +++ b/api/src/models/survey-create.ts @@ -17,12 +17,15 @@ export class PostSurveyObject { sedis_procedures_accepted: boolean; focal_species: number[]; ancillary_species: number[]; - common_survey_methodology_id: number; + field_method_id: number; + ecological_season_id: number; + vantage_code_ids: number[]; start_date: string; end_date: string; survey_area_name: string; survey_data_proprietary: boolean; - survey_purpose: string; + intended_outcome_id: number; + additional_details: string; geometry: Feature[]; permit_number: string; permit_type: string; @@ -48,7 +51,7 @@ export class PostSurveyObject { this.sedis_procedures_accepted = obj?.sedis_procedures_accepted === 'true' || false; this.focal_species = (obj?.focal_species?.length && obj.focal_species) || []; this.ancillary_species = (obj?.ancillary_species?.length && obj.ancillary_species) || []; - this.common_survey_methodology_id = obj?.common_survey_methodology_id || null; + this.field_method_id = obj?.field_method_id || null; this.start_date = obj?.start_date || null; this.survey_area_name = obj?.survey_area_name || null; this.permit_number = obj?.permit_number || null; @@ -56,7 +59,10 @@ export class PostSurveyObject { this.funding_sources = (obj?.funding_sources?.length && obj.funding_sources) || []; this.survey_data_proprietary = obj?.survey_data_proprietary === 'true' || false; this.survey_name = obj?.survey_name || null; - this.survey_purpose = obj?.survey_purpose || null; + this.intended_outcome_id = obj?.intended_outcome_id || null; + this.ecological_season_id = obj?.ecological_season_id || null; + this.additional_details = obj?.additional_details || null; + this.vantage_code_ids = (obj?.vantage_code_ids?.length && obj.vantage_code_ids) || []; this.geometry = (obj?.geometry?.length && obj.geometry) || []; this.survey_proprietor = (obj && obj.survey_data_proprietary === 'true' && new PostSurveyProprietorData(obj)) || undefined; diff --git a/api/src/models/survey-update.test.ts b/api/src/models/survey-update.test.ts index 355d345dd4..d959730136 100644 --- a/api/src/models/survey-update.test.ts +++ b/api/src/models/survey-update.test.ts @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { describe } from 'mocha'; -import { GetUpdateSurveyDetailsData, PutSurveyDetailsData } from './survey-update'; +import { GetUpdateSurveyDetailsData, PutSurveyDetailsData, PutSurveyPurposeAndMethodologyData } from './survey-update'; describe('GetUpdateSurveyDetailsData', () => { describe('No values provided', () => { @@ -14,10 +14,6 @@ describe('GetUpdateSurveyDetailsData', () => { expect(data.survey_name).to.equal(''); }); - it('sets survey_purpose', () => { - expect(data.survey_purpose).to.equal(''); - }); - it('sets focal_species', () => { expect(data.focal_species).to.eql([]); }); @@ -26,10 +22,6 @@ describe('GetUpdateSurveyDetailsData', () => { expect(data.ancillary_species).to.eql([]); }); - it('sets common survey methodology id', () => { - expect(data.common_survey_methodology_id).to.equal(null); - }); - it('sets start_date', () => { expect(data.start_date).to.equal(''); }); @@ -107,10 +99,6 @@ describe('GetUpdateSurveyDetailsData', () => { expect(data.survey_name).to.equal(surveyData.name); }); - it('sets survey_purpose', () => { - expect(data.survey_purpose).to.equal(surveyData.objectives); - }); - it('sets focal_species', () => { expect(data.focal_species).to.eql(surveyData.focal_species); }); @@ -119,10 +107,6 @@ describe('GetUpdateSurveyDetailsData', () => { expect(data.ancillary_species).to.eql(surveyData.ancillary_species); }); - it('sets common survey methodology id', () => { - expect(data.common_survey_methodology_id).to.equal(surveyData.common_survey_methodology_id); - }); - it('sets start_date', () => { expect(data.start_date).to.equal(surveyData.start_date); }); @@ -204,10 +188,6 @@ describe('GetUpdateSurveyDetailsData', () => { expect(data.survey_name).to.equal(surveyData.name); }); - it('sets survey_purpose', () => { - expect(data.survey_purpose).to.equal(surveyData.objectives); - }); - it('sets focal_species', () => { expect(data.focal_species).to.eql(surveyData.focal_species); }); @@ -216,10 +196,6 @@ describe('GetUpdateSurveyDetailsData', () => { expect(data.ancillary_species).to.eql(surveyData.ancillary_species); }); - it('sets common survey methodology id', () => { - expect(data.common_survey_methodology_id).to.equal(surveyData.common_survey_methodology_id); - }); - it('sets start_date', () => { expect(data.start_date).to.equal(surveyData.start_date); }); @@ -274,10 +250,6 @@ describe('PutSurveyData', () => { expect(data.name).to.equal(null); }); - it('sets objectives', () => { - expect(data.objectives).to.equal(null); - }); - it('sets focal_species', () => { expect(data.focal_species).to.eql([]); }); @@ -286,10 +258,6 @@ describe('PutSurveyData', () => { expect(data.ancillary_species).to.eql([]); }); - it('sets common_survey_methodology_id', () => { - expect(data.common_survey_methodology_id).to.equal(null); - }); - it('sets geometry', () => { expect(data.geometry).to.equal(null); }); @@ -363,10 +331,6 @@ describe('PutSurveyData', () => { expect(data.name).to.equal(surveyData.survey_details.survey_name); }); - it('sets objectives', () => { - expect(data.objectives).to.equal(surveyData.survey_details.survey_purpose); - }); - it('sets focal_species', () => { expect(data.focal_species).to.eql(surveyData.survey_details.focal_species); }); @@ -375,10 +339,6 @@ describe('PutSurveyData', () => { expect(data.ancillary_species).to.eql(surveyData.survey_details.ancillary_species); }); - it('sets common_survey_methodology_id', () => { - expect(data.common_survey_methodology_id).to.equal(surveyData.survey_details.common_survey_methodology_id); - }); - it('sets start_date', () => { expect(data.start_date).to.equal(surveyData.survey_details.start_date); }); @@ -408,3 +368,87 @@ describe('PutSurveyData', () => { }); }); }); + +describe('PutSurveyPurposeAndMethodologyData', () => { + describe('No values provided', () => { + let data: PutSurveyPurposeAndMethodologyData; + + before(() => { + data = new PutSurveyPurposeAndMethodologyData(null); + }); + + it('sets id', () => { + expect(data.id).to.equal(null); + }); + + it('sets intended_outcomes_id', () => { + expect(data.intended_outcome_id).to.eql(null); + }); + + it('sets field_method_id', () => { + expect(data.field_method_id).to.eql(null); + }); + + it('sets additional_details', () => { + expect(data.additional_details).to.equal(null); + }); + + it('sets ecological_season_id', () => { + expect(data.ecological_season_id).to.equal(null); + }); + + // it('sets vantage_code_ids', () => { + // expect(data.vantage_code_ids).to.equal([]); + // }); + + it('sets revision_count', () => { + expect(data.revision_count).to.equal(null); + }); + }); + + describe('All values provided', () => { + let data: PutSurveyPurposeAndMethodologyData; + + const purposeAndMethodologyData = { + id: 1, + field_method_id: 1, + additional_details: 'additional details', + vantage_code_ids: [1, 2], + ecological_season_id: 1, + intended_outcome_id: 1, + revision_count: 1 + }; + + before(() => { + data = new PutSurveyPurposeAndMethodologyData(purposeAndMethodologyData); + }); + + it('sets id', () => { + expect(data.id).to.equal(purposeAndMethodologyData.id); + }); + + it('sets intended_outcomes_id', () => { + expect(data.intended_outcome_id).to.eql(purposeAndMethodologyData.intended_outcome_id); + }); + + it('sets additional_details', () => { + expect(data.additional_details).to.eql(purposeAndMethodologyData.additional_details); + }); + + it('sets field_method_id', () => { + expect(data.field_method_id).to.equal(purposeAndMethodologyData.field_method_id); + }); + + it('sets ecological_season_id', () => { + expect(data.ecological_season_id).to.equal(purposeAndMethodologyData.ecological_season_id); + }); + + it('sets vantage_code_ids', () => { + expect(data.vantage_code_ids).to.equal(purposeAndMethodologyData.vantage_code_ids); + }); + + it('sets revision_count', () => { + expect(data.revision_count).to.equal(purposeAndMethodologyData.revision_count); + }); + }); +}); diff --git a/api/src/models/survey-update.ts b/api/src/models/survey-update.ts index 482e242bdf..c408a9d91a 100644 --- a/api/src/models/survey-update.ts +++ b/api/src/models/survey-update.ts @@ -14,10 +14,8 @@ const defaultLog = getLogger('models/survey-update'); export class GetUpdateSurveyDetailsData { id: number; survey_name: string; - survey_purpose: string; focal_species: (string | number)[]; ancillary_species: (string | number)[]; - common_survey_methodology_id: number; start_date: string; end_date: string; biologist_first_name: string; @@ -45,12 +43,10 @@ export class GetUpdateSurveyDetailsData { this.id = surveyDetailsData?.id ?? null; this.survey_name = surveyDetailsData?.name || ''; - this.survey_purpose = surveyDetailsData?.objectives || ''; this.focal_species = surveyDetailsData?.focal_species || []; this.ancillary_species = surveyDetailsData?.ancillary_species || []; this.start_date = surveyDetailsData?.start_date || ''; this.end_date = surveyDetailsData?.end_date || ''; - this.common_survey_methodology_id = surveyDetailsData?.common_survey_methodology_id ?? null; this.biologist_first_name = surveyDetailsData?.lead_first_name || ''; this.biologist_last_name = surveyDetailsData?.lead_last_name || ''; this.survey_area_name = surveyDetailsData?.location_name || ''; @@ -77,10 +73,8 @@ export class GetUpdateSurveyDetailsData { */ export class PutSurveyDetailsData { name: string; - objectives: string; focal_species: number[]; ancillary_species: number[]; - common_survey_methodology_id: number; start_date: string; end_date: string; lead_first_name: string; @@ -105,13 +99,11 @@ export class PutSurveyDetailsData { }); this.name = obj?.survey_details?.survey_name || null; - this.objectives = obj?.survey_details?.survey_purpose || null; this.focal_species = (obj?.survey_details?.focal_species?.length && obj.survey_details?.focal_species) || []; this.ancillary_species = (obj?.survey_details?.ancillary_species?.length && obj.survey_details?.ancillary_species) || []; this.start_date = obj?.survey_details?.start_date || null; this.end_date = obj?.survey_details?.end_date || null; - this.common_survey_methodology_id = obj?.survey_details?.common_survey_methodology_id || null; this.lead_first_name = obj?.survey_details?.biologist_first_name || null; this.lead_last_name = obj?.survey_details?.biologist_last_name || null; this.location_name = obj?.survey_details?.survey_area_name || null; @@ -152,3 +144,31 @@ export class PutSurveyProprietorData { this.revision_count = obj?.revision_count ?? null; } } + +/** + * Pre-processes PUT /project/{projectId}/survey/{surveyId} survey purpose and methodology data for update + * + * @export + * @class PutSurveyPurposeAndMethodologyData + */ +export class PutSurveyPurposeAndMethodologyData { + id: number; + intended_outcome_id: number; + field_method_id: number; + additional_details: string; + ecological_season_id: number; + vantage_code_ids: number[]; + revision_count: number; + + constructor(obj?: any) { + defaultLog.debug({ label: 'PutSurveyPurposeAndMethodologyData', message: 'params', obj }); + + this.id = obj?.id ?? null; + this.intended_outcome_id = obj?.intended_outcome_id || null; + this.field_method_id = obj?.field_method_id || null; + this.additional_details = obj?.additional_details || null; + this.ecological_season_id = obj?.ecological_season_id || null; + this.vantage_code_ids = (obj?.vantage_code_ids?.length && obj.vantage_code_ids) || []; + this.revision_count = obj?.revision_count ?? null; + } +} diff --git a/api/src/models/survey-view-update.ts b/api/src/models/survey-view-update.ts index 4f20d5d3f6..1710a66e54 100644 --- a/api/src/models/survey-view-update.ts +++ b/api/src/models/survey-view-update.ts @@ -39,3 +39,32 @@ export class GetSurveyProprietorData { this.revision_count = data?.revision_count ?? null; } } + +export class GetSurveyPurposeAndMethodologyData { + constructor(responseData?: any) { + defaultLog.debug({ + label: 'GetSurveyPurposeAndMethodologyData', + message: 'params', + data: responseData + }); + + const obj = {}; + + responseData.forEach((item: any) => { + if (!obj[item.id]) { + obj[item.id] = { + id: item.id, + intended_outcome_id: item.intended_outcome_id, + additional_details: item.additional_details, + field_method_id: item.field_method_id, + ecological_season_id: item.ecological_season_id, + revision_count: item.revision_count, + vantage_code_ids: [item.vantage_id] + }; + } else { + obj[item.id].vantage_code_ids.push(item.vantage_id); + } + }); + return Object.values(obj); + } +} diff --git a/api/src/models/survey-view.test.ts b/api/src/models/survey-view.test.ts index 2dfde5144a..307d92479e 100644 --- a/api/src/models/survey-view.test.ts +++ b/api/src/models/survey-view.test.ts @@ -14,10 +14,6 @@ describe('GetViewSurveyDetailsData', () => { expect(data.survey_name).to.equal(''); }); - it('sets survey_purpose', () => { - expect(data.survey_purpose).to.equal(''); - }); - it('sets focal_species', () => { expect(data.focal_species).to.eql([]); }); @@ -26,10 +22,6 @@ describe('GetViewSurveyDetailsData', () => { expect(data.ancillary_species).to.eql([]); }); - it('sets common survey methodology', () => { - expect(data.common_survey_methodology).to.equal(''); - }); - it('sets start_date', () => { expect(data.start_date).to.equal(''); }); @@ -115,10 +107,6 @@ describe('GetViewSurveyDetailsData', () => { expect(data.survey_name).to.equal(surveyData.name); }); - it('sets survey_purpose', () => { - expect(data.survey_purpose).to.equal(surveyData.objectives); - }); - it('sets focal_species', () => { expect(data.focal_species).to.eql(surveyData.focal_species); }); @@ -127,10 +115,6 @@ describe('GetViewSurveyDetailsData', () => { expect(data.ancillary_species).to.eql(surveyData.ancillary_species); }); - it('sets common survey methodology', () => { - expect(data.common_survey_methodology).to.equal(surveyData.common_survey_methodology); - }); - it('sets start_date', () => { expect(data.start_date).to.equal(surveyData.start_date); }); diff --git a/api/src/models/survey-view.ts b/api/src/models/survey-view.ts index bd67c49027..6471d5d970 100644 --- a/api/src/models/survey-view.ts +++ b/api/src/models/survey-view.ts @@ -14,10 +14,8 @@ const defaultLog = getLogger('models/survey-view'); export class GetViewSurveyDetailsData { id: number; survey_name: string; - survey_purpose: string; focal_species: (string | number)[]; ancillary_species: (string | number)[]; - common_survey_methodology: string; start_date: string; end_date: string; biologist_first_name: string; @@ -47,13 +45,11 @@ export class GetViewSurveyDetailsData { this.id = surveyDetailsData?.id ?? null; this.occurrence_submission_id = surveyDetailsData?.occurrence_submission_id ?? null; this.survey_name = surveyDetailsData?.name || ''; - this.survey_purpose = surveyDetailsData?.objectives || ''; this.focal_species = surveyDetailsData?.focal_species || []; this.ancillary_species = surveyDetailsData?.ancillary_species || []; this.start_date = surveyDetailsData?.start_date || ''; this.end_date = surveyDetailsData?.end_date || ''; this.biologist_first_name = surveyDetailsData?.lead_first_name || ''; - this.common_survey_methodology = surveyDetailsData?.common_survey_methodology || ''; this.biologist_last_name = surveyDetailsData?.lead_last_name || ''; this.survey_area_name = surveyDetailsData?.location_name || ''; this.geometry = (surveyDetailsData?.geometry?.length && surveyDetailsData.geometry) || []; diff --git a/api/src/openapi/schemas/survey.test.ts b/api/src/openapi/schemas/survey.test.ts deleted file mode 100644 index 4235a8366c..0000000000 --- a/api/src/openapi/schemas/survey.test.ts +++ /dev/null @@ -1,20 +0,0 @@ -import Ajv from 'ajv'; -import { expect } from 'chai'; -import { describe } from 'mocha'; -import { surveyCreatePostRequestObject, surveyIdResponseObject } from './survey'; - -describe('surveyCreatePostRequestObject', () => { - const ajv = new Ajv(); - - it('is valid openapi v3 schema', () => { - expect(ajv.validateSchema(surveyCreatePostRequestObject)).to.be.true; - }); -}); - -describe('surveyIdResponseObject', () => { - const ajv = new Ajv(); - - it('is valid openapi v3 schema', () => { - expect(ajv.validateSchema(surveyIdResponseObject)).to.be.true; - }); -}); diff --git a/api/src/openapi/schemas/survey.ts b/api/src/openapi/schemas/survey.ts deleted file mode 100644 index b957162d9b..0000000000 --- a/api/src/openapi/schemas/survey.ts +++ /dev/null @@ -1,140 +0,0 @@ -/** - * Request Object for survey create POST request - */ -export const surveyCreatePostRequestObject = { - title: 'SurveyProject post request object', - type: 'object', - required: [ - 'survey_name', - 'start_date', - 'end_date', - 'focal_species', - 'ancillary_species', - 'survey_purpose', - 'biologist_first_name', - 'biologist_last_name', - 'survey_area_name', - 'survey_data_proprietary' - ], - properties: { - survey_name: { - type: 'string' - }, - start_date: { - type: 'string', - description: 'ISO 8601 date string' - }, - end_date: { - type: 'string', - description: 'ISO 8601 date string' - }, - focal_species: { - type: 'array', - items: { - type: 'number' - }, - description: 'Selected focal species ids' - }, - ancillary_species: { - type: 'array', - items: { - type: 'number' - }, - description: 'Selected ancillary species ids' - }, - survey_purpose: { - type: 'string' - }, - biologist_first_name: { - type: 'string' - }, - biologist_last_name: { - type: 'string' - }, - survey_area_name: { - type: 'string' - }, - survey_data_proprietary: { - type: 'string' - }, - proprietary_data_category: { - type: 'number' - }, - proprietor_name: { - type: 'string' - }, - category_rationale: { - type: 'string' - }, - first_nations_id: { - type: 'number' - }, - data_sharing_agreement_required: { - type: 'string' - } - } -}; - -/** - * Basic response object for a survey. - */ -export const surveyIdResponseObject = { - title: 'Survey Response Object', - type: 'object', - required: ['id'], - properties: { - id: { - type: 'number' - } - } -}; - -/** - * Response object for survey view GET request - */ -export const surveyViewGetResponseObject = { - title: 'Survey get response object, for view purposes', - type: 'object', - properties: {} -}; - -/** - * Response object for survey update GET request - */ -export const surveyUpdateGetResponseObject = { - title: 'Survey get response object, for update purposes', - type: 'object', - properties: {} -}; - -/** - * Request object for survey update PUT request - */ -export const surveyUpdatePutRequestObject = { - title: 'Survey Put Object', - type: 'object', - properties: { - survey_name: { type: 'string' }, - survey_purpose: { type: 'string' }, - focal_species: { - type: 'array', - items: { - type: 'number' - }, - description: 'Selected focal species ids' - }, - ancillary_species: { - type: 'array', - items: { - type: 'number' - }, - description: 'Selected ancillary species ids' - }, - start_date: { type: 'string' }, - end_date: { type: 'string' }, - biologist_first_name: { type: 'string' }, - biologist_last_name: { type: 'string' }, - survey_area_name: { type: 'string' }, - revision_count: { type: 'number' } - } -}; diff --git a/api/src/paths/codes.ts b/api/src/paths/codes.ts index 3923b64876..c6ceb9ac61 100644 --- a/api/src/paths/codes.ts +++ b/api/src/paths/codes.ts @@ -270,7 +270,49 @@ GET.apiDoc = { } } }, - common_survey_methodologies: { + field_methods: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'number' + }, + name: { + type: 'string' + } + } + } + }, + ecological_seasons: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'number' + }, + name: { + type: 'string' + } + } + } + }, + intended_outcomes: { + type: 'array', + items: { + type: 'object', + properties: { + id: { + type: 'number' + }, + name: { + type: 'string' + } + } + } + }, + vantage_codes: { type: 'array', items: { type: 'object', diff --git a/api/src/paths/project/{projectId}/survey/create.ts b/api/src/paths/project/{projectId}/survey/create.ts index a3d45b691d..ac3e546f58 100644 --- a/api/src/paths/project/{projectId}/survey/create.ts +++ b/api/src/paths/project/{projectId}/survey/create.ts @@ -4,7 +4,6 @@ import { PROJECT_ROLE } from '../../../../constants/roles'; import { getDBConnection, IDBConnection } from '../../../../database/db'; import { HTTP400 } from '../../../../errors/custom-error'; import { PostSurveyObject, PostSurveyProprietorData } from '../../../../models/survey-create'; -import { surveyCreatePostRequestObject, surveyIdResponseObject } from '../../../../openapi/schemas/survey'; import { queries } from '../../../../queries/queries'; import { authorizeRequestHandler } from '../../../../request-handlers/security/authorization'; import { getLogger } from '../../../../utils/logger'; @@ -39,7 +38,114 @@ POST.apiDoc = { content: { 'application/json': { schema: { - ...(surveyCreatePostRequestObject as object) + title: 'SurveyProject post request object', + type: 'object', + required: [ + 'survey_name', + 'start_date', + 'end_date', + 'focal_species', + 'ancillary_species', + 'intended_outcome_id', + 'additional_details', + 'field_method_id', + 'vantage_code_ids', + 'ecological_season_id', + 'biologist_first_name', + 'biologist_last_name', + 'survey_area_name', + 'survey_data_proprietary' + ], + properties: { + survey_name: { + type: 'string' + }, + start_date: { + type: 'string', + description: 'ISO 8601 date string' + }, + end_date: { + type: 'string', + description: 'ISO 8601 date string' + }, + focal_species: { + type: 'array', + items: { + type: 'number' + }, + description: 'Selected focal species ids' + }, + ancillary_species: { + type: 'array', + items: { + type: 'number' + }, + description: 'Selected ancillary species ids' + }, + intended_outcome_id: { + type: 'number' + }, + additional_details: { + type: 'string' + }, + field_method_id: { + type: 'number' + }, + vantage_code_ids: { + type: 'array', + items: { + type: 'number' + } + }, + ecological_season_id: { + type: 'number' + }, + biologist_first_name: { + type: 'string' + }, + biologist_last_name: { + type: 'string' + }, + survey_area_name: { + type: 'string' + }, + survey_data_proprietary: { + type: 'string' + }, + proprietary_data_category: { + type: 'number' + }, + proprietor_name: { + type: 'string' + }, + category_rationale: { + type: 'string' + }, + first_nations_id: { + type: 'number' + }, + data_sharing_agreement_required: { + type: 'string' + }, + foippa_requirements_accepted: { + type: 'boolean' + }, + sedis_procedures_accepted: { + type: 'boolean' + }, + funding_sources: { + type: 'array', + items: { + type: 'number' + } + }, + permit_number: { + type: 'string' + }, + permit_type: { + type: 'string' + } + } } } } @@ -50,7 +156,14 @@ POST.apiDoc = { content: { 'application/json': { schema: { - ...(surveyIdResponseObject as object) + title: 'Survey Response Object', + type: 'object', + required: ['id'], + properties: { + id: { + type: 'number' + } + } } } } @@ -169,6 +282,15 @@ export function createSurvey(): RequestHandler { sanitizedPostSurveyData.survey_proprietor && promises.push(insertSurveyProprietor(sanitizedPostSurveyData.survey_proprietor, surveyId, connection)); + //Handle vantage codes associated to this survey + promises.push( + Promise.all( + sanitizedPostSurveyData.vantage_code_ids.map((vantageCode: number) => + insertVantageCodes(vantageCode, surveyId, connection) + ) + ) + ); + await Promise.all(promises); await connection.commit(); @@ -229,6 +351,27 @@ export const insertAncillarySpecies = async ( return result.id; }; +export const insertVantageCodes = async ( + vantage_code_id: number, + survey_id: number, + connection: IDBConnection +): Promise => { + const sqlStatement = queries.survey.postVantageCodesSQL(vantage_code_id, survey_id); + + if (!sqlStatement) { + throw new HTTP400('Failed to build SQL insert statement'); + } + + const response = await connection.query(sqlStatement.text, sqlStatement.values); + const result = (response && response.rows && response.rows[0]) || null; + + if (!result || !result.id) { + throw new HTTP400('Failed to insert ancillary species data'); + } + + return result.id; +}; + export const insertSurveyProprietor = async ( survey_proprietor: PostSurveyProprietorData, survey_id: number, diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/publish.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/publish.ts index e76c47be3a..46c860c35c 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/publish.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/publish.ts @@ -3,7 +3,6 @@ import { Operation } from 'express-openapi'; import { PROJECT_ROLE } from '../../../../../constants/roles'; import { getDBConnection, IDBConnection } from '../../../../../database/db'; import { HTTP400, HTTP500 } from '../../../../../errors/custom-error'; -import { surveyIdResponseObject } from '../../../../../openapi/schemas/survey'; import { queries } from '../../../../../queries/queries'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; import { getLogger } from '../../../../../utils/logger'; @@ -69,7 +68,14 @@ PUT.apiDoc = { 'application/json': { schema: { // TODO is there any return value? or is it just an HTTP status with no content? - ...(surveyIdResponseObject as object) + title: 'Survey Response Object', + type: 'object', + required: ['id'], + properties: { + id: { + type: 'number' + } + } } } } diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/update.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/update.test.ts index 72bb66a8a6..f96f098fc9 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/update.test.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/update.test.ts @@ -92,10 +92,9 @@ describe('getSurveyForUpdate', () => { const survey_details = { id: 1, name: 'name', - objectives: 'objective', focal_species: [1], ancillary_species: [3], - common_survey_methodology_id: 1, + additional_details: 'details', start_date: '2020-04-04', end_date: '2020-05-05', lead_first_name: 'first', @@ -124,6 +123,7 @@ describe('getSurveyForUpdate', () => { }); sinon.stub(survey_queries, 'getSurveyDetailsForUpdateSQL').returns(SQL`some query`); + sinon.stub(survey_queries, 'getSurveyPurposeAndMethodologyForUpdateSQL').returns(SQL`some query`); sinon.stub(survey_queries, 'getSurveyProprietorForUpdateSQL').returns(SQL`some query`); const requestHandler = update.getSurveyForUpdate(); @@ -134,10 +134,8 @@ describe('getSurveyForUpdate', () => { survey_details: { id: 1, survey_name: survey_details.name, - survey_purpose: survey_details.objectives, focal_species: survey_details.focal_species, ancillary_species: survey_details.ancillary_species, - common_survey_methodology_id: survey_details.common_survey_methodology_id, start_date: survey_details.start_date, end_date: survey_details.end_date, biologist_first_name: survey_details.lead_first_name, @@ -151,6 +149,7 @@ describe('getSurveyForUpdate', () => { publish_date: '', funding_sources: survey_details.pfs_id }, + survey_purpose_and_methodology: null, survey_proprietor: null }); }); @@ -203,6 +202,7 @@ describe('getSurveyForUpdate', () => { expect(mockRes.sendValue).to.eql({ survey_details: null, + survey_purpose_and_methodology: null, survey_proprietor: { category_rationale: survey_proprietor.category_rationale, data_sharing_agreement_required: survey_proprietor.data_sharing_agreement_required, @@ -218,7 +218,7 @@ describe('getSurveyForUpdate', () => { }); }); - it('should return survey details and proprietor info when no entity is specified, on success', async () => { + it('should return survey details, survey purpose and methodology, as well as proprietor info when no entity is specified, on success', async () => { const dbConnectionObj = getMockDBConnection(); const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); @@ -231,10 +231,8 @@ describe('getSurveyForUpdate', () => { const survey_details = { id: 1, name: 'name', - objectives: 'objective', focal_species: [1], ancillary_species: [3], - common_survey_methodology_id: 1, start_date: '2020-04-04', end_date: '2020-05-05', lead_first_name: 'first', @@ -246,6 +244,16 @@ describe('getSurveyForUpdate', () => { pfs_id: [10] }; + const survey_purpose_and_methodology = { + id: 1, + field_method_id: 1, + additional_details: 'details', + ecological_season_id: 1, + intended_outcome_id: 8, + revision_count: 0, + vantage_id: 2 + }; + const survey_proprietor = { category_rationale: '', data_sharing_agreement_required: 'false', @@ -267,6 +275,10 @@ describe('getSurveyForUpdate', () => { rows: [survey_details] }) .onSecondCall() + .resolves({ + rows: [survey_purpose_and_methodology] + }) + .onThirdCall() .resolves({ rows: [survey_proprietor] }); @@ -280,6 +292,7 @@ describe('getSurveyForUpdate', () => { }); sinon.stub(survey_queries, 'getSurveyDetailsForUpdateSQL').returns(SQL`some query`); + sinon.stub(survey_queries, 'getSurveyPurposeAndMethodologyForUpdateSQL').returns(SQL`some query`); sinon.stub(survey_queries, 'getSurveyProprietorForUpdateSQL').returns(SQL`some query`); const requestHandler = update.getSurveyForUpdate(); @@ -290,10 +303,8 @@ describe('getSurveyForUpdate', () => { survey_details: { id: 1, survey_name: survey_details.name, - survey_purpose: survey_details.objectives, focal_species: survey_details.focal_species, ancillary_species: survey_details.ancillary_species, - common_survey_methodology_id: survey_details.common_survey_methodology_id, start_date: survey_details.start_date, end_date: survey_details.end_date, biologist_first_name: survey_details.lead_first_name, @@ -307,6 +318,15 @@ describe('getSurveyForUpdate', () => { publish_date: '', funding_sources: survey_details.pfs_id }, + survey_purpose_and_methodology: { + id: 1, + intended_outcome_id: 8, + field_method_id: 1, + additional_details: 'details', + ecological_season_id: 1, + vantage_code_ids: [2], + revision_count: 0 + }, survey_proprietor: { category_rationale: survey_proprietor.category_rationale, data_sharing_agreement_required: survey_proprietor.data_sharing_agreement_required, @@ -340,7 +360,6 @@ describe('updateSurvey', () => { mockReq.body = { survey_details: { survey_name: 'name', - survey_purpose: 'purpose', species: 'species', start_date: '2020-03-03', end_date: '2020-04-04', @@ -382,7 +401,6 @@ describe('updateSurvey', () => { mockReq.body = { survey_details: { survey_name: 'name', - survey_purpose: 'purpose', species: 'species', start_date: '2020-03-03', end_date: '2020-04-04', @@ -453,7 +471,6 @@ describe('updateSurvey', () => { mockReq.body = { survey_details: { survey_name: 'name', - survey_purpose: 'purpose', species: 'species', start_date: '2020-03-03', end_date: '2020-04-04', @@ -495,7 +512,6 @@ describe('updateSurvey', () => { mockReq.body = { survey_details: { survey_name: 'name', - survey_purpose: 'purpose', species: 'species', start_date: '2020-03-03', end_date: '2020-04-04', @@ -539,7 +555,7 @@ describe('updateSurvey', () => { mockReq.body = { survey_details: { survey_name: 'name', - survey_purpose: 'purpose', + species: 'species', start_date: '2020-03-03', end_date: '2020-04-04', @@ -588,7 +604,6 @@ describe('updateSurvey', () => { mockReq.body = { survey_details: { survey_name: 'name', - survey_purpose: 'purpose', species: 'species', start_date: '2020-03-03', end_date: '2020-04-04', @@ -670,7 +685,6 @@ describe('updateSurvey', () => { mockReq.body = { survey_details: { survey_name: 'name', - survey_purpose: 'purpose', species: 'species', start_date: '2020-03-03', end_date: '2020-04-04', @@ -719,7 +733,6 @@ describe('updateSurvey', () => { mockReq.body = { survey_details: { survey_name: 'name', - survey_purpose: 'purpose', species: 'species', start_date: '2020-03-03', end_date: '2020-04-04', @@ -775,6 +788,7 @@ describe('updateSurveyProprietorData', () => { focal_species: [1], ancillary_species: [2] }, + survey_purpose_and_methodology: {}, survey_proprietor: { id: 0, survey_data_proprietary: 'true' @@ -873,6 +887,7 @@ describe('updateSurveyDetailsData', () => { focal_species: [1], ancillary_species: [2] }, + survey_purpose_and_methodology: null, survey_proprietor: null }; diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/update.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/update.ts index 18960c9364..fce60ebdce 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/update.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/update.ts @@ -2,26 +2,29 @@ import { RequestHandler } from 'express'; import { Operation } from 'express-openapi'; import { PROJECT_ROLE } from '../../../../../constants/roles'; import { getDBConnection, IDBConnection } from '../../../../../database/db'; -import { HTTP400, HTTP409 } from '../../../../../errors/custom-error'; +import { HTTP400, HTTP409, HTTP500 } from '../../../../../errors/custom-error'; import { PostSurveyProprietorData } from '../../../../../models/survey-create'; import { GetUpdateSurveyDetailsData, PutSurveyDetailsData, - PutSurveyProprietorData + PutSurveyProprietorData, + PutSurveyPurposeAndMethodologyData } from '../../../../../models/survey-update'; -import { GetSurveyProprietorData } from '../../../../../models/survey-view-update'; -import { - surveyIdResponseObject, - surveyUpdateGetResponseObject, - surveyUpdatePutRequestObject -} from '../../../../../openapi/schemas/survey'; +import { GetSurveyProprietorData, GetSurveyPurposeAndMethodologyData } from '../../../../../models/survey-view-update'; +import { geoJsonFeature } from '../../../../../openapi/schemas/geoJson'; import { queries } from '../../../../../queries/queries'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; import { getLogger } from '../../../../../utils/logger'; -import { insertAncillarySpecies, insertFocalSpecies, insertSurveyFundingSource, insertSurveyPermit } from '../create'; - +import { + insertAncillarySpecies, + insertFocalSpecies, + insertSurveyFundingSource, + insertSurveyPermit, + insertVantageCodes +} from '../create'; export interface IUpdateSurvey { survey_details: object | null; + survey_purpose_and_methodology: object | null; survey_proprietor: object | null; } @@ -59,6 +62,7 @@ export const PUT: Operation = [ export enum GET_SURVEY_ENTITIES { survey_details = 'survey_details', + survey_purpose_and_methodology = 'survey_purpose_and_methodology', survey_proprietor = 'survey_proprietor' } @@ -107,7 +111,185 @@ GET.apiDoc = { content: { 'application/json': { schema: { - ...(surveyUpdateGetResponseObject as object) + title: 'Survey get response object, for update purposes', + type: 'object', + required: ['survey_details', 'survey_purpose_and_methodology', 'survey_proprietor'], + properties: { + survey_details: { + description: 'Survey Details', + type: 'object', + required: [ + 'id', + 'focal_species', + 'ancillary_species', + 'biologist_first_name', + 'biologist_last_name', + 'completion_status', + 'start_date', + 'end_date', + 'funding_sources', + 'geometry', + 'permit_number', + 'permit_type', + 'publish_date', + 'revision_count', + 'survey_area_name', + 'survey_name' + ], + properties: { + id: { + description: 'Survey id', + type: 'number' + }, + ancillary_species: { + type: 'array', + items: { + type: 'string' + } + }, + focal_species: { + type: 'array', + items: { + type: 'string' + } + }, + biologist_first_name: { + type: 'string' + }, + biologist_last_name: { + type: 'string' + }, + completion_status: { + type: 'string' + }, + start_date: { + type: 'string', + format: 'date', + description: 'ISO 8601 date string for the funding end_date' + }, + end_date: { + type: 'string', + format: 'date', + description: 'ISO 8601 date string for the funding end_date' + }, + funding_sources: { + type: 'array', + items: { + title: 'survey funding agency', + type: 'object', + required: ['agency_name', 'funding_amount', 'funding_start_date', 'funding_end_date'], + properties: { + pfs_id: { + type: 'number' + }, + agency_name: { + type: 'string' + }, + funding_amount: { + type: 'number' + }, + funding_start_date: { + type: 'string', + description: 'ISO 8601 date string' + }, + funding_end_date: { + type: 'string', + description: 'ISO 8601 date string' + } + } + } + }, + geometry: { + type: 'array', + items: { + ...(geoJsonFeature as object) + } + }, + permit_number: { + type: 'string' + }, + permit_type: { + type: 'string' + }, + publish_date: { + type: 'string' + }, + revision_count: { + type: 'number' + }, + survey_area_name: { + type: 'string' + }, + survey_name: { + type: 'string' + } + } + }, + survey_purpose_and_methodology: { + description: 'Survey Details', + type: 'object', + properties: { + id: { + type: 'number' + }, + field_method_id: { + type: 'number' + }, + additional_details: { + type: 'string' + }, + intended_outcome_id: { + type: 'number' + }, + ecological_season_id: { + type: 'number' + }, + revision_count: { + type: 'number' + }, + vantage_code_ids: { + type: 'array', + items: { + type: 'number' + } + } + } + }, + survey_proprietor: { + description: 'Survey Details', + type: 'object', + //Note: do not make any of these fields required as the object can be null + properties: { + survey_data_proprietary: { + type: 'string' + }, + id: { + type: 'number' + }, + category_rationale: { + type: 'string' + }, + data_sharing_agreement_required: { + type: 'string' + }, + first_nations_id: { + type: 'number' + }, + first_nations_name: { + type: 'string' + }, + proprietary_data_category: { + type: 'number' + }, + proprietary_data_category_name: { + type: 'string' + }, + revision_count: { + type: 'number' + } + } + } + } } } } @@ -161,7 +343,164 @@ PUT.apiDoc = { content: { 'application/json': { schema: { - ...(surveyUpdatePutRequestObject as object) + title: 'Survey Put Object', + type: 'object', + properties: { + survey_details: { + description: 'Survey Details', + type: 'object', + required: [ + 'id', + 'focal_species', + 'ancillary_species', + 'biologist_first_name', + 'biologist_last_name', + 'completion_status', + 'start_date', + 'end_date', + 'funding_sources', + 'geometry', + 'permit_number', + 'permit_type', + 'publish_date', + 'revision_count', + 'survey_area_name', + 'survey_name' + ], + properties: { + id: { + description: 'Survey id', + type: 'number' + }, + ancillary_species: { + type: 'array', + items: { + type: 'number' + } + }, + focal_species: { + type: 'array', + items: { + type: 'number' + } + }, + biologist_first_name: { + type: 'string' + }, + biologist_last_name: { + type: 'string' + }, + completion_status: { + type: 'string' + }, + start_date: { + type: 'string', + format: 'date', + description: 'ISO 8601 date string for the funding end_date' + }, + end_date: { + type: 'string', + format: 'date', + description: 'ISO 8601 date string for the funding end_date' + }, + funding_sources: { + type: 'array', + items: { + title: 'survey funding agency', + type: 'number' + } + }, + geometry: { + type: 'array', + items: { + ...(geoJsonFeature as object) + } + }, + permit_number: { + type: 'string' + }, + permit_type: { + type: 'string' + }, + publish_date: { + type: 'string' + }, + revision_count: { + type: 'number' + }, + survey_area_name: { + type: 'string' + }, + survey_name: { + type: 'string' + } + } + }, + survey_purpose_and_methodology: { + description: 'Survey Details', + type: 'object', + properties: { + id: { + type: 'number' + }, + field_method_id: { + type: 'number' + }, + additional_details: { + type: 'string' + }, + intended_outcome_id: { + type: 'number' + }, + ecological_season_id: { + type: 'number' + }, + revision_count: { + type: 'number' + }, + vantage_code_ids: { + type: 'array', + items: { + type: 'number' + } + } + } + }, + survey_proprietor: { + description: 'Survey Details', + type: 'object', + //Note: do not make any of these fields required as the object can be null + properties: { + survey_data_proprietary: { + type: 'string' + }, + id: { + type: 'number' + }, + category_rationale: { + type: 'string' + }, + data_sharing_agreement_required: { + type: 'string' + }, + first_nations_id: { + type: 'number' + }, + first_nations_name: { + type: 'string' + }, + proprietary_data_category: { + type: 'number' + }, + proprietary_data_category_name: { + type: 'string' + }, + revision_count: { + type: 'number' + } + } + } + } } } } @@ -172,7 +511,14 @@ PUT.apiDoc = { content: { 'application/json': { schema: { - ...(surveyIdResponseObject as object) + title: 'Survey Response Object', + type: 'object', + required: ['id'], + properties: { + id: { + type: 'number' + } + } } } } @@ -197,6 +543,7 @@ PUT.apiDoc = { export interface IGetSurveyForUpdate { survey_details: GetUpdateSurveyDetailsData | null; + survey_purpose_and_methodology: GetSurveyPurposeAndMethodologyData | null; survey_proprietor: GetSurveyProprietorData | null; } @@ -222,6 +569,7 @@ export function getSurveyForUpdate(): RequestHandler { const results: IGetSurveyForUpdate = { survey_details: null, + survey_purpose_and_methodology: null, survey_proprietor: null }; @@ -235,6 +583,14 @@ export function getSurveyForUpdate(): RequestHandler { ); } + if (entities.includes(GET_SURVEY_ENTITIES.survey_purpose_and_methodology)) { + promises.push( + getSurveyPurposeAndMethodologyData(surveyId, connection).then((value) => { + results.survey_purpose_and_methodology = value; + }) + ); + } + if (entities.includes(GET_SURVEY_ENTITIES.survey_proprietor)) { promises.push( getSurveyProprietorData(surveyId, connection).then((value) => { @@ -278,6 +634,21 @@ export const getSurveyDetailsData = async ( return result; }; +export const getSurveyPurposeAndMethodologyData = async ( + surveyId: number, + connection: IDBConnection +): Promise => { + const sqlStatement = queries.survey.getSurveyPurposeAndMethodologyForUpdateSQL(surveyId); + + if (!sqlStatement) { + throw new HTTP400('Failed to build survey proprietor SQL get statement'); + } + + const response = await connection.query(sqlStatement.text, sqlStatement.values); + + return (response && response.rows && new GetSurveyPurposeAndMethodologyData(response.rows)[0]) || null; +}; + export const getSurveyProprietorData = async ( surveyId: number, connection: IDBConnection @@ -328,6 +699,10 @@ export function updateSurvey(): RequestHandler { promises.push(updateSurveyDetailsData(projectId, surveyId, entities, connection)); } + if (entities.survey_purpose_and_methodology) { + promises.push(updateSurveyPurposeAndMethodologyData(surveyId, entities, connection)); + } + if (entities.survey_proprietor) { promises.push(updateSurveyProprietorData(surveyId, entities, connection)); } @@ -515,3 +890,68 @@ export const unassociatePermitFromSurvey = async (survey_id: number, connection: throw new HTTP400('Failed to update survey permit number data'); } }; + +export const updateSurveyPurposeAndMethodologyData = async ( + surveyId: number, + entities: IUpdateSurvey, + connection: IDBConnection +): Promise => { + const putPurposeAndMethodologyData = + (entities?.survey_purpose_and_methodology && + new PutSurveyPurposeAndMethodologyData(entities.survey_purpose_and_methodology)) || + null; + + const revision_count = putPurposeAndMethodologyData?.revision_count ?? null; + + if (!revision_count && revision_count !== 0) { + throw new HTTP400('Failed to parse request body'); + } + + const updateSurveySQLStatement = queries.survey.putSurveyPurposeAndMethodologySQL( + surveyId, + putPurposeAndMethodologyData, + revision_count + ); + + if (!updateSurveySQLStatement) { + throw new HTTP400('Failed to build SQL update statement'); + } + + const result = await connection.query(updateSurveySQLStatement.text, updateSurveySQLStatement.values); + if (!result || !result.rowCount) { + throw new HTTP409('Failed to update stale survey data'); + } + + const promises: Promise[] = []; + + promises.push(deleteSurveyVantageCodes(surveyId, connection)); + //Handle vantage codes associated to this survey + + if (putPurposeAndMethodologyData?.vantage_code_ids) { + promises.push( + Promise.all( + putPurposeAndMethodologyData.vantage_code_ids.map((vantageCode: number) => + insertVantageCodes(vantageCode, surveyId, connection) + ) + ) + ); + } + + await Promise.all(promises); + + await connection.commit(); +}; + +export const deleteSurveyVantageCodes = async (survey_id: number, connection: IDBConnection): Promise => { + const sqlStatement = queries.survey.deleteSurveyVantageCodesSQL(survey_id); + + if (!sqlStatement) { + throw new HTTP400('Failed to build delete survey vantage codes SQL statement'); + } + + const response = await connection.query(sqlStatement.text, sqlStatement.values); + + if (!response) { + throw new HTTP500('Failed to delete survey vantage codes'); + } +}; diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/view.test.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/view.test.ts index ffd1485f26..1a0c19d1ed 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/view.test.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/view.test.ts @@ -31,6 +31,9 @@ describe('getSurveyForView', () => { survey_details: { id: null }, + survey_purpose_and_methodology: { + id: null + }, survey_proprietor: { id: null } @@ -58,6 +61,7 @@ describe('getSurveyForView', () => { }); sinon.stub(survey_queries, 'getSurveyBasicDataForViewSQL').returns(null); + sinon.stub(survey_queries, 'getSurveyPurposeAndMethodologyForUpdateSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyFundingSourcesDataForViewSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveySpeciesDataForViewSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyProprietorForUpdateSQL').returns(SQL`valid sql`); @@ -89,6 +93,10 @@ describe('getSurveyForView', () => { rows: [{}] }) .onCall(3) + .resolves({ + rows: [{}] + }) + .onCall(4) .resolves({ rows: [{}] }); @@ -102,6 +110,7 @@ describe('getSurveyForView', () => { }); sinon.stub(survey_queries, 'getSurveyBasicDataForViewSQL').returns(SQL`valid sql`); + sinon.stub(survey_queries, 'getSurveyPurposeAndMethodologyForUpdateSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyFundingSourcesDataForViewSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveySpeciesDataForViewSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyProprietorForUpdateSQL').returns(SQL`valid sql`); @@ -128,6 +137,7 @@ describe('getSurveyForView', () => { }); sinon.stub(survey_queries, 'getSurveyBasicDataForViewSQL').returns(SQL`valid sql`); + sinon.stub(survey_queries, 'getSurveyPurposeAndMethodologyForUpdateSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyFundingSourcesDataForViewSQL').returns(null); sinon.stub(survey_queries, 'getSurveySpeciesDataForViewSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyProprietorForUpdateSQL').returns(SQL`valid sql`); @@ -154,6 +164,7 @@ describe('getSurveyForView', () => { }); sinon.stub(survey_queries, 'getSurveyBasicDataForViewSQL').returns(SQL`valid sql`); + sinon.stub(survey_queries, 'getSurveyPurposeAndMethodologyForUpdateSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyFundingSourcesDataForViewSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveySpeciesDataForViewSQL').returns(null); sinon.stub(survey_queries, 'getSurveyProprietorForUpdateSQL').returns(SQL`valid sql`); @@ -182,11 +193,11 @@ describe('getSurveyForView', () => { }) .onCall(2) .resolves({ - rows: [] // empty response + rows: [{}] }) .onCall(3) .resolves({ - rows: [{}] + rows: [] // empty response }); sinon.stub(db, 'getDBConnection').returns({ @@ -198,6 +209,7 @@ describe('getSurveyForView', () => { }); sinon.stub(survey_queries, 'getSurveyBasicDataForViewSQL').returns(SQL`valid sql`); + sinon.stub(survey_queries, 'getSurveyPurposeAndMethodologyForUpdateSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyFundingSourcesDataForViewSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveySpeciesDataForViewSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyProprietorForUpdateSQL').returns(SQL`valid sql`); @@ -224,6 +236,7 @@ describe('getSurveyForView', () => { }); sinon.stub(survey_queries, 'getSurveyBasicDataForViewSQL').returns(SQL`valid sql`); + sinon.stub(survey_queries, 'getSurveyPurposeAndMethodologyForUpdateSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyFundingSourcesDataForViewSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveySpeciesDataForViewSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyProprietorForUpdateSQL').returns(null); @@ -242,7 +255,6 @@ describe('getSurveyForView', () => { const survey_basic_data = { id: 2, name: 'name', - objectives: 'objective', start_date: '2020/04/04', end_date: '2020/05/05', lead_first_name: 'first', @@ -254,9 +266,17 @@ describe('getSurveyForView', () => { publish_timestamp: null, number: '123', type: 'scientific', - common_survey_methodology: 'method', occurrence_submission_id: 3 }; + const survey_purpose_and_methodology = { + id: 17, + field_method_id: 1, + additional_details: 'details', + ecological_season_id: 1, + intended_outcome_id: 8, + revision_count: 0, + vantage_id: 2 + }; const survey_funding_source_data = { pfs_id: 1, @@ -293,13 +313,17 @@ describe('getSurveyForView', () => { }) .onCall(1) .resolves({ - rows: [survey_funding_source_data] + rows: [survey_purpose_and_methodology] }) .onCall(2) .resolves({ - rows: [survey_species_data] + rows: [survey_funding_source_data] }) .onCall(3) + .resolves({ + rows: [survey_species_data] + }) + .onCall(4) .resolves({ rows: [survey_proprietor_data] }); @@ -315,6 +339,7 @@ describe('getSurveyForView', () => { sinon.stub(survey_queries, 'getSurveyBasicDataForViewSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyFundingSourcesDataForViewSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveySpeciesDataForViewSQL').returns(SQL`valid sql`); + sinon.stub(survey_queries, 'getSurveyPurposeAndMethodologyForUpdateSQL').returns(SQL`valid sql`); sinon.stub(survey_queries, 'getSurveyProprietorForUpdateSQL').returns(SQL`valid sql`); const result = view.getSurveyForView(); @@ -326,10 +351,8 @@ describe('getSurveyForView', () => { id: survey_basic_data.id, occurrence_submission_id: survey_basic_data.occurrence_submission_id, survey_name: survey_basic_data.name, - survey_purpose: survey_basic_data.objectives, focal_species: survey_species_data.focal_species, ancillary_species: survey_species_data.ancillary_species, - common_survey_methodology: survey_basic_data.common_survey_methodology, start_date: survey_basic_data.start_date, end_date: survey_basic_data.end_date, biologist_first_name: survey_basic_data.lead_first_name, @@ -363,5 +386,15 @@ describe('getSurveyForView', () => { data_sharing_agreement_required: 'true', revision_count: survey_proprietor_data.revision_count }); + + expect(actualResult.survey_purpose_and_methodology).to.eql({ + id: survey_purpose_and_methodology.id, + additional_details: survey_purpose_and_methodology.additional_details, + ecological_season_id: survey_purpose_and_methodology.ecological_season_id, + field_method_id: survey_purpose_and_methodology.field_method_id, + intended_outcome_id: survey_purpose_and_methodology.intended_outcome_id, + revision_count: survey_purpose_and_methodology.revision_count, + vantage_code_ids: [survey_purpose_and_methodology.vantage_id] + }); }); }); diff --git a/api/src/paths/project/{projectId}/survey/{surveyId}/view.ts b/api/src/paths/project/{projectId}/survey/{surveyId}/view.ts index cb0640bd85..be8e86e731 100644 --- a/api/src/paths/project/{projectId}/survey/{surveyId}/view.ts +++ b/api/src/paths/project/{projectId}/survey/{surveyId}/view.ts @@ -4,8 +4,8 @@ import { PROJECT_ROLE } from '../../../../../constants/roles'; import { getDBConnection, IDBConnection } from '../../../../../database/db'; import { HTTP400 } from '../../../../../errors/custom-error'; import { GetViewSurveyDetailsData } from '../../../../../models/survey-view'; -import { GetSurveyProprietorData } from '../../../../../models/survey-view-update'; -import { surveyViewGetResponseObject } from '../../../../../openapi/schemas/survey'; +import { GetSurveyProprietorData, GetSurveyPurposeAndMethodologyData } from '../../../../../models/survey-view-update'; +import { geoJsonFeature } from '../../../../../openapi/schemas/geoJson'; import { queries } from '../../../../../queries/queries'; import { authorizeRequestHandler } from '../../../../../request-handlers/security/authorization'; import { getLogger } from '../../../../../utils/logger'; @@ -55,11 +55,204 @@ GET.apiDoc = { ], responses: { 200: { - description: 'Survey with matching surveyId.', + description: 'Survey with matching surveyId and projectId.', content: { 'application/json': { schema: { - ...(surveyViewGetResponseObject as object) + title: 'Survey get response object, for view purposes', + type: 'object', + required: ['survey_details', 'survey_purpose_and_methodology', 'survey_proprietor'], + properties: { + survey_details: { + description: 'Survey Details', + type: 'object', + required: [ + 'id', + 'occurrence_submission_id', + 'focal_species', + 'ancillary_species', + 'biologist_first_name', + 'biologist_last_name', + 'completion_status', + 'start_date', + 'end_date', + 'funding_sources', + 'geometry', + 'permit_number', + 'permit_type', + 'publish_date', + 'revision_count', + 'survey_area_name', + 'survey_name' + ], + properties: { + id: { + description: 'Survey id', + type: 'number' + }, + ancillary_species: { + type: 'array', + items: { + type: 'string' + } + }, + focal_species: { + type: 'array', + items: { + type: 'string' + } + }, + biologist_first_name: { + type: 'string' + }, + biologist_last_name: { + type: 'string' + }, + completion_status: { + type: 'string' + }, + start_date: { + type: 'string', + format: 'date', + description: 'ISO 8601 date string for the funding end_date' + }, + end_date: { + type: 'string', + format: 'date', + description: 'ISO 8601 date string for the funding end_date' + }, + funding_sources: { + type: 'array', + items: { + title: 'survey funding agency', + type: 'object', + required: ['agency_name', 'funding_amount', 'funding_start_date', 'funding_end_date'], + properties: { + pfs_id: { + type: 'number' + }, + agency_name: { + type: 'string' + }, + funding_amount: { + type: 'number' + }, + funding_start_date: { + type: 'string', + description: 'ISO 8601 date string' + }, + funding_end_date: { + type: 'string', + description: 'ISO 8601 date string' + } + } + } + }, + geometry: { + type: 'array', + items: { + ...(geoJsonFeature as object) + } + }, + occurrence_submission_id: { + description: 'A survey occurrence submission ID', + type: 'number', + example: 1 + }, + permit_number: { + type: 'string' + }, + permit_type: { + type: 'string' + }, + publish_date: { + type: 'string' + }, + revision_count: { + type: 'number' + }, + survey_area_name: { + type: 'string' + }, + survey_name: { + type: 'string' + } + } + }, + survey_purpose_and_methodology: { + description: 'Survey Details', + type: 'object', + required: [ + 'id', + 'field_method_id', + 'additional_details', + 'intended_outcome_id', + 'ecological_season_id', + 'revision_count', + 'vantage_code_ids' + ], + properties: { + id: { + type: 'number' + }, + field_method_id: { + type: 'number' + }, + additional_details: { + type: 'string' + }, + intended_outcome_id: { + type: 'number' + }, + ecological_season_id: { + type: 'number' + }, + revision_count: { + type: 'number' + }, + vantage_code_ids: { + type: 'array', + items: { + type: 'number' + } + } + } + }, + survey_proprietor: { + description: 'Survey Details', + type: 'object', + //Note: do not make any of these fields required as the object can be null + properties: { + survey_data_proprietary: { + type: 'string' + }, + id: { + type: 'number' + }, + category_rationale: { + type: 'string' + }, + data_sharing_agreement_required: { + type: 'string' + }, + first_nations_id: { + type: 'number' + }, + first_nations_name: { + type: 'string' + }, + proprietary_data_category: { + type: 'number' + }, + proprietary_data_category_name: { + type: 'string' + }, + revision_count: { + type: 'number' + } + } + } + } } } } @@ -100,8 +293,15 @@ export function getSurveyForView(): RequestHandler { try { await connection.open(); - const [surveyBasicData, surveyFundingSourcesData, SurveySpeciesData, surveyProprietorData] = await Promise.all([ + const [ + surveyBasicData, + surveyPurposeAndMethodology, + surveyFundingSourcesData, + SurveySpeciesData, + surveyProprietorData + ] = await Promise.all([ getSurveyBasicDataForView(surveyId, connection), + getSurveyPurposeAndMethodologyDataForView(surveyId, connection), getSurveyFundingSourcesDataForView(surveyId, connection), getSurveySpeciesDataForView(surveyId, connection), getSurveyProprietorDataForView(surveyId, connection) @@ -115,11 +315,15 @@ export function getSurveyForView(): RequestHandler { ...SurveySpeciesData }); + const getSurveyPurposeAndMethodology = + (surveyPurposeAndMethodology && new GetSurveyPurposeAndMethodologyData(surveyPurposeAndMethodology))[0] || null; + const getSurveyProprietorData = (surveyProprietorData && new GetSurveyProprietorData(surveyProprietorData)) || null; const result = { survey_details: getSurveyData, + survey_purpose_and_methodology: getSurveyPurposeAndMethodology, survey_proprietor: getSurveyProprietorData }; @@ -149,6 +353,25 @@ export const getSurveyBasicDataForView = async (surveyId: number, connection: ID return (response && response.rows?.[0]) || null; }; +export const getSurveyPurposeAndMethodologyDataForView = async ( + surveyId: number, + connection: IDBConnection +): Promise => { + const sqlStatement = queries.survey.getSurveyPurposeAndMethodologyForUpdateSQL(surveyId); + + if (!sqlStatement) { + throw new HTTP400('Failed to build SQL get statement'); + } + + const response = await connection.query(sqlStatement.text, sqlStatement.values); + + if (!response || !response?.rows?.[0]) { + throw new HTTP400('Failed to get survey purpose and methodology data'); + } + + return (response && response.rows) || []; +}; + export const getSurveyFundingSourcesDataForView = async ( surveyId: number, connection: IDBConnection diff --git a/api/src/paths/project/{projectId}/surveys.ts b/api/src/paths/project/{projectId}/surveys.ts index e3be057687..9f9ba32830 100644 --- a/api/src/paths/project/{projectId}/surveys.ts +++ b/api/src/paths/project/{projectId}/surveys.ts @@ -6,7 +6,6 @@ import { COMPLETION_STATUS } from '../../../constants/status'; import { getDBConnection } from '../../../database/db'; import { HTTP400 } from '../../../errors/custom-error'; import { GetSurveyListData } from '../../../models/survey-view'; -import { surveyIdResponseObject } from '../../../openapi/schemas/survey'; import { queries } from '../../../queries/queries'; import { authorizeRequestHandler } from '../../../request-handlers/security/authorization'; import { getLogger } from '../../../utils/logger'; @@ -54,7 +53,14 @@ GET.apiDoc = { schema: { type: 'array', items: { - ...(surveyIdResponseObject as object) + title: 'Survey Response Object', + type: 'object', + required: ['id'], + properties: { + id: { + type: 'number' + } + } } } } diff --git a/api/src/paths/xlsx/transform.ts b/api/src/paths/xlsx/transform.ts index d6d86c950f..b69628c8ca 100644 --- a/api/src/paths/xlsx/transform.ts +++ b/api/src/paths/xlsx/transform.ts @@ -138,12 +138,10 @@ export function getTransformationSchema(): RequestHandler { const xlsxCsv = req['xlsx']; const template_id = xlsxCsv.workbook.rawWorkbook.Custprops.sims_template_id; - const species_id = xlsxCsv.workbook.rawWorkbook.Custprops.sims_species_id; - const csm_id = xlsxCsv.workbook.rawWorkbook.Custprops.sims_csm_id; + const field_method_id = xlsxCsv.workbook.rawWorkbook.Custprops.sims_csm_id; const templateMethodologySpeciesRecord = await getTemplateMethodologySpeciesRecord( - Number(species_id), - Number(csm_id), + Number(field_method_id), Number(template_id), connection ); diff --git a/api/src/paths/xlsx/validate.test.ts b/api/src/paths/xlsx/validate.test.ts index 1bee9404e6..89a7412836 100644 --- a/api/src/paths/xlsx/validate.test.ts +++ b/api/src/paths/xlsx/validate.test.ts @@ -135,7 +135,7 @@ describe('getTemplateMethodologySpeciesRecord', () => { sinon.stub(survey_queries, 'getTemplateMethodologySpeciesRecordSQL').returns(null); try { - await validate.getTemplateMethodologySpeciesRecord(1234, 1, 1, { ...dbConnectionObj, systemUserId: () => 20 }); + await validate.getTemplateMethodologySpeciesRecord(1, 1, { ...dbConnectionObj, systemUserId: () => 20 }); expect.fail(); } catch (actualError) { @@ -156,7 +156,7 @@ describe('getTemplateMethodologySpeciesRecord', () => { sinon.stub(survey_queries, 'getTemplateMethodologySpeciesRecordSQL').returns(SQL`something`); try { - await validate.getTemplateMethodologySpeciesRecord(1234, 1, 1, { + await validate.getTemplateMethodologySpeciesRecord(1, 1, { ...dbConnectionObj, systemUserId: () => 20 }); @@ -180,7 +180,7 @@ describe('getTemplateMethodologySpeciesRecord', () => { sinon.stub(survey_queries, 'getTemplateMethodologySpeciesRecordSQL').returns(SQL`something`); - const result = await validate.getTemplateMethodologySpeciesRecord(1234, 1, 1, { + const result = await validate.getTemplateMethodologySpeciesRecord(1, 1, { ...dbConnectionObj, query: mockQuery, systemUserId: () => 20 diff --git a/api/src/paths/xlsx/validate.ts b/api/src/paths/xlsx/validate.ts index 4748d4c2c5..35a0680768 100644 --- a/api/src/paths/xlsx/validate.ts +++ b/api/src/paths/xlsx/validate.ts @@ -108,12 +108,10 @@ export function getValidationSchema(): RequestHandler { const xlsxCsv = req['xlsx']; const template_id = xlsxCsv.workbook.rawWorkbook.Custprops.sims_template_id; - const species_id = xlsxCsv.workbook.rawWorkbook.Custprops.sims_species_id; - const csm_id = xlsxCsv.workbook.rawWorkbook.Custprops.sims_csm_id; + const field_method_id = xlsxCsv.workbook.rawWorkbook.Custprops.sims_csm_id; const templateMethodologySpeciesRecord = await getTemplateMethodologySpeciesRecord( - Number(species_id), - Number(csm_id), + Number(field_method_id), Number(template_id), connection ); @@ -188,17 +186,17 @@ export function validateXLSX(): RequestHandler { /** * Get a template_methodology_species record from the template_methodologies_species table * - * @param {number} surveyId + * @param {number} fieldMethodId + * @param {number} templateId * @param {IDBConnection} connection * @return {*} {Promise} */ export const getTemplateMethodologySpeciesRecord = async ( - speciesId: number, - surveyMethodology: number, + fieldMethodId: number, templateId: number, connection: IDBConnection ): Promise => { - const sqlStatement = queries.survey.getTemplateMethodologySpeciesRecordSQL(speciesId, surveyMethodology, templateId); + const sqlStatement = queries.survey.getTemplateMethodologySpeciesRecordSQL(fieldMethodId, templateId); if (!sqlStatement) { throw new HTTP400('Failed to build SQL get template methodology species record sql statement'); diff --git a/api/src/queries/codes/code-queries.test.ts b/api/src/queries/codes/code-queries.test.ts index 524e7dc7c2..8e6fb1df41 100644 --- a/api/src/queries/codes/code-queries.test.ts +++ b/api/src/queries/codes/code-queries.test.ts @@ -3,7 +3,6 @@ import { describe } from 'mocha'; import { getActivitySQL, getAdministrativeActivityStatusTypeSQL, - getCommonSurveyMethodologiesSQL, getFirstNationsSQL, getFundingSourceSQL, getInvestmentActionCategorySQL, @@ -31,13 +30,6 @@ describe('getFirstNationsSQL', () => { }); }); -describe('getCommonSurveyMethodologiesSQL', () => { - it('returns valid sql statement', () => { - const response = getCommonSurveyMethodologiesSQL(); - expect(response).to.not.be.null; - }); -}); - describe('getFundingSourceSQL', () => { it('returns valid sql statement', () => { const response = getFundingSourceSQL(); diff --git a/api/src/queries/codes/code-queries.ts b/api/src/queries/codes/code-queries.ts index 565d750691..ec17b35443 100644 --- a/api/src/queries/codes/code-queries.ts +++ b/api/src/queries/codes/code-queries.ts @@ -6,7 +6,7 @@ import { SQL, SQLStatement } from 'sql-template-strings'; * @returns {SQLStatement} sql query object */ export const getManagementActionTypeSQL = (): SQLStatement => - SQL`SELECT management_action_type_id as id, name from management_action_type;`; + SQL`SELECT management_action_type_id as id, name from management_action_type where record_end_date is null;`; /** * SQL query to fetch first nation codes. @@ -14,7 +14,7 @@ export const getManagementActionTypeSQL = (): SQLStatement => * @returns {SQLStatement} sql query object */ export const getFirstNationsSQL = (): SQLStatement => - SQL`SELECT first_nations_id as id, name from first_nations ORDER BY name ASC;`; + SQL`SELECT first_nations_id as id, name from first_nations where record_end_date is null ORDER BY name ASC;`; /** * SQL query to fetch funding source codes. @@ -22,7 +22,7 @@ export const getFirstNationsSQL = (): SQLStatement => * @returns {SQLStatement} sql query object */ export const getFundingSourceSQL = (): SQLStatement => - SQL`SELECT funding_source_id as id, name from funding_source ORDER BY name ASC;`; + SQL`SELECT funding_source_id as id, name from funding_source where record_end_date is null ORDER BY name ASC;`; /** * SQL query to fetch proprietor type codes. @@ -30,29 +30,55 @@ export const getFundingSourceSQL = (): SQLStatement => * @returns {SQLStatement} sql query object */ export const getProprietorTypeSQL = (): SQLStatement => - SQL`SELECT proprietor_type_id as id, name, is_first_nation from proprietor_type;`; + SQL`SELECT proprietor_type_id as id, name, is_first_nation from proprietor_type where record_end_date is null;`; /** * SQL query to fetch activity codes. * * @returns {SQLStatement} sql query object */ -export const getActivitySQL = (): SQLStatement => SQL`SELECT activity_id as id, name from activity;`; +export const getActivitySQL = (): SQLStatement => + SQL`SELECT activity_id as id, name from activity where record_end_date is null;`; /** - * SQL query to fetch common survey methodology codes. + * SQL query to fetch field method codes. * * @returns {SQLStatement} sql query object */ -export const getCommonSurveyMethodologiesSQL = (): SQLStatement => - SQL`SELECT common_survey_methodology_id as id, name from common_survey_methodology;`; +export const getFieldMethodsSQL = (): SQLStatement => + SQL`SELECT field_method_id as id, name, description from field_method where record_end_date is null;`; + +/** + * SQL query to fetch ecological season codes. + * + * @returns {SQLStatement} sql query object + */ +export const getEcologicalSeasonsSQL = (): SQLStatement => + SQL`SELECT ecological_season_id as id, name, description from ecological_season where record_end_date is null;`; + +/** + * SQL query to fetch vantage codes. + * + * @returns {SQLStatement} sql query object + */ +export const getVantageCodesSQL = (): SQLStatement => + SQL`SELECT vantage_id as id, name from vantage where record_end_date is null;`; + +/** + * SQL query to intended outcomes codes. + * + * @returns {SQLStatement} sql query object + */ +export const getIntendedOutcomesSQL = (): SQLStatement => + SQL`SELECT intended_outcome_id as id, name, description from intended_outcome where record_end_date is null;`; /** * SQL query to fetch project type codes. * * @returns {SQLStatement} sql query object */ -export const getProjectTypeSQL = (): SQLStatement => SQL`SELECT project_type_id as id, name from project_type;`; +export const getProjectTypeSQL = (): SQLStatement => + SQL`SELECT project_type_id as id, name from project_type where record_end_date is null;`; /** * SQL query to fetch investment action category codes. @@ -60,7 +86,7 @@ export const getProjectTypeSQL = (): SQLStatement => SQL`SELECT project_type_id * @returns {SQLStatement} sql query object */ export const getInvestmentActionCategorySQL = (): SQLStatement => - SQL`SELECT investment_action_category_id as id, funding_source_id as fs_id, name from investment_action_category ORDER BY name ASC;`; + SQL`SELECT investment_action_category_id as id, funding_source_id as fs_id, name from investment_action_category where record_end_date is null ORDER BY name ASC;`; /** * SQL query to fetch IUCN conservation action level 1 classification codes. @@ -68,7 +94,7 @@ export const getInvestmentActionCategorySQL = (): SQLStatement => * @returns {SQLStatement} sql query object */ export const getIUCNConservationActionLevel1ClassificationSQL = (): SQLStatement => - SQL`SELECT iucn_conservation_action_level_1_classification_id as id, name from iucn_conservation_action_level_1_classification;`; + SQL`SELECT iucn_conservation_action_level_1_classification_id as id, name from iucn_conservation_action_level_1_classification where record_end_date is null;`; /** * SQL query to fetch IUCN conservation action level 2 sub-classification codes. @@ -76,7 +102,7 @@ export const getIUCNConservationActionLevel1ClassificationSQL = (): SQLStatement * @returns {SQLStatement} sql query object */ export const getIUCNConservationActionLevel2SubclassificationSQL = (): SQLStatement => - SQL`SELECT iucn_conservation_action_level_2_subclassification_id as id, iucn_conservation_action_level_1_classification_id as iucn1_id, name from iucn_conservation_action_level_2_subclassification;`; + SQL`SELECT iucn_conservation_action_level_2_subclassification_id as id, iucn_conservation_action_level_1_classification_id as iucn1_id, name from iucn_conservation_action_level_2_subclassification where record_end_date is null;`; /** * SQL query to fetch IUCN conservation action level 3 sub-classification codes. @@ -84,21 +110,23 @@ export const getIUCNConservationActionLevel2SubclassificationSQL = (): SQLStatem * @returns {SQLStatement} sql query object */ export const getIUCNConservationActionLevel3SubclassificationSQL = (): SQLStatement => - SQL`SELECT iucn_conservation_action_level_3_subclassification_id as id, iucn_conservation_action_level_2_subclassification_id as iucn2_id, name from iucn_conservation_action_level_3_subclassification;`; + SQL`SELECT iucn_conservation_action_level_3_subclassification_id as id, iucn_conservation_action_level_2_subclassification_id as iucn2_id, name from iucn_conservation_action_level_3_subclassification where record_end_date is null;`; /** * SQL query to fetch system role codes. * * @returns {SQLStatement} sql query object */ -export const getSystemRolesSQL = (): SQLStatement => SQL`SELECT system_role_id as id, name from system_role;`; +export const getSystemRolesSQL = (): SQLStatement => + SQL`SELECT system_role_id as id, name from system_role where record_end_date is null;`; /** * SQL query to fetch project role codes. * * @returns {SQLStatement} sql query object */ -export const getProjectRolesSQL = (): SQLStatement => SQL`SELECT project_role_id as id, name from project_role;`; +export const getProjectRolesSQL = (): SQLStatement => + SQL`SELECT project_role_id as id, name from project_role where record_end_date is null;`; /** * SQL query to fetch administrative activity status type codes. @@ -106,7 +134,7 @@ export const getProjectRolesSQL = (): SQLStatement => SQL`SELECT project_role_id * @returns {SQLStatement} sql query object */ export const getAdministrativeActivityStatusTypeSQL = (): SQLStatement => - SQL`SELECT administrative_activity_status_type_id as id, name FROM administrative_activity_status_type;`; + SQL`SELECT administrative_activity_status_type_id as id, name FROM administrative_activity_status_type where record_end_date is null;`; /** * SQL query to fetch taxon codes. @@ -122,7 +150,7 @@ export const getTaxonsSQL = (): SQLStatement => wldtaxonomic_units WHERE tty_name = 'SPECIES' - and + and end_date is null ORDER BY name;`; diff --git a/api/src/queries/dwc/dwc-queries.ts b/api/src/queries/dwc/dwc-queries.ts index 484f241427..c7cf248696 100644 --- a/api/src/queries/dwc/dwc-queries.ts +++ b/api/src/queries/dwc/dwc-queries.ts @@ -14,12 +14,12 @@ export const getSurveyOccurrenceSubmissionSQL = (dataPackageId: number): SQLStat defaultLog.debug({ label: debugLabel, message: 'params', dataPackageId }); const sqlStatement: SQLStatement = SQL` - SELECT - os.* - from + SELECT + os.* + from occurrence_submission os - , occurrence_submission_data_package osdp - where + , occurrence_submission_data_package osdp + where osdp.data_package_id = ${dataPackageId} and os.occurrence_submission_id = osdp.occurrence_submission_id; `; @@ -45,11 +45,11 @@ export const getDataPackageSQL = (dataPackageId: number): SQLStatement => { defaultLog.debug({ label: debugLabel, message: 'params', dataPackageId }); const sqlStatement: SQLStatement = SQL` - SELECT - * - from + SELECT + * + from data_package - where + where data_package_id = ${dataPackageId}; `; @@ -74,9 +74,9 @@ export const getPublishedSurveyStatusSQL = (occurrenceSubmissionId: number): SQL defaultLog.debug({ label: debugLabel, message: 'params', occurrenceSubmissionId }); const sqlStatement: SQLStatement = SQL` - SELECT + SELECT * - from + from survey_status where survey_status = api_get_character_system_constant('OCCURRENCE_SUBMISSION_STATE_PUBLISHED') @@ -104,10 +104,10 @@ export const getSurveySQL = (surveyId: number): SQLStatement => { defaultLog.debug({ label: debugLabel, message: 'params', surveyId }); const sqlStatement: SQLStatement = SQL` - SELECT + SELECT survey_id, project_id, - common_survey_methodology_id, + field_method_id, uuid, name, objectives, @@ -123,7 +123,7 @@ export const getSurveySQL = (surveyId: number): SQLStatement => { update_date, update_user, revision_count - from + from survey where survey_id = ${surveyId}; `; @@ -149,7 +149,7 @@ export const getProjectSQL = (projectId: number): SQLStatement => { defaultLog.debug({ label: debugLabel, message: 'params', projectId }); const sqlStatement: SQLStatement = SQL` - SELECT + SELECT project_id, project_type_id, uuid, @@ -171,7 +171,7 @@ export const getProjectSQL = (projectId: number): SQLStatement => { update_date, update_user, revision_count - from + from project where project_id = ${projectId}; `; @@ -197,23 +197,23 @@ export const getSurveyFundingSourceSQL = (surveyId: number): SQLStatement => { defaultLog.debug({ label: debugLabel, message: 'params', surveyId }); const sqlStatement: SQLStatement = SQL` - select - a.*, - b.name investment_action_category_name, - c.name funding_source_name - from - project_funding_source a, - investment_action_category b, + select + a.*, + b.name investment_action_category_name, + c.name funding_source_name + from + project_funding_source a, + investment_action_category b, funding_source c - where + where project_funding_source_id in ( - select - project_funding_source_id - from - survey_funding_source - where + select + project_funding_source_id + from + survey_funding_source + where survey_id = ${surveyId}) - and b.investment_action_category_id = a.investment_action_category_id + and b.investment_action_category_id = a.investment_action_category_id and c.funding_source_id = b.funding_source_id; `; @@ -238,17 +238,17 @@ export const getProjectFundingSourceSQL = (projectId: number): SQLStatement => { defaultLog.debug({ label: debugLabel, message: 'params', projectId }); const sqlStatement: SQLStatement = SQL` - select - a.*, - b.name investment_action_category_name, - c.name funding_source_name - from - project_funding_source a, - investment_action_category b, + select + a.*, + b.name investment_action_category_name, + c.name funding_source_name + from + project_funding_source a, + investment_action_category b, funding_source c - where + where project_id = ${projectId} - and b.investment_action_category_id = a.investment_action_category_id + and b.investment_action_category_id = a.investment_action_category_id and c.funding_source_id = b.funding_source_id; `; @@ -281,8 +281,8 @@ export const getGeometryBoundingBoxSQL = ( // TODO: this only provides us with the bounding box of the first polygon const sqlStatement: SQLStatement = SQL` with envelope as ( - select - ST_Envelope(geography::geometry) geom + select + ST_Envelope(geography::geometry) geom from ` .append(targetTable) .append( @@ -290,12 +290,12 @@ export const getGeometryBoundingBoxSQL = ( where ` ) .append(primaryKeyName).append(SQL` = ${primaryKey}) - select + select st_xmax(geom), st_ymax(geom), st_xmin(geom), st_ymin(geom) - from + from envelope; `); @@ -327,11 +327,11 @@ export const getGeometryPolygonsSQL = ( const sqlStatement: SQLStatement = SQL` with polygons as ( - select - (st_dumppoints(g.geom)).* + select + (st_dumppoints(g.geom)).* from ( - select - geography::geometry as geom + select + geography::geometry as geom from ` .append(targetTable) .append( @@ -340,20 +340,20 @@ export const getGeometryPolygonsSQL = ( ) .append(primaryKeyName).append(SQL` = ${primaryKey}) as g), points as ( - select - path[1] polygon, - path[2] point, - jsonb_build_array(st_y(p.geom), st_x(p.geom)) points - from - polygons p - order by - path[1], + select + path[1] polygon, + path[2] point, + jsonb_build_array(st_y(p.geom), st_x(p.geom)) points + from + polygons p + order by + path[1], path[2]) - select + select json_agg(p.points) points - from - points p - group by + from + points p + group by polygon; `); @@ -383,12 +383,12 @@ export const getTaxonomicCoverageSQL = (surveyId: number, isFocal: boolean): SQL focalPredicate = 'and not b.is_focal'; } const sqlStatement: SQLStatement = SQL` - select - a.* - from - wldtaxonomic_units a, + select + a.* + from + wldtaxonomic_units a, study_species b - where + where a.wldtaxonomic_units_id = b.wldtaxonomic_units_id and b.survey_id = ${surveyId} `.append(focalPredicate); @@ -414,19 +414,19 @@ export const getProjectIucnConservationSQL = (projectId: number): SQLStatement = defaultLog.debug({ label: debugLabel, message: 'params', projectId }); const sqlStatement: SQLStatement = SQL` - select - a.name level_1_name, - b.name level_2_name, - c.name level_3_name - from - iucn_conservation_action_level_1_classification a, - iucn_conservation_action_level_2_subclassification b, - iucn_conservation_action_level_3_subclassification c, + select + a.name level_1_name, + b.name level_2_name, + c.name level_3_name + from + iucn_conservation_action_level_1_classification a, + iucn_conservation_action_level_2_subclassification b, + iucn_conservation_action_level_3_subclassification c, project_iucn_action_classification d - where + where d.project_id = ${projectId} - and c.iucn_conservation_action_level_3_subclassification_id = d.iucn_conservation_action_level_3_subclassification_id - and b.iucn_conservation_action_level_2_subclassification_id = c.iucn_conservation_action_level_2_subclassification_id + and c.iucn_conservation_action_level_3_subclassification_id = d.iucn_conservation_action_level_3_subclassification_id + and b.iucn_conservation_action_level_2_subclassification_id = c.iucn_conservation_action_level_2_subclassification_id and a.iucn_conservation_action_level_1_classification_id = b.iucn_conservation_action_level_1_classification_id; `; @@ -451,11 +451,11 @@ export const getProjectStakeholderPartnershipSQL = (projectId: number): SQLState defaultLog.debug({ label: debugLabel, message: 'params', projectId }); const sqlStatement: SQLStatement = SQL` - select - a.name - from + select + a.name + from stakeholder_partnership a - where + where a.project_id = ${projectId}; `; @@ -480,12 +480,12 @@ export const getProjectActivitySQL = (projectId: number): SQLStatement => { defaultLog.debug({ label: debugLabel, message: 'params', projectId }); const sqlStatement: SQLStatement = SQL` - select - a.name - from - activity a, + select + a.name + from + activity a, project_activity b - where + where b.project_id = ${projectId} and a.activity_id = b.activity_id; `; @@ -511,12 +511,12 @@ export const getProjectClimateInitiativeSQL = (projectId: number): SQLStatement defaultLog.debug({ label: debugLabel, message: 'params', projectId }); const sqlStatement: SQLStatement = SQL` - select - a.name - from - climate_change_initiative a, + select + a.name + from + climate_change_initiative a, project_climate_initiative b - where + where b.project_id = ${projectId} and a.climate_change_initiative_id = b.climate_change_initiative_id; `; @@ -542,12 +542,12 @@ export const getProjectFirstNationsSQL = (projectId: number): SQLStatement => { defaultLog.debug({ label: debugLabel, message: 'params', projectId }); const sqlStatement: SQLStatement = SQL` - select - a.name - from - first_nations a, + select + a.name + from + first_nations a, project_first_nation b - where + where b.project_id = ${projectId} and a.first_nations_id = b.first_nations_id; `; @@ -573,12 +573,12 @@ export const getProjectManagementActionsSQL = (projectId: number): SQLStatement defaultLog.debug({ label: debugLabel, message: 'params', projectId }); const sqlStatement: SQLStatement = SQL` - select - a.* - from - management_action_type a, + select + a.* + from + management_action_type a, project_management_actions b - where + where a.management_action_type_id = b.management_action_type_id and b.project_id = ${projectId}; `; @@ -604,15 +604,15 @@ export const getSurveyProprietorSQL = (surveyId: number): SQLStatement => { defaultLog.debug({ label: debugLabel, message: 'params', surveyId }); const sqlStatement: SQLStatement = SQL` - select - a.name proprietor_type_name, - b.name first_nations_name, - c.* - from - proprietor_type a, - first_nations b, + select + a.name proprietor_type_name, + b.name first_nations_name, + c.* + from + proprietor_type a, + first_nations b, survey_proprietor c - where + where c.survey_id = ${surveyId} and b.first_nations_id = c.first_nations_id and a.proprietor_type_id = c.proprietor_type_id; diff --git a/api/src/queries/project/project-view-queries.ts b/api/src/queries/project/project-view-queries.ts index 048c025ce4..6121cdd6aa 100644 --- a/api/src/queries/project/project-view-queries.ts +++ b/api/src/queries/project/project-view-queries.ts @@ -84,7 +84,7 @@ export const getProjectListSQL = ( p.name, p.start_date, p.end_date, - p.coordinator_agency_name, + p.coordinator_agency_name as coordinator_agency, p.publish_timestamp, pt.name as project_type, string_agg(DISTINCT pp.number, ', ') as permits_list diff --git a/api/src/queries/public/project-queries.ts b/api/src/queries/public/project-queries.ts index 1469bf2373..133033b989 100644 --- a/api/src/queries/public/project-queries.ts +++ b/api/src/queries/public/project-queries.ts @@ -109,7 +109,7 @@ export const getPublicProjectListSQL = (): SQLStatement | null => { p.name, p.start_date, p.end_date, - p.coordinator_agency_name, + p.coordinator_agency_name as coordinator_agency, pt.name as project_type, string_agg(DISTINCT pp.number, ', ') as permits_list from @@ -120,9 +120,6 @@ export const getPublicProjectListSQL = (): SQLStatement | null => { on p.project_id = pp.project_id where p.publish_timestamp is not null - `; - - sqlStatement.append(SQL` group by p.project_id, p.name, @@ -130,7 +127,7 @@ export const getPublicProjectListSQL = (): SQLStatement | null => { p.end_date, p.coordinator_agency_name, pt.name; - `); + `; defaultLog.debug({ label: 'getPublicProjectListSQL', diff --git a/api/src/queries/survey/survey-create-queries.ts b/api/src/queries/survey/survey-create-queries.ts index e849747431..a1f945b9bb 100644 --- a/api/src/queries/survey/survey-create-queries.ts +++ b/api/src/queries/survey/survey-create-queries.ts @@ -28,26 +28,30 @@ export const postSurveySQL = (projectId: number, survey: PostSurveyObject): SQLS INSERT INTO survey ( project_id, name, - objectives, + additional_details, + ecological_season_id, + intended_outcome_id, start_date, end_date, lead_first_name, lead_last_name, location_name, geojson, - common_survey_methodology_id, + field_method_id, geography ) VALUES ( ${projectId}, ${survey.survey_name}, - ${survey.survey_purpose}, + ${survey.additional_details}, + ${survey.ecological_season_id}, + ${survey.intended_outcome_id}, ${survey.start_date}, ${survey.end_date}, ${survey.biologist_first_name}, ${survey.biologist_last_name}, ${survey.survey_area_name}, ${JSON.stringify(survey.geometry)}, - ${survey.common_survey_methodology_id} + ${survey.field_method_id} `; if (survey.geometry && survey.geometry.length) { @@ -305,3 +309,37 @@ export const postAncillarySpeciesSQL = (speciesId: number, surveyId: number): SQ return sqlStatement; }; + +/** + * SQL query to insert a ancillary species row into the study_species table. + * + * @param {number} speciesId + * @param {number} surveyId + * @returns {SQLStatement} sql query object + */ +export const postVantageCodesSQL = (vantageCodeId: number, surveyId: number): SQLStatement | null => { + defaultLog.debug({ label: 'postVantageCodesSQL', message: 'params', vantageCodeId, surveyId }); + + if (!vantageCodeId || !surveyId) { + return null; + } + + const sqlStatement: SQLStatement = SQL` + INSERT INTO survey_vantage ( + vantage_id, + survey_id + ) VALUES ( + ${vantageCodeId}, + ${surveyId} + ) RETURNING survey_vantage_id as id; + `; + + defaultLog.debug({ + label: 'postVantageCodesSQL', + message: 'sql', + 'sqlStatement.text': sqlStatement.text, + 'sqlStatement.values': sqlStatement.values + }); + + return sqlStatement; +}; diff --git a/api/src/queries/survey/survey-delete-queries.ts b/api/src/queries/survey/survey-delete-queries.ts index 63123f6003..b7a103681e 100644 --- a/api/src/queries/survey/survey-delete-queries.ts +++ b/api/src/queries/survey/survey-delete-queries.ts @@ -211,3 +211,38 @@ export const deleteSurveySQL = (surveyId: number): SQLStatement | null => { return sqlStatement; }; + +/** + * SQL query to delete survey proprietor rows. + * + * @param {number} surveyId + * @param {number} surveyProprietorId + * @returns {SQLStatement} sql query object + */ +export const deleteSurveyVantageCodesSQL = (surveyId: number): SQLStatement | null => { + defaultLog.debug({ + label: 'deleteSurveyVantageCodeSQL', + message: 'params', + surveyId + }); + + if (!surveyId && surveyId !== 0) { + return null; + } + + const sqlStatement: SQLStatement = SQL` + DELETE + from survey_vantage + WHERE + survey_id = ${surveyId} + `; + + defaultLog.debug({ + label: 'deleteSurveyVantageCodeSQL', + message: 'sql', + 'sqlStatement.text': sqlStatement.text, + 'sqlStatement.values': sqlStatement.values + }); + + return sqlStatement; +}; diff --git a/api/src/queries/survey/survey-occurrence-queries.test.ts b/api/src/queries/survey/survey-occurrence-queries.test.ts index 4735e94c19..7d5991dcfe 100644 --- a/api/src/queries/survey/survey-occurrence-queries.test.ts +++ b/api/src/queries/survey/survey-occurrence-queries.test.ts @@ -245,25 +245,20 @@ describe('getOccurrenceSubmissionMessagesSQL', () => { }); describe('getTemplateMethodologySpeciesRecordSQL', () => { - it('returns null response when null speciesId provided', () => { - const response = getTemplateMethodologySpeciesRecordSQL((null as unknown) as number, 1, 1); - - expect(response).to.be.null; - }); it('returns null response when null methodologyId provided', () => { - const response = getTemplateMethodologySpeciesRecordSQL(1234, (null as unknown) as number, 1); + const response = getTemplateMethodologySpeciesRecordSQL((null as unknown) as number, 1); expect(response).to.be.null; }); it('returns null response when null templateId provided', () => { - const response = getTemplateMethodologySpeciesRecordSQL(1234, 1, (null as unknown) as number); + const response = getTemplateMethodologySpeciesRecordSQL(1, (null as unknown) as number); expect(response).to.be.null; }); it('returns non null response when valid params provided', () => { - const response = getTemplateMethodologySpeciesRecordSQL(1234, 1, 1); + const response = getTemplateMethodologySpeciesRecordSQL(1, 1); expect(response).to.not.be.null; }); diff --git a/api/src/queries/survey/survey-occurrence-queries.ts b/api/src/queries/survey/survey-occurrence-queries.ts index c82e07cbc8..47bb1ec868 100644 --- a/api/src/queries/survey/survey-occurrence-queries.ts +++ b/api/src/queries/survey/survey-occurrence-queries.ts @@ -513,25 +513,22 @@ export const getOccurrenceSubmissionMessagesSQL = (occurrenceSubmissionId: numbe /** * SQL query to get a template methodology species id. * - * @param {number} surveyId - * @param {string} source - * @param {string} inputKey + * @param {number} fieldMethodId + * @param {number} templateId * @return {*} {(SQLStatement | null)} */ export const getTemplateMethodologySpeciesRecordSQL = ( - speciesId: number, - methodologyId: number, + fieldMethodId: number, templateId: number ): SQLStatement | null => { defaultLog.debug({ label: 'getTemplateMethodologySpeciesRecordSQL', message: 'params', - speciesId, - methodologyId, + fieldMethodId, templateId }); - if (!speciesId || !methodologyId || !templateId) { + if (!fieldMethodId || !templateId) { return null; } @@ -540,9 +537,7 @@ export const getTemplateMethodologySpeciesRecordSQL = ( FROM template_methodology_species tms WHERE - tms.common_survey_methodology_id = ${methodologyId} - AND - tms.wldtaxonomic_units_id = ${speciesId} + tms.field_method_id = ${fieldMethodId} AND tms.template_id = ${templateId} ; diff --git a/api/src/queries/survey/survey-update-queries.test.ts b/api/src/queries/survey/survey-update-queries.test.ts index bb74c0ee5f..d21c6b13ce 100644 --- a/api/src/queries/survey/survey-update-queries.test.ts +++ b/api/src/queries/survey/survey-update-queries.test.ts @@ -12,10 +12,8 @@ import { describe('putSurveyDetailsSQL', () => { const surveyData: PutSurveyDetailsData = { name: 'test', - objectives: 'objectives', focal_species: [1, 2], ancillary_species: [3, 4], - common_survey_methodology_id: 1, start_date: '2020/04/04', end_date: '2020/05/05', lead_first_name: 'first', diff --git a/api/src/queries/survey/survey-update-queries.ts b/api/src/queries/survey/survey-update-queries.ts index f915acf139..d02be3abfc 100644 --- a/api/src/queries/survey/survey-update-queries.ts +++ b/api/src/queries/survey/survey-update-queries.ts @@ -1,5 +1,9 @@ import { SQL, SQLStatement } from 'sql-template-strings'; -import { PutSurveyDetailsData, PutSurveyProprietorData } from '../../models/survey-update'; +import { + PutSurveyDetailsData, + PutSurveyProprietorData, + PutSurveyPurposeAndMethodologyData +} from '../../models/survey-update'; import { getLogger } from '../../utils/logger'; import { queries } from '../queries'; @@ -133,14 +137,12 @@ export const putSurveyDetailsSQL = ( UPDATE survey SET name = ${data.name}, - objectives = ${data.objectives}, start_date = ${data.start_date}, end_date = ${data.end_date}, lead_first_name = ${data.lead_first_name}, lead_last_name = ${data.lead_last_name}, location_name = ${data.location_name}, geojson = ${JSON.stringify(data.geometry)}, - common_survey_methodology_id = ${data.common_survey_methodology_id}, geography = `; @@ -262,3 +264,52 @@ export const updateSurveyPublishStatusSQL = (surveyId: number, publish: boolean) return sqlStatement; }; + +/** + * SQL query to update a survey row. + * + * @param {number} projectId + * @param {number} surveyId + * @param {PutSurveyPurposeAndMethodologyData} data + * @returns {SQLStatement} sql query object + */ +export const putSurveyPurposeAndMethodologySQL = ( + surveyId: number, + data: PutSurveyPurposeAndMethodologyData | null, + revision_count: number +): SQLStatement | null => { + defaultLog.debug({ + label: 'putSurveyPurposeAndMethodologySQL', + message: 'params', + surveyId, + data, + revision_count + }); + + if (!surveyId || !data) { + return null; + } + + const sqlStatement = SQL` + UPDATE + survey + SET + field_method_id = ${data.field_method_id}, + additional_details = ${data.additional_details}, + ecological_season_id = ${data.ecological_season_id}, + intended_outcome_id = ${data.intended_outcome_id} + WHERE + survey_id = ${surveyId} + AND + revision_count = ${revision_count}; + `; + + defaultLog.debug({ + label: 'putSurveyPurposeAndMethodologySQL', + message: 'sql', + 'sqlStatement.text': sqlStatement.text, + 'sqlStatement.values': sqlStatement.values + }); + + return sqlStatement; +}; diff --git a/api/src/queries/survey/survey-view-queries.ts b/api/src/queries/survey/survey-view-queries.ts index 66dc564b07..0ba1b15309 100644 --- a/api/src/queries/survey/survey-view-queries.ts +++ b/api/src/queries/survey/survey-view-queries.ts @@ -145,7 +145,10 @@ export const getSurveyBasicDataForViewSQL = (surveyId: number): SQLStatement | n SELECT s.survey_id as id, s.name, - s.objectives, + s.additional_details, + s.field_method_id, + s.ecological_season_id, + s.intended_outcome_id, s.start_date, s.end_date, s.lead_first_name, @@ -156,7 +159,6 @@ export const getSurveyBasicDataForViewSQL = (surveyId: number): SQLStatement | n s.publish_timestamp as publish_date, per.number, per.type, - csm.name as common_survey_methodology, max(os.occurrence_submission_id) as occurrence_submission_id, max(sss.survey_summary_submission_id) as survey_summary_submission_id FROM @@ -166,9 +168,9 @@ export const getSurveyBasicDataForViewSQL = (surveyId: number): SQLStatement | n ON per.survey_id = s.survey_id LEFT OUTER JOIN - common_survey_methodology as csm + field_method as fm ON - csm.common_survey_methodology_id = s.common_survey_methodology_id + fm.field_method_id = s.field_method_id LEFT OUTER JOIN occurrence_submission as os ON @@ -182,7 +184,10 @@ export const getSurveyBasicDataForViewSQL = (surveyId: number): SQLStatement | n GROUP BY s.survey_id, s.name, - s.objectives, + s.field_method_id, + s.additional_details, + s.intended_outcome_id, + s.ecological_season_id, s.start_date, s.end_date, s.lead_first_name, @@ -192,8 +197,7 @@ export const getSurveyBasicDataForViewSQL = (surveyId: number): SQLStatement | n s.revision_count, s.publish_timestamp, per.number, - per.type, - csm.name; + per.type; `; defaultLog.debug({ @@ -212,7 +216,6 @@ export const getSurveyFundingSourcesDataForViewSQL = (surveyId: number): SQLStat message: 'params', surveyId }); - if (!surveyId) { return null; } diff --git a/api/src/queries/survey/survey-view-update-queries.ts b/api/src/queries/survey/survey-view-update-queries.ts index cf125e2437..36276e1a92 100644 --- a/api/src/queries/survey/survey-view-update-queries.ts +++ b/api/src/queries/survey/survey-view-update-queries.ts @@ -24,7 +24,7 @@ export const getSurveyDetailsForUpdateSQL = (surveyId: number): SQLStatement | n SELECT s.survey_id as id, s.name, - s.objectives, + s.additional_details, s.start_date, s.end_date, s.lead_first_name, @@ -32,7 +32,7 @@ export const getSurveyDetailsForUpdateSQL = (surveyId: number): SQLStatement | n s.location_name, s.geojson as geometry, s.revision_count, - s.common_survey_methodology_id, + s.field_method_id, s.publish_timestamp as publish_date, per.number, per.type, @@ -83,7 +83,7 @@ export const getSurveyDetailsForUpdateSQL = (surveyId: number): SQLStatement | n group by s.survey_id, s.name, - s.objectives, + s.additional_details, s.start_date, s.end_date, s.lead_first_name, @@ -91,7 +91,7 @@ export const getSurveyDetailsForUpdateSQL = (surveyId: number): SQLStatement | n s.location_name, s.geojson, s.revision_count, - s.common_survey_methodology_id, + s.field_method_id, s.publish_timestamp, per.number, per.type; @@ -158,3 +158,85 @@ export const getSurveyProprietorForUpdateSQL = (surveyId: number): SQLStatement return sqlStatement; }; + +/** + * SQL query to retrieve a survey_proprietor row. + * + * @param {number} surveyId + * @returns {SQLStatement} sql query object + */ +export const getSurveyPurposeAndMethodologyForUpdateSQL = (surveyId: number): SQLStatement | null => { + defaultLog.debug({ + label: 'getSurveyPurposeAndMethodologyForUpdateSQL', + message: 'params', + surveyId + }); + + if (!surveyId) { + return null; + } + + const sqlStatement = SQL` + SELECT + s.survey_id as id, + s.field_method_id, + s.additional_details, + s.ecological_season_id, + s.intended_outcome_id, + s.revision_count, + sv.vantage_id + FROM + survey s + LEFT OUTER JOIN + survey_vantage sv + ON + sv.survey_id = s.survey_id + WHERE + s.survey_id = ${surveyId}; + `; + + defaultLog.debug({ + label: 'getSurveyPurposeAndMethodologyForUpdateSQL', + message: 'sql', + 'sqlStatement.text': sqlStatement.text, + 'sqlStatement.values': sqlStatement.values + }); + + return sqlStatement; +}; + +/** + * SQL query to retrieve a survey_proprietor row. + * + * @param {number} surveyId + * @returns {SQLStatement} sql query object + */ +export const getSurveyVantageCodesSQL = (surveyId: number): SQLStatement | null => { + defaultLog.debug({ + label: 'getSurveyVantageCodesSQL', + message: 'params', + surveyId + }); + + if (!surveyId) { + return null; + } + + const sqlStatement = SQL` + SELECT + vantage_id + FROM + survey_vantage + WHERE + survey_id = ${surveyId}; + `; + + defaultLog.debug({ + label: 'getSurveyVantageCodesSQL', + message: 'sql', + 'sqlStatement.text': sqlStatement.text, + 'sqlStatement.values': sqlStatement.values + }); + + return sqlStatement; +}; diff --git a/api/src/services/code-service.test.ts b/api/src/services/code-service.test.ts index 537139d6b0..bbbed1f2ee 100644 --- a/api/src/services/code-service.test.ts +++ b/api/src/services/code-service.test.ts @@ -43,7 +43,10 @@ describe('CodeService', () => { 'project_roles', 'regional_offices', 'administrative_activity_status_type', - 'common_survey_methodologies' + 'ecological_seasons', + 'field_methods', + 'intended_outcomes', + 'vantage_codes' ); }); }); diff --git a/api/src/services/code-service.ts b/api/src/services/code-service.ts index 7c3e63dc57..ceca3793b0 100644 --- a/api/src/services/code-service.ts +++ b/api/src/services/code-service.ts @@ -39,7 +39,10 @@ export interface IAllCodeSets { project_roles: CodeSet; regional_offices: CodeSet; administrative_activity_status_type: CodeSet; - common_survey_methodologies: CodeSet; + field_methods: CodeSet<{ id: number; name: string; description: string }>; + ecological_seasons: CodeSet<{ id: number; name: string; description: string }>; + intended_outcomes: CodeSet<{ id: number; name: string; description: string }>; + vantage_codes: CodeSet; } export class CodeService extends DBService { @@ -67,7 +70,10 @@ export class CodeService extends DBService { project_roles, administrative_activity_status_type, species, - common_survey_methodologies + field_methods, + ecological_seasons, + intended_outcomes, + vantage_codes ] = await Promise.all([ await this.connection.query(queries.codes.getManagementActionTypeSQL().text), await this.connection.query(queries.codes.getFirstNationsSQL().text), @@ -83,7 +89,12 @@ export class CodeService extends DBService { await this.connection.query(queries.codes.getProjectRolesSQL().text), await this.connection.query(queries.codes.getAdministrativeActivityStatusTypeSQL().text), await this.connection.query(queries.codes.getTaxonsSQL().text), - await this.connection.query(queries.codes.getCommonSurveyMethodologiesSQL().text) + await this.connection.query(queries.codes.getFieldMethodsSQL().text), + await this.connection.query(queries.codes.getEcologicalSeasonsSQL().text), + + await this.connection.query(queries.codes.getIntendedOutcomesSQL().text), + + await this.connection.query(queries.codes.getVantageCodesSQL().text) ]); return { @@ -109,7 +120,11 @@ export class CodeService extends DBService { administrative_activity_status_type: (administrative_activity_status_type && administrative_activity_status_type.rows) || [], species: (species && species.rows) || [], - common_survey_methodologies: (common_survey_methodologies && common_survey_methodologies.rows) || [], + field_methods: (field_methods && field_methods.rows) || [], + ecological_seasons: (ecological_seasons && ecological_seasons.rows) || [], + intended_outcomes: (intended_outcomes && intended_outcomes.rows) || [], + vantage_codes: (vantage_codes && vantage_codes.rows) || [], + // TODO Temporarily hard coded list of code values below coordinator_agency, region, diff --git a/api/src/services/project-service.test.ts b/api/src/services/project-service.test.ts index fc6eae11ee..db178a2ab9 100644 --- a/api/src/services/project-service.test.ts +++ b/api/src/services/project-service.test.ts @@ -285,4 +285,179 @@ describe('ProjectService', () => { expect(mockQuery).to.have.been.calledOnce; }); }); + + describe('getPublicProjectsList', () => { + afterEach(() => { + sinon.restore(); + }); + + it('should throw a 400 error when no sql statement produced', async () => { + const mockDBConnection = getMockDBConnection(); + + sinon.stub(queries.public, 'getPublicProjectListSQL').returns(null); + + const projectService = new ProjectService(mockDBConnection); + + try { + await projectService.getPublicProjectsList(); + expect.fail(); + } catch (actualError) { + expect((actualError as HTTPError).message).to.equal('Failed to build SQL get statement'); + expect((actualError as HTTPError).status).to.equal(400); + } + }); + + it('returns empty array if there are no rows', async () => { + const mockQueryResponse = ({ rows: [] } as unknown) as QueryResult; + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + + sinon.stub(queries.public, 'getPublicProjectListSQL').returns(SQL`valid sql`); + + const projectService = new ProjectService(mockDBConnection); + + const result = await projectService.getPublicProjectsList(); + + expect(result).to.eql([]); + }); + + it('returns rows on success', async () => { + const mockRowObj = [ + { + id: 123, + name: 'Project 1', + start_date: '1900-01-01', + end_date: '2000-10-10', + coordinator_agency: 'Agency 1', + permits_list: '3, 100', + project_type: 'Aquatic Habitat' + }, + { + id: 456, + name: 'Project 2', + start_date: '1900-01-01', + end_date: '2000-12-31', + coordinator_agency: 'Agency 2', + permits_list: '1, 4', + project_type: 'Terrestrial Habitat' + } + ]; + const mockQueryResponse = ({ rows: mockRowObj } as unknown) as QueryResult; + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + + sinon.stub(queries.public, 'getPublicProjectListSQL').returns(SQL`valid sql`); + + const projectService = new ProjectService(mockDBConnection); + + const result = await projectService.getPublicProjectsList(); + + expect(result[0].id).to.equal(123); + expect(result[0].name).to.equal('Project 1'); + expect(result[0].completion_status).to.equal('Completed'); + + expect(result[1].id).to.equal(456); + expect(result[1].name).to.equal('Project 2'); + expect(result[1].completion_status).to.equal('Completed'); + }); + }); + + describe('getProjectList', () => { + afterEach(() => { + sinon.restore(); + }); + + it('should throw a 400 error when no sql statement produced', async () => { + const mockDBConnection = getMockDBConnection(); + + sinon.stub(queries.project, 'getProjectListSQL').returns(null); + + const projectService = new ProjectService(mockDBConnection); + + try { + await projectService.getProjectList(true, 1, {}); + expect.fail(); + } catch (actualError) { + expect((actualError as HTTPError).message).to.equal('Failed to build SQL select statement'); + expect((actualError as HTTPError).status).to.equal(400); + } + }); + + it('returns empty array if there are no rows', async () => { + const mockQueryResponse = ({ rows: [] } as unknown) as QueryResult; + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + + sinon.stub(queries.project, 'getProjectListSQL').returns(SQL`valid sql`); + + const projectService = new ProjectService(mockDBConnection); + + const result = await projectService.getProjectList(true, 1, {}); + + expect(result).to.eql([]); + }); + + it('returns rows on success', async () => { + const mockRowObj = [ + { + id: 123, + name: 'Project 1', + start_date: '1900-01-01', + end_date: '2200-10-10', + coordinator_agency: 'Agency 1', + publish_timestamp: '2010-01-01', + permits_list: '3, 100', + project_type: 'Aquatic Habitat' + }, + { + id: 456, + name: 'Project 2', + start_date: '1900-01-01', + end_date: '2000-12-31', + coordinator_agency: 'Agency 2', + publish_timestamp: '', + permits_list: '1, 4', + project_type: 'Terrestrial Habitat' + } + ]; + const mockQueryResponse = ({ rows: mockRowObj } as unknown) as QueryResult; + const mockDBConnection = getMockDBConnection({ query: async () => mockQueryResponse }); + + sinon.stub(queries.project, 'getProjectListSQL').returns(SQL`valid sql`); + + const projectService = new ProjectService(mockDBConnection); + + const result = await projectService.getProjectList(true, 1, {}); + + expect(result[0].id).to.equal(123); + expect(result[0].name).to.equal('Project 1'); + expect(result[0].completion_status).to.equal('Active'); + expect(result[0].publish_status).to.equal('Published'); + + expect(result[1].id).to.equal(456); + expect(result[1].name).to.equal('Project 2'); + expect(result[1].completion_status).to.equal('Completed'); + expect(result[1].publish_status).to.equal('Unpublished'); + }); + }); + + describe('getPublicProjectById', () => { + afterEach(() => { + sinon.restore(); + }); + + it('should throw a 400 error when no sql statement produced', async () => { + const mockDBConnection = getMockDBConnection(); + + sinon.stub(queries.public, 'getPublicProjectSQL').returns(null); + sinon.stub(queries.public, 'getActivitiesByPublicProjectSQL').returns(null); + + const projectService = new ProjectService(mockDBConnection); + + try { + await projectService.getPublicProjectById(1); + expect.fail(); + } catch (actualError) { + expect((actualError as HTTPError).message).to.equal('Failed to build SQL get statement'); + expect((actualError as HTTPError).status).to.equal(400); + } + }); + }); }); diff --git a/api/src/services/project-service.ts b/api/src/services/project-service.ts index 0d1dd43f57..5de1979c61 100644 --- a/api/src/services/project-service.ts +++ b/api/src/services/project-service.ts @@ -21,14 +21,14 @@ import { PutProjectData } from '../models/project-update'; import { - GetIUCNClassificationData, - GetPermitData, - GetProjectData, GetCoordinatorData, + GetFundingData, + GetIUCNClassificationData, GetLocationData, GetObjectivesData, GetPartnershipsData, - GetFundingData, + GetPermitData, + GetProjectData, IGetProject } from '../models/project-view'; import { GetPublicCoordinatorData, GetPublicProjectData } from '../models/public/project'; @@ -162,7 +162,7 @@ export class ProjectService extends DBService { name: row.name, start_date: row.start_date, end_date: row.end_date, - coordinator_agency: row.coordinator_agency_name, + coordinator_agency: row.coordinator_agency, completion_status: (row.end_date && moment(row.end_date).endOf('day').isBefore(moment()) && COMPLETION_STATUS.COMPLETED) || COMPLETION_STATUS.ACTIVE, diff --git a/app/README.md b/app/README.md index f9847b4a48..7976b303be 100644 --- a/app/README.md +++ b/app/README.md @@ -1,6 +1,13 @@ # bcgov/biohubbc/app -A standard React web-app for SIMS management activities. +## Technologies Used + +| Technology | Version | Website | Description | +| ---------- | ------- | ------------------------------------ | -------------------- | +| node | 14.x.x | https://nodejs.org/en/ | JavaScript Runtime | +| npm | 6.x.x | https://www.npmjs.com/ | Node Package Manager | + + ## Documenation diff --git a/app/src/components/fields/SelectWithSubtext.tsx b/app/src/components/fields/SelectWithSubtext.tsx new file mode 100644 index 0000000000..116973785d --- /dev/null +++ b/app/src/components/fields/SelectWithSubtext.tsx @@ -0,0 +1,107 @@ +import FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import InputLabel from '@material-ui/core/InputLabel'; +import ListItemText from '@material-ui/core/ListItemText'; +import MenuItem from '@material-ui/core/MenuItem'; +import Select from '@material-ui/core/Select'; +import { ThemeProvider } from '@material-ui/styles'; +import { createMuiTheme } from '@material-ui/core'; +import { useFormikContext } from 'formik'; +import get from 'lodash-es/get'; +import React from 'react'; +import appTheme from 'themes/appTheme'; + +export interface ISelectWithSubtextFieldOption { + value: string | number; + label: string; + subText?: string; +} + +export interface ISelectWithSubtextField { + id: string; + name: string; + label: string; + options: ISelectWithSubtextFieldOption[]; + required?: boolean; +} + +const selectWithSubtextTheme = createMuiTheme({ + ...appTheme, + overrides: { + ...(appTheme?.overrides || {}), + MuiMenu: { + paper: { + minWidth: '72ch !important', + maxWidth: '72ch !important', + maxHeight: 500 + } + }, + MuiMenuItem: { + root: { + whiteSpace: 'break-spaces', + borderBottom: '1px solid rgba(0, 0, 0, 0.12)' + } + }, + MuiListItemText: { + primary: { + fontSize: '14px', + fontWeight: 700 + }, + secondary: { + marginTop: appTheme.spacing(0.5) + } + }, + MuiInputLabel: { + root: { + paddingRight: '20px' + } + } + } +}); + +const SelectWithSubtextField: React.FC = (props) => { + const { values, touched, errors, handleChange } = useFormikContext(); + + return ( + + + {props.label} + { + // convert the selected `value` back into its matching `label` + const code = props.options.find((item) => item.value === value); + return <>{code?.label}>; + }} + MenuProps={{ + getContentAnchorEl: null, + className: 'menuTest', + anchorOrigin: { + vertical: 'bottom', + horizontal: 'left' + }, + transformOrigin: { + vertical: 'top', + horizontal: 'left' + } + }}> + {props.options.map((item) => ( + + + + ))} + + {get(touched, props.name) && get(errors, props.name)} + + + ); +}; + +export default SelectWithSubtextField; diff --git a/app/src/constants/i18n.ts b/app/src/constants/i18n.ts index 0ecb599366..0f510e8e8d 100644 --- a/app/src/constants/i18n.ts +++ b/app/src/constants/i18n.ts @@ -91,6 +91,13 @@ export const EditSurveyProprietorI18N = { 'An error has occurred while attempting to edit your survey proprietor information, please try again. If the error persists, please contact your system administrator.' }; +export const EditSurveyPurposeAndMethodologyI18N = { + editTitle: 'Edit Survey Purpose and Methodology', + editErrorTitle: 'Error Editing Survey Purpose and Methodology', + editErrorText: + 'An error has occurred while attempting to edit your survey purpose and methodology information, please try again. If the error persists, please contact your system administrator.' +}; + export const EditLocationBoundaryI18N = { editTitle: 'Edit Project Location', editErrorTitle: 'Error Editing Project Location', diff --git a/app/src/features/surveys/CreateSurveyPage.test.tsx b/app/src/features/surveys/CreateSurveyPage.test.tsx index cd8ae13dd9..c9b721521a 100644 --- a/app/src/features/surveys/CreateSurveyPage.test.tsx +++ b/app/src/features/surveys/CreateSurveyPage.test.tsx @@ -8,6 +8,7 @@ import CreateSurveyPage from './CreateSurveyPage'; import { MemoryRouter, Router } from 'react-router'; import { createMemoryHistory } from 'history'; import { DialogContextProvider } from 'contexts/dialogContext'; +import { codes } from 'test-helpers/code-helpers'; const history = createMemoryHistory(); @@ -60,9 +61,7 @@ describe('CreateSurveyPage', () => { it('renders correctly when codes and project data are loaded', async () => { mockBiohubApi().project.getProjectForView.mockResolvedValue(getProjectForViewResponse); - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ - species: [{ id: 1, name: 'species 1' }] - } as any); + mockBiohubApi().codes.getAllCodeSets.mockResolvedValue(codes); mockBiohubApi().survey.getSurveyPermits.mockResolvedValue([ { number: '123', type: 'Scientific' }, @@ -89,9 +88,7 @@ describe('CreateSurveyPage', () => { it('calls history.push() if the user clicks `Yes`', async () => { mockBiohubApi().project.getProjectForView.mockResolvedValue(getProjectForViewResponse); - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ - species: [{ id: 1, name: 'species 1' }] - } as any); + mockBiohubApi().codes.getAllCodeSets.mockResolvedValue(codes); mockBiohubApi().survey.getSurveyPermits.mockResolvedValue([ { number: '123', type: 'Scientific' }, @@ -140,9 +137,7 @@ describe('CreateSurveyPage', () => { it('does nothing if the user clicks `No` or away from the dialog', async () => { mockBiohubApi().project.getProjectForView.mockResolvedValue(getProjectForViewResponse); - mockBiohubApi().codes.getAllCodeSets.mockResolvedValue({ - species: [{ id: 1, name: 'species 1' }] - } as any); + mockBiohubApi().codes.getAllCodeSets.mockResolvedValue(codes); mockBiohubApi().survey.getSurveyPermits.mockResolvedValue([ { number: '123', type: 'Scientific' }, diff --git a/app/src/features/surveys/CreateSurveyPage.tsx b/app/src/features/surveys/CreateSurveyPage.tsx index 09346c992e..86a6cb8d22 100644 --- a/app/src/features/surveys/CreateSurveyPage.tsx +++ b/app/src/features/surveys/CreateSurveyPage.tsx @@ -10,15 +10,23 @@ import { Theme } from '@material-ui/core/styles/createMuiTheme'; import makeStyles from '@material-ui/core/styles/makeStyles'; import Typography from '@material-ui/core/Typography'; import { IErrorDialogProps } from 'components/dialog/ErrorDialog'; +import HorizontalSplitFormComponent from 'components/fields/HorizontalSplitFormComponent'; +import { DATE_FORMAT, DATE_LIMIT } from 'constants/dateTimeFormats'; import { CreateSurveyI18N } from 'constants/i18n'; +import { DialogContext } from 'contexts/dialogContext'; import { Formik, FormikProps } from 'formik'; +import * as History from 'history'; +import { APIError } from 'hooks/api/useAxios'; import { useBiohubApi } from 'hooks/useBioHubApi'; import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; import { IGetProjectForViewResponse } from 'interfaces/useProjectApi.interface'; import { ICreateSurveyRequest, SurveyFundingSources, SurveyPermits } from 'interfaces/useSurveyApi.interface'; +import moment from 'moment'; import React, { useCallback, useContext, useEffect, useRef, useState } from 'react'; import { Prompt, useHistory, useParams } from 'react-router'; import { validateFormFieldsAndReportCompletion } from 'utils/customValidation'; +import { getFormattedAmount, getFormattedDate, getFormattedDateRangeString } from 'utils/Utils'; +import yup from 'utils/YupSchema'; import AgreementsForm, { AgreementsInitialValues, AgreementsYupSchema } from './components/AgreementsForm'; import GeneralInformationForm, { GeneralInformationInitialValues, @@ -28,15 +36,11 @@ import ProprietaryDataForm, { ProprietaryDataInitialValues, ProprietaryDataYupSchema } from './components/ProprietaryDataForm'; +import PurposeAndMethodologyForm, { + PurposeAndMethodologyInitialValues, + PurposeAndMethodologyYupSchema +} from './components/PurposeAndMethodologyForm'; import StudyAreaForm, { StudyAreaInitialValues, StudyAreaYupSchema } from './components/StudyAreaForm'; -import HorizontalSplitFormComponent from 'components/fields/HorizontalSplitFormComponent'; -import * as History from 'history'; -import { APIError } from 'hooks/api/useAxios'; -import { DialogContext } from 'contexts/dialogContext'; -import yup from 'utils/YupSchema'; -import { DATE_FORMAT, DATE_LIMIT } from 'constants/dateTimeFormats'; -import moment from 'moment'; -import { getFormattedAmount, getFormattedDate, getFormattedDateRangeString } from 'utils/Utils'; const useStyles = makeStyles((theme: Theme) => ({ actionButton: { @@ -117,6 +121,7 @@ const CreateSurveyPage = () => { const [surveyInitialValues] = useState({ ...GeneralInformationInitialValues, ...StudyAreaInitialValues, + ...PurposeAndMethodologyInitialValues, ...ProprietaryDataInitialValues, ...AgreementsInitialValues }); @@ -158,6 +163,7 @@ const CreateSurveyPage = () => { ) }) .concat(StudyAreaYupSchema) + .concat(PurposeAndMethodologyYupSchema) .concat(ProprietaryDataYupSchema) .concat(AgreementsYupSchema); @@ -358,11 +364,6 @@ const CreateSurveyPage = () => { return { value: item.id, label: item.name }; }) || [] } - common_survey_methodologies={ - codes?.common_survey_methodologies?.map((item) => { - return { value: item.id, label: item.name }; - }) || [] - } permit_numbers={ surveyPermits?.map((item) => { return { value: item.number, label: `${item.number} - ${item.type}` }; @@ -389,6 +390,36 @@ const CreateSurveyPage = () => { + { + return { value: item.id, label: item.name, subText: item.description }; + }) || [] + } + field_methods={ + codes?.field_methods.map((item) => { + return { value: item.id, label: item.name, subText: item.description }; + }) || [] + } + ecological_seasons={ + codes?.ecological_seasons.map((item) => { + return { value: item.id, label: item.name, subText: item.description }; + }) || [] + } + vantage_codes={ + codes?.vantage_codes.map((item) => { + return { value: item.id, label: item.name }; + }) || [] + } + /> + }> + + + - - - - Purpose of Survey - -  * - - - - - - - - Purpose of Survey * - - - - - - @@ -314,6 +268,11 @@ exports[`CreateSurveyPage renders correctly when codes and project data are load + + Species + - - - - Survey Methodology - -  * - - - - - - ​ - - - - - - - - - - Survey Methodology - - - - - - - - Study Area + Purpose and Methodology - + + Purpose of Survey + + + + + + Intended Outcomes + +  * + + + + + + + + + + + + Intended Outcomes + + + + + + + + + + + Additional Details + + + + + + + Additional Details + + + + + + + + + + + Survey Methodology + + + + + + Field Method + +  * + + + + + + + + + + + + Field Method + + + + + + + + + + + Ecological Season + +  * + + + + + + + + + + + + Ecological Season + + + + + + + + + + + + Vantage Code + +  * + + + + + + + + + + + + + + + + + + + + + + + + + + Vantage Code * + + + + + + + + + + + + + + + + + Study Area + + + + + + + + Study Area Boundary Is the data captured in this survey proprietary? @@ -1577,20 +1873,20 @@ exports[`CreateSurveyPage renders correctly when codes and project data are load > @@ -1639,10 +1935,10 @@ exports[`CreateSurveyPage renders correctly when codes and project data are load class="MuiDivider-root makeStyles-sectionDivider-12" /> Species and Ecosystems Data and Information Security (SEDIS) Procedures @@ -1684,13 +1980,13 @@ exports[`CreateSurveyPage renders correctly when codes and project data are load > Freedom of Information and Protection of Privacy Act (FOIPPA) Requirements @@ -1746,13 +2042,13 @@ exports[`CreateSurveyPage renders correctly when codes and project data are load > { return yup.object().shape({ survey_name: yup.string().required('Required'), - survey_purpose: yup - .string() - .max(3000, 'Cannot exceed 3000 characters') - .required('You must provide a purpose for the survey'), focal_species: yup.array().min(1, 'You must specify a focal species').required('Required'), ancillary_species: yup.array().isUniqueFocalAncillarySpecies('Focal and Ancillary species must be unique'), biologist_first_name: yup.string().required('Required'), @@ -72,7 +64,6 @@ export interface IGeneralInformationFormProps { species: IMultiAutocompleteFieldOption[]; permit_numbers: IAutocompleteFieldOption[]; funding_sources: IMultiAutocompleteFieldOption[]; - common_survey_methodologies: IAutocompleteFieldOption[]; projectStartDate: string; projectEndDate: string; } @@ -96,7 +87,6 @@ const GeneralInformationForm: React.FC = (props) = onClick={() => { formikProps.setFieldValue('permit_number', ''); formikProps.setFieldValue('permit_type', ''); - setShowAddPermitRow(true); }}> Add Permit @@ -116,13 +106,6 @@ const GeneralInformationForm: React.FC = (props) = }} /> - - - = (props) = )}` } /> + + Species = (props) = required={false} /> - - - Survey Methodology - - {props.common_survey_methodologies.map((item) => ( - - {item.label} - - ))} - - - {formikProps.touched.common_survey_methodology_id && formikProps.errors.common_survey_methodology_id} - - - diff --git a/app/src/features/surveys/components/PurposeAndMethodologyForm.tsx b/app/src/features/surveys/components/PurposeAndMethodologyForm.tsx new file mode 100644 index 0000000000..919b7146df --- /dev/null +++ b/app/src/features/surveys/components/PurposeAndMethodologyForm.tsx @@ -0,0 +1,107 @@ +import Box from '@material-ui/core/Box'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import CustomTextField from 'components/fields/CustomTextField'; +import MultiAutocompleteFieldVariableSize, { + IMultiAutocompleteFieldOption +} from 'components/fields/MultiAutocompleteFieldVariableSize'; +import SelectWithSubtextField, { ISelectWithSubtextFieldOption } from 'components/fields/SelectWithSubtext'; +import React from 'react'; +import yup from 'utils/YupSchema'; + +export interface IPurposeAndMethodologyForm { + intended_outcome_id: number; + additional_details: string; + field_method_id: number; + ecological_season_id: number; + vantage_code_ids: number[]; +} + +export const PurposeAndMethodologyInitialValues: IPurposeAndMethodologyForm = { + intended_outcome_id: ('' as unknown) as number, + additional_details: '', + field_method_id: ('' as unknown) as number, + ecological_season_id: ('' as unknown) as number, + vantage_code_ids: [] +}; + +export const PurposeAndMethodologyYupSchema = yup.object().shape({ + field_method_id: yup.number().required('You must provide a field method'), + additional_details: yup.string(), + intended_outcome_id: yup.number().required('You must provide intended outcomes for the survey'), + ecological_season_id: yup.number().required('You must provide an ecological season for the survey'), + vantage_code_ids: yup.array().min(1, 'You must one or more vantage codes').required('Required') +}); + +export interface IPurposeAndMethodologyFormProps { + intended_outcomes: ISelectWithSubtextFieldOption[]; + field_methods: ISelectWithSubtextFieldOption[]; + ecological_seasons: ISelectWithSubtextFieldOption[]; + vantage_codes: IMultiAutocompleteFieldOption[]; +} + +/** + * Create survey - general information fields + * + * @return {*} + */ +const PurposeAndMethodologyForm: React.FC = (props) => { + return ( + + + Purpose of Survey + + + + + + + + + + + Survey Methodology + + + + + + + + + + + + + + ); +}; + +export default PurposeAndMethodologyForm; diff --git a/app/src/features/surveys/components/__snapshots__/GeneralInformationForm.test.tsx.snap b/app/src/features/surveys/components/__snapshots__/GeneralInformationForm.test.tsx.snap index 563872af9d..3abf702eee 100644 --- a/app/src/features/surveys/components/__snapshots__/GeneralInformationForm.test.tsx.snap +++ b/app/src/features/surveys/components/__snapshots__/GeneralInformationForm.test.tsx.snap @@ -53,52 +53,6 @@ exports[`General Information Form renders correctly the empty component correctl - - - - Purpose of Survey - -  * - - - - - - - - Purpose of Survey * - - - - - - @@ -213,6 +167,11 @@ exports[`General Information Form renders correctly the empty component correctl + + Species + - - - - Survey Methodology - -  * - - - - - - ​ - - - - - - - - - - Survey Methodology - - - - - - - - - - - Purpose of Survey - -  * - - - - - purpose - - - - - Purpose of Survey * - - - - - - @@ -1099,6 +938,11 @@ exports[`General Information Form renders correctly the filled component correct + + Species + - - - - Survey Methodology - -  * - - - - - Recruitment - - - - - - - - - Survey Methodology - - - - - - - - - - - Purpose of Survey - -  * - - - - - purpose - - - - - Purpose of Survey * - - - - - - error on survey purpose field - - - @@ -1997,6 +1716,11 @@ exports[`General Information Form renders correctly when errors exist 1`] = ` + + Species + - - - - Survey Methodology - -  * - - - - - Recruitment - - - - - - - - - Survey Methodology - - - - - - - = (props) => { refresh={refresh} /> + { + + + + } - - - survey purpose - - - - - Survey Methodology - - - method - - @@ -168,7 +145,7 @@ exports[`SurveyDetails renders correctly 1`] = ` + + + + Purpose and Methodology Data + + + + + + + + + + + Edit + + + + + + + + + + + + Intended Outcome + + + Intended Outcome 1 + + + + + Additional Details + + + details + + + + + Field Method + + + Recruitment + + + + + + Ecological Season + + + Season 1 + + + + + Vantage Code + + + Vantage Code 1 + + + Vantage Code 2 + + + + + + + = (prop return { value: item.id, label: item.name }; }) || [] } - common_survey_methodologies={ - codes?.common_survey_methodologies?.map((item) => { - return { value: item.id, label: item.name }; - }) || [] - } permit_numbers={ surveyPermits?.map((item) => { return { value: item.number, label: `${item.number} - ${item.type}` }; @@ -256,9 +251,6 @@ const SurveyGeneralInformation: React.FC = (prop toolbarProps={{ disableGutters: true }} /> - - {survey_details.survey_purpose} - @@ -269,14 +261,6 @@ const SurveyGeneralInformation: React.FC = (prop {survey_details.survey_name} - - - Survey Methodology - - - {survey_details.common_survey_methodology || 'No Survey Methodology'} - - Survey Timeline diff --git a/app/src/features/surveys/view/components/SurveyPurposeAndMethodology.tsx b/app/src/features/surveys/view/components/SurveyPurposeAndMethodology.tsx new file mode 100644 index 0000000000..6f60062239 --- /dev/null +++ b/app/src/features/surveys/view/components/SurveyPurposeAndMethodology.tsx @@ -0,0 +1,266 @@ +import Box from '@material-ui/core/Box'; +import Divider from '@material-ui/core/Divider'; +import Grid from '@material-ui/core/Grid'; +import Typography from '@material-ui/core/Typography'; +import { mdiPencilOutline } from '@mdi/js'; +import Icon from '@mdi/react'; +import EditDialog from 'components/dialog/EditDialog'; +import { ErrorDialog, IErrorDialogProps } from 'components/dialog/ErrorDialog'; +import { H3ButtonToolbar } from 'components/toolbar/ActionToolbars'; +import { EditSurveyPurposeAndMethodologyI18N } from 'constants/i18n'; +import PurposeAndMethodologyForm, { + IPurposeAndMethodologyForm, + PurposeAndMethodologyInitialValues, + PurposeAndMethodologyYupSchema +} from 'features/surveys/components/PurposeAndMethodologyForm'; +import { APIError } from 'hooks/api/useAxios'; +import { useBiohubApi } from 'hooks/useBioHubApi'; +import { IGetAllCodeSetsResponse } from 'interfaces/useCodesApi.interface'; +import { IGetProjectForViewResponse } from 'interfaces/useProjectApi.interface'; +import { + IGetSurveyForUpdateResponsePurposeAndMethodology, + IGetSurveyForViewResponse, + UPDATE_GET_SURVEY_ENTITIES +} from 'interfaces/useSurveyApi.interface'; +import React, { useState } from 'react'; + +export interface ISurveyPurposeAndMethodologyDataProps { + surveyForViewData: IGetSurveyForViewResponse; + codes: IGetAllCodeSetsResponse; + projectForViewData: IGetProjectForViewResponse; + refresh: () => void; +} + +/** + * Purpose and Methodology data content for a survey. + * + * @return {*} + */ +const SurveyPurposeAndMethodologyData: React.FC = (props) => { + const biohubApi = useBiohubApi(); + + const { + projectForViewData, + surveyForViewData: { survey_details, survey_purpose_and_methodology }, + codes, + refresh + } = props; + + const [openEditDialog, setOpenEditDialog] = useState(false); + const [ + surveyPurposeAndMethodologyForUpdate, + setSurveyPurposeAndMethodologyForUpdate + ] = useState(null); + const [purposeAndMethodologyFormData, setPurposeAndMethodologyFormData] = useState( + PurposeAndMethodologyInitialValues + ); + + const [errorDialogProps, setErrorDialogProps] = useState({ + dialogTitle: EditSurveyPurposeAndMethodologyI18N.editErrorTitle, + dialogText: EditSurveyPurposeAndMethodologyI18N.editErrorText, + open: false, + onClose: () => { + setErrorDialogProps({ ...errorDialogProps, open: false }); + }, + onOk: () => { + setErrorDialogProps({ ...errorDialogProps, open: false }); + } + }); + + const showErrorDialog = (textDialogProps?: Partial) => { + setErrorDialogProps({ ...errorDialogProps, ...textDialogProps, open: true }); + }; + + const handleDialogEditOpen = async () => { + if (!survey_purpose_and_methodology) { + setSurveyPurposeAndMethodologyForUpdate(null); + setPurposeAndMethodologyFormData(PurposeAndMethodologyInitialValues); + setOpenEditDialog(true); + return; + } + + let surveyPurposeAndMethodologyResponseData; + + try { + const response = await biohubApi.survey.getSurveyForUpdate(projectForViewData.id, survey_details?.id, [ + UPDATE_GET_SURVEY_ENTITIES.survey_purpose_and_methodology + ]); + + if (!response) { + showErrorDialog({ open: true }); + return; + } + + surveyPurposeAndMethodologyResponseData = response?.survey_purpose_and_methodology || null; + } catch (error) { + const apiError = error as APIError; + showErrorDialog({ dialogText: apiError.message, open: true }); + return; + } + + setSurveyPurposeAndMethodologyForUpdate(surveyPurposeAndMethodologyResponseData); + + setPurposeAndMethodologyFormData({ + intended_outcome_id: + surveyPurposeAndMethodologyResponseData?.intended_outcome_id || + PurposeAndMethodologyInitialValues.intended_outcome_id, + additional_details: + surveyPurposeAndMethodologyResponseData?.additional_details || + PurposeAndMethodologyInitialValues.additional_details, + field_method_id: + surveyPurposeAndMethodologyResponseData?.field_method_id || PurposeAndMethodologyInitialValues.field_method_id, + ecological_season_id: + surveyPurposeAndMethodologyResponseData?.ecological_season_id || + PurposeAndMethodologyInitialValues.ecological_season_id, + vantage_code_ids: + surveyPurposeAndMethodologyResponseData?.vantage_code_ids || PurposeAndMethodologyInitialValues.vantage_code_ids + }); + + setOpenEditDialog(true); + }; + + const handleDialogEditSave = async (values: IPurposeAndMethodologyForm) => { + const surveyData = { + survey_purpose_and_methodology: { + ...values, + id: surveyPurposeAndMethodologyForUpdate?.id, + revision_count: surveyPurposeAndMethodologyForUpdate?.revision_count + } + }; + + try { + await biohubApi.survey.updateSurvey(projectForViewData.id, survey_details.id, surveyData); + } catch (error) { + const apiError = error as APIError; + showErrorDialog({ dialogText: apiError.message, dialogErrorDetails: apiError.errors, open: true }); + return; + } finally { + setOpenEditDialog(false); + } + + refresh(); + }; + + return ( + <> + { + return { value: item.id, label: item.name, subText: item.description }; + }) || [] + } + field_methods={ + codes?.field_methods?.map((item) => { + return { value: item.id, label: item.name, subText: item.description }; + }) || [] + } + ecological_seasons={ + codes?.ecological_seasons?.map((item) => { + return { value: item.id, label: item.name, subText: item.description }; + }) || [] + } + vantage_codes={ + codes?.vantage_codes?.map((item) => { + return { value: item.id, label: item.name }; + }) || [] + } + /> + ), + initialValues: purposeAndMethodologyFormData, + validationSchema: PurposeAndMethodologyYupSchema + }} + onCancel={() => setOpenEditDialog(false)} + onSave={handleDialogEditSave} + /> + + + } + buttonOnClick={() => handleDialogEditOpen()} + toolbarProps={{ disableGutters: true }} + /> + + + {!survey_purpose_and_methodology && ( + + + + The data captured in this survey does not have the purpose and methodology section. + + + + )} + {survey_purpose_and_methodology && ( + + + + Intended Outcome + + + {survey_purpose_and_methodology.intended_outcome_id && + codes?.intended_outcomes?.find( + (item: any) => item.id === survey_purpose_and_methodology.intended_outcome_id + )?.name} + + + + + Additional Details + + + {survey_purpose_and_methodology.additional_details} + + + + + Field Method + + + + {survey_purpose_and_methodology.field_method_id && + codes?.field_methods?.find( + (item: any) => item.id === survey_purpose_and_methodology.field_method_id + )?.name} + + + + + + Ecological Season + + + {survey_purpose_and_methodology.ecological_season_id && + codes?.ecological_seasons?.find( + (item: any) => item.id === survey_purpose_and_methodology.ecological_season_id + )?.name} + + + + + Vantage Code + + {survey_purpose_and_methodology.vantage_code_ids?.map((vc_id: number, index: number) => { + return ( + + {codes?.vantage_codes?.find((item: any) => item.id === vc_id)?.name} + + ); + })} + + + )} + + + > + ); +}; + +export default SurveyPurposeAndMethodologyData; diff --git a/app/src/features/surveys/view/components/__snapshots__/SurveyGeneralInformation.test.tsx.snap b/app/src/features/surveys/view/components/__snapshots__/SurveyGeneralInformation.test.tsx.snap index dd03647c33..ed5cd77cc0 100644 --- a/app/src/features/surveys/view/components/__snapshots__/SurveyGeneralInformation.test.tsx.snap +++ b/app/src/features/surveys/view/components/__snapshots__/SurveyGeneralInformation.test.tsx.snap @@ -3,7 +3,7 @@ exports[`SurveyGeneralInformation renders correctly with all fields 1`] = ` - - - survey purpose - - - - - Survey Methodology - - - method - - @@ -157,7 +134,7 @@ exports[`SurveyGeneralInformation renders correctly with all fields 1`] = ` - - - survey purpose - - - - - Survey Methodology - - - method - - @@ -526,7 +480,7 @@ exports[`SurveyGeneralInformation renders correctly with no end date (only start ; + intended_outcomes: CodeSet<{ id: number; name: string; description: string }>; + ecological_seasons: CodeSet<{ id: number; name: string; description: string }>; + vantage_codes: CodeSet; } diff --git a/app/src/interfaces/useSurveyApi.interface.ts b/app/src/interfaces/useSurveyApi.interface.ts index 2863ee98e2..8a4899c4cb 100644 --- a/app/src/interfaces/useSurveyApi.interface.ts +++ b/app/src/interfaces/useSurveyApi.interface.ts @@ -24,7 +24,11 @@ export interface ICreateSurveyRequest { survey_area_name: string; survey_data_proprietary: string; survey_name: string; - survey_purpose: string; + intended_outcome_id: number; + additional_details: string; + field_method_id: number; + ecological_season_id: number; + vantage_id: number; geometry: Feature[]; permit_number: string; } @@ -50,10 +54,8 @@ export interface ISurveyFundingSourceForView { export interface IGetSurveyForViewResponseDetails { id: number; survey_name: string; - survey_purpose: string; focal_species: string[]; ancillary_species: string[]; - common_survey_methodology: string; start_date: string; end_date: string; biologist_first_name: string; @@ -68,6 +70,15 @@ export interface IGetSurveyForViewResponseDetails { occurrence_submission_id: number | null; } +export interface IGetSurveyForViewResponsePurposeAndMethodology { + id: number; + intended_outcome_id: number; + additional_details: string; + field_method_id: number; + ecological_season_id: number; + vantage_code_ids: number[]; +} + export interface IGetSurveyForViewResponseProprietor { id: number; proprietary_data_category_name: string; @@ -80,10 +91,8 @@ export interface IGetSurveyForViewResponseProprietor { export interface IGetSurveyForUpdateResponseDetails { id: number; survey_name: string; - survey_purpose: string; focal_species: number[]; ancillary_species: number[]; - common_survey_methodology_id: number; start_date: string; end_date: string; biologist_first_name: string; @@ -96,6 +105,16 @@ export interface IGetSurveyForUpdateResponseDetails { funding_sources: number[]; } +export interface IGetSurveyForUpdateResponsePurposeAndMethodology { + id?: number; + intended_outcome_id: number; + additional_details: string; + field_method_id: number; + ecological_season_id: number; + vantage_code_ids: number[]; + revision_count?: number; +} + export interface IGetSurveyForUpdateResponseProprietor { id?: number; proprietary_data_category_name?: string; @@ -117,6 +136,7 @@ export interface IGetSurveyForUpdateResponseProprietor { */ export interface IGetSurveyForUpdateResponse { survey_details?: IGetSurveyForUpdateResponseDetails; + survey_purpose_and_methodology?: IGetSurveyForUpdateResponsePurposeAndMethodology | null; survey_proprietor?: IGetSurveyForUpdateResponseProprietor | null; } @@ -128,6 +148,7 @@ export interface IGetSurveyForUpdateResponse { */ export interface IGetSurveyForViewResponse { survey_details: IGetSurveyForViewResponseDetails; + survey_purpose_and_methodology: IGetSurveyForViewResponsePurposeAndMethodology; survey_proprietor: IGetSurveyForViewResponseProprietor; } @@ -158,6 +179,7 @@ export interface IGetSurveysListResponse { export enum UPDATE_GET_SURVEY_ENTITIES { survey_details = 'survey_details', + survey_purpose_and_methodology = 'survey_purpose_and_methodology', survey_proprietor = 'survey_proprietor' } diff --git a/app/src/test-helpers/code-helpers.ts b/app/src/test-helpers/code-helpers.ts index 429600e297..823d2ea91d 100644 --- a/app/src/test-helpers/code-helpers.ts +++ b/app/src/test-helpers/code-helpers.ts @@ -40,8 +40,20 @@ export const codes: IGetAllCodeSetsResponse = { { id: 2, name: 'Actioned' }, { id: 3, name: 'Rejected' } ], - common_survey_methodologies: [ - { id: 1, name: 'Recruitment' }, - { id: 2, name: 'SRB' } + field_methods: [ + { id: 1, name: 'Recruitment', description: 'Description' }, + { id: 2, name: 'SRB', description: 'Description' } + ], + ecological_seasons: [ + { id: 1, name: 'Season 1', description: 'Description' }, + { id: 2, name: 'Season 2', description: 'Description' } + ], + vantage_codes: [ + { id: 1, name: 'Vantage Code 1' }, + { id: 2, name: 'Vantage Code 2' } + ], + intended_outcomes: [ + { id: 1, name: 'Intended Outcome 1', description: 'Description 1' }, + { id: 2, name: 'Intended Outcome 2', description: 'Description 2' } ] }; diff --git a/app/src/test-helpers/survey-helpers.ts b/app/src/test-helpers/survey-helpers.ts index ee454c0846..d900d472e3 100644 --- a/app/src/test-helpers/survey-helpers.ts +++ b/app/src/test-helpers/survey-helpers.ts @@ -5,10 +5,8 @@ export const getSurveyForViewResponse: IGetSurveyForViewResponse = { id: 1, occurrence_submission_id: null, survey_name: 'survey name', - survey_purpose: 'survey purpose', focal_species: ['focal species 1'], ancillary_species: ['ancillary species 1'], - common_survey_methodology: 'method', start_date: '1998-10-10', end_date: '2021-02-26', biologist_first_name: 'first', @@ -49,6 +47,14 @@ export const getSurveyForViewResponse: IGetSurveyForViewResponse = { completion_status: 'Active', publish_date: (null as unknown) as string }, + survey_purpose_and_methodology: { + id: 1, + intended_outcome_id: 1, + additional_details: 'details', + field_method_id: 1, + ecological_season_id: 1, + vantage_code_ids: [1, 2] + }, survey_proprietor: { id: 23, proprietary_data_category_name: 'proprietor type', diff --git a/app/src/themes/appTheme.ts b/app/src/themes/appTheme.ts index 13fc3a8117..522dbaf003 100644 --- a/app/src/themes/appTheme.ts +++ b/app/src/themes/appTheme.ts @@ -54,11 +54,6 @@ const appTheme = createMuiTheme({ h6: { letterSpacing: '-0.01rem', fontWeight: 700 - }, - body1: { - '& + p': { - marginTop: '1rem' - } } }, MuiAlert: { diff --git a/database/README.md b/database/README.md index 6c38659506..b50894b5fb 100644 --- a/database/README.md +++ b/database/README.md @@ -4,7 +4,7 @@ | Technology | Version | Website | Description | | ---------- | ------- | ------------------------------------ | -------------------- | -| node | 10.x.x | https://nodejs.org/en/ | JavaScript Runtime | +| node | 14.x.x | https://nodejs.org/en/ | JavaScript Runtime | | npm | 6.x.x | https://www.npmjs.com/ | Node Package Manager | | PostgreSQL | 12.5 | https://www.postgresql.org/download/ | PSQL database | | PostGIS | 3 | https://postgis.net/ | GIS (spatial) tools | diff --git a/database/src/migrations/20220404093900_SIMS.1.1.0.ts b/database/src/migrations/20220404093900_SIMS.1.1.0.ts new file mode 100644 index 0000000000..1002a95d51 --- /dev/null +++ b/database/src/migrations/20220404093900_SIMS.1.1.0.ts @@ -0,0 +1,452 @@ +import { Knex } from 'knex'; + +/** + * Apply biohub release changes. + * + * @export + * @param {Knex} knex + * @return {*} {Promise} + */ +export async function up(knex: Knex): Promise { + await knex.raw(` + set search_path = biohub_dapi_v1; + + drop view survey; + drop view common_survey_methodology; + drop view template_methodology_species; + + + set search_path = biohub, public; + + CREATE TABLE ecological_season( + ecological_season_id integer GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1), + name varchar(50) NOT NULL, + record_effective_date date NOT NULL, + description varchar(3000), + record_end_date date, + create_date timestamptz(6) DEFAULT now() NOT NULL, + create_user integer NOT NULL, + update_date timestamptz(6), + update_user integer, + revision_count integer DEFAULT 0 NOT NULL, + CONSTRAINT ecological_season_pk PRIMARY KEY (ecological_season_id) + ) + ; + + + + COMMENT ON COLUMN ecological_season.ecological_season_id IS 'System generated surrogate primary key identifier.' + ; + COMMENT ON COLUMN ecological_season.name IS 'The name of the project role.' + ; + COMMENT ON COLUMN ecological_season.record_effective_date IS 'Record level effective date.' + ; + COMMENT ON COLUMN ecological_season.description IS 'The description of the project type.' + ; + COMMENT ON COLUMN ecological_season.record_end_date IS 'Record level end date.' + ; + COMMENT ON COLUMN ecological_season.create_date IS 'The datetime the record was created.' + ; + COMMENT ON COLUMN ecological_season.create_user IS 'The id of the user who created the record as identified in the system user table.' + ; + COMMENT ON COLUMN ecological_season.update_date IS 'The datetime the record was updated.' + ; + COMMENT ON COLUMN ecological_season.update_user IS 'The id of the user who updated the record as identified in the system user table.' + ; + COMMENT ON COLUMN ecological_season.revision_count IS 'Revision count used for concurrency control.' + ; + COMMENT ON TABLE ecological_season IS 'Broad classification for the ecological season of a survey.' + ; + + -- + -- TABLE: intended_outcome + -- + + CREATE TABLE intended_outcome( + intended_outcome_id integer GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1), + name varchar(50) NOT NULL, + record_effective_date date NOT NULL, + description varchar(3000), + record_end_date date, + create_date timestamptz(6) DEFAULT now() NOT NULL, + create_user integer NOT NULL, + update_date timestamptz(6), + update_user integer, + revision_count integer DEFAULT 0 NOT NULL, + CONSTRAINT intended_outcome_pk PRIMARY KEY (intended_outcome_id) + ) + ; + + + + COMMENT ON COLUMN intended_outcome.intended_outcome_id IS 'System generated surrogate primary key identifier.' + ; + COMMENT ON COLUMN intended_outcome.name IS 'The name of the project role.' + ; + COMMENT ON COLUMN intended_outcome.record_effective_date IS 'Record level effective date.' + ; + COMMENT ON COLUMN intended_outcome.description IS 'The description of the project type.' + ; + COMMENT ON COLUMN intended_outcome.record_end_date IS 'Record level end date.' + ; + COMMENT ON COLUMN intended_outcome.create_date IS 'The datetime the record was created.' + ; + COMMENT ON COLUMN intended_outcome.create_user IS 'The id of the user who created the record as identified in the system user table.' + ; + COMMENT ON COLUMN intended_outcome.update_date IS 'The datetime the record was updated.' + ; + COMMENT ON COLUMN intended_outcome.update_user IS 'The id of the user who updated the record as identified in the system user table.' + ; + COMMENT ON COLUMN intended_outcome.revision_count IS 'Revision count used for concurrency control.' + ; + COMMENT ON TABLE intended_outcome IS 'Broad classification of intended outcomes of the survey work.' + ; + + -- + -- TABLE: vantage + -- + + CREATE TABLE vantage( + vantage_id integer GENERATED ALWAYS AS IDENTITY (START WITH 1 INCREMENT BY 1), + name varchar(50) NOT NULL, + record_effective_date date NOT NULL, + description varchar(250), + record_end_date date, + create_date timestamptz(6) DEFAULT now() NOT NULL, + create_user integer NOT NULL, + update_date timestamptz(6), + update_user integer, + revision_count integer DEFAULT 0 NOT NULL, + CONSTRAINT vantage_pk PRIMARY KEY (vantage_id) + ) + ; + + + + COMMENT ON COLUMN vantage.vantage_id IS 'System generated surrogate primary key identifier.' + ; + COMMENT ON COLUMN vantage.name IS 'The name of the project role.' + ; + COMMENT ON COLUMN vantage.record_effective_date IS 'Record level effective date.' + ; + COMMENT ON COLUMN vantage.description IS 'The description of the project type.' + ; + COMMENT ON COLUMN vantage.record_end_date IS 'Record level end date.' + ; + COMMENT ON COLUMN vantage.create_date IS 'The datetime the record was created.' + ; + COMMENT ON COLUMN vantage.create_user IS 'The id of the user who created the record as identified in the system user table.' + ; + COMMENT ON COLUMN vantage.update_date IS 'The datetime the record was updated.' + ; + COMMENT ON COLUMN vantage.update_user IS 'The id of the user who updated the record as identified in the system user table.' + ; + COMMENT ON COLUMN vantage.revision_count IS 'Revision count used for concurrency control.' + ; + COMMENT ON TABLE vantage IS 'Broad classification for the vantage code of the survey.' + ; + + CREATE TABLE survey_vantage + ( + survey_vantage_id integer NOT NULL GENERATED ALWAYS AS IDENTITY + (START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + NO CYCLE), + survey_id integer NOT NULL, + vantage_id integer NOT NULL, + create_date timestamptz(6) NOT NULL DEFAULT now(), + create_user integer NOT NULL, + update_date timestamptz(6), + update_user integer, + revision_count integer NOT NULL DEFAULT 0, + CONSTRAINT survey_vantage_pk PRIMARY KEY (survey_vantage_id) + ) + WITH ( + OIDS=false + ); + + CREATE UNIQUE INDEX survey_vantage_uk1 ON survey_vantage + (survey_id, + vantage_id) + + ; + + CREATE INDEX "Ref153215" ON survey_vantage + (survey_id) + + ; + + CREATE INDEX "Ref222216" ON survey_vantage + (vantage_id) + + ; + + -- Add Referencing Foreign Keys SQL + + + ALTER TABLE survey_vantage + ADD + FOREIGN KEY (vantage_id) + REFERENCES vantage (vantage_id) + MATCH SIMPLE + ON DELETE NO ACTION + ON UPDATE NO ACTION + NOT VALID + ; + + ALTER TABLE survey_vantage + ADD + FOREIGN KEY (survey_id) + REFERENCES survey (survey_id) + MATCH SIMPLE + ON DELETE NO ACTION + ON UPDATE NO ACTION + NOT VALID + ; + + COMMENT ON TABLE survey_vantage IS 'An associative entity that joins surveys and vantage codes.'; + + COMMENT ON COLUMN survey_vantage.survey_vantage_id IS 'System generated surrogate primary key identifier.'; + + COMMENT ON COLUMN survey_vantage.survey_id IS 'System generated surrogate primary key identifier.'; + + COMMENT ON COLUMN survey_vantage.vantage_id IS 'System generated surrogate primary key identifier.'; + + COMMENT ON COLUMN survey_vantage.create_date IS 'The datetime the record was created.'; + + COMMENT ON COLUMN survey_vantage.create_user IS 'The id of the user who created the record as identified in the system user table.'; + + COMMENT ON COLUMN survey_vantage.update_date IS 'The datetime the record was updated.'; + + COMMENT ON COLUMN survey_vantage.update_user IS 'The id of the user who updated the record as identified in the system user table.'; + + COMMENT ON COLUMN survey_vantage.revision_count IS 'Revision count used for concurrency control.'; + + + CREATE UNIQUE INDEX ecological_season_nuk1 ON ecological_season(name, (record_end_date is NULL)) where record_end_date is null + ; + + CREATE UNIQUE INDEX vantage_nuk1 ON vantage(name, (record_end_date is NULL)) where record_end_date is null + ; + + CREATE UNIQUE INDEX intended_outcome_nuk1 ON intended_outcome(name, (record_end_date is NULL)) where record_end_date is null + ; + + alter table survey + add column ecological_season_id integer, + add column intended_outcome_id integer + ; + + COMMENT ON COLUMN survey.ecological_season_id IS 'System generated surrogate primary key identifier.' + ; + COMMENT ON COLUMN survey.intended_outcome_id IS 'System generated surrogate primary key identifier.' + ; + + ALTER TABLE survey ADD CONSTRAINT "Refecological_season212" + FOREIGN KEY (ecological_season_id) + REFERENCES ecological_season(ecological_season_id) + ; + + ALTER TABLE survey ADD CONSTRAINT "Refintended_outcome211" + FOREIGN KEY (intended_outcome_id) + REFERENCES intended_outcome(intended_outcome_id) + ; + + CREATE INDEX "Ref223211" ON survey(intended_outcome_id) + ; + + CREATE INDEX "Ref220212" ON survey(ecological_season_id) + ; + + alter table common_survey_methodology + rename to field_method + ; + + alter table field_method + rename column common_survey_methodology_id to field_method_id + ; + + alter table survey + rename column common_survey_methodology_id to field_method_id + ; + + ALTER TABLE survey drop CONSTRAINT "Refcommon_survey_methodology190"; + + ALTER TABLE survey ADD CONSTRAINT "Reffield_method190" FOREIGN KEY (field_method_id) REFERENCES field_method(field_method_id); + ALTER TABLE survey ADD CONSTRAINT "Refecological_season190" FOREIGN KEY (ecological_season_id) REFERENCES ecological_season(ecological_season_id); + ALTER TABLE survey ADD CONSTRAINT "Refintended_outcome190" FOREIGN KEY (intended_outcome_id) REFERENCES intended_outcome(intended_outcome_id); + + alter table survey + rename column objectives to additional_details + ; + + alter table survey + alter column additional_details drop not null + ; + + alter trigger audit_common_survey_methodology on field_method rename to audit_field_method; + alter trigger journal_common_survey_methodology on field_method rename to journal_field_method; + + create trigger journal_intended_outcome after + insert or delete or update on biohub.intended_outcome for each row execute function biohub.tr_journal_trigger(); + + create trigger journal_ecological_season after + insert or delete or update on biohub.ecological_season for each row execute function biohub.tr_journal_trigger(); + + create trigger journal_vantage after + insert or delete or update on biohub.vantage for each row execute function biohub.tr_journal_trigger(); + + create trigger journal_survey_vantage after + insert or delete or update on biohub.survey_vantage for each row execute function biohub.tr_journal_trigger(); + + create trigger audit_intended_outcome before + insert or delete or update on biohub.intended_outcome for each row execute function biohub.tr_audit_trigger(); + + create trigger audit_ecological_season before + insert or delete or update on biohub.ecological_season for each row execute function biohub.tr_audit_trigger(); + + create trigger audit_vantage before + insert or delete or update on biohub.vantage for each row execute function biohub.tr_audit_trigger(); + + create trigger audit_survey_vantage before + insert or delete or update on biohub.survey_vantage for each row execute function biohub.tr_audit_trigger(); + + alter table template_methodology_species rename Constraint "PK192" to template_methodology_species_pk; + + alter table template_methodology_species rename column common_survey_methodology_id to field_method_id; + + alter table template_methodology_species add column intended_outcome_id integer; + + COMMENT ON COLUMN template_methodology_species.intended_outcome_id IS 'System generated surrogate primary key identifier.'; + + ALTER TABLE template_methodology_species ADD CONSTRAINT "Refintended_outcome217" + FOREIGN KEY (intended_outcome_id) + REFERENCES intended_outcome(intended_outcome_id); + + CREATE INDEX "Ref223217" ON template_methodology_species(intended_outcome_id); + + ALTER TABLE template_methodology_species drop column wldtaxonomic_units_id; + + -- api_delete_survey.sql + drop procedure if exists api_delete_survey; + + create or replace procedure api_delete_survey(p_survey_id survey.survey_id%type) + language plpgsql + security definer + as + $$ + -- ******************************************************************* + -- Procedure: api_delete_survey + -- Purpose: deletes a survey and dependencies + -- + -- MODIFICATION HISTORY + -- Person Date Comments + -- ---------------- ----------- -------------------------------------- + -- shreyas.devalapurkar@quartech.com + -- 2021-06-18 initial release + -- charlie.garrettjones@quartech.com + -- 2021-06-21 added occurrence submission delete + -- charlie.garrettjones@quartech.com + -- 2021-09-21 added survey summary submission delete + -- charlie.garrettjones@quartech.com + -- 2022-04-06 added survey_vantage + -- ******************************************************************* + declare + _occurrence_submission_id occurrence_submission.occurrence_submission_id%type; + begin + for _occurrence_submission_id in (select occurrence_submission_id from occurrence_submission where survey_id = p_survey_id) loop + call api_delete_occurrence_submission(_occurrence_submission_id); + end loop; + + delete from survey_vantage where survey_id = p_survey_id; + delete from survey_summary_submission_message where survey_summary_submission_id in (select survey_summary_submission_id from survey_summary_submission where survey_id = p_survey_id); + delete from survey_summary_detail where survey_summary_submission_id in (select survey_summary_submission_id from survey_summary_submission where survey_id = p_survey_id); + delete from survey_summary_submission where survey_id = p_survey_id; + delete from survey_proprietor where survey_id = p_survey_id; + delete from survey_attachment where survey_id = p_survey_id; + delete from survey_report_author where survey_report_attachment_id in (select survey_report_attachment_id from survey_report_attachment where survey_id = p_survey_id); + delete from survey_report_attachment where survey_id = p_survey_id; + delete from study_species where survey_id = p_survey_id; + delete from survey_funding_source where survey_id = p_survey_id; + + update permit set survey_id = null where survey_id = p_survey_id; + + exception + when others THEN + raise; + end; + $$; + + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Habitat Assessment', 'To assess habitat for its value to wildlife and to record evidence of its usage by wildlife.'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Reconnaissance', 'To provide information for planning another Survey or to informally determine species presence.'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Recruitment', 'To count or obtain an index of the number of new individuals (e.g., young) that have been added to the population between 2 points in time. For example, a caribou recruitment Survey counts young animals after winter; the young are considered established and contributing to the population.'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Population Composition', 'To count or obtain an index of the number of individuals in a population belonging to particular age or sex categories. E.g., bull:cow ratio for moose.'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Community Composition', 'To determine the numbers or proportions of species in an ecological community or geographic area. E.g., relative ground-cover by plant species, relative density of birds of each species in a forest.'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Population Count', 'To obtain a number that indicates the number of individuals in an area. A population count may be obtained by enumerating every individual in a population (e.g., by doing a census) or by sampling a portion of the population (e.g., stratified random block design) and then adjusting the observed number to estimate the population size.'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Population Count & Recruitment', 'To obtain a number that indicates the number of individuals in an area (population count) AND to count or obtain an index of the number of new individuals (e.g., young) that have been added to the population between 2 points in time (recruitment).'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Population Count & Composition', 'To obtain a number that indicates the number of individuals in an area (population count) AND to count or obtain an index of the number of individuals in a population belonging to particular age or sex categories (composition).'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Population Index', 'To obtain a population index. For example, to obtain a relative abundance index by calculating the number of tracks detected per kilometre of transect, or number of detections per hour of surveying.'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Mortality', 'To count or obtain an index of the number and conditions of dead individuals, and/or the causes of death.'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Survival', 'To count or obtain an index of the number of individuals in a population that have survived a period between 2 points in time.'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Specimen Collection', 'To collect sample specimens of a species or taxon.'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Translocation', 'To move individuals from one location to another.'); + insert into intended_outcome(record_effective_date, name, description) values (now(), 'Distribution or Range Map', 'To determine the manner in which a species (or population or taxon) is spatially arranged, or to define the geographic limits of the species.'); + + update field_method set description = 'A sampling technique in which a population is divided into discrete units called strata based on similar attributes. The researcher selects a small sample size with similar characteristics to represent a population group under study.' + where name = 'Stratified Random Block'; + + update field_method set record_end_date = now() + where name in ('Composition', 'Recruitment'); + + insert into field_method(record_effective_date, name, description) values (now(), 'Total Count', 'They are intended to enumerate all animals using 100% flight coverage of the study area. Alpine areas are usually small, and thus the technique is practical for surveying mountain sheep and goats, and sometimes caribou.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Encounter Transects', 'The recommended protocol for conducting herd composition surveys are encounter transects. Occasionally, however, sample blocks may be selected. The purpose of these surveys is to provide information on population composition and recruitment. Encounter transects can be flown by either fixed-wing aircraft or helicopter, and all visible animals are counted and classified. Encounter transects may follow predetermined straight lines, contours, or drainages. When classification is conducted, it will normally be necessary to deviate from the transect line to ensure accurate classification of the animals. Following the classification, the pilot should be instructed to resume the transect line. Ground transects are often secondary roads or trails.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Fixed-width Transects', 'When using fixed-width transects only animals within a defined survey area (strip) are counted. Fixed-widths can be defined by marks on the airplane struts, or by placing a board across the helicopter skids and calibrating that with a mark on the bubble window.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Wildlife Camera', 'To use a camera to record individuals or species in the absence of an observer.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Track Count', 'To count the number of tracks of a species or group of species.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Spotlight Count', 'To use a spotlight to see and identify or count the number of individuals.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Classification Transects/Blocks', 'The recommended protocol for conducting herd composition surveys are encounter transects. Occasionally, however, sample blocks may be selected. The purpose of these surveys is to provide information on population composition and recruitment.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Pellet/Scat Count', 'To count the number of pellet and/or scat groups of a species or group of species.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Call Playback', 'To play prerecorded calls of species and listen for responses.'); + insert into field_method(record_effective_date, name, description) values (now(), 'DNA - Individual', 'To obtain DNA samples from individuals.'); + insert into field_method(record_effective_date, name, description) values (now(), 'DNA - Environmental', 'To obtain environmental DNA.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Mark Resight Recapture', 'To mark and subsequently resight or recapture individuals.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Mark Resight Recapture - Wildlife Camera', 'To mark and subsequently resight or recapture individuals by use of a camera to record individuals or species in the absence of an observer.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Mark Resight Recapture - Spotlight Count', 'To mark and subsequently resight or recapture individuals by use of a spotlight to see and identify or count the number of individuals.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Mark Resight Recapture - DNA - Individual', 'To mark and subsequently resight or recapture individuals by obtaining DNA samples from individuals.'); + insert into field_method(record_effective_date, name, description) values (now(), 'Described in Comments', 'The field method is described in the comments field of the Survey. Note: Describing the data in comments rather than using a predefined code may reduce the clarity and accessibility of data.'); + + insert into ecological_season(record_effective_date, name, description) values (now(), 'Spring', ''); + insert into ecological_season(record_effective_date, name, description) values (now(), 'Summer', ''); + insert into ecological_season(record_effective_date, name, description) values (now(), 'Fall', ''); + insert into ecological_season(record_effective_date, name, description) values (now(), 'Winter', ''); + insert into ecological_season(record_effective_date, name, description) values (now(), 'Early Spring', ''); + insert into ecological_season(record_effective_date, name, description) values (now(), 'Growing', 'The season of growth for a species; often includes all or portions of Spring, Summer, and Fall.'); + insert into ecological_season(record_effective_date, name, description) values (now(), 'Early Winter', ''); + insert into ecological_season(record_effective_date, name, description) values (now(), 'Mid Winter', ''); + insert into ecological_season(record_effective_date, name, description) values (now(), 'Late Winter', ''); + insert into ecological_season(record_effective_date, name, description) values (now(), 'Rutting', 'The courtship and copulation period of mammals, typically large mammals.'); + insert into ecological_season(record_effective_date, name, description) values (now(), 'Breeding', 'The term "breeding season" typically applies to species, such as birds and some insects and some rodents, in which courtship and/or copulation is followed (within hours, days, or weeks) by hatching or birthing of young. In contrast, large mammals do not have a "breeding season" because they tend to have long gestation periods in which the birthing period is far removed from courtship and copulation.'); + insert into ecological_season(record_effective_date, name, description) values (now(), 'Post Birthing/Calving', 'The period after a species within a Study Area has finished giving birth to young, and the young are still closely associated with their parent(s) . For large mammals this period may start weeks after birthing, and extend for several weeks.'); + + insert into vantage(record_effective_date, name) values (now(), 'Aerial'); + insert into vantage(record_effective_date, name) values (now(), 'Walking'); + insert into vantage(record_effective_date, name) values (now(), 'Vehicle'); + insert into vantage(record_effective_date, name) values (now(), 'Boat'); + + set search_path = biohub_dapi_v1; + + create view ecological_season as select * from biohub.ecological_season; + create view intended_outcome as select * from biohub.intended_outcome; + create view vantage as select * from biohub.vantage; + create view survey_vantage as select * from biohub.survey_vantage; + create view survey as select * from biohub.survey; + create view field_method as select * from biohub.field_method; + create view template_methodology_species as select * from biohub.template_methodology_species; + `); +} + +export async function down(knex: Knex): Promise { + await knex.raw(``); +} diff --git a/database/src/migrations/release.0.34/smoketest_release.sql b/database/src/migrations/smoke_tests/smoketest_release.1.0.0.sql similarity index 99% rename from database/src/migrations/release.0.34/smoketest_release.sql rename to database/src/migrations/smoke_tests/smoketest_release.1.0.0.sql index 9fdeb3d45d..193c2ba196 100644 --- a/database/src/migrations/release.0.34/smoketest_release.sql +++ b/database/src/migrations/smoke_tests/smoketest_release.1.0.0.sql @@ -1,4 +1,3 @@ --- smoketest_release.sql -- run as db super user \c biohub set role postgres; diff --git a/database/src/migrations/smoke_tests/smoketest_release.1.1.0.sql b/database/src/migrations/smoke_tests/smoketest_release.1.1.0.sql new file mode 100644 index 0000000000..f671ce52fe --- /dev/null +++ b/database/src/migrations/smoke_tests/smoketest_release.1.1.0.sql @@ -0,0 +1,241 @@ +-- run as db super user +\c biohub +set role postgres; +set search_path=biohub; + +do $$ +declare + _count integer = 0; + _system_user system_user%rowtype; + _system_user_id system_user.system_user_id%type; +begin + select * into _system_user from system_user where user_identifier = 'myIDIR'; + if _system_user.system_user_id is not null then + delete from permit where system_user_id = _system_user.system_user_id; + delete from administrative_activity where reported_system_user_id = _system_user.system_user_id; + delete from administrative_activity where assigned_system_user_id = _system_user.system_user_id; + delete from system_user_role where system_user_id = _system_user.system_user_id; + delete from system_user where system_user_id = _system_user.system_user_id; + end if; + + insert into system_user (user_identity_source_id, user_identifier, record_effective_date) values ((select user_identity_source_id from user_identity_source where name = 'IDIR' and record_end_date is null), 'myIDIR', now()) returning system_user_id into _system_user_id; + insert into system_user_role (system_user_id, system_role_id) values (_system_user_id, (select system_role_id from system_role where name = 'System Administrator')); + + select count(1) into _count from system_user; + assert _count > 1, 'FAIL system_user'; + select count(1) into _count from audit_log; + assert _count > 1, 'FAIL audit_log'; + + -- drop security context for subsequent roles to instantiate + drop table biohub_context_temp; + + raise notice 'smoketest_release(1): PASS'; +end +$$; + +set role biohub_api; +set search_path to biohub_dapi_v1, biohub, public, topology; +do $$ +declare + _project_id project.project_id%type; + _survey_id survey.survey_id%type; + _count integer = 0; + _system_user_id system_user.system_user_id%type; + _study_species_id study_species.study_species_id%type; + _occurrence_submission_id occurrence_submission.occurrence_submission_id%type; + _submission_status_id submission_status.submission_status_id%type; + _survey_status_query text := 'select project_id, survey_id, survey_status from survey_status'; + _survey_status_rec survey_status%rowtype; + _geography project.geography%type; + _project_funding_source_id project_funding_source.project_funding_source_id%type; + _project_report_attachment_id project_report_attachment.project_report_attachment_id%type; + _survey_report_attachment_id survey_report_attachment.survey_report_attachment_id%type; +begin + -- set security context + select api_set_context('myIDIR', 'IDIR') into _system_user_id; + --select api_set_context('biohub_api', 'DATABASE') into _system_user_id; + + select st_GeomFromEWKT('SRID=4326;POLYGON((-123.920288 48.592142,-123.667603 48.645205,-123.539886 48.536204,-123.583832 48.46978,-123.728027 48.460674,-123.868103 48.467959,-123.940887 48.5262,-123.920288 48.592142), (-103.920288 38.592142,-103.667603 38.645205,-103.539886 38.536204,-103.583832 38.46978,-103.728027 38.460674,-103.868103 38.467959,-103.940887 38.5262,-103.920288 38.592142))') into _geography; + + insert into project (project_type_id + , name + , objectives + , start_date + , end_date + , coordinator_first_name + , coordinator_last_name + , coordinator_email_address + , coordinator_agency_name + , coordinator_public + , geography + ) values ((select project_type_id from project_type where name = 'Wildlife') + , 'project 10' + , 'my objectives' + , now() + , now()+interval '1 day' + , 'coordinator_first_name' + , 'coordinator_last_name' + , 'coordinator_email_address@nowhere.com' + , 'coordinator_agency_name' + , TRUE + , _geography + ) returning project_id into _project_id; + + insert into stakeholder_partnership (project_id, name) values (_project_id, 'test'); + insert into project_activity (project_id, activity_id) values (_project_id, (select activity_id from activity where name = 'Monitoring')); + insert into project_climate_initiative (project_id, climate_change_initiative_id) values (_project_id, (select climate_change_initiative_id from climate_change_initiative where name = 'Monitoring')); + insert into project_management_actions (project_id, management_action_type_id) values (_project_id, (select management_action_type_id from management_action_type where name = 'Recovery Action')); + insert into project_funding_source (project_id, investment_action_category_id, funding_amount, funding_start_date, funding_end_date, funding_source_project_id) values (_project_id, (select investment_action_category_id from investment_action_category where name = 'Action 1'), '$1,000.00', now(), now(), 'test') returning project_funding_source_id into _project_funding_source_id; + --insert into project_funding_source (project_id, investment_action_category_id, funding_amount, funding_start_date, funding_end_date) values (_project_id, 43, '$1,000.00', now(), now()); + insert into project_iucn_action_classification (project_id, iucn_conservation_action_level_3_subclassification_id) values (_project_id, (select iucn_conservation_action_level_3_subclassification_id from iucn_conservation_action_level_3_subclassification where name = 'Primary Education')); + insert into project_attachment (project_id, file_name, title, key, file_size, file_type) values (_project_id, 'test_filename.txt', 'test filename', 'projects/'||_project_id::text, 10000, 'video'); + insert into project_report_attachment (project_id, file_name, title, key, file_size, year, description) values (_project_id, 'test_filename.txt', 'test filename', 'projects/'||_project_id::text, 10000, '2021', 'example abstract') returning project_report_attachment_id into _project_report_attachment_id; + insert into project_report_author (project_report_attachment_id, first_name, last_name) values (_project_report_attachment_id, 'john', 'doe'); + insert into project_report_author (project_report_attachment_id, first_name, last_name) values (_project_report_attachment_id, 'bob', 'dole'); + insert into project_first_nation (project_id, first_nations_id) values (_project_id, (select first_nations_id from first_nations where name = 'Kitselas Nation')); + insert into permit (system_user_id, project_id, number, type, issue_date, end_date) values (_system_user_id, _project_id, '8377262', 'permit type', now(), now()+interval '1 day'); + + select count(1) into _count from stakeholder_partnership; + assert _count = 1, 'FAIL stakeholder_partnership'; + select count(1) into _count from project_activity; + assert _count = 1, 'FAIL project_activity'; + select count(1) into _count from project_climate_initiative; + assert _count = 1, 'FAIL project_climate_initiative'; + select count(1) into _count from project_management_actions; + assert _count = 1, 'FAIL project_management_actions'; + select count(1) into _count from project_funding_source; + assert _count = 1, 'FAIL project_funding_source'; + select count(1) into _count from project_iucn_action_classification; + assert _count = 1, 'FAIL project_iucn_action_classification'; + select count(1) into _count from project_attachment; + assert _count = 1, 'FAIL project_attachment'; + select count(1) into _count from project_attachment; + assert _count = 1, 'FAIL project_attachment'; + select count(1) into _count from project_report_attachment; + assert _count = 1, 'FAIL project_report_attachment'; + select count(1) into _count from project_report_author; + assert _count = 2, 'FAIL project_report_author'; + select count(1) into _count from project_first_nation; + assert _count = 1, 'FAIL project_first_nation'; + select count(1) into _count from permit; + assert _count = 1, 'FAIL permit'; + + -- surveys + insert into survey (project_id + , name + , additional_details + , location_name + , location_description + , start_date + , lead_first_name + , lead_last_name + , geography + , ecological_season_id + , intended_outcome_id) + values (_project_id + , 'survey name' + , 'survey objectives' + , 'survey location name' + , 'survey location description' + , now() + , 'lead first' + , 'lead last' + , _geography + , (select ecological_season_id from ecological_season where name = 'Growing') + , (select intended_outcome_id from intended_outcome where name = 'Survival') + ) returning survey_id into _survey_id; + + insert into survey_proprietor (survey_id, first_nations_id, proprietor_type_id, rationale,disa_required) + values (_survey_id, (select first_nations_id from first_nations where name = 'Squamish Nation'), (select proprietor_type_id from proprietor_type where name = 'First Nations Land'), 'proprietor rationale', true); + insert into survey_attachment (survey_id, file_name, title, key, file_size, file_type) values (_survey_id, 'test_filename.txt', 'test filename', 'projects/'||_project_id::text||'/surveys/'||_survey_id::text, 10000, 'video'); + insert into survey_report_attachment (survey_id, file_name, title, key, file_size, year, description) values (_survey_id, 'test_filename.txt', 'test filename', 'projects/'||_survey_id::text, 10000, '2021', 'example abstract') returning survey_report_attachment_id into _survey_report_attachment_id; + insert into survey_report_author (survey_report_attachment_id, first_name, last_name) values (_survey_report_attachment_id, 'john', 'doe'); + insert into survey_report_author (survey_report_attachment_id, first_name, last_name) values (_survey_report_attachment_id, 'bob', 'dole'); + insert into study_species (survey_id, wldtaxonomic_units_id, is_focal) values (_survey_id, (select wldtaxonomic_units_id from wldtaxonomic_units where CODE = 'AMARALB'), true); + insert into survey_funding_source (survey_id, project_funding_source_id) values (_survey_id, _project_funding_source_id); + insert into survey_vantage(survey_id, vantage_id) values (_survey_id, (select vantage_id from vantage where name = 'Aerial')); + + select count(1) into _count from survey; + assert _count = 1, 'FAIL survey'; + select count(1) into _count from survey_proprietor; + assert _count = 1, 'FAIL survey_proprietor'; + select count(1) into _count from survey_attachment where survey_id = _survey_id; + assert _count = 1, 'FAIL survey_attachment'; + select count(1) into _count from survey_report_attachment; + assert _count = 1, 'FAIL survey_report_attachment'; + select count(1) into _count from survey_report_author; + assert _count = 2, 'FAIL survey_report_author'; + select count(1) into _count from study_species; + assert _count = 1, 'FAIL study_species'; + select count(1) into _count from survey_funding_source; + assert _count = 1, 'FAIL survey_funding_source'; + select count(1) into _count from survey_vantage; + assert _count = 1, 'FAIL survey_vantage'; + + -- occurrence + -- occurrence submission 1 + insert into occurrence_submission (survey_id, source, event_timestamp) values (_survey_id, 'BIOHUB BATCH', now()-interval '1 day') returning occurrence_submission_id into _occurrence_submission_id; + select count(1) into _count from occurrence_submission; + assert _count = 1, 'FAIL occurrence_submission'; + insert into occurrence (occurrence_submission_id, taxonid, lifestage, eventdate, sex) values (_occurrence_submission_id, 'M-ALAL', 'Adult', now()-interval '10 day', 'male'); + select count(1) into _count from occurrence; + assert _count = 1, 'FAIL occurrence'; + insert into submission_status (occurrence_submission_id, submission_status_type_id, event_timestamp) values (_occurrence_submission_id, (select submission_status_type_id from submission_status_type where name = 'Submitted'), now()-interval '1 day') returning submission_status_id into _submission_status_id; + -- transpose comments on next three lines to test deletion of published surveys by system administrator + insert into submission_status (occurrence_submission_id, submission_status_type_id, event_timestamp) values (_occurrence_submission_id, (select submission_status_type_id from submission_status_type where name = 'Awaiting Curration'), now()-interval '1 day') returning submission_status_id into _submission_status_id; + insert into submission_status (occurrence_submission_id, submission_status_type_id, event_timestamp) values (_occurrence_submission_id, (select submission_status_type_id from submission_status_type where name = 'Published'), now()-interval '1 day') returning submission_status_id into _submission_status_id; + --insert into system_user_role (system_user_id, system_role_id) values (_system_user_id, (select system_role_id from system_role where name = 'System Administrator')); + + -- occurrence submission 2 + insert into occurrence_submission (survey_id, source, event_timestamp) values (_survey_id, 'BIOHUB BATCH', now()) returning occurrence_submission_id into _occurrence_submission_id; + select count(1) into _count from occurrence_submission; + assert _count = 2, 'FAIL occurrence_submission'; + insert into occurrence (occurrence_submission_id, taxonid, lifestage, eventdate, sex) values (_occurrence_submission_id, 'M-ALAL', 'Adult', now()-interval '5 day', 'female'); + select count(1) into _count from occurrence; + assert _count = 2, 'FAIL occurrence'; + insert into submission_status (occurrence_submission_id, submission_status_type_id, event_timestamp) values (_occurrence_submission_id, (select submission_status_type_id from submission_status_type where name = 'Submitted'), now()) returning submission_status_id into _submission_status_id; + insert into submission_status (occurrence_submission_id, submission_status_type_id, event_timestamp) values (_occurrence_submission_id, (select submission_status_type_id from submission_status_type where name = 'Rejected'), now()) returning submission_status_id into _submission_status_id; + insert into submission_message (submission_status_id, submission_message_type_id, event_timestamp, message) values (_submission_status_id, (select submission_message_type_id from submission_message_type where name = 'Missing Required Field'), now(), 'Some required field was not supplied.'); + select count(1) into _count from submission_status; + assert _count = 5, 'FAIL submission_status'; + select count(1) into _count from submission_message; + assert _count = 1, 'FAIL submission_message'; + +-- raise notice 'survey status (project_id, survey_id, survey_status):'; +-- for _survey_status_rec in execute _survey_status_query loop +-- raise notice 'survey status results are % % % %', _survey_status_rec.project_id, _survey_status_rec.survey_id, _survey_status_rec.occurrence_id, _survey_status_rec.survey_status; +-- end loop; + + -- test ancillary data + delete from webform_draft; + insert into webform_draft (system_user_id, name, data) values ((select system_user_id from system_user limit 1), 'my draft name', '{ "customer": "John Doe", "items": {"product": "Beer","qty": 6}}'); + select count(1) into _count from webform_draft; + assert _count = 1, 'FAIL webform_draft'; + + -- work ledger + delete from administrative_activity; + insert into administrative_activity (reported_system_user_id + , administrative_activity_type_id + , administrative_activity_status_type_id + , description + , data) + values (_system_user_id + , (select administrative_activity_type_id from administrative_activity_type where name = 'System Access') + , (select administrative_activity_status_type_id from administrative_activity_status_type where name = 'Pending') + , 'my activity' + , '{ "customer": "John Doe", "items": {"product": "Beer","qty": 6}}') + ; + select count(1) into _count from administrative_activity; + assert _count = 1, 'FAIL administrative_activity'; + + insert into permit (system_user_id, number, type, issue_date, end_date, coordinator_first_name, coordinator_last_name, coordinator_email_address, coordinator_agency_name) values (_system_user_id, '8377261', 'permit type', now(), now()+interval '1 day', 'first', 'last', 'nobody@nowhere.com', 'agency'); + + -- delete project + raise notice 'deleting data.'; + call api_delete_project(_project_id); + + raise notice 'smoketest_release(2): PASS'; +end +$$; + +delete from permit; diff --git a/docker-compose.yml b/docker-compose.yml index 89b170970a..89a66cea3b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,9 +16,9 @@ services: - ${DB_PORT}:5432 healthcheck: test: ["CMD-SHELL", "pg_isready -U $DB_ADMIN -p 5432 -d $DB_DATABASE"] - interval: 30s - timeout: 10s - retries: 5 + interval: 5s + timeout: 5s + retries: 30 environment: - NODE_ENV=${NODE_ENV} - POSTGRES_USER=${DB_ADMIN}
Study Area Boundary
Is the data captured in this survey proprietary?
Species and Ecosystems Data and Information Security (SEDIS) Procedures
Freedom of Information and Protection of Privacy Act (FOIPPA) Requirements
- error on survey purpose field -
- survey purpose -