Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions frontend/.changeset/shaky-sites-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'pydantic-forms': patch
---

Adds default value for required array fields. Disables descendants of disabled fields
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import { useFieldArray } from 'react-hook-form';
import { usePydanticFormContext } from '@/core';
import { fieldToComponentMatcher } from '@/core/helper';
import { PydanticFormElementProps } from '@/types';
import { itemizeArrayItem } from '@/utils';
import { disableField, itemizeArrayItem } from '@/utils';

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

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

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

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

return (
<div
Expand All @@ -49,15 +55,16 @@ export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
]}
extraTriggerFields={[arrayName]}
/>
{(!minItems || (minItems && fields.length > minItems)) && (
<span
onClick={() => {
remove(index);
}}
>
-
</span>
)}
{(!minItems || (minItems && fields.length > minItems)) &&
!disabled && (
<span
onClick={() => {
remove(index);
}}
>
-
</span>
)}
</div>
);
};
Expand All @@ -66,7 +73,7 @@ export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
<div
data-testid={arrayName}
style={{
border: 'thin solid green',
border: '1px solid #ccc',
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any ideas how to make the coloring configurable same as for the WFO UI?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. The design - if any - of the base version on the forms is still up for debate

padding: '1rem',
marginTop: '16px',
display: 'flex',
Expand All @@ -75,26 +82,27 @@ export const ArrayField = ({ pydanticFormField }: PydanticFormElementProps) => {
}}
>
{fields.map(renderField)}
{(!maxItems || (maxItems && fields.length < maxItems)) && (
<div
onClick={() => {
append({
[arrayName]: arrayItem.default,
});
}}
style={{
cursor: 'pointer',
fontSize: '32px',
display: 'flex',
justifyContent: 'end',
marginTop: '8px',
marginBottom: '8px',
padding: '16px',
}}
>
+
</div>
)}
{(!maxItems || (maxItems && fields.length < maxItems)) &&
!disabled && (
<div
onClick={() => {
append({
[arrayName]: arrayItem.default,
});
}}
style={{
cursor: 'pointer',
fontSize: '32px',
display: 'flex',
justifyContent: 'end',
marginTop: '8px',
marginBottom: '8px',
padding: '16px',
}}
>
+
</div>
)}
</div>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,30 @@ import React from 'react';
import { usePydanticFormContext } from '@/core';
import { getPydanticFormComponents } from '@/core/helper';
import { PydanticFormElementProps } from '@/types';
import { disableField } from '@/utils';

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

export const ObjectField = ({
pydanticFormField,
}: PydanticFormElementProps) => {
const { config } = usePydanticFormContext();
const disabled = pydanticFormField.attributes?.disabled || false;
const components = getPydanticFormComponents(
pydanticFormField.properties || {},
config?.componentMatcherExtender,
);

// We have decided - for now - on the convention that all descendants of disabled fields will be disabled as well
// so we will not displaying any interactive elements inside a disabled element
if (disabled) {
components.forEach((component) => {
component.pydanticFormField = disableField(
component.pydanticFormField,
);
});
}

return (
<div
data-testid={pydanticFormField.id}
Expand Down
23 changes: 20 additions & 3 deletions frontend/packages/pydantic-forms/src/core/helper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,8 +714,8 @@ describe('getFormValuesFromFieldOrLabels', () => {
});
});

it('Returns empty object if arrayItem and array both have no default values', () => {
// When an array fields has no default value the default value should be taken from the arrayItem
it('Returns empty object if arrayItem and array both have no default values and the array is not required', () => {
// When an array field has no default value the default value and the arrayItem doesn't either we assume an empty array
const properties: Properties = {
test: getMockPydanticFormField({
id: 'test',
Expand All @@ -724,11 +724,28 @@ describe('getFormValuesFromFieldOrLabels', () => {
id: 'nestedField',
type: PydanticFormFieldType.STRING,
}),
required: false,
}),
};
expect(getFormValuesFromFieldOrLabels(properties)).toEqual({});
});

it('Returns empty array if arrayItem and array both have no default values and the array is required', () => {
// When an array field has no default value the default value and the arrayItem doesn't either we assume an empty array
const properties: Properties = {
test: getMockPydanticFormField({
id: 'test',
type: PydanticFormFieldType.ARRAY,
arrayItem: getMockPydanticFormField({
id: 'nestedField',
type: PydanticFormFieldType.STRING,
}),
required: true,
}),
};
expect(getFormValuesFromFieldOrLabels(properties)).toEqual({
test: [],
});
});
it('Returns empty object if object field and properties both have no default values', () => {
// When an array fields has no default value the default value should be taken from the arrayItem
const properties: Properties = {
Expand Down
6 changes: 6 additions & 0 deletions frontend/packages/pydantic-forms/src/core/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,10 +385,16 @@ export const getFormValuesFromFieldOrLabels = (
labelData,
componentMatcherExtender,
);

if (objectHasProperties(arrayItemDefault)) {
fieldValues[pydanticFormField.id] = [
arrayItemDefault[arrayItem.id],
];
} else if (pydanticFormField.required) {
// This is somewhat of a special case.
// It deals with the situation where an array is marked required but has no default value.
// Not setting the value here would require a user to select and then unselect an item if they want to send an empty array
fieldValues[pydanticFormField.id] = [];
}
}
} else if (hasDefaultValue(defaultFieldValue)) {
Expand Down
11 changes: 11 additions & 0 deletions frontend/packages/pydantic-forms/src/utils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { FieldValues } from 'react-hook-form';
import { getMockPydanticFormField } from './core/helper.spec';
import { PydanticFormFieldType } from './types';
import {
disableField,
getFormFieldIdWithPath,
getFormFieldValue,
insertItemAtIndex,
Expand Down Expand Up @@ -374,3 +375,13 @@ describe('itemizeArrayItem', () => {
}
});
});

describe('disableField', () => {
it('Disables the field by setting the disabled attribute to true', () => {
const field = getMockPydanticFormField({
attributes: { disabled: false },
});
const disabledField = disableField(field);
expect(disabledField.attributes?.disabled).toBe(true);
});
});
12 changes: 12 additions & 0 deletions frontend/packages/pydantic-forms/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ export const itemizeArrayItem = (
};
};

export const disableField = (
pydanticFormField: PydanticFormField,
): PydanticFormField => {
return {
...pydanticFormField,
attributes: {
...pydanticFormField.attributes,
disabled: true,
},
};
};

/**
* Determines how many parts to slice from the PydanticFormField's id.
* 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).
Expand Down