Skip to content

Commit

Permalink
Merge pull request #5091 from specify/issue-4980
Browse files Browse the repository at this point in the history
Add a feature to choose type of taxon tree in WB
  • Loading branch information
acwhite211 authored Sep 11, 2024
2 parents f58f0d4 + eb3c820 commit dfa30a6
Show file tree
Hide file tree
Showing 22 changed files with 875 additions and 174 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ test('domain data is fetched and parsed correctly', async () =>
],
paleoContextChildTable: 'collectionobject',
referenceSymbol: '#',
treeSymbol: '$',
treeRankSymbol: '$',
treeDefinitionSymbol: '%',
}));
7 changes: 5 additions & 2 deletions specifyweb/frontend/js_src/lib/components/DataModel/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ type Schema = {
'Institution'
];
readonly referenceSymbol: string;
readonly treeSymbol: string;
readonly treeDefinitionSymbol: string;
readonly treeRankSymbol: string;
readonly fieldPartSeparator: string;
};

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

// Prefix for -to-many indexes
referenceSymbol: '#',
// Prefix for Tree Definitions
treeDefinitionSymbol: '%',
// Prefix for tree ranks
treeSymbol: '$',
treeRankSymbol: '$',
// Separator for partial fields (date parts in Query Builder)
fieldPartSeparator: '-',
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,14 @@ export function getTreeDefinitions<TREE_NAME extends AnyTree['tableName']>(
tableName
);

return typeof treeDefinitionId === 'number'
? specificTreeDefinitions.filter(
({ definition }) => definition.id === treeDefinitionId
)
: specificTreeDefinitions;
if (typeof treeDefinitionId === 'number') {
const resolvedDefinition = specificTreeDefinitions.find(
({ definition }) => definition.id === treeDefinitionId
);
return resolvedDefinition === undefined
? specificTreeDefinitions
: [resolvedDefinition];
} else return specificTreeDefinitions;
}

export function getTreeDefinitionItems<TREE_NAME extends AnyTree['tableName']>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ test('Editing Data Object Formatter', () => {
</switch>
</format>
<aggregators>
<aggregator name='AccessionAgent' title='AccessionAgent' class='edu.ku.brc.specify.datamodel.Agent' default='true' separator='' ending='' format='AccessionAgent'/>
<aggregator name='AccessionAgent' title='AccessionAgent' class='edu.ku.brc.specify.datamodel.AccessionAgent' default='true' separator='' ending='' format='AccessionAgent'/>
<aggregator name='AccessionAgent' title='AccessionAgent' class='edu.ku.brc.specify.datamodel.Agent' default='true' separator='; ' ending='' format='AccessionAgent'/>
<aggregator name='AccessionAgent' title='AccessionAgent' class='edu.ku.brc.specify.datamodel.AccessionAgent' default='true' separator='; ' ending='' format='AccessionAgent'/>
</aggregators>
</formatters>"
`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export type HtmlGeneratorFieldData = {
readonly isDefault?: boolean;
readonly isRelationship?: boolean;
readonly tableName?: keyof Tables;
readonly tableTreeDefName?: string;
};

type MappingLineBaseProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ theories(valueIsToManyIndex, [
[[`${schema.referenceSymbol}2`], true],
[[`${schema.referenceSymbol}999`], true],
[['collectionobject'], false],
[[`${schema.treeSymbol}Kingdom`], false],
[[`${schema.treeRankSymbol}Kingdom`], false],
]);

theories(valueIsTreeRank, [
[[`${schema.referenceSymbol}1`], false],
[[`${schema.referenceSymbol}2`], false],
[[`${schema.referenceSymbol}999`], false],
[['collectionobject'], false],
[[`${schema.treeSymbol}Kingdom`], true],
[[`${schema.treeSymbol}County`], true],
[[`${schema.treeRankSymbol}Kingdom`], true],
[[`${schema.treeRankSymbol}County`], true],
]);

theories(getNumberFromToManyIndex, [
Expand All @@ -36,8 +36,8 @@ theories(getNumberFromToManyIndex, [
]);

theories(getNameFromTreeRankName, [
[[`${schema.treeSymbol}Kingdom`], 'Kingdom'],
[[`${schema.treeSymbol}County`], 'County'],
[[`${schema.treeRankSymbol}Kingdom`], 'Kingdom'],
[[`${schema.treeRankSymbol}County`], 'County'],
]);

theories(findDuplicateMappings, [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,70 +314,60 @@ theories(getMappingLineData, [
isRelationship: true,
isDefault: false,
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Phylum: {
optionLabel: 'Phylum',
isRelationship: true,
isDefault: false,
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Class: {
optionLabel: 'Class',
isRelationship: true,
isDefault: false,
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Order: {
optionLabel: 'Order',
isRelationship: true,
isDefault: false,
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Family: {
optionLabel: 'Family',
isRelationship: true,
isDefault: true,
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Subfamily: {
optionLabel: 'Subfamily',
isRelationship: true,
isDefault: false,
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Genus: {
optionLabel: 'Genus',
isRelationship: true,
isDefault: false,
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Subgenus: {
optionLabel: 'Subgenus',
isRelationship: true,
isDefault: false,
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Species: {
optionLabel: 'Species',
isRelationship: true,
isDefault: false,
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Subspecies: {
optionLabel: 'Subspecies',
isRelationship: true,
isDefault: false,
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
},
tableName: 'Taxon',
Expand Down Expand Up @@ -870,70 +860,60 @@ theories(getMappingLineData, [
isRelationship: true,
optionLabel: 'Class',
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Family: {
isDefault: true,
isRelationship: true,
optionLabel: 'Family',
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Genus: {
isDefault: false,
isRelationship: true,
optionLabel: 'Genus',
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Kingdom: {
isDefault: false,
isRelationship: true,
optionLabel: 'Kingdom',
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Order: {
isDefault: false,
isRelationship: true,
optionLabel: 'Order',
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Phylum: {
isDefault: false,
isRelationship: true,
optionLabel: 'Phylum',
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Species: {
isDefault: false,
isRelationship: true,
optionLabel: 'Species',
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Subfamily: {
isDefault: false,
isRelationship: true,
optionLabel: 'Subfamily',
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Subgenus: {
isDefault: false,
isRelationship: true,
optionLabel: 'Subgenus',
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
$Subspecies: {
isDefault: false,
isRelationship: true,
optionLabel: 'Subspecies',
tableName: 'Taxon',
tableTreeDefName: 'Taxonomy',
},
},
selectLabel: localized('Taxon'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,20 @@ export const valueIsToManyIndex = (value: string | undefined): boolean =>
value?.slice(0, schema.referenceSymbol.length) === schema.referenceSymbol ||
false;

/**
* Returns whether a value is any special tree-related meta information
* (e.g. tree defintiion, tree rank)
*/
export const valueIsTreeMeta = (value: string | undefined): boolean =>
valueIsTreeDefinition(value) || valueIsTreeRank(value);

/** Returns whether a value is a tree definition name (e.x %Taxonomy) */
export const valueIsTreeDefinition = (value: string | undefined): boolean =>
value?.startsWith(schema.treeDefinitionSymbol) ?? false;

/** Returns whether a value is a tree rank name (e.x $Kingdom, $Order) */
export const valueIsTreeRank = (value: string | undefined): boolean =>
value?.startsWith(schema.treeSymbol) || false;
value?.startsWith(schema.treeRankSymbol) ?? false;

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

/**
* Returns tree definition name from a complete tree definition name
* (e.x %Taxonomy => Taxonomy)
* Opposite of formatTreeDefinition
*/
export const getNameFromTreeDefinitionName = (value: string): string =>
value.slice(schema.treeDefinitionSymbol.length);

/*
* BUG: in places where output of this function is displayed to the user,
* make sure to use tree rank title instead of name
Expand All @@ -51,7 +70,7 @@ export const getNumberFromToManyIndex = (value: string): number =>
*
*/
export const getNameFromTreeRankName = (value: string): string =>
value.slice(schema.treeSymbol.length);
value.slice(schema.treeRankSymbol.length);

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

// Meta fields
export const anyTreeRank = `${schema.fieldPartSeparator}any`;
export const anyTreeRank = `${schema.fieldPartSeparator}any` as const;
export const formattedEntry = `${schema.fieldPartSeparator}formatted`;

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

/**
* Returns a complete tree definition name from a tree definition name
* (e.x Taxononomy => %Taxonomy)
* Opposite of getNameFromTreeDefinitionName
*/
export const formatTreeDefinition = (definitionName: string): string =>
`${schema.treeDefinitionSymbol}${definitionName}`;

/**
* Returns a complete tree rank name from a tree rank name
* (e.x Kingdom => $Kingdom)
* Opposite of getNameFromTreeRankName
*
*/
export const formatTreeRank = (rankName: string): string =>
`${schema.treeSymbol}${rankName}`;
`${schema.treeRankSymbol}${rankName}`;

// Match fields names like startDate_fullDate, but not _formatted
export const valueIsPartialField = (value: string): boolean =>
Expand Down Expand Up @@ -160,7 +187,7 @@ export const getGenericMappingPath = (mappingPath: MappingPath): MappingPath =>
mappingPath.filter(
(mappingPathPart) =>
!valueIsToManyIndex(mappingPathPart) &&
!valueIsTreeRank(mappingPathPart) &&
!valueIsTreeMeta(mappingPathPart) &&
mappingPathPart !== formattedEntry &&
mappingPathPart !== emptyMapping
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
parsePartialField,
valueIsPartialField,
valueIsToManyIndex,
valueIsTreeDefinition,
valueIsTreeRank,
} from './mappingHelpers';
import { getMappingLineData } from './navigator';
Expand Down Expand Up @@ -120,16 +121,19 @@ export function generateMappingPathPreview(
: 1;
const toManyIndexFormatted = toManyIndexNumber > 1 ? toManyIndex : undefined;

const [databaseFieldName, databaseTableOrRankName, databaseParentTableName] =
mappingPathSubset([baseTableName, ...mappingPath]);
const [
databaseFieldName,
databaseTableOrRankName,
databaseParentTableOrTreeName,
] = mappingPathSubset([baseTableName, ...mappingPath]);

// Attributes parts of filedLables to each variable or creates one if empty
const [
fieldName = camelToHuman(databaseFieldName),
tableOrRankName = camelToHuman(
getNameFromTreeRankName(databaseTableOrRankName)
),
parentTableName = camelToHuman(databaseParentTableName),
parentTableOrTreeName = camelToHuman(databaseParentTableOrTreeName),
] = mappingPathSubset(fieldLabels);

const isAnyRank = databaseTableOrRankName === formatTreeRank(anyTreeRank);
Expand Down Expand Up @@ -163,16 +167,20 @@ export function generateMappingPathPreview(
const tableNameFormatted =
tablesToHide.has(databaseTableOrRankName) &&
databaseFieldName !== formattedEntry
? [parentTableName || tableNameNonEmpty]
? [parentTableOrTreeName || tableNameNonEmpty]
: genericTables.has(databaseTableOrRankName)
? [parentTableName, tableNameNonEmpty]
? [parentTableOrTreeName, tableNameNonEmpty]
: [tableNameNonEmpty];

return filterArray([
...(valueIsTreeRank(databaseTableOrRankName)
? [isAnyRank ? parentTableName : tableOrRankName]
? [isAnyRank ? parentTableOrTreeName : tableOrRankName]
: tableNameFormatted),
fieldNameFormatted,
...(valueIsTreeRank(databaseTableOrRankName) &&
valueIsTreeDefinition(databaseParentTableOrTreeName)
? [parentTableOrTreeName]
: []),
toManyIndexFormatted,
])
.filter(Boolean)
Expand Down
Loading

0 comments on commit dfa30a6

Please sign in to comment.