Skip to content

Commit 2d20f9c

Browse files
Display additional properties on Form tab (#60)
* display card with additional properties added via json
1 parent 58f85fe commit 2d20f9c

File tree

4 files changed

+123
-42
lines changed

4 files changed

+123
-42
lines changed

__tests__/components/JSONEditor.test.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -84,17 +84,20 @@ const mockFormData = {
8484
describe('JSONEditor', () => {
8585
let mockOnChange: Mock;
8686
let mockSetHasJSONChanges: Mock;
87+
let mockSetAdditionalProperties: Mock;
8788
let defaultProps: any;
8889

8990
beforeEach(() => {
9091
mockOnChange = vi.fn();
9192
mockSetHasJSONChanges = vi.fn();
93+
mockSetAdditionalProperties = vi.fn();
9294
defaultProps = {
9395
value: mockFormData,
9496
onChange: mockOnChange,
9597
disableCollectionNameChange: false,
9698
hasJSONChanges: true,
9799
setHasJSONChanges: mockSetHasJSONChanges,
100+
setAdditionalProperties: mockSetAdditionalProperties,
98101
};
99102
});
100103

__tests__/playwright/CreateIngestPage.test.tsx

+12
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,21 @@ test.describe('Create Ingest Page', () => {
218218
body: JSONScreenshot,
219219
contentType: 'image/png',
220220
});
221+
221222
await page.getByRole('button', { name: /apply changes/i }).click();
222223
});
223224

225+
await expect(
226+
page.getByTestId('extra-properties-card').getByText('extraField'),
227+
'verify that extra properties are displayed on the form tab'
228+
).toBeVisible();
229+
230+
const extraPropertiesScreenshot = await page.screenshot({ fullPage: true });
231+
testInfo.attach('extra properties listed on form tab', {
232+
body: extraPropertiesScreenshot,
233+
contentType: 'image/png',
234+
});
235+
224236
await test.step('submit form and validate that POST body values match pasted config values including extra field', async () => {
225237
await page.getByRole('button', { name: /submit/i }).click();
226238
});

components/IngestForm.tsx

+93-42
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import '@ant-design/v5-patch-for-react-19';
44

55
import { SetStateAction, useEffect, useState } from 'react';
6-
import { Tabs } from 'antd';
6+
import { Card, Tabs } from 'antd';
7+
import { ExclamationCircleOutlined } from '@ant-design/icons';
78
import { withTheme } from '@rjsf/core';
89
import { Theme as AntDTheme } from '@rjsf/antd';
910
import validator from '@rjsf/validator-ajv8';
@@ -53,6 +54,9 @@ function IngestForm({
5354
const [activeTab, setActiveTab] = useState<string>('form');
5455
const [forceRenderKey, setForceRenderKey] = useState<number>(0); // Force refresh RJSF to clear validation errors
5556
const [hasJSONChanges, setHasJSONChanges] = useState<boolean>(false);
57+
const [additionalProperties, setAdditionalProperties] = useState<
58+
string[] | null
59+
>(null);
5660

5761
useEffect(() => {
5862
if (defaultTemporalExtent) {
@@ -114,47 +118,94 @@ function IngestForm({
114118
};
115119

116120
return (
117-
<Tabs
118-
activeKey={activeTab}
119-
onChange={setActiveTab}
120-
items={[
121-
{
122-
key: 'form',
123-
label: 'Form',
124-
children: (
125-
<Form
126-
key={forceRenderKey} // Forces re-render when data updates
127-
schema={jsonSchema as JSONSchema7}
128-
uiSchema={uiSchema}
129-
validator={validator}
130-
customValidate={customValidate}
131-
templates={{
132-
ObjectFieldTemplate: ObjectFieldTemplate,
133-
}}
134-
formData={formData}
135-
onChange={onFormDataChanged}
136-
onSubmit={(data) => handleSubmit(data, onSubmit)}
137-
formContext={{ updateFormData }}
138-
>
139-
{children}
140-
</Form>
141-
),
142-
},
143-
{
144-
key: 'json',
145-
label: 'Manual JSON Edit',
146-
children: (
147-
<JSONEditor
148-
value={formData || {}}
149-
onChange={handleJsonEditorChange}
150-
disableCollectionNameChange={disableCollectionNameChange}
151-
hasJSONChanges={hasJSONChanges}
152-
setHasJSONChanges={setHasJSONChanges}
153-
/>
154-
),
155-
},
156-
]}
157-
/>
121+
<>
122+
<Tabs
123+
activeKey={activeTab}
124+
onChange={setActiveTab}
125+
items={[
126+
{
127+
key: 'form',
128+
label: 'Form',
129+
children: (
130+
<>
131+
<Form
132+
key={forceRenderKey} // Forces re-render when data updates
133+
schema={jsonSchema as JSONSchema7}
134+
uiSchema={uiSchema}
135+
validator={validator}
136+
customValidate={customValidate}
137+
templates={{
138+
ObjectFieldTemplate: ObjectFieldTemplate,
139+
}}
140+
formData={formData}
141+
onChange={onFormDataChanged}
142+
onSubmit={(data) => handleSubmit(data, onSubmit)}
143+
formContext={{ updateFormData }}
144+
>
145+
{children}
146+
</Form>
147+
{additionalProperties && additionalProperties.length > 0 && (
148+
<Card
149+
data-testid="extra-properties-card"
150+
title={
151+
<div
152+
style={{
153+
display: 'flex',
154+
alignItems: 'center',
155+
gap: '8px',
156+
color: '#faad14',
157+
}}
158+
>
159+
<ExclamationCircleOutlined />
160+
<span>Extra Properties set via JSON Editor</span>
161+
</div>
162+
}
163+
style={{
164+
width: '100%',
165+
marginTop: '10px',
166+
maxHeight: '300px',
167+
overflowY: 'auto',
168+
}}
169+
>
170+
<ul
171+
style={{
172+
display: 'grid',
173+
gridTemplateRows: 'repeat(3, auto)', // 3 rows before wrapping to new column
174+
gridAutoFlow: 'column',
175+
gap: '10px',
176+
padding: 0,
177+
listStyleType: 'none',
178+
}}
179+
>
180+
{additionalProperties.map((prop) => (
181+
<li key={prop} style={{ paddingLeft: '10px' }}>
182+
{prop}
183+
</li>
184+
))}
185+
</ul>
186+
</Card>
187+
)}
188+
</>
189+
),
190+
},
191+
{
192+
key: 'json',
193+
label: 'Manual JSON Edit',
194+
children: (
195+
<JSONEditor
196+
value={formData || {}}
197+
onChange={handleJsonEditorChange}
198+
disableCollectionNameChange={disableCollectionNameChange}
199+
hasJSONChanges={hasJSONChanges}
200+
setHasJSONChanges={setHasJSONChanges}
201+
additionalProperties={additionalProperties}
202+
setAdditionalProperties={setAdditionalProperties}
203+
/>
204+
),
205+
},
206+
]}
207+
/>
208+
</>
158209
);
159210
}
160211

components/JSONEditor.tsx

+15
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ interface JSONEditorProps {
2323
disableCollectionNameChange?: boolean;
2424
hasJSONChanges?: boolean;
2525
setHasJSONChanges: (hasJSONChanges: boolean) => void;
26+
additionalProperties: string[] | null;
27+
setAdditionalProperties: (additionalProperties: string[] | null) => void;
2628
}
2729

2830
const JSONEditor: React.FC<JSONEditorProps> = ({
@@ -31,6 +33,8 @@ const JSONEditor: React.FC<JSONEditorProps> = ({
3133
hasJSONChanges,
3234
setHasJSONChanges,
3335
disableCollectionNameChange = false,
36+
additionalProperties,
37+
setAdditionalProperties,
3438
}) => {
3539
const [editorValue, setEditorValue] = useState<string>(
3640
JSON.stringify(value, null, 2)
@@ -136,6 +140,17 @@ const JSONEditor: React.FC<JSONEditorProps> = ({
136140
const validateSchema = ajv.compile(modifiedSchema);
137141

138142
const isValid = validateSchema(parsedValue);
143+
// Extract additional properties manually when strictSchema is false
144+
if (!strictSchema && typeof parsedValue === 'object') {
145+
const schemaProperties = Object.keys(modifiedSchema.properties || {});
146+
const userProperties = Object.keys(parsedValue);
147+
const extraProps = userProperties.filter(
148+
(prop) => !schemaProperties.includes(prop)
149+
);
150+
151+
setAdditionalProperties(extraProps.length > 0 ? extraProps : null);
152+
}
153+
139154
if (!isValid) {
140155
setSchemaErrors(
141156
validateSchema.errors?.map((err) =>

0 commit comments

Comments
 (0)