sanity-plugin-taxonomy-manager 4.7.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +13 -108
- package/{lib → dist}/index.d.ts +66 -48
- package/dist/index.js +106 -0
- package/dist/index.js.map +1 -0
- package/package.json +40 -46
- package/src/components/Children.tsx +4 -3
- package/src/components/Concepts.tsx +2 -2
- package/src/components/Hierarchy.test.tsx +92 -0
- package/src/components/Hierarchy.tsx +65 -30
- package/src/components/TopConcepts.tsx +2 -2
- package/src/components/TreeStructure.tsx +3 -3
- package/src/components/inputs/ArrayHierarchyInput.test.tsx +102 -0
- package/src/components/inputs/ArrayHierarchyInput.tsx +23 -24
- package/src/components/inputs/Identifier.tsx +1 -1
- package/src/components/inputs/InputHierarchy.test.tsx +38 -0
- package/src/components/inputs/InputHierarchy.tsx +20 -68
- package/src/components/inputs/ReferenceHierarchyInput.test.tsx +109 -0
- package/src/components/inputs/ReferenceHierarchyInput.tsx +24 -25
- package/src/components/interactions/ConceptDetailLink.tsx +0 -1
- package/src/components/interactions/ConceptEditAction.tsx +1 -1
- package/src/components/interactions/ConceptSelectLink.test.tsx +49 -0
- package/src/components/interactions/ConceptSelectLink.tsx +10 -6
- package/src/context.ts +34 -14
- package/src/core/__snapshots__/queries.test.ts.snap +386 -0
- package/src/core/__snapshots__/semanticRecommendations.test.ts.snap +10 -0
- package/src/core/createId.test.ts +35 -0
- package/src/core/filters.test.ts +64 -0
- package/src/core/filters.ts +68 -0
- package/src/core/ids.test.ts +68 -0
- package/src/core/ids.ts +61 -0
- package/src/core/mutations.test.ts +114 -0
- package/src/core/mutations.ts +105 -0
- package/src/core/ports.ts +59 -0
- package/src/core/queries.test.ts +50 -0
- package/src/{queries.ts → core/queries.ts} +7 -6
- package/src/core/semanticRecommendations.test.ts +160 -0
- package/src/core/semanticRecommendations.ts +127 -0
- package/src/core/tree/annotateRecommendations.test.ts +43 -0
- package/src/core/tree/annotateRecommendations.ts +30 -0
- package/src/core/tree/pruneConcepts.test.ts +77 -0
- package/src/core/tree/pruneConcepts.ts +72 -0
- package/src/core/validation.test.ts +42 -0
- package/src/core/validation.ts +42 -0
- package/src/helpers/baseIriField.ts +22 -0
- package/src/helpers/branchFilter.test.ts +48 -0
- package/src/helpers/branchFilter.ts +17 -26
- package/src/helpers/schemeFilter.test.ts +51 -0
- package/src/helpers/schemeFilter.ts +11 -23
- package/src/hooks/index.ts +1 -1
- package/src/hooks/useCreateConcept.tsx +33 -110
- package/src/hooks/useRemoveConcept.tsx +24 -36
- package/src/hooks/useSemanticRecommendations.tsx +75 -0
- package/src/index.test.ts +31 -0
- package/src/index.ts +2 -6
- package/src/seams/StudioDataAdapter.ts +81 -0
- package/src/seams/TaxonomyPortContext.tsx +33 -0
- package/src/skosConcept.tsx +23 -234
- package/src/skosConceptScheme.tsx +2 -4
- package/src/structure.tsx +60 -0
- package/src/test/FakeDataPort.test.ts +62 -0
- package/src/test/FakeDataPort.ts +69 -0
- package/src/test/inputHarness.tsx +71 -0
- package/src/test/renderWithUi.test.tsx +14 -0
- package/src/test/renderWithUi.tsx +24 -0
- package/src/test-setup.ts +35 -0
- package/src/types.tsx +30 -11
- package/src/views/ConceptUseView.tsx +4 -10
- package/lib/index.esm.d.mts +0 -492
- package/lib/index.esm.esm.js +0 -182
- package/lib/index.esm.esm.js.map +0 -1
- package/lib/index.esm.mjs +0 -182
- package/lib/index.esm.mjs.map +0 -1
- package/lib/index.js +0 -182
- package/lib/index.js.map +0 -1
- package/sanity.json +0 -8
- package/src/config.ts +0 -16
- package/src/helpers/baseIriField.tsx +0 -42
- package/src/hooks/useEmbeddingsRecs.tsx +0 -75
- package/src/skosConcept.module.css +0 -10
- package/src/structure.ts +0 -40
- package/v2-incompatible.js +0 -11
- /package/src/{helpers → core}/createId.ts +0 -0
package/src/context.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {createContext} from 'react'
|
|
2
2
|
|
|
3
|
-
import type {ConceptSchemeDocument} from './types'
|
|
3
|
+
import type {ConceptSchemeDocument, Options} from './types'
|
|
4
4
|
|
|
5
5
|
type TreeContextType = {
|
|
6
6
|
globalVisibility?: {treeId: string; treeVisibility: 'open' | 'closed'}
|
|
@@ -8,18 +8,38 @@ type TreeContextType = {
|
|
|
8
8
|
setEditControls?: (value: boolean) => void
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
export type ReleaseContextType = {
|
|
12
|
-
isPublished?: boolean
|
|
13
|
-
isInRelease: boolean
|
|
14
|
-
releaseName?: string
|
|
15
|
-
documentId: string
|
|
16
|
-
versionId?: string
|
|
17
|
-
}
|
|
18
|
-
|
|
19
11
|
export const SchemeContext = createContext<ConceptSchemeDocument | null>(null)
|
|
20
12
|
export const TreeContext = createContext<TreeContextType>({editControls: false})
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The active perspective name from `usePerspective().selectedPerspectiveName`
|
|
16
|
+
* (provided by TreeView): `undefined` for the default drafts view, otherwise a
|
|
17
|
+
* release name or `'published'`. Drives perspective-aware queries and the gating
|
|
18
|
+
* of edit controls.
|
|
19
|
+
*/
|
|
20
|
+
export const ReleaseContext = createContext<string | undefined>(undefined)
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Plugin config the render tree needs at runtime. Provided around the structure
|
|
24
|
+
* Tree View (see `createDefaultDocumentNode`); replaces the old `config.ts`
|
|
25
|
+
* `getPluginConfig` singleton. Defaults to empty so input-component trees (which
|
|
26
|
+
* never create concepts) render correctly without a provider.
|
|
27
|
+
*/
|
|
28
|
+
export type TaxonomyConfig = {ident?: Options['ident']}
|
|
29
|
+
export const TaxonomyConfigContext = createContext<TaxonomyConfig>({})
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Connects the deep remove actions to Hierarchy's optimistic-removal layer:
|
|
33
|
+
* removing a concept marks its id so the tree prunes it instantly, ahead of the
|
|
34
|
+
* live listener catching up (see Hierarchy + core/tree/pruneConcepts). Defaults
|
|
35
|
+
* to no-ops, so trees rendered without the provider — the input components,
|
|
36
|
+
* which never remove — behave normally.
|
|
37
|
+
*/
|
|
38
|
+
export type OptimisticTreeContextType = {
|
|
39
|
+
markRemoved: (id: string) => void
|
|
40
|
+
unmarkRemoved: (id: string) => void
|
|
41
|
+
}
|
|
42
|
+
export const OptimisticTreeContext = createContext<OptimisticTreeContextType>({
|
|
43
|
+
markRemoved: () => undefined,
|
|
44
|
+
unmarkRemoved: () => undefined,
|
|
45
|
+
})
|
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`inputBuilder > matches the characterized GROQ output 1`] = `
|
|
4
|
+
"
|
|
5
|
+
select($branchId != null =>
|
|
6
|
+
*[_id == $id][0] {
|
|
7
|
+
_updatedAt,
|
|
8
|
+
"concepts": concepts[$branchId in @->broader[]->.conceptId]->|order(prefLabel) {
|
|
9
|
+
"id": _id,
|
|
10
|
+
"level": 0,
|
|
11
|
+
"isOrphan": true,
|
|
12
|
+
_originalId,
|
|
13
|
+
prefLabel,
|
|
14
|
+
definition,
|
|
15
|
+
example,
|
|
16
|
+
scopeNote,
|
|
17
|
+
"childConcepts": *[_id in ^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
18
|
+
{
|
|
19
|
+
"id": _id,
|
|
20
|
+
"level": 1,
|
|
21
|
+
_originalId,
|
|
22
|
+
prefLabel,
|
|
23
|
+
definition,
|
|
24
|
+
example,
|
|
25
|
+
scopeNote,
|
|
26
|
+
"childConcepts": *[_id in ^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
27
|
+
{
|
|
28
|
+
"id": _id,
|
|
29
|
+
"level": 2,
|
|
30
|
+
_originalId,
|
|
31
|
+
prefLabel,
|
|
32
|
+
definition,
|
|
33
|
+
example,
|
|
34
|
+
scopeNote,
|
|
35
|
+
"childConcepts": *[_id in ^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
36
|
+
{
|
|
37
|
+
"id": _id,
|
|
38
|
+
"level": 3,
|
|
39
|
+
_originalId,
|
|
40
|
+
prefLabel,
|
|
41
|
+
definition,
|
|
42
|
+
example,
|
|
43
|
+
scopeNote,
|
|
44
|
+
"childConcepts": *[_id in ^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
45
|
+
{
|
|
46
|
+
"id": _id,
|
|
47
|
+
"level": 4,
|
|
48
|
+
_originalId,
|
|
49
|
+
prefLabel,
|
|
50
|
+
definition,
|
|
51
|
+
example,
|
|
52
|
+
scopeNote,
|
|
53
|
+
"childConcepts": *[_id in ^.^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
54
|
+
{
|
|
55
|
+
"id": _id,
|
|
56
|
+
"level": 5,
|
|
57
|
+
_originalId,
|
|
58
|
+
prefLabel,
|
|
59
|
+
definition,
|
|
60
|
+
example,
|
|
61
|
+
scopeNote,
|
|
62
|
+
"childConcepts": *[_id in ^.^.^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
63
|
+
{
|
|
64
|
+
"id": _id,
|
|
65
|
+
"level": 6,
|
|
66
|
+
_originalId,
|
|
67
|
+
prefLabel,
|
|
68
|
+
definition,
|
|
69
|
+
example,
|
|
70
|
+
scopeNote,
|
|
71
|
+
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
*[_id == $id][0] {
|
|
81
|
+
_updatedAt,
|
|
82
|
+
"topConcepts":topConcepts[]->|order(prefLabel){
|
|
83
|
+
"id": _id,
|
|
84
|
+
"level": 0,
|
|
85
|
+
_originalId,
|
|
86
|
+
prefLabel,
|
|
87
|
+
definition,
|
|
88
|
+
example,
|
|
89
|
+
scopeNote,
|
|
90
|
+
"childConcepts": *[_id in ^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
91
|
+
{
|
|
92
|
+
"id": _id,
|
|
93
|
+
"level": 1,
|
|
94
|
+
_originalId,
|
|
95
|
+
prefLabel,
|
|
96
|
+
definition,
|
|
97
|
+
example,
|
|
98
|
+
scopeNote,
|
|
99
|
+
"childConcepts": *[_id in ^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
100
|
+
{
|
|
101
|
+
"id": _id,
|
|
102
|
+
"level": 2,
|
|
103
|
+
_originalId,
|
|
104
|
+
prefLabel,
|
|
105
|
+
definition,
|
|
106
|
+
example,
|
|
107
|
+
scopeNote,
|
|
108
|
+
"childConcepts": *[_id in ^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
109
|
+
{
|
|
110
|
+
"id": _id,
|
|
111
|
+
"level": 3,
|
|
112
|
+
_originalId,
|
|
113
|
+
prefLabel,
|
|
114
|
+
definition,
|
|
115
|
+
example,
|
|
116
|
+
scopeNote,
|
|
117
|
+
"childConcepts": *[_id in ^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
118
|
+
{
|
|
119
|
+
"id": _id,
|
|
120
|
+
"level": 4,
|
|
121
|
+
_originalId,
|
|
122
|
+
prefLabel,
|
|
123
|
+
definition,
|
|
124
|
+
example,
|
|
125
|
+
scopeNote,
|
|
126
|
+
"childConcepts": *[_id in ^.^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
127
|
+
{
|
|
128
|
+
"id": _id,
|
|
129
|
+
"level": 5,
|
|
130
|
+
_originalId,
|
|
131
|
+
prefLabel,
|
|
132
|
+
definition,
|
|
133
|
+
example,
|
|
134
|
+
scopeNote,
|
|
135
|
+
"childConcepts": *[_id in ^.^.^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
136
|
+
{
|
|
137
|
+
"id": _id,
|
|
138
|
+
"level": 6,
|
|
139
|
+
_originalId,
|
|
140
|
+
prefLabel,
|
|
141
|
+
definition,
|
|
142
|
+
example,
|
|
143
|
+
scopeNote,
|
|
144
|
+
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"concepts": concepts[]->|order(prefLabel){
|
|
153
|
+
"id": _id,
|
|
154
|
+
"level": 0,
|
|
155
|
+
"isOrphan":
|
|
156
|
+
coalesce(
|
|
157
|
+
!array::intersects(
|
|
158
|
+
broader[]._ref, (
|
|
159
|
+
coalesce(^.concepts[]._ref, []) + coalesce(^.topConcepts[]._ref, [])
|
|
160
|
+
)
|
|
161
|
+
),
|
|
162
|
+
true),
|
|
163
|
+
_originalId,
|
|
164
|
+
prefLabel,
|
|
165
|
+
definition,
|
|
166
|
+
example,
|
|
167
|
+
scopeNote,
|
|
168
|
+
"childConcepts": *[_id in ^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
169
|
+
{
|
|
170
|
+
"id": _id,
|
|
171
|
+
"level": 1,
|
|
172
|
+
_originalId,
|
|
173
|
+
prefLabel,
|
|
174
|
+
definition,
|
|
175
|
+
example,
|
|
176
|
+
scopeNote,
|
|
177
|
+
"childConcepts": *[_id in ^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
178
|
+
{
|
|
179
|
+
"id": _id,
|
|
180
|
+
"level": 2,
|
|
181
|
+
_originalId,
|
|
182
|
+
prefLabel,
|
|
183
|
+
definition,
|
|
184
|
+
example,
|
|
185
|
+
scopeNote,
|
|
186
|
+
"childConcepts": *[_id in ^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
187
|
+
{
|
|
188
|
+
"id": _id,
|
|
189
|
+
"level": 3,
|
|
190
|
+
_originalId,
|
|
191
|
+
prefLabel,
|
|
192
|
+
definition,
|
|
193
|
+
example,
|
|
194
|
+
scopeNote,
|
|
195
|
+
"childConcepts": *[_id in ^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
196
|
+
{
|
|
197
|
+
"id": _id,
|
|
198
|
+
"level": 4,
|
|
199
|
+
_originalId,
|
|
200
|
+
prefLabel,
|
|
201
|
+
definition,
|
|
202
|
+
example,
|
|
203
|
+
scopeNote,
|
|
204
|
+
"childConcepts": *[_id in ^.^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
205
|
+
{
|
|
206
|
+
"id": _id,
|
|
207
|
+
"level": 5,
|
|
208
|
+
_originalId,
|
|
209
|
+
prefLabel,
|
|
210
|
+
definition,
|
|
211
|
+
example,
|
|
212
|
+
scopeNote,
|
|
213
|
+
"childConcepts": *[_id in ^.^.^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
214
|
+
{
|
|
215
|
+
"id": _id,
|
|
216
|
+
"level": 6,
|
|
217
|
+
_originalId,
|
|
218
|
+
prefLabel,
|
|
219
|
+
definition,
|
|
220
|
+
example,
|
|
221
|
+
scopeNote,
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
)"
|
|
232
|
+
`;
|
|
233
|
+
|
|
234
|
+
exports[`trunkBuilder > matches the characterized GROQ output 1`] = `
|
|
235
|
+
"*[_id == $id][0] {
|
|
236
|
+
_updatedAt,
|
|
237
|
+
"topConcepts": topConcepts[]->|order(prefLabel) {
|
|
238
|
+
"id": _id,
|
|
239
|
+
"level": 0,
|
|
240
|
+
_originalId,
|
|
241
|
+
prefLabel,
|
|
242
|
+
definition,
|
|
243
|
+
example,
|
|
244
|
+
scopeNote,
|
|
245
|
+
"childConcepts": *[_id in ^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
246
|
+
{
|
|
247
|
+
"id": _id,
|
|
248
|
+
"level": 1,
|
|
249
|
+
_originalId,
|
|
250
|
+
prefLabel,
|
|
251
|
+
definition,
|
|
252
|
+
example,
|
|
253
|
+
scopeNote,
|
|
254
|
+
"childConcepts": *[_id in ^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
255
|
+
{
|
|
256
|
+
"id": _id,
|
|
257
|
+
"level": 2,
|
|
258
|
+
_originalId,
|
|
259
|
+
prefLabel,
|
|
260
|
+
definition,
|
|
261
|
+
example,
|
|
262
|
+
scopeNote,
|
|
263
|
+
"childConcepts": *[_id in ^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
264
|
+
{
|
|
265
|
+
"id": _id,
|
|
266
|
+
"level": 3,
|
|
267
|
+
_originalId,
|
|
268
|
+
prefLabel,
|
|
269
|
+
definition,
|
|
270
|
+
example,
|
|
271
|
+
scopeNote,
|
|
272
|
+
"childConcepts": *[_id in ^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
273
|
+
{
|
|
274
|
+
"id": _id,
|
|
275
|
+
"level": 4,
|
|
276
|
+
_originalId,
|
|
277
|
+
prefLabel,
|
|
278
|
+
definition,
|
|
279
|
+
example,
|
|
280
|
+
scopeNote,
|
|
281
|
+
"childConcepts": *[_id in ^.^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
282
|
+
{
|
|
283
|
+
"id": _id,
|
|
284
|
+
"level": 5,
|
|
285
|
+
_originalId,
|
|
286
|
+
prefLabel,
|
|
287
|
+
definition,
|
|
288
|
+
example,
|
|
289
|
+
scopeNote,
|
|
290
|
+
"childConcepts": *[_id in ^.^.^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
291
|
+
{
|
|
292
|
+
"id": _id,
|
|
293
|
+
"level": 6,
|
|
294
|
+
_originalId,
|
|
295
|
+
prefLabel,
|
|
296
|
+
definition,
|
|
297
|
+
example,
|
|
298
|
+
scopeNote,
|
|
299
|
+
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"concepts": concepts[]->|order(prefLabel) {
|
|
308
|
+
"id": _id,
|
|
309
|
+
"level": 0,
|
|
310
|
+
"isOrphan":
|
|
311
|
+
coalesce(
|
|
312
|
+
!array::intersects(
|
|
313
|
+
broader[]._ref, (
|
|
314
|
+
coalesce(^.concepts[]._ref, []) + coalesce(^.topConcepts[]._ref, [])
|
|
315
|
+
)
|
|
316
|
+
),
|
|
317
|
+
true),
|
|
318
|
+
_originalId,
|
|
319
|
+
prefLabel,
|
|
320
|
+
definition,
|
|
321
|
+
example,
|
|
322
|
+
scopeNote,
|
|
323
|
+
"childConcepts": *[_id in ^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
324
|
+
{
|
|
325
|
+
"id": _id,
|
|
326
|
+
"level": 1,
|
|
327
|
+
_originalId,
|
|
328
|
+
prefLabel,
|
|
329
|
+
definition,
|
|
330
|
+
example,
|
|
331
|
+
scopeNote,
|
|
332
|
+
"childConcepts": *[_id in ^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
333
|
+
{
|
|
334
|
+
"id": _id,
|
|
335
|
+
"level": 2,
|
|
336
|
+
_originalId,
|
|
337
|
+
prefLabel,
|
|
338
|
+
definition,
|
|
339
|
+
example,
|
|
340
|
+
scopeNote,
|
|
341
|
+
"childConcepts": *[_id in ^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
342
|
+
{
|
|
343
|
+
"id": _id,
|
|
344
|
+
"level": 3,
|
|
345
|
+
_originalId,
|
|
346
|
+
prefLabel,
|
|
347
|
+
definition,
|
|
348
|
+
example,
|
|
349
|
+
scopeNote,
|
|
350
|
+
"childConcepts": *[_id in ^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
351
|
+
{
|
|
352
|
+
"id": _id,
|
|
353
|
+
"level": 4,
|
|
354
|
+
_originalId,
|
|
355
|
+
prefLabel,
|
|
356
|
+
definition,
|
|
357
|
+
example,
|
|
358
|
+
scopeNote,
|
|
359
|
+
"childConcepts": *[_id in ^.^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
360
|
+
{
|
|
361
|
+
"id": _id,
|
|
362
|
+
"level": 5,
|
|
363
|
+
_originalId,
|
|
364
|
+
prefLabel,
|
|
365
|
+
definition,
|
|
366
|
+
example,
|
|
367
|
+
scopeNote,
|
|
368
|
+
"childConcepts": *[_id in ^.^.^.^.^.^.^.concepts[]._ref && ^._id in broader[]._ref]|order(prefLabel)
|
|
369
|
+
{
|
|
370
|
+
"id": _id,
|
|
371
|
+
"level": 6,
|
|
372
|
+
_originalId,
|
|
373
|
+
prefLabel,
|
|
374
|
+
definition,
|
|
375
|
+
example,
|
|
376
|
+
scopeNote,
|
|
377
|
+
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}"
|
|
386
|
+
`;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`recommendationsQuery > matches the characterized GROQ output 1`] = `
|
|
4
|
+
"*[_type == "skosConcept" && _id in *[_type == "skosConceptScheme" && schemeId == $schemeId][0]{
|
|
5
|
+
"ids": coalesce(topConcepts[]._ref, []) + coalesce(concepts[]._ref, [])
|
|
6
|
+
}.ids] | score(text::semanticSimilarity($searchQuery)) | order(_score desc) [0...$maxResults] {
|
|
7
|
+
"conceptId": _id,
|
|
8
|
+
"score": _score
|
|
9
|
+
}"
|
|
10
|
+
`;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {createId} from './createId'
|
|
4
|
+
|
|
5
|
+
const DEFAULT_ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
|
|
6
|
+
|
|
7
|
+
// nanoid is non-deterministic, so these assert SHAPE (length, charset, prefix),
|
|
8
|
+
// not exact values — pinning the configurable id format the plugin ships with.
|
|
9
|
+
describe('createId', () => {
|
|
10
|
+
it('generates a 6-character id from the default alphabet', () => {
|
|
11
|
+
const id = createId()
|
|
12
|
+
expect(id).toHaveLength(6)
|
|
13
|
+
expect([...id].every((char) => DEFAULT_ALPHABET.includes(char))).toBe(true)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('honors a custom length', () => {
|
|
17
|
+
expect(createId({length: 10})).toHaveLength(10)
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
it('honors a custom alphabet/pattern', () => {
|
|
21
|
+
const id = createId({pattern: 'abc'})
|
|
22
|
+
expect(id).toHaveLength(6)
|
|
23
|
+
expect(/^[abc]{6}$/.test(id)).toBe(true)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('prepends a prefix on top of the generated length', () => {
|
|
27
|
+
const id = createId({prefix: 'Q', length: 5})
|
|
28
|
+
expect(id.startsWith('Q')).toBe(true)
|
|
29
|
+
expect(id).toHaveLength(6) // prefix (1) + 5 generated chars
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('produces a different id on each call', () => {
|
|
33
|
+
expect(createId()).not.toBe(createId())
|
|
34
|
+
})
|
|
35
|
+
})
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
assertBranchId,
|
|
5
|
+
assertSchemeId,
|
|
6
|
+
buildBranchFilterResult,
|
|
7
|
+
buildSchemeFilterResult,
|
|
8
|
+
} from './filters'
|
|
9
|
+
|
|
10
|
+
describe('assertSchemeId', () => {
|
|
11
|
+
it('throws on a missing or non-string scheme id', () => {
|
|
12
|
+
expect(() => assertSchemeId(undefined)).toThrow(
|
|
13
|
+
'Invalid or missing schemeId: scheme Id must be a string',
|
|
14
|
+
)
|
|
15
|
+
expect(() => assertSchemeId('')).toThrow('Invalid or missing schemeId')
|
|
16
|
+
expect(() => assertSchemeId(123)).toThrow('Invalid or missing schemeId')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('accepts a valid scheme id', () => {
|
|
20
|
+
expect(() => assertSchemeId('abc123')).not.toThrow()
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
describe('assertBranchId', () => {
|
|
25
|
+
it('throws on a missing or non-string branch id', () => {
|
|
26
|
+
expect(() => assertBranchId(null)).toThrow(
|
|
27
|
+
'Invalid or missing branchId: branch Id must be a string',
|
|
28
|
+
)
|
|
29
|
+
expect(() => assertBranchId('')).toThrow('Invalid or missing branchId')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('accepts a valid branch id', () => {
|
|
33
|
+
expect(() => assertBranchId('d4e5f6')).not.toThrow()
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
describe('buildSchemeFilterResult', () => {
|
|
38
|
+
it('assembles the filter, params, and option passthrough', () => {
|
|
39
|
+
const result = buildSchemeFilterResult({
|
|
40
|
+
schemeId: 'abc123',
|
|
41
|
+
concepts: ['c-a', 'c-b'],
|
|
42
|
+
topConcepts: ['t-1'],
|
|
43
|
+
expanded: true,
|
|
44
|
+
browseOnly: false,
|
|
45
|
+
})
|
|
46
|
+
expect(result.filter).toMatchInlineSnapshot(`
|
|
47
|
+
"(_id in $concepts
|
|
48
|
+
|| _id in $topConcepts)"
|
|
49
|
+
`)
|
|
50
|
+
expect(result.params).toEqual({concepts: ['c-a', 'c-b'], topConcepts: ['t-1'], schemeId: 'abc123'})
|
|
51
|
+
expect(result.expanded).toBe(true)
|
|
52
|
+
expect(result.browseOnly).toBe(false)
|
|
53
|
+
})
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
describe('buildBranchFilterResult', () => {
|
|
57
|
+
it('assembles the filter, params, and option passthrough', () => {
|
|
58
|
+
const result = buildBranchFilterResult({schemeId: 'abc123', branchId: 'd4e5f6', concepts: ['c-x']})
|
|
59
|
+
expect(result.filter).toMatchInlineSnapshot(`"_id in $concepts"`)
|
|
60
|
+
expect(result.params).toEqual({concepts: ['c-x'], schemeId: 'abc123', branchId: 'd4e5f6'})
|
|
61
|
+
expect(result.expanded).toBeUndefined()
|
|
62
|
+
expect(result.browseOnly).toBeUndefined()
|
|
63
|
+
})
|
|
64
|
+
})
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure halves of the reference-field filters. The public `schemeFilter` /
|
|
3
|
+
* `branchFilter` factories (in ../helpers) keep the impure GROQ fetch; the
|
|
4
|
+
* validation and result-assembly below are pure and unit-tested here.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export type SchemeFilterResult = {
|
|
8
|
+
filter: string
|
|
9
|
+
params: {schemeId: string; concepts: string[]; topConcepts: string[]}
|
|
10
|
+
expanded?: boolean
|
|
11
|
+
browseOnly?: boolean
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type BranchFilterResult = {
|
|
15
|
+
filter: string
|
|
16
|
+
params: {schemeId: string; branchId: string; concepts: string[]}
|
|
17
|
+
expanded?: boolean
|
|
18
|
+
browseOnly?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Throws if the scheme id is missing or not a string. */
|
|
22
|
+
export function assertSchemeId(schemeId: unknown): asserts schemeId is string {
|
|
23
|
+
if (!schemeId || typeof schemeId !== 'string') {
|
|
24
|
+
throw new Error('Invalid or missing schemeId: scheme Id must be a string')
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Throws if the branch id is missing or not a string. */
|
|
29
|
+
export function assertBranchId(branchId: unknown): asserts branchId is string {
|
|
30
|
+
if (!branchId || typeof branchId !== 'string') {
|
|
31
|
+
throw new Error('Invalid or missing branchId: branch Id must be a string')
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Assemble the scheme reference-filter result from the fetched concept refs. */
|
|
36
|
+
export function buildSchemeFilterResult(input: {
|
|
37
|
+
schemeId: string
|
|
38
|
+
concepts: string[]
|
|
39
|
+
topConcepts: string[]
|
|
40
|
+
expanded?: boolean
|
|
41
|
+
browseOnly?: boolean
|
|
42
|
+
}): SchemeFilterResult {
|
|
43
|
+
const {schemeId, concepts, topConcepts, expanded, browseOnly} = input
|
|
44
|
+
return {
|
|
45
|
+
filter: `(_id in $concepts
|
|
46
|
+
|| _id in $topConcepts)`,
|
|
47
|
+
params: {concepts, topConcepts, schemeId},
|
|
48
|
+
expanded,
|
|
49
|
+
browseOnly,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Assemble the branch reference-filter result from the fetched concept refs. */
|
|
54
|
+
export function buildBranchFilterResult(input: {
|
|
55
|
+
schemeId: string
|
|
56
|
+
branchId: string
|
|
57
|
+
concepts: string[]
|
|
58
|
+
expanded?: boolean
|
|
59
|
+
browseOnly?: boolean
|
|
60
|
+
}): BranchFilterResult {
|
|
61
|
+
const {schemeId, branchId, concepts, expanded, browseOnly} = input
|
|
62
|
+
return {
|
|
63
|
+
filter: `_id in $concepts`,
|
|
64
|
+
params: {concepts, schemeId, branchId},
|
|
65
|
+
expanded,
|
|
66
|
+
browseOnly,
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import {describe, expect, it} from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
conceptReferenceStrength,
|
|
5
|
+
deriveNewConceptId,
|
|
6
|
+
deriveSchemeMutationTarget,
|
|
7
|
+
SKOS_CONCEPT_STRENGTHEN,
|
|
8
|
+
} from './ids'
|
|
9
|
+
|
|
10
|
+
describe('deriveSchemeMutationTarget', () => {
|
|
11
|
+
it('targets the draft id for a published scheme', () => {
|
|
12
|
+
expect(deriveSchemeMutationTarget('scheme-1')).toEqual({
|
|
13
|
+
isInRelease: false,
|
|
14
|
+
releaseName: undefined,
|
|
15
|
+
schemeId: 'drafts.scheme-1',
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('targets the draft id for a scheme that is already a draft', () => {
|
|
20
|
+
expect(deriveSchemeMutationTarget('drafts.scheme-1')).toEqual({
|
|
21
|
+
isInRelease: false,
|
|
22
|
+
releaseName: undefined,
|
|
23
|
+
schemeId: 'drafts.scheme-1',
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('targets the version id (and extracts the release) for a scheme in a release', () => {
|
|
28
|
+
expect(deriveSchemeMutationTarget('versions.rel1.scheme-1')).toEqual({
|
|
29
|
+
isInRelease: true,
|
|
30
|
+
releaseName: 'rel1',
|
|
31
|
+
schemeId: 'versions.rel1.scheme-1',
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
describe('deriveNewConceptId', () => {
|
|
37
|
+
it('creates a draft id when not in a release', () => {
|
|
38
|
+
expect(deriveNewConceptId('new-1', {isInRelease: false, releaseName: undefined})).toBe(
|
|
39
|
+
'drafts.new-1',
|
|
40
|
+
)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('creates a version id when in a release', () => {
|
|
44
|
+
expect(deriveNewConceptId('new-1', {isInRelease: true, releaseName: 'rel1'})).toBe(
|
|
45
|
+
'versions.rel1.new-1',
|
|
46
|
+
)
|
|
47
|
+
})
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
describe('conceptReferenceStrength', () => {
|
|
51
|
+
it('is a strong (non-weak) reference when the concept is published', () => {
|
|
52
|
+
expect(conceptReferenceStrength('concept-1')).toEqual({_weak: false})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('is weak + strengthen-on-publish when the concept is a draft', () => {
|
|
56
|
+
expect(conceptReferenceStrength('drafts.concept-1')).toEqual({
|
|
57
|
+
_weak: true,
|
|
58
|
+
_strengthenOnPublish: SKOS_CONCEPT_STRENGTHEN,
|
|
59
|
+
})
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
it('is weak + strengthen-on-publish when the concept is in a release', () => {
|
|
63
|
+
expect(conceptReferenceStrength('versions.rel1.concept-1')).toEqual({
|
|
64
|
+
_weak: true,
|
|
65
|
+
_strengthenOnPublish: {type: 'skosConcept', template: {id: 'skosConcept'}},
|
|
66
|
+
})
|
|
67
|
+
})
|
|
68
|
+
})
|