From 07d7f3f4867bf91d5e68938dc6b52e8662d23478 Mon Sep 17 00:00:00 2001 From: jlpereira Date: Mon, 1 Jul 2024 09:59:18 -0300 Subject: [PATCH 01/57] Fix nuke modal error --- .../collection_objects/slide_breakdown/components/Nuke.vue | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/app/javascript/vue/tasks/collection_objects/slide_breakdown/components/Nuke.vue b/app/javascript/vue/tasks/collection_objects/slide_breakdown/components/Nuke.vue index a77d6283ea..8a4bab0bc9 100644 --- a/app/javascript/vue/tasks/collection_objects/slide_breakdown/components/Nuke.vue +++ b/app/javascript/vue/tasks/collection_objects/slide_breakdown/components/Nuke.vue @@ -9,7 +9,7 @@ Nuke @@ -19,10 +24,23 @@ import BlockLayout from '@/components/layout/BlockLayout.vue' import DepictionRow from './DepictionRow.vue' -const props = defineProps({ - images: { - type: Array, - required: true - } +const images = defineModel({ + type: Array, + required: true }) + + diff --git a/app/javascript/vue/tasks/images/new_filename_depicting_image/components/PanelImage.vue b/app/javascript/vue/tasks/images/new_filename_depicting_image/components/PanelImage.vue index 0a18d563b0..1b038d00f9 100644 --- a/app/javascript/vue/tasks/images/new_filename_depicting_image/components/PanelImage.vue +++ b/app/javascript/vue/tasks/images/new_filename_depicting_image/components/PanelImage.vue @@ -1,10 +1,34 @@ @@ -12,9 +36,17 @@ diff --git a/config/interface/hub/user_tasks.yml b/config/interface/hub/user_tasks.yml index 9ed3955e36..0a62fa96e1 100644 --- a/config/interface/hub/user_tasks.yml +++ b/config/interface/hub/user_tasks.yml @@ -948,7 +948,7 @@ filter_dwc_occurrences_task: index_new_filename_depicting_image_task: hub: true - name: 'TODO: Task name' + name: 'New filename depicting image' related: categories: status: prototype diff --git a/config/routes/tasks.rb b/config/routes/tasks.rb index 321972baa4..b654090d0b 100644 --- a/config/routes/tasks.rb +++ b/config/routes/tasks.rb @@ -210,7 +210,7 @@ scope :images do scope :new_filename_depicting_image, controller: 'tasks/images/new_filename_depicting_image' do - get :index, as: 'index_new_filename_depicting_image_task' + get '/', action: :index, as: 'index_new_filename_depicting_image_task' end scope :filter, controller: 'tasks/images/filter' do From fbfe6cb54280c426e08497bbe0e597f8b7ad2f54 Mon Sep 17 00:00:00 2001 From: jlpereira Date: Wed, 10 Jul 2024 19:13:20 -0300 Subject: [PATCH 16/57] Update packages --- package-lock.json | 29 +++++++++++++++-------------- package.json | 4 ++-- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7cbf048a9b..56c2e17468 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "parse-dms": "0.0.5", "pdfjs-dist": "4.4.168", "pinia": "^2.1.7", - "qs": "^6.12.2", + "qs": "^6.12.3", "shakapacker": "8.0.0", "v-network-graph": "^0.9.16", "vue": "^3.4.31", @@ -61,7 +61,7 @@ "postcss-preset-env": "^9.6.0", "raw-loader": "^4.0.2", "resolve-url-loader": "^5.0.0", - "sass": "^1.77.6", + "sass": "^1.77.7", "sass-loader": "^14.2.1", "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.10", @@ -10218,9 +10218,9 @@ } }, "node_modules/qs": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.2.tgz", - "integrity": "sha512-x+NLUpx9SYrcwXtX7ob1gnkSems4i/mGZX5SlYxwIau6RrUSODO89TR/XDGGpn5RPWSYIB+aSfuSlV5+CmbTBg==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.3.tgz", + "integrity": "sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" @@ -10627,10 +10627,11 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sass": { - "version": "1.77.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", - "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "version": "1.77.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.7.tgz", + "integrity": "sha512-9ywH75cO+rLjbrZ6en3Gp8qAMwPGBapFtlsMJoDTkcMU/bSe5a6cjKVUn5Jr4Gzg5GbP3HE8cm+02pLCgcoMow==", "dev": true, + "license": "MIT", "dependencies": { "chokidar": ">=3.0.0 <4.0.0", "immutable": "^4.0.0", @@ -19177,9 +19178,9 @@ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "qs": { - "version": "6.12.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.2.tgz", - "integrity": "sha512-x+NLUpx9SYrcwXtX7ob1gnkSems4i/mGZX5SlYxwIau6RrUSODO89TR/XDGGpn5RPWSYIB+aSfuSlV5+CmbTBg==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.3.tgz", + "integrity": "sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==", "requires": { "side-channel": "^1.0.6" } @@ -19469,9 +19470,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sass": { - "version": "1.77.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", - "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "version": "1.77.7", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.7.tgz", + "integrity": "sha512-9ywH75cO+rLjbrZ6en3Gp8qAMwPGBapFtlsMJoDTkcMU/bSe5a6cjKVUn5Jr4Gzg5GbP3HE8cm+02pLCgcoMow==", "dev": true, "requires": { "chokidar": ">=3.0.0 <4.0.0", diff --git a/package.json b/package.json index 2340b590fe..ff41ee12dc 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "parse-dms": "0.0.5", "pdfjs-dist": "4.4.168", "pinia": "^2.1.7", - "qs": "^6.12.2", + "qs": "^6.12.3", "shakapacker": "8.0.0", "v-network-graph": "^0.9.16", "vue": "^3.4.31", @@ -64,7 +64,7 @@ "postcss-preset-env": "^9.6.0", "raw-loader": "^4.0.2", "resolve-url-loader": "^5.0.0", - "sass": "^1.77.6", + "sass": "^1.77.7", "sass-loader": "^14.2.1", "style-loader": "^4.0.0", "terser-webpack-plugin": "^5.3.10", From f09ed8a3cd496441fff2227c80fc8b3deebfbd61 Mon Sep 17 00:00:00 2001 From: jlpereira Date: Thu, 11 Jul 2024 10:26:32 -0300 Subject: [PATCH 17/57] Reorder CSS attributes to remove SASS deprecated warnings --- .../vue/components/layout/BlockLayout.vue | 27 ++++++++++--------- .../biological_associations/network/App.vue | 3 ++- app/javascript/vue/tasks/dwca_import/app.vue | 3 ++- .../vue/tasks/graph/object_graph/app.vue | 3 ++- .../nomenclature/new_combination/app.vue | 9 ++++--- .../components/previewView.vue | 8 +++--- .../components/basicInformation.vue | 15 +++++++---- .../vue/tasks/type_specimens/app.vue | 7 ++--- 8 files changed, 44 insertions(+), 31 deletions(-) diff --git a/app/javascript/vue/components/layout/BlockLayout.vue b/app/javascript/vue/components/layout/BlockLayout.vue index d5414a7027..63d2fdfa0c 100644 --- a/app/javascript/vue/components/layout/BlockLayout.vue +++ b/app/javascript/vue/components/layout/BlockLayout.vue @@ -77,41 +77,44 @@ watch( } ) -watch( - expanded, - (newVal) => { emit('expandedChanged', newVal) } -) - +watch(expanded, (newVal) => { + emit('expandedChanged', newVal) +}) diff --git a/app/javascript/vue/tasks/graph/object_graph/app.vue b/app/javascript/vue/tasks/graph/object_graph/app.vue index 6bf465bc85..3ba0565379 100644 --- a/app/javascript/vue/tasks/graph/object_graph/app.vue +++ b/app/javascript/vue/tasks/graph/object_graph/app.vue @@ -137,12 +137,13 @@ onMounted(() => { diff --git a/app/javascript/vue/tasks/nomenclature/new_taxon_name/components/basicInformation.vue b/app/javascript/vue/tasks/nomenclature/new_taxon_name/components/basicInformation.vue index f92e4bad23..eb9d82ead9 100644 --- a/app/javascript/vue/tasks/nomenclature/new_taxon_name/components/basicInformation.vue +++ b/app/javascript/vue/tasks/nomenclature/new_taxon_name/components/basicInformation.vue @@ -219,32 +219,37 @@ export default { diff --git a/app/javascript/vue/tasks/images/new_filename_depicting_image/components/ImageErrorList.vue b/app/javascript/vue/tasks/images/new_filename_depicting_image/components/ImageErrorList.vue index 02eb1917df..697b2d3506 100644 --- a/app/javascript/vue/tasks/images/new_filename_depicting_image/components/ImageErrorList.vue +++ b/app/javascript/vue/tasks/images/new_filename_depicting_image/components/ImageErrorList.vue @@ -1,27 +1,28 @@ @@ -53,5 +54,6 @@ const list = defineModel({ .new-filename-error-row:last-child { border-bottom: none; + padding-bottom: 0; } diff --git a/app/javascript/vue/tasks/images/new_filename_depicting_image/components/PanelImage.vue b/app/javascript/vue/tasks/images/new_filename_depicting_image/components/PanelImage.vue index 1b038d00f9..da58cd3890 100644 --- a/app/javascript/vue/tasks/images/new_filename_depicting_image/components/PanelImage.vue +++ b/app/javascript/vue/tasks/images/new_filename_depicting_image/components/PanelImage.vue @@ -1,24 +1,7 @@ - - diff --git a/app/javascript/vue/tasks/images/new_filename_depicting_image/components/PanelImage.vue b/app/javascript/vue/tasks/images/new_filename_depicting_image/components/PanelImage.vue index da58cd3890..4a068e10c8 100644 --- a/app/javascript/vue/tasks/images/new_filename_depicting_image/components/PanelImage.vue +++ b/app/javascript/vue/tasks/images/new_filename_depicting_image/components/PanelImage.vue @@ -1,5 +1,5 @@ @@ -132,10 +140,12 @@ import SmartSelector from '@/components/ui/SmartSelector' import DepictionList from './DepictionList.vue' import VBtn from '@/components/ui/VBtn/index.vue' import VSpinner from '@/components/ui/VSpinner.vue' +import VPagination from '@/components/pagination.vue' import MoveTo from './MoveTo.vue' +import { getPagination } from '@/helpers' import { useSlice } from '@/components/radials/composables' import { Depiction, Image } from '@/routes/endpoints' -import { computed, ref } from 'vue' +import { computed, ref, onBeforeMount } from 'vue' const DROPZONE_CONFIG = { paramName: 'depiction[image_attributes][image_file]', @@ -173,6 +183,7 @@ const props = defineProps({ const parameters = ref({}) const depiction = ref() +const pagination = ref({}) const isDataDepiction = ref(false) const selectedObject = ref() const filterList = ref([]) @@ -255,12 +266,19 @@ function removeItem(item) { }) } -Depiction.where({ - depiction_object_id: props.objectId, - depiction_object_type: props.objectType -}).then(({ body }) => { - list.value = body -}) +function loadDepictions(page = 1) { + Depiction.where({ + depiction_object_id: props.objectId, + depiction_object_type: props.objectType, + per: 50, + page + }).then((response) => { + list.value = response.body + pagination.value = getPagination(response) + }) +} + +onBeforeMount(() => loadDepictions()) From f0fef8b94cfcca2984bd7ecee1f9833a2b181723 Mon Sep 17 00:00:00 2001 From: mjy Date: Fri, 26 Jul 2024 09:56:36 -0500 Subject: [PATCH 38/57] Add integer_param type checking and raise pattern for /api calls --- CHANGELOG.md | 3 ++- app/controllers/concerns/rescue_from.rb | 5 +++++ .../constants/app/taxonworks_errors.rb | 1 + db/schema.rb | 3 +-- lib/queries/asserted_distribution/filter.rb | 16 ++++++++++------ lib/queries/helpers.rb | 16 +++++++++++++++- 6 files changed, 34 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 771346f24f..83bdcd82de 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv ### Added +- Type checking pattern for integers sent to `*_id` params in the API - Add new task to quickly assign images as depictions to the objects identified in their filename [#3986] - Radial annotator: Pagination in Depictions slice @@ -19,7 +20,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv ### Fixed -- Removed bad foreign-key constraint on BiocurationClassifications +- Removed bad foreign-key constraint on BiocurationClassifications, TaxonDeterminations - Content autocomplete not scoped to projects - Some hotkeys don't work on Firefox on Linux [#3988] - Cancel previous autocomplete requests [#3982] diff --git a/app/controllers/concerns/rescue_from.rb b/app/controllers/concerns/rescue_from.rb index b827a5d487..bc44f4b062 100644 --- a/app/controllers/concerns/rescue_from.rb +++ b/app/controllers/concerns/rescue_from.rb @@ -5,6 +5,11 @@ module RescueFrom included do rescue_from 'ActiveRecord::RecordNotFound', with: :record_not_found + rescue_from 'TaxonWorks::Error::API' do |exception| + raise unless request.format == :json + render json: {error: exception}, status: :bad_request + end + rescue_from 'ActionController::ParameterMissing' do |exception| raise unless request.format == :json render json: { error: exception }, status: 400 diff --git a/config/initializers/constants/app/taxonworks_errors.rb b/config/initializers/constants/app/taxonworks_errors.rb index bb2bec6308..ca6a46f488 100644 --- a/config/initializers/constants/app/taxonworks_errors.rb +++ b/config/initializers/constants/app/taxonworks_errors.rb @@ -1 +1,2 @@ class TaxonWorks::Error < StandardError; end; +class TaxonWorks::Error::API < TaxonWorks::Error; end; diff --git a/db/schema.rb b/db/schema.rb index 05f9dcacba..fc1ff744aa 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_07_16_174544) do +ActiveRecord::Schema[7.1].define(version: 2024_07_16_191947) do # These are extensions that must be enabled in order to support this database enable_extension "btree_gin" enable_extension "fuzzystrmatch" @@ -2472,7 +2472,6 @@ add_foreign_key "tags", "projects", name: "tags_project_id_fkey" add_foreign_key "tags", "users", column: "created_by_id", name: "tags_created_by_id_fkey" add_foreign_key "tags", "users", column: "updated_by_id", name: "tags_updated_by_id_fkey" - add_foreign_key "taxon_determinations", "collection_objects", column: "biological_collection_object_id", name: "taxon_determinations_biological_collection_object_id_fkey" add_foreign_key "taxon_determinations", "otus", name: "taxon_determinations_otu_id_fkey" add_foreign_key "taxon_determinations", "projects", name: "taxon_determinations_project_id_fkey" add_foreign_key "taxon_determinations", "users", column: "created_by_id", name: "taxon_determinations_created_by_id_fkey" diff --git a/lib/queries/asserted_distribution/filter.rb b/lib/queries/asserted_distribution/filter.rb index 4696003f32..0d561c357a 100644 --- a/lib/queries/asserted_distribution/filter.rb +++ b/lib/queries/asserted_distribution/filter.rb @@ -76,17 +76,17 @@ class Filter < Query::Filter def initialize(query_params) super - @asserted_distribution_id = params[:asserted_distribution_id] + @asserted_distribution_id = integer_param(params, :asserted_distribution_id) @descendants = boolean_param(params, :descendants) @geo_json = params[:geo_json] - @geographic_area_id = params[:geographic_area_id] - @geographic_item_id = params[:geographic_item_id] + @geographic_area_id = integer_param(params,:geographic_area_id) + @geographic_item_id = integer_param(params, :geographic_item_id) @geographic_area_mode = boolean_param(params, :geographic_area_mode) @geographic_area_mode = boolean_param(params, :geographic_area_mode) - @otu_id = params[:otu_id] + @otu_id = integer_param(params, :otu_id) @presence = boolean_param(params, :presence) @radius = params[:radius].presence || 100.0 - @taxon_name_id = params[:taxon_name_id] + @taxon_name_id = integer_param(params, :taxon_name_id) @wkt = params[:wkt] set_citations_params(params) @@ -228,8 +228,12 @@ def taxon_name_id_facet j = o.join(h, Arel::Nodes::InnerJoin).on(o[:taxon_name_id].eq(h[:descendant_id])) z = h[:ancestor_id].in(taxon_name_id) - ::AssertedDistribution.joins(:otu).joins(j.join_sources).where(z) + ::AssertedDistribution.joins(:otu) + .joins('JOIN taxon_name_hierarchies tnh ON tnh.descendant_id = otus.id') + .where('tnh.ancestor_id IN ?', taxon_name_id) + else + ::AssertedDistribution.joins(:otu).where(otus: {taxon_name_id:}) end end diff --git a/lib/queries/helpers.rb b/lib/queries/helpers.rb index 7f13a1ae8c..6a663aaf6d 100644 --- a/lib/queries/helpers.rb +++ b/lib/queries/helpers.rb @@ -1,6 +1,6 @@ module Queries::Helpers - # @params params [] + # @params params # @params attribute [Symbol] # @return [Boolean, nil] def boolean_param(params, attribute) @@ -17,4 +17,18 @@ def boolean_param(params, attribute) raise end end + + # @params params + # @params attribute [Symbol] + # @return [Boolean, nil] + def integer_param(params, attribute) + return nil if attribute.nil? || params[attribute].nil? + + [params[attribute]].flatten.each do |v| + next if Utilities::Strings.only_integer(v) + raise TaxonWorks::Error::API, "values of #{attribute} must be integers (provided: #{params[attribute]})" + end + params[attribute] + end + end From 106dae4e892dad66fdc8bf498e3c1ad56ee1419d Mon Sep 17 00:00:00 2001 From: mjy Date: Fri, 26 Jul 2024 11:42:27 -0500 Subject: [PATCH 39/57] Add /sources/download_formatted --- app/controllers/sources_controller.rb | 12 +++++++++++- .../filter/components/BibliographyDownload.vue | 2 +- .../vue/tasks/sources/filter/components/bibtex.vue | 2 +- config/routes/data.rb | 1 + 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/app/controllers/sources_controller.rb b/app/controllers/sources_controller.rb index 8069615595..6e85d313b9 100644 --- a/app/controllers/sources_controller.rb +++ b/app/controllers/sources_controller.rb @@ -244,7 +244,17 @@ def generate (params[:is_public] == 'true' ? true : false), params[:style_id] ) - render '/downloads/show.json' + render '/downloads/show' + end + + # GET /sources/generate.json? + def download_formatted + params.require(:style_id) + @sources = Queries::Source::Filter.new(params).all + .order(:cached) + + f = render_to_string(:index, formats: [:bib]) + send_data(f, filename: "tw_bibliography_#{DateTime.now}.txt", type: 'text/plain') end # GET /api/v1/sources diff --git a/app/javascript/vue/tasks/sources/filter/components/BibliographyDownload.vue b/app/javascript/vue/tasks/sources/filter/components/BibliographyDownload.vue index 9f72eae71d..e1683af1de 100644 --- a/app/javascript/vue/tasks/sources/filter/components/BibliographyDownload.vue +++ b/app/javascript/vue/tasks/sources/filter/components/BibliographyDownload.vue @@ -113,7 +113,7 @@ const props = defineProps({ }) const NO_API_MESSAGE = - 'To share your project administrator must create an API token.' + 'To share your project administrator must create an project API token.' const isLoading = ref(false) const bibtex = ref() diff --git a/app/javascript/vue/tasks/sources/filter/components/bibtex.vue b/app/javascript/vue/tasks/sources/filter/components/bibtex.vue index 56d02b3e1f..3cb5fe8771 100644 --- a/app/javascript/vue/tasks/sources/filter/components/bibtex.vue +++ b/app/javascript/vue/tasks/sources/filter/components/bibtex.vue @@ -75,7 +75,7 @@ import { ref, watch, computed } from 'vue' import { downloadTextFile } from '@/helpers/files.js' const NO_API_MESSAGE = - 'To share your project administrator must create an API token.' + 'To share your project administrator must create a project API token.' const props = defineProps({ params: { diff --git a/config/routes/data.rb b/config/routes/data.rb index e2e5c773a9..87b439cfb6 100644 --- a/config/routes/data.rb +++ b/config/routes/data.rb @@ -749,6 +749,7 @@ get :csl_types, defaults: {format: :json} get :generate, defaults: {format: :json} patch :batch_update + get :download_formatted, defaults: {format: :json} end member do From f6d0c50f071c45ee1d133d837a4f9b31c2363054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Luis=20Pereira?= Date: Fri, 26 Jul 2024 13:52:55 -0300 Subject: [PATCH 40/57] Change download formatted modal --- .../components/BibliographyDownload.vue | 47 ++++++------------- 1 file changed, 15 insertions(+), 32 deletions(-) diff --git a/app/javascript/vue/tasks/sources/filter/components/BibliographyDownload.vue b/app/javascript/vue/tasks/sources/filter/components/BibliographyDownload.vue index e1683af1de..b51789f6d1 100644 --- a/app/javascript/vue/tasks/sources/filter/components/BibliographyDownload.vue +++ b/app/javascript/vue/tasks/sources/filter/components/BibliographyDownload.vue @@ -45,35 +45,14 @@ @@ -83,17 +62,17 @@ diff --git a/app/javascript/vue/tasks/collection_objects/browse/store/actions/actionNames.js b/app/javascript/vue/tasks/collection_objects/browse/store/actions/actionNames.js index d2e1f2b632..7c2c3a3fb6 100644 --- a/app/javascript/vue/tasks/collection_objects/browse/store/actions/actionNames.js +++ b/app/javascript/vue/tasks/collection_objects/browse/store/actions/actionNames.js @@ -1,6 +1,7 @@ const ActionNames = { LoadBiocurations: 'loadBiocurations', LoadCollectionObject: 'loadCollectionObject', + LoadDepictions: 'loadDepictions', LoadDwc: 'loadDwc', LoadIdentifiersFor: 'loadIdentifiersFor', LoadSoftValidation: 'loadSoftValidation', diff --git a/app/javascript/vue/tasks/collection_objects/browse/store/actions/actions.js b/app/javascript/vue/tasks/collection_objects/browse/store/actions/actions.js index 5dacd7739b..63862c51bb 100644 --- a/app/javascript/vue/tasks/collection_objects/browse/store/actions/actions.js +++ b/app/javascript/vue/tasks/collection_objects/browse/store/actions/actions.js @@ -2,6 +2,7 @@ import ActionNames from './actionNames.js' import loadBiocurations from './loadBiocurations.js' import loadCollectionObject from './loadCollectionObject' +import loadDepictions from './loadDepictions.js' import loadDwc from './loadDwc.js' import loadSoftValidation from './loadSoftValidation.js' import loadIdentifiersFor from './loadIdentifiersFor.js' @@ -12,6 +13,7 @@ import resetState from './resetState.js' const ActionFunctions = { [ActionNames.LoadBiocurations]: loadBiocurations, [ActionNames.LoadCollectionObject]: loadCollectionObject, + [ActionNames.LoadDepictions]: loadDepictions, [ActionNames.LoadDwc]: loadDwc, [ActionNames.LoadIdentifiersFor]: loadIdentifiersFor, [ActionNames.LoadSoftValidation]: loadSoftValidation, diff --git a/app/javascript/vue/tasks/collection_objects/browse/store/actions/loadCollectionObject.js b/app/javascript/vue/tasks/collection_objects/browse/store/actions/loadCollectionObject.js index 621e53607e..d5766147cf 100644 --- a/app/javascript/vue/tasks/collection_objects/browse/store/actions/loadCollectionObject.js +++ b/app/javascript/vue/tasks/collection_objects/browse/store/actions/loadCollectionObject.js @@ -56,12 +56,7 @@ export default ({ state, dispatch }, coId) => { state.navigation = body }) - Depiction.where({ - depiction_object_id: [coId], - depiction_object_type: COLLECTION_OBJECT - }).then(({ body }) => { - state.depictions = body - }) + dispatch(ActionNames.LoadDepictions, { id: coId, page: 1 }) TaxonDetermination.where({ taxon_determination_object_id: [coId], diff --git a/app/javascript/vue/tasks/collection_objects/browse/store/actions/loadDepictions.js b/app/javascript/vue/tasks/collection_objects/browse/store/actions/loadDepictions.js new file mode 100644 index 0000000000..92d712f0f9 --- /dev/null +++ b/app/javascript/vue/tasks/collection_objects/browse/store/actions/loadDepictions.js @@ -0,0 +1,14 @@ +import { Depiction } from '@/routes/endpoints' +import { COLLECTION_OBJECT } from '@/constants' +import { getPagination } from '@/helpers' + +export default ({ state }, { id, page = 1 }) => { + Depiction.where({ + depiction_object_id: [id], + depiction_object_type: COLLECTION_OBJECT, + page + }).then((response) => { + state.depictions.list = response.body + state.depictions.pagination = getPagination(response) + }) +} diff --git a/app/javascript/vue/tasks/collection_objects/browse/store/store.js b/app/javascript/vue/tasks/collection_objects/browse/store/store.js index 55d1a287e9..6eac57621d 100644 --- a/app/javascript/vue/tasks/collection_objects/browse/store/store.js +++ b/app/javascript/vue/tasks/collection_objects/browse/store/store.js @@ -11,7 +11,10 @@ const makeInitialState = () => { collectionObject: {}, container: {}, currentRepository: {}, - depictions: [], + depictions: { + list: [], + pagination: {} + }, determinations: [], dwc: {}, geographicArea: {}, From 09e6300ff0789efc28835c69f7b68a71e22c2058 Mon Sep 17 00:00:00 2001 From: mjy Date: Wed, 31 Jul 2024 14:54:21 -0500 Subject: [PATCH 53/57] Add TaxonName hierarchy module, experimenting with performant rank-including queries. --- app/models/taxon_name.rb | 2 +- app/models/taxon_name/hierarchy.rb | 94 ++++++++++++++++++++ config/initializers/constants/model/ranks.rb | 11 +++ config/routes/tasks.rb | 3 +- 4 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 app/models/taxon_name/hierarchy.rb diff --git a/app/models/taxon_name.rb b/app/models/taxon_name.rb index c7e39073a6..0205ad4e6b 100644 --- a/app/models/taxon_name.rb +++ b/app/models/taxon_name.rb @@ -162,6 +162,7 @@ def self.parent include Shared::IsData include Shared::QueryBatchUpdate include TaxonName::OtuSyncronization + include TaxonName::Hierarchy include Shared::MatrixHooks::Member include Shared::MatrixHooks::Dynamic @@ -1181,7 +1182,6 @@ def safe_self_and_ancestors self_and_ancestors .unscope(:order) .order(generations: :DESC) - .reload # TODO Why needed? Should not be .to_a end end diff --git a/app/models/taxon_name/hierarchy.rb b/app/models/taxon_name/hierarchy.rb new file mode 100644 index 0000000000..29a537e07a --- /dev/null +++ b/app/models/taxon_name/hierarchy.rb @@ -0,0 +1,94 @@ +# Methods faciliating use of `parent_id`, nomenclature ranks, ancestors and descendants +# +# TODO: consider use in refactoring full_name_hash +# +module TaxonName::Hierarchy + extend ActiveSupport::Concern + + included do + end + + module ClassMethods + + # Return summaries of TaxonName with their requested ranks/names + # @return Array of TaxonName (not a relation!) + # @param taxon_name_scope TaxonName::ActiveRecordRelation + def ranked_taxon_names(taxon_name_scope: TaxonName.none, ranks: ['order', 'family', 'genus', 'species']) + TaxonName.find_by_sql( + taxon_name_ancestors_sql(taxon_name_scope:, ranks:) + ) + end + + # A SQL string that builds a crosstab query, selecting names at the requested + # ranks into columns, i.e. summarizing ranks in a single row. + # + # The `unnest` was critical in getting this correct, as was distinct, and ordering. + # + # @return String + # @param taxon_name_scope TaxonName::ActiveRecordRelation + # required + def taxon_name_ancestors_sql(taxon_name_scope: TaxonName.none, ranks: ['order', 'family', 'genus', 'species']) + ranks = RANKS_BY_NAME.keys if ranks.blank? + + target_ranks = [] + + # Note that we need to single quote the first query in the crosstab, + # thuse the $$ and other quote weirdness + + # !!! scoping query *must* be distinct results, and without order + s = "SELECT * from crosstab( 'WITH tnq AS (" + taxon_name_scope.unscope(:select, :order).select(:id).all.distinct.to_sql.gsub(/'/,"''") + + ' ) SELECT h.descendant_id id, CASE ' + + ord = [] + + # translate `rank_class` to common name, + # works across nomenclature codes, could be optimized + # to target a specific code at some point + ranks.each_with_index do |r, i| + l = RANKS_BY_NAME[r].collect{ |x| "''#{x}''" } + target_ranks += l + ord.push "\n WHEN rank_class IN (#{ l.join(', ') }) THEN ''#{r}'' " + end + + s << ord.join("\n ") + s << 'ELSE null END cat, t.name value FROM taxon_names t ' + + "JOIN taxon_name_hierarchies h on t.id = h.ancestor_id + JOIN tnq as tnq1 on tnq1.id = h.descendant_id + WHERE + t.rank_class IN (#{target_ranks.join(', ')}) + AND t.name != ''Root'' " + + "GROUP BY 1,2,3', + $$SELECT unnest('{#{ranks.join(', ')}}'::character varying[])$$) + AS ct(id integer, " + ranks.collect{|r| "\"#{r}\" character varying"}.join(', ') + ')' + end + + # @param otu_scope Otu::ActiveRecordRelation + # distinct, no order, no select + # @return Array of Objects + # [ { "id" => 926908, + # "name" => "Diestrammena (Atachycines) horazumi", + # "taxon_name_id" => 946156, + # "order" => "Orthoptera", + # "family" => "Rhaphidophoridae", + # "genus" => "Atachycines", + # "species" => "apicalis" } ] + # + # Use `.attributes to directly return Hash + # + # A bit of a hybrid method, might also fit as an Otu class method + def ranked_otus(otu_scope: Otu.none, ranks: ['order', 'family', 'genus', 'species']) + return Otu.none if ranks.blank? || otu_scope.blank? + + tns = ::Queries::TaxonName::Filter.new({}) + tns.otu_query = otu_scope.unscope(:order).unscope(:select) + + s = 'WITH tn_anc AS (' + taxon_name_ancestors_sql(taxon_name_scope: tns.all, ranks: ) + ') ' + + ::Otu.select("otus.id, otus.name, otus.taxon_name_id, #{ranks.collect{|r| "tn_anc1.#{r}"}.join(', ')}").joins('JOIN tn_anc as tn_anc1 ON otus.taxon_name_id = tn_anc1.id') + .to_sql + + ::Otu.find_by_sql(s) + end + + end +end + diff --git a/config/initializers/constants/model/ranks.rb b/config/initializers/constants/model/ranks.rb index be7ab39ec7..d34aaaa4a1 100644 --- a/config/initializers/constants/model/ranks.rb +++ b/config/initializers/constants/model/ranks.rb @@ -161,4 +161,15 @@ def self.rank_attributes(rank) TaxonNameRelationship::OriginalCombination::OriginalSubform }.freeze + d = {} + + RANKS_LOOKUP.keys.each do |k| + r = RANKS_LOOKUP[k] + + d[r] ||= [] + d[r].push k + end + + RANKS_BY_NAME = d.freeze + end diff --git a/config/routes/tasks.rb b/config/routes/tasks.rb index c521ee14a1..8e85c7bbd3 100644 --- a/config/routes/tasks.rb +++ b/config/routes/tasks.rb @@ -653,8 +653,9 @@ # get 'set_author', as: 'set_author_for_otu_filter' # get 'set_nomen', as: 'set_nomen_for_otu_filter' # get 'set_verbatim', as: 'set_verbatim_for_otu_filter' - get 'download', action: 'download', as: 'download_otus_filter_result' + post 'download', action: 'download', as: 'download_otus_filter_result' # nested/large URIs end + end scope :people do From 8d6d36c912b59e1d936fcbcac1605459d0d2477a Mon Sep 17 00:00:00 2001 From: mjy Date: Wed, 31 Jul 2024 15:43:51 -0500 Subject: [PATCH 54/57] Ensure only target OTUs are returned, not "synonymous". --- app/models/taxon_name/hierarchy.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/models/taxon_name/hierarchy.rb b/app/models/taxon_name/hierarchy.rb index 29a537e07a..da37b604d3 100644 --- a/app/models/taxon_name/hierarchy.rb +++ b/app/models/taxon_name/hierarchy.rb @@ -27,6 +27,8 @@ def ranked_taxon_names(taxon_name_scope: TaxonName.none, ranks: ['order', 'famil # @return String # @param taxon_name_scope TaxonName::ActiveRecordRelation # required + # + # TODO: we'd have to ?UNION? in the cached/cached_author_year fields? def taxon_name_ancestors_sql(taxon_name_scope: TaxonName.none, ranks: ['order', 'family', 'genus', 'species']) ranks = RANKS_BY_NAME.keys if ranks.blank? @@ -82,8 +84,12 @@ def ranked_otus(otu_scope: Otu.none, ranks: ['order', 'family', 'genus', 'specie tns = ::Queries::TaxonName::Filter.new({}) tns.otu_query = otu_scope.unscope(:order).unscope(:select) - s = 'WITH tn_anc AS (' + taxon_name_ancestors_sql(taxon_name_scope: tns.all, ranks: ) + ') ' + - ::Otu.select("otus.id, otus.name, otus.taxon_name_id, #{ranks.collect{|r| "tn_anc1.#{r}"}.join(', ')}").joins('JOIN tn_anc as tn_anc1 ON otus.taxon_name_id = tn_anc1.id') + s = 'WITH tn_anc AS (' + taxon_name_ancestors_sql(taxon_name_scope: tns.all, ranks: ) + '), otu_limit AS (' + otu_scope.select(:id).to_sql + ')' + + ::Otu + .joins('LEFT JOIN taxon_names tn_ca on otus.taxon_name_id = tn_ca.id') + .joins('JOIN otu_limit AS ol on ol.id = otus.id') + .select("otus.id, otus.name, tn_ca.cached, tn_ca.cached_author_year, otus.taxon_name_id, #{ranks.collect{|r| "tn_anc1.#{r}"}.join(', ')}").joins('JOIN tn_anc as tn_anc1 ON otus.taxon_name_id = tn_anc1.id') + .distinct .to_sql ::Otu.find_by_sql(s) From 7c7a85027a5b6a3e0566ce6a2e3377f6ffc609c0 Mon Sep 17 00:00:00 2001 From: mjy Date: Wed, 31 Jul 2024 15:48:07 -0500 Subject: [PATCH 55/57] Move otu table logic ot helper. Maybe ultimately to lib/export if advanced. --- .../tasks/otus/filter_controller.rb | 10 ++++++++ app/helpers/otus_helper.rb | 24 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/app/controllers/tasks/otus/filter_controller.rb b/app/controllers/tasks/otus/filter_controller.rb index 33121c7d9b..e2747fd5a2 100644 --- a/app/controllers/tasks/otus/filter_controller.rb +++ b/app/controllers/tasks/otus/filter_controller.rb @@ -1,3 +1,13 @@ class Tasks::Otus::FilterController < ApplicationController include TaskControllerConfiguration + + def download + @otus = ::Queries::Otu::Filter.new(params).all + + # See helpers/otus_helper.rb + send_data helpers.ranked_otu_table(@otus), + type: 'text', + filename: "otus_#{DateTime.now}.tsv" + end + end diff --git a/app/helpers/otus_helper.rb b/app/helpers/otus_helper.rb index cebc4eccfa..ae20fd9b5c 100644 --- a/app/helpers/otus_helper.rb +++ b/app/helpers/otus_helper.rb @@ -302,4 +302,28 @@ def add_distribution_geo_json(otu, target) h end + def ranked_otu_table(otus) + d = TaxonName.ranked_otus(otu_scope: @otus) + tbl = %w{otu_id order family genus species otu_name taxon_name taxon_name_author_year} + output = StringIO.new + output.puts ::CSV.generate_line(tbl, col_sep: "\t", encoding: Encoding::UTF_8) + + d.each do |o| + output.puts ::CSV.generate_line( + [ + o.id, + o['order'], + o['family'], + o['genus'], + o['species'], + o.name, + o.cached, + o.cached_author_year + ], + col_sep: "\t", encoding: Encoding::UTF_8) + end + + output.string + end + end From 33bc02f35fc53242688376ecb4bb61f2916e0e59 Mon Sep 17 00:00:00 2001 From: mjy Date: Wed, 31 Jul 2024 16:09:22 -0500 Subject: [PATCH 56/57] Fix Original combination with/out facet --- CHANGELOG.md | 1 + lib/queries/taxon_name/filter.rb | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c8ef40c1d..b2b756f1d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv ### Fixed +- TaxonName filter Original combination with/out facet (both with and without) - Removed deprecated GoogleMap georeference form [#3991] - Print label generation [#3992] - Generating a TaxonWorks Download for a bibtex result failing [#3997] diff --git a/lib/queries/taxon_name/filter.rb b/lib/queries/taxon_name/filter.rb index 05c46c0b0c..408b5b2191 100644 --- a/lib/queries/taxon_name/filter.rb +++ b/lib/queries/taxon_name/filter.rb @@ -448,9 +448,9 @@ def not_specified_facet def original_combination_facet return nil if original_combination.nil? if original_combination - ::Protonym.joins(:original_combination_relationships) + ::Protonym.joins(:original_combination_relationships).distinct else - ::Protonym.where.missing(:original_combination_relationships) + ::Protonym.left_joins(:original_combination_relationships).where(taxon_name_relationships: {id: nil}) end end From aeae40552b40864f52632fcb003335a01f214d91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hern=C3=A1n=20Lucas=20Pereira?= Date: Wed, 31 Jul 2024 22:41:49 -0300 Subject: [PATCH 57/57] Preparing for version 0.43.0 release [skip ci] --- CHANGELOG.md | 7 ++++++- lib/taxonworks/version.rb | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2b756f1d2..c244938385 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ This project does not yet adheres to [Semantic Versioning](https://semv ## [unreleased] +\- + +## [0.43.0] - 2024-07-31 + ### Added - Task to add image and as depictions to the objects identified in their filename [#3986] @@ -4708,7 +4712,8 @@ _Special thanks to Tom Klein for his amazing open-source contributions on this r - Loosing input page numbers when switching tabs on New Taxon Name task [#1532]: https://github.com/SpeciesFileGroup/taxonworks/issues/1532 -[unreleased]: https://github.com/SpeciesFileGroup/taxonworks/compare/v0.42.0..development +[unreleased]: https://github.com/SpeciesFileGroup/taxonworks/compare/v0.43.0..development +[0.43.0]: https://github.com/SpeciesFileGroup/taxonworks/compare/v0.42.0...v0.43.0 [0.42.0]: https://github.com/SpeciesFileGroup/taxonworks/compare/v0.41.1...v0.42.0 [0.41.1]: https://github.com/SpeciesFileGroup/taxonworks/compare/v0.41.0...v0.41.1 [0.41.0]: https://github.com/SpeciesFileGroup/taxonworks/compare/v0.40.6...v0.41.0 diff --git a/lib/taxonworks/version.rb b/lib/taxonworks/version.rb index 0eb6359297..5a7ab570cc 100644 --- a/lib/taxonworks/version.rb +++ b/lib/taxonworks/version.rb @@ -1,3 +1,3 @@ module TaxonWorks - VERSION = "0.42.0" + VERSION = '0.43.0' end