Refactor default value for select (#5343)
In this PR, we are refactoring two things: - leverage field.defaultValue for Select and MultiSelect settings form (instead of option.isDefault) - use quoted string (ex: "'USD'") for string default values to embrace backend format --------- Co-authored-by: Thaïs Guigon <guigon.thais@gmail.com>
This commit is contained in:
@ -1,59 +1,33 @@
|
||||
import { FieldMetadataItemOption } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
import {
|
||||
formatFieldMetadataItemInput,
|
||||
getOptionValueFromLabel,
|
||||
} from '../formatFieldMetadataItemInput';
|
||||
|
||||
describe('getOptionValueFromLabel', () => {
|
||||
it('should return the option value from the label', () => {
|
||||
const label = 'Example Label';
|
||||
const expected = 'EXAMPLE_LABEL';
|
||||
|
||||
const result = getOptionValueFromLabel(label);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle labels with accents', () => {
|
||||
const label = 'Éxàmplè Làbèl';
|
||||
const expected = 'EXAMPLE_LABEL';
|
||||
|
||||
const result = getOptionValueFromLabel(label);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle labels with special characters', () => {
|
||||
const label = 'Example!@#$%^&*() Label';
|
||||
const expected = 'EXAMPLE_LABEL';
|
||||
|
||||
const result = getOptionValueFromLabel(label);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should handle labels with emojis', () => {
|
||||
const label = '📱 Example Label';
|
||||
const expected = 'EXAMPLE_LABEL';
|
||||
|
||||
const result = getOptionValueFromLabel(label);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
import { formatFieldMetadataItemInput } from '../formatFieldMetadataItemInput';
|
||||
|
||||
describe('formatFieldMetadataItemInput', () => {
|
||||
it('should format the field metadata item input correctly', () => {
|
||||
const options: FieldMetadataItemOption[] = [
|
||||
{
|
||||
id: '1',
|
||||
label: 'Option 1',
|
||||
color: 'red' as const,
|
||||
position: 0,
|
||||
value: 'OPTION_1',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Option 2',
|
||||
color: 'blue' as const,
|
||||
position: 1,
|
||||
value: 'OPTION_2',
|
||||
},
|
||||
];
|
||||
const input = {
|
||||
defaultValue: "'OPTION_1'",
|
||||
label: 'Example Label',
|
||||
icon: 'example-icon',
|
||||
type: FieldMetadataType.Select,
|
||||
description: 'Example description',
|
||||
options: [
|
||||
{ id: '1', label: 'Option 1', color: 'red' as const, isDefault: true },
|
||||
{ id: '2', label: 'Option 2', color: 'blue' as const },
|
||||
],
|
||||
options,
|
||||
};
|
||||
|
||||
const expected = {
|
||||
@ -61,22 +35,7 @@ describe('formatFieldMetadataItemInput', () => {
|
||||
icon: 'example-icon',
|
||||
label: 'Example Label',
|
||||
name: 'exampleLabel',
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
label: 'Option 1',
|
||||
color: 'red',
|
||||
position: 0,
|
||||
value: 'OPTION_1',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Option 2',
|
||||
color: 'blue',
|
||||
position: 1,
|
||||
value: 'OPTION_2',
|
||||
},
|
||||
],
|
||||
options,
|
||||
defaultValue: "'OPTION_1'",
|
||||
};
|
||||
|
||||
@ -108,15 +67,29 @@ describe('formatFieldMetadataItemInput', () => {
|
||||
});
|
||||
|
||||
it('should format the field metadata item multi select input correctly', () => {
|
||||
const options: FieldMetadataItemOption[] = [
|
||||
{
|
||||
id: '1',
|
||||
label: 'Option 1',
|
||||
color: 'red' as const,
|
||||
position: 0,
|
||||
value: 'OPTION_1',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Option 2',
|
||||
color: 'blue' as const,
|
||||
position: 1,
|
||||
value: 'OPTION_2',
|
||||
},
|
||||
];
|
||||
const input = {
|
||||
defaultValue: ["'OPTION_1'", "'OPTION_2'"],
|
||||
label: 'Example Label',
|
||||
icon: 'example-icon',
|
||||
type: FieldMetadataType.MultiSelect,
|
||||
description: 'Example description',
|
||||
options: [
|
||||
{ id: '1', label: 'Option 1', color: 'red' as const, isDefault: true },
|
||||
{ id: '2', label: 'Option 2', color: 'blue' as const, isDefault: true },
|
||||
],
|
||||
options,
|
||||
};
|
||||
|
||||
const expected = {
|
||||
@ -124,22 +97,7 @@ describe('formatFieldMetadataItemInput', () => {
|
||||
icon: 'example-icon',
|
||||
label: 'Example Label',
|
||||
name: 'exampleLabel',
|
||||
options: [
|
||||
{
|
||||
id: '1',
|
||||
label: 'Option 1',
|
||||
color: 'red',
|
||||
position: 0,
|
||||
value: 'OPTION_1',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
label: 'Option 2',
|
||||
color: 'blue',
|
||||
position: 1,
|
||||
value: 'OPTION_2',
|
||||
},
|
||||
],
|
||||
options,
|
||||
defaultValue: ["'OPTION_1'", "'OPTION_2'"],
|
||||
};
|
||||
|
||||
|
||||
@ -1,82 +1,22 @@
|
||||
import toSnakeCase from 'lodash.snakecase';
|
||||
|
||||
import { FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem';
|
||||
import { getDefaultValueForBackend } from '@/object-metadata/utils/getDefaultValueForBackend';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
import { formatMetadataLabelToMetadataNameOrThrows } from '~/pages/settings/data-model/utils/format-metadata-label-to-name.util';
|
||||
import { isDefined } from '~/utils/isDefined';
|
||||
|
||||
import { FieldMetadataOption } from '../types/FieldMetadataOption';
|
||||
|
||||
export const getOptionValueFromLabel = (label: string) => {
|
||||
// Remove accents
|
||||
const unaccentedLabel = label
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, '');
|
||||
// Remove special characters
|
||||
const noSpecialCharactersLabel = unaccentedLabel.replace(
|
||||
/[^a-zA-Z0-9 ]/g,
|
||||
'',
|
||||
);
|
||||
|
||||
return toSnakeCase(noSpecialCharactersLabel).toUpperCase();
|
||||
};
|
||||
|
||||
export const formatFieldMetadataItemInput = (
|
||||
input: Partial<
|
||||
Pick<
|
||||
FieldMetadataItem,
|
||||
'type' | 'label' | 'defaultValue' | 'icon' | 'description'
|
||||
'type' | 'label' | 'defaultValue' | 'icon' | 'description' | 'options'
|
||||
>
|
||||
> & { options?: FieldMetadataOption[] },
|
||||
>,
|
||||
) => {
|
||||
const options = input.options as FieldMetadataOption[] | undefined;
|
||||
let defaultValue = input.defaultValue;
|
||||
if (input.type === FieldMetadataType.MultiSelect) {
|
||||
defaultValue = options
|
||||
?.filter((option) => option.isDefault)
|
||||
?.map((defaultOption) => getOptionValueFromLabel(defaultOption.label));
|
||||
}
|
||||
if (input.type === FieldMetadataType.Select) {
|
||||
const defaultOption = options?.find((option) => option.isDefault);
|
||||
defaultValue = isDefined(defaultOption)
|
||||
? getOptionValueFromLabel(defaultOption.label)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
// Check if options has unique values
|
||||
if (options !== undefined) {
|
||||
// Compute the values based on the label
|
||||
const values = options.map((option) =>
|
||||
getOptionValueFromLabel(option.label),
|
||||
);
|
||||
|
||||
if (new Set(values).size !== options.length) {
|
||||
throw new Error(
|
||||
`Options must have unique values, but contains the following duplicates ${values.join(
|
||||
', ',
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const label = input.label?.trim();
|
||||
|
||||
return {
|
||||
defaultValue:
|
||||
isDefined(defaultValue) && input.type
|
||||
? getDefaultValueForBackend(defaultValue, input.type)
|
||||
: undefined,
|
||||
defaultValue: input.defaultValue,
|
||||
description: input.description?.trim() ?? null,
|
||||
icon: input.icon,
|
||||
label,
|
||||
name: label ? formatMetadataLabelToMetadataNameOrThrows(label) : undefined,
|
||||
options: options?.map((option, index) => ({
|
||||
color: option.color,
|
||||
id: option.id,
|
||||
label: option.label.trim(),
|
||||
position: index,
|
||||
value: getOptionValueFromLabel(option.label),
|
||||
})),
|
||||
options: input.options,
|
||||
};
|
||||
};
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
import { CurrencyCode } from '@/object-record/record-field/types/CurrencyCode';
|
||||
import { FieldCurrencyValue } from '@/object-record/record-field/types/FieldMetadata';
|
||||
import { FieldMetadataType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const getDefaultValueForBackend = (
|
||||
defaultValue: any,
|
||||
fieldMetadataType: FieldMetadataType,
|
||||
) => {
|
||||
if (fieldMetadataType === FieldMetadataType.Currency) {
|
||||
const currencyDefaultValue = defaultValue as FieldCurrencyValue;
|
||||
return {
|
||||
amountMicros: currencyDefaultValue.amountMicros,
|
||||
currencyCode: `'${currencyDefaultValue.currencyCode}'` as CurrencyCode,
|
||||
} satisfies FieldCurrencyValue;
|
||||
} else if (fieldMetadataType === FieldMetadataType.Select) {
|
||||
return defaultValue ? `'${defaultValue}'` : null;
|
||||
} else if (fieldMetadataType === FieldMetadataType.MultiSelect) {
|
||||
return defaultValue.map((value: string) => `'${value}'`);
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
};
|
||||
Reference in New Issue
Block a user