diff --git a/CHANGELOG.md b/CHANGELOG.md index 573986bc1f..3b531900b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +### 1.9.0 + +* Add parameter to taxon concept show API internal endpoint to trimmed response for the mobile app + ### 1.8.6 * Schedule rebuild job to run daily on staging diff --git a/app/controllers/api/v1/taxon_concepts_controller.rb b/app/controllers/api/v1/taxon_concepts_controller.rb index aa7350f132..9c402613b5 100644 --- a/app/controllers/api/v1/taxon_concepts_controller.rb +++ b/app/controllers/api/v1/taxon_concepts_controller.rb @@ -31,7 +31,7 @@ def show s = Species::ShowTaxonConceptSerializerCites end render :json => @taxon_concept, - :serializer => s + :serializer => s, :trimmed => params[:trimmed] end protected diff --git a/app/models/species/taxon_concept_prefix_matcher.rb b/app/models/species/taxon_concept_prefix_matcher.rb index cb5069a9f9..f0d7a5874f 100644 --- a/app/models/species/taxon_concept_prefix_matcher.rb +++ b/app/models/species/taxon_concept_prefix_matcher.rb @@ -73,7 +73,7 @@ def initialize_query end @query = @query. - select('id, full_name, rank_name, name_status, + select('id, full_name, rank_name, name_status, author_year, ARRAY_AGG_NOTNULL( DISTINCT CASE WHEN matched_name != full_name THEN matched_name ELSE NULL @@ -85,7 +85,7 @@ def initialize_query rank_display_name_en, rank_display_name_es, rank_display_name_fr'). where(type_of_match: types_of_match). group([ - :id, :full_name, :rank_name, :name_status, :rank_order, + :id, :full_name, :rank_name, :name_status, :author_year, :rank_order, :rank_display_name_en, :rank_display_name_es, :rank_display_name_fr ]) diff --git a/app/serializers/species/autocomplete_taxon_concept_serializer.rb b/app/serializers/species/autocomplete_taxon_concept_serializer.rb index e50aec72d0..6dcf168041 100644 --- a/app/serializers/species/autocomplete_taxon_concept_serializer.rb +++ b/app/serializers/species/autocomplete_taxon_concept_serializer.rb @@ -1,5 +1,5 @@ class Species::AutocompleteTaxonConceptSerializer < ActiveModel::Serializer - attributes :id, :full_name, :rank_name, :name_status, :matching_names + attributes :id, :full_name, :author_year, :rank_name, :name_status, :matching_names def rank_name rank_with_locale = "rank_display_name_#{I18n.locale.to_s}" diff --git a/app/serializers/species/cites_listing_change_serializer.rb b/app/serializers/species/cites_listing_change_serializer.rb index 9db5d68c27..4f50a0bc73 100644 --- a/app/serializers/species/cites_listing_change_serializer.rb +++ b/app/serializers/species/cites_listing_change_serializer.rb @@ -3,6 +3,21 @@ class Species::CitesListingChangeSerializer < Species::ListingChangeSerializer :hash_full_note_en, :hash_display, :nomenclature_note_en, :nomenclature_note_fr, :nomenclature_note_es + def include_is_addition? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_nomenclature_note_fr? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_nomenclature_note_es? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + def change_type if object.change_type_name == ChangeType::RESERVATION_WITHDRAWAL "w" diff --git a/app/serializers/species/cites_suspension_serializer.rb b/app/serializers/species/cites_suspension_serializer.rb index d1072a1668..4485b7badc 100644 --- a/app/serializers/species/cites_suspension_serializer.rb +++ b/app/serializers/species/cites_suspension_serializer.rb @@ -7,10 +7,21 @@ class Species::CitesSuspensionSerializer < ActiveModel::Serializer :start_notification def geo_entity - object['geo_entity_en'] + @options[:trimmed] == 'true' ? object['geo_entity_en'].slice('iso_code2') : object['geo_entity_en'] end def start_notification - object['start_notification'] + @options[:trimmed] == 'true' ? object['start_notification'].except('date') : object['start_notification'] end + + def include_nomenclature_note_fr? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_nomenclature_note_es? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + end diff --git a/app/serializers/species/eu_decision_serializer.rb b/app/serializers/species/eu_decision_serializer.rb index ed46803262..672646d0fa 100644 --- a/app/serializers/species/eu_decision_serializer.rb +++ b/app/serializers/species/eu_decision_serializer.rb @@ -12,20 +12,40 @@ class Species::EuDecisionSerializer < ActiveModel::Serializer :private_url, :intersessional_decision_id + def include_nomenclature_note_fr? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_nomenclature_note_es? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_private_url? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_intersessional_decision_id? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + def eu_decision_type - object['eu_decision_type'] + @options[:trimmed] == 'true' ? object['eu_decision_type'].slice('name') : object['eu_decision_type'] end def srg_history - object['srg_history'] + @options[:trimmed] == 'true' ? object['srg_history'].except('description') : object['srg_history'] end def geo_entity - object['geo_entity_en'] + @options[:trimmed] == 'true' ? object['geo_entity_en'].slice('iso_code2') : object['geo_entity_en'] end def start_event - object['start_event'] + @options[:trimmed] == 'true' ? object['start_event'].except('date') : object['start_event'] end def source @@ -33,7 +53,7 @@ def source end def term - object['term_en'] + @options[:trimmed] == 'true' ? object['term_en'].slice('name') : object['term_en'] end def private_url diff --git a/app/serializers/species/eu_listing_change_serializer.rb b/app/serializers/species/eu_listing_change_serializer.rb index 8eaa1946bf..1140ae49f9 100644 --- a/app/serializers/species/eu_listing_change_serializer.rb +++ b/app/serializers/species/eu_listing_change_serializer.rb @@ -4,6 +4,21 @@ class Species::EuListingChangeSerializer < Species::ListingChangeSerializer :hash_full_note_en, :hash_display, :nomenclature_note_en, :nomenclature_note_fr, :nomenclature_note_es + def include_change_type_class? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_nomenclature_note_fr? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_nomenclature_note_es? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + def change_type if object.change_type_name == ChangeType::RESERVATION_WITHDRAWAL "w" diff --git a/app/serializers/species/quota_serializer.rb b/app/serializers/species/quota_serializer.rb index 14f719a016..e7e99164f5 100644 --- a/app/serializers/species/quota_serializer.rb +++ b/app/serializers/species/quota_serializer.rb @@ -5,8 +5,23 @@ class Species::QuotaSerializer < ActiveModel::Serializer :geo_entity, :unit + def include_nomenclature_note_fr? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_nomenclature_note_es? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_publication_date_formatted? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + def geo_entity - object['geo_entity_en'] + @options[:trimmed] == 'true' ? object['geo_entity_en'].slice('iso_code2') : object['geo_entity_en'] end def unit diff --git a/app/serializers/species/show_taxon_concept_serializer.rb b/app/serializers/species/show_taxon_concept_serializer.rb index a14ac8cfc2..60fa0679d3 100644 --- a/app/serializers/species/show_taxon_concept_serializer.rb +++ b/app/serializers/species/show_taxon_concept_serializer.rb @@ -10,6 +10,41 @@ class Species::ShowTaxonConceptSerializer < ActiveModel::Serializer has_many :taxon_concept_references, :serializer => Species::ReferenceSerializer, :key => :references + def include_parent_id? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_nomenclature_notification? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_subspecies? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_kingdom_name? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_species_name? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_rank_name? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_name_status? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + def rank_name object.data['rank_name'] end @@ -90,7 +125,14 @@ def distributions_with_tags_and_references end def distributions - distributions_with_tags_and_references + @options[:trimmed] == 'true' ? distributions_with_tags_and_references_trimmed : distributions_with_tags_and_references + end + + def distributions_with_tags_and_references_trimmed + Distribution.from('api_distributions_view distributions'). + where(taxon_concept_id: object.id). + select("iso_code2, ARRAY_TO_STRING(tags, ',') AS tags_list"). + order('iso_code2').all end def subspecies @@ -105,7 +147,7 @@ def standard_references end def distribution_references - distributions_with_tags_and_references + @options[:trimmed] == 'true' ? distributions_with_tags_and_references_trimmed : distributions_with_tags_and_references end def cache_key @@ -115,7 +157,8 @@ def cache_key object.updated_at, object.dependents_updated_at, object.m_taxon_concept.try(:updated_at) || "", - scope.current_user ? true : false + scope.current_user ? true : false, + @options[:trimmed] == 'true' ? true : false ] Rails.logger.debug "CACHE KEY: #{key.inspect}" key diff --git a/app/serializers/species/show_taxon_concept_serializer_cites.rb b/app/serializers/species/show_taxon_concept_serializer_cites.rb index 09059bd852..7cfd4f67e6 100644 --- a/app/serializers/species/show_taxon_concept_serializer_cites.rb +++ b/app/serializers/species/show_taxon_concept_serializer_cites.rb @@ -9,6 +9,31 @@ class Species::ShowTaxonConceptSerializerCites < Species::ShowTaxonConceptSerial :key => :eu_listings has_many :eu_decisions, :serializer => Species::EuDecisionSerializer + def include_distribution_references? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_standard_references? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_taxon_concept_references? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_cites_listing? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_eu_listing? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + def quotas Quota.from('api_cites_quotas_view trade_restrictions'). where(" diff --git a/app/serializers/species/show_taxon_concept_serializer_cms.rb b/app/serializers/species/show_taxon_concept_serializer_cms.rb index 2576922f15..60aff1f8ff 100644 --- a/app/serializers/species/show_taxon_concept_serializer_cms.rb +++ b/app/serializers/species/show_taxon_concept_serializer_cms.rb @@ -5,6 +5,26 @@ class Species::ShowTaxonConceptSerializerCms < Species::ShowTaxonConceptSerializ :key => :cms_listings has_many :cms_instruments, :serializer => Species::CmsInstrumentsSerializer + def include_standard_references? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_taxon_concept_references? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_distribution_references? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + + def include_cms_listing? + return true unless @options[:trimmed] + @options[:trimmed] == 'false' + end + def cms_listing_changes MCmsListingChange.from('cms_listing_changes_mview listing_changes_mview'). where( diff --git a/db/migrate/20220808124523_add_author_year_to_autocomplete_view.rb b/db/migrate/20220808124523_add_author_year_to_autocomplete_view.rb new file mode 100644 index 0000000000..59f86dd411 --- /dev/null +++ b/db/migrate/20220808124523_add_author_year_to_autocomplete_view.rb @@ -0,0 +1,15 @@ +class AddAuthorYearToAutocompleteView < ActiveRecord::Migration + def up + execute "DROP VIEW IF EXISTS auto_complete_taxon_concepts_view" + execute "CREATE VIEW auto_complete_taxon_concepts_view AS #{view_sql('20220808123526', 'auto_complete_taxon_concepts_view')}" + execute File.read(File.expand_path('../../mviews/011_rebuild_auto_complete_taxon_concepts_mview.sql', __FILE__)) + execute "SELECT * FROM rebuild_auto_complete_taxon_concepts_mview()" + end + + def down + execute "DROP VIEW IF EXISTS auto_complete_taxon_concepts_view" + execute "CREATE VIEW auto_complete_taxon_concepts_view AS #{view_sql('20150318150923', 'auto_complete_taxon_concepts_view')}" + execute File.read(File.expand_path('../../mviews/011_rebuild_auto_complete_taxon_concepts_mview.sql', __FILE__)) + execute "SELECT * FROM rebuild_auto_complete_taxon_concepts_mview()" + end +end diff --git a/db/mviews/011_rebuild_auto_complete_taxon_concepts_mview.sql b/db/mviews/011_rebuild_auto_complete_taxon_concepts_mview.sql new file mode 100644 index 0000000000..6a668c7458 --- /dev/null +++ b/db/mviews/011_rebuild_auto_complete_taxon_concepts_mview.sql @@ -0,0 +1,33 @@ +CREATE OR REPLACE FUNCTION rebuild_auto_complete_taxon_concepts_mview() RETURNS void + LANGUAGE plpgsql + AS $$ + BEGIN + + DROP table IF EXISTS auto_complete_taxon_concepts_mview_tmp CASCADE; + RAISE INFO 'Creating auto complete taxon concepts materialized view (tmp)'; + CREATE TABLE auto_complete_taxon_concepts_mview_tmp AS + SELECT * FROM auto_complete_taxon_concepts_view; + + RAISE INFO 'Creating indexes on auto complete taxon concepts materialized view (tmp)'; + + --this one used for Species+ autocomplete (both main and higher taxa in downloads) + CREATE INDEX ON auto_complete_taxon_concepts_mview_tmp + USING BTREE(name_for_matching text_pattern_ops, taxonomy_is_cites_eu, type_of_match, show_in_species_plus_ac); + --this one used for Checklist autocomplete + CREATE INDEX ON auto_complete_taxon_concepts_mview_tmp + USING BTREE(name_for_matching text_pattern_ops, taxonomy_is_cites_eu, type_of_match, show_in_checklist_ac); + --this one used for Trade autocomplete + CREATE INDEX ON auto_complete_taxon_concepts_mview_tmp + USING BTREE(name_for_matching text_pattern_ops, taxonomy_is_cites_eu, type_of_match, show_in_trade_ac); + --this one used for Trade internal autocomplete + CREATE INDEX ON auto_complete_taxon_concepts_mview_tmp + USING BTREE(name_for_matching text_pattern_ops, taxonomy_is_cites_eu, type_of_match, show_in_trade_internal_ac); + + RAISE INFO 'Swapping auto complete taxon concepts materialized view'; + DROP table IF EXISTS auto_complete_taxon_concepts_mview CASCADE; + ALTER TABLE auto_complete_taxon_concepts_mview_tmp RENAME TO auto_complete_taxon_concepts_mview; + + END; + $$; + +COMMENT ON FUNCTION rebuild_taxon_concepts_mview() IS 'Procedure to rebuild taxon concepts materialized view in the database.'; diff --git a/db/views/auto_complete_taxon_concepts_view/20220808123526.sql b/db/views/auto_complete_taxon_concepts_view/20220808123526.sql new file mode 100644 index 0000000000..65f1fee06f --- /dev/null +++ b/db/views/auto_complete_taxon_concepts_view/20220808123526.sql @@ -0,0 +1,202 @@ +WITH synonyms_segmented(taxon_concept_id, full_name, author_year, matched_taxon_concept_id, matched_name, matched_name_segment) AS ( + SELECT + atc.id, + atc.full_name, + atc.author_year, + tc.id, + tc.full_name, + UPPER(REGEXP_SPLIT_TO_TABLE(tc.full_name, ' ')) + FROM taxon_concepts tc + JOIN taxon_relationships tr + ON tr.other_taxon_concept_id = tc.id + JOIN taxon_relationship_types trt + ON trt.id = tr.taxon_relationship_type_id + AND trt.name = 'HAS_SYNONYM' + JOIN taxon_concepts atc + ON atc.id = tr.taxon_concept_id + WHERE tc.name_status = 'S' AND atc.name_status = 'A' +), scientific_names_segmented(taxon_concept_id, full_name, author_year, matched_taxon_concept_id, matched_name, matched_name_segment) AS ( + SELECT + id, + taxon_concepts.full_name, + taxon_concepts.author_year, + id, + taxon_concepts.full_name, + UPPER(REGEXP_SPLIT_TO_TABLE(full_name, ' ')) + FROM taxon_concepts +), unlisted_subspecies_segmented(taxon_concept_id, full_name, author_year, matched_taxon_concept_id, matched_name, matched_name_segment) AS ( + SELECT + parents.id, + parents.full_name, + parents.author_year, + taxon_concepts.id, + taxon_concepts.full_name, + UPPER(REGEXP_SPLIT_TO_TABLE(taxon_concepts.full_name, ' ')) + FROM taxon_concepts + JOIN ranks ON ranks.id = taxon_concepts.rank_id + AND ranks.name IN ('SUBSPECIES', 'VARIETY') + JOIN taxon_concepts parents + ON parents.id = taxon_concepts.parent_id + WHERE taxon_concepts.name_status NOT IN ('S', 'T', 'N') + AND parents.name_status = 'A' + + EXCEPT + + SELECT + parents.id, + parents.full_name, + parents.author_year, + taxon_concepts.id, + taxon_concepts.full_name, + UPPER(REGEXP_SPLIT_TO_TABLE(taxon_concepts.full_name, ' ')) + FROM taxon_concepts + JOIN ranks ON ranks.id = taxon_concepts.rank_id + AND ranks.name IN ('SUBSPECIES') -- VARIETY not here on purpose + JOIN taxon_concepts parents + ON parents.id = taxon_concepts.parent_id + JOIN taxonomies ON taxonomies.id = taxon_concepts.taxonomy_id + WHERE taxon_concepts.name_status NOT IN ('S', 'T', 'N') + AND parents.name_status = 'A' + AND CASE + WHEN taxonomies.name = 'CMS' + THEN (taxon_concepts.listing->'cms_historically_listed')::BOOLEAN + ELSE (taxon_concepts.listing->'cites_historically_listed')::BOOLEAN + OR (taxon_concepts.listing->'eu_historically_listed')::BOOLEAN + END +), taxon_common_names AS ( + SELECT + taxon_commons.*, + common_names.name + FROM taxon_commons + JOIN common_names + ON common_names.id = taxon_commons.common_name_id + JOIN languages + ON languages.id = common_names.language_id + AND languages.iso_code1 IN ('EN', 'ES', 'FR') +), common_names_segmented(taxon_concept_id, full_name, author_year, matched_taxon_concept_id, matched_name, matched_name_segment) AS ( + SELECT + taxon_concept_id, + taxon_concepts.full_name, + taxon_concepts.author_year, + NULL::INT, + taxon_common_names.name, + UPPER(REGEXP_SPLIT_TO_TABLE(taxon_common_names.name, E'\\s|''')) + FROM taxon_common_names + JOIN taxon_concepts + ON taxon_common_names.taxon_concept_id = taxon_concepts.id +), taxon_common_names_dehyphenated AS ( + SELECT + taxon_concept_id, + taxon_concepts.full_name, + taxon_concepts.author_year, + NULL::INT, + taxon_common_names.name, + UPPER(REPLACE(taxon_common_names.name, '-', ' ')) + FROM taxon_common_names + JOIN taxon_concepts + ON taxon_common_names.taxon_concept_id = taxon_concepts.id + WHERE STRPOS(taxon_common_names.name, '-') > 0 +), common_names_segmented_dehyphenated AS ( + SELECT taxon_concept_id, full_name, author_year, matched_taxon_concept_id, matched_name, matched_name_segment + FROM common_names_segmented + UNION + SELECT taxon_concept_id, full_name, author_year, matched_taxon_concept_id, matched_name, REGEXP_SPLIT_TO_TABLE(matched_name_segment, E'-') + FROM common_names_segmented + WHERE STRPOS(matched_name_segment, '-') > 0 + UNION + SELECT * FROM taxon_common_names_dehyphenated +), all_names_segmented_cleaned AS ( + SELECT * FROM ( + SELECT taxon_concept_id, full_name, author_year, matched_taxon_concept_id, matched_name, + CASE + WHEN POSITION(matched_name_segment IN + UPPER(matched_name) + ) = 1 THEN UPPER(matched_name) + ELSE matched_name_segment + END, type_of_match + FROM ( + SELECT *, 'SELF' AS type_of_match + FROM scientific_names_segmented + UNION + SELECT *, 'SYNONYM' + FROM synonyms_segmented + UNION + SELECT *, 'SUBSPECIES' + FROM unlisted_subspecies_segmented + UNION + SELECT *, 'COMMON_NAME' + FROM common_names_segmented_dehyphenated + ) all_names_segmented + ) all_names_segmented_no_prefixes + WHERE LENGTH(matched_name_segment) >= 3 +), taxa_with_visibility_flags AS ( + SELECT taxon_concepts.id, + CASE + WHEN taxonomies.name = 'CITES_EU' THEN TRUE + ELSE FALSE + END AS taxonomy_is_cites_eu, + name_status, + ranks.name AS rank_name, + ranks.display_name_en AS rank_display_name_en, + ranks.display_name_es AS rank_display_name_es, + ranks.display_name_fr AS rank_display_name_fr, + ranks.taxonomic_position AS rank_order, + taxon_concepts.taxonomic_position, + CASE + WHEN + name_status = 'A' + AND ( + ranks.name != 'SUBSPECIES' + AND ranks.name != 'VARIETY' + OR taxonomies.name = 'CITES_EU' + AND ( + (listing->'cites_historically_listed')::BOOLEAN + OR (listing->'eu_historically_listed')::BOOLEAN + ) + OR taxonomies.name = 'CMS' + AND (listing->'cms_historically_listed')::BOOLEAN + ) + THEN TRUE + ELSE FALSE + END AS show_in_species_plus_ac, + CASE + WHEN + name_status = 'A' + AND ( + ranks.name != 'SUBSPECIES' + AND ranks.name != 'VARIETY' + OR (listing->'cites_show')::BOOLEAN + ) + THEN TRUE + ELSE FALSE + END AS show_in_checklist_ac, + CASE + WHEN + taxonomies.name = 'CITES_EU' + AND ARRAY['A', 'H', 'N']::VARCHAR[] && ARRAY[name_status] + THEN TRUE + ELSE FALSE + END AS show_in_trade_ac, + CASE + WHEN + taxonomies.name = 'CITES_EU' + AND ARRAY['A', 'H', 'N', 'T']::VARCHAR[] && ARRAY[name_status] + THEN TRUE + ELSE FALSE + END AS show_in_trade_internal_ac + FROM taxon_concepts + JOIN ranks ON ranks.id = rank_id + JOIN taxonomies ON taxonomies.id = taxon_concepts.taxonomy_id +) +SELECT + t1.*, + matched_name_segment AS name_for_matching, + matched_taxon_concept_id AS matched_id, + matched_name, + full_name, + author_year, + type_of_match +FROM taxa_with_visibility_flags t1 +JOIN all_names_segmented_cleaned t2 +ON t1.id = t2.taxon_concept_id +WHERE LENGTH(matched_name_segment) >= 3;