Skip to content

Commit 99e9aa6

Browse files
authored
MMT-4002 & MMT-4017: Custom widget support for searching/selecting a KMS keyword (#1358)
1 parent e858fa6 commit 99e9aa6

31 files changed

+1528
-204
lines changed

static/src/js/components/CustomTitleFieldTemplate/__tests__/CustomTitleFieldTemplate.test.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe('CustomTitleFieldTemplate', () => {
5656
})
5757

5858
describe('when a title field with hide-header set to true', () => {
59-
it('renders it with no header', () => {
59+
test('renders it with no header', () => {
6060
setup({
6161
uiSchema: {
6262
'ui:hide-header': true

static/src/js/components/JsonPreview/__tests__/JsonPreview.test.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ const setup = (draft = undefined) => {
1717

1818
describe('JsonPreview Component', () => {
1919
describe('when draft is not present in the context', () => {
20-
it('renders JSONPretty', () => {
20+
test('renders JSONPretty', () => {
2121
setup()
2222

2323
expect(JSONPretty).toHaveBeenCalledTimes(1)
@@ -28,7 +28,7 @@ describe('JsonPreview Component', () => {
2828
})
2929

3030
describe('when ummMetadata is not present in draft', () => {
31-
it('renders JSONPretty', () => {
31+
test('renders JSONPretty', () => {
3232
setup({})
3333

3434
expect(JSONPretty).toHaveBeenCalledTimes(1)
@@ -39,7 +39,7 @@ describe('JsonPreview Component', () => {
3939
})
4040

4141
describe('when draft metadata exists', () => {
42-
it('renders JSONPretty', () => {
42+
test('renders JSONPretty', () => {
4343
setup({
4444
ummMetadata: {
4545
Name: 'Mock Name'

static/src/js/components/KeywordForm/KeywordForm.jsx

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
1-
import React, { useState, useEffect } from 'react'
2-
import PropTypes from 'prop-types'
3-
import validator from '@rjsf/validator-ajv8'
41
import Form from '@rjsf/core'
2+
import validator from '@rjsf/validator-ajv8'
3+
import PropTypes from 'prop-types'
4+
import React, { useEffect, useState } from 'react'
55

66
import CustomArrayTemplate from '@/js/components/CustomArrayFieldTemplate/CustomArrayFieldTemplate'
77
import CustomFieldTemplate from '@/js/components/CustomFieldTemplate/CustomFieldTemplate'
88
import CustomTextareaWidget from '@/js/components/CustomTextareaWidget/CustomTextareaWidget'
99
import CustomTextWidget from '@/js/components/CustomTextWidget/CustomTextWidget'
1010
import GridLayout from '@/js/components/GridLayout/GridLayout'
11-
1211
import editKeywordsUiSchema from '@/js/schemas/uiSchemas/keywords/editKeyword'
1312
import keywordSchema from '@/js/schemas/umm/keywordSchema'
1413

14+
import KmsConceptSelectionWidget from '../KmsConceptSelectionWidget/KmsConceptSelectionWidget'
15+
1516
const KeywordForm = ({
1617
initialData,
17-
onFormDataChange
18+
onFormDataChange,
19+
scheme,
20+
version
1821
}) => {
1922
const [formData, setFormData] = useState(initialData)
2023

@@ -23,11 +26,12 @@ const KeywordForm = ({
2326
}, [initialData])
2427

2528
const fields = {
29+
kmsConceptSelection: KmsConceptSelectionWidget,
2630
layout: GridLayout
2731
}
2832
const widgets = {
29-
TextareaWidget: CustomTextareaWidget,
30-
TextWidget: CustomTextWidget
33+
TextWidget: CustomTextWidget,
34+
TextareaWidget: CustomTextareaWidget
3135
}
3236
const templates = {
3337
ArrayFieldTemplate: CustomArrayTemplate,
@@ -55,6 +59,12 @@ const KeywordForm = ({
5559
uiSchema={editKeywordsUiSchema}
5660
formData={formData}
5761
onChange={handleChange}
62+
formContext={
63+
{
64+
scheme,
65+
version
66+
}
67+
}
5868
// OnSubmit={handleSubmit}
5969
validator={validator}
6070
>
@@ -75,29 +85,38 @@ KeywordForm.defaultProps = {
7585

7686
KeywordForm.propTypes = {
7787
initialData: PropTypes.shape({
78-
KeywordUUID: PropTypes.string,
79-
BroaderKeyword: PropTypes.string,
80-
NarrowerKeywords: PropTypes.arrayOf(PropTypes.shape({
81-
NarrowerUUID: PropTypes.string
82-
})),
83-
PreferredLabel: PropTypes.string,
8488
AlternateLabels: PropTypes.arrayOf(PropTypes.shape({
8589
LabelName: PropTypes.string,
8690
LabelType: PropTypes.string
8791
})),
92+
BroaderKeywords: PropTypes.arrayOf(PropTypes.shape({
93+
BroaderUUID: PropTypes.string
94+
})),
95+
ChangeLogs: PropTypes.string,
8896
Definition: PropTypes.string,
8997
DefinitionReference: PropTypes.string,
90-
Resources: PropTypes.arrayOf(PropTypes.shape({
91-
ResourceType: PropTypes.string,
92-
ResourceUri: PropTypes.string
98+
KeywordUUID: PropTypes.string,
99+
NarrowerKeywords: PropTypes.arrayOf(PropTypes.shape({
100+
NarrowerUUID: PropTypes.string
93101
})),
102+
PreferredLabel: PropTypes.string,
94103
RelatedKeywords: PropTypes.arrayOf(PropTypes.shape({
95-
UUID: PropTypes.string,
96-
RelationshipType: PropTypes.string
104+
RelationshipType: PropTypes.string,
105+
UUID: PropTypes.string
97106
})),
98-
ChangeLogs: PropTypes.string
107+
Resources: PropTypes.arrayOf(PropTypes.shape({
108+
ResourceType: PropTypes.string,
109+
ResourceUri: PropTypes.string
110+
}))
99111
}),
100-
onFormDataChange: PropTypes.func
112+
onFormDataChange: PropTypes.func,
113+
scheme: PropTypes.shape({
114+
name: PropTypes.string
115+
}).isRequired,
116+
version: PropTypes.shape({
117+
version: PropTypes.string,
118+
version_type: PropTypes.string
119+
}).isRequired
101120
}
102121

103122
export default KeywordForm

static/src/js/components/KeywordForm/__tests__/KeywordForm.test.jsx

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import React from 'react'
21
import {
32
render,
43
screen,
54
waitFor
65
} from '@testing-library/react'
6+
import userEvent from '@testing-library/user-event'
7+
import React from 'react'
78
import {
89
describe,
9-
test,
1010
expect,
11+
test,
1112
vi
1213
} from 'vitest'
13-
import userEvent from '@testing-library/user-event'
14+
1415
import KeywordForm from '../KeywordForm'
1516

1617
vi.mock('@/js/utils/getUmmSchema', () => ({
@@ -40,12 +41,22 @@ describe('when KeywordForm is rendered', () => {
4041
}
4142

4243
test('should display the form title', () => {
43-
render(<KeywordForm initialData={mockInitialData} />)
44+
render(<KeywordForm
45+
initialData={mockInitialData}
46+
scheme={{ name: 'sciencekeywords' }}
47+
version={{ version: 'draft' }}
48+
/>)
49+
4450
expect(screen.getByText('Edit Keyword')).toBeInTheDocument()
4551
})
4652

4753
test('should render the form with initial data', () => {
48-
render(<KeywordForm initialData={mockInitialData} />)
54+
render(<KeywordForm
55+
initialData={mockInitialData}
56+
scheme={{ name: 'sciencekeywords' }}
57+
version={{ version: 'draft' }}
58+
/>)
59+
4960
expect(screen.getByDisplayValue('Test Keyword')).toBeInTheDocument()
5061
expect(screen.getByDisplayValue('This is a test keyword')).toBeInTheDocument()
5162
})
@@ -58,6 +69,8 @@ describe('when user types in the form', () => {
5869

5970
render(<KeywordForm
6071
initialData={{ PreferredLabel: '' }}
72+
scheme={{ name: 'sciencekeywords' }}
73+
version={{ version: 'draft' }}
6174
onFormDataChange={mockOnFormDataChange}
6275
/>)
6376

@@ -80,10 +93,19 @@ describe('when user types in the form', () => {
8093

8194
describe('when initialData prop changes', () => {
8295
test('should update the form', () => {
83-
const { rerender } = render(<KeywordForm initialData={{ PreferredLabel: 'Initial Keyword' }} />)
96+
const { rerender } = render(<KeywordForm
97+
initialData={{ PreferredLabel: 'Initial Keyword' }}
98+
scheme={{ name: 'sciencekeywords' }}
99+
version={{ version: 'draft' }}
100+
/>)
84101
expect(screen.getByDisplayValue('Initial Keyword')).toBeInTheDocument()
85102

86-
rerender(<KeywordForm initialData={{ PreferredLabel: 'Updated Keyword' }} />)
103+
rerender(<KeywordForm
104+
initialData={{ PreferredLabel: 'Updated Keyword' }}
105+
scheme={{ name: 'sciencekeywords' }}
106+
version={{ version: 'draft' }}
107+
/>)
108+
87109
expect(screen.getByDisplayValue('Updated Keyword')).toBeInTheDocument()
88110
})
89111
})

static/src/js/components/KeywordTree/KeywordTree.jsx

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ import './KeywordTree.scss'
7272
* );
7373
*/
7474
export const KeywordTree = ({
75-
data, onAddNarrower, onNodeClick, onNodeEdit
75+
data, onAddNarrower, onNodeClick, onNodeEdit, searchTerm, selectedNodeId, showContextMenu, openAll
7676
}) => {
7777
const [treeData, setTreeData] = useState(Array.isArray(data) ? data : [data])
7878
const treeRef = useRef(null)
@@ -97,15 +97,29 @@ export const KeywordTree = ({
9797
}
9898
}, [])
9999

100+
// Effect to manage tree expansion or node selection
100101
useEffect(() => {
101102
if (treeRef.current && treeData.length > 0) {
102-
const tree = treeRef.current
103-
const rootNode = tree.get(treeData[0].id)
104-
if (rootNode) {
105-
rootNode.open()
103+
if (openAll) {
104+
treeRef.current.openAll()
105+
} else if (selectedNodeId) {
106+
treeRef.current.openParents(selectedNodeId)
107+
setTimeout(() => { // Delay to potentially allow tree updates
108+
const node = treeRef.current.get(selectedNodeId)
109+
if (node) {
110+
treeRef.current.select(selectedNodeId)
111+
treeRef.current.scrollTo(selectedNodeId, 'center')
112+
}
113+
}, 0)
114+
} else {
115+
const tree = treeRef.current
116+
const rootNode = tree.get(treeData[0].id)
117+
if (rootNode) {
118+
rootNode.open()
119+
}
106120
}
107121
}
108-
}, [treeData])
122+
}, [treeData, openAll])
109123

110124
const closeAllDescendants = (node) => {
111125
if (node.isOpen) {
@@ -223,9 +237,9 @@ export const KeywordTree = ({
223237
node={node}
224238
onAdd={handleAdd}
225239
onDelete={handleDelete}
240+
searchTerm={searchTerm}
226241
setContextMenu={setContextMenu}
227242
onToggle={handleToggle}
228-
onClick={onNodeClick}
229243
onEdit={onNodeEdit}
230244
onNodeClick={onNodeClick}
231245
handleAdd={handleAdd}
@@ -234,7 +248,7 @@ export const KeywordTree = ({
234248
}
235249
</Tree>
236250
{
237-
contextMenu && (
251+
contextMenu && showContextMenu && (
238252
<KeywordTreeContextMenu
239253
{...contextMenu}
240254
onClose={() => setContextMenu(null)}
@@ -273,12 +287,23 @@ const NodeShape = {
273287
}
274288
NodeShape.children = PropTypes.arrayOf(PropTypes.shape(NodeShape))
275289

290+
KeywordTree.defaultProps = {
291+
searchTerm: null,
292+
selectedNodeId: null,
293+
showContextMenu: true,
294+
openAll: false
295+
}
296+
276297
KeywordTree.propTypes = {
298+
selectedNodeId: PropTypes.string,
277299
data: PropTypes.oneOfType([
278300
PropTypes.shape(NodeShape),
279301
PropTypes.arrayOf(PropTypes.shape(NodeShape))
280302
]).isRequired,
281303
onAddNarrower: PropTypes.func.isRequired,
282304
onNodeClick: PropTypes.func.isRequired,
283-
onNodeEdit: PropTypes.func.isRequired
305+
onNodeEdit: PropTypes.func.isRequired,
306+
searchTerm: PropTypes.string,
307+
showContextMenu: PropTypes.bool,
308+
openAll: PropTypes.bool
284309
}

0 commit comments

Comments
 (0)