From 0e91c3813556b2c56f72be392273c85377316ffa Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Fri, 21 Jun 2024 09:34:10 -0700 Subject: [PATCH 01/10] insert features --- api/src/paths/taxonomy/taxon/index.ts | 2 +- api/src/paths/taxonomy/taxon/tsn/index.ts | 2 +- .../search-index-repository.test.ts | 4 +- .../20240619000000_insert_feature_types.ts | 528 ++++++++++++++++++ .../src/seeds/02_populate_feature_tables.ts | 102 ---- 5 files changed, 532 insertions(+), 106 deletions(-) create mode 100644 database/src/migrations/20240619000000_insert_feature_types.ts delete mode 100644 database/src/seeds/02_populate_feature_tables.ts diff --git a/api/src/paths/taxonomy/taxon/index.ts b/api/src/paths/taxonomy/taxon/index.ts index 590927f9..8345f7ee 100644 --- a/api/src/paths/taxonomy/taxon/index.ts +++ b/api/src/paths/taxonomy/taxon/index.ts @@ -9,7 +9,7 @@ export const GET: Operation = [findTaxonBySearchTerms()]; GET.apiDoc = { description: 'Find taxon records by search criteria.', - tags: ['taxonomy'], + tags: ['taxon_id'], security: [ { Bearer: [] diff --git a/api/src/paths/taxonomy/taxon/tsn/index.ts b/api/src/paths/taxonomy/taxon/tsn/index.ts index ca366067..8f21b6fd 100644 --- a/api/src/paths/taxonomy/taxon/tsn/index.ts +++ b/api/src/paths/taxonomy/taxon/tsn/index.ts @@ -10,7 +10,7 @@ export const GET: Operation = [getTaxonByTSN()]; GET.apiDoc = { description: 'Get taxon records by TSN ids.', - tags: ['taxonomy'], + tags: ['taxon_id'], security: [ { Bearer: [] diff --git a/api/src/repositories/search-index-repository.test.ts b/api/src/repositories/search-index-repository.test.ts index 0bef523a..adeb4268 100644 --- a/api/src/repositories/search-index-repository.test.ts +++ b/api/src/repositories/search-index-repository.test.ts @@ -187,7 +187,7 @@ describe('SearchIndexRepository', () => { feature_property_type_name: 'number', feature_property_id: 3, feature_property_type_id: 2, - name: 'taxonomy', + name: 'taxon_id', display_name: 'Taxonomy Id', description: 'The taxonomy Id associated to the record', parent_feature_property_id: null, @@ -392,7 +392,7 @@ describe('SearchIndexRepository', () => { feature_property_type_name: 'number', feature_property_id: 3, feature_property_type_id: 2, - name: 'taxonomy', + name: 'taxon_id', display_name: 'Taxonomy Id', description: 'The taxonomy Id associated to the record', parent_feature_property_id: null, diff --git a/database/src/migrations/20240619000000_insert_feature_types.ts b/database/src/migrations/20240619000000_insert_feature_types.ts new file mode 100644 index 00000000..5ee2c83e --- /dev/null +++ b/database/src/migrations/20240619000000_insert_feature_types.ts @@ -0,0 +1,528 @@ +import { Knex } from 'knex'; + +/** + * Insert new feature types + * + * @export + * @param {Knex} knex + * @return {*} {Promise} + */ + +export async function up(knex: Knex): Promise { + await knex.raw(` + set search_path=biohub,public; + + ---------------------------------------------------------------------------------------- + -- Insert feature types + ---------------------------------------------------------------------------------------- + INSERT INTO feature_type (name, display_name, description) + VALUES + ( + 'dataset', + 'Dataset', + 'A related collection of data.' + ), + ( + 'file', + 'File', + 'A file such as an image or document.' + ), + ( + 'sample_site', + 'Sampling site', + 'A location where species observations are collected.' + ), + ( + 'sample_method', + 'Sampling method', + 'A method by which species observations or other ecological data are collected.' + ), + ( + 'sample_period', + 'Sampling period', + 'A time period in which a sampling method is conducted.' + ), + ( + 'species_observation', + 'Species observation', + 'Information about an encounter with a species or evidence of a species, such as hair or tracks.' + ), + ( + 'animal', + 'Animal', + 'An individual animal.' + ), + ( + 'telemetry', + 'Telemetry', + 'Positional data from a telemetry device.' + ), + ( + 'telemetry_deployment', + 'Telemetry deployment', + 'Metadata describing the deployment of a telemetry device.' + ), + ( + 'capture', + 'Capture', + 'Information about an animal capture event.' + ), + ( + 'mortality', + 'Mortality', + 'Information about an animal mortality event.' + ), + ( + 'measurement', + 'Measurement', + 'Information about a species measurement, + such as body condition or length.' + ), + ( + 'marking', + 'Marking', + 'Information about a marking applied to an animal for identification.' + ), + ( + 'ecological_unit', + 'Ecological unit', + 'An ecological unit, such as a population unit, herd, or pack.' + ); + + + ---------------------------------------------------------------------------------------- + -- Insert feature property types + ---------------------------------------------------------------------------------------- + INSERT INTO + feature_property_type (name, description) + VALUES + ('string', 'A string type'), + ('number', 'A number type'), + ('datetime', 'A datetime type (ISO 8601)'), + ('spatial', 'A spatial type'), + ('boolean', 'A boolean type'), + ('object', 'An object type'), + ('array', 'An array type'), + ('artifact_key', 'An artifact key type (string)'); + + ---------------------------------------------------------------------------------------- + -- Insert feature properties + ---------------------------------------------------------------------------------------- + INSERT INTO + feature_property (feature_property_type_id, name, display_name, description, calculated_value) + VALUES + -- COMMON PROPERTIES + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'string'), + 'name', + 'Name', + 'The name of the record.', + false + ), + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'string'), + 'description', + 'Description', + 'The description of the record.', + false + ), + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'number'), + 'taxon_id', + 'Taxonomic identifier', + 'The unique identifier of the species associated to the record.', + false + ), + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'string'), + 'scientific_name', + 'Scientific name', + 'The scientific name of the species associated to the record.', + false + ), + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'object'), + 'date_range', + 'Date Range', + 'A date range.', + false + ), + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'datetime'), + 'start_date', + 'Start Date', + 'The start date of the record.', + false + ), + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'datetime'), + 'end_date', + 'End Date', + 'The end date of the record.', + false + ), + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'datetime'), + 'timestamp', + 'Timestamp', + 'The timestamp of the record.', + false + ), + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'spatial'), + 'geometry', + 'Geometry', + 'The location of the record.', + false + ), + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'number'), + 'count', + 'Count', + 'The count of the record.', + false + ), + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'number'), + 'latitude', + 'Latitude', + 'The latitude of the record.', + false + ), + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'number'), + 'longitude', + 'Longitude', + 'The longitude of the record.', + false + ), + ( + (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'artifact_key'), + 'artifact_key', + 'Key', + 'The S3 storage key for an artifact', + true + ), + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'identifier', + 'Identifier', + 'Identifier of the record from an external source system.', + false + + ), + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'animal_identifier', + 'Animal identifier', + 'Identifier of the animal.', + false + ), + -- DEVICE DEPLOYMENT-RELATED PROPERTIES + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'device_manufacturer', + 'Device manufacturer', + 'Manufacturer of the device.', + false + ), + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'device_model', + 'Device model', + 'Model of the device.', + false + ), + ( + (SELECT feature_property_type_id from feature_property_type where name = 'number'), + 'frequency', + 'Frequency', + 'Frequency of the device.', + false + ), + ( + (SELECT feature_property_type_id from feature_property_type where name = 'number'), + 'fix_rate', + 'Fix rate', + 'Rate at which the device records a location.', + false + ), + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'frequency_unit', + 'Frequency Unit', + 'Unit of frequency of the device.', + false + ), + ( + (SELECT feature_property_type_id from feature_property_type where name = 'number'), + 'fix_rate_unit', + 'Fix rate unit', + 'Unit of the rate at which the device records a location.', + false + ), + -- CAPTURE-RELATED PROPERTIES + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'date', + 'Date', + 'A date.', + false + ), + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'time', + 'Time', + 'A time of day.', + false + ), + -- MEASUREMENT-RELATED PROPERTIES + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'measurement_type', + 'Measurement', + 'The type of measurement.', + false + ), + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'measurement_value', + 'Measurement value', + 'The value of the measurement.', + false + ), + -- MARKING-RELATED PROPERTIES + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'colour', + 'Colour', + 'The colour of a marking on an animal.', + false + ), + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'body_position', + 'Body position', + 'The location of a marking on an animal', + false + ), + -- ECOLOGICAL UNIT-RELATED PROPERTIES + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'ecological_unit_type', + 'Ecological unit', + 'The ecological unit type or category, such as population unit, herd, or pack.', + false + ), + ( + (SELECT feature_property_type_id from feature_property_type where name = 'string'), + 'ecological_unit_value', + 'Ecological unit value', + 'The value of the ecological unit type, like the name of a specific population unit if the type is population unit.', + false + ); + + +---------------------------------------------------------------------------------------- +-- Assign feature properties to feature types +---------------------------------------------------------------------------------------- +INSERT INTO + feature_type_property (feature_type_id, feature_property_id, required_value) +VALUES + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'dataset'), + (SELECT feature_property_id FROM feature_property WHERE name = 'name'), + true + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'dataset'), + (SELECT feature_property_id FROM feature_property WHERE name = 'description'), + true + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'dataset'), + (SELECT feature_property_id FROM feature_property WHERE name = 'start_date'), + true + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'dataset'), + (SELECT feature_property_id FROM feature_property WHERE name = 'end_date'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), + (SELECT feature_property_id FROM feature_property WHERE name = 'animal_identifier'), + true + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), + (SELECT feature_property_id FROM feature_property WHERE name = 'start_date'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), + (SELECT feature_property_id FROM feature_property WHERE name = 'start_time'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), + (SELECT feature_property_id FROM feature_property WHERE name = 'device_manufacturer'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), + (SELECT feature_property_id FROM feature_property WHERE name = 'device_model'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), + (SELECT feature_property_id FROM feature_property WHERE name = 'frequency'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), + (SELECT feature_property_id FROM feature_property WHERE name = 'frequency_unit'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), + (SELECT feature_property_id FROM feature_property WHERE name = 'fix_rate'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), + (SELECT feature_property_id FROM feature_property WHERE name = 'fix_rate_unit'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'animal'), + (SELECT feature_property_id FROM feature_property WHERE name = 'taxon_id'), + true + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'animal'), + (SELECT feature_property_id FROM feature_property WHERE name = 'animal_identifier'), + true + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'animal'), + (SELECT feature_property_id FROM feature_property WHERE name = 'description'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'capture'), + (SELECT feature_property_id FROM feature_property WHERE name = 'identifier'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'capture'), + (SELECT feature_property_id FROM feature_property WHERE name = 'description'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'capture'), + (SELECT feature_property_id FROM feature_property WHERE name = 'timestamp'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'capture'), + (SELECT feature_property_id FROM feature_property WHERE name = 'geometry'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'mortality'), + (SELECT feature_property_id FROM feature_property WHERE name = 'identifier'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'mortality'), + (SELECT feature_property_id FROM feature_property WHERE name = 'description'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'mortality'), + (SELECT feature_property_id FROM feature_property WHERE name = 'timestamp'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'mortality'), + (SELECT feature_property_id FROM feature_property WHERE name = 'geometry'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'species_observation'), + (SELECT feature_property_id FROM feature_property WHERE name = 'identifier'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'species_observation'), + (SELECT feature_property_id FROM feature_property WHERE name = 'taxon_id'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'species_observation'), + (SELECT feature_property_id FROM feature_property WHERE name = 'animal_identifier'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'species_observation'), + (SELECT feature_property_id FROM feature_property WHERE name = 'description'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'species_observation'), + (SELECT feature_property_id FROM feature_property WHERE name = 'timestamp'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'species_observation'), + (SELECT feature_property_id FROM feature_property WHERE name = 'geometry'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'measurement'), + (SELECT feature_property_id FROM feature_property WHERE name = 'description'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'measurement'), + (SELECT feature_property_id FROM feature_property WHERE name = 'measurement_type'), + true + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'measurement'), + (SELECT feature_property_id FROM feature_property WHERE name = 'measurement_value'), + true + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'marking'), + (SELECT feature_property_id FROM feature_property WHERE name = 'body_position'), + true + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'marking'), + (SELECT feature_property_id FROM feature_property WHERE name = 'identifier'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'marking'), + (SELECT feature_property_id FROM feature_property WHERE name = 'colour'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'ecological_unit'), + (SELECT feature_property_id FROM feature_property WHERE name = 'ecological_unit_type'), + false + ), + ( + (SELECT feature_type_id FROM feature_type WHERE name = 'ecological_unit'), + (SELECT feature_property_id FROM feature_property WHERE name = 'ecological_unit_value'), + false + ); + + `); +} + +export async function down(knex: Knex): Promise { + await knex.raw(``); +} diff --git a/database/src/seeds/02_populate_feature_tables.ts b/database/src/seeds/02_populate_feature_tables.ts deleted file mode 100644 index 9dcd2871..00000000 --- a/database/src/seeds/02_populate_feature_tables.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { Knex } from 'knex'; - -/** - * Populate tables: - * - feature_property_type - * - feature_property - * - feature_type - * - feature_type_property - * - * @export - * @param {Knex} knex - * @return {*} {Promise} - */ -export async function seed(knex: Knex): Promise { - await knex.raw(` - ---------------------------------------------------------------------------------------- - -- Create tables - ---------------------------------------------------------------------------------------- - set search_path=biohub,public; - - -- populate feature_property_type table - insert into feature_property_type (name, description, record_effective_date) values ('string', 'A string type', now()) ON CONFLICT DO NOTHING; - insert into feature_property_type (name, description, record_effective_date) values ('number', 'A number type', now()) ON CONFLICT DO NOTHING; - insert into feature_property_type (name, description, record_effective_date) values ('datetime', 'A datetime type (ISO 8601)', now()) ON CONFLICT DO NOTHING; - insert into feature_property_type (name, description, record_effective_date) values ('spatial', 'A spatial type', now()) ON CONFLICT DO NOTHING; - insert into feature_property_type (name, description, record_effective_date) values ('boolean', 'A boolean type', now()) ON CONFLICT DO NOTHING; - insert into feature_property_type (name, description, record_effective_date) values ('object', 'An object type', now()) ON CONFLICT DO NOTHING; - insert into feature_property_type (name, description, record_effective_date) values ('array', 'An array type', now()) ON CONFLICT DO NOTHING; - insert into feature_property_type (name, description, record_effective_date) values ('artifact_key', 'An artifact key type (string)', now()) ON CONFLICT DO NOTHING; - - -- populate feature_property table - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('name', 'Name', 'The name of the record', (select feature_property_type_id from feature_property_type where name = 'string'), null, now()) ON CONFLICT DO NOTHING; - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('description', 'Description', 'The description of the record', (select feature_property_type_id from feature_property_type where name = 'string'), null, now()) ON CONFLICT DO NOTHING; - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('taxonomy', 'Taxonomy Id', 'The taxonomy Id associated to the record', (select feature_property_type_id from feature_property_type where name = 'number'), null, now()) ON CONFLICT DO NOTHING; - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('date_range', 'Date Range', 'A date range', (select feature_property_type_id from feature_property_type where name = 'object'), null, now()) ON CONFLICT DO NOTHING; - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('start_date', 'Start Date', 'The start date of the record', (select feature_property_type_id from feature_property_type where name = 'datetime'), (select feature_property_id from feature_property where name = 'date_range'), now()) ON CONFLICT DO NOTHING; - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('end_date', 'End Date', 'The end date of the record', (select feature_property_type_id from feature_property_type where name = 'datetime'), (select feature_property_id from feature_property where name = 'date_range'), now()) ON CONFLICT DO NOTHING; - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('timestamp', 'Timestamp', 'The timestamp of the record', (select feature_property_type_id from feature_property_type where name = 'datetime'), null, now()) ON CONFLICT DO NOTHING; - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('geometry', 'Geometry', 'The location of the record', (select feature_property_type_id from feature_property_type where name = 'spatial'), null, now()) ON CONFLICT DO NOTHING; - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('count', 'Count', 'The count of the record', (select feature_property_type_id from feature_property_type where name = 'number'), null, now()) ON CONFLICT DO NOTHING; - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('latitude', 'Latitude', 'The latitude of the record', (select feature_property_type_id from feature_property_type where name = 'number'), null, now()) ON CONFLICT DO NOTHING; - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('longitude', 'Longitude', 'The longitude of the record', (select feature_property_type_id from feature_property_type where name = 'number'), null, now()) ON CONFLICT DO NOTHING; - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date, calculated_value) values ('artifact_key', 'Key', 'The S3 storage key for an artifact', (select feature_property_type_id from feature_property_type where name = 'artifact_key'), null, now(), true) ON CONFLICT DO NOTHING; - insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('filename', 'Filename', 'The original name of an artifact, including extension', (select feature_property_type_id from feature_property_type where name = 'string'), null, now()) ON CONFLICT DO NOTHING; - - -- populate feature_type table - insert into feature_type (name, display_name, description, sort, record_effective_date) values ('dataset', 'Dataset', 'A related collection of data (ie: survey)', 1, now()) ON CONFLICT DO NOTHING; - insert into feature_type (name, display_name, description, sort, record_effective_date) values ('artifact', 'Artifact', 'An artifact (ie: image, document, pdf)', 2, now()) ON CONFLICT DO NOTHING; - insert into feature_type (name, display_name, description, sort, record_effective_date) values ('sample_site', 'Sample Site', 'A location at which data was collected', 3, now()) ON CONFLICT DO NOTHING; - insert into feature_type (name, display_name, description, sort, record_effective_date) values ('sample_method', 'Sample Method', 'A method used to collect data', 4, now()) ON CONFLICT DO NOTHING; - insert into feature_type (name, display_name, description, sort, record_effective_date) values ('sample_period', 'Sample Period', 'A datetime period in which data was collected', 5, now()) ON CONFLICT DO NOTHING; - insert into feature_type (name, display_name, description, sort, record_effective_date) values ('observation', 'Observation', 'An observation record', 6, now()) ON CONFLICT DO NOTHING; - insert into feature_type (name, display_name, description, sort, record_effective_date) values ('animal', 'Animal', 'An individual animal record', 7, now()) ON CONFLICT DO NOTHING; - insert into feature_type (name, display_name, description, sort, record_effective_date) values ('telemetry', 'Telemetry', 'A telemetry record', 8, now()) ON CONFLICT DO NOTHING; - - -- populate feature_type_property table - -- feature_type: dataset - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'name'), 1, now()) ON CONFLICT DO NOTHING; - -- insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'description'), 2, now()) ON CONFLICT DO NOTHING; - -- insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'taxonomy'), 3, now()) ON CONFLICT DO NOTHING; - -- insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'date_range'), 4, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'start_date'), 5, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'end_date'), 6, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'geometry'), 7, now()) ON CONFLICT DO NOTHING; - - -- feature_type: artifact - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'artifact'), (select feature_property_id from feature_property where name = 'filename'), 1, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'artifact'), (select feature_property_id from feature_property where name = 'artifact_key'), 2, now()) ON CONFLICT DO NOTHING; - - -- feature_type: sample_site - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_site'), (select feature_property_id from feature_property where name = 'name'), 1, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_site'), (select feature_property_id from feature_property where name = 'description'), 2, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_site'), (select feature_property_id from feature_property where name = 'geometry'), 3, now()) ON CONFLICT DO NOTHING; - - -- feature_type: sample_method - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_method'), (select feature_property_id from feature_property where name = 'name'), 1, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_method'), (select feature_property_id from feature_property where name = 'description'), 2, now()) ON CONFLICT DO NOTHING; - - -- feature_type: sample_period - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_period'), (select feature_property_id from feature_property where name = 'date_range'), 1, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_period'), (select feature_property_id from feature_property where name = 'start_date'), 2, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_period'), (select feature_property_id from feature_property where name = 'end_date'), 3, now()) ON CONFLICT DO NOTHING; - - -- feature_type: observation - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'taxonomy'), 1, now()) ON CONFLICT DO NOTHING; - -- insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'date_range'), 1, now()) ON CONFLICT DO NOTHING; - -- insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'start_date'), 2, now()) ON CONFLICT DO NOTHING; - -- insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'end_date'), 3, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'geometry'), 7, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'latitude'), 5, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'longitude'), 6, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'count'), 4, now()) ON CONFLICT DO NOTHING; - - -- feature_type: animal - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'animal'), (select feature_property_id from feature_property where name = 'taxonomy'), 1, now()) ON CONFLICT DO NOTHING; - - -- feature_type: telemetry - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'telemetry'), (select feature_property_id from feature_property where name = 'date_range'), 1, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'telemetry'), (select feature_property_id from feature_property where name = 'start_date'), 2, now()) ON CONFLICT DO NOTHING; - insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'telemetry'), (select feature_property_id from feature_property where name = 'end_date'), 3, now()) ON CONFLICT DO NOTHING; - `); -} From fbce7411802240ad2867001c07fc3471f5ee6e20 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Wed, 7 Aug 2024 17:39:48 -0700 Subject: [PATCH 02/10] add hierarchy endpoint --- api/src/paths/taxonomy/taxon/tsn/hierarchy.ts | 129 ++++++++++++++++++ api/src/services/itis-service.ts | 58 ++++++++ 2 files changed, 187 insertions(+) create mode 100644 api/src/paths/taxonomy/taxon/tsn/hierarchy.ts diff --git a/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts b/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts new file mode 100644 index 00000000..842d4456 --- /dev/null +++ b/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts @@ -0,0 +1,129 @@ +import { RequestHandler } from 'express'; +import { Operation } from 'express-openapi'; +import { getAPIUserDBConnection } from '../../../../database/db'; +import { ItisService } from '../../../../services/itis-service'; +import { getLogger } from '../../../../utils/logger'; + +const defaultLog = getLogger('paths/taxonomy/taxon/tsn'); + +export const GET: Operation = [getTaxonByTSN()]; + +GET.apiDoc = { + description: 'Get taxon records by TSN ids.', + tags: ['taxon_id'], + security: [ + { + Bearer: [] + } + ], + parameters: [ + { + description: 'Taxon TSN ids.', + in: 'query', + name: 'tsn', + schema: { + type: 'array', + description: 'One or more Taxon TSN ids.', + items: { + type: 'integer', + minimum: 0, + minItems: 1, + maxItems: 100 + } + }, + required: true + } + ], + responses: { + 200: { + description: 'Taxonomy response.', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + searchResponse: { + type: 'array', + items: { + title: 'Species', + type: 'object', + required: ['tsn', 'commonNames', 'scientificName'], + properties: { + tsn: { + type: 'integer' + }, + commonNames: { + type: 'array', + items: { + type: 'string' + } + }, + scientificName: { + type: 'string' + }, + rank: { + type: 'string' + }, + kingdom: { + type: 'string' + } + }, + additionalProperties: false + } + } + }, + additionalProperties: false + } + } + } + }, + 400: { + $ref: '#/components/responses/400' + }, + 401: { + $ref: '#/components/responses/401' + }, + 500: { + $ref: '#/components/responses/500' + }, + default: { + $ref: '#/components/responses/default' + } + } +}; + +/** + * Get taxon by ITIS TSN. + * + * @returns {RequestHandler} + */ +export function getTaxonByTSN(): RequestHandler { + return async (req, res) => { + defaultLog.debug({ label: 'getTaxonByTSN', message: 'query params', query: req.query }); + + const connection = getAPIUserDBConnection(); + + const tsnIds: number[] = (req.query.tsn as (string | number)[]).map(Number); + + try { + await connection.open(); + + const itisService = new ItisService(); + + const response = await itisService.getHierarchyForTSNs(tsnIds); + + connection.commit(); + + // Overwrite default cache-control header, allow caching up to 7 days + res.setHeader('Cache-Control', 'max-age=604800'); + + res.status(200).json({ searchResponse: response }); + } catch (error) { + defaultLog.error({ label: 'getTaxonByTSN', message: 'error', error }); + connection.rollback(); + throw error; + } finally { + connection.release(); + } + }; +} diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 9407d867..61335ff6 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -17,6 +17,11 @@ export type ItisSolrSearchResponse = { rank: string; }; +export type ItisSolrSearchResponseHierarchy = { + tsn: string; + hierarchy: string[]; +}; + /** * Service for retrieving and processing taxonomic data from the Integrated Taxonomic Information System (ITIS). * @@ -75,6 +80,27 @@ export class ItisService { return response.data.response.docs; } + /** + * Returns the parent hierarchy for multiple TSNs + * + * @param {number[]} tsnIds + * @return {*} {Promise} + * @memberof ItisService + */ + async getHierarchyForTSNs(tsnIds: number[]): Promise { + const url = await this.getItisSolrTsnHierarchyUrl(tsnIds); + + defaultLog.debug({ label: 'getHierarchyForTSNs', message: 'url', url }); + + const response = await axios.get(url); + + if (!response.data || !response.data.response || !response.data.response.docs) { + return []; + } + + return response.data.response.docs; + } + /** * Cleans up the ITIS search response data * @@ -140,6 +166,28 @@ export class ItisService { )}&${this._getItisSolrFilterParam()}&&q=${this._getItisSolrTsnSearch(searchTsnIds)}`; } + /** + * Get the ITIS SOLR search-by-tsn URL with hierarchy + * + * @param {number[]} tsnIds + * @return {*} {Promise} + * @memberof ItisService + */ + async getItisSolrTsnHierarchyUrl(tsnIds: number[]): Promise { + const itisUrl = this._getItisSolrUrl(); + + if (!itisUrl) { + defaultLog.debug({ label: 'getItisTsnHierarchyUrl', message: 'Environment variable ITIS_URL is not defined.' }); + throw new Error('Failed to build ITIS query.'); + } + + return `${itisUrl}??${this._getItisSolrTypeParam()}&${this._getItisSolrSortParam( + ['kingdom'], + ['asc'], + 150 + )}&${this._getItisSolrFilterParam()}&&q=${this._getItisSolrTsnSearch(tsnIds)}`; + } + /** * Get ITIS SOLR base URL. * @@ -183,6 +231,16 @@ export class ItisService { return 'omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage+rank'; } + /** + * Get ITIS SOLR filter param. + * + * @return {*} {string} + * @memberof ItisService + */ + _getItisSolrHierarchyParam(): string { + return 'omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage+rank'; + } + /** * Get ITIS SOLR query by search term param. * From 5507f52eaa6378e745a349e5843ba9b51490c22e Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Wed, 7 Aug 2024 18:10:13 -0700 Subject: [PATCH 03/10] typo --- api/src/services/itis-service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 61335ff6..fc86dd0a 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -185,7 +185,7 @@ export class ItisService { ['kingdom'], ['asc'], 150 - )}&${this._getItisSolrFilterParam()}&&q=${this._getItisSolrTsnSearch(tsnIds)}`; + )}&${this._getItisSolrHierarchyParam()}&&q=${this._getItisSolrTsnSearch(tsnIds)}`; } /** @@ -238,7 +238,7 @@ export class ItisService { * @memberof ItisService */ _getItisSolrHierarchyParam(): string { - return 'omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage+rank'; + return 'omitHeader=true&fl=tsn+hierarchy'; } /** From cb305e0f1467c995916cef13109b9f9b33c3bf8a Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Thu, 8 Aug 2024 15:26:17 -0700 Subject: [PATCH 04/10] changed tbd to to be determined --- app/src/features/datasets/DatasetPage.tsx | 2 +- .../20240619000000_insert_feature_types.ts | 528 ------------------ .../src/seeds/02_population_feature_tables.ts | 102 ++++ 3 files changed, 103 insertions(+), 529 deletions(-) delete mode 100644 database/src/migrations/20240619000000_insert_feature_types.ts create mode 100644 database/src/seeds/02_population_feature_tables.ts diff --git a/app/src/features/datasets/DatasetPage.tsx b/app/src/features/datasets/DatasetPage.tsx index 5fc4eb84..d44aee64 100644 --- a/app/src/features/datasets/DatasetPage.tsx +++ b/app/src/features/datasets/DatasetPage.tsx @@ -7,7 +7,7 @@ const DatasetPage: React.FC = () => { - TBD + To be determined diff --git a/database/src/migrations/20240619000000_insert_feature_types.ts b/database/src/migrations/20240619000000_insert_feature_types.ts deleted file mode 100644 index 5ee2c83e..00000000 --- a/database/src/migrations/20240619000000_insert_feature_types.ts +++ /dev/null @@ -1,528 +0,0 @@ -import { Knex } from 'knex'; - -/** - * Insert new feature types - * - * @export - * @param {Knex} knex - * @return {*} {Promise} - */ - -export async function up(knex: Knex): Promise { - await knex.raw(` - set search_path=biohub,public; - - ---------------------------------------------------------------------------------------- - -- Insert feature types - ---------------------------------------------------------------------------------------- - INSERT INTO feature_type (name, display_name, description) - VALUES - ( - 'dataset', - 'Dataset', - 'A related collection of data.' - ), - ( - 'file', - 'File', - 'A file such as an image or document.' - ), - ( - 'sample_site', - 'Sampling site', - 'A location where species observations are collected.' - ), - ( - 'sample_method', - 'Sampling method', - 'A method by which species observations or other ecological data are collected.' - ), - ( - 'sample_period', - 'Sampling period', - 'A time period in which a sampling method is conducted.' - ), - ( - 'species_observation', - 'Species observation', - 'Information about an encounter with a species or evidence of a species, such as hair or tracks.' - ), - ( - 'animal', - 'Animal', - 'An individual animal.' - ), - ( - 'telemetry', - 'Telemetry', - 'Positional data from a telemetry device.' - ), - ( - 'telemetry_deployment', - 'Telemetry deployment', - 'Metadata describing the deployment of a telemetry device.' - ), - ( - 'capture', - 'Capture', - 'Information about an animal capture event.' - ), - ( - 'mortality', - 'Mortality', - 'Information about an animal mortality event.' - ), - ( - 'measurement', - 'Measurement', - 'Information about a species measurement, - such as body condition or length.' - ), - ( - 'marking', - 'Marking', - 'Information about a marking applied to an animal for identification.' - ), - ( - 'ecological_unit', - 'Ecological unit', - 'An ecological unit, such as a population unit, herd, or pack.' - ); - - - ---------------------------------------------------------------------------------------- - -- Insert feature property types - ---------------------------------------------------------------------------------------- - INSERT INTO - feature_property_type (name, description) - VALUES - ('string', 'A string type'), - ('number', 'A number type'), - ('datetime', 'A datetime type (ISO 8601)'), - ('spatial', 'A spatial type'), - ('boolean', 'A boolean type'), - ('object', 'An object type'), - ('array', 'An array type'), - ('artifact_key', 'An artifact key type (string)'); - - ---------------------------------------------------------------------------------------- - -- Insert feature properties - ---------------------------------------------------------------------------------------- - INSERT INTO - feature_property (feature_property_type_id, name, display_name, description, calculated_value) - VALUES - -- COMMON PROPERTIES - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'string'), - 'name', - 'Name', - 'The name of the record.', - false - ), - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'string'), - 'description', - 'Description', - 'The description of the record.', - false - ), - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'number'), - 'taxon_id', - 'Taxonomic identifier', - 'The unique identifier of the species associated to the record.', - false - ), - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'string'), - 'scientific_name', - 'Scientific name', - 'The scientific name of the species associated to the record.', - false - ), - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'object'), - 'date_range', - 'Date Range', - 'A date range.', - false - ), - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'datetime'), - 'start_date', - 'Start Date', - 'The start date of the record.', - false - ), - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'datetime'), - 'end_date', - 'End Date', - 'The end date of the record.', - false - ), - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'datetime'), - 'timestamp', - 'Timestamp', - 'The timestamp of the record.', - false - ), - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'spatial'), - 'geometry', - 'Geometry', - 'The location of the record.', - false - ), - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'number'), - 'count', - 'Count', - 'The count of the record.', - false - ), - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'number'), - 'latitude', - 'Latitude', - 'The latitude of the record.', - false - ), - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'number'), - 'longitude', - 'Longitude', - 'The longitude of the record.', - false - ), - ( - (SELECT feature_property_type_id FROM feature_property_type WHERE name = 'artifact_key'), - 'artifact_key', - 'Key', - 'The S3 storage key for an artifact', - true - ), - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'identifier', - 'Identifier', - 'Identifier of the record from an external source system.', - false - - ), - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'animal_identifier', - 'Animal identifier', - 'Identifier of the animal.', - false - ), - -- DEVICE DEPLOYMENT-RELATED PROPERTIES - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'device_manufacturer', - 'Device manufacturer', - 'Manufacturer of the device.', - false - ), - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'device_model', - 'Device model', - 'Model of the device.', - false - ), - ( - (SELECT feature_property_type_id from feature_property_type where name = 'number'), - 'frequency', - 'Frequency', - 'Frequency of the device.', - false - ), - ( - (SELECT feature_property_type_id from feature_property_type where name = 'number'), - 'fix_rate', - 'Fix rate', - 'Rate at which the device records a location.', - false - ), - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'frequency_unit', - 'Frequency Unit', - 'Unit of frequency of the device.', - false - ), - ( - (SELECT feature_property_type_id from feature_property_type where name = 'number'), - 'fix_rate_unit', - 'Fix rate unit', - 'Unit of the rate at which the device records a location.', - false - ), - -- CAPTURE-RELATED PROPERTIES - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'date', - 'Date', - 'A date.', - false - ), - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'time', - 'Time', - 'A time of day.', - false - ), - -- MEASUREMENT-RELATED PROPERTIES - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'measurement_type', - 'Measurement', - 'The type of measurement.', - false - ), - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'measurement_value', - 'Measurement value', - 'The value of the measurement.', - false - ), - -- MARKING-RELATED PROPERTIES - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'colour', - 'Colour', - 'The colour of a marking on an animal.', - false - ), - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'body_position', - 'Body position', - 'The location of a marking on an animal', - false - ), - -- ECOLOGICAL UNIT-RELATED PROPERTIES - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'ecological_unit_type', - 'Ecological unit', - 'The ecological unit type or category, such as population unit, herd, or pack.', - false - ), - ( - (SELECT feature_property_type_id from feature_property_type where name = 'string'), - 'ecological_unit_value', - 'Ecological unit value', - 'The value of the ecological unit type, like the name of a specific population unit if the type is population unit.', - false - ); - - ----------------------------------------------------------------------------------------- --- Assign feature properties to feature types ----------------------------------------------------------------------------------------- -INSERT INTO - feature_type_property (feature_type_id, feature_property_id, required_value) -VALUES - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'dataset'), - (SELECT feature_property_id FROM feature_property WHERE name = 'name'), - true - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'dataset'), - (SELECT feature_property_id FROM feature_property WHERE name = 'description'), - true - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'dataset'), - (SELECT feature_property_id FROM feature_property WHERE name = 'start_date'), - true - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'dataset'), - (SELECT feature_property_id FROM feature_property WHERE name = 'end_date'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), - (SELECT feature_property_id FROM feature_property WHERE name = 'animal_identifier'), - true - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), - (SELECT feature_property_id FROM feature_property WHERE name = 'start_date'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), - (SELECT feature_property_id FROM feature_property WHERE name = 'start_time'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), - (SELECT feature_property_id FROM feature_property WHERE name = 'device_manufacturer'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), - (SELECT feature_property_id FROM feature_property WHERE name = 'device_model'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), - (SELECT feature_property_id FROM feature_property WHERE name = 'frequency'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), - (SELECT feature_property_id FROM feature_property WHERE name = 'frequency_unit'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), - (SELECT feature_property_id FROM feature_property WHERE name = 'fix_rate'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'telemetry_deployment'), - (SELECT feature_property_id FROM feature_property WHERE name = 'fix_rate_unit'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'animal'), - (SELECT feature_property_id FROM feature_property WHERE name = 'taxon_id'), - true - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'animal'), - (SELECT feature_property_id FROM feature_property WHERE name = 'animal_identifier'), - true - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'animal'), - (SELECT feature_property_id FROM feature_property WHERE name = 'description'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'capture'), - (SELECT feature_property_id FROM feature_property WHERE name = 'identifier'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'capture'), - (SELECT feature_property_id FROM feature_property WHERE name = 'description'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'capture'), - (SELECT feature_property_id FROM feature_property WHERE name = 'timestamp'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'capture'), - (SELECT feature_property_id FROM feature_property WHERE name = 'geometry'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'mortality'), - (SELECT feature_property_id FROM feature_property WHERE name = 'identifier'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'mortality'), - (SELECT feature_property_id FROM feature_property WHERE name = 'description'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'mortality'), - (SELECT feature_property_id FROM feature_property WHERE name = 'timestamp'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'mortality'), - (SELECT feature_property_id FROM feature_property WHERE name = 'geometry'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'species_observation'), - (SELECT feature_property_id FROM feature_property WHERE name = 'identifier'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'species_observation'), - (SELECT feature_property_id FROM feature_property WHERE name = 'taxon_id'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'species_observation'), - (SELECT feature_property_id FROM feature_property WHERE name = 'animal_identifier'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'species_observation'), - (SELECT feature_property_id FROM feature_property WHERE name = 'description'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'species_observation'), - (SELECT feature_property_id FROM feature_property WHERE name = 'timestamp'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'species_observation'), - (SELECT feature_property_id FROM feature_property WHERE name = 'geometry'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'measurement'), - (SELECT feature_property_id FROM feature_property WHERE name = 'description'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'measurement'), - (SELECT feature_property_id FROM feature_property WHERE name = 'measurement_type'), - true - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'measurement'), - (SELECT feature_property_id FROM feature_property WHERE name = 'measurement_value'), - true - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'marking'), - (SELECT feature_property_id FROM feature_property WHERE name = 'body_position'), - true - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'marking'), - (SELECT feature_property_id FROM feature_property WHERE name = 'identifier'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'marking'), - (SELECT feature_property_id FROM feature_property WHERE name = 'colour'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'ecological_unit'), - (SELECT feature_property_id FROM feature_property WHERE name = 'ecological_unit_type'), - false - ), - ( - (SELECT feature_type_id FROM feature_type WHERE name = 'ecological_unit'), - (SELECT feature_property_id FROM feature_property WHERE name = 'ecological_unit_value'), - false - ); - - `); -} - -export async function down(knex: Knex): Promise { - await knex.raw(``); -} diff --git a/database/src/seeds/02_population_feature_tables.ts b/database/src/seeds/02_population_feature_tables.ts new file mode 100644 index 00000000..1cf050f1 --- /dev/null +++ b/database/src/seeds/02_population_feature_tables.ts @@ -0,0 +1,102 @@ +import { Knex } from 'knex'; + +/** + * Populate tables: + * - feature_property_type + * - feature_property + * - feature_type + * - feature_type_property + * + * @export + * @param {Knex} knex + * @return {*} {Promise} + */ +export async function seed(knex: Knex): Promise { + await knex.raw(` + ---------------------------------------------------------------------------------------- + -- Create tables + ---------------------------------------------------------------------------------------- + set search_path=biohub,public; + + -- populate feature_property_type table + insert into feature_property_type (name, description, record_effective_date) values ('string', 'A string type', now()) ON CONFLICT DO NOTHING; + insert into feature_property_type (name, description, record_effective_date) values ('number', 'A number type', now()) ON CONFLICT DO NOTHING; + insert into feature_property_type (name, description, record_effective_date) values ('datetime', 'A datetime type (ISO 8601)', now()) ON CONFLICT DO NOTHING; + insert into feature_property_type (name, description, record_effective_date) values ('spatial', 'A spatial type', now()) ON CONFLICT DO NOTHING; + insert into feature_property_type (name, description, record_effective_date) values ('boolean', 'A boolean type', now()) ON CONFLICT DO NOTHING; + insert into feature_property_type (name, description, record_effective_date) values ('object', 'An object type', now()) ON CONFLICT DO NOTHING; + insert into feature_property_type (name, description, record_effective_date) values ('array', 'An array type', now()) ON CONFLICT DO NOTHING; + insert into feature_property_type (name, description, record_effective_date) values ('artifact_key', 'An artifact key type (string)', now()) ON CONFLICT DO NOTHING; + + -- populate feature_property table + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('name', 'Name', 'The name of the record', (select feature_property_type_id from feature_property_type where name = 'string'), null, now()) ON CONFLICT DO NOTHING; + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('description', 'Description', 'The description of the record', (select feature_property_type_id from feature_property_type where name = 'string'), null, now()) ON CONFLICT DO NOTHING; + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('taxonomy', 'Taxonomy Id', 'The taxonomy Id associated to the record', (select feature_property_type_id from feature_property_type where name = 'number'), null, now()) ON CONFLICT DO NOTHING; + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('date_range', 'Date Range', 'A date range', (select feature_property_type_id from feature_property_type where name = 'object'), null, now()) ON CONFLICT DO NOTHING; + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('start_date', 'Start Date', 'The start date of the record', (select feature_property_type_id from feature_property_type where name = 'datetime'), (select feature_property_id from feature_property where name = 'date_range'), now()) ON CONFLICT DO NOTHING; + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('end_date', 'End Date', 'The end date of the record', (select feature_property_type_id from feature_property_type where name = 'datetime'), (select feature_property_id from feature_property where name = 'date_range'), now()) ON CONFLICT DO NOTHING; + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('timestamp', 'Timestamp', 'The timestamp of the record', (select feature_property_type_id from feature_property_type where name = 'datetime'), null, now()) ON CONFLICT DO NOTHING; + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('geometry', 'Geometry', 'The location of the record', (select feature_property_type_id from feature_property_type where name = 'spatial'), null, now()) ON CONFLICT DO NOTHING; + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('count', 'Count', 'The count of the record', (select feature_property_type_id from feature_property_type where name = 'number'), null, now()) ON CONFLICT DO NOTHING; + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('latitude', 'Latitude', 'The latitude of the record', (select feature_property_type_id from feature_property_type where name = 'number'), null, now()) ON CONFLICT DO NOTHING; + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('longitude', 'Longitude', 'The longitude of the record', (select feature_property_type_id from feature_property_type where name = 'number'), null, now()) ON CONFLICT DO NOTHING; + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date, calculated_value) values ('artifact_key', 'Key', 'The S3 storage key for an artifact', (select feature_property_type_id from feature_property_type where name = 'artifact_key'), null, now(), true) ON CONFLICT DO NOTHING; + insert into feature_property (name, display_name, description, feature_property_type_id, parent_feature_property_id, record_effective_date) values ('filename', 'Filename', 'The original name of an artifact, including extension', (select feature_property_type_id from feature_property_type where name = 'string'), null, now()) ON CONFLICT DO NOTHING; + + -- populate feature_type table + insert into feature_type (name, display_name, description, sort, record_effective_date) values ('dataset', 'Dataset', 'A related collection of data (ie: survey)', 1, now()) ON CONFLICT DO NOTHING; + insert into feature_type (name, display_name, description, sort, record_effective_date) values ('artifact', 'Artifact', 'An artifact (ie: image, document, pdf)', 2, now()) ON CONFLICT DO NOTHING; + insert into feature_type (name, display_name, description, sort, record_effective_date) values ('sample_site', 'Sample Site', 'A location at which data was collected', 3, now()) ON CONFLICT DO NOTHING; + insert into feature_type (name, display_name, description, sort, record_effective_date) values ('sample_method', 'Sample Method', 'A method used to collect data', 4, now()) ON CONFLICT DO NOTHING; + insert into feature_type (name, display_name, description, sort, record_effective_date) values ('sample_period', 'Sample Period', 'A datetime period in which data was collected', 5, now()) ON CONFLICT DO NOTHING; + insert into feature_type (name, display_name, description, sort, record_effective_date) values ('observation', 'Observation', 'An observation record', 6, now()) ON CONFLICT DO NOTHING; + insert into feature_type (name, display_name, description, sort, record_effective_date) values ('animal', 'Animal', 'An individual animal record', 7, now()) ON CONFLICT DO NOTHING; + insert into feature_type (name, display_name, description, sort, record_effective_date) values ('telemetry', 'Telemetry', 'A telemetry record', 8, now()) ON CONFLICT DO NOTHING; + + -- populate feature_type_property table + -- feature_type: dataset + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'name'), 1, now()) ON CONFLICT DO NOTHING; + -- insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'description'), 2, now()) ON CONFLICT DO NOTHING; + -- insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'taxonomy'), 3, now()) ON CONFLICT DO NOTHING; + -- insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'date_range'), 4, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'start_date'), 5, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'end_date'), 6, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'dataset'), (select feature_property_id from feature_property where name = 'geometry'), 7, now()) ON CONFLICT DO NOTHING; + + -- feature_type: artifact + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'artifact'), (select feature_property_id from feature_property where name = 'filename'), 1, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'artifact'), (select feature_property_id from feature_property where name = 'artifact_key'), 2, now()) ON CONFLICT DO NOTHING; + + -- feature_type: sample_site + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_site'), (select feature_property_id from feature_property where name = 'name'), 1, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_site'), (select feature_property_id from feature_property where name = 'description'), 2, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_site'), (select feature_property_id from feature_property where name = 'geometry'), 3, now()) ON CONFLICT DO NOTHING; + + -- feature_type: add sample_method + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_method'), (select feature_property_id from feature_property where name = 'name'), 1, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_method'), (select feature_property_id from feature_property where name = 'description'), 2, now()) ON CONFLICT DO NOTHING; + + -- feature_type: sample_period + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_period'), (select feature_property_id from feature_property where name = 'date_range'), 1, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_period'), (select feature_property_id from feature_property where name = 'start_date'), 2, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_period'), (select feature_property_id from feature_property where name = 'end_date'), 3, now()) ON CONFLICT DO NOTHING; + + -- feature_type: observation + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'taxonomy'), 1, now()) ON CONFLICT DO NOTHING; + -- insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'date_range'), 1, now()) ON CONFLICT DO NOTHING; + -- insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'start_date'), 2, now()) ON CONFLICT DO NOTHING; + -- insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'end_date'), 3, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'geometry'), 7, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'latitude'), 5, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'longitude'), 6, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'observation'), (select feature_property_id from feature_property where name = 'count'), 4, now()) ON CONFLICT DO NOTHING; + + -- feature_type: animal + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'animal'), (select feature_property_id from feature_property where name = 'taxonomy'), 1, now()) ON CONFLICT DO NOTHING; + + -- feature_type: telemetry + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'telemetry'), (select feature_property_id from feature_property where name = 'date_range'), 1, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'telemetry'), (select feature_property_id from feature_property where name = 'start_date'), 2, now()) ON CONFLICT DO NOTHING; + insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'telemetry'), (select feature_property_id from feature_property where name = 'end_date'), 3, now()) ON CONFLICT DO NOTHING; + `); +} \ No newline at end of file From b7ba233db5d4a944b671b67dc019cc046c0388ce Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Fri, 9 Aug 2024 17:44:10 -0700 Subject: [PATCH 05/10] add hierarchy endpoint for retrieving tsn hierarchies --- api/src/paths/taxonomy/taxon/tsn/hierarchy.ts | 61 ++++++------------- api/src/services/itis-service.ts | 29 +++++++-- .../src/seeds/02_population_feature_tables.ts | 2 +- 3 files changed, 45 insertions(+), 47 deletions(-) diff --git a/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts b/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts index 842d4456..2d182946 100644 --- a/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts +++ b/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts @@ -4,9 +4,9 @@ import { getAPIUserDBConnection } from '../../../../database/db'; import { ItisService } from '../../../../services/itis-service'; import { getLogger } from '../../../../utils/logger'; -const defaultLog = getLogger('paths/taxonomy/taxon/tsn'); +const defaultLog = getLogger('paths/taxonomy/taxon/tsn/hierarchy'); -export const GET: Operation = [getTaxonByTSN()]; +export const GET: Operation = [getTaxonHierarchyByTSN()]; GET.apiDoc = { description: 'Get taxon records by TSN ids.', @@ -40,39 +40,19 @@ GET.apiDoc = { content: { 'application/json': { schema: { - type: 'object', - properties: { - searchResponse: { - type: 'array', - items: { - title: 'Species', - type: 'object', - required: ['tsn', 'commonNames', 'scientificName'], - properties: { - tsn: { - type: 'integer' - }, - commonNames: { - type: 'array', - items: { - type: 'string' - } - }, - scientificName: { - type: 'string' - }, - rank: { - type: 'string' - }, - kingdom: { - type: 'string' - } - }, - additionalProperties: false - } - } - }, - additionalProperties: false + type: 'array', + items: { + title: 'Species', + type: 'object', + required: ['tsn', 'hierarchy'], + properties: { + tsn: { + type: 'integer' + }, + hierarchy: { type: 'array', items: { type: 'integer' } } + }, + additionalProperties: false + } } } } @@ -97,9 +77,9 @@ GET.apiDoc = { * * @returns {RequestHandler} */ -export function getTaxonByTSN(): RequestHandler { +export function getTaxonHierarchyByTSN(): RequestHandler { return async (req, res) => { - defaultLog.debug({ label: 'getTaxonByTSN', message: 'query params', query: req.query }); + defaultLog.debug({ label: 'getTaxonHierarchyByTSN', message: 'query params', query: req.query }); const connection = getAPIUserDBConnection(); @@ -114,12 +94,9 @@ export function getTaxonByTSN(): RequestHandler { connection.commit(); - // Overwrite default cache-control header, allow caching up to 7 days - res.setHeader('Cache-Control', 'max-age=604800'); - - res.status(200).json({ searchResponse: response }); + res.status(200).json(response); } catch (error) { - defaultLog.error({ label: 'getTaxonByTSN', message: 'error', error }); + defaultLog.error({ label: 'getTaxonHierarchyByTSN', message: 'error', error }); connection.rollback(); throw error; } finally { diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 1be27860..82cffe33 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -19,7 +19,12 @@ export type ItisSolrSearchResponse = { export type ItisSolrSearchResponseHierarchy = { tsn: string; - hierarchy: string[]; + hierarchyTSN: [string]; // Array with one item +}; + +export type TSNWithHierarchy = { + tsn: number; + hierarchy: number[]; }; /** @@ -87,7 +92,7 @@ export class ItisService { * @return {*} {Promise} * @memberof ItisService */ - async getHierarchyForTSNs(tsnIds: number[]): Promise { + async getHierarchyForTSNs(tsnIds: number[]): Promise { const url = await this.getItisSolrTsnHierarchyUrl(tsnIds); defaultLog.debug({ label: 'getHierarchyForTSNs', message: 'url', url }); @@ -98,7 +103,7 @@ export class ItisService { return []; } - return response.data.response.docs; + return this._sanitizeHierarchyData(response.data.response.docs); } /** @@ -121,6 +126,22 @@ export class ItisService { }); }; + /** + * Cleans up the ITIS hierarchy response data + * + * @param {ItisSolrSearchResponse[]} data + * @memberof ItisService + */ + _sanitizeHierarchyData = (data: ItisSolrSearchResponseHierarchy[]): TSNWithHierarchy[] => { + return data.map((item: ItisSolrSearchResponseHierarchy) => ({ + tsn: Number(item.tsn), + hierarchy: item.hierarchyTSN[0] + .split('$') + .filter((part) => part !== '') + .map((tsn) => Number(tsn)) + })); + }; + /** * Get the ITIS SORL search-by-term URL. * @@ -237,7 +258,7 @@ export class ItisService { * @memberof ItisService */ _getItisSolrHierarchyParam(): string { - return 'omitHeader=true&fl=tsn+hierarchy'; + return 'omitHeader=true&fl=tsn+hierarchyTSN'; } /** diff --git a/database/src/seeds/02_population_feature_tables.ts b/database/src/seeds/02_population_feature_tables.ts index 1cf050f1..c29d23cd 100644 --- a/database/src/seeds/02_population_feature_tables.ts +++ b/database/src/seeds/02_population_feature_tables.ts @@ -72,7 +72,7 @@ export async function seed(knex: Knex): Promise { insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_site'), (select feature_property_id from feature_property where name = 'description'), 2, now()) ON CONFLICT DO NOTHING; insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_site'), (select feature_property_id from feature_property where name = 'geometry'), 3, now()) ON CONFLICT DO NOTHING; - -- feature_type: add sample_method + -- feature_type: sample_method insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_method'), (select feature_property_id from feature_property where name = 'name'), 1, now()) ON CONFLICT DO NOTHING; insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'sample_method'), (select feature_property_id from feature_property where name = 'description'), 2, now()) ON CONFLICT DO NOTHING; From e7eaeca1b97656debb36616b431d8020169db233 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Fri, 9 Aug 2024 17:55:08 -0700 Subject: [PATCH 06/10] undo unintended commits --- app/src/features/datasets/DatasetPage.tsx | 2 +- database/src/seeds/02_population_feature_tables.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/features/datasets/DatasetPage.tsx b/app/src/features/datasets/DatasetPage.tsx index d44aee64..5fc4eb84 100644 --- a/app/src/features/datasets/DatasetPage.tsx +++ b/app/src/features/datasets/DatasetPage.tsx @@ -7,7 +7,7 @@ const DatasetPage: React.FC = () => { - To be determined + TBD diff --git a/database/src/seeds/02_population_feature_tables.ts b/database/src/seeds/02_population_feature_tables.ts index c29d23cd..9dcd2871 100644 --- a/database/src/seeds/02_population_feature_tables.ts +++ b/database/src/seeds/02_population_feature_tables.ts @@ -99,4 +99,4 @@ export async function seed(knex: Knex): Promise { insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'telemetry'), (select feature_property_id from feature_property where name = 'start_date'), 2, now()) ON CONFLICT DO NOTHING; insert into feature_type_property (feature_type_id, feature_property_id, sort, record_effective_date) values ((select feature_type_id from feature_type where name = 'telemetry'), (select feature_property_id from feature_property where name = 'end_date'), 3, now()) ON CONFLICT DO NOTHING; `); -} \ No newline at end of file +} From e7ce6a4e448bab1c3a008191133c59ad1f32e049 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Fri, 16 Aug 2024 15:50:47 -0700 Subject: [PATCH 07/10] fix comments --- api/src/paths/taxonomy/taxon/index.ts | 2 +- api/src/paths/taxonomy/taxon/tsn/index.ts | 2 +- api/src/repositories/search-index-repository.test.ts | 4 ++-- api/src/services/itis-service.ts | 5 +++-- ...ation_feature_tables.ts => 02_populate_feature_tables.ts} | 0 5 files changed, 7 insertions(+), 6 deletions(-) rename database/src/seeds/{02_population_feature_tables.ts => 02_populate_feature_tables.ts} (100%) diff --git a/api/src/paths/taxonomy/taxon/index.ts b/api/src/paths/taxonomy/taxon/index.ts index 8345f7ee..590927f9 100644 --- a/api/src/paths/taxonomy/taxon/index.ts +++ b/api/src/paths/taxonomy/taxon/index.ts @@ -9,7 +9,7 @@ export const GET: Operation = [findTaxonBySearchTerms()]; GET.apiDoc = { description: 'Find taxon records by search criteria.', - tags: ['taxon_id'], + tags: ['taxonomy'], security: [ { Bearer: [] diff --git a/api/src/paths/taxonomy/taxon/tsn/index.ts b/api/src/paths/taxonomy/taxon/tsn/index.ts index 8f21b6fd..ca366067 100644 --- a/api/src/paths/taxonomy/taxon/tsn/index.ts +++ b/api/src/paths/taxonomy/taxon/tsn/index.ts @@ -10,7 +10,7 @@ export const GET: Operation = [getTaxonByTSN()]; GET.apiDoc = { description: 'Get taxon records by TSN ids.', - tags: ['taxon_id'], + tags: ['taxonomy'], security: [ { Bearer: [] diff --git a/api/src/repositories/search-index-repository.test.ts b/api/src/repositories/search-index-repository.test.ts index adeb4268..0bef523a 100644 --- a/api/src/repositories/search-index-repository.test.ts +++ b/api/src/repositories/search-index-repository.test.ts @@ -187,7 +187,7 @@ describe('SearchIndexRepository', () => { feature_property_type_name: 'number', feature_property_id: 3, feature_property_type_id: 2, - name: 'taxon_id', + name: 'taxonomy', display_name: 'Taxonomy Id', description: 'The taxonomy Id associated to the record', parent_feature_property_id: null, @@ -392,7 +392,7 @@ describe('SearchIndexRepository', () => { feature_property_type_name: 'number', feature_property_id: 3, feature_property_type_id: 2, - name: 'taxon_id', + name: 'taxonomy', display_name: 'Taxonomy Id', description: 'The taxonomy Id associated to the record', parent_feature_property_id: null, diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 82cffe33..5378fdf2 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -89,7 +89,7 @@ export class ItisService { * Returns the parent hierarchy for multiple TSNs * * @param {number[]} tsnIds - * @return {*} {Promise} + * @return {*} {Promise} * @memberof ItisService */ async getHierarchyForTSNs(tsnIds: number[]): Promise { @@ -110,6 +110,7 @@ export class ItisService { * Cleans up the ITIS search response data * * @param {ItisSolrSearchResponse[]} data + * @return {*} {Promise} * @memberof ItisService */ _sanitizeItisData = (data: ItisSolrSearchResponse[]): TaxonSearchResult[] => { @@ -187,7 +188,7 @@ export class ItisService { } /** - * Get the ITIS SOLR search-by-tsn URL with hierarchy + * Get the ITIS SOLR search-by-tsn URL for hierarchy information * * @param {number[]} tsnIds * @return {*} {Promise} diff --git a/database/src/seeds/02_population_feature_tables.ts b/database/src/seeds/02_populate_feature_tables.ts similarity index 100% rename from database/src/seeds/02_population_feature_tables.ts rename to database/src/seeds/02_populate_feature_tables.ts From 9183875de6f3b721d127962ad98451dd6c01b9ac Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Mon, 26 Aug 2024 12:15:27 -0700 Subject: [PATCH 08/10] improve itis service types & address PR comments --- api/src/paths/taxonomy/taxon/tsn/hierarchy.ts | 18 ++++++++++-------- api/src/services/itis-service.ts | 15 ++++++++++++--- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts b/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts index 2d182946..33898d80 100644 --- a/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts +++ b/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts @@ -9,13 +9,9 @@ const defaultLog = getLogger('paths/taxonomy/taxon/tsn/hierarchy'); export const GET: Operation = [getTaxonHierarchyByTSN()]; GET.apiDoc = { - description: 'Get taxon records by TSN ids.', + description: 'Get taxon hierarchy information by TSN ids.', tags: ['taxon_id'], - security: [ - { - Bearer: [] - } - ], + security: [], parameters: [ { description: 'Taxon TSN ids.', @@ -44,12 +40,18 @@ GET.apiDoc = { items: { title: 'Species', type: 'object', + description: 'Taxon hierarchy response object with an array of parent TSNs', required: ['tsn', 'hierarchy'], properties: { tsn: { type: 'integer' }, - hierarchy: { type: 'array', items: { type: 'integer' } } + hierarchy: { + type: 'array', + description: + 'Array of parent TSNs in descending order, where the highest-ranking parent is first and the TSN for which the hierarchy was requested is last.', + items: { type: 'integer' } + } }, additionalProperties: false } @@ -83,7 +85,7 @@ export function getTaxonHierarchyByTSN(): RequestHandler { const connection = getAPIUserDBConnection(); - const tsnIds: number[] = (req.query.tsn as (string | number)[]).map(Number); + const tsnIds: number[] = (req.query.tsn as string[]).map(Number); try { await connection.open(); diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index 5378fdf2..b3f95117 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -27,6 +27,15 @@ export type TSNWithHierarchy = { hierarchy: number[]; }; +/** + * Generic base type for the ITIS Solr service + */ +type ItisSolrResponseBase = { + response: { + docs: T; + }; +}; + /** * Service for retrieving and processing taxonomic data from the Integrated Taxonomic Information System (ITIS). * @@ -48,7 +57,7 @@ export class ItisService { defaultLog.debug({ label: 'searchItisByTerm', message: 'url', url }); - const response = await axios.get(url); + const response = await axios.get>(url); if (!response.data || !response.data.response || !response.data.response.docs) { return []; @@ -76,7 +85,7 @@ export class ItisService { defaultLog.debug({ label: 'searchItisByTSN', message: 'url', url }); - const response = await axios.get(url); + const response = await axios.get>(url); if (!response.data || !response.data.response || !response.data.response.docs) { return []; @@ -97,7 +106,7 @@ export class ItisService { defaultLog.debug({ label: 'getHierarchyForTSNs', message: 'url', url }); - const response = await axios.get(url); + const response = await axios.get>(url); if (!response.data || !response.data.response || !response.data.response.docs) { return []; From be77b0b39879df2ad53e076f836999ab285e81c0 Mon Sep 17 00:00:00 2001 From: Macgregor Aubertin-Young Date: Mon, 26 Aug 2024 15:48:33 -0700 Subject: [PATCH 09/10] add unit tests --- .../taxonomy/taxon/tsn/hierarchy.test.ts | 103 ++++++++++++++ api/src/paths/taxonomy/taxon/tsn/hierarchy.ts | 8 +- api/src/services/itis-service.test.ts | 131 +++++++++++++++++- api/src/services/itis-service.ts | 7 +- 4 files changed, 241 insertions(+), 8 deletions(-) create mode 100644 api/src/paths/taxonomy/taxon/tsn/hierarchy.test.ts diff --git a/api/src/paths/taxonomy/taxon/tsn/hierarchy.test.ts b/api/src/paths/taxonomy/taxon/tsn/hierarchy.test.ts new file mode 100644 index 00000000..ad0c9c68 --- /dev/null +++ b/api/src/paths/taxonomy/taxon/tsn/hierarchy.test.ts @@ -0,0 +1,103 @@ +import Ajv from 'ajv'; +import chai, { expect } from 'chai'; +import { describe } from 'mocha'; +import sinon from 'sinon'; +import sinonChai from 'sinon-chai'; +import { GET } from '.'; +import * as db from '../../../../database/db'; +import { HTTPError } from '../../../../errors/http-error'; +import { ItisService } from '../../../../services/itis-service'; +import { getMockDBConnection, getRequestHandlerMocks } from '../../../../__mocks__/db'; +import { getHierarchyForTSNs } from './hierarchy'; + +chai.use(sinonChai); + +describe('taxonomy/taxon/tsn/hierarchy', () => { + describe('openapi schema', () => { + const ajv = new Ajv(); + + it('is valid openapi v3 schema', () => { + expect(ajv.validateSchema(GET.apiDoc as unknown as object)).to.be.true; + }); + }); + + describe('getHierarchyForTSNs', () => { + afterEach(() => { + sinon.restore(); + }); + + it('returns an empty array if no species are found', async () => { + const dbConnectionObj = getMockDBConnection(); + + sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); + + const getHierarchyForTSNsStub = sinon.stub(ItisService.prototype, 'getHierarchyForTSNs').resolves([]); + + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + mockReq.query = { + tsn: ['1', '2'] + }; + + const requestHandler = getHierarchyForTSNs(); + + await requestHandler(mockReq, mockRes, mockNext); + + expect(getHierarchyForTSNsStub).to.have.been.calledWith([1, 2]); + + expect(mockRes.statusValue).to.equal(200); + expect(mockRes.jsonValue).to.eql([]); + }); + + it('returns an array of species', async () => { + const dbConnectionObj = getMockDBConnection(); + + sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); + + const mock1 = { + tsn: 1, + commonNames: ['something'], + scientificName: 'string', + hierarchy: [3, 2, 1] + } as unknown as any; + const mock2 = { tsn: '2', commonNames: [], scientificName: 'string', hierarchy: [3, 2] } as unknown as any; + + const getHierarchyForTSNsStub = sinon.stub(ItisService.prototype, 'getHierarchyForTSNs').resolves([mock1, mock2]); + + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + mockReq.query = { + tsn: ['1', '2'] + }; + + const requestHandler = getHierarchyForTSNs(); + + await requestHandler(mockReq, mockRes, mockNext); + + expect(getHierarchyForTSNsStub).to.have.been.calledWith([1, 2]); + + expect(mockRes.jsonValue).to.eql([mock1, mock2]); + expect(mockRes.statusValue).to.equal(200); + }); + + it('catches error, and re-throws error', async () => { + const dbConnectionObj = getMockDBConnection({ rollback: sinon.stub(), release: sinon.stub() }); + + sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); + + sinon.stub(ItisService.prototype, 'getHierarchyForTSNs').rejects(new Error('a test error')); + + const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); + mockReq.query = { + tsn: ['1', '2'] + }; + + try { + const requestHandler = getHierarchyForTSNs(); + + await requestHandler(mockReq, mockRes, mockNext); + expect.fail(); + } catch (actualError) { + expect((actualError as HTTPError).message).to.equal('a test error'); + } + }); + }); +}); diff --git a/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts b/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts index 33898d80..8f20c57c 100644 --- a/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts +++ b/api/src/paths/taxonomy/taxon/tsn/hierarchy.ts @@ -6,7 +6,7 @@ import { getLogger } from '../../../../utils/logger'; const defaultLog = getLogger('paths/taxonomy/taxon/tsn/hierarchy'); -export const GET: Operation = [getTaxonHierarchyByTSN()]; +export const GET: Operation = [getHierarchyForTSNs()]; GET.apiDoc = { description: 'Get taxon hierarchy information by TSN ids.', @@ -79,9 +79,9 @@ GET.apiDoc = { * * @returns {RequestHandler} */ -export function getTaxonHierarchyByTSN(): RequestHandler { +export function getHierarchyForTSNs(): RequestHandler { return async (req, res) => { - defaultLog.debug({ label: 'getTaxonHierarchyByTSN', message: 'query params', query: req.query }); + defaultLog.debug({ label: 'getHierarchyForTSNs', message: 'query params', query: req.query }); const connection = getAPIUserDBConnection(); @@ -98,7 +98,7 @@ export function getTaxonHierarchyByTSN(): RequestHandler { res.status(200).json(response); } catch (error) { - defaultLog.error({ label: 'getTaxonHierarchyByTSN', message: 'error', error }); + defaultLog.error({ label: 'getHierarchyForTSNs', message: 'error', error }); connection.rollback(); throw error; } finally { diff --git a/api/src/services/itis-service.test.ts b/api/src/services/itis-service.test.ts index be8b8dd7..92c2c037 100644 --- a/api/src/services/itis-service.test.ts +++ b/api/src/services/itis-service.test.ts @@ -3,7 +3,7 @@ import chai, { expect } from 'chai'; import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import { ApiGeneralError } from '../errors/api-error'; -import { ItisService } from './itis-service'; +import { ItisService, ItisSolrSearchResponseHierarchy } from './itis-service'; chai.use(sinonChai); @@ -182,6 +182,65 @@ describe('ItisService', () => { }); }); + describe('getHierarchyForTSNs', async () => { + it('returns array of hierarchy objects for tsns', async () => { + const mockTsns = [1, 2]; + const mockTsnHierarchies = [ + { tsn: mockTsns[0], hierarchyTSN: ['$3$2$1$'] }, + { tsn: mockTsns[1], hierarchyTSN: ['$3$2$'] } + ]; + const mockAxiosResponse = { + data: { + response: { + docs: mockTsnHierarchies + } + } + }; + + const getItisSolrTsnHierarchyUrlStub = sinon + .stub(ItisService.prototype, 'getItisSolrTsnHierarchyUrl') + .returns('url'); + + const axiosStub = sinon.stub(axios, 'get').resolves(mockAxiosResponse); + + const itisService = new ItisService(); + + const response = await itisService.getHierarchyForTSNs(mockTsns); + + expect(getItisSolrTsnHierarchyUrlStub).to.have.been.calledWith(mockTsns); + expect(axiosStub).to.have.been.calledWith('url'); + expect(response).to.eql([ + { + tsn: mockTsns[0], + hierarchy: [3, 2, 1] + }, + { + tsn: mockTsns[1], + hierarchy: [3, 2] + } + ]); + }); + + it('catches and re-throws an error', async () => { + const mockTsns = [1, 2]; + sinon.stub(axios, 'get').rejects(new Error('a test error')); + + const itisService = new ItisService(); + const getItisSolrTsnHierarchyUrlStub = sinon + .stub(ItisService.prototype, 'getItisSolrTsnHierarchyUrl') + .resolves('url'); + + try { + await itisService.getHierarchyForTSNs(mockTsns); + + expect.fail(); + } catch (error) { + expect((error as ApiGeneralError).message).to.equal('a test error'); + expect(getItisSolrTsnHierarchyUrlStub).to.have.been.calledWith(mockTsns); + } + }); + }); + describe('getItisSolrTermSearchUrl', () => { it('throws an error when itis solr url is not set', async () => { process.env.ITIS_SOLR_URL = ''; @@ -210,6 +269,35 @@ describe('ItisService', () => { }); }); + describe('getItisSolrTsnHierarchyUrl', () => { + const mockTsns = [1]; + it('throws an error when itis solr url is not set', async () => { + process.env.ITIS_SOLR_URL = ''; + + const itisService = new ItisService(); + + try { + await itisService.getItisSolrTsnHierarchyUrl(mockTsns); + + expect.fail(); + } catch (error) { + expect((error as ApiGeneralError).message).to.equal('Failed to build ITIS query.'); + } + }); + + it('returns a valid url', async () => { + process.env.ITIS_SOLR_URL = 'https://services.itis.gov/'; + + const itisService = new ItisService(); + + const response = await itisService.getItisSolrTsnHierarchyUrl(mockTsns); + + expect(response).to.equal( + 'https://services.itis.gov/??wt=json&sort=kingdom+asc&rows=150&omitHeader=true&fl=tsn+hierarchyTSN&&q=tsn:1' + ); + }); + }); + describe('getItisSolrTsnSearchUrl', () => { it('throws an error when itis solr url is not set', async () => { process.env.ITIS_SOLR_URL = ''; @@ -237,4 +325,45 @@ describe('ItisService', () => { ); }); }); + + describe('getItisSolrTsnSearchUrl', () => { + it('throws an error when itis solr url is not set', async () => { + process.env.ITIS_SOLR_URL = ''; + + const itisService = new ItisService(); + + try { + await itisService.getItisSolrTsnSearchUrl([123]); + + expect.fail(); + } catch (error) { + expect((error as ApiGeneralError).message).to.equal('Failed to build ITIS query.'); + } + }); + + it('returns a valid url', async () => { + process.env.ITIS_SOLR_URL = 'https://services.itis.gov/'; + + const itisService = new ItisService(); + + const response = await itisService.getItisSolrTsnSearchUrl([123]); + + expect(response).to.equal( + 'https://services.itis.gov/??wt=json&sort=kingdom+asc&rows=150&omitHeader=true&fl=tsn+scientificName:nameWOInd+kingdom+parentTSN+commonNames:vernacular+updateDate+usage+rank&&q=tsn:123' + ); + }); + }); + + describe('_sanitizeHierarchyData', () => { + it('turns an ITIS hierarchy string into an array'), + () => { + const mockData: ItisSolrSearchResponseHierarchy[] = [{ tsn: '1', hierarchyTSN: ['$3$2$1$'] }]; + + const itisService = new ItisService(); + + const result = itisService._sanitizeHierarchyData(mockData); + + expect(result).to.eql([3, 2, 1]); + }; + }); }); diff --git a/api/src/services/itis-service.ts b/api/src/services/itis-service.ts index b3f95117..290df3b8 100644 --- a/api/src/services/itis-service.ts +++ b/api/src/services/itis-service.ts @@ -102,7 +102,7 @@ export class ItisService { * @memberof ItisService */ async getHierarchyForTSNs(tsnIds: number[]): Promise { - const url = await this.getItisSolrTsnHierarchyUrl(tsnIds); + const url = this.getItisSolrTsnHierarchyUrl(tsnIds); defaultLog.debug({ label: 'getHierarchyForTSNs', message: 'url', url }); @@ -140,6 +140,7 @@ export class ItisService { * Cleans up the ITIS hierarchy response data * * @param {ItisSolrSearchResponse[]} data + * @return {TSNWithHierarchy[]} * @memberof ItisService */ _sanitizeHierarchyData = (data: ItisSolrSearchResponseHierarchy[]): TSNWithHierarchy[] => { @@ -200,10 +201,10 @@ export class ItisService { * Get the ITIS SOLR search-by-tsn URL for hierarchy information * * @param {number[]} tsnIds - * @return {*} {Promise} + * @return {*} {string} * @memberof ItisService */ - async getItisSolrTsnHierarchyUrl(tsnIds: number[]): Promise { + getItisSolrTsnHierarchyUrl(tsnIds: number[]): string { const itisUrl = this._getItisSolrUrl(); if (!itisUrl) { From 7ecf5d51c87b6e4f251f40bc7df357d229f09f7e Mon Sep 17 00:00:00 2001 From: Nick Phura Date: Wed, 28 Aug 2024 10:01:11 -0700 Subject: [PATCH 10/10] Remove redundant test --- .../taxonomy/taxon/tsn/hierarchy.test.ts | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/api/src/paths/taxonomy/taxon/tsn/hierarchy.test.ts b/api/src/paths/taxonomy/taxon/tsn/hierarchy.test.ts index ad0c9c68..13167f1c 100644 --- a/api/src/paths/taxonomy/taxon/tsn/hierarchy.test.ts +++ b/api/src/paths/taxonomy/taxon/tsn/hierarchy.test.ts @@ -5,7 +5,6 @@ import sinon from 'sinon'; import sinonChai from 'sinon-chai'; import { GET } from '.'; import * as db from '../../../../database/db'; -import { HTTPError } from '../../../../errors/http-error'; import { ItisService } from '../../../../services/itis-service'; import { getMockDBConnection, getRequestHandlerMocks } from '../../../../__mocks__/db'; import { getHierarchyForTSNs } from './hierarchy'; @@ -77,27 +76,5 @@ describe('taxonomy/taxon/tsn/hierarchy', () => { expect(mockRes.jsonValue).to.eql([mock1, mock2]); expect(mockRes.statusValue).to.equal(200); }); - - it('catches error, and re-throws error', async () => { - const dbConnectionObj = getMockDBConnection({ rollback: sinon.stub(), release: sinon.stub() }); - - sinon.stub(db, 'getDBConnection').returns(dbConnectionObj); - - sinon.stub(ItisService.prototype, 'getHierarchyForTSNs').rejects(new Error('a test error')); - - const { mockReq, mockRes, mockNext } = getRequestHandlerMocks(); - mockReq.query = { - tsn: ['1', '2'] - }; - - try { - const requestHandler = getHierarchyForTSNs(); - - await requestHandler(mockReq, mockRes, mockNext); - expect.fail(); - } catch (actualError) { - expect((actualError as HTTPError).message).to.equal('a test error'); - } - }); }); });