From 698dda7f8a095c37c712a57a9d634b05ab63cfc6 Mon Sep 17 00:00:00 2001 From: jlpereira Date: Tue, 9 May 2023 13:32:45 -0300 Subject: [PATCH 01/88] Add button to add biological associations from Related modal --- CHANGELOG.md | 8 +++- .../biological_associations_graph/App.vue | 16 +++++++ .../adapters/makeBiologicalAssociation.js | 1 + .../components/BiologicalAssociationGraph.vue | 6 ++- .../components/ModalRelated.vue | 47 +++++++++++++++++-- .../composition/useGraph.js | 29 ++++++++---- 6 files changed, 91 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80a308922a..14ce51ef77 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,14 @@ This project does not yet adheres to [Semantic Versioning](https://semv ## [unreleased] -\- +### Added + +- `Add` button to add biological associations from `Related` modal in new biological associations task ## [0.32.3] - 2023-05-05 ### Added + - Add/move/return collection objects from collection object filter [#3387] - Interpretation help for `full name` facet in people filter [#3394] - Total individuals to loan show/recipient form [#3398] @@ -23,6 +26,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv - Caption and figure label editable in Depictions list in Radial annotator [#3396] ### Changed + - Pagination headers are exposed via CORS [#3380] - Updated bundle gems - Ruby 3.2 is now required as minimum @@ -33,6 +37,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv - Browse OTU biological association table contains related modal ### Fixed + - Global identifiers not appearing on community data [#3393] - Lag in selecting loan items on edit loan [#3399] - Collection object was loanable 2x in some cases @@ -3577,7 +3582,6 @@ This project does not yet adheres to [Semantic Versioning](https://semv - 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.32.3...development [0.32.3]: https://github.com/SpeciesFileGroup/taxonworks/compare/v0.32.2...v0.32.3 [0.32.2]: https://github.com/SpeciesFileGroup/taxonworks/compare/v0.32.1...v0.32.2 diff --git a/app/javascript/vue/tasks/biological_associations/biological_associations_graph/App.vue b/app/javascript/vue/tasks/biological_associations/biological_associations_graph/App.vue index a1fa292580..5b1794e317 100644 --- a/app/javascript/vue/tasks/biological_associations/biological_associations_graph/App.vue +++ b/app/javascript/vue/tasks/biological_associations/biological_associations_graph/App.vue @@ -98,6 +98,12 @@ v-if="showModalRelated" :current-graph="currentGraph" :relations="graph.getBiologicalRelationships().value" + @add:biological-associations=" + (ids) => { + addBiologicalAssociationsToGraph(ids) + showModalRelated = false + } + " @select:graph=" ($event) => { loadGraph($event.id) @@ -232,6 +238,16 @@ function saveGraph() { }) } +function addBiologicalAssociationsToGraph(ids) { + const { loadBiologicalAssociations, updateObjectByUuid } = graph.value + + loadBiologicalAssociations(ids).then((biologicalAssociations) => + biologicalAssociations.forEach(({ uuid }) => { + updateObjectByUuid(uuid, { isUnsaved: true }) + }) + ) +} + function reset() { graph.value.resetStore() setParam( diff --git a/app/javascript/vue/tasks/biological_associations/biological_associations_graph/adapters/makeBiologicalAssociation.js b/app/javascript/vue/tasks/biological_associations/biological_associations_graph/adapters/makeBiologicalAssociation.js index 8caf367298..c1f59e58af 100644 --- a/app/javascript/vue/tasks/biological_associations/biological_associations_graph/adapters/makeBiologicalAssociation.js +++ b/app/javascript/vue/tasks/biological_associations/biological_associations_graph/adapters/makeBiologicalAssociation.js @@ -12,6 +12,7 @@ export async function makeBiologicalAssociation(ba) { globalId: ba.global_id, name: ba.name, objectType: BIOLOGICAL_ASSOCIATION, + isUnsaved: false, citations: ba.citations.map((c) => makeCitation({ ...c, objectUuid: uuid })) || [], biologicalRelationship: { diff --git a/app/javascript/vue/tasks/biological_associations/biological_associations_graph/components/BiologicalAssociationGraph.vue b/app/javascript/vue/tasks/biological_associations/biological_associations_graph/components/BiologicalAssociationGraph.vue index 68871e8bea..16b2e664d9 100644 --- a/app/javascript/vue/tasks/biological_associations/biological_associations_graph/components/BiologicalAssociationGraph.vue +++ b/app/javascript/vue/tasks/biological_associations/biological_associations_graph/components/BiologicalAssociationGraph.vue @@ -244,7 +244,8 @@ const { selectedEdges, selectedNodes, setGraphName, - setNodePosition + setNodePosition, + updateObjectByUuid } = useGraph() const graph = ref() @@ -418,7 +419,8 @@ defineExpose({ save, saveBiologicalAssociations, setGraph, - downloadAsSvg + downloadAsSvg, + updateObjectByUuid }) diff --git a/app/javascript/vue/tasks/biological_associations/biological_associations_graph/components/ModalRelated.vue b/app/javascript/vue/tasks/biological_associations/biological_associations_graph/components/ModalRelated.vue index e0a9a99f4c..f169abb6ba 100644 --- a/app/javascript/vue/tasks/biological_associations/biological_associations_graph/components/ModalRelated.vue +++ b/app/javascript/vue/tasks/biological_associations/biological_associations_graph/components/ModalRelated.vue @@ -58,9 +58,25 @@ >Biological relationships containing related CollectionObjects/OTUS

- + + Add + + + @@ -71,6 +87,13 @@ v-for="item in biologicalAssociations" :key="item.id" > + + + Add + @@ -99,7 +130,7 @@ import VModal from 'components/ui/Modal.vue' import VBtn from 'components/ui/VBtn/index.vue' import VIcon from 'components/ui/VIcon/index.vue' import RadialAnnotator from 'components/radials/annotator/annotator.vue' -import { ref, onBeforeMount } from 'vue' +import { ref, onBeforeMount, computed } from 'vue' import { BiologicalAssociation, BiologicalAssociationGraph @@ -118,10 +149,20 @@ const props = defineProps({ } }) -const emit = defineEmits(['select:graph']) +const emit = defineEmits(['select:graph', 'add:biologicalAssociations']) const biologicalAssociations = ref([]) const graphs = ref([]) const isLoading = ref(false) +const selectedIds = ref([]) + +const toggleSelection = computed({ + get: () => biologicalAssociations.value.length === selectedIds.value.length, + set(value) { + selectedIds.value = value + ? biologicalAssociations.value.map((item) => item.id) + : [] + } +}) function makeObjectIdPayload() { const otuIds = [] diff --git a/app/javascript/vue/tasks/biological_associations/biological_associations_graph/composition/useGraph.js b/app/javascript/vue/tasks/biological_associations/biological_associations_graph/composition/useGraph.js index e2b7190c53..dcf4bc575b 100644 --- a/app/javascript/vue/tasks/biological_associations/biological_associations_graph/composition/useGraph.js +++ b/app/javascript/vue/tasks/biological_associations/biological_associations_graph/composition/useGraph.js @@ -224,19 +224,29 @@ export function useGraph() { } async function loadBiologicalAssociations(ids) { - return BiologicalAssociation.where({ + const { body } = await BiologicalAssociation.where({ biological_association_id: ids, extend: EXTEND_BA - }).then(async ({ body }) => { - for (const item of body) { - const ba = await makeBiologicalAssociation(item) + }) - addObject(ba.subject) - addObject(ba.object) + for (const item of body) { + const ba = await makeBiologicalAssociation(item) - state.biologicalAssociations.push(ba) - } - }) + addObject(ba.subject) + addObject(ba.object) + + state.biologicalAssociations.push(ba) + } + + return state.biologicalAssociations.filter((ba) => + body.find((item) => item.id === ba.id) + ) + } + + function updateObjectByUuid(uuid, objProps) { + const obj = getObjectByUuid(uuid) + + Object.assign(obj, objProps) } function getSourceIds() { @@ -450,6 +460,7 @@ export function useGraph() { saveGraph, setGraphName, setNodePosition, + updateObjectByUuid, ...toRefs(state) } } From 5d570e65baf0e44d3c7eab3a3914ced4dab954f4 Mon Sep 17 00:00:00 2001 From: jlpereira Date: Tue, 9 May 2023 13:42:49 -0300 Subject: [PATCH 02/88] Fix #3408 --- CHANGELOG.md | 6 ++++++ .../vue/components/Filter/Facets/Source/FacetBibtexType.vue | 6 +++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14ce51ef77..6acafa77e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,12 @@ This project does not yet adheres to [Semantic Versioning](https://semv - `Add` button to add biological associations from `Related` modal in new biological associations task +### Fixed + +- BibTeX typo [#3408] + +[#3408]: https://github.com/SpeciesFileGroup/taxonworks/issues/3408 + ## [0.32.3] - 2023-05-05 ### Added diff --git a/app/javascript/vue/components/Filter/Facets/Source/FacetBibtexType.vue b/app/javascript/vue/components/Filter/Facets/Source/FacetBibtexType.vue index f67e19f61e..5692e18081 100644 --- a/app/javascript/vue/components/Filter/Facets/Source/FacetBibtexType.vue +++ b/app/javascript/vue/components/Filter/Facets/Source/FacetBibtexType.vue @@ -1,6 +1,6 @@ \ No newline at end of file +} + From ccad96f12fedeb4e12afe2954ed8cd7dd12cffd3 Mon Sep 17 00:00:00 2001 From: mjy Date: Wed, 10 May 2023 09:44:52 -0500 Subject: [PATCH 05/88] Scaffold OtuRelationship. #257 --- .../otu_relationships_controller.rb | 94 +++++++++++++++ app/helpers/otu_relationships_helper.rb | 23 ++++ app/models/otu.rb | 39 ++++--- app/models/otu_relationship.rb | 39 +++++++ app/models/otu_relationship/disjoint.rb | 7 ++ app/models/otu_relationship/equal.rb | 7 ++ .../otu_relationship/partially_overlapping.rb | 7 ++ app/models/otu_relationship/proper_part.rb | 7 ++ .../otu_relationship/proper_part_inverse.rb | 7 ++ app/models/project.rb | 29 ++--- .../otu_relationships/_attributes.html.erb | 20 ++++ .../_attributes.json.jbuilder | 3 + app/views/otu_relationships/_form.html.erb | 32 ++++++ app/views/otu_relationships/edit.html.erb | 6 + .../otu_relationships/index.json.jbuilder | 1 + app/views/otu_relationships/list.html.erb | 25 ++++ app/views/otu_relationships/new.html.erb | 5 + app/views/otu_relationships/show.html.erb | 1 + .../otu_relationships/show.json.jbuilder | 1 + app/views/otus/_attributes.json.jbuilder | 2 +- config/interface/hub/data.yml | 64 ++++++----- config/routes.rb | 2 +- config/routes/data.rb | 9 +- ...20230509185624_create_otu_relationships.rb | 23 ++++ db/schema.rb | 107 +++++++++++++++--- spec/controllers/otus_controller_spec.rb | 1 - spec/factories/otu_relationships.rb | 7 ++ spec/models/otu_relationship_spec.rb | 14 +++ spec/rails_helper.rb | 1 + .../routing/otu_relationships_routing_spec.rb | 38 +++++++ 30 files changed, 541 insertions(+), 80 deletions(-) create mode 100644 app/controllers/otu_relationships_controller.rb create mode 100644 app/helpers/otu_relationships_helper.rb create mode 100644 app/models/otu_relationship.rb create mode 100644 app/models/otu_relationship/disjoint.rb create mode 100644 app/models/otu_relationship/equal.rb create mode 100644 app/models/otu_relationship/partially_overlapping.rb create mode 100644 app/models/otu_relationship/proper_part.rb create mode 100644 app/models/otu_relationship/proper_part_inverse.rb create mode 100644 app/views/otu_relationships/_attributes.html.erb create mode 100644 app/views/otu_relationships/_attributes.json.jbuilder create mode 100644 app/views/otu_relationships/_form.html.erb create mode 100644 app/views/otu_relationships/edit.html.erb create mode 100644 app/views/otu_relationships/index.json.jbuilder create mode 100644 app/views/otu_relationships/list.html.erb create mode 100644 app/views/otu_relationships/new.html.erb create mode 100644 app/views/otu_relationships/show.html.erb create mode 100644 app/views/otu_relationships/show.json.jbuilder create mode 100644 db/migrate/20230509185624_create_otu_relationships.rb create mode 100644 spec/factories/otu_relationships.rb create mode 100644 spec/models/otu_relationship_spec.rb create mode 100644 spec/routing/otu_relationships_routing_spec.rb diff --git a/app/controllers/otu_relationships_controller.rb b/app/controllers/otu_relationships_controller.rb new file mode 100644 index 0000000000..e7464eb34c --- /dev/null +++ b/app/controllers/otu_relationships_controller.rb @@ -0,0 +1,94 @@ +class OtuRelationshipsController < ApplicationController + include DataControllerConfiguration::ProjectDataControllerConfiguration + + before_action :set_otu_relationship, only: %i[ show edit update destroy ] + + after_action -> { set_pagination_headers(:otu_relationships) }, only: [:index], if: :json_request? + + # GET /otu_relationships or /otu_relationships.json + def index + respond_to do |format| + format.html do + @recent_objects = OtuRelationship.where(project_id: sessions_current_project_id) + .order(updated_at: :desc) + .limit(10) + render '/shared/data/all/index' + end + format.json { + @otu_relationships = OtuRelationship.where(project_id: sessions_current_project_id) + .page(params[:page]) + .per(params[:per]) + } + end + end + + # GET /otu_relationships/1 or /otu_relationships/1.json + def show + end + + def list + @otu_relationships = OtuRelationship.where(project_id: sessions_current_project_id) + .page(params[:page]) + .per(params[:per]) + end + + # GET /otu_relationships/new + def new + @otu_relationship = OtuRelationship.new + end + + # GET /otu_relationships/1/edit + def edit + end + + # POST /otu_relationships or /otu_relationships.json + def create + @otu_relationship = OtuRelationship.new(otu_relationship_params) + + respond_to do |format| + if @otu_relationship.save + format.html { redirect_to otu_relationship_url(@otu_relationship), notice: 'Otu relationship was successfully created.' } + format.json { render :show, status: :created, location: @otu_relationship } + else + format.html { render :new, status: :unprocessable_entity } + format.json { render json: @otu_relationship.errors, status: :unprocessable_entity } + end + end + end + + # PATCH/PUT /otu_relationships/1 or /otu_relationships/1.json + def update + respond_to do |format| + if @otu_relationship.update(otu_relationship_params) + format.html { redirect_to otu_relationship_url(@otu_relationship), notice: 'Otu relationship was successfully updated.' } + format.json { render :show, status: :ok, location: @otu_relationship.metamorphosize } + else + format.html { render :edit, status: :unprocessable_entity } + format.json { render json: @otu_relationship.errors, status: :unprocessable_entity } + end + end + end + + # DELETE /otu_relationships/1 or /otu_relationships/1.json + def destroy + @otu_relationship.destroy + + respond_to do |format| + format.html { redirect_to otu_relationships_url, notice: 'Otu relationship was successfully destroyed.' } + format.json { head :no_content } + end + end + + private + def set_otu_relationship + @otu_relationship = OtuRelationship.where(project_id: sessions_current_project_id).find(params[:id]) + end + + def otu_relationship_params + params.require(:otu_relationship).permit( + :subject_otu_id, + :type, + :object_otu_id + ) + end +end diff --git a/app/helpers/otu_relationships_helper.rb b/app/helpers/otu_relationships_helper.rb new file mode 100644 index 0000000000..1e113cd9e9 --- /dev/null +++ b/app/helpers/otu_relationships_helper.rb @@ -0,0 +1,23 @@ +module OtuRelationshipsHelper + + def otu_relationship_tag(otu_relationship) + return nil if otu_relationship.nil? + [otu_tag(otu_relationship.subject_otu), otu_relationship_type_label(otu_relationship), otu_tag(otu_relationship.object_otu) ].compact.join(' ') + end + + def otu_relationship_label(otu_relationship) + return nil if otu_relationship.nil? + [label_for_otu(otu_relationship.subject_otu), otu_relationship.type_name, label_for_otu(otu_relationship.object_otu) ].compact.join(' ') + end + + def otu_relationship_link(otu_relationship) + return nil if otu_relationship.nil? + link_to(otu_relationship, otu_relationship_label(otu_relationship)) + end + + def otu_relationship_type_label(otu_relationship) + return nil if otu_relationship.nil? + otu_relationship.type_name + end + +end diff --git a/app/models/otu.rb b/app/models/otu.rb index 7f98494079..57833ed1a1 100644 --- a/app/models/otu.rb +++ b/app/models/otu.rb @@ -51,7 +51,7 @@ class Otu < ApplicationRecord GRAPH_ENTRY_POINTS = [:asserted_distributions, :biological_associations, :common_names, :contents, :data_attributes] belongs_to :taxon_name, inverse_of: :otus - + # Why? Could be combination too. belongs_to :protonym, -> { where(type: 'Protonym') }, foreign_key: :taxon_name_id @@ -78,8 +78,11 @@ class Otu < ApplicationRecord has_many :content_topics, through: :contents, source: :topic - scope :with_taxon_name_id, -> (taxon_name_id) { where(taxon_name_id: taxon_name_id) } - scope :with_name, -> (name) { where(name: name) } + has_many :otu_relationships, foreign_key: :subject_otu_id + has_many :related_otu_relationships, foreign_key: :object_otu_id + + scope :with_taxon_name_id, -> (taxon_name_id) { where(taxon_name_id:) } + scope :with_name, -> (name) { where(name:) } validate :check_required_fields @@ -146,7 +149,7 @@ def self.coordinate_otus(otu_id) # @param taxon_name_id [The id of a valid TaxonName] def self.descendant_of_taxon_name(taxon_name_id = []) ids = [taxon_name_id].flatten.compact.uniq - + o = Otu.arel_table t = TaxonName.arel_table h = TaxonNameHierarchy.arel_table @@ -221,7 +224,7 @@ def self.used_recently(user_id, project_id, used_on = '') when 'BiologicalAssociation' t.project(t['biological_association_object_id'], t['updated_at']).from(t) .where( - t['updated_at'].gt(1.weeks.ago).and( + t['updated_at'].gt(1.week.ago).and( t['biological_association_object_type'].eq('Otu') ) ) @@ -230,7 +233,7 @@ def self.used_recently(user_id, project_id, used_on = '') .order(t['updated_at'].desc) else t.project(t['otu_id'], t['updated_at']).from(t) - .where(t['updated_at'].gt( 1.weeks.ago )) + .where(t['updated_at'].gt( 1.week.ago )) .where(t['updated_by_id'].eq(user_id)) .where(t['project_id'].eq(project_id)) .order(t['updated_at'].desc) @@ -256,23 +259,23 @@ def self.select_optimized(user_id, project_id, target = nil) r = used_recently(user_id, project_id, target) h = { quick: [], - pinboard: Otu.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a, + pinboard: Otu.pinned_by(user_id).where(pinboard_items: {project_id:}).to_a, recent: [] } if target && !r.empty? h[:recent] = ( Otu.where('"otus"."id" IN (?)', r.first(10) ).to_a + - Otu.where(project_id: project_id, created_by_id: user_id, created_at: 3.hours.ago..Time.now) + Otu.where(project_id:, created_by_id: user_id, created_at: 3.hours.ago..Time.now) .order('updated_at DESC') .limit(3).to_a ).uniq.sort{|a,b| a.otu_name <=> b.otu_name} h[:quick] = ( - Otu.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a + + Otu.pinned_by(user_id).where(pinboard_items: {project_id:}).to_a + Otu.where('"otus"."id" IN (?)', r.first(4) ).to_a).uniq.sort{|a,b| a.otu_name <=> b.otu_name} else - h[:recent] = Otu.where(project_id: project_id).order('updated_at DESC').limit(10).to_a.sort{|a,b| a.otu_name <=> b.otu_name} - h[:quick] = Otu.pinned_by(user_id).where(pinboard_items: {project_id: project_id}).to_a.sort{|a,b| a.otu_name <=> b.otu_name} + h[:recent] = Otu.where(project_id:).order('updated_at DESC').limit(10).to_a.sort{|a,b| a.otu_name <=> b.otu_name} + h[:quick] = Otu.pinned_by(user_id).where(pinboard_items: {project_id:}).to_a.sort{|a,b| a.otu_name <=> b.otu_name} end h @@ -285,12 +288,12 @@ def current_collection_objects # @return [Boolean] # whether or not this otu is coordinate (see coordinate_otus) with this otu def coordinate_with?(otu_id) - Otu.coordinate_otus(otu_id).where(otus: {id: id}).any? + Otu.coordinate_otus(otu_id).where(otus: {id:}).any? end # TODO: Deprecate for helper method, HTML does not belong here def otu_name - if !name.blank? + if name.present? name elsif !taxon_name_id.nil? taxon_name.cached_html_name_and_author_year @@ -392,10 +395,10 @@ def taxa_by_geographic_area file_name1 = '/tmp/' + area + '_geographic_area_' + Time.now.to_i.to_s + '.csv' file_name2 = '/tmp/' + area + '_collection_object_' + Time.now.to_i.to_s + '.csv' c1 = GeographicArea.where(name: area).pluck(:id) - c2 = GeographicArea.where("parent_id in (?)", c1).pluck(:id) - c3 = GeographicArea.where("parent_id in (?)", c2).pluck(:id) + c2 = GeographicArea.where('parent_id in (?)', c1).pluck(:id) + c3 = GeographicArea.where('parent_id in (?)', c2).pluck(:id) c = c1 + c2 + c3 - ad = AssertedDistribution.where("geographic_area_id in (?)", c) + ad = AssertedDistribution.where('geographic_area_id in (?)', c) CSV.open(file_name1, 'w') do |csv| csv << ['genus', 'species', 'geographic_area'] @@ -420,7 +423,7 @@ def taxa_by_geographic_area end end - co = CollectionObject.joins(:collecting_event).where("collecting_events.geographic_area_id in (?)", c) + co = CollectionObject.joins(:collecting_event).where('collecting_events.geographic_area_id in (?)', c) CSV.open(file_name2, 'w') do |csv| csv << ['genus', 'species', 'geographic_area', 'lat', 'long'] @@ -468,7 +471,7 @@ def sv_taxon_name def sv_duplicate_otu unless Otu.with_taxon_name_id(taxon_name_id).with_name(name).not_self(self).with_project_id(project_id).empty? - m = "Another OTU with an identical nomenclature (taxon name) and name exists in this project" + m = 'Another OTU with an identical nomenclature (taxon name) and name exists in this project' soft_validations.add(:base, m ) end end diff --git a/app/models/otu_relationship.rb b/app/models/otu_relationship.rb new file mode 100644 index 0000000000..802980378e --- /dev/null +++ b/app/models/otu_relationship.rb @@ -0,0 +1,39 @@ +# TODO: controller spec refactor +# +# +# An OtuRelationhip links two OTUs in a euler/rcc5 relatinship. +# +# # @!attribute subject_otu_id +# @return [integer] +# the OTU on the left side of the relationhip +# +# @!attribute object_otu_id +# @return [integer] +# the OTU on the right side of the relationhip +# +# @!attribute type +# @return [String] +# The rails STI name for the relationship type, e.g. OtuRelationship::Disjoint +# See also http://api.checklistbank.org/vocab/taxonconceptreltype, https://github.com/tdwg/tcs2/blob/9eebd904001baab61476852bda53851317161186/master/tcs.yaml#L207 +# +class OtuRelationship < ApplicationRecord + include Housekeeping + # include SoftValidation + include Shared::Citations + # include Shared::Identifiers + include Shared::Notes + include Shared::Tags + # include Shared::Depictions + include Shared::Confidences + # include Shared::OriginRelationship + # TODO: the conscensus names for our new things + # include Shared::Taxonomy + include Shared::IsData + + belongs_to :subject_otu , class_name: 'Otu', inverse_of: :otu_relationships + belongs_to :object_otu , class_name: 'Otu', inverse_of: :related_otu_relationships + + validates_presence_of :subject_otu + validates_presence_of :object_otu + +end diff --git a/app/models/otu_relationship/disjoint.rb b/app/models/otu_relationship/disjoint.rb new file mode 100644 index 0000000000..9ee09c59fb --- /dev/null +++ b/app/models/otu_relationship/disjoint.rb @@ -0,0 +1,7 @@ +class OtuRelationship::Disjoint < OtuRelationship + + def type_name + 'excludes' + end + +end diff --git a/app/models/otu_relationship/equal.rb b/app/models/otu_relationship/equal.rb new file mode 100644 index 0000000000..c045ece549 --- /dev/null +++ b/app/models/otu_relationship/equal.rb @@ -0,0 +1,7 @@ +class OtuRelationship::Equal < OtuRelationship + + def type_name + 'equals' + end + +end diff --git a/app/models/otu_relationship/partially_overlapping.rb b/app/models/otu_relationship/partially_overlapping.rb new file mode 100644 index 0000000000..9607a7e5e2 --- /dev/null +++ b/app/models/otu_relationship/partially_overlapping.rb @@ -0,0 +1,7 @@ +class OtuRelationship::PartiallyOverlapping < OtuRelationship + + def type_name + 'overlaps' + end + +end diff --git a/app/models/otu_relationship/proper_part.rb b/app/models/otu_relationship/proper_part.rb new file mode 100644 index 0000000000..0cfedef8c9 --- /dev/null +++ b/app/models/otu_relationship/proper_part.rb @@ -0,0 +1,7 @@ +class OtuRelationship::ProperPart < OtuRelationship + + def type_name + 'included in' + end + +end diff --git a/app/models/otu_relationship/proper_part_inverse.rb b/app/models/otu_relationship/proper_part_inverse.rb new file mode 100644 index 0000000000..38e91227b8 --- /dev/null +++ b/app/models/otu_relationship/proper_part_inverse.rb @@ -0,0 +1,7 @@ +class OtuRelationship::ProperPartInverse < OtuRelationship + + def type_name + 'includes' + end + +end diff --git a/app/models/project.rb b/app/models/project.rb index 10c2903735..f9eefadb1f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -12,7 +12,7 @@ # # @!attribute api_access_token # @return [String, nil] -# The token is not intended to be private. Generating one is akin to indicating that your project's data are public, and they will be exposed in the general API to all. The token is primarily for tracking "anonymous" use. +# The token is not intended to be private. Generating one is akin to indicating that your project's data are public, and they will be exposed in the general API to all. The token is primarily for tracking "anonymous" use. # class Project < ApplicationRecord include Housekeeping::Users @@ -23,19 +23,19 @@ class Project < ApplicationRecord attr_accessor :without_root_taxon_name attr_accessor :clear_api_access_token attr_accessor :set_new_api_access_token - + # ORDER MATTERS - # Used in `nuke` order (not available in production UI), but + # Used in `nuke` order (not available in production UI), but # ultimately also for dumping records. # # The intent is to use `delete_all` for speed. This means # that callbacks are *not* fired (associated destroys). MANIFEST = %w{ - Observation + Observation CitationTopic Citation - Note - CharacterState + Note + CharacterState Protocol AlternateValue DataAttribute @@ -88,13 +88,14 @@ class Project < ApplicationRecord SequenceRelationship Extract GeneAttribute - ObservationMatrixColumn + ObservationMatrixColumn ObservationMatrixColumnItem ObservationMatrixRow ObservationMatrixRowItem ObservationMatrix CollectionObject Otu + OtuRelationship TaxonName Descriptor ProjectMember @@ -102,8 +103,8 @@ class Project < ApplicationRecord DatasetRecordField DatasetRecord ImportDataset - } - + }.freeze + has_many :project_members, dependent: :restrict_with_error has_many :users, through: :project_members @@ -144,7 +145,7 @@ def annotators begin MANIFEST.each do |o| klass = o.safe_constantize - + end @@ -155,7 +156,7 @@ def annotators end end - + # !! This is not production ready. # @return [Boolean] @@ -184,14 +185,14 @@ def nuke end end - + # TODO: boot load checks def root_taxon_name # Calling TaxonName is a hack to load the required has_many into Project, # "has_many :taxon_names" is invoked through TaxonName within Housekeeping::Project - # Within TaxonName closure_tree (appears to?) require a database connection. + # Within TaxonName closure_tree (appears to?) require a database connection. # Since we shouldn't (can't?) initiate a connection prior to a require_dependency # we simply load TaxonName for the first time here. @@ -218,7 +219,7 @@ def generate_api_access_token end def destroy_api_access_token - self.api_access_token = nil + self.api_access_token = nil end end diff --git a/app/views/otu_relationships/_attributes.html.erb b/app/views/otu_relationships/_attributes.html.erb new file mode 100644 index 0000000000..b9b180b14b --- /dev/null +++ b/app/views/otu_relationships/_attributes.html.erb @@ -0,0 +1,20 @@ +

+ Subject otu: + <%= otu_link(@otu_relationship.subject_otu) %> +

+ +

+ Type: + <%= @otu_relationship.type_name %> +

+ +

+ Object otu: + <%= otu_link(@otu_relationship.object_otu) %> +

+ +

+ Project: + <%= @otu_relationship.project_id %> +

+ diff --git a/app/views/otu_relationships/_attributes.json.jbuilder b/app/views/otu_relationships/_attributes.json.jbuilder new file mode 100644 index 0000000000..52f9b1c786 --- /dev/null +++ b/app/views/otu_relationships/_attributes.json.jbuilder @@ -0,0 +1,3 @@ +json.extract! otu_relationship, :id, :subject_otu_id, :type, :object_otu_id, :project_id, :created_at, :updated_at + +json.partial! '/shared/data/all/metadata', object: otu_relationship diff --git a/app/views/otu_relationships/_form.html.erb b/app/views/otu_relationships/_form.html.erb new file mode 100644 index 0000000000..3b7a456584 --- /dev/null +++ b/app/views/otu_relationships/_form.html.erb @@ -0,0 +1,32 @@ +<%= form_with(model: otu_relationship.metamorphosize) do |form| %> + <% if otu_relationship.errors.any? %> +
+

<%= pluralize(otu_relationship.errors.count, "error") %> prohibited this otu_relationship from being saved:

+ +
    + <% otu_relationship.errors.each do |error| %> +
  • <%= error.full_message %>
  • + <% end %> +
+
+ <% end %> + +
+ <%= form.label :subject_otu_id %> + <%= render '/otus/otu_picker', otu: @otu_relationship.subject_otu, field_name: 'Subject OTU', object_method: :subject_otu_id, f: form -%> +
+ +
+ <%= form.label :type %> + <%= form.select :type, options: %w{OtuRelationship::Disjoint OtuRelationship::Equal OtuRelationship::ProperPartInverse OtuRelationship::ProperPart OtuRelationship::PartiallyOverlapping } %> +
+ +
+ <%= form.label :object_otu_id %> + <%= render '/otus/otu_picker', otu: @otu_relationship.object_otu, field_name: 'Object OTU', object_method: :object_otu_id, f: form -%> +
+ +
+ <%= form.submit %> +
+<% end %> diff --git a/app/views/otu_relationships/edit.html.erb b/app/views/otu_relationships/edit.html.erb new file mode 100644 index 0000000000..9f41eec129 --- /dev/null +++ b/app/views/otu_relationships/edit.html.erb @@ -0,0 +1,6 @@ +

Editing Otu Relationship

+ +<%= render 'form', otu_relationship: @otu_relationship %> + +<%= link_to 'Show', @otu_relationship %> | +<%= link_to 'Back', otu_relationships_path %> diff --git a/app/views/otu_relationships/index.json.jbuilder b/app/views/otu_relationships/index.json.jbuilder new file mode 100644 index 0000000000..2e0f05d76f --- /dev/null +++ b/app/views/otu_relationships/index.json.jbuilder @@ -0,0 +1 @@ +json.array! @otu_relationships, partial: "otu_relationships/attributes", as: :otu_relationship diff --git a/app/views/otu_relationships/list.html.erb b/app/views/otu_relationships/list.html.erb new file mode 100644 index 0000000000..cf6ef16417 --- /dev/null +++ b/app/views/otu_relationships/list.html.erb @@ -0,0 +1,25 @@ +<%= render("/shared/data/all/list/list_header", objects: @otu_relationships) -%> + + + + + <%= fancy_th_tag(name: 'Subject OTU') -%> + <%= fancy_th_tag(name: 'Type') -%> + <%= fancy_th_tag(name: 'Object OTU') -%> + <%= fancy_th_tag(name: 'Updated by', group: 'housekeeping') -%> + <%= fancy_th_tag(name: 'Last updated', group: 'housekeeping') -%> + + + + + <% @otu_relationships.each do |otu_relationship| %> + <%= content_tag(:tr, class: :contextMenuCells) do -%> + + + + <%= fancy_metadata_cells_tag(otu_relationship) -%> + <% end %> + <% end %> + +
<%= otu_tag( otu_relationship.subject_otu ) %><%= otu_relationship.type_name %><%= otu_tag( otu_relationship.object_otu ) %>
+ diff --git a/app/views/otu_relationships/new.html.erb b/app/views/otu_relationships/new.html.erb new file mode 100644 index 0000000000..aa82553486 --- /dev/null +++ b/app/views/otu_relationships/new.html.erb @@ -0,0 +1,5 @@ +

New Otu Relationship

+ +<%= render 'form', otu_relationship: @otu_relationship %> + +<%= link_to 'Back', otu_relationships_path %> diff --git a/app/views/otu_relationships/show.html.erb b/app/views/otu_relationships/show.html.erb new file mode 100644 index 0000000000..496aaa00df --- /dev/null +++ b/app/views/otu_relationships/show.html.erb @@ -0,0 +1 @@ +<%= render(partial: 'shared/data/project/show', locals: {object: @otu_relationship}) -%> diff --git a/app/views/otu_relationships/show.json.jbuilder b/app/views/otu_relationships/show.json.jbuilder new file mode 100644 index 0000000000..bf002129c1 --- /dev/null +++ b/app/views/otu_relationships/show.json.jbuilder @@ -0,0 +1 @@ +json.partial! "otu_relationships/attributes", otu_relationship: @otu_relationship diff --git a/app/views/otus/_attributes.json.jbuilder b/app/views/otus/_attributes.json.jbuilder index 33798ddb63..d6c793f1bf 100644 --- a/app/views/otus/_attributes.json.jbuilder +++ b/app/views/otus/_attributes.json.jbuilder @@ -5,4 +5,4 @@ if extend_response_with('taxonomy') json.taxonomy do json.merge! otu.taxonomy end -end \ No newline at end of file +end diff --git a/config/interface/hub/data.yml b/config/interface/hub/data.yml index fb558ff406..db40115fb2 100644 --- a/config/interface/hub/data.yml +++ b/config/interface/hub/data.yml @@ -5,7 +5,7 @@ # status - one of [complete, stable, prototype, unknown (default) ] (see user_tasks.yml) # categories - an array of one or more of [CollectingEvent, CollectionObject, Source, TaxonName] # hide - default false -# description - +# description - # related_models - an array of similar models # # See lib/hub/data.rb for integration (or CONFIG_DATA) @@ -28,11 +28,11 @@ Core: # a Section - TaxonNameRelationship CollectingEvent: status: :prototype - categories: + categories: - collecting_event related_models: - Georeference - CollectionObject: + CollectionObject: status: :stable categories: - collection_object @@ -42,14 +42,14 @@ Core: # a Section - TaxonDetermination - Repository Descriptor: - categories: + categories: - matrix - dna - related_models: + related_models: - ObservationMatrix status: :prototype Observation: - categories: + categories: - matrix - collection_object related_models: @@ -64,7 +64,7 @@ Core: # a Section status: :stable shared: true categories: - - source + - source related_models: - Serial - People @@ -72,14 +72,14 @@ Core: # a Section status: :stable related_models: - Source - - Person + - Person categories: - source BiologicalAssociation: status: :prototype related_models: - BiologicalRelationship - categories: + categories: - biology Sequence: status: :prototype @@ -91,12 +91,12 @@ Supporting: AssertedDistribution: status: :prototype categories: - - source + - source BiologicalAssociationsGraph: status: :prototype related_models: - Source - categories: + categories: - biology BiologicalRelationship: status: :prototype @@ -110,7 +110,7 @@ Supporting: - matrix CollectionProfile: status: :prototype - categories: + categories: - collection_object CollectionObjectObservation: status: :prototype @@ -147,7 +147,7 @@ Supporting: Documentation: status: :prototype related_models: - - Document + - Document ImportDataset: status: :stable Depiction: @@ -163,7 +163,7 @@ Supporting: status: :prototype related_models: - BiologicalRelationship - categories: + categories: - biology - dna GeneAttribute: @@ -177,7 +177,7 @@ Supporting: Georeference: status: :prototype categories: - - collecting_event + - collecting_event GeographicArea: status: :stable shared: true @@ -224,18 +224,18 @@ Supporting: - Loan - CollectionObject - Otu - ObservationMatrixColumn: - categories: + ObservationMatrixColumn: + categories: - matrix - related_models: + related_models: - ObservationMatrix - Descriptor status: :prototype hide: true - ObservationMatrixColumnItem: - categories: + ObservationMatrixColumnItem: + categories: - matrix - related_models: + related_models: - ObservationMatrix - Descriptor status: :prototype @@ -251,7 +251,7 @@ Supporting: categories: - matrix related_models: - - ObservationMatrix + - ObservationMatrix OriginRelationship: catgories: - collection_object @@ -264,6 +264,12 @@ Supporting: status: :prototype related_models: - ControlledVocabularyTerm + OtuRelationship: + status: :prototype + categories: + - biology + related_models: + - Otu Organization: status: :stable shared: true @@ -301,9 +307,9 @@ Supporting: - CollectingObject Serial: status: :prototype - shared: true + shared: true categories: - - source + - source related_models: - Source - Person @@ -318,26 +324,26 @@ Supporting: categories: - collection_object related_models: - - Otu + - Otu - CollectionObject TaxonNameClassification: status: :stable categories: - - nomenclature + - nomenclature related_models: - TaxonName - TaxonNameRelationship TaxonNameRelationship: status: :stable categories: - - nomenclature + - nomenclature related_model: - TaxonName - TaxonNameClassification TypeMaterial: status: :prototype categories: - - nomenclature + - nomenclature - collection_object Annotations: Attribution: @@ -347,7 +353,7 @@ Annotations: Citation: status: :stable categories: - - source + - source Confidence: status: prototype DataAttribute: diff --git a/config/routes.rb b/config/routes.rb index 48dce6577e..6186fd9ec2 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,7 +3,7 @@ # See initializer config/initializers/routing_draw.rb -# Routes are moved to config/routes. +# Routes are moved to config/routes. TaxonWorks::Application.routes.draw do draw :base draw :data diff --git a/config/routes/data.rb b/config/routes/data.rb index 1e2013d78b..40de8991fe 100644 --- a/config/routes/data.rb +++ b/config/routes/data.rb @@ -502,7 +502,7 @@ end resources :otus do - concerns [:data_routes ] + concerns [:data_routes] resources :biological_associations, shallow: true, only: [:index], defaults: {format: :json} resources :asserted_distributions, shallow: true, only: [:index], defaults: {format: :json} resources :common_names, shallow: true, only: [:index], defaults: {format: :json} @@ -550,6 +550,13 @@ end end +resources :otu_relationships do + collection do + get :list + end + concerns [:data_routes] +end + resources :organizations do collection do get :autocomplete, defaults: {format: :json} diff --git a/db/migrate/20230509185624_create_otu_relationships.rb b/db/migrate/20230509185624_create_otu_relationships.rb new file mode 100644 index 0000000000..3d7a2ebd24 --- /dev/null +++ b/db/migrate/20230509185624_create_otu_relationships.rb @@ -0,0 +1,23 @@ +class CreateOtuRelationships < ActiveRecord::Migration[6.1] + def change + create_table :otu_relationships do |t| + t.integer :subject_otu_id, null: false, index: true + t.string :type, null: false + t.integer :object_otu_id, null: false, index: true + t.references :project, foreign_key: true + + t.integer :created_by_id, null: false, index: true + t.integer :updated_by_id, null: false, index: true + + t.timestamps + end + + add_foreign_key :otu_relationships, :otus, column: :subject_otu_id, primary_key: :id + add_foreign_key :otu_relationships, :otus, column: :object_otu_id, primary_key: :id + + add_foreign_key :otu_relationships, :users, column: :created_by_id, primary_key: :id + add_foreign_key :otu_relationships, :users, column: :updated_by_id, primary_key: :id + + + end +end diff --git a/db/schema.rb b/db/schema.rb index 823ab918b1..267b96f1bd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,13 +10,14 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_03_07_041322) do +ActiveRecord::Schema.define(version: 2023_05_09_185624) do # These are extensions that must be enabled in order to support this database enable_extension "fuzzystrmatch" enable_extension "hstore" enable_extension "plpgsql" enable_extension "postgis" + enable_extension "postgis_raster" enable_extension "tablefunc" create_table "alternate_values", id: :serial, force: :cascade do |t| @@ -171,6 +172,58 @@ t.index ["updated_by_id"], name: "bio_rel_updated_by" end + create_table "cached_map_item_translations", force: :cascade do |t| + t.bigint "geographic_item_id" + t.bigint "translated_geographic_item_id" + t.string "cached_map_type" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["cached_map_type"], name: "cmgit_cmt" + t.index ["geographic_item_id", "translated_geographic_item_id", "cached_map_type"], name: "cmgit_translation", unique: true + t.index ["geographic_item_id"], name: "cmgit_gi" + t.index ["translated_geographic_item_id"], name: "cmgit_tgi" + end + + create_table "cached_map_items", force: :cascade do |t| + t.bigint "otu_id", null: false + t.bigint "geographic_item_id", null: false + t.string "type" + t.integer "reference_count" + t.boolean "is_absent" + t.string "level0_geographic_name" + t.string "level1_geographic_name" + t.string "level2_geographic_name" + t.bigint "project_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.boolean "untranslated" + t.index ["geographic_item_id"], name: "index_cached_map_items_on_geographic_item_id" + t.index ["otu_id", "geographic_item_id"], name: "index_cached_map_items_on_otu_id_and_geographic_item_id" + t.index ["otu_id"], name: "index_cached_map_items_on_otu_id" + t.index ["project_id"], name: "index_cached_map_items_on_project_id" + end + + create_table "cached_map_registers", force: :cascade do |t| + t.string "cached_map_register_object_type" + t.bigint "cached_map_register_object_id" + t.bigint "project_id" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["cached_map_register_object_type", "cached_map_register_object_id"], name: "index_cached_map_registers_on_cached_map_register_object" + t.index ["project_id"], name: "index_cached_map_registers_on_project_id" + end + + create_table "cached_maps", force: :cascade do |t| + t.bigint "otu_id", null: false + t.geography "geometry", limit: {:srid=>4326, :type=>"geometry", :geographic=>true} + t.integer "reference_count" + t.bigint "project_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["otu_id"], name: "index_cached_maps_on_otu_id" + t.index ["project_id"], name: "index_cached_maps_on_project_id" + end + create_table "character_states", id: :serial, force: :cascade do |t| t.string "name", null: false t.string "label", null: false @@ -380,8 +433,8 @@ end create_table "confidences", id: :serial, force: :cascade do |t| - t.string "confidence_object_type", null: false t.integer "confidence_object_id", null: false + t.string "confidence_object_type", null: false t.integer "position", null: false t.integer "created_by_id", null: false t.integer "updated_by_id", null: false @@ -613,8 +666,8 @@ end create_table "documentation", id: :serial, force: :cascade do |t| - t.string "documentation_object_type", null: false t.integer "documentation_object_id", null: false + t.string "documentation_object_type", null: false t.integer "document_id", null: false t.integer "project_id", null: false t.integer "created_by_id", null: false @@ -632,7 +685,7 @@ create_table "documents", id: :serial, force: :cascade do |t| t.string "document_file_file_name", null: false t.string "document_file_content_type", null: false - t.bigint "document_file_file_size", null: false + t.integer "document_file_file_size", null: false t.datetime "document_file_updated_at", null: false t.integer "project_id", null: false t.integer "created_by_id", null: false @@ -841,8 +894,8 @@ t.string "vernacularName" t.string "waterBody" t.string "year" - t.string "dwc_occurrence_object_type" t.integer "dwc_occurrence_object_id" + t.string "dwc_occurrence_object_type" t.integer "created_by_id", null: false t.integer "updated_by_id", null: false t.integer "project_id" @@ -1041,7 +1094,7 @@ t.datetime "updated_at", null: false t.string "image_file_file_name" t.string "image_file_content_type" - t.bigint "image_file_file_size" + t.integer "image_file_file_size" t.datetime "image_file_updated_at" t.integer "updated_by_id", null: false t.text "image_file_meta" @@ -1122,8 +1175,8 @@ t.integer "project_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.string "loan_item_object_type" t.integer "loan_item_object_id" + t.string "loan_item_object_type" t.integer "total" t.string "disposition" t.index ["created_by_id"], name: "index_loan_items_on_created_by_id" @@ -1152,7 +1205,7 @@ t.datetime "updated_at", null: false t.string "recipient_honorific" t.string "recipient_country" - t.text "lender_address", null: false + t.text "lender_address", default: "Lender's address not provided.", null: false t.boolean "is_gift" t.index ["created_by_id"], name: "index_loans_on_created_by_id" t.index ["project_id"], name: "index_loans_on_project_id" @@ -1348,10 +1401,10 @@ end create_table "origin_relationships", id: :serial, force: :cascade do |t| - t.string "old_object_type", null: false t.integer "old_object_id", null: false - t.string "new_object_type", null: false + t.string "old_object_type", null: false t.integer "new_object_id", null: false + t.string "new_object_type", null: false t.integer "position" t.integer "created_by_id", null: false t.integer "updated_by_id", null: false @@ -1397,6 +1450,22 @@ t.index ["updated_by_id"], name: "index_otu_page_layouts_on_updated_by_id" end + create_table "otu_relationships", force: :cascade do |t| + t.integer "subject_otu_id", null: false + t.string "type", null: false + t.integer "object_otu_id", null: false + t.bigint "project_id" + t.integer "created_by_id", null: false + t.integer "updated_by_id", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["created_by_id"], name: "index_otu_relationships_on_created_by_id" + t.index ["object_otu_id"], name: "index_otu_relationships_on_object_otu_id" + t.index ["project_id"], name: "index_otu_relationships_on_project_id" + t.index ["subject_otu_id"], name: "index_otu_relationships_on_subject_otu_id" + t.index ["updated_by_id"], name: "index_otu_relationships_on_updated_by_id" + end + create_table "otus", id: :serial, force: :cascade do |t| t.string "name" t.datetime "created_at", null: false @@ -1435,8 +1504,8 @@ end create_table "pinboard_items", id: :serial, force: :cascade do |t| - t.string "pinned_object_type", null: false t.integer "pinned_object_id", null: false + t.string "pinned_object_type", null: false t.integer "user_id", null: false t.integer "project_id", null: false t.integer "position", null: false @@ -1500,7 +1569,7 @@ t.datetime "updated_at", null: false t.integer "created_by_id", null: false t.integer "updated_by_id", null: false - t.jsonb "preferences", default: "{}", null: false + t.jsonb "preferences", default: {}, null: false t.string "api_access_token" t.index ["created_by_id"], name: "index_projects_on_created_by_id" t.index ["updated_by_id"], name: "index_projects_on_updated_by_id" @@ -1508,8 +1577,8 @@ create_table "protocol_relationships", id: :serial, force: :cascade do |t| t.integer "protocol_id", null: false - t.string "protocol_relationship_object_type", null: false t.integer "protocol_relationship_object_id", null: false + t.string "protocol_relationship_object_type", null: false t.integer "position", null: false t.integer "created_by_id", null: false t.integer "updated_by_id", null: false @@ -1767,8 +1836,8 @@ t.string "boundary_finder", null: false t.boolean "has_border", null: false t.string "layout", null: false - t.jsonb "metadata_map", default: "{}", null: false - t.jsonb "specimen_coordinates", default: "{}", null: false + t.jsonb "metadata_map", default: {}, null: false + t.jsonb "specimen_coordinates", default: {}, null: false t.integer "project_id", null: false t.integer "created_by_id", null: false t.integer "updated_by_id", null: false @@ -2053,6 +2122,9 @@ add_foreign_key "biological_relationships", "projects", name: "biological_relationships_project_id_fkey" add_foreign_key "biological_relationships", "users", column: "created_by_id", name: "biological_relationships_created_by_id_fkey" add_foreign_key "biological_relationships", "users", column: "updated_by_id", name: "biological_relationships_updated_by_id_fkey" + add_foreign_key "cached_map_item_translations", "geographic_items" + add_foreign_key "cached_map_item_translations", "geographic_items", column: "translated_geographic_item_id" + add_foreign_key "cached_map_registers", "projects" add_foreign_key "character_states", "descriptors" add_foreign_key "character_states", "projects" add_foreign_key "character_states", "users", column: "created_by_id" @@ -2230,6 +2302,11 @@ add_foreign_key "otu_page_layouts", "projects", name: "otu_page_layouts_project_id_fkey" add_foreign_key "otu_page_layouts", "users", column: "created_by_id", name: "otu_page_layouts_created_by_id_fkey" add_foreign_key "otu_page_layouts", "users", column: "updated_by_id", name: "otu_page_layouts_updated_by_id_fkey" + add_foreign_key "otu_relationships", "otus", column: "object_otu_id" + add_foreign_key "otu_relationships", "otus", column: "subject_otu_id" + add_foreign_key "otu_relationships", "projects" + add_foreign_key "otu_relationships", "users", column: "created_by_id" + add_foreign_key "otu_relationships", "users", column: "updated_by_id" add_foreign_key "otus", "projects", name: "otus_project_id_fkey" add_foreign_key "otus", "taxon_names", name: "otus_taxon_name_id_fkey" add_foreign_key "otus", "users", column: "created_by_id", name: "otus_created_by_id_fkey" diff --git a/spec/controllers/otus_controller_spec.rb b/spec/controllers/otus_controller_spec.rb index b67d204b90..8ca29ed070 100644 --- a/spec/controllers/otus_controller_spec.rb +++ b/spec/controllers/otus_controller_spec.rb @@ -28,7 +28,6 @@ # adjust the attributes here as well. let(:valid_attributes) { strip_housekeeping_attributes(FactoryBot.build(:valid_otu).attributes) } - # This should return the minimal set of values that should be in the session # in order to pass any filters (e.g. authentication) defined in # OtusController. Be sure to keep this updated too. diff --git a/spec/factories/otu_relationships.rb b/spec/factories/otu_relationships.rb new file mode 100644 index 0000000000..842b0af0ee --- /dev/null +++ b/spec/factories/otu_relationships.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :valid_otu_relationship, class: 'OtuRelationship' do + association :subject_otu, factory: :valid_otu + type { 'OtuRelationship::Disjoint' } + association :object_otu, factory: :valid_otu + end +end diff --git a/spec/models/otu_relationship_spec.rb b/spec/models/otu_relationship_spec.rb new file mode 100644 index 0000000000..a2938576db --- /dev/null +++ b/spec/models/otu_relationship_spec.rb @@ -0,0 +1,14 @@ +require 'rails_helper' + +describe OtuRelationship, type: :model, group: :otu do + + let(:otu_relationship) { OtuRelationship.new } + + specify 'subject_otu is requred' do + otu_relationship.valid? + expect(otu_relationship.errors.messages.include?(:subject_otu)).to be_truthy + end + +end + + diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 8713b647c7..e206858a6e 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -26,3 +26,4 @@ ApplicationRecord.connection.tables.each { |t| ApplicationRecord.connection.reset_pk_sequence!(t) } FactoryBot.use_parent_strategy = false + diff --git a/spec/routing/otu_relationships_routing_spec.rb b/spec/routing/otu_relationships_routing_spec.rb new file mode 100644 index 0000000000..962b15df0c --- /dev/null +++ b/spec/routing/otu_relationships_routing_spec.rb @@ -0,0 +1,38 @@ +require "rails_helper" + +RSpec.describe OtuRelationshipsController, type: :routing do + describe "routing" do + it "routes to #index" do + expect(get: "/otu_relationships").to route_to("otu_relationships#index") + end + + it "routes to #new" do + expect(get: "/otu_relationships/new").to route_to("otu_relationships#new") + end + + it "routes to #show" do + expect(get: "/otu_relationships/1").to route_to("otu_relationships#show", id: "1") + end + + it "routes to #edit" do + expect(get: "/otu_relationships/1/edit").to route_to("otu_relationships#edit", id: "1") + end + + + it "routes to #create" do + expect(post: "/otu_relationships").to route_to("otu_relationships#create") + end + + it "routes to #update via PUT" do + expect(put: "/otu_relationships/1").to route_to("otu_relationships#update", id: "1") + end + + it "routes to #update via PATCH" do + expect(patch: "/otu_relationships/1").to route_to("otu_relationships#update", id: "1") + end + + it "routes to #destroy" do + expect(delete: "/otu_relationships/1").to route_to("otu_relationships#destroy", id: "1") + end + end +end From 845f7153097f4eb5be9d3e67f9d61e2f4c0cf2f5 Mon Sep 17 00:00:00 2001 From: jlpereira Date: Wed, 10 May 2023 14:44:38 -0300 Subject: [PATCH 06/88] Fix includes facet in filter images --- CHANGELOG.md | 1 + .../vue/components/Filter/Facets/Otu/components/Includes.vue | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6acafa77e7..d23e67d3cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv ### Fixed - BibTeX typo [#3408] +- Includes `type material` and `type material observations` don't work in Filter images [#3408]: https://github.com/SpeciesFileGroup/taxonworks/issues/3408 diff --git a/app/javascript/vue/components/Filter/Facets/Otu/components/Includes.vue b/app/javascript/vue/components/Filter/Facets/Otu/components/Includes.vue index c0cc2d9e51..7e749be98d 100644 --- a/app/javascript/vue/components/Filter/Facets/Otu/components/Includes.vue +++ b/app/javascript/vue/components/Filter/Facets/Otu/components/Includes.vue @@ -36,8 +36,8 @@ const includes = [ 'collection_objects', 'collection_object_observations', 'otu_observations', - 'type_materials', - 'type_material_observation' + 'type_material', + 'type_material_observations' ] const params = computed({ From 777117fa58ca11dc2c5dca9fd2e714426f8fb371 Mon Sep 17 00:00:00 2001 From: Dmitry Dmitriev Date: Wed, 10 May 2023 15:22:17 -0500 Subject: [PATCH 07/88] recent languages --- CHANGELOG.md | 3 +++ app/models/language.rb | 39 ++++++++++++++---------------- app/models/predicate.rb | 17 +++++++------ lib/queries/person/autocomplete.rb | 5 ++-- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d23e67d3cc..e48c6b5412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -717,6 +717,9 @@ This project does not yet adheres to [Semantic Versioning](https://semv ### Changed +- Recent predicate list +- Recent languages list +- People autocomplete - Remove search box in observation matrix hub [#3032] - Type material form allows multiple type species in comprehensive task. [#2584] - Updated Ruby gems. diff --git a/app/models/language.rb b/app/models/language.rb index d4861887b3..a87b5808c6 100644 --- a/app/models/language.rb +++ b/app/models/language.rb @@ -29,10 +29,11 @@ class Language < ApplicationRecord has_many :alternate_value_translations, class_name: 'AlternateValue::Translation' scope :used_recently_on_sources, -> { joins(sources: [:project_sources]).includes(sources: [:project_sources]).where(sources: { updated_at: 10.weeks.ago..Time.now } ).order('"sources"."updated_at" DESC') } - - - # TODO: dry + + + # TODO: dry scope :used_recently_on_serials, -> { joins(:serials).includes(:serials).where(serials: { updated_at: 10.weeks.ago..Time.now } ).order('"serials"."updated_at" DESC') } + scope :used_recently_on_common_names, -> { joins(:common_names).includes(:common_names).where(common_names: { updated_at: 10.weeks.ago..Time.now } ).order('"serials"."updated_at" DESC') } scope :used_recently_on_alternate_values, -> { joins(:alternate_value_translations).includes(:alternate_value_translations).where(alternate_values: { updated_at: 10.weeks.ago..Time.now } ).order('"alternate_values"."updated_at" DESC') } scope :with_english_name_containing, ->(name) {where('english_name ILIKE ?', "%#{name}%")} # non-case sensitive comparison @@ -57,26 +58,22 @@ def self.find_for_autocomplete(params) # @param klass ['Source' || 'Serial'] def self.select_optimized(user_id, project_id, klass = 'Source') - recent = if klass == 'Source' - Language.used_recently_on_sources.where('project_sources.project_id = ? AND sources.updated_by_id = ?', project_id, user_id).distinct.limit(10) - elsif klass == 'Serial' - Language.used_recently_on_serials.where('serials.updated_by_id = ?', user_id).distinct.limit(10).to_a - end + language_ids = case klass + when 'Source' + Language.used_recently_on_sources.where('project_sources.project_id = ? AND sources.updated_by_id = ?', project_id, user_id).pluck(:id).uniq + when 'Serial' + Language.used_recently_on_serials.where('serials.updated_by_id = ?', user_id).pluck(:id).uniq + when 'AlternateValue' + Language.used_recently_on_alternate_values.where('alternate_values.updated_by_id = ?', user_id).pluck(:id).uniq + when 'CommonNames' + Language.used_recently_on_alternate_values.where('alternate_values.updated_by_id = ?', user_id).pluck(:id).uniq + end + h = { - recent: recent, - pinboard: Language.pinned_by(user_id).pinned_in_project(project_id).to_a + recent: Language.where(id: language_ids.first(10)).order(:english_name).to_a, + pinboard: Language.pinned_by(user_id).pinned_in_project(project_id).to_a, + quick: (Language.pinned_by(user_id).pinboard_inserted.pinned_in_project(project_id).to_a + Language.where(id: language_ids.first(4)).order(:english_name).to_a).uniq } - - quick = if klass == 'Source' - Language.used_recently_on_sources.where('project_sources.project_id = ? AND sources.updated_by_id = ?', project_id, user_id).distinct.limit(4) - elsif klass == 'Serial' - Language.used_recently_on_serials.where('serials.updated_by_id = ?', user_id).distinct.limit(4).to_a - elsif klass == 'AlternateValue' - Language.used_recently_on_alternate_values.where('alternate_values.updated_by_id = ?', user_id).distinct.limit(4).to_a - end - - h[:quick] = (Language.pinned_by(user_id).pinboard_inserted.pinned_in_project(project_id).to_a + quick).uniq h end - end diff --git a/app/models/predicate.rb b/app/models/predicate.rb index 721a261767..8ce997a428 100644 --- a/app/models/predicate.rb +++ b/app/models/predicate.rb @@ -7,22 +7,23 @@ class Predicate < ControlledVocabularyTerm # @return [Scope] # the max 10 most recently used predicates def self.used_recently(user_id, project_id, klass) - i = InternalAttribute.arel_table + t = InternalAttribute.arel_table p = Predicate.arel_table # i is a select manager - i = i.project(i['controlled_vocabulary_term_id'], i['updated_at']).from(i) - .where(i['updated_at'].gt( 10.weeks.ago )) - .where(i['updated_by_id'].eq(user_id)) - .where(i['project_id'].eq(project_id)) - .order(i['updated_at'].desc) + i = t.project(t['controlled_vocabulary_term_id'], t['updated_at']).from(t) + .where(t['updated_at'].gt( 10.weeks.ago )) + .where(t['updated_by_id'].eq(user_id)) + .where(t['attribute_subject_type'].eq(klass)) + .where(t['project_id'].eq(project_id)) + .order(t['updated_at'].desc) # z is a table alias z = i.as('recent_t') - Predicate.used_on_klass(klass).joins( + Predicate.joins( Arel::Nodes::InnerJoin.new(z, Arel::Nodes::On.new(z['controlled_vocabulary_term_id'].eq(p['id']))) - ).select('distinct controlled_vocabulary_terms.id').pluck(:id) + ).pluck(:id).uniq end def self.select_optimized(user_id, project_id, klass) diff --git a/lib/queries/person/autocomplete.rb b/lib/queries/person/autocomplete.rb index 3fd704b0c3..2f422f8afd 100644 --- a/lib/queries/person/autocomplete.rb +++ b/lib/queries/person/autocomplete.rb @@ -134,10 +134,11 @@ def autocomplete a = a.left_outer_joins(:roles) .joins("LEFT OUTER JOIN sources ON roles.role_object_id = sources.id AND roles.role_object_type = 'Source'") .joins('LEFT OUTER JOIN project_sources ON sources.id = project_sources.source_id') - .select("people.*, COUNT(roles.id) AS use_count, CASE WHEN MAX(roles.project_id) IN (#{pr_id}) THEN MAX(roles.project_id) ELSE MAX(project_sources.project_id) END AS in_project") - .where("roles.project_id IN (#{pr_id}) OR project_sources.project_id IN (#{pr_id}) OR (roles.project_id NOT IN (#{pr_id}) AND project_sources.project_id NOT IN (#{pr_id})) OR (roles.project_id IS NULL AND project_sources.project_id IS NULL)") + .select("people.*, COUNT(roles.id) AS use_count, CASE WHEN MAX(roles.project_id) IN (#{pr_id}) THEN MAX(roles.project_id) WHEN MAX(project_sources.project_id) IN (#{pr_id}) THEN MAX(project_sources.project_id) ELSE NULL END AS in_project") + .where("roles.project_id IN (#{pr_id}) OR project_sources.project_id IN (#{pr_id}) OR (roles.project_id NOT IN (#{pr_id}) AND (project_sources.project_id NOT IN (#{pr_id}) or project_sources.project_id IS NULL)) OR ((roles.project_id NOT IN (#{pr_id}) OR roles.project_id IS NULL) AND project_sources.project_id NOT IN (#{pr_id})) OR (roles.project_id IS NULL AND project_sources.project_id IS NULL)") .group('people.id') .order('in_project, use_count DESC') + # .where("roles.project_id IN (#{pr_id}) OR project_sources.project_id IN (#{pr_id}) OR (roles.project_id NOT IN (#{pr_id}) AND project_sources.project_id NOT IN (#{pr_id})) OR (roles.project_id IS NULL AND project_sources.project_id IS NULL)") end end From 5ce20eaf53806ef55307762dd7012398c03c843b Mon Sep 17 00:00:00 2001 From: Dmitry Dmitriev Date: Wed, 10 May 2023 15:58:40 -0500 Subject: [PATCH 08/88] recent languages --- CHANGELOG.md | 2 +- lib/queries/person/autocomplete.rb | 13 +++++++++---- lib/queries/repository/autocomplete.rb | 1 - lib/queries/serial/autocomplete.rb | 1 - lib/queries/source/autocomplete.rb | 1 - 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e48c6b5412..e106b964f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -722,7 +722,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv - People autocomplete - Remove search box in observation matrix hub [#3032] - Type material form allows multiple type species in comprehensive task. [#2584] -- Updated Ruby gems. +- Updated Ruby gems.Yes - wikidata-client dependency is now fetching from RubyGems rather than custom fork. - serrano has been changed to a new custom branch which is identical to official gem except `thor` dependency has been downgraded for TW compatibility. - DwC occurrences importer mappings are not sorted by `institutionCode`:`collectionCode` diff --git a/lib/queries/person/autocomplete.rb b/lib/queries/person/autocomplete.rb index 2f422f8afd..e8b45d49c6 100644 --- a/lib/queries/person/autocomplete.rb +++ b/lib/queries/person/autocomplete.rb @@ -134,11 +134,16 @@ def autocomplete a = a.left_outer_joins(:roles) .joins("LEFT OUTER JOIN sources ON roles.role_object_id = sources.id AND roles.role_object_type = 'Source'") .joins('LEFT OUTER JOIN project_sources ON sources.id = project_sources.source_id') - .select("people.*, COUNT(roles.id) AS use_count, CASE WHEN MAX(roles.project_id) IN (#{pr_id}) THEN MAX(roles.project_id) WHEN MAX(project_sources.project_id) IN (#{pr_id}) THEN MAX(project_sources.project_id) ELSE NULL END AS in_project") - .where("roles.project_id IN (#{pr_id}) OR project_sources.project_id IN (#{pr_id}) OR (roles.project_id NOT IN (#{pr_id}) AND (project_sources.project_id NOT IN (#{pr_id}) or project_sources.project_id IS NULL)) OR ((roles.project_id NOT IN (#{pr_id}) OR roles.project_id IS NULL) AND project_sources.project_id NOT IN (#{pr_id})) OR (roles.project_id IS NULL AND project_sources.project_id IS NULL)") - .group('people.id') + .select("people.*, COUNT(roles.id) AS use_count, CASE WHEN roles.project_id IN (#{pr_id}) THEN roles.project_id WHEN project_sources.project_id IN (#{pr_id}) THEN project_sources.project_id ELSE NULL END AS in_project") + .group('people.id, roles.project_id, project_sources.project_id') .order('in_project, use_count DESC') - # .where("roles.project_id IN (#{pr_id}) OR project_sources.project_id IN (#{pr_id}) OR (roles.project_id NOT IN (#{pr_id}) AND project_sources.project_id NOT IN (#{pr_id})) OR (roles.project_id IS NULL AND project_sources.project_id IS NULL)") + #a = a.left_outer_joins(:roles) + # .joins("LEFT OUTER JOIN sources ON roles.role_object_id = sources.id AND roles.role_object_type = 'Source'") + # .joins('LEFT OUTER JOIN project_sources ON sources.id = project_sources.source_id') + # .select("people.*, COUNT(roles.id) AS use_count, CASE WHEN MAX(roles.project_id) IN (#{pr_id}) THEN MAX(roles.project_id) WHEN MAX(project_sources.project_id) IN (#{pr_id}) THEN MAX(project_sources.project_id) ELSE NULL END AS in_project") + # .where("roles.project_id IN (#{pr_id}) OR project_sources.project_id IN (#{pr_id}) OR ( (roles.project_id NOT IN (#{pr_id}) OR roles.project_id IS NULL) AND (project_sources.project_id NOT IN (#{pr_id}) OR project_sources.project_id IS NULL))") + # .group('people.id') + # .order('in_project, use_count DESC') end end diff --git a/lib/queries/repository/autocomplete.rb b/lib/queries/repository/autocomplete.rb index d5ad483394..071100f1ba 100644 --- a/lib/queries/repository/autocomplete.rb +++ b/lib/queries/repository/autocomplete.rb @@ -58,7 +58,6 @@ def autocomplete if project_id && scope a = a.select("repositories.*, COUNT(collection_objects.id) AS use_count, CASE WHEN collection_objects.project_id IN (#{pr_id}) THEN collection_objects.project_id ELSE NULL END AS in_project") .joins('LEFT OUTER JOIN collection_objects ON (repositories.id = collection_objects.repository_id OR repositories.id = collection_objects.current_repository_id)') - .where("collection_objects.project_id IN (#{pr_id}) OR collection_objects.project_id NOT IN (#{pr_id}) OR collection_objects.project_id IS NULL") .group('repositories.id, collection_objects.project_id') .order('in_project, use_count DESC') end diff --git a/lib/queries/serial/autocomplete.rb b/lib/queries/serial/autocomplete.rb index f3187958d7..ebdc136f7f 100644 --- a/lib/queries/serial/autocomplete.rb +++ b/lib/queries/serial/autocomplete.rb @@ -41,7 +41,6 @@ def autocomplete a = a.select("serials.*, COUNT(project_sources.source_id) AS use_count, CASE WHEN project_sources.project_id IN (#{pr_id}) THEN project_sources.project_id ELSE NULL END AS in_project") .left_outer_joins(:sources) .joins('LEFT OUTER JOIN project_sources ON sources.id = project_sources.source_id') - .where("project_sources.project_id IN (#{pr_id}) OR project_sources.project_id NOT IN (#{pr_id}) OR project_sources.project_id IS NULL") .group('serials.id, project_sources.project_id') .order('in_project, use_count DESC') end diff --git a/lib/queries/source/autocomplete.rb b/lib/queries/source/autocomplete.rb index 83fa4e4615..3f2cc82364 100644 --- a/lib/queries/source/autocomplete.rb +++ b/lib/queries/source/autocomplete.rb @@ -237,7 +237,6 @@ def autocomplete .left_outer_joins(:project_sources) .select("sources.*, COUNT(citations.id) AS use_count, CASE WHEN project_sources.project_id IN (#{pr_id}) THEN project_sources.project_id ELSE NULL END AS in_project") .where('citations.project_id IN (?) OR citations.project_id NOT IN (?) OR citations.project_id IS NULL', pr_id, pr_id) - .where("project_sources.project_id IN (#{pr_id}) OR project_sources.project_id NOT IN (#{pr_id}) OR project_sources.project_id IS NULL") .group('sources.id, citations.project_id, project_sources.project_id') .order('in_project, use_count DESC') end From fc67cc8e9388bc4366532bb3e121e54f79e5961a Mon Sep 17 00:00:00 2001 From: jlpereira Date: Wed, 10 May 2023 18:03:39 -0300 Subject: [PATCH 09/88] Add smart selector initializer --- .../workbench/smart_selector_helper.rb | 15 +++ app/javascript/packs/application.js | 3 +- .../vue/initializers/smart_selector/App.vue | 106 ++++++++++++++++++ .../vue/initializers/smart_selector/main.js | 63 +++++++++++ app/views/otu_relationships/_form.html.erb | 10 +- app/views/otu_relationships/edit.html.erb | 2 +- 6 files changed, 189 insertions(+), 10 deletions(-) create mode 100644 app/helpers/workbench/smart_selector_helper.rb create mode 100644 app/javascript/vue/initializers/smart_selector/App.vue create mode 100644 app/javascript/vue/initializers/smart_selector/main.js diff --git a/app/helpers/workbench/smart_selector_helper.rb b/app/helpers/workbench/smart_selector_helper.rb new file mode 100644 index 0000000000..942ff087a1 --- /dev/null +++ b/app/helpers/workbench/smart_selector_helper.rb @@ -0,0 +1,15 @@ +module Workbench::SmartSelectorHelper + def smart_selector(params) + content_tag(:div, '', data: { + 'smart-selector' => true, + 'smart-selector-model' => params[:model], + 'smart-selector-target' => params[:target], + 'smart-selector-klass' => params[:klass], + 'smart-selector-field-object' => params[:field_object], + 'smart-selector-field-property' => params[:field_property], + 'smart-selector-title' => params[:title], + 'smart-selector-current-object-id' => params[:current]&.id, + 'smart-selector-current-object-label' => label_for(params[:current]) + }) + end +end diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js index 5c3e08d714..ca19bb456d 100644 --- a/app/javascript/packs/application.js +++ b/app/javascript/packs/application.js @@ -29,6 +29,7 @@ require('../vue/initializers/tagButton/main.js') require('../vue/initializers/quick_citation_init/main.js') require('../vue/initializers/browse_nomenclature/main.js') require('../vue/initializers/pinboard_navigator/main.js') +require('../vue/initializers/smart_selector/main.js') require('../vue/initializers/soft_validations/main.js') require('../vue/initializers/simple_map/main') require('../vue/tasks/type_specimens/main.js') @@ -88,4 +89,4 @@ require('../vue/tasks/descriptors/filter/main.js') require('../vue/tasks/loans/filter/main.js') require('../vue/tasks/observations/filter/main.js') require('../vue/tasks/contents/filter/main.js') -require('../vue/tasks/biological_associations/biological_associations_graph/main.js') \ No newline at end of file +require('../vue/tasks/biological_associations/biological_associations_graph/main.js') diff --git a/app/javascript/vue/initializers/smart_selector/App.vue b/app/javascript/vue/initializers/smart_selector/App.vue new file mode 100644 index 0000000000..6867a03e60 --- /dev/null +++ b/app/javascript/vue/initializers/smart_selector/App.vue @@ -0,0 +1,106 @@ + + + + + diff --git a/app/javascript/vue/initializers/smart_selector/main.js b/app/javascript/vue/initializers/smart_selector/main.js new file mode 100644 index 0000000000..643083884c --- /dev/null +++ b/app/javascript/vue/initializers/smart_selector/main.js @@ -0,0 +1,63 @@ +import { createApp } from 'vue' +import App from './App.vue' + +function removeEmptyProps(props) { + const obj = { ...props } + + for (const key in obj) { + if (obj[key] === null) { + delete obj[key] + } + } + + return obj +} + +function makeCurrentObject(id, label, objectLabel) { + return id && label + ? { + id: Number(id), + [objectLabel]: label + } + : null +} + +function init(element) { + const model = element.getAttribute('data-smart-selector-model') + const target = element.getAttribute('data-smart-selector-target') + const klass = element.getAttribute('data-smart-selector-klass') + const fieldObject = element.getAttribute('data-smart-selector-field-object') + const objectLabelProperty = element.getAttribute('data-smart-selector-object-label') + const fieldProperty = element.getAttribute('data-smart-selector-field-property') + const objectProperty = element.getAttribute('data-smart-selector-object-property') + const title = element.getAttribute('data-smart-selector-title') + const currentObjectId = element.getAttribute('data-smart-selector-current-object-id') + const currentObjectLabel = element.getAttribute('data-smart-selector-current-object-label') + + const props = removeEmptyProps({ + model, + target, + klass, + fieldObject, + objectProperty, + fieldProperty, + title, + currentObject: makeCurrentObject( + currentObjectId, + currentObjectLabel, + objectLabelProperty || 'object_tag' + ) + }) + + const app = createApp(App, props) + + app.mount(element) +} + +document.addEventListener('turbolinks:load', () => { + if (document.querySelector('[data-smart-selector]')) { + document.querySelectorAll('[data-smart-selector]').forEach((element) => { + init(element) + }) + } +}) diff --git a/app/views/otu_relationships/_form.html.erb b/app/views/otu_relationships/_form.html.erb index 3b7a456584..a7791cdf8b 100644 --- a/app/views/otu_relationships/_form.html.erb +++ b/app/views/otu_relationships/_form.html.erb @@ -11,20 +11,14 @@ <% end %> -
- <%= form.label :subject_otu_id %> - <%= render '/otus/otu_picker', otu: @otu_relationship.subject_otu, field_name: 'Subject OTU', object_method: :subject_otu_id, f: form -%> -
+ <%= smart_selector model: :otus, target: :OtuRelationship, field_property: :subject_otu_id, field_object: :otu_relationship, title: 'OTU subject', current: @otu_relationship.subject_otu %>
<%= form.label :type %> <%= form.select :type, options: %w{OtuRelationship::Disjoint OtuRelationship::Equal OtuRelationship::ProperPartInverse OtuRelationship::ProperPart OtuRelationship::PartiallyOverlapping } %>
-
- <%= form.label :object_otu_id %> - <%= render '/otus/otu_picker', otu: @otu_relationship.object_otu, field_name: 'Object OTU', object_method: :object_otu_id, f: form -%> -
+ <%= smart_selector model: :otus, target: :OtuRelationship, field_property: :object_otu_id, field_object: :otu_relationship, title: 'OTU object', current: @otu_relationship.object_otu %>
<%= form.submit %> diff --git a/app/views/otu_relationships/edit.html.erb b/app/views/otu_relationships/edit.html.erb index 9f41eec129..90cbc08ccc 100644 --- a/app/views/otu_relationships/edit.html.erb +++ b/app/views/otu_relationships/edit.html.erb @@ -2,5 +2,5 @@ <%= render 'form', otu_relationship: @otu_relationship %> -<%= link_to 'Show', @otu_relationship %> | +<%= link_to 'Show', @otu_relationship.metamorphosize %> | <%= link_to 'Back', otu_relationships_path %> From b9694b98bc9eed05d24114836fc37942c1f7b1ee Mon Sep 17 00:00:00 2001 From: Dmitry Dmitriev Date: Thu, 11 May 2023 09:31:33 -0500 Subject: [PATCH 10/88] geographic area --- CHANGELOG.md | 1 + lib/queries/geographic_area/autocomplete.rb | 1 + lib/queries/query/autocomplete.rb | 4 ++-- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e106b964f4..25d1d4c560 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -720,6 +720,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv - Recent predicate list - Recent languages list - People autocomplete +- GeographicArea autocomplete (exact match on alternate value) - Remove search box in observation matrix hub [#3032] - Type material form allows multiple type species in comprehensive task. [#2584] - Updated Ruby gems.Yes diff --git a/lib/queries/geographic_area/autocomplete.rb b/lib/queries/geographic_area/autocomplete.rb index d006ce2724..ecdfc137cb 100644 --- a/lib/queries/geographic_area/autocomplete.rb +++ b/lib/queries/geographic_area/autocomplete.rb @@ -27,6 +27,7 @@ def autocomplete queries = [ ::GeographicArea.where(id: query_string).all, ::GeographicArea.where(name: query_string).all, + ::GeographicArea.joins(:alternate_values).where('alternate_values.value ILIKE ?', query_string).all, ::GeographicArea.joins(parent_child_join).where(Arel.sql(parent_child_where.to_sql)).limit(5).all, ::GeographicArea.where(Arel.sql(where_sql)).includes(:geographic_area_type, :geographic_items).order(Arel.sql('LENGTH(name)')).limit(dynamic_limit).all, autocomplete_exact_id, diff --git a/lib/queries/query/autocomplete.rb b/lib/queries/query/autocomplete.rb index 787bd83c80..319739cfff 100644 --- a/lib/queries/query/autocomplete.rb +++ b/lib/queries/query/autocomplete.rb @@ -113,12 +113,12 @@ def parent_child_join table.join(parent).on(table[:parent_id].eq(parent[:id])).join_sources end - # Match at two levels, for example, 'wa te" will match "Washington Co., Texas" + # Match at two levels, for example, 'te wa" will match "Texas, Washington Co." # @return [Arel::Nodes::Grouping] def parent_child_where a,b = query_string.split(/\s+/, 2) return table[:id].eq(-1) if a.nil? || b.nil? - table[:name].matches("#{a}%").and(parent[:name].matches("#{b}%")) + parent[:name].matches("#{a}%").and(table[:name].matches("#{b}%")) end # @return [Arel::Nodes, nil] From 82c75b25b8120ed4b52c9f7b6d1d9b6ca48f65c0 Mon Sep 17 00:00:00 2001 From: jlpereira Date: Thu, 11 May 2023 13:27:08 -0300 Subject: [PATCH 11/88] Refactor data attribute slice --- .../components/data_attribute_annotator.vue | 211 ++++++++---------- .../annotator/components/shared/tableList.vue | 206 ++++++++--------- 2 files changed, 196 insertions(+), 221 deletions(-) diff --git a/app/javascript/vue/components/radials/annotator/components/data_attribute_annotator.vue b/app/javascript/vue/components/radials/annotator/components/data_attribute_annotator.vue index 64f4fea5d7..b24f3b33b3 100644 --- a/app/javascript/vue/components/radials/annotator/components/data_attribute_annotator.vue +++ b/app/javascript/vue/components/radials/annotator/components/data_attribute_annotator.vue @@ -1,164 +1,137 @@