Skip to content

Commit 3dc39ca

Browse files
authored
Merge pull request #166 from workfloworchestrator/2102-default-values
2102 default values
2 parents b020c20 + f71cd2d commit 3dc39ca

File tree

7 files changed

+106
-35
lines changed

7 files changed

+106
-35
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'pydantic-forms': patch
3+
---
4+
5+
Adds default value for required array fields. Disables descendants of disabled fields

frontend/packages/pydantic-forms/src/components/fields/ArrayField.tsx

Lines changed: 40 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@ import { useFieldArray } from 'react-hook-form';
44
import { usePydanticFormContext } from '@/core';
55
import { fieldToComponentMatcher } from '@/core/helper';
66
import { PydanticFormElementProps } from '@/types';
7-
import { itemizeArrayItem } from '@/utils';
7+
import { disableField, itemizeArrayItem } from '@/utils';
88

99
import { RenderFields } from '../render';
1010

1111
export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
1212
const { rhf, config } = usePydanticFormContext();
1313

14+
const disabled = pydanticFormField.attributes?.disabled || false;
1415
const { control } = rhf;
1516
const { id: arrayName, arrayItem } = pydanticFormField;
1617
const { fields, append, remove } = useFieldArray({
@@ -28,7 +29,12 @@ export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
2829
);
2930

3031
const renderField = (field: Record<'id', string>, index: number) => {
31-
const arrayItemField = itemizeArrayItem(index, arrayItem, arrayName);
32+
const itemizedField = itemizeArrayItem(index, arrayItem, arrayName);
33+
// We have decided - for now - on the convention that all descendants of disabled fields will be disabled as well
34+
// so we will not displaying any interactive elements inside a disabled element
35+
const arrayItemField = disabled
36+
? disableField(itemizedField)
37+
: itemizedField;
3238

3339
return (
3440
<div
@@ -49,15 +55,16 @@ export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
4955
]}
5056
extraTriggerFields={[arrayName]}
5157
/>
52-
{(!minItems || (minItems && fields.length > minItems)) && (
53-
<span
54-
onClick={() => {
55-
remove(index);
56-
}}
57-
>
58-
-
59-
</span>
60-
)}
58+
{(!minItems || (minItems && fields.length > minItems)) &&
59+
!disabled && (
60+
<span
61+
onClick={() => {
62+
remove(index);
63+
}}
64+
>
65+
-
66+
</span>
67+
)}
6168
</div>
6269
);
6370
};
@@ -66,7 +73,7 @@ export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
6673
<div
6774
data-testid={arrayName}
6875
style={{
69-
border: 'thin solid green',
76+
border: '1px solid #ccc',
7077
padding: '1rem',
7178
marginTop: '16px',
7279
display: 'flex',
@@ -75,26 +82,27 @@ export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
7582
}}
7683
>
7784
{fields.map(renderField)}
78-
{(!maxItems || (maxItems && fields.length < maxItems)) && (
79-
<div
80-
onClick={() => {
81-
append({
82-
[arrayName]: arrayItem.default,
83-
});
84-
}}
85-
style={{
86-
cursor: 'pointer',
87-
fontSize: '32px',
88-
display: 'flex',
89-
justifyContent: 'end',
90-
marginTop: '8px',
91-
marginBottom: '8px',
92-
padding: '16px',
93-
}}
94-
>
95-
+
96-
</div>
97-
)}
85+
{(!maxItems || (maxItems && fields.length < maxItems)) &&
86+
!disabled && (
87+
<div
88+
onClick={() => {
89+
append({
90+
[arrayName]: arrayItem.default,
91+
});
92+
}}
93+
style={{
94+
cursor: 'pointer',
95+
fontSize: '32px',
96+
display: 'flex',
97+
justifyContent: 'end',
98+
marginTop: '8px',
99+
marginBottom: '8px',
100+
padding: '16px',
101+
}}
102+
>
103+
+
104+
</div>
105+
)}
98106
</div>
99107
);
100108
};

frontend/packages/pydantic-forms/src/components/fields/ObjectField.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,30 @@ import React from 'react';
33
import { usePydanticFormContext } from '@/core';
44
import { getPydanticFormComponents } from '@/core/helper';
55
import { PydanticFormElementProps } from '@/types';
6+
import { disableField } from '@/utils';
67

78
import { RenderFields } from '../render';
89

910
export const ObjectField = ({
1011
pydanticFormField,
1112
}: PydanticFormElementProps) => {
1213
const { config } = usePydanticFormContext();
14+
const disabled = pydanticFormField.attributes?.disabled || false;
1315
const components = getPydanticFormComponents(
1416
pydanticFormField.properties || {},
1517
config?.componentMatcherExtender,
1618
);
1719

20+
// We have decided - for now - on the convention that all descendants of disabled fields will be disabled as well
21+
// so we will not displaying any interactive elements inside a disabled element
22+
if (disabled) {
23+
components.forEach((component) => {
24+
component.pydanticFormField = disableField(
25+
component.pydanticFormField,
26+
);
27+
});
28+
}
29+
1830
return (
1931
<div
2032
data-testid={pydanticFormField.id}

frontend/packages/pydantic-forms/src/core/helper.spec.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -714,8 +714,8 @@ describe('getFormValuesFromFieldOrLabels', () => {
714714
});
715715
});
716716

717-
it('Returns empty object if arrayItem and array both have no default values', () => {
718-
// When an array fields has no default value the default value should be taken from the arrayItem
717+
it('Returns empty object if arrayItem and array both have no default values and the array is not required', () => {
718+
// When an array field has no default value the default value and the arrayItem doesn't either we assume an empty array
719719
const properties: Properties = {
720720
test: getMockPydanticFormField({
721721
id: 'test',
@@ -724,11 +724,28 @@ describe('getFormValuesFromFieldOrLabels', () => {
724724
id: 'nestedField',
725725
type: PydanticFormFieldType.STRING,
726726
}),
727+
required: false,
727728
}),
728729
};
729730
expect(getFormValuesFromFieldOrLabels(properties)).toEqual({});
730731
});
731-
732+
it('Returns empty array if arrayItem and array both have no default values and the array is required', () => {
733+
// When an array field has no default value the default value and the arrayItem doesn't either we assume an empty array
734+
const properties: Properties = {
735+
test: getMockPydanticFormField({
736+
id: 'test',
737+
type: PydanticFormFieldType.ARRAY,
738+
arrayItem: getMockPydanticFormField({
739+
id: 'nestedField',
740+
type: PydanticFormFieldType.STRING,
741+
}),
742+
required: true,
743+
}),
744+
};
745+
expect(getFormValuesFromFieldOrLabels(properties)).toEqual({
746+
test: [],
747+
});
748+
});
732749
it('Returns empty object if object field and properties both have no default values', () => {
733750
// When an array fields has no default value the default value should be taken from the arrayItem
734751
const properties: Properties = {

frontend/packages/pydantic-forms/src/core/helper.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,10 +385,16 @@ export const getFormValuesFromFieldOrLabels = (
385385
labelData,
386386
componentMatcherExtender,
387387
);
388+
388389
if (objectHasProperties(arrayItemDefault)) {
389390
fieldValues[pydanticFormField.id] = [
390391
arrayItemDefault[arrayItem.id],
391392
];
393+
} else if (pydanticFormField.required) {
394+
// This is somewhat of a special case.
395+
// It deals with the situation where an array is marked required but has no default value.
396+
// Not setting the value here would require a user to select and then unselect an item if they want to send an empty array
397+
fieldValues[pydanticFormField.id] = [];
392398
}
393399
}
394400
} else if (hasDefaultValue(defaultFieldValue)) {

frontend/packages/pydantic-forms/src/utils.spec.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { FieldValues } from 'react-hook-form';
33
import { getMockPydanticFormField } from './core/helper.spec';
44
import { PydanticFormFieldType } from './types';
55
import {
6+
disableField,
67
getFormFieldIdWithPath,
78
getFormFieldValue,
89
insertItemAtIndex,
@@ -374,3 +375,13 @@ describe('itemizeArrayItem', () => {
374375
}
375376
});
376377
});
378+
379+
describe('disableField', () => {
380+
it('Disables the field by setting the disabled attribute to true', () => {
381+
const field = getMockPydanticFormField({
382+
attributes: { disabled: false },
383+
});
384+
const disabledField = disableField(field);
385+
expect(disabledField.attributes?.disabled).toBe(true);
386+
});
387+
});

frontend/packages/pydantic-forms/src/utils.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,18 @@ export const itemizeArrayItem = (
4343
};
4444
};
4545

46+
export const disableField = (
47+
pydanticFormField: PydanticFormField,
48+
): PydanticFormField => {
49+
return {
50+
...pydanticFormField,
51+
attributes: {
52+
...pydanticFormField.attributes,
53+
disabled: true,
54+
},
55+
};
56+
};
57+
4658
/**
4759
* Determines how many parts to slice from the PydanticFormField's id.
4860
* If the last segment is a number we conclude it's an array item and it returns 2 (to slice off the index and the field name).

0 commit comments

Comments
 (0)