Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/production' into issue-4801
Browse files Browse the repository at this point in the history
  • Loading branch information
CarolineDenis committed May 6, 2024
2 parents c3c8bbb + 71ad206 commit 0ee3770
Show file tree
Hide file tree
Showing 32 changed files with 580 additions and 364 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,20 @@ describe('treeBusinessRules', () => {
resource_uri: '/api/specify/taxontreedefitem/2/',
};

const subSpeciesResponse: Partial<SerializedResource<TaxonTreeDefItem>> = {
id: 22,
fullNameSeparator: ' ',
isEnforced: false,
isInFullName: true,
name: 'Subspecies',
rankId: 230,
title: null,
version: 0,
parent: '/api/specify/taxontreedefitem/2/',
treeDef: '/api/specify/taxontreedef/1/',
resource_uri: '/api/specify/taxontreedefitem/22/',
};

const oxyrinchusFullNameResponse = 'Acipenser oxyrinchus';

overrideAjax('/api/specify/taxon/2/', animaliaResponse);
Expand All @@ -258,10 +272,19 @@ describe('treeBusinessRules', () => {
overrideAjax('/api/specify/taxon/5/', oxyrinchusSubSpeciesResponse);
overrideAjax('/api/specify/taxontreedefitem/9/', genusResponse);
overrideAjax('/api/specify/taxontreedefitem/2/', speciesResponse);
overrideAjax('/api/specify/taxontreedefitem/22/', subSpeciesResponse);
overrideAjax(
'/api/specify_tree/taxon/3/predict_fullname/?name=oxyrinchus&treedefitemid=2',
oxyrinchusFullNameResponse
);
overrideAjax('/api/specify/taxon/?limit=1&parent=4&orderby=rankid', {
objects: [oxyrinchusSubSpeciesResponse],
meta: {
limit: 1,
offset: 0,
total_count: 1,
},
});

test('fullName being set', async () => {
const oxyrinchus = new tables.Taxon.Resource({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import { isTreeResource } from '../InitialContext/treeRanks';
import type { BusinessRuleDefs } from './businessRuleDefs';
import { businessRuleDefs } from './businessRuleDefs';
import { backboneFieldSeparator, djangoLookupSeparator } from './helpers';
import type { AnySchema, AnyTree, CommonFields } from './helperTypes';
import type {
AnySchema,
AnyTree,
CommonFields,
TableFields,
} from './helperTypes';
import type { SpecifyResource } from './legacyTypes';
import {
getFieldBlockerKey,
Expand Down Expand Up @@ -77,7 +82,7 @@ export class BusinessRuleManager<SCHEMA extends AnySchema> {
isTreeResource(this.resource as SpecifyResource<AnySchema>)
? treeBusinessRules(
this.resource as SpecifyResource<AnyTree>,
processedFieldName
processedFieldName as TableFields<AnyTree>
)
: Promise.resolve({ isValid: true }),
];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { treeText } from '../../localization/tree';
import { ajax } from '../../utils/ajax';
import { f } from '../../utils/functools';
import { fetchPossibleRanks } from '../PickLists/TreeLevelPickList';
import { formatUrl } from '../Router/queryString';
import type { BusinessRuleResult } from './businessRules';
import type { AnyTree, FilterTablesByEndsWith } from './helperTypes';
import type {
AnyTree,
FilterTablesByEndsWith,
TableFields,
} from './helperTypes';
import type { SpecifyResource } from './legacyTypes';

// eslint-disable-next-line unicorn/prevent-abbreviations
type TreeDefItem<TREE extends AnyTree> =
export type TreeDefItem<TREE extends AnyTree> =
FilterTablesByEndsWith<`${TREE['tableName']}TreeDefItem`>;

export const initializeTreeRecord = (
Expand All @@ -19,15 +24,27 @@ export const initializeTreeRecord = (

export const treeBusinessRules = async (
resource: SpecifyResource<AnyTree>,
fieldName: string
fieldName: TableFields<AnyTree>
): Promise<BusinessRuleResult | undefined> =>
getRelatedTreeTables(resource).then(async ({ parent, definitionItem }) => {
if (parent === undefined) return undefined;

const parentDefItem = ((await parent.rgetPromise('definitionItem')) ??
undefined) as SpecifyResource<TreeDefItem<AnyTree>> | undefined;

const possibleRanks =
parentDefItem === undefined
? undefined
: await fetchPossibleRanks(resource, parentDefItem.get('rankId'));

const hasBadTreeStrcuture =
parent.id === resource.id ||
definitionItem === undefined ||
parent.get('rankId') >= definitionItem.get('rankId');
parent.get('rankId') >= definitionItem.get('rankId') ||
(possibleRanks !== undefined &&
!possibleRanks
.map(({ resource_uri }) => resource_uri)
.includes(definitionItem.get('resource_uri')));

if (!hasBadTreeStrcuture && (resource.get('name').length ?? 0) === 0)
return {
Expand Down
13 changes: 9 additions & 4 deletions specifyweb/frontend/js_src/lib/components/FormEditor/Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react';
import { useOutletContext } from 'react-router';
import { useNavigate } from 'react-router-dom';
import type { LocalizedString } from 'typesafe-i18n';
import { useAsyncState } from '../../hooks/useAsyncState';

import { useBooleanState } from '../../hooks/useBooleanState';
import { useId } from '../../hooks/useId';
Expand Down Expand Up @@ -192,10 +193,14 @@ export function PreviewView({
readonly onSelect: () => void;
}): JSX.Element {
const resource = React.useMemo(() => new table.Resource(), [table]);
const viewDefinition = React.useMemo(
() => parseViewDefinition(view, 'form', 'edit', table),
[view, table]
);
const viewDefinition = useAsyncState(
React.useCallback(
async () => parseViewDefinition(view, 'form', 'edit', table),
[view, table]
),
true
)[0];

return (
<Dialog
buttons={
Expand Down
58 changes: 28 additions & 30 deletions specifyweb/frontend/js_src/lib/components/FormEditor/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -295,39 +295,37 @@ function FormPreview({
ViewDescription | undefined
>(undefined);
React.useEffect(() => {
try {
const parsed = parseViewDefinition(
{
altviews: {
[table.name]: {
default: 'true',
mode: 'edit',
name: '',
viewdef: table.name,
},
parseViewDefinition(
{
altviews: {
[table.name]: {
default: 'true',
mode: 'edit',
name: '',
viewdef: table.name,
},
busrules: '',
class: table.longName,
name: localized(table.name),
view: '',
resourcelabels: 'true',
viewdefs: {
[table.name]: xml,
},
viewsetLevel: '',
viewsetName: '',
viewsetSource: '',
viewsetFile: null,
viewsetId: null,
},
'form',
'edit',
table
);
setViewDefinition(parsed);
} catch {
busrules: '',
class: table.longName,
name: localized(table.name),
view: '',
resourcelabels: 'true',
viewdefs: {
[table.name]: xml,
},
viewsetLevel: '',
viewsetName: '',
viewsetSource: '',
viewsetFile: null,
viewsetId: null,
},
'form',
'edit',
table
)
.then((definition) => setViewDefinition(definition))
// Ignore errors, as they would already be reported by the editor
}
.catch(() => undefined);
}, [xml, table]);
const resource = React.useMemo(() => new table.Resource(), [table]);
const [layout = 'horizontal'] = useCachedState('formEditor', 'layout');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ exports[`Can edit a form definition 1`] = `
},
],
},
"businessRules": "edu.ku.brc.specify.datamodel.busrules.AccessionBusRules",
"description": "The Accession form.",
"legacyResourceLabels": false,
"legacyUseBusinessRules": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,9 @@ const viewSets = (): ViewSets =>
{
name: localized('CollectionObjectAttachment'),
description: 'The Collection Object-Attachment View.',
businessRules: localized(
'edu.ku.brc.specify.datamodel.busrules.AttachmentBusRules'
),
legacyIsInternal: undefined,
legacyIsExternal: undefined,
legacyTable: undefined,
Expand Down Expand Up @@ -360,6 +363,8 @@ test('Create new view definition', () =>
],
},
description: 'The Collection Object-Attachment View.',
businessRules:
'edu.ku.brc.specify.datamodel.busrules.AttachmentBusRules',
legacyResourceLabels: false,
legacyUseBusinessRules: true,
name: 'CollectionObjectAttachment',
Expand Down Expand Up @@ -414,6 +419,8 @@ test('Create new view definition', () =>
],
},
description: '',
businessRules:
'edu.ku.brc.specify.datamodel.busrules.AttachmentBusRules',
legacyUseBusinessRules: true,
name: 'CollectionObjectAttachment_2',
table: '[table CollectionObjectAttachment]',
Expand Down Expand Up @@ -558,6 +565,8 @@ test('Add new view definition based on existing', () =>
],
},
description: 'The Collection Object-Attachment View.',
businessRules:
'edu.ku.brc.specify.datamodel.busrules.AttachmentBusRules',
legacyResourceLabels: false,
legacyUseBusinessRules: true,
name: 'CollectionObjectAttachment',
Expand Down Expand Up @@ -608,6 +617,8 @@ test('Add new view definition based on existing', () =>
],
},
description: 'The Collection Object-Attachment View.',
businessRules:
'edu.ku.brc.specify.datamodel.busrules.AttachmentBusRules',
legacyResourceLabels: false,
legacyUseBusinessRules: true,
name: 'A',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getLogContext, setLogContext } from '../Errors/logContext';
import type { ViewDefinition } from '../FormParse';
import { fromSimpleXmlNode } from '../Syncer/fromSimpleXmlNode';
import { createSimpleXmlNode } from '../Syncer/xmlToJson';
import { getBusinessRuleClassFromTable } from './helpers';
import type { ViewSets } from './spec';
import { parseFormView } from './spec';

Expand Down Expand Up @@ -91,6 +92,7 @@ function createNewView(
title: localized(table.name),
description: '',
table,
businessRules: localized(getBusinessRuleClassFromTable(table.name)),
legacyTable: undefined,
altViews,
legacyIsInternal: undefined,
Expand Down
82 changes: 82 additions & 0 deletions specifyweb/frontend/js_src/lib/components/FormEditor/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import type { RR } from '../../utils/types';
import type { Tables } from '../DataModel/types';

export const tablesWithBusRulesIn6 = new Set([
'AccessionAgentBusRules',
'AccessionAuthorizationBusRules',
'AccessionBusRules',
'AddressBusRules',
'AgentBusRules',
'AppraisalBusRules',
'AttachmentBusRules',
'AuthorBusRules',
'BioStratBusRules',
'BorrowBusRules',
'CatalogNumberingSchemeBusRules',
'CatAutoNumberingSchemeBusRules',
'CollectingEventBusRules',
'CollectingTripAuthorizationBusRules',
'CollectingTripBusRules',
'CollectionBusRules',
'CollectionObjectBusRules',
'CollectorBusRules',
'ConservDescriptionBusRules',
'ConservEventBusRules',
'ContainerBusRules',
'DataTypeBusRules',
'DeaccessionAgentBusRules',
'DeterminationBusRules',
'DeterminerBusRules',
'DisciplineBusRules',
'DivisionBusRules',
'DNASequenceBusRules',
'DNASequencingRunBusRules',
'ExchangeOutBusRules',
'ExchangeOutPrepBusRules',
'ExtractorBusRules',
'FieldNotebookBusRules',
'FieldNotebookPageSetBusRules',
'FundingAgentBusRules',
'GeographyBusRules',
'GeologicTimePeriodBusRules',
'GiftBusRules',
'GiftPreparationBusRules',
'GroupPersonBusRules',
'InfoRequestBusRules',
'LithoStratBusRules',
'LoanBusRules',
'LoanGiftShipmentBusRules',
'LoanPreparationBusRules',
'LoanReturnPreparationBusRules',
'LocalityBusRules',
'PcrPersonBusRules',
'PermitBusRules',
'PickListBusRules',
'PreparationBusRules',
'PrepTypeBusRules',
'SpecifyUserBusRules',
'StorageBusRules',
'TaxonBusRules',
'TreatmentEventBusRules',
'TreeDefBusRules',
]);

/**
* Most of the time business rules class name can be inferred from table name.
* Exceptions:
*/
export const businessRulesOverride: Partial<RR<keyof Tables, string>> = {
Shipment: 'LoanGiftShipment',
CollectionObjectAttachment: 'Attachment',
};

export const getBusinessRuleClassFromTable = (
tableName: keyof Tables
): string =>
businessRulesOverride[tableName] === undefined
? tablesWithBusRulesIn6.has(`${tableName}BusRules`)
? `edu.ku.brc.specify.datamodel.busrules.${tableName}BusRules`
: ''
: `edu.ku.brc.specify.datamodel.busrules.${businessRulesOverride[
tableName
]!}BusRules`;
Loading

0 comments on commit 0ee3770

Please sign in to comment.