From 57a052c4589be3c6c02b9c62846ec05abead3698 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 28 Jan 2025 10:52:55 -0600 Subject: [PATCH 01/15] try handling null appVersion when getting altKeyName --- .../js_src/lib/components/Preferences/UserDefinitions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx index f3856c2fee4..f729df0cd31 100644 --- a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx +++ b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx @@ -64,7 +64,7 @@ const isDarkMode = ({ isRedirecting, }: PreferencesVisibilityContext): boolean => isDarkMode || isRedirecting; -const altKeyName = globalThis.navigator?.appVersion.includes('Mac') +const altKeyName = globalThis.navigator?.appVersion?.includes('Mac') ? 'Option' : 'Alt'; From c73d10b07bf6955a5cf9bb9a988517ab1f83576d Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 28 Jan 2025 11:20:37 -0600 Subject: [PATCH 02/15] replace deprecated navigator.appVersion with navigator.userAgent --- .../js_src/lib/components/Preferences/UserDefinitions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx index f729df0cd31..5184e499575 100644 --- a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx +++ b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx @@ -64,7 +64,7 @@ const isDarkMode = ({ isRedirecting, }: PreferencesVisibilityContext): boolean => isDarkMode || isRedirecting; -const altKeyName = globalThis.navigator?.appVersion?.includes('Mac') +const altKeyName = globalThis.navigator?.userAgent?.includes('Mac') ? 'Option' : 'Alt'; From dacec9eaf8ac679dcb10281fde89c28a5419eabc Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 28 Jan 2025 11:32:40 -0600 Subject: [PATCH 03/15] navigator may not be defined in some environments, like non-browser environments --- .../js_src/lib/components/Preferences/UserDefinitions.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx index 5184e499575..0554d5015a7 100644 --- a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx +++ b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx @@ -64,7 +64,8 @@ const isDarkMode = ({ isRedirecting, }: PreferencesVisibilityContext): boolean => isDarkMode || isRedirecting; -const altKeyName = globalThis.navigator?.userAgent?.includes('Mac') +// navigator may not be defined in some environments, like non-browser environments +const altKeyName = typeof navigator !== 'undefined' && navigator?.userAgent?.includes('Mac') ? 'Option' : 'Alt'; From 7089427342897910c7d42eb4d004ce010584ca98 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 28 Jan 2025 17:36:17 +0000 Subject: [PATCH 04/15] Lint code with ESLint and Prettier Triggered by dacec9eaf8ac679dcb10281fde89c28a5419eabc on branch refs/heads/issue-6129 --- .../js_src/lib/components/Preferences/UserDefinitions.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx index 0554d5015a7..044bbd9fc25 100644 --- a/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx +++ b/specifyweb/frontend/js_src/lib/components/Preferences/UserDefinitions.tsx @@ -64,7 +64,7 @@ const isDarkMode = ({ isRedirecting, }: PreferencesVisibilityContext): boolean => isDarkMode || isRedirecting; -// navigator may not be defined in some environments, like non-browser environments +// Navigator may not be defined in some environments, like non-browser environments const altKeyName = typeof navigator !== 'undefined' && navigator?.userAgent?.includes('Mac') ? 'Option' : 'Alt'; From 76b5d662d8ff75008beaea4990ad44700279ae08 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 28 Jan 2025 12:04:16 -0600 Subject: [PATCH 05/15] change libsasl2-modules-gssapi-mit to libsasl2-modules --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6c946ef85b1..f27ee5ad6b1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -49,7 +49,7 @@ RUN apt-get update \ curl \ git \ libsasl2-dev \ - libsasl2-modules-gssapi-mit \ + libsasl2-modules \ libldap2-dev \ libssl-dev \ libgmp-dev \ From 5aab6ee3f32990d5009225d8814a5fb14ef77de0 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 28 Jan 2025 14:01:34 -0600 Subject: [PATCH 06/15] trigger weblate localization workflow --- specifyweb/frontend/js_src/lib/localization/common.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/localization/common.ts b/specifyweb/frontend/js_src/lib/localization/common.ts index 8206e95ea66..b524a6a007d 100644 --- a/specifyweb/frontend/js_src/lib/localization/common.ts +++ b/specifyweb/frontend/js_src/lib/localization/common.ts @@ -15,7 +15,7 @@ export const commonText = createDictionary({ translators. `, 'en-us': 'Specify 7', - 'ru-ru': 'Specify 7', + //'ru-ru': 'Specify 7', 'es-es': 'Specify 7', 'fr-fr': 'Specify 7', 'uk-ua': 'Вкажіть 7', From bad8e594f3395e4b656db00ccce03102c69a741c Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 28 Jan 2025 14:48:04 -0600 Subject: [PATCH 07/15] add temp localization to trigger workflow --- specifyweb/frontend/js_src/lib/localization/common.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/localization/common.ts b/specifyweb/frontend/js_src/lib/localization/common.ts index b524a6a007d..b2586bf66f7 100644 --- a/specifyweb/frontend/js_src/lib/localization/common.ts +++ b/specifyweb/frontend/js_src/lib/localization/common.ts @@ -15,7 +15,7 @@ export const commonText = createDictionary({ translators. `, 'en-us': 'Specify 7', - //'ru-ru': 'Specify 7', + 'ru-ru': 'Specify 7', 'es-es': 'Specify 7', 'fr-fr': 'Specify 7', 'uk-ua': 'Вкажіть 7', @@ -736,6 +736,7 @@ export const commonText = createDictionary({ }, zoom: { 'en-us': 'Zoom', + 'fr-fr': 'Zoom', }, unzoom: { 'en-us': 'Unzoom', From 3cdf6e9f8f7aff4698b00951fe9b771f368fb1c7 Mon Sep 17 00:00:00 2001 From: Sharad S Date: Wed, 29 Jan 2025 17:39:43 -0500 Subject: [PATCH 08/15] Block save on invalid determinations --- .../components/DataModel/businessRuleDefs.ts | 52 ++++++++----------- .../components/DataModel/businessRuleUtils.ts | 1 + .../js_src/lib/localization/resources.ts | 4 ++ 3 files changed, 27 insertions(+), 30 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts index b92b36ae863..0fd4d207122 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts @@ -1,10 +1,10 @@ import { resourcesText } from '../../localization/resources'; -import { f } from '../../utils/functools'; import type { BusinessRuleResult } from './businessRules'; import { COG_PRIMARY_KEY, COG_TOITSELF, CURRENT_DETERMINATION_KEY, + DETERMINATION_TAXON_KEY, ensureSingleCollectionObjectCheck, hasNoCurrentDetermination, } from './businessRuleUtils'; @@ -19,7 +19,6 @@ import { updateLoanPrep, } from './interactionBusinessRules'; import type { SpecifyResource } from './legacyTypes'; -import { fetchResource, idFromUrl } from './resource'; import { setSaveBlockers } from './saveBlockers'; import { schema } from './schema'; import type { Collection } from './specifyTable'; @@ -170,38 +169,31 @@ export const businessRuleDefs: MappedBusinessRuleDefs = { }, fieldChecks: { collectionObjectType: async (resource): Promise => { - /* - * TEST: write tests for this - * Delete all determinations - */ const determinations = resource.getDependentResource('determinations'); - const currentDetermination = determinations?.models.find( - (determination) => determination.get('isCurrent') + if (determinations === undefined || determinations.models.length === 0) + return; + + const taxons = await Promise.all( + determinations.models.map((det) => det.rgetPromise('taxon')) ); + const coType = await resource.rgetPromise('collectionObjectType'); + const coTypeTreeDef = coType.get('taxonTreeDef'); - const taxonId = idFromUrl(currentDetermination?.get('taxon') ?? ''); - const COTypeID = idFromUrl(resource.get('collectionObjectType') ?? ''); - if ( - taxonId !== undefined && - COTypeID !== undefined && - currentDetermination !== undefined && - determinations !== undefined - ) - await f - .all({ - fetchedTaxon: fetchResource('Taxon', taxonId), - fetchedCOType: fetchResource('CollectionObjectType', COTypeID), - }) - .then(({ fetchedTaxon, fetchedCOType }) => { - const taxonTreeDefinition = fetchedTaxon.definition; - const COTypeTreeDefinition = fetchedCOType.taxonTreeDef; + // Block save when a Determination -> Taxon does not belong to the COType's tree definition + determinations.models.forEach((determination, index) => { + const taxon = taxons[index]; + const taxonTreeDef = taxon?.get('definition'); + const isValid = + typeof taxonTreeDef === 'string' && taxonTreeDef === coTypeTreeDef; + + setSaveBlockers( + determination, + determination.specifyTable.field.taxon, + isValid ? [] : [resourcesText.invalidDeterminationTaxon()], + DETERMINATION_TAXON_KEY + ); + }); - if (taxonTreeDefinition !== COTypeTreeDefinition) - resource.set('determinations', []); - }) - .catch((error) => { - console.error('Error fetching resources:', error); - }); return undefined; }, }, diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleUtils.ts b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleUtils.ts index be05d2c23aa..554d4c7ac96 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleUtils.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleUtils.ts @@ -7,6 +7,7 @@ export const CURRENT_DETERMINATION_KEY = 'determination-isCurrent'; export const COG_TOITSELF = 'cog-toItself'; export const PARENTCOG_KEY = 'cog-parentCog'; export const COG_PRIMARY_KEY = 'cog-isPrimary'; +export const DETERMINATION_TAXON_KEY = 'determination-Taxon'; /** * diff --git a/specifyweb/frontend/js_src/lib/localization/resources.ts b/specifyweb/frontend/js_src/lib/localization/resources.ts index 568dc4972fd..e1f570f1581 100644 --- a/specifyweb/frontend/js_src/lib/localization/resources.ts +++ b/specifyweb/frontend/js_src/lib/localization/resources.ts @@ -846,4 +846,8 @@ export const resourcesText = createDictionary({ 'en-us': 'A Consolidated Collection Object Group must have a primary Collection Object child', }, + invalidDeterminationTaxon: { + 'en-us': + 'Determination does not belong to the taxon tree associated with the Collection Object Type', + }, } as const); From 95caccf145a2afcabbdfb7aab5c978d530ff644f Mon Sep 17 00:00:00 2001 From: Sharad S Date: Wed, 29 Jan 2025 22:43:24 +0000 Subject: [PATCH 09/15] Lint code with ESLint and Prettier Triggered by 3cdf6e9f8f7aff4698b00951fe9b771f368fb1c7 on branch refs/heads/issue-6097 --- .../js_src/lib/components/DataModel/businessRuleDefs.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts index 0fd4d207122..a89856b6e9a 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/businessRuleDefs.ts @@ -174,7 +174,7 @@ export const businessRuleDefs: MappedBusinessRuleDefs = { return; const taxons = await Promise.all( - determinations.models.map((det) => det.rgetPromise('taxon')) + determinations.models.map(async (det) => det.rgetPromise('taxon')) ); const coType = await resource.rgetPromise('collectionObjectType'); const coTypeTreeDef = coType.get('taxonTreeDef'); From fb92bc999b81a116b0bb04b4ac6f2121743adb75 Mon Sep 17 00:00:00 2001 From: Sharad S Date: Thu, 30 Jan 2025 10:26:19 -0500 Subject: [PATCH 10/15] Add unit test --- .../DataModel/__tests__/businessRules.test.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/businessRules.test.ts b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/businessRules.test.ts index 73955b9c7be..f8cba708515 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/businessRules.test.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/businessRules.test.ts @@ -131,6 +131,41 @@ describe('Collection Object business rules', () => { }; overrideAjax(otherCollectionObjectTypeUrl, otherCollectionObjectType); + test('CollectionObject -> determinations: Save blocked when a determination does not belong to COT tree', async () => { + const collectionObject = getBaseCollectionObject(); + collectionObject.set( + 'collectionObjectType', + getResourceApiUrl('CollectionObjectType', 1) + ); + + const determination = + collectionObject.getDependentResource('determinations')?.models[0]; + + const { result } = renderHook(() => + useSaveBlockers(determination, tables.Determination.getField('Taxon')) + ); + + await act(async () => { + await collectionObject?.businessRuleManager?.checkField( + 'collectionObjectType' + ); + }); + expect(result.current[0]).toStrictEqual([ + resourcesText.invalidDeterminationTaxon(), + ]); + + collectionObject.set( + 'collectionObjectType', + getResourceApiUrl('CollectionObjectType', 2) + ); + await act(async () => { + await collectionObject?.businessRuleManager?.checkField( + 'collectionObjectType' + ); + }); + expect(result.current[0]).toStrictEqual([]); + }); + test('CollectionObject -> determinations: New determinations are current by default', async () => { const collectionObject = getBaseCollectionObject(); const determinations = From eee2038c2febcc81a8cf6b4c4fc3900966e5fda8 Mon Sep 17 00:00:00 2001 From: Caroline D <108160931+CarolineDenis@users.noreply.github.com> Date: Mon, 3 Feb 2025 13:30:00 -0800 Subject: [PATCH 11/15] Prevent from choosing other tree node parent in treeViewer Fixes #6189 --- .../lib/components/QueryComboBox/index.tsx | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx b/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx index 8eca661e45c..d197842b99e 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx @@ -264,25 +264,27 @@ export function QueryComboBox({ (typeof typeSearch === 'object' ? typeSearch?.table : undefined) ?? field.relatedTable; - const [fetchedTreeDefinition] = useAsyncState( - React.useCallback( - async () => - resource?.specifyTable === tables.Determination && - resource.collection?.related?.specifyTable === tables.CollectionObject - ? (resource.collection?.related as SpecifyResource) - .rgetPromise('collectionObjectType') - .then( - ( - collectionObjectType: - | SpecifyResource - | undefined - ) => collectionObjectType?.get('taxonTreeDef') - ) - : undefined, - [resource, resource?.collection?.related?.get('collectionObjectType')] - ), - false - ); + const [fetchedTreeDefinition] = useAsyncState( + React.useCallback(async () => { + if (resource?.specifyTable === tables.Determination) { + return resource.collection?.related?.specifyTable === tables.CollectionObject + ? (resource.collection?.related as SpecifyResource) + .rgetPromise('collectionObjectType') + .then( + ( + collectionObjectType: + | SpecifyResource + | undefined + ) => collectionObjectType?.get('taxonTreeDef') + ) + : undefined; + } else if (resource?.specifyTable === tables.Taxon) { + return resource.get('definition'); + } + return undefined; + }, [resource, resource?.collection?.related?.get('collectionObjectType')]), + false + ); // Tree Definition passed by a parent QCBX in the component tree const parentTreeDefinition = React.useContext(TreeDefinitionContext); From e8a33ea80a35b3a1a5abcb70c3b39111e9dbef43 Mon Sep 17 00:00:00 2001 From: Caroline D <108160931+CarolineDenis@users.noreply.github.com> Date: Mon, 3 Feb 2025 13:43:37 -0800 Subject: [PATCH 12/15] Prevent from adding node to wrong tree --- .../frontend/js_src/lib/components/QueryComboBox/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx b/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx index d197842b99e..6a47a733371 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx @@ -279,8 +279,8 @@ export function QueryComboBox({ ) : undefined; } else if (resource?.specifyTable === tables.Taxon) { - return resource.get('definition'); - } + return resource.get('definition') || resource.independentResources?.parent?.get('definition'); + } return undefined; }, [resource, resource?.collection?.related?.get('collectionObjectType')]), false From eefcc9f6dd04a971c06de87c92e1702d6199e9c9 Mon Sep 17 00:00:00 2001 From: Caroline D <108160931+CarolineDenis@users.noreply.github.com> Date: Tue, 4 Feb 2025 06:42:14 -0800 Subject: [PATCH 13/15] Add type assertion --- .../frontend/js_src/lib/components/QueryComboBox/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx b/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx index 6a47a733371..81f98171382 100644 --- a/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/QueryComboBox/index.tsx @@ -279,7 +279,9 @@ export function QueryComboBox({ ) : undefined; } else if (resource?.specifyTable === tables.Taxon) { - return resource.get('definition') || resource.independentResources?.parent?.get('definition'); + const definition = resource.get('definition') + const parentDefinition = (resource?.independentResources?.parent as SpecifyResource)?.get?.('definition'); + return definition || parentDefinition; } return undefined; }, [resource, resource?.collection?.related?.get('collectionObjectType')]), From 756f9e26511411ad93f5cbca02a782b0308aa934 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 4 Feb 2025 16:45:07 -0600 Subject: [PATCH 14/15] Update __init__.py to set SPECIFY_CONFIG_DIR to environment --- specifyweb/settings/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/settings/__init__.py b/specifyweb/settings/__init__.py index 963f3914f5a..6d90abf82d1 100644 --- a/specifyweb/settings/__init__.py +++ b/specifyweb/settings/__init__.py @@ -73,7 +73,7 @@ def get_sa_db_url(db_name): SPECIFY_THICK_CLIENT = os.path.expanduser(THICK_CLIENT_LOCATION) -SPECIFY_CONFIG_DIR = os.path.join(SPECIFY_THICK_CLIENT, "config") +SPECIFY_CONFIG_DIR = os.environ.get('SPECIFY_CONFIG_DIR', os.path.join(SPECIFY_THICK_CLIENT, "config")) RO_MODE = False From 8f3b3fbc4ac170a769ac401a3cd23ad05835e246 Mon Sep 17 00:00:00 2001 From: alec_dev Date: Tue, 4 Feb 2025 17:28:14 -0600 Subject: [PATCH 15/15] Update specify_settings.py to set SPECIFY_CONFIG_DIR to environment --- specifyweb/settings/specify_settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specifyweb/settings/specify_settings.py b/specifyweb/settings/specify_settings.py index db767dbe660..88cea0dccc4 100644 --- a/specifyweb/settings/specify_settings.py +++ b/specifyweb/settings/specify_settings.py @@ -1,8 +1,10 @@ +import os # Specify 7 requires the files from a Specify 6 install. # This setting should point to a directory containing an installation # of Specify 6 of the same version as the Specify database. THICK_CLIENT_LOCATION = '/opt/Specify' +SPECIFY_CONFIG_DIR = os.environ.get('SPECIFY_CONFIG_DIR', os.path.join(THICK_CLIENT_LOCATION, "config")) # Set the database name to the MySQL database you # want to access which must be a Specify database already