From 8bb5bf0c2ab2d25c67dc08f02d4f11efd7ec1107 Mon Sep 17 00:00:00 2001 From: Dmitry Dmitriev Date: Tue, 13 Aug 2024 11:45:38 -0500 Subject: [PATCH 01/88] redundant verbatim name --- .ruby-version | 2 +- app/models/combination.rb | 3 ++- .../latinized/part_of_speech/adjective.rb | 6 +++--- .../latinized/part_of_speech/participle.rb | 6 +++--- config/initializers/constants/model/taxon_names.rb | 6 +++--- lib/queries/repository/autocomplete.rb | 6 +++--- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.ruby-version b/.ruby-version index 619b537668..bea438e9ad 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -3.3.3 +3.3.1 diff --git a/app/models/combination.rb b/app/models/combination.rb index 04d334d718..cca32a7ab3 100644 --- a/app/models/combination.rb +++ b/app/models/combination.rb @@ -166,7 +166,8 @@ class Combination < TaxonName set: :cached, fix: :sv_fix_redundant_verbatim_name, name: 'Redundant verbatim name', - description: 'Verbatim name is present but identical to computed name' ) + description: 'Verbatim name is present but identical to computed name', + flagged: true ) soft_validate( :sv_combination_duplicates, diff --git a/app/models/taxon_name_classification/latinized/part_of_speech/adjective.rb b/app/models/taxon_name_classification/latinized/part_of_speech/adjective.rb index d85d4e69e6..c0068c06dc 100644 --- a/app/models/taxon_name_classification/latinized/part_of_speech/adjective.rb +++ b/app/models/taxon_name_classification/latinized/part_of_speech/adjective.rb @@ -13,7 +13,7 @@ def self.assignable true end - def set_cached + def set_cached set_gender_in_taxon_name super end @@ -25,10 +25,10 @@ def sv_not_specific_classes !t.end_with?('um') && !t.end_with?('is') && !t.end_with?('e') && - !t.end_with?('or') && + !t.end_with?('ior') && !t.end_with?('er') if taxon_name.name != taxon_name.masculine_name || taxon_name.name != taxon_name.feminine_name || taxon_name.name != taxon_name.neuter_name - soft_validations.add(:type, 'Declinable adjective name should end with one of the following endings: -us, -a, -um, -is, -e, -er, -or') + soft_validations.add(:type, 'Declinable adjective name should end with one of the following endings: -us, -a, -um, -is, -e, -er, -ior') end end end diff --git a/app/models/taxon_name_classification/latinized/part_of_speech/participle.rb b/app/models/taxon_name_classification/latinized/part_of_speech/participle.rb index c174bda272..f690db2726 100644 --- a/app/models/taxon_name_classification/latinized/part_of_speech/participle.rb +++ b/app/models/taxon_name_classification/latinized/part_of_speech/participle.rb @@ -13,7 +13,7 @@ def self.assignable true end - def set_cached + def set_cached set_gender_in_taxon_name super end @@ -25,10 +25,10 @@ def sv_not_specific_classes !t.end_with?('um') && !t.end_with?('is') && !t.end_with?('e') && - !t.end_with?('or') && + !t.end_with?('ior') && !t.end_with?('er') if taxon_name.name != taxon_name.masculine_name || taxon_name.name != taxon_name.feminine_name || taxon_name.name != taxon_name.neuter_name - soft_validations.add(:type, 'Declinable participle name should end with one of the following endings: -us, -a, -um, -is, -e, -er, -or') + soft_validations.add(:type, 'Declinable participle name should end with one of the following endings: -us, -a, -um, -is, -e, -er, -ior') end end end diff --git a/config/initializers/constants/model/taxon_names.rb b/config/initializers/constants/model/taxon_names.rb index 039806b46a..8e15f2f50a 100644 --- a/config/initializers/constants/model/taxon_names.rb +++ b/config/initializers/constants/model/taxon_names.rb @@ -32,9 +32,9 @@ 'december' => {masculine_name: 'december', feminine_name: 'decembris', neuter_name: 'decembre'}, 'decembris' => {masculine_name: 'december', feminine_name: 'decembris', neuter_name: 'decembre'}, 'decembre' => {masculine_name: 'december', feminine_name: 'decembris', neuter_name: 'decembre'}, - 'decimusquartus' => {masculine_name: 'decimusquartus', feminine_name: 'decimaquarta', neuter_name: 'decimumquartum'}, - 'decimaquarta' => {masculine_name: 'decimusquartus', feminine_name: 'decimaquarta', neuter_name: 'decimumquartum'}, - 'decimumquartum' => {masculine_name: 'decimusquartus', feminine_name: 'decimaquarta', neuter_name: 'decimumquartum'}, + 'decimaquartus' => {masculine_name: 'decimaquartus', feminine_name: 'decimaquarta', neuter_name: 'decimaquartum'}, + 'decimaquarta' => {masculine_name: 'decimaquartus', feminine_name: 'decimaquarta', neuter_name: 'decimaquartum'}, + 'decimaquartum' => {masculine_name: 'decimaquartus', feminine_name: 'decimaquarta', neuter_name: 'decimaquartum'}, 'dementius' => {masculine_name: 'dementior', feminine_name: 'dementior', neuter_name: 'dementius'}, 'dexter' => {masculine_name: 'dexter', feminine_name: 'dextra', neuter_name: 'dextrum'}, 'dextra' => {masculine_name: 'dexter', feminine_name: 'dextra', neuter_name: 'dextrum'}, diff --git a/lib/queries/repository/autocomplete.rb b/lib/queries/repository/autocomplete.rb index a13caab461..f5d5650cc5 100644 --- a/lib/queries/repository/autocomplete.rb +++ b/lib/queries/repository/autocomplete.rb @@ -58,9 +58,9 @@ def autocomplete a = q if project_id && scope && query_string.length > 2 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)') - .group('repositories.id, collection_objects.project_id') - .order('in_project, use_count DESC') + .joins('LEFT OUTER JOIN collection_objects ON (repositories.id = collection_objects.repository_id OR repositories.id = collection_objects.current_repository_id)') + .group('repositories.id, collection_objects.project_id') + .order('in_project, use_count DESC') end a ||= q From d7d498051b2ff46aa3c0e3bba4f3439138c3cf4d Mon Sep 17 00:00:00 2001 From: jlpereira Date: Tue, 13 Aug 2024 18:49:44 -0300 Subject: [PATCH 02/88] Update packages --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c774f8c1da..40c8fe9d51 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "@sfgrp/svg-detailer": "^0.2.5", "@sfgrp/svg-radial-menu": "^1.0.7", "@viz-js/viz": "^3.7.0", - "axios": "^1.7.3", + "axios": "^1.7.4", "chart.js": "^4.4.3", "d3": "^7.9.0", "dompurify": "^3.1.6", @@ -53,7 +53,7 @@ "compression-webpack-plugin": "^11.1.0", "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", - "eslint": "^9.8.0", + "eslint": "^9.9.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-vue": "^9.27.0", "mini-css-extract-plugin": "^2.9.0", From bf2e03b18161470902d391b2efddd91451dda89e Mon Sep 17 00:00:00 2001 From: jlpereira Date: Wed, 14 Aug 2024 11:51:25 -0300 Subject: [PATCH 03/88] Update sled and grid digitizer viewer --- .../slide_breakdown/app.vue | 84 +++-- .../slide_breakdown/components/AddLine.vue | 49 --- .../slide_breakdown/components/grid/Quick.vue | 95 +++-- .../components/grid/RemoveLine.vue | 49 --- package-lock.json | 348 +++++++++--------- package.json | 4 +- 6 files changed, 265 insertions(+), 364 deletions(-) delete mode 100644 app/javascript/vue/tasks/collection_objects/slide_breakdown/components/AddLine.vue delete mode 100644 app/javascript/vue/tasks/collection_objects/slide_breakdown/components/grid/RemoveLine.vue diff --git a/app/javascript/vue/tasks/collection_objects/slide_breakdown/app.vue b/app/javascript/vue/tasks/collection_objects/slide_breakdown/app.vue index 98988b0aaa..57d585e85c 100644 --- a/app/javascript/vue/tasks/collection_objects/slide_breakdown/app.vue +++ b/app/javascript/vue/tasks/collection_objects/slide_breakdown/app.vue @@ -23,8 +23,10 @@ @grid="setGrid" />
+ + diff --git a/app/javascript/vue/tasks/collection_objects/slide_breakdown/components/AddLine.vue b/app/javascript/vue/tasks/collection_objects/slide_breakdown/components/AddLine.vue deleted file mode 100644 index cc3684f74f..0000000000 --- a/app/javascript/vue/tasks/collection_objects/slide_breakdown/components/AddLine.vue +++ /dev/null @@ -1,49 +0,0 @@ - - - - diff --git a/app/javascript/vue/tasks/collection_objects/slide_breakdown/components/grid/Quick.vue b/app/javascript/vue/tasks/collection_objects/slide_breakdown/components/grid/Quick.vue index b29ae6a366..1f2a662b1c 100644 --- a/app/javascript/vue/tasks/collection_objects/slide_breakdown/components/grid/Quick.vue +++ b/app/javascript/vue/tasks/collection_objects/slide_breakdown/components/grid/Quick.vue @@ -1,14 +1,14 @@ diff --git a/app/javascript/vue/components/radials/RadialMenu.vue b/app/javascript/vue/components/radials/RadialMenu.vue index 157cdc5515..ed635a3eb6 100644 --- a/app/javascript/vue/components/radials/RadialMenu.vue +++ b/app/javascript/vue/components/radials/RadialMenu.vue @@ -2,6 +2,10 @@
+ + diff --git a/app/javascript/vue/components/radials/annotator/annotator.vue b/app/javascript/vue/components/radials/annotator/annotator.vue index 465798b7ac..6646ddb3e3 100644 --- a/app/javascript/vue/components/radials/annotator/annotator.vue +++ b/app/javascript/vue/components/radials/annotator/annotator.vue @@ -25,7 +25,7 @@
@@ -431,7 +431,7 @@ function createTag() { } function deleteTag() { - Tag.destroy(defaultTag.value.id).then((_) => { + Tag.destroy(defaultTag.value.id).then(() => { defaultTag.value = undefined TW.workbench.alert.create('Tag item was successfully destroyed.', 'notice') }) diff --git a/app/javascript/vue/components/radials/filter/radial.vue b/app/javascript/vue/components/radials/filter/radial.vue index 3eede0abe8..0b5a17884c 100644 --- a/app/javascript/vue/components/radials/filter/radial.vue +++ b/app/javascript/vue/components/radials/filter/radial.vue @@ -15,7 +15,7 @@
diff --git a/app/javascript/vue/components/radials/label/radial.vue b/app/javascript/vue/components/radials/label/radial.vue index f78daa7f17..01fbd7463d 100644 --- a/app/javascript/vue/components/radials/label/radial.vue +++ b/app/javascript/vue/components/radials/label/radial.vue @@ -17,7 +17,7 @@
@@ -116,7 +116,7 @@ function createLabels({ name }) { (slice) => slice.link === name ) - AjaxCall('post', link, { [param]: props.ids }).then((_) => { + AjaxCall('post', link, { [param]: props.ids }).then(() => { TW.workbench.alert.create('Label was successfully created.', 'notice') closeModal() }) diff --git a/app/javascript/vue/components/radials/linker/constants/links.js b/app/javascript/vue/components/radials/linker/constants/links.js index 9d99937cde..e139f0bbab 100644 --- a/app/javascript/vue/components/radials/linker/constants/links.js +++ b/app/javascript/vue/components/radials/linker/constants/links.js @@ -87,7 +87,8 @@ export const TASK_PEOPLE_METADATA = { export const TASK_FIELD_SYNCHRONIZE = { label: 'Field synchronize', link: '/tasks/data_attributes/field_synchronize', - queryParam: true + queryParam: true, + saveQuery: true } export const TASK_DWC_OCCURRENCE_STATUS = { diff --git a/app/javascript/vue/components/radials/linker/radial.vue b/app/javascript/vue/components/radials/linker/radial.vue index 2f8d51e068..25b5d20cf2 100644 --- a/app/javascript/vue/components/radials/linker/radial.vue +++ b/app/javascript/vue/components/radials/linker/radial.vue @@ -15,8 +15,11 @@
@@ -43,6 +46,7 @@ import { computed, ref, watch } from 'vue' import { copyObjectByArray, createAndSubmitForm } from '@/helpers' import { ID_PARAM_FOR } from '@/components/radials/filter/constants/idParams.js' import { QUERY_PARAM } from '@/components/radials/filter/constants/queryParam.js' +import { LinkerStorage } from '@/shared/Filter/utils' import RadialMenu from '@/components/radials/RadialMenu.vue' import VIcon from '@/components/ui/VIcon/index.vue' import VBtn from '@/components/ui/VBtn/index.vue' @@ -51,6 +55,12 @@ import getFilterAttributes from './composition/getFilterAttributes' import qs from 'qs' import * as LINKER_LIST from './links/index.js' +const MAX_LINK_SIZE = 2000 + +defineOptions({ + name: 'RadialLinker' +}) + const props = defineProps({ disabled: { type: Boolean, @@ -110,6 +120,8 @@ const menuOptions = computed(() => { if (Object.values(filteredParameters).some(Boolean)) { if (item.post) { slices.push(addSlice({ label: item.label })) + } else if (link.length > MAX_LINK_SIZE) { + slices.push(addSlice({ ...item, link: item.link })) } else { slices.push(addSlice({ ...item, link })) } @@ -170,12 +182,13 @@ function addSlice({ label, link }) { function closeModal() { isVisible.value = false - sessionStorage.removeItem('linkerQuery') + LinkerStorage.removeParameters() emit('close') } function openRadialMenu() { isVisible.value = true + LinkerStorage.removeParameters() } function filterEmptyParams(object) { @@ -192,29 +205,53 @@ function filterEmptyParams(object) { return obj } -function handleClick({ name }) { - const item = filterLinks.value.find(({ label }) => label === name) +function getItemByName(name) { + return filterLinks.value.find(({ label }) => label === name) +} + +function getLinkParameters(item) { const filteredParameters = filterEmptyParams( isOnlyIds.value ? getParametersForId() : getParametersForAll() ) + const parameters = item.queryParam + ? { [QUERY_PARAM[props.objectType]]: filteredParameters } + : filteredParameters + + return parameters +} + +function setParametersFor({ name }) { + const item = getItemByName(name) + const parameters = getLinkParameters(item) + const link = + item.link + '?' + qs.stringify(parameters, { arrayFormat: 'brackets' }) + + if (link.length > MAX_LINK_SIZE && item.saveQuery) { + LinkerStorage.saveParameters(parameters) + } +} + +function handleClick({ name }, { openTab = false }) { + const item = getItemByName(name) + const parameters = getLinkParameters(item) if (item.post) { - createAndSubmitForm({ action: item.link, data: filteredParameters }) - } else { - sessionStorage.setItem('linkerQuery', JSON.stringify(filteredParameters)) + createAndSubmitForm({ + action: item.link, + data: parameters, + openTab + }) } } function handleContextMenu({ name }) { - const item = filterLinks.value.find(({ label }) => label === name) - const filteredParameters = filterEmptyParams( - isOnlyIds.value ? getParametersForId() : getParametersForAll() - ) + const item = getItemByName(name) + const parameters = getLinkParameters(item) if (item.post) { createAndSubmitForm({ action: item.link, - data: filteredParameters, + data: parameters, openTab: true }) } @@ -226,9 +263,3 @@ watch(isVisible, (newVal) => { } }) - - diff --git a/app/javascript/vue/components/radials/mass/radial.vue b/app/javascript/vue/components/radials/mass/radial.vue index df4f8c89e2..fb136cb33f 100644 --- a/app/javascript/vue/components/radials/mass/radial.vue +++ b/app/javascript/vue/components/radials/mass/radial.vue @@ -21,7 +21,7 @@
@@ -80,6 +80,10 @@ import { ref, onBeforeMount } from 'vue' const EXCLUDE_PARAMETERS = ['per', 'page', 'extend'] +defineOptions({ + name: 'RadialMassAnnotator' +}) + const props = defineProps({ disabled: { type: Boolean, @@ -107,8 +111,6 @@ const props = defineProps({ } }) -const emit = defineEmits(['close']) - const { closeRadialBatch, currentSlice, @@ -124,28 +126,11 @@ const { slices: props.nestedQuery ? ANNOTATORS.all : ANNOTATORS.ids }) -const isModalVisible = ref(false) -const currentAnnotator = ref() const annotatorTypes = ref({}) -function selectComponent({ name }) { - currentAnnotator.value = name -} - -function closeModal() { - isModalVisible.value = false - emit('close') -} - onBeforeMount(() => { Metadata.annotators().then(({ body }) => { annotatorTypes.value = body }) }) - - diff --git a/app/javascript/vue/components/radials/navigation/radial.vue b/app/javascript/vue/components/radials/navigation/radial.vue index ccd44f1d3c..bde2dfd9c2 100644 --- a/app/javascript/vue/components/radials/navigation/radial.vue +++ b/app/javascript/vue/components/radials/navigation/radial.vue @@ -23,7 +23,7 @@ v-if="metadata" ref="radialElement" :options="menuOptions" - @onClick="selectedRadialOption" + @click="selectedRadialOption" /> diff --git a/app/javascript/vue/components/radials/otu/radial.vue b/app/javascript/vue/components/radials/otu/radial.vue index e07db7df81..f9dc6250de 100644 --- a/app/javascript/vue/components/radials/otu/radial.vue +++ b/app/javascript/vue/components/radials/otu/radial.vue @@ -15,7 +15,7 @@
diff --git a/app/javascript/vue/components/radials/shared/RadialBatch.vue b/app/javascript/vue/components/radials/shared/RadialBatch.vue index 75d7ab7376..3b68e1ae32 100644 --- a/app/javascript/vue/components/radials/shared/RadialBatch.vue +++ b/app/javascript/vue/components/radials/shared/RadialBatch.vue @@ -20,7 +20,7 @@
diff --git a/app/javascript/vue/shared/Filter/utils/index.js b/app/javascript/vue/shared/Filter/utils/index.js index d7f7eae68a..aa03268166 100644 --- a/app/javascript/vue/shared/Filter/utils/index.js +++ b/app/javascript/vue/shared/Filter/utils/index.js @@ -1 +1,2 @@ export * from './getDataAttributesFor' +export * from './storageParameters' diff --git a/app/javascript/vue/shared/Filter/utils/storageParameters.js b/app/javascript/vue/shared/Filter/utils/storageParameters.js new file mode 100644 index 0000000000..458878d434 --- /dev/null +++ b/app/javascript/vue/shared/Filter/utils/storageParameters.js @@ -0,0 +1,15 @@ +const FILTER_QUERY_STORAGE_KEY = 'linkerQuery' + +export class LinkerStorage { + static getParameters() { + return JSON.parse(localStorage.getItem(FILTER_QUERY_STORAGE_KEY)) + } + + static saveParameters(parameters) { + localStorage.setItem(FILTER_QUERY_STORAGE_KEY, JSON.stringify(parameters)) + } + + static removeParameters() { + localStorage.removeItem(FILTER_QUERY_STORAGE_KEY) + } +} diff --git a/app/javascript/vue/tasks/biological_associations/network/App.vue b/app/javascript/vue/tasks/biological_associations/network/App.vue index 51301de828..1b72db99cb 100644 --- a/app/javascript/vue/tasks/biological_associations/network/App.vue +++ b/app/javascript/vue/tasks/biological_associations/network/App.vue @@ -81,6 +81,7 @@ import { ref, onMounted } from 'vue' import { downloadTextFile } from '@/helpers/files' import { URLParamsToJSON } from '@/helpers/url/parse' import { BiologicalAssociation } from '@/routes/endpoints' +import { LinkerStorage } from '@/shared/Filter/utils' import VSpinner from '@/components/ui/VSpinner.vue' import VBtn from '@/components/ui/VBtn/index.vue' import VIcon from '@/components/ui/VIcon/index.vue' @@ -130,11 +131,11 @@ async function makeEdges(edges, nodes) { onMounted(() => { const urlParameters = { ...URLParamsToJSON(location.href), - ...JSON.parse(sessionStorage.getItem('linkerQuery')) + ...LinkerStorage.getParameters() } parameters.value = urlParameters - sessionStorage.removeItem('linkerQuery') + LinkerStorage.removeParameters() if (Object.keys(urlParameters).length) { loadGraph(urlParameters) diff --git a/app/javascript/vue/tasks/data_attributes/field_synchronize/composables/useFieldSync.js b/app/javascript/vue/tasks/data_attributes/field_synchronize/composables/useFieldSync.js index 678c1be807..6ae9a1b2b3 100644 --- a/app/javascript/vue/tasks/data_attributes/field_synchronize/composables/useFieldSync.js +++ b/app/javascript/vue/tasks/data_attributes/field_synchronize/composables/useFieldSync.js @@ -335,15 +335,9 @@ export function useFieldSync() { function loadAttributes(params) { const request = ajaxCall( - 'get', + 'post', '/tasks/data_attributes/field_synchronize/values', - { - params, - paramsSerializer: { - serialize: (params) => - Qs.stringify(params, { arrayFormat: 'brackets' }) - } - } + params ) request.then((response) => { diff --git a/app/javascript/vue/tasks/data_attributes/field_synchronize/composables/useQueryParam.js b/app/javascript/vue/tasks/data_attributes/field_synchronize/composables/useQueryParam.js index b631adf851..534540947a 100644 --- a/app/javascript/vue/tasks/data_attributes/field_synchronize/composables/useQueryParam.js +++ b/app/javascript/vue/tasks/data_attributes/field_synchronize/composables/useQueryParam.js @@ -1,11 +1,17 @@ import { ref } from 'vue' import { QUERY_PARAM } from '@/components/radials/filter/constants/queryParam' import { URLParamsToJSON } from '@/helpers' +import { LinkerStorage } from '@/shared/Filter/utils' export function useQueryParam() { const queryParam = ref(null) const queryValue = ref(null) - const parameters = URLParamsToJSON(window.location.href) + const parameters = { + ...URLParamsToJSON(window.location.href), + ...LinkerStorage.getParameters() + } + + LinkerStorage.removeParameters() queryParam.value = Object.keys(parameters).find((param) => Object.values(QUERY_PARAM).includes(param) diff --git a/package-lock.json b/package-lock.json index c1464e99db..c02c6e61a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@sfgrp/gbifference": "^0.0.5", "@sfgrp/sled": "^1.1.3", "@sfgrp/svg-detailer": "^0.2.5", - "@sfgrp/svg-radial-menu": "^1.0.7", + "@sfgrp/svg-radial-menu": "^1.0.9", "@viz-js/viz": "^3.8.0", "axios": "^1.7.4", "chart.js": "^4.4.3", @@ -3383,9 +3383,10 @@ "integrity": "sha512-ap1akEbHYgo8WqX4/SLAFRplZQKf4UK/wFHC/EMkEDwlCvEe4BxWh1JPymsGDNorYlFBcrS7saZxwMBR2WzrXw==" }, "node_modules/@sfgrp/svg-radial-menu": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@sfgrp/svg-radial-menu/-/svg-radial-menu-1.0.7.tgz", - "integrity": "sha512-0KfZBAIO8EjY4l7WfrmHKX4GOXYnYctK/I4tsTyy2O4qFnYLJmyxaMBxu3Jt4NlRNKdZnHqzQJOWe/5GeNcd4w==" + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@sfgrp/svg-radial-menu/-/svg-radial-menu-1.0.9.tgz", + "integrity": "sha512-h567EGYFY6VTEhb3PBN2PZFp9gdpUBj6I4o99HcMyZe9X23Mv/3+e6J6w+evoy41oQYoRAXM8lcS+pMbZBcxuQ==", + "license": "NCSA" }, "node_modules/@sinclair/typebox": { "version": "0.27.8", @@ -14586,9 +14587,9 @@ "integrity": "sha512-ap1akEbHYgo8WqX4/SLAFRplZQKf4UK/wFHC/EMkEDwlCvEe4BxWh1JPymsGDNorYlFBcrS7saZxwMBR2WzrXw==" }, "@sfgrp/svg-radial-menu": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@sfgrp/svg-radial-menu/-/svg-radial-menu-1.0.7.tgz", - "integrity": "sha512-0KfZBAIO8EjY4l7WfrmHKX4GOXYnYctK/I4tsTyy2O4qFnYLJmyxaMBxu3Jt4NlRNKdZnHqzQJOWe/5GeNcd4w==" + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@sfgrp/svg-radial-menu/-/svg-radial-menu-1.0.9.tgz", + "integrity": "sha512-h567EGYFY6VTEhb3PBN2PZFp9gdpUBj6I4o99HcMyZe9X23Mv/3+e6J6w+evoy41oQYoRAXM8lcS+pMbZBcxuQ==" }, "@sinclair/typebox": { "version": "0.27.8", diff --git a/package.json b/package.json index 2187b9b637..06ab649a2d 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "@sfgrp/gbifference": "^0.0.5", "@sfgrp/sled": "^1.1.3", "@sfgrp/svg-detailer": "^0.2.5", - "@sfgrp/svg-radial-menu": "^1.0.7", + "@sfgrp/svg-radial-menu": "^1.0.9", "@viz-js/viz": "^3.8.0", "axios": "^1.7.4", "chart.js": "^4.4.3", From a2619091b998dc45ca851095281894f295cde69e Mon Sep 17 00:00:00 2001 From: jlpereira Date: Thu, 22 Aug 2024 11:13:35 -0300 Subject: [PATCH 22/88] Fix #4018 --- CHANGELOG.md | 5 ++++ .../otu/filter/components/FilterView.vue | 1 + lib/queries/otu/filter.rb | 24 +++++++++++++++++-- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb8cdfa56c..8e05308d4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ This project does not yet adheres to [Semantic Versioning](https://semv ## [unreleased] +### Added + +- Filter Otu: With/without common names + ### Changed - Sort order of descendant inventory @@ -20,6 +24,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv [#4013]: https://github.com/SpeciesFileGroup/taxonworks/issues/4013 [#4017]: https://github.com/SpeciesFileGroup/taxonworks/issues/4017 +[#4018]: https://github.com/SpeciesFileGroup/taxonworks/issues/4018 ## [0.43.2] - 2024-08-10 diff --git a/app/javascript/vue/tasks/otu/filter/components/FilterView.vue b/app/javascript/vue/tasks/otu/filter/components/FilterView.vue index a37465aa37..df2cf58304 100644 --- a/app/javascript/vue/tasks/otu/filter/components/FilterView.vue +++ b/app/javascript/vue/tasks/otu/filter/components/FilterView.vue @@ -63,6 +63,7 @@ const WITH_PARAMS = [ 'biological_associations', 'citations', 'collection_objects', + 'common_names', 'contents', 'data_depictions', 'depictions', diff --git a/lib/queries/otu/filter.rb b/lib/queries/otu/filter.rb index f80d538944..29903ec6b8 100644 --- a/lib/queries/otu/filter.rb +++ b/lib/queries/otu/filter.rb @@ -15,6 +15,7 @@ class Filter < Query::Filter :biological_associations, :collecting_event_id, :collection_objects, + :common_names, :contents, :coordinatify, :descendants, @@ -147,8 +148,14 @@ class Filter < Query::Filter attr_accessor :asserted_distributions # @return [True, False, nil] - # true - Otu has Conten - # false - Otu without Conten + # true - Otu has common names + # false - Otu without common names + # nil - not applied + attr_accessor :common_names + + # @return [True, False, nil] + # true - Otu has Content + # false - Otu without Content # nil - not applied attr_accessor :contents @@ -183,6 +190,7 @@ def initialize(query_params) @biological_associations = boolean_param(params, :biological_associations) @collecting_event_id = params[:collecting_event_id] @collection_objects = boolean_param(params, :collection_objects) + @common_names = boolean_param(params, :common_names) @contents = boolean_param(params, :contents) @coordinatify = boolean_param(params, :coordinatify) @descendants = boolean_param(params, :descendants) @@ -316,6 +324,17 @@ def contents_facet end end + def common_names_facet + return nil if @common_names.nil? + + if @common_names + ::Otu.joins(:common_names) + else + ::Otu.where.missing(:common_names) + end + end + + # UNION, NOT EXISTS example def biological_associations_facet return nil if biological_associations.nil? @@ -621,6 +640,7 @@ def merge_clauses biological_associations_facet, collecting_event_id_facet, collection_objects_facet, + common_names_facet, contents_facet, descriptor_id_facet, geo_json_facet, From 475bdb0c9cfaeabbef72795f7b514e8d706057e4 Mon Sep 17 00:00:00 2001 From: mjy Date: Thu, 22 Aug 2024 16:00:38 -0500 Subject: [PATCH 23/88] Bundle update --- Gemfile.lock | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 46b855ef75..bad50071d3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -199,7 +199,7 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - chartkick (5.0.7) + chartkick (5.1.0) chronic (0.10.2) chunky_png (1.4.0) citeproc (1.0.10) @@ -241,7 +241,7 @@ GEM debug_inspector (1.2.0) delayed_job (4.1.12) activesupport (>= 3.0, < 8.0) - delayed_job_active_record (4.1.8) + delayed_job_active_record (4.1.10) activerecord (>= 3.0, < 8.0) delayed_job (>= 3.0, < 5) diff-lcs (1.5.1) @@ -250,7 +250,7 @@ GEM drb (2.2.1) dropzonejs-rails (0.8.5) rails (> 3.1) - dwc_agent (3.1.4.0) + dwc_agent (3.1.5.0) namae (~> 1) namecase (~> 2) erubi (1.13.0) @@ -374,9 +374,9 @@ GEM method_source (1.1.0) mime-types (3.5.2) mime-types-data (~> 3.2015) - mime-types-data (3.2024.0806) + mime-types-data (3.2024.0820) mini_mime (1.1.5) - minitest (5.25.0) + minitest (5.25.1) modularity (3.0.1) msgpack (1.7.2) multi_json (1.15.0) @@ -420,7 +420,7 @@ GEM paper_trail (15.1.0) activerecord (>= 6.1) request_store (~> 1.4) - parallel (1.26.2) + parallel (1.26.3) parallel_tests (4.7.1) parallel parser (3.3.4.2) @@ -532,7 +532,7 @@ GEM http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) - rexml (3.3.5) + rexml (3.3.6) strscan rgb (0.1.2) rgeo (3.0.1) @@ -566,7 +566,7 @@ GEM rspec-mocks (>= 2.99, < 4.0) rspec-core (3.13.0) rspec-support (~> 3.13.0) - rspec-expectations (3.13.1) + rspec-expectations (3.13.2) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) rspec-mocks (3.13.1) @@ -593,7 +593,7 @@ GEM rubocop-ast (>= 1.31.1, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 2.4.0, < 3.0) - rubocop-ast (1.32.0) + rubocop-ast (1.32.1) parser (>= 3.3.1.0) rubocop-capybara (2.21.0) rubocop (~> 1.41) @@ -714,7 +714,8 @@ GEM validates_timeliness (7.0.0.beta2) activemodel (>= 7.0.0, < 8) timeliness (>= 0.3.10, < 1) - vcr (6.2.0) + vcr (6.3.1) + base64 waxy (0.1.1) tilt (~> 2.1) web-console (4.2.1) From 1ac84d3ae92c661b41b7483049e7f00f6c4f5866 Mon Sep 17 00:00:00 2001 From: mjy Date: Thu, 22 Aug 2024 16:14:17 -0500 Subject: [PATCH 24/88] Export wikidata IDs for collectors/determiners. Fix #3989 --- CHANGELOG.md | 2 + .../collection_object/dwc_extensions.rb | 23 +++++++--- app/models/identifier/global.rb | 32 +++++++++++++- app/models/person.rb | 6 +++ lib/queries.rb | 4 +- .../collection_object/dwc_extensions_spec.rb | 42 +++++++++++++++++++ spec/models/identifier/global/orcid_spec.rb | 20 +++++++++ .../models/identifier/global/wikidata_spec.rb | 20 +++++++++ 8 files changed, 140 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75abe56beb..4b722ffe89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv ### Changed +- Wikidata IDs are now also loaded into recordedByID and identifiedByID [#3989] - Sort order of descendant inventory - Removed net-pop gem workaround for Ruby 3.3.3 @@ -24,6 +25,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv - Filter collecting events: data attribute table view is empty [#4013] - Field synchronize: URI Too Large error when user pass a long query [#4017] +[#3989]: https://github.com/SpeciesFileGroup/taxonworks/issues/3989 [#4013]: https://github.com/SpeciesFileGroup/taxonworks/issues/4013 [#4017]: https://github.com/SpeciesFileGroup/taxonworks/issues/4017 [#4018]: https://github.com/SpeciesFileGroup/taxonworks/issues/4018 diff --git a/app/models/collection_object/dwc_extensions.rb b/app/models/collection_object/dwc_extensions.rb index 124a33384c..ba78df7a01 100644 --- a/app/models/collection_object/dwc_extensions.rb +++ b/app/models/collection_object/dwc_extensions.rb @@ -487,11 +487,13 @@ def dwc_recorded_by def dwc_recorded_by_id if collecting_event collecting_event.collectors - .order('roles.position') - .map(&:orcid) - .compact - .join(CollectionObject::DWC_DELIMITER) - .presence + .joins(:identifiers) + .where(identifiers: {type: ['Identifier::Global::Orcid', 'Identifier::Global::Wikidata']}) + .select('identifiers.identifier_object_id, identifiers.cached') + .unscope(:order) + .distinct + .pluck('identifiers.cached') + .join(CollectionObject::DWC_DELIMITER)&.presence end end @@ -502,7 +504,16 @@ def dwc_identified_by def dwc_identified_by_id # TaxonWorks allows for groups of determiners to collaborate on a single determination if they collectively came to a conclusion. - current_taxon_determination&.determiners&.map(&:orcid)&.join(CollectionObject::DWC_DELIMITER).presence + if current_taxon_determination + current_taxon_determination&.determiners + .joins(:identifiers) + .where(identifiers: {type: ['Identifier::Global::Orcid', 'Identifier::Global::Wikidata']}) + .select('identifiers.identifier_object_id, identifiers.cached') + .unscope(:order) + .distinct + .pluck('identifiers.cached') + .join(CollectionObject::DWC_DELIMITER)&.presence + end end # we assert custody, NOT ownership diff --git a/app/models/identifier/global.rb b/app/models/identifier/global.rb index d13501b925..5be0ec221c 100644 --- a/app/models/identifier/global.rb +++ b/app/models/identifier/global.rb @@ -15,6 +15,9 @@ class Identifier::Global < Identifier include SoftValidation + # Only implemented for a couple, but for now DRYer to keep here + include Shared::DwcOccurrenceHooks + validates :namespace_id, absence: true validates :relation, inclusion: {in: ::SKOS_RELATIONS.keys}, allow_nil: true validate :permit_only_one_global_without_relation_supplied_per_type @@ -28,6 +31,33 @@ def is_global? true end + def dwc_occurrences + + return DwcOccurrence.none unless %w{ + Identifier::Global::Wikidata + Identifier::Global::Orcid + }.include?(type) + + # Collectors + a = DwcOccurrence.joins("JOIN collection_objects co on dwc_occurrence_object_id = co.id AND dwc_occurrence_object_type = 'CollectionObject'") + .joins('JOIN collecting_events ce on co.collecting_event_id = ce.id') + .joins("JOIN roles r on r.type = 'Collector' AND r.role_object_type = 'CollectingEvent' AND r.role_object_id = ce.id") + .joins("JOIN identifiers i on i.identifier_object_id = r.person_id AND i.identifier_object_type = 'Person' AND i.type = '#{type}' ") + .where(i: {id:}) + .distinct + + # Determiners + b = DwcOccurrence + .joins("JOIN collection_objects co on dwc_occurrence_object_id = co.id AND dwc_occurrence_object_type = 'CollectionObject'") + .joins("JOIN taxon_determinations td on co.id = td.taxon_determination_object_id AND td.taxon_determination_object_type = 'CollectionObject'") + .joins("JOIN roles r on r.type = 'Determiner' AND r.role_object_type = 'TaxonDetermination' AND r.role_object_id = td.id") + .joins("JOIN identifiers i on i.identifier_object_id = r.person_id AND i.identifier_object_type = 'Person' AND i.type = '#{type}'") + .where(r: {id:}) + .distinct + + ::Queries.union(::DwcOccurrence, [a,b]) + end + protected def build_cached @@ -41,7 +71,7 @@ def permit_only_one_global_without_relation_supplied_per_type end end - # TODO: add a resolution method so that this works on theings like wikidata Q numbers + # TODO: add a resolution method so that this works on theings like wikidata Q numbers def sv_resolves? responded = identifier.present? && (Utilities::Net.resolves?(identifier) rescue false) soft_validations.add(:identifier, "Identifier '#{identifier}' does not resolve.") unless responded diff --git a/app/models/person.rb b/app/models/person.rb index 99f7dc9672..5882d3adca 100644 --- a/app/models/person.rb +++ b/app/models/person.rb @@ -186,6 +186,12 @@ def orcid identifiers.where(type: 'Identifier::Global::Orcid').first&.cached end + # Return [String, nil] + # convenience, maybe a delegate: candidate + def wikidata_id + identifiers.where(type: 'Identifier::Global::Wikidata').first&.cached + end + # @param [Integer] person_id # @return [Boolean] # true if all records updated, false if any one failed (all or none) diff --git a/lib/queries.rb b/lib/queries.rb index d44ffdb0d2..32ed3f6f1a 100644 --- a/lib/queries.rb +++ b/lib/queries.rb @@ -15,7 +15,7 @@ def self.venn(a_query, b_query, mode) def self.intersect(target, queries) table = target.table_name - q = queries.compact.collect{|y| y.unscope(:select).select(:id) } + q = queries.compact # .collect{|y| y.unscope(:select).select(:id) } # We can return this directly, though we get conflicts with `from:` on merge clauses z = target.from("( #{q.collect{|i| '(' + i.to_sql + ')' }.join(' INTERSECT ')}) as #{table}") @@ -25,7 +25,7 @@ def self.intersect(target, queries) def self.union(target, queries) table = target.name.tableize - q = queries.compact.collect{|y| y.unscope(:select).select(:id) } + q = queries.compact # .collect{|y| y.unscope(:select).select(:id) } target.from("( #{q.collect{|i| '(' + i.to_sql + ')' }.join(' UNION ')}) as #{table}") end diff --git a/spec/models/collection_object/dwc_extensions_spec.rb b/spec/models/collection_object/dwc_extensions_spec.rb index 95e18797ee..51b4783f98 100644 --- a/spec/models/collection_object/dwc_extensions_spec.rb +++ b/spec/models/collection_object/dwc_extensions_spec.rb @@ -56,6 +56,7 @@ it 'should only include remarks for the latest determination' do otu = FactoryBot.create(:valid_otu) + d1 = TaxonDetermination.create!(taxon_determination_object: s, otu:) d2 = TaxonDetermination.create!(taxon_determination_object: s, otu:) @@ -493,6 +494,47 @@ expect(s.dwc_recorded_by).to eq('John von Doe') end + specify '#dwc_recorded_by_id - wikidata' do + p1 = Protonym.create!( + name: 'aus', + rank_class: Ranks.lookup(:iczn, :species), + parent: root + ) + + ce.update!(collectors_attributes: [{last_name: 'Doe', first_name: 'John', prefix: 'von'}]) + TaxonDetermination.create!( + taxon_determination_object: s, + otu: Otu.create!(taxon_name: p1), determiner_roles_attributes: [{person: p}] + ) + + Identifier::Global::Wikidata.create!(identifier_object: ce.collectors.first, identifier: 'Q1234566') + + s.reload + + expect(s.dwc_recorded_by_id).to eq('Q1234566') + end + + specify '#dwc_recorded_by_id - orcid' do + p1 = Protonym.create!( + name: 'aus', + rank_class: Ranks.lookup(:iczn, :species), + parent: root + ) + + ce.update!(collectors_attributes: [{last_name: 'Doe', first_name: 'John', prefix: 'von'}]) + TaxonDetermination.create!( + taxon_determination_object: s, + otu: Otu.create!(taxon_name: p1), + determiner_roles_attributes: [{person: p}] ) + + i = 'http://orcid.org/0000-0002-0554-1354' # sorry whomever you are + + Identifier::Global::Orcid.create!(identifier_object: ce.collectors.first, identifier: i) + + s.reload + expect(s.dwc_recorded_by_id).to eq(i) + end + specify '#dwc_other_catalog_numbers' do a = Identifier::Local::CatalogNumber.create!(identifier: '123', identifier_object: s, namespace: FactoryBot.create(:valid_namespace) ) b = Identifier::Local::CatalogNumber.create!(identifier: '456', identifier_object: s, namespace: FactoryBot.create(:valid_namespace) ) diff --git a/spec/models/identifier/global/orcid_spec.rb b/spec/models/identifier/global/orcid_spec.rb index 71d6d42a91..1c90d4931c 100644 --- a/spec/models/identifier/global/orcid_spec.rb +++ b/spec/models/identifier/global/orcid_spec.rb @@ -4,6 +4,26 @@ context 'Orcid' do let(:id) {Identifier::Global::Orcid.new(identifier_object: FactoryBot.build(:valid_person)) } + specify 'dwc_occurrence hooks - Collector' do + s = Specimen.create(collecting_event: FactoryBot.create(:valid_collecting_event)) + p = FactoryBot.build(:valid_person) + s.collecting_event.collectors << p + + Identifier::Global::Orcid.create!(identifier_object: p, identifier: 'http://orcid.org/0000-0002-1825-0097' ) + + expect(s.dwc_occurrence.reload.recordedByID).to eq('http://orcid.org/0000-0002-1825-0097') + end + + specify 'dwc_occurrence hooks - Determiner' do + s = Specimen.create! + p = FactoryBot.build(:valid_person) + t = FactoryBot.build(:valid_taxon_determination, determiners: [p], taxon_determination_object: s) + + Identifier::Global::Orcid.create!(identifier_object: p, identifier: 'http://orcid.org/0000-0002-1825-0097' ) + + expect(s.dwc_occurrence.reload.identifiedByID).to eq('http://orcid.org/0000-0002-1825-0097') + end + context '#identifier is validly formatted' do specify 'empty' do diff --git a/spec/models/identifier/global/wikidata_spec.rb b/spec/models/identifier/global/wikidata_spec.rb index 2173164aec..61f1777f46 100644 --- a/spec/models/identifier/global/wikidata_spec.rb +++ b/spec/models/identifier/global/wikidata_spec.rb @@ -3,6 +3,26 @@ describe Identifier::Global::Wikidata, type: :model, group: :identifiers do let(:id) { Identifier::Global::Wikidata.new(identifier_object: FactoryBot.create(:valid_person)) } + specify 'dwc_occurrence hooks - Collector' do + s = Specimen.create(collecting_event: FactoryBot.create(:valid_collecting_event)) + p = FactoryBot.build(:valid_person) + s.collecting_event.collectors << p + + Identifier::Global::Wikidata.create!(identifier_object: p, identifier: 'Q123' ) + + expect(s.dwc_occurrence.reload.recordedByID).to eq('Q123') + end + + specify 'dwc_occurrence hooks - Determiner' do + s = Specimen.create! + p = FactoryBot.build(:valid_person) + t = FactoryBot.build(:valid_taxon_determination, determiners: [p], taxon_determination_object: s) + + Identifier::Global::Wikidata.create!(identifier_object: p, identifier: 'Q123' ) + + expect(s.dwc_occurrence.reload.identifiedByID).to eq('Q123') + end + context 'looking for data', vcr: true do specify 'matches "namespace" 1' do id.identifier = 'Q9999999999999' # hmmm From 727bca131281284da0b4e15bb1b41f6a5f83cecf Mon Sep 17 00:00:00 2001 From: jlpereira Date: Fri, 23 Aug 2024 12:01:37 -0300 Subject: [PATCH 25/88] Add sort to identifiers table in radial annotator #4021 --- CHANGELOG.md | 2 + app/controllers/identifiers_controller.rb | 9 +++ .../components/identifier/IdentifierTable.vue | 69 +++++++++++++++++++ .../identifier/identifier_annotator.vue | 12 ++-- .../vue/routes/endpoints/Identifier.js | 8 ++- .../identifiers/_attributes.json.jbuilder | 2 +- config/routes/data.rb | 1 + 7 files changed, 92 insertions(+), 11 deletions(-) create mode 100644 app/javascript/vue/components/radials/annotator/components/identifier/IdentifierTable.vue diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b722ffe89..83408e3e50 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv ### Added - Filter Otu: With/without common names +- Radial annotator: Add sort to identifiers slice [#4021] ### Changed @@ -29,6 +30,7 @@ This project does not yet adheres to [Semantic Versioning](https://semv [#4013]: https://github.com/SpeciesFileGroup/taxonworks/issues/4013 [#4017]: https://github.com/SpeciesFileGroup/taxonworks/issues/4017 [#4018]: https://github.com/SpeciesFileGroup/taxonworks/issues/4018 +[#4021]: https://github.com/SpeciesFileGroup/taxonworks/issues/4021 ## [0.43.2] - 2024-08-10 diff --git a/app/controllers/identifiers_controller.rb b/app/controllers/identifiers_controller.rb index 706f5462d5..95ed522379 100644 --- a/app/controllers/identifiers_controller.rb +++ b/app/controllers/identifiers_controller.rb @@ -16,6 +16,7 @@ def index format.json { # project_id handling logic is in filter, it must be handled there. This contrasts pattern used elsewhere, but see alternate_values_controller.rb @identifiers = Queries::Identifier::Filter.new(params.merge(project_id: sessions_current_project_id)).all + .order(:identifier_object_type, :identifier_object_id, :position) .page(params[:page]) .per(params[:per]) } @@ -67,6 +68,14 @@ def update end end + # PATCH /identifiers/reorder?id[]=1 + def reorder + params[:id].reverse.each do |identifier_id| + Identifier.find(identifier_id).move_to_top + end + render json: true + end + # DELETE /identifiers/1 # DELETE /identifiers/1.json def destroy diff --git a/app/javascript/vue/components/radials/annotator/components/identifier/IdentifierTable.vue b/app/javascript/vue/components/radials/annotator/components/identifier/IdentifierTable.vue new file mode 100644 index 0000000000..ca97643c24 --- /dev/null +++ b/app/javascript/vue/components/radials/annotator/components/identifier/IdentifierTable.vue @@ -0,0 +1,69 @@ + + + diff --git a/app/javascript/vue/components/radials/annotator/components/identifier/identifier_annotator.vue b/app/javascript/vue/components/radials/annotator/components/identifier/identifier_annotator.vue index 13d2515612..6bc9d9719b 100644 --- a/app/javascript/vue/components/radials/annotator/components/identifier/identifier_annotator.vue +++ b/app/javascript/vue/components/radials/annotator/components/identifier/identifier_annotator.vue @@ -23,22 +23,18 @@ @create="saveIdentifier" /> - + + diff --git a/app/javascript/vue/tasks/digitize/components/settings/SettingCollectionObject.vue b/app/javascript/vue/tasks/digitize/components/settings/SettingCollectionObject.vue index 083a0c89ec..b58e1c56e8 100644 --- a/app/javascript/vue/tasks/digitize/components/settings/SettingCollectionObject.vue +++ b/app/javascript/vue/tasks/digitize/components/settings/SettingCollectionObject.vue @@ -54,6 +54,7 @@ import { COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_PREPARATION, COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_REPOSITORY, COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_CATALOG_NUMBER, + COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_RECORD_NUMBER, COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_VALIDATIONS } from '@/tasks/digitize/const/layout' import VBtn from '@/components/ui/VBtn/index.vue' @@ -63,6 +64,7 @@ const LAYOUT_SETTING = { [COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_ATTRIBUTES]: 'Attributes', [COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_BUFFERED]: 'Buffered', [COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_CATALOG_NUMBER]: 'Catalog number', + [COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_RECORD_NUMBER]: 'Record number', [COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_CITATIONS]: 'Citations', [COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_DEPICTIONS]: 'Depictions', [COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_PREPARATION]: 'Preparation', diff --git a/app/javascript/vue/tasks/digitize/const/layout.js b/app/javascript/vue/tasks/digitize/const/layout.js index a7f22b6553..5d88601efd 100644 --- a/app/javascript/vue/tasks/digitize/const/layout.js +++ b/app/javascript/vue/tasks/digitize/const/layout.js @@ -1,6 +1,7 @@ export const COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_ATTRIBUTES = 'tasks::digitize::collectionObjects::hideAttributes' export const COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_BUFFERED = 'tasks::digitize::collectionObjects::hideBuffered' export const COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_CATALOG_NUMBER = 'tasks::digitize::collectionObjects::hideCatalogNumber' +export const COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_RECORD_NUMBER = 'tasks::digitize::collectionObjects::hideRecordNumber' export const COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_CITATIONS = 'tasks::digitize::collectionObjects::hideCitations' export const COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_DEPICTIONS = 'tasks::digitize::collectionObjects::hideDepictions' export const COMPREHENSIVE_COLLECTION_OBJECT_LAYOUT_PREPARATION = 'tasks::digitize::collectionObjects::hidePreparation' diff --git a/app/javascript/vue/tasks/digitize/main.js b/app/javascript/vue/tasks/digitize/main.js index d9644689db..ce74f9f15b 100644 --- a/app/javascript/vue/tasks/digitize/main.js +++ b/app/javascript/vue/tasks/digitize/main.js @@ -1,11 +1,13 @@ import { createApp } from 'vue' import { newStore } from './store/store.js' +import { createPinia } from 'pinia' import HelpSystem from '@/plugins/help/help' import en from './lang/help/en' import App from './app.vue' function init() { const app = createApp(App) + app.use(createPinia()) app.use(newStore()) app.use(HelpSystem, { languages: { diff --git a/app/javascript/vue/tasks/digitize/store/actions/loadDigitalization.js b/app/javascript/vue/tasks/digitize/store/actions/loadDigitalization.js index da6f5a5200..dd0d9890b4 100644 --- a/app/javascript/vue/tasks/digitize/store/actions/loadDigitalization.js +++ b/app/javascript/vue/tasks/digitize/store/actions/loadDigitalization.js @@ -1,9 +1,16 @@ import ActionNames from './actionNames' import { MutationNames } from '../mutations/mutations' -import { COLLECTION_OBJECT, CONTAINER } from '@/constants/index.js' +import { + COLLECTION_OBJECT, + CONTAINER, + IDENTIFIER_LOCAL_RECORD_NUMBER +} from '@/constants/index.js' +import { useIdentifierStore } from '../pinia/identifiers' export default ({ commit, dispatch, state }, coId) => new Promise((resolve, reject) => { + const recordNumber = useIdentifierStore(IDENTIFIER_LOCAL_RECORD_NUMBER)() + state.settings.loading = true dispatch(ActionNames.GetCollectionObject, coId) .then(({ body }) => { @@ -23,8 +30,14 @@ export default ({ commit, dispatch, state }, coId) => } }) ) + promises.push( + recordNumber.load({ + objectId: response.body.id, + objectType: CONTAINER + }) + ) }) - .catch((_) => { + .catch(() => { promises.push( dispatch(ActionNames.GetIdentifiers, { id: coId, @@ -36,6 +49,13 @@ export default ({ commit, dispatch, state }, coId) => } }) ) + + promises.push( + recordNumber.load({ + objectId: coId, + objectType: COLLECTION_OBJECT + }) + ) }) if (coObject.collecting_event_id) { diff --git a/app/javascript/vue/tasks/digitize/store/actions/resetStore.js b/app/javascript/vue/tasks/digitize/store/actions/resetStore.js index 7d7afacf1e..53b574de4c 100644 --- a/app/javascript/vue/tasks/digitize/store/actions/resetStore.js +++ b/app/javascript/vue/tasks/digitize/store/actions/resetStore.js @@ -1,5 +1,9 @@ import { makeInitialState } from '../store.js' -import { EVENT_TAXON_DETERMINATION_FORM_RESET } from '@/constants/index.js' +import { useIdentifierStore } from '../pinia/identifiers.js' +import { + EVENT_TAXON_DETERMINATION_FORM_RESET, + IDENTIFIER_LOCAL_RECORD_NUMBER +} from '@/constants/index.js' const resetTaxonDeterminationForm = () => { const event = new CustomEvent(EVENT_TAXON_DETERMINATION_FORM_RESET) @@ -8,11 +12,13 @@ const resetTaxonDeterminationForm = () => { export default ({ state }) => { const { preferences, project_preferences } = state + const recordNumber = useIdentifierStore(IDENTIFIER_LOCAL_RECORD_NUMBER)() history.replaceState(null, null, '/tasks/accessions/comprehensive') state = Object.assign(state, makeInitialState()) state.preferences = preferences state.project_preferences = project_preferences + recordNumber.$reset() resetTaxonDeterminationForm() } diff --git a/app/javascript/vue/tasks/digitize/store/actions/resetWithDefault.js b/app/javascript/vue/tasks/digitize/store/actions/resetWithDefault.js index b03546bdee..e55111a714 100644 --- a/app/javascript/vue/tasks/digitize/store/actions/resetWithDefault.js +++ b/app/javascript/vue/tasks/digitize/store/actions/resetWithDefault.js @@ -1,7 +1,11 @@ import ActionNames from './actionNames' +import { useIdentifierStore } from '../pinia/identifiers' +import { IDENTIFIER_LOCAL_RECORD_NUMBER } from '@/constants' +import incrementIdentifier from '../../helpers/incrementIdentifier' export default ({ dispatch, state }) => { const { locked } = state.settings + const recordNumber = useIdentifierStore(IDENTIFIER_LOCAL_RECORD_NUMBER)() dispatch(ActionNames.NewCollectingEvent) dispatch(ActionNames.NewCollectionObject) @@ -23,8 +27,17 @@ export default ({ dispatch, state }) => { state.georeferences = [] } + recordNumber.reset({ + keepNamespace: locked.recordNumber, + increment: state.settings.incrementRecordNumber + }) + state.biologicalAssociations = locked.biologicalAssociations - ? state.biologicalAssociations.map(item => ({ ...item, id: undefined, global_id: undefined })) + ? state.biologicalAssociations.map((item) => ({ + ...item, + id: undefined, + global_id: undefined + })) : [] dispatch(ActionNames.ResetTaxonDetermination) diff --git a/app/javascript/vue/tasks/digitize/store/actions/saveContainerItem.js b/app/javascript/vue/tasks/digitize/store/actions/saveContainerItem.js index a5860651bb..2044b2ebde 100644 --- a/app/javascript/vue/tasks/digitize/store/actions/saveContainerItem.js +++ b/app/javascript/vue/tasks/digitize/store/actions/saveContainerItem.js @@ -1,8 +1,12 @@ import { MutationNames } from '../mutations/mutations' import { Identifier, ContainerItem, CollectionObject } from '@/routes/endpoints' +import { useIdentifierStore } from '../pinia/identifiers' +import { CONTAINER, IDENTIFIER_LOCAL_RECORD_NUMBER } from '@/constants' export default ({ commit, state }) => new Promise((resolve, reject) => { + const recordNumber = useIdentifierStore(IDENTIFIER_LOCAL_RECORD_NUMBER)() + if ( state.container && !state.containerItems.find( @@ -23,6 +27,11 @@ export default ({ commit, state }) => identifier_object_id: state.container.id } + recordNumber.save({ + objectType: CONTAINER, + objectId: state.container.id + }) + Identifier.update(identifier.id, { identifier }).then((response) => { state.identifiers[0] = response.body }) diff --git a/app/javascript/vue/tasks/digitize/store/actions/saveDigitalization.js b/app/javascript/vue/tasks/digitize/store/actions/saveDigitalization.js index 7cbaffc165..357e075a2c 100644 --- a/app/javascript/vue/tasks/digitize/store/actions/saveDigitalization.js +++ b/app/javascript/vue/tasks/digitize/store/actions/saveDigitalization.js @@ -2,6 +2,12 @@ import ActionNames from './actionNames' import { MutationNames } from '../mutations/mutations' import { EVENT_SMART_SELECTOR_UPDATE } from '@/constants/index.js' import { CollectionObject } from '@/routes/endpoints' +import { useIdentifierStore } from '../pinia/identifiers' +import { + IDENTIFIER_LOCAL_RECORD_NUMBER, + COLLECTION_OBJECT, + CONTAINER +} from '@/constants/index.js' const updateSmartSelectors = () => { const event = new CustomEvent(EVENT_SMART_SELECTOR_UPDATE) @@ -10,6 +16,8 @@ const updateSmartSelectors = () => { export default ({ commit, dispatch, state }, { resetAfter = false } = {}) => new Promise((resolve, reject) => { + const recordNumber = useIdentifierStore(IDENTIFIER_LOCAL_RECORD_NUMBER)() + state.settings.saving = true dispatch(ActionNames.SaveCollectingEvent) .then(() => { @@ -17,6 +25,10 @@ export default ({ commit, dispatch, state }, { resetAfter = false } = {}) => dispatch(ActionNames.SaveCollectionObject, state.collection_object) .then(({ body }) => { const coCreated = body + const payload = { + objectId: state.container ? state.container.id : coCreated.id, + objectType: state.container ? CONTAINER : COLLECTION_OBJECT + } commit(MutationNames.SetCollectionObject, coCreated) commit(MutationNames.AddCollectionObject, coCreated) @@ -26,7 +38,8 @@ export default ({ commit, dispatch, state }, { resetAfter = false } = {}) => dispatch(ActionNames.SaveCOCitations), dispatch(ActionNames.SaveIdentifier, coCreated.id), dispatch(ActionNames.SaveDeterminations), - dispatch(ActionNames.SaveBiologicalAssociations) + dispatch(ActionNames.SaveBiologicalAssociations), + recordNumber.save(payload) ] Promise.allSettled(actions) diff --git a/app/javascript/vue/tasks/digitize/store/pinia/identifiers.js b/app/javascript/vue/tasks/digitize/store/pinia/identifiers.js new file mode 100644 index 0000000000..ce06442794 --- /dev/null +++ b/app/javascript/vue/tasks/digitize/store/pinia/identifiers.js @@ -0,0 +1,129 @@ +import { defineStore } from 'pinia' +import { Identifier } from '@/routes/endpoints' +import incrementIdentifier from '../../helpers/incrementIdentifier' + +export function useIdentifierStore(type) { + function makeIdentifier(obj) { + return { + id: obj.id, + identifier: obj.identifier, + namespaceId: obj.namespace_id, + objectId: obj.identifier_object_id, + objectType: obj.identifier_object_type, + type, + isUnsaved: false + } + } + + function makePayload(obj) { + return { + identifier: { + id: obj.id, + identifier: obj.identifier, + namespace_id: obj.namespaceId, + identifier_object_id: obj.objectId, + identifier_object_type: obj.objectType, + type + } + } + } + + return defineStore(`identifier-${type}`, { + state: () => ({ + identifier: { + id: null, + identifier: null, + namespaceId: null, + type, + isUnsaved: true + }, + namespace: null, + identifiers: [], + existingIdentifiers: [] + }), + + getters: { + isNamespaceSet(state) { + return ( + state.identifier.namespaceId && state.identifier.identifier?.length + ) + } + }, + + actions: { + checkExistingIdentifiers() { + Identifier.where({ + namespace_id: this.identifier.namespaceId, + identifier: this.identifier.identifier, + type + }).then(({ body }) => { + this.existingIdentifiers = body.filter( + (item) => item.id !== this.identifier.id + ) + }) + }, + + save({ objectId, objectType }) { + if ( + this.existingIdentifiers.length || + !this.identifier.identifier || + !this.identifier.namespaceId || + !this.identifier.isUnsaved + ) + return + + const payload = makePayload({ + ...this.identifier, + objectId, + objectType + }) + + const request = this.identifier.id + ? Identifier.update(this.identifier.id, payload) + : Identifier.create(payload) + + request + .then(({ body }) => { + this.identifier = makeIdentifier(body) + }) + .catch(() => {}) + + return request + }, + + load({ objectId, objectType }) { + const request = Identifier.where({ + identifier_object_type: objectType, + identifier_object_id: objectId, + type + }) + .then(({ body }) => { + const list = body.map(makeIdentifier) + + this.identifiers = list + + if (list.length) { + this.identifier = list[0] + } + }) + .catch(() => {}) + + return request + }, + + reset({ keepNamespace, increment }) { + const { namespaceId, identifier } = this.identifier + + this.$reset() + + if (keepNamespace) { + this.identifier.namespaceId = namespaceId + } + + if (increment) { + this.identifier.identifier = incrementIdentifier(identifier) + } + } + } + }) +} diff --git a/app/javascript/vue/tasks/digitize/store/store.js b/app/javascript/vue/tasks/digitize/store/store.js index b643591458..3c9137de1f 100644 --- a/app/javascript/vue/tasks/digitize/store/store.js +++ b/app/javascript/vue/tasks/digitize/store/store.js @@ -25,6 +25,7 @@ function makeInitialState() { return reactive({ settings: { increment: false, + incrementRecordNumber: false, isLocked: false, lastChange: 0, lastSave: 0, @@ -45,6 +46,7 @@ function makeInitialState() { repository_id: false, current_repository_id: false }, + recordNumber: false, identifier: false, taxon_determination: { otu_id: false, @@ -91,6 +93,7 @@ function makeInitialState() { ceTotalUsed: 0, taxon_determinations: [], typeSpecimens: [], + existingIdentifiers: [], componentsOrder: { leftColumn: Object.values(ComponentLeftColumn), ComponentParse: Object.values(ComponentParse), From 272b688a74ffc602bbe0848d577e7cc4e9404482 Mon Sep 17 00:00:00 2001 From: jlpereira Date: Thu, 29 Aug 2024 21:01:16 -0300 Subject: [PATCH 49/88] Add record number identifier to new collecing event --- .../components/CollectionObjectsTable.vue | 316 +++++++++--------- .../components/Identifiers.vue | 8 +- 2 files changed, 168 insertions(+), 156 deletions(-) diff --git a/app/javascript/vue/tasks/collecting_events/new_collecting_event/components/CollectionObjectsTable.vue b/app/javascript/vue/tasks/collecting_events/new_collecting_event/components/CollectionObjectsTable.vue index 33f00cbe36..3ce490c03c 100644 --- a/app/javascript/vue/tasks/collecting_events/new_collecting_event/components/CollectionObjectsTable.vue +++ b/app/javascript/vue/tasks/collecting_events/new_collecting_event/components/CollectionObjectsTable.vue @@ -43,7 +43,7 @@ v-if="isLoading || isSaving" :legend=" isSaving - ? `Creating ${index} of ${count} collection object(s)...` + ? `Creating ${currentIndex} of ${count} collection object(s)...` : 'Loading...' " /> @@ -76,7 +76,14 @@ /> +