Skip to content

Commit dfa30a6

Browse files
authored
Merge pull request #5091 from specify/issue-4980
Add a feature to choose type of taxon tree in WB
2 parents f58f0d4 + eb3c820 commit dfa30a6

File tree

22 files changed

+875
-174
lines changed

22 files changed

+875
-174
lines changed

specifyweb/frontend/js_src/lib/components/DataModel/__tests__/schemaBase.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ test('domain data is fetched and parsed correctly', async () =>
2424
],
2525
paleoContextChildTable: 'collectionobject',
2626
referenceSymbol: '#',
27-
treeSymbol: '$',
27+
treeRankSymbol: '$',
28+
treeDefinitionSymbol: '%',
2829
}));

specifyweb/frontend/js_src/lib/components/DataModel/schema.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ type Schema = {
2929
'Institution'
3030
];
3131
readonly referenceSymbol: string;
32-
readonly treeSymbol: string;
32+
readonly treeDefinitionSymbol: string;
33+
readonly treeRankSymbol: string;
3334
readonly fieldPartSeparator: string;
3435
};
3536

@@ -60,8 +61,10 @@ const schemaBase: Writable<Schema> = {
6061

6162
// Prefix for -to-many indexes
6263
referenceSymbol: '#',
64+
// Prefix for Tree Definitions
65+
treeDefinitionSymbol: '%',
6366
// Prefix for tree ranks
64-
treeSymbol: '$',
67+
treeRankSymbol: '$',
6568
// Separator for partial fields (date parts in Query Builder)
6669
fieldPartSeparator: '-',
6770
};

specifyweb/frontend/js_src/lib/components/InitialContext/treeRanks.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,14 @@ export function getTreeDefinitions<TREE_NAME extends AnyTree['tableName']>(
118118
tableName
119119
);
120120

121-
return typeof treeDefinitionId === 'number'
122-
? specificTreeDefinitions.filter(
123-
({ definition }) => definition.id === treeDefinitionId
124-
)
125-
: specificTreeDefinitions;
121+
if (typeof treeDefinitionId === 'number') {
122+
const resolvedDefinition = specificTreeDefinitions.find(
123+
({ definition }) => definition.id === treeDefinitionId
124+
);
125+
return resolvedDefinition === undefined
126+
? specificTreeDefinitions
127+
: [resolvedDefinition];
128+
} else return specificTreeDefinitions;
126129
}
127130

128131
export function getTreeDefinitionItems<TREE_NAME extends AnyTree['tableName']>(

specifyweb/frontend/js_src/lib/components/Syncer/__tests__/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@ test('Editing Data Object Formatter', () => {
9494
</switch>
9595
</format>
9696
<aggregators>
97-
<aggregator name='AccessionAgent' title='AccessionAgent' class='edu.ku.brc.specify.datamodel.Agent' default='true' separator='' ending='' format='AccessionAgent'/>
98-
<aggregator name='AccessionAgent' title='AccessionAgent' class='edu.ku.brc.specify.datamodel.AccessionAgent' default='true' separator='' ending='' format='AccessionAgent'/>
97+
<aggregator name='AccessionAgent' title='AccessionAgent' class='edu.ku.brc.specify.datamodel.Agent' default='true' separator='; ' ending='' format='AccessionAgent'/>
98+
<aggregator name='AccessionAgent' title='AccessionAgent' class='edu.ku.brc.specify.datamodel.AccessionAgent' default='true' separator='; ' ending='' format='AccessionAgent'/>
9999
</aggregators>
100100
</formatters>"
101101
`);

specifyweb/frontend/js_src/lib/components/WbPlanView/LineComponents.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ export type HtmlGeneratorFieldData = {
3737
readonly isDefault?: boolean;
3838
readonly isRelationship?: boolean;
3939
readonly tableName?: keyof Tables;
40-
readonly tableTreeDefName?: string;
4140
};
4241

4342
type MappingLineBaseProps = {

specifyweb/frontend/js_src/lib/components/WbPlanView/__tests__/mappingHelpers.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,16 @@ theories(valueIsToManyIndex, [
1616
[[`${schema.referenceSymbol}2`], true],
1717
[[`${schema.referenceSymbol}999`], true],
1818
[['collectionobject'], false],
19-
[[`${schema.treeSymbol}Kingdom`], false],
19+
[[`${schema.treeRankSymbol}Kingdom`], false],
2020
]);
2121

2222
theories(valueIsTreeRank, [
2323
[[`${schema.referenceSymbol}1`], false],
2424
[[`${schema.referenceSymbol}2`], false],
2525
[[`${schema.referenceSymbol}999`], false],
2626
[['collectionobject'], false],
27-
[[`${schema.treeSymbol}Kingdom`], true],
28-
[[`${schema.treeSymbol}County`], true],
27+
[[`${schema.treeRankSymbol}Kingdom`], true],
28+
[[`${schema.treeRankSymbol}County`], true],
2929
]);
3030

3131
theories(getNumberFromToManyIndex, [
@@ -36,8 +36,8 @@ theories(getNumberFromToManyIndex, [
3636
]);
3737

3838
theories(getNameFromTreeRankName, [
39-
[[`${schema.treeSymbol}Kingdom`], 'Kingdom'],
40-
[[`${schema.treeSymbol}County`], 'County'],
39+
[[`${schema.treeRankSymbol}Kingdom`], 'Kingdom'],
40+
[[`${schema.treeRankSymbol}County`], 'County'],
4141
]);
4242

4343
theories(findDuplicateMappings, [

specifyweb/frontend/js_src/lib/components/WbPlanView/__tests__/navigator.test.ts

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -314,70 +314,60 @@ theories(getMappingLineData, [
314314
isRelationship: true,
315315
isDefault: false,
316316
tableName: 'Taxon',
317-
tableTreeDefName: 'Taxonomy',
318317
},
319318
$Phylum: {
320319
optionLabel: 'Phylum',
321320
isRelationship: true,
322321
isDefault: false,
323322
tableName: 'Taxon',
324-
tableTreeDefName: 'Taxonomy',
325323
},
326324
$Class: {
327325
optionLabel: 'Class',
328326
isRelationship: true,
329327
isDefault: false,
330328
tableName: 'Taxon',
331-
tableTreeDefName: 'Taxonomy',
332329
},
333330
$Order: {
334331
optionLabel: 'Order',
335332
isRelationship: true,
336333
isDefault: false,
337334
tableName: 'Taxon',
338-
tableTreeDefName: 'Taxonomy',
339335
},
340336
$Family: {
341337
optionLabel: 'Family',
342338
isRelationship: true,
343339
isDefault: true,
344340
tableName: 'Taxon',
345-
tableTreeDefName: 'Taxonomy',
346341
},
347342
$Subfamily: {
348343
optionLabel: 'Subfamily',
349344
isRelationship: true,
350345
isDefault: false,
351346
tableName: 'Taxon',
352-
tableTreeDefName: 'Taxonomy',
353347
},
354348
$Genus: {
355349
optionLabel: 'Genus',
356350
isRelationship: true,
357351
isDefault: false,
358352
tableName: 'Taxon',
359-
tableTreeDefName: 'Taxonomy',
360353
},
361354
$Subgenus: {
362355
optionLabel: 'Subgenus',
363356
isRelationship: true,
364357
isDefault: false,
365358
tableName: 'Taxon',
366-
tableTreeDefName: 'Taxonomy',
367359
},
368360
$Species: {
369361
optionLabel: 'Species',
370362
isRelationship: true,
371363
isDefault: false,
372364
tableName: 'Taxon',
373-
tableTreeDefName: 'Taxonomy',
374365
},
375366
$Subspecies: {
376367
optionLabel: 'Subspecies',
377368
isRelationship: true,
378369
isDefault: false,
379370
tableName: 'Taxon',
380-
tableTreeDefName: 'Taxonomy',
381371
},
382372
},
383373
tableName: 'Taxon',
@@ -870,70 +860,60 @@ theories(getMappingLineData, [
870860
isRelationship: true,
871861
optionLabel: 'Class',
872862
tableName: 'Taxon',
873-
tableTreeDefName: 'Taxonomy',
874863
},
875864
$Family: {
876865
isDefault: true,
877866
isRelationship: true,
878867
optionLabel: 'Family',
879868
tableName: 'Taxon',
880-
tableTreeDefName: 'Taxonomy',
881869
},
882870
$Genus: {
883871
isDefault: false,
884872
isRelationship: true,
885873
optionLabel: 'Genus',
886874
tableName: 'Taxon',
887-
tableTreeDefName: 'Taxonomy',
888875
},
889876
$Kingdom: {
890877
isDefault: false,
891878
isRelationship: true,
892879
optionLabel: 'Kingdom',
893880
tableName: 'Taxon',
894-
tableTreeDefName: 'Taxonomy',
895881
},
896882
$Order: {
897883
isDefault: false,
898884
isRelationship: true,
899885
optionLabel: 'Order',
900886
tableName: 'Taxon',
901-
tableTreeDefName: 'Taxonomy',
902887
},
903888
$Phylum: {
904889
isDefault: false,
905890
isRelationship: true,
906891
optionLabel: 'Phylum',
907892
tableName: 'Taxon',
908-
tableTreeDefName: 'Taxonomy',
909893
},
910894
$Species: {
911895
isDefault: false,
912896
isRelationship: true,
913897
optionLabel: 'Species',
914898
tableName: 'Taxon',
915-
tableTreeDefName: 'Taxonomy',
916899
},
917900
$Subfamily: {
918901
isDefault: false,
919902
isRelationship: true,
920903
optionLabel: 'Subfamily',
921904
tableName: 'Taxon',
922-
tableTreeDefName: 'Taxonomy',
923905
},
924906
$Subgenus: {
925907
isDefault: false,
926908
isRelationship: true,
927909
optionLabel: 'Subgenus',
928910
tableName: 'Taxon',
929-
tableTreeDefName: 'Taxonomy',
930911
},
931912
$Subspecies: {
932913
isDefault: false,
933914
isRelationship: true,
934915
optionLabel: 'Subspecies',
935916
tableName: 'Taxon',
936-
tableTreeDefName: 'Taxonomy',
937917
},
938918
},
939919
selectLabel: localized('Taxon'),

specifyweb/frontend/js_src/lib/components/WbPlanView/mappingHelpers.ts

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,20 @@ export const valueIsToManyIndex = (value: string | undefined): boolean =>
2929
value?.slice(0, schema.referenceSymbol.length) === schema.referenceSymbol ||
3030
false;
3131

32+
/**
33+
* Returns whether a value is any special tree-related meta information
34+
* (e.g. tree defintiion, tree rank)
35+
*/
36+
export const valueIsTreeMeta = (value: string | undefined): boolean =>
37+
valueIsTreeDefinition(value) || valueIsTreeRank(value);
38+
39+
/** Returns whether a value is a tree definition name (e.x %Taxonomy) */
40+
export const valueIsTreeDefinition = (value: string | undefined): boolean =>
41+
value?.startsWith(schema.treeDefinitionSymbol) ?? false;
42+
3243
/** Returns whether a value is a tree rank name (e.x $Kingdom, $Order) */
3344
export const valueIsTreeRank = (value: string | undefined): boolean =>
34-
value?.startsWith(schema.treeSymbol) || false;
45+
value?.startsWith(schema.treeRankSymbol) ?? false;
3546

3647
/**
3748
* Returns index from a formatted -to-many index value (e.x #1 => 1)
@@ -40,6 +51,14 @@ export const valueIsTreeRank = (value: string | undefined): boolean =>
4051
export const getNumberFromToManyIndex = (value: string): number =>
4152
Number(value.slice(schema.referenceSymbol.length));
4253

54+
/**
55+
* Returns tree definition name from a complete tree definition name
56+
* (e.x %Taxonomy => Taxonomy)
57+
* Opposite of formatTreeDefinition
58+
*/
59+
export const getNameFromTreeDefinitionName = (value: string): string =>
60+
value.slice(schema.treeDefinitionSymbol.length);
61+
4362
/*
4463
* BUG: in places where output of this function is displayed to the user,
4564
* make sure to use tree rank title instead of name
@@ -51,7 +70,7 @@ export const getNumberFromToManyIndex = (value: string): number =>
5170
*
5271
*/
5372
export const getNameFromTreeRankName = (value: string): string =>
54-
value.slice(schema.treeSymbol.length);
73+
value.slice(schema.treeRankSymbol.length);
5574

5675
/**
5776
* Returns a formatted -to-many index from an index (e.x 1 => #1)
@@ -61,7 +80,7 @@ export const formatToManyIndex = (index: number): string =>
6180
`${schema.referenceSymbol}${index}`;
6281

6382
// Meta fields
64-
export const anyTreeRank = `${schema.fieldPartSeparator}any`;
83+
export const anyTreeRank = `${schema.fieldPartSeparator}any` as const;
6584
export const formattedEntry = `${schema.fieldPartSeparator}formatted`;
6685

6786
/**
@@ -75,14 +94,22 @@ export const formattedEntry = `${schema.fieldPartSeparator}formatted`;
7594
*/
7695
export const emptyMapping = '0';
7796

97+
/**
98+
* Returns a complete tree definition name from a tree definition name
99+
* (e.x Taxononomy => %Taxonomy)
100+
* Opposite of getNameFromTreeDefinitionName
101+
*/
102+
export const formatTreeDefinition = (definitionName: string): string =>
103+
`${schema.treeDefinitionSymbol}${definitionName}`;
104+
78105
/**
79106
* Returns a complete tree rank name from a tree rank name
80107
* (e.x Kingdom => $Kingdom)
81108
* Opposite of getNameFromTreeRankName
82109
*
83110
*/
84111
export const formatTreeRank = (rankName: string): string =>
85-
`${schema.treeSymbol}${rankName}`;
112+
`${schema.treeRankSymbol}${rankName}`;
86113

87114
// Match fields names like startDate_fullDate, but not _formatted
88115
export const valueIsPartialField = (value: string): boolean =>
@@ -160,7 +187,7 @@ export const getGenericMappingPath = (mappingPath: MappingPath): MappingPath =>
160187
mappingPath.filter(
161188
(mappingPathPart) =>
162189
!valueIsToManyIndex(mappingPathPart) &&
163-
!valueIsTreeRank(mappingPathPart) &&
190+
!valueIsTreeMeta(mappingPathPart) &&
164191
mappingPathPart !== formattedEntry &&
165192
mappingPathPart !== emptyMapping
166193
);

specifyweb/frontend/js_src/lib/components/WbPlanView/mappingPreview.ts

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
parsePartialField,
2323
valueIsPartialField,
2424
valueIsToManyIndex,
25+
valueIsTreeDefinition,
2526
valueIsTreeRank,
2627
} from './mappingHelpers';
2728
import { getMappingLineData } from './navigator';
@@ -120,16 +121,19 @@ export function generateMappingPathPreview(
120121
: 1;
121122
const toManyIndexFormatted = toManyIndexNumber > 1 ? toManyIndex : undefined;
122123

123-
const [databaseFieldName, databaseTableOrRankName, databaseParentTableName] =
124-
mappingPathSubset([baseTableName, ...mappingPath]);
124+
const [
125+
databaseFieldName,
126+
databaseTableOrRankName,
127+
databaseParentTableOrTreeName,
128+
] = mappingPathSubset([baseTableName, ...mappingPath]);
125129

126130
// Attributes parts of filedLables to each variable or creates one if empty
127131
const [
128132
fieldName = camelToHuman(databaseFieldName),
129133
tableOrRankName = camelToHuman(
130134
getNameFromTreeRankName(databaseTableOrRankName)
131135
),
132-
parentTableName = camelToHuman(databaseParentTableName),
136+
parentTableOrTreeName = camelToHuman(databaseParentTableOrTreeName),
133137
] = mappingPathSubset(fieldLabels);
134138

135139
const isAnyRank = databaseTableOrRankName === formatTreeRank(anyTreeRank);
@@ -163,16 +167,20 @@ export function generateMappingPathPreview(
163167
const tableNameFormatted =
164168
tablesToHide.has(databaseTableOrRankName) &&
165169
databaseFieldName !== formattedEntry
166-
? [parentTableName || tableNameNonEmpty]
170+
? [parentTableOrTreeName || tableNameNonEmpty]
167171
: genericTables.has(databaseTableOrRankName)
168-
? [parentTableName, tableNameNonEmpty]
172+
? [parentTableOrTreeName, tableNameNonEmpty]
169173
: [tableNameNonEmpty];
170174

171175
return filterArray([
172176
...(valueIsTreeRank(databaseTableOrRankName)
173-
? [isAnyRank ? parentTableName : tableOrRankName]
177+
? [isAnyRank ? parentTableOrTreeName : tableOrRankName]
174178
: tableNameFormatted),
175179
fieldNameFormatted,
180+
...(valueIsTreeRank(databaseTableOrRankName) &&
181+
valueIsTreeDefinition(databaseParentTableOrTreeName)
182+
? [parentTableOrTreeName]
183+
: []),
176184
toManyIndexFormatted,
177185
])
178186
.filter(Boolean)

0 commit comments

Comments
 (0)