-
Notifications
You must be signed in to change notification settings - Fork 36
/
treeRanks.ts
140 lines (130 loc) · 4.79 KB
/
treeRanks.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* Fetch tree definitions (and tree ranks) for all fields accessible in a
* given discipline.
*/
import { f } from '../../utils/functools';
import type { RA } from '../../utils/types';
import { defined } from '../../utils/types';
import {
caseInsensitiveHash,
sortFunction,
unCapitalize,
} from '../../utils/utils';
import { fetchRelated } from '../DataModel/collection';
import { getDomainResource } from '../DataModel/domain';
import { serializeResource } from '../DataModel/helpers';
import type {
AnySchema,
AnyTree,
SerializedResource,
} from '../DataModel/helperTypes';
import type { SpecifyResource } from '../DataModel/legacyTypes';
import { schema } from '../DataModel/schema';
import { fetchContext as fetchDomain } from '../DataModel/schemaBase';
import type { Tables } from '../DataModel/types';
let treeDefinitions: {
readonly [TREE_NAME in AnyTree['tableName']]: {
readonly definition: SpecifyResource<Tables[`${TREE_NAME}TreeDef`]>;
readonly ranks: RA<SerializedResource<Tables[`${TREE_NAME}TreeDefItem`]>>;
};
} = undefined!;
/*
* FEATURE: allow reordering trees
* See https://github.com/specify/specify7/issues/2121#issuecomment-1432158152
*/
const commonTrees = ['Geography', 'Storage', 'Taxon'] as const;
const treesForPaleo = ['GeologicTimePeriod', 'LithoStrat'] as const;
export const allTrees = [...commonTrees, ...treesForPaleo] as const;
const paleoDiscs = new Set(['paleobotany', 'invertpaleo', 'vertpaleo']);
/*
* Until discipline information is loaded, assume all trees are appropriate in
* this discipline
*/
let disciplineTrees: RA<AnyTree['tableName']> = allTrees;
export const getDisciplineTrees = (): typeof disciplineTrees => disciplineTrees;
export const isTreeModel = (
tableName: keyof Tables
): tableName is AnyTree['tableName'] => f.includes(allTrees, tableName);
export const isTreeResource = (
resource: SpecifyResource<AnySchema>
): resource is SpecifyResource<AnyTree> =>
f.includes(allTrees, resource.specifyModel.name);
export const treeRanksPromise = Promise.all([
// Dynamic imports are used to prevent circular dependencies
import('../Permissions/helpers'),
import('../Permissions').then(async ({ fetchContext }) => fetchContext),
import('../DataModel/schema').then(async ({ fetchContext }) => fetchContext),
fetchDomain,
])
.then(async ([{ hasTreeAccess, hasTablePermission }]) =>
hasTablePermission('Discipline', 'read')
? getDomainResource('discipline')
?.fetch()
.then((discipline) => {
if (!f.has(paleoDiscs, discipline?.get('type')))
disciplineTrees = commonTrees;
})
.then(async () =>
Promise.all(
disciplineTrees
.filter((treeName) => hasTreeAccess(treeName, 'read'))
.map(async (treeName) =>
getDomainResource(getTreeScope(treeName) as 'discipline')
?.rgetPromise(
`${unCapitalize(treeName) as 'geography'}TreeDef`
)
.then(async (treeDefinition) => ({
definition: treeDefinition,
ranks: await fetchRelated(
serializeResource(treeDefinition),
'treeDefItems',
0
).then(({ records }) =>
Array.from(records).sort(
sortFunction(({ rankId }) => rankId)
)
),
}))
.then((ranks) => [treeName, ranks] as const)
)
)
)
: []
)
.then((ranks) => {
// @ts-expect-error
treeDefinitions = Object.fromEntries(ranks.filter(Boolean));
return treeDefinitions;
});
function getTreeScope(
treeName: AnyTree['tableName']
): keyof typeof schema['domainLevelIds'] | undefined {
const treeRelationships = new Set(
schema.models[`${treeName}TreeDef`].relationships.map(({ relatedModel }) =>
relatedModel.name.toLowerCase()
)
);
return Object.keys(schema.domainLevelIds).find((domainTable) =>
treeRelationships.has(domainTable)
);
}
export function getTreeDefinitionItems<TREE_NAME extends AnyTree['tableName']>(
tableName: TREE_NAME,
includeRoot: boolean
): typeof treeDefinitions[TREE_NAME]['ranks'] | undefined {
const definition = caseInsensitiveHash(treeDefinitions, tableName);
return definition?.ranks.slice(includeRoot ? 0 : 1);
}
export const strictGetTreeDefinitionItems = <
TREE_NAME extends AnyTree['tableName']
>(
tableName: TREE_NAME,
includeRoot: boolean
): typeof treeDefinitions[TREE_NAME]['ranks'] =>
defined(
getTreeDefinitionItems(tableName, includeRoot),
`Unable to get tree ranks for a ${tableName} table`
);
export const exportsForTests = {
getTreeScope,
};