Fix display empty value if boolean instead of false on show page (#4468)

* default value boolean fixed

* fixed creation, fixed updating a value to false

* fixed default value for default value if boolean

* fixed tests

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
This commit is contained in:
brendanlaschke
2024-03-30 11:38:08 +01:00
committed by GitHub
parent 1d351a29b8
commit da8f1b0a66
11 changed files with 136 additions and 15 deletions

View File

@ -17,7 +17,7 @@ export const useFieldMetadataItem = () => {
const { deleteOneFieldMetadataItem } = useDeleteOneFieldMetadataItem(); const { deleteOneFieldMetadataItem } = useDeleteOneFieldMetadataItem();
const createMetadataField = ( const createMetadataField = (
input: Pick<Field, 'label' | 'icon' | 'description'> & { input: Pick<Field, 'label' | 'icon' | 'description' | 'defaultValue'> & {
defaultValue?: unknown; defaultValue?: unknown;
objectMetadataId: string; objectMetadataId: string;
options?: Omit<FieldMetadataOption, 'id'>[]; options?: Omit<FieldMetadataOption, 'id'>[];
@ -26,7 +26,9 @@ export const useFieldMetadataItem = () => {
) => { ) => {
const formatedInput = formatFieldMetadataItemInput(input); const formatedInput = formatFieldMetadataItemInput(input);
const defaultValue = input.defaultValue const defaultValue = input.defaultValue
? `'${input.defaultValue}'` ? typeof input.defaultValue == 'string'
? `'${input.defaultValue}'`
: input.defaultValue
: formatedInput.defaultValue ?? undefined; : formatedInput.defaultValue ?? undefined;
return createOneFieldMetadataItem({ return createOneFieldMetadataItem({
@ -38,14 +40,25 @@ export const useFieldMetadataItem = () => {
}; };
const editMetadataField = ( const editMetadataField = (
input: Pick<Field, 'id' | 'label' | 'icon' | 'description'> & { input: Pick<
Field,
'id' | 'label' | 'icon' | 'description' | 'defaultValue'
> & {
options?: FieldMetadataOption[]; options?: FieldMetadataOption[];
}, },
) => ) => {
updateOneFieldMetadataItem({ const formatedInput = formatFieldMetadataItemInput(input);
const defaultValue = input.defaultValue
? typeof input.defaultValue == 'string'
? `'${input.defaultValue}'`
: input.defaultValue
: formatedInput.defaultValue ?? undefined;
return updateOneFieldMetadataItem({
fieldMetadataIdToUpdate: input.id, fieldMetadataIdToUpdate: input.id,
updatePayload: formatFieldMetadataItemInput({ updatePayload: formatFieldMetadataItemInput({
...input, ...input,
defaultValue,
// In Edit mode, all options need an id, // In Edit mode, all options need an id,
// so we generate an id for newly created options. // so we generate an id for newly created options.
options: input.options?.map((option) => options: input.options?.map((option) =>
@ -53,6 +66,7 @@ export const useFieldMetadataItem = () => {
), ),
}), }),
}); });
};
const activateMetadataField = (metadataField: FieldMetadataItem) => const activateMetadataField = (metadataField: FieldMetadataItem) =>
updateOneFieldMetadataItem({ updateOneFieldMetadataItem({

View File

@ -45,7 +45,7 @@ export const formatFieldMetadataItemInput = (
return { return {
defaultValue: defaultOption defaultValue: defaultOption
? `'${getOptionValueFromLabel(defaultOption.label)}'` ? `'${getOptionValueFromLabel(defaultOption.label)}'`
: undefined, : input.defaultValue,
description: input.description?.trim() ?? null, description: input.description?.trim() ?? null,
icon: input.icon, icon: input.icon,
label: input.label.trim(), label: input.label.trim(),

View File

@ -0,0 +1,60 @@
import styled from '@emotion/styled';
import { IconCheck, IconX } from '@/ui/display/icon';
import { Select } from '@/ui/input/components/Select';
import { CardContent } from '@/ui/layout/card/components/CardContent';
type SettingsDataModelDefaultValueFormProps = {
className?: string;
disabled?: boolean;
onChange?: (defaultValue: SettingsDataModelDefaultValue) => void;
value?: SettingsDataModelDefaultValue;
};
export type SettingsDataModelDefaultValue = any;
const StyledContainer = styled(CardContent)`
padding-bottom: ${({ theme }) => theme.spacing(3.5)};
`;
const StyledLabel = styled.span`
color: ${({ theme }) => theme.font.color.light};
display: block;
font-size: ${({ theme }) => theme.font.size.xs};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
margin-bottom: 6px;
margin-top: ${({ theme }) => theme.spacing(1)};
`;
export const SettingsDataModelDefaultValueForm = ({
className,
disabled,
onChange,
value,
}: SettingsDataModelDefaultValueFormProps) => {
return (
<StyledContainer>
<StyledLabel>Default Value</StyledLabel>
<Select
className={className}
fullWidth
disabled={disabled}
dropdownId="object-field-default-value-select"
value={value}
onChange={(value) => onChange?.(value)}
options={[
{
value: true,
label: 'True',
Icon: IconCheck,
},
{
value: false,
label: 'False',
Icon: IconX,
},
]}
/>
</StyledContainer>
);
};

View File

@ -2,6 +2,7 @@ import styled from '@emotion/styled';
import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings'; import { useObjectMetadataItemForSettings } from '@/object-metadata/hooks/useObjectMetadataItemForSettings';
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
import { SettingsDataModelDefaultValueForm } from '@/settings/data-model/components/SettingsDataModelDefaultValue';
import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard'; import { SettingsDataModelPreviewFormCard } from '@/settings/data-model/components/SettingsDataModelPreviewFormCard';
import { import {
SettingsObjectFieldCurrencyForm, SettingsObjectFieldCurrencyForm,
@ -26,6 +27,7 @@ export type SettingsDataModelFieldSettingsFormValues = {
currency: SettingsObjectFieldCurrencyFormValues; currency: SettingsObjectFieldCurrencyFormValues;
relation: SettingsObjectFieldRelationFormValues; relation: SettingsObjectFieldRelationFormValues;
select: SettingsObjectFieldSelectFormValues; select: SettingsObjectFieldSelectFormValues;
defaultValue: any;
}; };
type SettingsDataModelFieldSettingsFormCardProps = { type SettingsDataModelFieldSettingsFormCardProps = {
@ -152,6 +154,13 @@ export const SettingsDataModelFieldSettingsFormCard = ({
onChange({ select: nextSelectValues }) onChange({ select: nextSelectValues })
} }
/> />
) : fieldMetadataItem.type === FieldMetadataType.Boolean ? (
<SettingsDataModelDefaultValueForm
value={values.defaultValue}
onChange={(nextValueDefaultValue) =>
onChange({ defaultValue: nextValueDefaultValue })
}
/>
) : undefined ) : undefined
} }
/> />

View File

@ -6,12 +6,19 @@ import {
} from '@/settings/data-model/constants/SettingsFieldTypeConfigs'; } from '@/settings/data-model/constants/SettingsFieldTypeConfigs';
import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType'; import { SettingsSupportedFieldType } from '@/settings/data-model/types/SettingsSupportedFieldType';
import { Select, SelectOption } from '@/ui/input/components/Select'; import { Select, SelectOption } from '@/ui/input/components/Select';
import { FieldMetadataType } from '~/generated-metadata/graphql';
type SettingsDataModelFieldTypeSelectProps = { type SettingsDataModelFieldTypeSelectProps = {
className?: string; className?: string;
disabled?: boolean; disabled?: boolean;
excludedFieldTypes?: SettingsSupportedFieldType[]; excludedFieldTypes?: SettingsSupportedFieldType[];
onChange?: ({ type }: { type: SettingsSupportedFieldType }) => void; onChange?: ({
type,
defaultValue,
}: {
type: SettingsSupportedFieldType;
defaultValue: any;
}) => void;
value?: SettingsSupportedFieldType; value?: SettingsSupportedFieldType;
}; };
@ -41,7 +48,12 @@ export const SettingsDataModelFieldTypeSelect = ({
disabled={disabled} disabled={disabled}
dropdownId="object-field-type-select" dropdownId="object-field-type-select"
value={value} value={value}
onChange={(value) => onChange?.({ type: value })} onChange={(value) =>
onChange?.({
type: value,
defaultValue: value === FieldMetadataType.Boolean ? false : undefined,
})
}
options={fieldTypeOptions} options={fieldTypeOptions}
/> />
); );

View File

@ -26,6 +26,7 @@ const defaultValues = {
currency: fieldMetadataFormDefaultValues.currency, currency: fieldMetadataFormDefaultValues.currency,
relation: fieldMetadataFormDefaultValues.relation, relation: fieldMetadataFormDefaultValues.relation,
select: fieldMetadataFormDefaultValues.select, select: fieldMetadataFormDefaultValues.select,
defaultValue: fieldMetadataFormDefaultValues.defaultValue,
}; };
const meta: Meta<typeof SettingsDataModelFieldSettingsFormCard> = { const meta: Meta<typeof SettingsDataModelFieldSettingsFormCard> = {
@ -110,6 +111,7 @@ export const WithSelectForm: Story = {
}, },
{ color: 'green', label: '🌏 NGO', value: 'NGO' }, { color: 'green', label: '🌏 NGO', value: 'NGO' },
], ],
defaultValue: undefined,
}, },
}, },
}; };

View File

@ -23,6 +23,7 @@ describe('useFieldMetadataForm', () => {
objectMetadataId: '', objectMetadataId: '',
field: { label: '' }, field: { label: '' },
}, },
defaultValue: null,
select: [ select: [
{ color: 'green', label: 'Option 1', value: expect.any(String) }, { color: 'green', label: 'Option 1', value: expect.any(String) },
], ],

View File

@ -18,6 +18,7 @@ type FormValues = {
description?: string; description?: string;
icon: string; icon: string;
label: string; label: string;
defaultValue: any;
type: SettingsSupportedFieldType; type: SettingsSupportedFieldType;
} & SettingsDataModelFieldSettingsFormValues; } & SettingsDataModelFieldSettingsFormValues;
@ -31,6 +32,7 @@ export const fieldMetadataFormDefaultValues: FormValues = {
objectMetadataId: '', objectMetadataId: '',
field: { label: '' }, field: { label: '' },
}, },
defaultValue: null,
select: [{ color: 'green', label: 'Option 1', value: v4() }], select: [{ color: 'green', label: 'Option 1', value: v4() }],
}; };
@ -38,6 +40,7 @@ const fieldSchema = z.object({
description: z.string().optional(), description: z.string().optional(),
icon: z.string().startsWith('Icon'), icon: z.string().startsWith('Icon'),
label: z.string().min(1), label: z.string().min(1),
defaultValue: z.any(),
}); });
const currencySchema = fieldSchema.merge( const currencySchema = fieldSchema.merge(
@ -124,6 +127,8 @@ export const useFieldMetadataForm = () => {
const [hasCurrencyFormChanged, setHasCurrencyFormChanged] = useState(false); const [hasCurrencyFormChanged, setHasCurrencyFormChanged] = useState(false);
const [hasRelationFormChanged, setHasRelationFormChanged] = useState(false); const [hasRelationFormChanged, setHasRelationFormChanged] = useState(false);
const [hasSelectFormChanged, setHasSelectFormChanged] = useState(false); const [hasSelectFormChanged, setHasSelectFormChanged] = useState(false);
const [hasDefaultValueChanged, setHasDefaultValueFormChanged] =
useState(false);
const [validationResult, setValidationResult] = useState( const [validationResult, setValidationResult] = useState(
schema.safeParse(formValues), schema.safeParse(formValues),
); );
@ -169,12 +174,14 @@ export const useFieldMetadataForm = () => {
currency: initialCurrencyFormValues, currency: initialCurrencyFormValues,
relation: initialRelationFormValues, relation: initialRelationFormValues,
select: initialSelectFormValues, select: initialSelectFormValues,
defaultValue: initalDefaultValue,
...initialFieldFormValues ...initialFieldFormValues
} = initialFormValues; } = initialFormValues;
const { const {
currency: nextCurrencyFormValues, currency: nextCurrencyFormValues,
relation: nextRelationFormValues, relation: nextRelationFormValues,
select: nextSelectFormValues, select: nextSelectFormValues,
defaultValue: nextDefaultValue,
...nextFieldFormValues ...nextFieldFormValues
} = nextFormValues; } = nextFormValues;
@ -193,6 +200,10 @@ export const useFieldMetadataForm = () => {
nextFieldFormValues.type === FieldMetadataType.Select && nextFieldFormValues.type === FieldMetadataType.Select &&
!isDeeplyEqual(initialSelectFormValues, nextSelectFormValues), !isDeeplyEqual(initialSelectFormValues, nextSelectFormValues),
); );
setHasDefaultValueFormChanged(
nextFieldFormValues.type === FieldMetadataType.Boolean &&
!isDeeplyEqual(initalDefaultValue, nextDefaultValue),
);
}; };
return { return {
@ -203,9 +214,11 @@ export const useFieldMetadataForm = () => {
hasFieldFormChanged || hasFieldFormChanged ||
hasCurrencyFormChanged || hasCurrencyFormChanged ||
hasRelationFormChanged || hasRelationFormChanged ||
hasSelectFormChanged, hasSelectFormChanged ||
hasDefaultValueChanged,
hasRelationFormChanged, hasRelationFormChanged,
hasSelectFormChanged, hasSelectFormChanged,
hasDefaultValueChanged,
initForm, initForm,
isInitialized, isInitialized,
isValid: validationResult.success, isValid: validationResult.success,

View File

@ -83,6 +83,7 @@ export const SettingsObjectFieldEdit = () => {
formValues, formValues,
handleFormChange, handleFormChange,
hasFieldFormChanged, hasFieldFormChanged,
hasDefaultValueChanged,
hasFormChanged, hasFormChanged,
hasRelationFormChanged, hasRelationFormChanged,
hasSelectFormChanged, hasSelectFormChanged,
@ -132,6 +133,7 @@ export const SettingsObjectFieldEdit = () => {
objectMetadataId: relationObjectMetadataItem?.id || '', objectMetadataId: relationObjectMetadataItem?.id || '',
type: relationType || RelationMetadataType.OneToMany, type: relationType || RelationMetadataType.OneToMany,
}, },
defaultValue: activeMetadataField.defaultValue,
...(selectOptions?.length ? { select: selectOptions } : {}), ...(selectOptions?.length ? { select: selectOptions } : {}),
}); });
}, [ }, [
@ -170,13 +172,17 @@ export const SettingsObjectFieldEdit = () => {
label: validatedFormValues.relation.field.label, label: validatedFormValues.relation.field.label,
}); });
} }
if (
if (hasFieldFormChanged || hasSelectFormChanged) { hasFieldFormChanged ||
hasSelectFormChanged ||
hasDefaultValueChanged
) {
await editMetadataField({ await editMetadataField({
description: validatedFormValues.description, description: validatedFormValues.description,
icon: validatedFormValues.icon, icon: validatedFormValues.icon,
id: activeMetadataField.id, id: activeMetadataField.id,
label: validatedFormValues.label, label: validatedFormValues.label,
defaultValue: validatedFormValues.defaultValue,
options: options:
validatedFormValues.type === FieldMetadataType.Select validatedFormValues.type === FieldMetadataType.Select
? validatedFormValues.select ? validatedFormValues.select
@ -255,6 +261,7 @@ export const SettingsObjectFieldEdit = () => {
currency: formValues.currency, currency: formValues.currency,
relation: formValues.relation, relation: formValues.relation,
select: formValues.select, select: formValues.select,
defaultValue: formValues.defaultValue,
}} }}
/> />
</Section> </Section>

View File

@ -213,7 +213,7 @@ export const SettingsObjectNewFieldStep2 = () => {
amountMicros: null, amountMicros: null,
currencyCode: validatedFormValues.currency.currencyCode, currencyCode: validatedFormValues.currency.currencyCode,
} }
: undefined, : validatedFormValues.defaultValue,
description: validatedFormValues.description, description: validatedFormValues.description,
icon: validatedFormValues.icon, icon: validatedFormValues.icon,
label: validatedFormValues.label ?? '', label: validatedFormValues.label ?? '',
@ -320,6 +320,7 @@ export const SettingsObjectNewFieldStep2 = () => {
currency: formValues.currency, currency: formValues.currency,
relation: formValues.relation, relation: formValues.relation,
select: formValues.select, select: formValues.select,
defaultValue: formValues.defaultValue,
}} }}
/> />
</Section> </Section>

View File

@ -300,10 +300,12 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
await fieldMetadataRepository.update(id, { await fieldMetadataRepository.update(id, {
...updatableFieldInput, ...updatableFieldInput,
defaultValue: defaultValue:
// Todo: we need to handle default value for all field types. Right now we are only allowing update for SELECt // Todo: we handle default value for all field types.
existingFieldMetadata.type !== FieldMetadataType.SELECT ![FieldMetadataType.SELECT, FieldMetadataType.BOOLEAN].includes(
existingFieldMetadata.type,
)
? existingFieldMetadata.defaultValue ? existingFieldMetadata.defaultValue
: updatableFieldInput.defaultValue : updatableFieldInput.defaultValue !== null
? updatableFieldInput.defaultValue ? updatableFieldInput.defaultValue
: null, : null,
// If the name is updated, the targetColumnMap should be updated as well // If the name is updated, the targetColumnMap should be updated as well