From 66f0ab261fe58d0797d061e37e45662c00c82cf6 Mon Sep 17 00:00:00 2001 From: Max Patiiuk Date: Wed, 5 Apr 2023 10:14:30 -0500 Subject: [PATCH 1/7] Properly scope all front-end requests Make "domainFilter" parameter required in fetchCollection() to make it harder to forget to specify correct value for that parameter (it determines whether query is scoped). Added "domainFilter:true" everywhere where necessary Fixes #2292 You would notice that I added "domainFilter:false" in a lot of places that seem like then need scoping. Example: ```js fetchCollection('LoanPreparation', { isResolved: true, limit: DEFAULT_FETCH_LIMIT, preparation: preparation.get('id'), domainFilter: false, }) ``` This was done because the results would already be scoped correctly because a filter on preparation id was added. In this case, setting "domainFilter: true", won't make any difference, except that it adds more filters to the query thus has performance overhead --- .../lib/components/AppResources/hooks.ts | 43 +++++++++++-------- .../lib/components/Attachments/index.tsx | 4 +- .../DataModel/__tests__/collection.test.ts | 13 +++--- .../lib/components/DataModel/collection.ts | 22 +++++++--- .../js_src/lib/components/DataModel/domain.ts | 2 +- .../lib/components/DataModel/specifyModel.ts | 20 ++++++--- .../FormCommands/ShowTransactions.tsx | 4 ++ .../FormFields/useCollectionRelationships.tsx | 4 +- .../lib/components/FormFields/useTreeData.tsx | 1 + .../lib/components/FormPlugins/HostTaxon.tsx | 1 + .../FormPlugins/collectionRelData.ts | 4 +- .../lib/components/FormSliders/RecordSet.tsx | 5 +++ .../js_src/lib/components/Forms/DataTask.tsx | 2 + .../lib/components/HomePage/AboutSpecify.tsx | 2 +- .../PickLists/TreeLevelPickList.tsx | 1 + .../js_src/lib/components/PickLists/fetch.ts | 10 ++--- .../js_src/lib/components/Reports/Report.tsx | 2 + .../lib/components/SchemaConfig/Hooks.tsx | 4 ++ .../components/Security/UserCollections.tsx | 2 +- .../lib/components/Security/UserHooks.tsx | 1 + .../lib/components/Statistics/index.tsx | 9 +--- .../js_src/lib/components/Toolbar/Query.tsx | 1 + .../lib/components/Toolbar/RecordSets.tsx | 1 + .../lib/components/Toolbar/Security.tsx | 14 +++--- .../lib/components/WorkBench/DataSetMeta.tsx | 4 +- 25 files changed, 109 insertions(+), 67 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/AppResources/hooks.ts b/specifyweb/frontend/js_src/lib/components/AppResources/hooks.ts index 4f04f2f049e..6e7f1b51287 100644 --- a/specifyweb/frontend/js_src/lib/components/AppResources/hooks.ts +++ b/specifyweb/frontend/js_src/lib/components/AppResources/hooks.ts @@ -33,26 +33,32 @@ export function useAppResources(): GetOrSet { React.useCallback( async () => f.all({ - directories: fetchCollection('SpAppResourceDir', { limit: 0 }).then< - AppResources['directories'] - >(({ records }) => + directories: fetchCollection('SpAppResourceDir', { + limit: 0, + domainFilter: false, + }).then(({ records }) => records.map((record) => ({ ...record, scope: getScope(record) })) ), - disciplines: fetchCollection('Discipline', { limit: 0 }).then( - ({ records }) => records - ), - collections: fetchCollection('Collection', { limit: 0 }).then( - ({ records }) => records - ), - users: fetchCollection('SpecifyUser', { limit: 0 }).then( - ({ records }) => records - ), - appResources: fetchCollection('SpAppResource', { limit: 0 }).then( - ({ records }) => records - ), - viewSets: fetchCollection('SpViewSetObj', { limit: 0 }).then( - ({ records }) => records - ), + disciplines: fetchCollection('Discipline', { + limit: 0, + domainFilter: false, + }).then(({ records }) => records), + collections: fetchCollection('Collection', { + limit: 0, + domainFilter: false, + }).then(({ records }) => records), + users: fetchCollection('SpecifyUser', { + limit: 0, + domainFilter: false, + }).then(({ records }) => records), + appResources: fetchCollection('SpAppResource', { + limit: 0, + domainFilter: false, + }).then(({ records }) => records), + viewSets: fetchCollection('SpViewSetObj', { + limit: 0, + domainFilter: false, + }).then(({ records }) => records), }), [] ), @@ -117,6 +123,7 @@ export function useAppResourceData( ? await fetchCollection('SpAppResourceData', { limit: 1, [relationshipName]: resource.id, + domainFilter: false, }).then(({ records }) => typeof records[0] === 'object' ? records[0] : newResource ) diff --git a/specifyweb/frontend/js_src/lib/components/Attachments/index.tsx b/specifyweb/frontend/js_src/lib/components/Attachments/index.tsx index 3e270462416..da106b8acea 100644 --- a/specifyweb/frontend/js_src/lib/components/Attachments/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/Attachments/index.tsx @@ -93,6 +93,7 @@ function Attachments(): JSX.Element { 'Attachment', { limit: 1, + domainFilter: true, }, allTablesWithAttachments().length === tablesWithAttachments().length ? {} @@ -104,7 +105,7 @@ function Attachments(): JSX.Element { ).then(({ totalCount }) => totalCount), unused: fetchCollection( 'Attachment', - { limit: 1 }, + { limit: 1, domainFilter: true }, { tableId__isNull: 'true' } ).then(({ totalCount }) => totalCount), byTable: f.all( @@ -114,6 +115,7 @@ function Attachments(): JSX.Element { fetchCollection('Attachment', { limit: 1, tableID: tableId, + domainFilter: true, }).then(({ totalCount }) => totalCount), ]) ) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/collection.test.ts b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/collection.test.ts index c7e89dcd918..2b2c5e961bf 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/collection.test.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/collection.test.ts @@ -10,7 +10,7 @@ describe('fetchCollection', () => { const baseCoRecord = { resource_uri: getResourceApiUrl('CollectionObject', 1), }; - overrideAjax('/api/specify/collectionobject/?limit=1', { + overrideAjax('/api/specify/collectionobject/?limit=1&domainfilter=true', { meta: { total_count: 2, }, @@ -18,7 +18,9 @@ describe('fetchCollection', () => { }); test('Simple collection objects query', async () => - expect(fetchCollection('CollectionObject', { limit: 1 })).resolves.toEqual({ + expect( + fetchCollection('CollectionObject', { limit: 1, domainFilter: true }) + ).resolves.toEqual({ records: [addMissingFields('CollectionObject', baseCoRecord)], totalCount: 2, })); @@ -27,7 +29,7 @@ describe('fetchCollection', () => { resource_uri: getResourceApiUrl('Locality', 1), }; overrideAjax( - '/api/specify/locality/?limit=1&localityname=Test&orderby=-latlongaccuracy', + '/api/specify/locality/?limit=1&localityname=Test&orderby=-latlongaccuracy&domainfilter=true', { meta: { total_count: 2, @@ -42,6 +44,7 @@ describe('fetchCollection', () => { limit: 1, localityName: 'Test', orderBy: '-latLongAccuracy', + domainFilter: true, }) ).resolves.toEqual({ records: [addMissingFields('Locality', baseLocalityRecord)], @@ -49,7 +52,7 @@ describe('fetchCollection', () => { })); overrideAjax( - '/api/specify/locality/?limit=1&localityname__istarswith=Test&id__in=1%2C2', + '/api/specify/locality/?limit=1&localityname__istarswith=Test&id__in=1%2C2&domainfilter=false', { meta: { total_count: 2, @@ -62,7 +65,7 @@ describe('fetchCollection', () => { expect( fetchCollection( 'Locality', - { limit: 1 }, + { limit: 1, domainFilter: false }, { localityName__iStarsWith: 'Test', id__in: '1,2', diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts b/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts index 3bf0f820311..7fd974dfafe 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts @@ -25,7 +25,7 @@ export type CollectionFetchFilters = Partial< > & { readonly limit: number; readonly offset?: number; - readonly domainFilter?: boolean; + readonly domainFilter: boolean; readonly orderBy?: | keyof CommonFields | keyof SCHEMA['fields'] @@ -79,12 +79,7 @@ export const fetchCollection = async < }).map(([key, value]) => value === undefined ? undefined - : [ - key.toLowerCase(), - key === 'orderBy' - ? value.toString().toLowerCase() - : value.toString(), - ] + : [key.toLowerCase(), mapValue(key, value, tableName)] ) ) ) @@ -97,6 +92,18 @@ export const fetchCollection = async < totalCount: meta.total_count, })); +function mapValue( + key: string, + value: unknown, + tableName: keyof Tables +): string { + if (key === 'orderBy') return (value as string).toString().toLowerCase(); + else if (key === 'domainFilter') { + const scopingField = schema.models[tableName].getScopingRelationship(); + return (value === true && typeof scopingField === 'object').toString(); + } else return (value as string).toString(); +} + /** * Fetch a related collection via an relationship independent -to-many * relationship @@ -129,6 +136,7 @@ export async function fetchRelated< const response = fetchCollection(relationship.relatedModel.name, { limit, [reverseName]: id, + domainFilter: false, }); return response as Promise<{ readonly records: RA< diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/domain.ts b/specifyweb/frontend/js_src/lib/components/DataModel/domain.ts index 1492177c543..11fa10ea9ab 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/domain.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/domain.ts @@ -134,7 +134,7 @@ export async function fetchCollectionsForResource( ? undefined : fetchCollection( 'Collection', - { limit: 0 }, + { limit: 0, domainFilter: false }, { [fieldsBetween]: domainResource.id.toString(), } diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/specifyModel.ts b/specifyweb/frontend/js_src/lib/components/DataModel/specifyModel.ts index b0d8a0beda9..9fa37bd460b 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/specifyModel.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/specifyModel.ts @@ -34,8 +34,8 @@ import { getTableOverwrite, modelViews } from './schemaOverrides'; import type { Relationship } from './specifyField'; import { type FieldDefinition, - type RelationshipDefinition, LiteralField, + type RelationshipDefinition, } from './specifyField'; type FieldAlias = { @@ -412,18 +412,24 @@ export class SpecifyModel { return this.localization.aggregator ?? undefined; } + private scopingRelationship: Relationship | false | undefined; + /** * Returns the relationship field of this model that places it in * the collection -> discipline -> division -> institution scoping * hierarchy. */ public getScopingRelationship(): Relationship | undefined { - return schema.orgHierarchy - .map((fieldName) => this.getField(fieldName)) - .find( - (field): field is Relationship => - field?.isRelationship === true && !relationshipIsToMany(field) - ); + this.scopingRelationship ??= + schema.orgHierarchy + .map((fieldName) => this.getField(fieldName)) + .find( + (field): field is Relationship => + field?.isRelationship === true && !relationshipIsToMany(field) + ) ?? false; + return this.scopingRelationship === false + ? undefined + : this.scopingRelationship; } /** diff --git a/specifyweb/frontend/js_src/lib/components/FormCommands/ShowTransactions.tsx b/specifyweb/frontend/js_src/lib/components/FormCommands/ShowTransactions.tsx index 40157972ed8..d67230852f3 100644 --- a/specifyweb/frontend/js_src/lib/components/FormCommands/ShowTransactions.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormCommands/ShowTransactions.tsx @@ -72,6 +72,7 @@ export function ShowLoansCommand({ isResolved: false, limit: DEFAULT_FETCH_LIMIT, preparation: preparation.get('id'), + domainFilter: false, }).then(({ records }) => records.map(deserializeResource)) : undefined, resolvedLoans: hasTablePermission('LoanPreparation', 'read') @@ -79,18 +80,21 @@ export function ShowLoansCommand({ isResolved: true, limit: DEFAULT_FETCH_LIMIT, preparation: preparation.get('id'), + domainFilter: false, }).then(({ records }) => records.map(deserializeResource)) : undefined, gifts: hasTablePermission('GiftPreparation', 'read') ? fetchCollection('GiftPreparation', { limit: DEFAULT_FETCH_LIMIT, preparation: preparation.get('id'), + domainFilter: false, }).then(({ records }) => records.map(deserializeResource)) : undefined, exchanges: hasTablePermission('ExchangeOutPrep', 'read') ? fetchCollection('ExchangeOutPrep', { limit: DEFAULT_FETCH_LIMIT, preparation: preparation.get('id'), + domainFilter: false, }).then(({ records }) => records.map(deserializeResource)) : undefined, }), diff --git a/specifyweb/frontend/js_src/lib/components/FormFields/useCollectionRelationships.tsx b/specifyweb/frontend/js_src/lib/components/FormFields/useCollectionRelationships.tsx index 9a4e7aa6c0d..9a8622aad78 100644 --- a/specifyweb/frontend/js_src/lib/components/FormFields/useCollectionRelationships.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormFields/useCollectionRelationships.tsx @@ -36,7 +36,7 @@ export function useCollectionRelationships( return f.all({ left: fetchCollection( 'CollectionRelType', - { limit: DEFAULT_FETCH_LIMIT }, + { limit: DEFAULT_FETCH_LIMIT, domainFilter: false }, { // eslint-disable-next-line @typescript-eslint/naming-convention leftsidecollection_id: schema.domainLevelIds.collection, @@ -49,7 +49,7 @@ export function useCollectionRelationships( ), right: fetchCollection( 'CollectionRelType', - { limit: DEFAULT_FETCH_LIMIT }, + { limit: DEFAULT_FETCH_LIMIT, domainFilter: false }, { // eslint-disable-next-line @typescript-eslint/naming-convention rightsidecollection_id: schema.domainLevelIds.collection, diff --git a/specifyweb/frontend/js_src/lib/components/FormFields/useTreeData.tsx b/specifyweb/frontend/js_src/lib/components/FormFields/useTreeData.tsx index 59d21b3309e..400581da1e4 100644 --- a/specifyweb/frontend/js_src/lib/components/FormFields/useTreeData.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormFields/useTreeData.tsx @@ -44,6 +44,7 @@ export function useTreeData( { limit: 1, orderBy: 'rankId', + domainFilter: false, }, { // eslint-disable-next-line @typescript-eslint/naming-convention diff --git a/specifyweb/frontend/js_src/lib/components/FormPlugins/HostTaxon.tsx b/specifyweb/frontend/js_src/lib/components/FormPlugins/HostTaxon.tsx index d058cd17c8a..b6784f18da5 100644 --- a/specifyweb/frontend/js_src/lib/components/FormPlugins/HostTaxon.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormPlugins/HostTaxon.tsx @@ -38,6 +38,7 @@ export function HostTaxon({ fetchCollection('CollectionRelType', { limit: 1, name: relationship, + domainFilter: false, }) .then(async ({ records }) => f diff --git a/specifyweb/frontend/js_src/lib/components/FormPlugins/collectionRelData.ts b/specifyweb/frontend/js_src/lib/components/FormPlugins/collectionRelData.ts index ca591368cf6..5417c839126 100644 --- a/specifyweb/frontend/js_src/lib/components/FormPlugins/collectionRelData.ts +++ b/specifyweb/frontend/js_src/lib/components/FormPlugins/collectionRelData.ts @@ -63,7 +63,7 @@ export async function fetchOtherCollectionData( ): Promise { const { relationshipType, left, right } = await fetchCollection( 'CollectionRelType', - { name: relationship, limit: 1 } + { name: relationship, limit: 1, domainFilter: false } ) // BUG: this does not handle the not found case .then(({ records }) => deserializeResource(records[0])) @@ -107,7 +107,7 @@ export async function fetchOtherCollectionData( typeof resource.id === 'number' ? await fetchCollection( 'CollectionRelationship', - { limit: DEFAULT_FETCH_LIMIT }, + { limit: DEFAULT_FETCH_LIMIT, domainFilter: false }, side === 'left' ? { leftside_id: resource.id, diff --git a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx index e3942adb90d..1ede4b59524 100644 --- a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx @@ -62,6 +62,7 @@ export function RecordSetWrapper({ recordSet: recordSet.id, limit: 1, recordId: resource.id, + domainFilter: false, }).then(async ({ records }) => { const recordSetItemId = records[0]?.id; if (recordSetItemId === undefined) { @@ -77,6 +78,7 @@ export function RecordSetWrapper({ { recordSet: recordSet.id, limit: 1, + domainFilter: false, }, { id__lt: recordSetItemId } ); @@ -91,6 +93,7 @@ export function RecordSetWrapper({ fetchCollection('RecordSetItem', { limit: 1, recordSet: recordSet.id, + domainFilter: false, }).then(({ totalCount }) => totalCount), [recordSet.id] ), @@ -316,6 +319,7 @@ function RecordSet({ recordSet: recordSet.id, recordId: resource.id, limit: 1, + domainFilter: false, }).then(({ totalCount }) => totalCount !== 0), }) ) @@ -347,6 +351,7 @@ function RecordSet({ limit: 1, recordId: ids[currentIndex], recordSet: recordSet.id, + domainFilter: false, }).then(async ({ records }) => deleteResource( 'RecordSetItem', diff --git a/specifyweb/frontend/js_src/lib/components/Forms/DataTask.tsx b/specifyweb/frontend/js_src/lib/components/Forms/DataTask.tsx index 586d7853390..daa0c8e2138 100644 --- a/specifyweb/frontend/js_src/lib/components/Forms/DataTask.tsx +++ b/specifyweb/frontend/js_src/lib/components/Forms/DataTask.tsx @@ -106,6 +106,7 @@ function DisplayRecordSet({ offset: resourceIndex, orderBy: recordToOpen === 'first' ? 'id' : '-id', limit: 1, + domainFilter: false, }).then(({ records }) => isReadOnly && records.length === 0 ? setReadOnlyState(true) @@ -165,6 +166,7 @@ export function ViewResourceByGuid({ fetchCollection((model as SpecifyModel).name, { guid, limit: 1, + domainFilter: true, }).then(({ records }) => records[0]?.id ?? false), [model, guid] ), diff --git a/specifyweb/frontend/js_src/lib/components/HomePage/AboutSpecify.tsx b/specifyweb/frontend/js_src/lib/components/HomePage/AboutSpecify.tsx index 05701efc5d3..193cc366230 100644 --- a/specifyweb/frontend/js_src/lib/components/HomePage/AboutSpecify.tsx +++ b/specifyweb/frontend/js_src/lib/components/HomePage/AboutSpecify.tsx @@ -155,7 +155,7 @@ function DatabaseCreationDate(): JSX.Element { const [date] = useAsyncState( React.useCallback( async () => - fetchCollection('SpVersion', { limit: 1 }).then( + fetchCollection('SpVersion', { limit: 1, domainFilter: false }).then( ({ records }) => records[0]?.timestampCreated ), [] diff --git a/specifyweb/frontend/js_src/lib/components/PickLists/TreeLevelPickList.tsx b/specifyweb/frontend/js_src/lib/components/PickLists/TreeLevelPickList.tsx index fcf4ded7918..d18080becd5 100644 --- a/specifyweb/frontend/js_src/lib/components/PickLists/TreeLevelPickList.tsx +++ b/specifyweb/frontend/js_src/lib/components/PickLists/TreeLevelPickList.tsx @@ -55,6 +55,7 @@ export const fetchLowestChildRank = async ( limit: 1, parent: resource.id, orderBy: 'rankId', + domainFilter: false, }).then(({ records }) => records[0]?.rankId ?? -1); /** diff --git a/specifyweb/frontend/js_src/lib/components/PickLists/fetch.ts b/specifyweb/frontend/js_src/lib/components/PickLists/fetch.ts index f4370b8e378..ece7dc50841 100644 --- a/specifyweb/frontend/js_src/lib/components/PickLists/fetch.ts +++ b/specifyweb/frontend/js_src/lib/components/PickLists/fetch.ts @@ -6,12 +6,12 @@ import { fetchRows } from '../../utils/ajax/specifyApi'; import { f } from '../../utils/functools'; import type { R, RA } from '../../utils/types'; import { defined } from '../../utils/types'; -import { sortFunction, toLowerCase } from '../../utils/utils'; +import { sortFunction } from '../../utils/utils'; import { fetchCollection } from '../DataModel/collection'; import { deserializeResource, serializeResource } from '../DataModel/helpers'; import type { SerializedResource } from '../DataModel/helperTypes'; import type { SpecifyResource } from '../DataModel/legacyTypes'; -import { schema, strictGetModel } from '../DataModel/schema'; +import { strictGetModel } from '../DataModel/schema'; import type { PickList, PickListItem, Tables } from '../DataModel/types'; import { softFail } from '../Errors/Crash'; import type { PickListItemSimple } from '../FormFields/ComboBox'; @@ -112,10 +112,7 @@ async function fetchFromTable( const tableName = strictGetModel(pickList.get('tableName')).name; if (!hasTablePermission(tableName, 'read')) return []; const { records } = await fetchCollection(tableName, { - domainFilter: !f.includes( - Object.keys(schema.domainLevelIds), - toLowerCase(tableName) - ), + domainFilter: true, limit, }); return Promise.all( @@ -149,6 +146,7 @@ async function fetchFromField( limit, fields: { [fieldName]: ['string', 'number', 'boolean', 'null'] }, distinct: true, + domainFilter: true, }).then((rows) => rows .map((row) => row[fieldName] ?? '') diff --git a/specifyweb/frontend/js_src/lib/components/Reports/Report.tsx b/specifyweb/frontend/js_src/lib/components/Reports/Report.tsx index 03ed8de3f2f..27269ec5622 100644 --- a/specifyweb/frontend/js_src/lib/components/Reports/Report.tsx +++ b/specifyweb/frontend/js_src/lib/components/Reports/Report.tsx @@ -85,6 +85,7 @@ function ReportDialog({ fetchCollection('SpAppResourceData', { limit: 1, spAppResource: appResource.id, + domainFilter: false, }) .then(({ records }) => parseXml( @@ -153,6 +154,7 @@ async function fixupImages(definition: Document): Promise> { 'Attachment', { limit: 0, + domainFilter: false, }, { title__in: Object.keys(fileNames).join(','), diff --git a/specifyweb/frontend/js_src/lib/components/SchemaConfig/Hooks.tsx b/specifyweb/frontend/js_src/lib/components/SchemaConfig/Hooks.tsx index b26a8f35452..aad248f286e 100644 --- a/specifyweb/frontend/js_src/lib/components/SchemaConfig/Hooks.tsx +++ b/specifyweb/frontend/js_src/lib/components/SchemaConfig/Hooks.tsx @@ -70,6 +70,7 @@ export function useContainerString( fetchCollection('SpLocaleItemStr', { limit: 0, [itemType]: container.id, + domainFilter: false, }).then(({ records }) => { initialValue.current = findString( records, @@ -119,11 +120,13 @@ export function useContainerItems( items: fetchCollection('SpLocaleContainerItem', { limit: 0, container: container.id, + domainFilter: false, }), names: fetchCollection( 'SpLocaleItemStr', { limit: 0, + domainFilter: false, }, { itemName__container: container.id, @@ -137,6 +140,7 @@ export function useContainerItems( 'SpLocaleItemStr', { limit: 0, + domainFilter: false, }, { itemDesc__container: container.id, diff --git a/specifyweb/frontend/js_src/lib/components/Security/UserCollections.tsx b/specifyweb/frontend/js_src/lib/components/Security/UserCollections.tsx index 1d8f63adf34..2dc45a49b69 100644 --- a/specifyweb/frontend/js_src/lib/components/Security/UserCollections.tsx +++ b/specifyweb/frontend/js_src/lib/components/Security/UserCollections.tsx @@ -44,7 +44,7 @@ function UserCollectionsUi({ const [allCollections] = useAsyncState( React.useCallback( async () => - fetchCollection('Collection', { limit: 0 }).then( + fetchCollection('Collection', { limit: 0, domainFilter: false }).then( ({ records }) => records ), [] diff --git a/specifyweb/frontend/js_src/lib/components/Security/UserHooks.tsx b/specifyweb/frontend/js_src/lib/components/Security/UserHooks.tsx index 1c3585327d5..7e05cdc6a16 100644 --- a/specifyweb/frontend/js_src/lib/components/Security/UserHooks.tsx +++ b/specifyweb/frontend/js_src/lib/components/Security/UserHooks.tsx @@ -150,6 +150,7 @@ export function useUserAgents( { limit: 1, specifyUser: userId, + domainFilter: false, }, { division__in: divisions.map(([id]) => id).join(','), diff --git a/specifyweb/frontend/js_src/lib/components/Statistics/index.tsx b/specifyweb/frontend/js_src/lib/components/Statistics/index.tsx index 63d9228086a..962b246b991 100644 --- a/specifyweb/frontend/js_src/lib/components/Statistics/index.tsx +++ b/specifyweb/frontend/js_src/lib/components/Statistics/index.tsx @@ -16,7 +16,6 @@ import { Form } from '../Atoms/Form'; import { Submit } from '../Atoms/Submit'; import { softFail } from '../Errors/Crash'; import { useMenuItem } from '../Header/useMenuItem'; -import { userInformation } from '../InitialContext/userInformation'; import { DateElement } from '../Molecules/DateElement'; import { downloadFile } from '../Molecules/FilePicker'; import { hasPermission } from '../Permissions/helpers'; @@ -301,13 +300,7 @@ function ProtectedStatsPage(): JSX.Element | null { formatterSpec ); - const filters = React.useMemo( - () => ({ - specifyUser: userInformation.id, - }), - [] - ); - const queries = useQueries(filters); + const queries = useQueries(); const previousCollectionLayout = React.useRef( sharedLayout as unknown as RA ); diff --git a/specifyweb/frontend/js_src/lib/components/Toolbar/Query.tsx b/specifyweb/frontend/js_src/lib/components/Toolbar/Query.tsx index b80380b35ac..a85ae989497 100644 --- a/specifyweb/frontend/js_src/lib/components/Toolbar/Query.tsx +++ b/specifyweb/frontend/js_src/lib/components/Toolbar/Query.tsx @@ -62,6 +62,7 @@ export function useQueries( async () => fetchCollection('SpQuery', { limit: QUERY_FETCH_LIMIT, + domainFilter: false, ...(spQueryFilter ?? { specifyUser: userInformation.id }), }).then(({ records }) => records), [spQueryFilter] diff --git a/specifyweb/frontend/js_src/lib/components/Toolbar/RecordSets.tsx b/specifyweb/frontend/js_src/lib/components/Toolbar/RecordSets.tsx index ce7ce2a66e3..76de84d38fa 100644 --- a/specifyweb/frontend/js_src/lib/components/Toolbar/RecordSets.tsx +++ b/specifyweb/frontend/js_src/lib/components/Toolbar/RecordSets.tsx @@ -228,6 +228,7 @@ function Row({ fetchCollection('RecordSetItem', { limit: 1, recordSet: recordSet.id, + domainFilter: false, }).then(({ totalCount }) => totalCount), [recordSet] ), diff --git a/specifyweb/frontend/js_src/lib/components/Toolbar/Security.tsx b/specifyweb/frontend/js_src/lib/components/Toolbar/Security.tsx index 4acffabaa50..4a5fa1d011f 100644 --- a/specifyweb/frontend/js_src/lib/components/Toolbar/Security.tsx +++ b/specifyweb/frontend/js_src/lib/components/Toolbar/Security.tsx @@ -40,9 +40,10 @@ export function SecurityPanel(): JSX.Element | null { React.useCallback( async () => hasTablePermission('Institution', 'read') - ? fetchCollection('Institution', { limit: 1 }).then( - ({ records }) => records[0] - ) + ? fetchCollection('Institution', { + limit: 1, + domainFilter: false, + }).then(({ records }) => records[0]) : undefined, [] ), @@ -53,9 +54,10 @@ export function SecurityPanel(): JSX.Element | null { React.useCallback( async () => hasTablePermission('SpecifyUser', 'read') - ? fetchCollection('SpecifyUser', { limit: 0 }).then(({ records }) => - index(records) - ) + ? fetchCollection('SpecifyUser', { + limit: 0, + domainFilter: false, + }).then(({ records }) => index(records)) : { [userInformation.id]: serializeResource(userInformation), }, diff --git a/specifyweb/frontend/js_src/lib/components/WorkBench/DataSetMeta.tsx b/specifyweb/frontend/js_src/lib/components/WorkBench/DataSetMeta.tsx index beb124d165d..b0e805b993b 100644 --- a/specifyweb/frontend/js_src/lib/components/WorkBench/DataSetMeta.tsx +++ b/specifyweb/frontend/js_src/lib/components/WorkBench/DataSetMeta.tsx @@ -343,8 +343,8 @@ function DataSetName({ const fetchListOfUsers = async (): Promise< RA> > => - fetchCollection('SpecifyUser', { limit: 500 }).then(({ records: users }) => - users.filter(({ id }) => id !== userInformation.id) + fetchCollection('SpecifyUser', { limit: 500, domainFilter: false }).then( + ({ records: users }) => users.filter(({ id }) => id !== userInformation.id) ); function ChangeOwner({ From 0b9a4d67806060adae6581d37d81667634a8d64f Mon Sep 17 00:00:00 2001 From: Max Patiiuk Date: Wed, 5 Apr 2023 10:30:01 -0500 Subject: [PATCH 2/7] Add tests for scoping of fetched resources --- .../DataModel/__tests__/collection.test.ts | 26 +++++++++++++-- .../lib/components/DataModel/collection.ts | 18 +++++++---- .../lib/components/FormSliders/RecordSet.tsx | 1 + .../js_src/lib/components/Forms/DataTask.tsx | 2 +- .../PickLists/__tests__/fetch.test.ts | 32 ++++++++----------- 5 files changed, 51 insertions(+), 28 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/collection.test.ts b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/collection.test.ts index 554fc142beb..d4ee4e85c37 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/collection.test.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/collection.test.ts @@ -25,11 +25,33 @@ describe('fetchCollection', () => { totalCount: 2, })); + const baseInstitutionRecord = { + resource_uri: getResourceApiUrl('Institution', 1), + }; + overrideAjax('/api/specify/institution/?limit=1', { + meta: { + total_count: 2, + }, + objects: [baseInstitutionRecord], + }); + + test("If query can't be scoped, it won't be", async () => + expect( + /* + * Deposit "domainFilter: true", false will be sent to back-end because + * this table can't be scoped + */ + fetchCollection('Institution', { limit: 1, domainFilter: true }) + ).resolves.toEqual({ + records: [addMissingFields('Institution', baseInstitutionRecord)], + totalCount: 2, + })); + const baseLocalityRecord = { resource_uri: getResourceApiUrl('Locality', 1), }; overrideAjax( - '/api/specify/locality/?limit=1&localityname=Test&orderby=-latlongaccuracy&yesno1=True&domainfilter=false', + '/api/specify/locality/?limit=1&localityname=Test&orderby=-latlongaccuracy&yesno1=True', { meta: { total_count: 2, @@ -53,7 +75,7 @@ describe('fetchCollection', () => { })); overrideAjax( - '/api/specify/locality/?limit=1&localityname__istarswith=Test&id__in=1%2C2&domainfilter=false', + '/api/specify/locality/?limit=1&localityname__istarswith=Test&id__in=1%2C2', { meta: { total_count: 2, diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts b/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts index e579775ab7c..639567cbdce 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts @@ -76,11 +76,15 @@ export const fetchCollection = async < Object.entries({ ...filters, ...advancedFilters, - }).map(([key, value]) => - value === undefined + }).map(([key, value]) => { + const mapped = + value === undefined + ? undefined + : mapValue(key, value, tableName); + return mapped === undefined ? undefined - : [key.toLowerCase(), mapValue(key, value, tableName)] - ) + : ([key.toLowerCase(), mapped] as const); + }) ) ) ) @@ -96,11 +100,13 @@ function mapValue( key: string, value: unknown, tableName: keyof Tables -): string { +): string | undefined { if (key === 'orderBy') return (value as string).toString().toLowerCase(); else if (key === 'domainFilter') { const scopingField = schema.models[tableName].getScopingRelationship(); - return (value === true && typeof scopingField === 'object').toString(); + return value === true && typeof scopingField === 'object' + ? 'true' + : undefined; } else if (typeof value === 'boolean') return value ? 'True' : 'False'; else return (value as string).toString(); } diff --git a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx index 1ede4b59524..ed93b278d2b 100644 --- a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx @@ -124,6 +124,7 @@ const fetchItems = async ( ): Promise> => fetchRows('RecordSetItem', { limit: fetchSize, + domainFilter: false, recordSet: recordSetId, orderBy: 'id', offset, diff --git a/specifyweb/frontend/js_src/lib/components/Forms/DataTask.tsx b/specifyweb/frontend/js_src/lib/components/Forms/DataTask.tsx index daa0c8e2138..9f717008096 100644 --- a/specifyweb/frontend/js_src/lib/components/Forms/DataTask.tsx +++ b/specifyweb/frontend/js_src/lib/components/Forms/DataTask.tsx @@ -166,7 +166,7 @@ export function ViewResourceByGuid({ fetchCollection((model as SpecifyModel).name, { guid, limit: 1, - domainFilter: true, + domainFilter: false, }).then(({ records }) => records[0]?.id ?? false), [model, guid] ), diff --git a/specifyweb/frontend/js_src/lib/components/PickLists/__tests__/fetch.test.ts b/specifyweb/frontend/js_src/lib/components/PickLists/__tests__/fetch.test.ts index 083dd5a1f7e..c01f1a5996b 100644 --- a/specifyweb/frontend/js_src/lib/components/PickLists/__tests__/fetch.test.ts +++ b/specifyweb/frontend/js_src/lib/components/PickLists/__tests__/fetch.test.ts @@ -58,30 +58,24 @@ describe('unsafeFetchPickList', () => { const otherPickList = { resource_uri: getResourceApiUrl('PickList', 2), }; - overrideAjax( - '/api/specify/picklist/?name=otherCollection&limit=1&domainfilter=false', - { - meta: { - total_count: 1, - }, - objects: [otherPickList], - } - ); + overrideAjax('/api/specify/picklist/?name=otherCollection&limit=1', { + meta: { + total_count: 1, + }, + objects: [otherPickList], + }); test('pick list from other collection', async () => { const resource = await unsafeFetchPickList('otherCollection'); const serialized = serializeResource(resource!); expect(serialized).toEqual(addMissingFields('PickList', otherPickList)); }); - overrideAjax( - '/api/specify/picklist/?name=unknownPickList&limit=1&domainfilter=false', - { - meta: { - total_count: 0, - }, - objects: [], - } - ); + overrideAjax('/api/specify/picklist/?name=unknownPickList&limit=1', { + meta: { + total_count: 0, + }, + objects: [], + }); overrideAjax( '/api/specify/picklist/?name=unknownPickList&limit=1&domainfilter=true', @@ -135,7 +129,7 @@ describe('fetchPickListItems', () => { }); overrideAjax( - '/api/specify_rows/locality/?limit=0&distinct=true&fields=localityname', + '/api/specify_rows/locality/?limit=0&domainfilter=true&distinct=true&fields=localityname', [['abc']] ); test('entire column', async () => { From 4820c890622baf717232bdc44a5b04a49b2ae979 Mon Sep 17 00:00:00 2001 From: Max Patiiuk Date: Wed, 5 Apr 2023 15:43:26 +0000 Subject: [PATCH 3/7] Lint code with ESLint and Prettier Triggered by 0b9a4d67806060adae6581d37d81667634a8d64f on branch refs/heads/issue-2292 --- .../frontend/js_src/lib/components/AppResources/hooks.ts | 2 +- .../frontend/js_src/lib/components/DataModel/specifyModel.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/AppResources/hooks.ts b/specifyweb/frontend/js_src/lib/components/AppResources/hooks.ts index 6e7f1b51287..3794206b042 100644 --- a/specifyweb/frontend/js_src/lib/components/AppResources/hooks.ts +++ b/specifyweb/frontend/js_src/lib/components/AppResources/hooks.ts @@ -17,7 +17,7 @@ import type { } from '../DataModel/types'; import { getAppResourceCount, getAppResourceMode } from './helpers'; import { getAppResourceTree, getScope } from './tree'; -import { ScopedAppResourceDir } from './types'; +import type { ScopedAppResourceDir } from './types'; export type AppResources = { readonly directories: RA; diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/specifyModel.ts b/specifyweb/frontend/js_src/lib/components/DataModel/specifyModel.ts index 9fa37bd460b..ba8504babf8 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/specifyModel.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/specifyModel.ts @@ -34,8 +34,8 @@ import { getTableOverwrite, modelViews } from './schemaOverrides'; import type { Relationship } from './specifyField'; import { type FieldDefinition, - LiteralField, type RelationshipDefinition, + LiteralField, } from './specifyField'; type FieldAlias = { @@ -412,7 +412,7 @@ export class SpecifyModel { return this.localization.aggregator ?? undefined; } - private scopingRelationship: Relationship | false | undefined; + private readonly scopingRelationship: Relationship | false | undefined; /** * Returns the relationship field of this model that places it in From f31ecce72e5c872f120fb088493d62246e3cf81a Mon Sep 17 00:00:00 2001 From: melton-jason Date: Tue, 26 Dec 2023 18:46:30 +0000 Subject: [PATCH 4/7] Lint code with ESLint and Prettier Triggered by a64ca2aca2320b845d6268627b1c6ddda7ec36db on branch refs/heads/issue-2292 --- .../components/DataModel/__tests__/specifyModel.test.ts | 8 ++++++-- .../js_src/lib/components/FormSliders/RecordSet.tsx | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/specifyModel.test.ts b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/specifyModel.test.ts index 5b16f7288f9..87cb9a600db 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/specifyModel.test.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/__tests__/specifyModel.test.ts @@ -606,9 +606,13 @@ describe('getScopingRelationship', () => { 'discipline' )); test('can get scoping relationship when scoped to Division', () => - expect(schema.models.Discipline.getScopingRelationship()?.name).toBe('division')); + expect(schema.models.Discipline.getScopingRelationship()?.name).toBe( + 'division' + )); test('can get scoping relationship when scoped to Institution', () => - expect(schema.models.Division.getScopingRelationship()?.name).toBe('institution')); + expect(schema.models.Division.getScopingRelationship()?.name).toBe( + 'institution' + )); test('returns undefined if table is not scoped', () => expect(schema.models.SpecifyUser.getScopingRelationship()).toBeUndefined()); }); diff --git a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx index 15147049c82..071d02ad78e 100644 --- a/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx +++ b/specifyweb/frontend/js_src/lib/components/FormSliders/RecordSet.tsx @@ -103,7 +103,7 @@ export function RecordSetWrapper({ : fetchCollection('RecordSetItem', { limit: 1, recordSet: recordSet.id, - domainFilter: false + domainFilter: false, }).then(({ totalCount }) => totalCount), [recordSet.id] ), From 1c93b31e281542c0fb7f4899274cf69149912e13 Mon Sep 17 00:00:00 2001 From: melton-jason Date: Tue, 26 Dec 2023 14:00:58 -0600 Subject: [PATCH 5/7] Add preference for scoping picklists based on Table --- .../components/InitialContext/remotePrefs.ts | 7 +++ .../PickLists/__tests__/fetch.test.ts | 51 +++++++++++++++++++ .../js_src/lib/components/PickLists/fetch.ts | 15 ++++-- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts b/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts index c4fd6d523aa..c9fdd21a35d 100644 --- a/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts +++ b/specifyweb/frontend/js_src/lib/components/InitialContext/remotePrefs.ts @@ -304,4 +304,11 @@ export const collectionPrefsDefinitions = { defaultValue: false, parser: 'java.lang.Boolean', }, + sp7_scope_table_picklists: { + separator: '_', + description: + "Whether to scope picklistitems for picklists of type 'Entire Table'", + defaultValue: true, + parser: 'java.lang.Boolean', + }, } as const; diff --git a/specifyweb/frontend/js_src/lib/components/PickLists/__tests__/fetch.test.ts b/specifyweb/frontend/js_src/lib/components/PickLists/__tests__/fetch.test.ts index c01f1a5996b..6c1553e42dd 100644 --- a/specifyweb/frontend/js_src/lib/components/PickLists/__tests__/fetch.test.ts +++ b/specifyweb/frontend/js_src/lib/components/PickLists/__tests__/fetch.test.ts @@ -128,6 +128,57 @@ describe('fetchPickListItems', () => { ]); }); + overrideAjax('/api/specify/collection/?domainfilter=true&limit=0', { + meta: { + total_count: 1, + }, + objects: [{ id: 1, _tableName: 'Collection', collectionname: 'abc' }], + }); + + overrideAjax('/api/specify/collection/?limit=0', { + meta: { + total_count: 2, + }, + objects: [ + { id: 1, _tableName: 'Collection', collectionname: 'abc' }, + { id: 2, _tableName: 'Collection', collectionname: 'cba' }, + ], + }); + + test('Picklistitems for Entire Table scoped by default', async () => { + const picklist = deserializeResource( + addMissingFields('PickList', { + type: PickListTypes.TABLE, + tableName: 'Collection', + }) + ); + const items = await fetchPickListItems(picklist); + + expect(items.map((item) => removeKey(item, 'timestampCreated'))).toEqual([ + removeKey(createPickListItem('1', 'abc'), 'timestampCreated'), + ]); + }); + + test('Picklistitems unscoped for sp7_scope_table_picklists', async () => { + const remotePrefs = await import('../../InitialContext/remotePrefs'); + jest + .spyOn(remotePrefs, 'getCollectionPref') + .mockImplementation(() => false); + + const picklist = deserializeResource( + addMissingFields('PickList', { + type: PickListTypes.TABLE, + tableName: 'Collection', + }) + ); + const items = await fetchPickListItems(picklist); + + expect(items.map((item) => removeKey(item, 'timestampCreated'))).toEqual([ + removeKey(createPickListItem('1', 'abc'), 'timestampCreated'), + removeKey(createPickListItem('2', 'cba'), 'timestampCreated'), + ]); + }); + overrideAjax( '/api/specify_rows/locality/?limit=0&domainfilter=true&distinct=true&fields=localityname', [['abc']] diff --git a/specifyweb/frontend/js_src/lib/components/PickLists/fetch.ts b/specifyweb/frontend/js_src/lib/components/PickLists/fetch.ts index ece7dc50841..1ba41d48468 100644 --- a/specifyweb/frontend/js_src/lib/components/PickLists/fetch.ts +++ b/specifyweb/frontend/js_src/lib/components/PickLists/fetch.ts @@ -6,16 +6,17 @@ import { fetchRows } from '../../utils/ajax/specifyApi'; import { f } from '../../utils/functools'; import type { R, RA } from '../../utils/types'; import { defined } from '../../utils/types'; -import { sortFunction } from '../../utils/utils'; +import { sortFunction, toLowerCase } from '../../utils/utils'; import { fetchCollection } from '../DataModel/collection'; import { deserializeResource, serializeResource } from '../DataModel/helpers'; import type { SerializedResource } from '../DataModel/helperTypes'; import type { SpecifyResource } from '../DataModel/legacyTypes'; -import { strictGetModel } from '../DataModel/schema'; +import { schema, strictGetModel } from '../DataModel/schema'; import type { PickList, PickListItem, Tables } from '../DataModel/types'; import { softFail } from '../Errors/Crash'; import type { PickListItemSimple } from '../FormFields/ComboBox'; import { format } from '../Forms/dataObjFormatters'; +import { getCollectionPref } from '../InitialContext/remotePrefs'; import { hasTablePermission, hasToolPermission } from '../Permissions/helpers'; import { createPickListItem, @@ -111,8 +112,16 @@ async function fetchFromTable( ): Promise> { const tableName = strictGetModel(pickList.get('tableName')).name; if (!hasTablePermission(tableName, 'read')) return []; + + const scopeTablePicklist = getCollectionPref( + 'sp7_scope_table_picklists', + schema.domainLevelIds.collection + ); + const { records } = await fetchCollection(tableName, { - domainFilter: true, + domainFilter: scopeTablePicklist + ? true + : !f.includes(Object.keys(schema.domainLevelIds), toLowerCase(tableName)), limit, }); return Promise.all( From 05ffff3c457e2986d54d76664a1cf8b96c32776f Mon Sep 17 00:00:00 2001 From: melton-jason Date: Tue, 26 Dec 2023 15:01:58 -0600 Subject: [PATCH 6/7] If provided, allow domainfilter to be passed with Attachment requests --- .../frontend/js_src/lib/components/DataModel/collection.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts b/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts index 639567cbdce..ddd73930f01 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts @@ -104,7 +104,8 @@ function mapValue( if (key === 'orderBy') return (value as string).toString().toLowerCase(); else if (key === 'domainFilter') { const scopingField = schema.models[tableName].getScopingRelationship(); - return value === true && typeof scopingField === 'object' + return value === true && + (tableName === 'Attachment' || typeof scopingField === 'object') ? 'true' : undefined; } else if (typeof value === 'boolean') return value ? 'True' : 'False'; From 97bf9a80747a5cc4b1bbbe2ce8a082e88484cdce Mon Sep 17 00:00:00 2001 From: melton-jason Date: Mon, 15 Jan 2024 09:19:53 -0600 Subject: [PATCH 7/7] Use getScope over getScopingRelationship for determining domainFilter --- .../frontend/js_src/lib/components/DataModel/collection.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts b/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts index ddd73930f01..bb4b5761861 100644 --- a/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts +++ b/specifyweb/frontend/js_src/lib/components/DataModel/collection.ts @@ -103,7 +103,7 @@ function mapValue( ): string | undefined { if (key === 'orderBy') return (value as string).toString().toLowerCase(); else if (key === 'domainFilter') { - const scopingField = schema.models[tableName].getScopingRelationship(); + const scopingField = schema.models[tableName].getScope(); return value === true && (tableName === 'Attachment' || typeof scopingField === 'object') ? 'true'