Skip to content

Commit

Permalink
4778 multi select field front implement multi select type (twentyhq#4887
Browse files Browse the repository at this point in the history
)
  • Loading branch information
martmull authored Apr 11, 2024
1 parent aecf878 commit a7fcc5d
Show file tree
Hide file tree
Showing 42 changed files with 695 additions and 251 deletions.
11 changes: 0 additions & 11 deletions packages/twenty-front/src/modules/auth/services/AuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@ import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';

const logger = loggerLink(() => 'Twenty-Refresh');

/**
* Renew token mutation with custom apollo client
* @param uri string | UriFunction | undefined
* @param refreshToken string
* @returns RenewTokenMutation
*/
const renewTokenMutation = async (
uri: string | UriFunction | undefined,
refreshToken: string,
Expand Down Expand Up @@ -54,11 +48,6 @@ const renewTokenMutation = async (
return data;
};

/**
* Renew token and update cookie storage
* @param uri string | UriFunction | undefined
* @returns TokenPair
*/
export const renewToken = async (
uri: string | UriFunction | undefined,
tokenPair: AuthTokenPair | undefined | null,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { v4 } from 'uuid';

import { FieldMetadataOption } from '@/object-metadata/types/FieldMetadataOption.ts';
import { Field } from '~/generated/graphql';
import { FieldMetadataType } from '~/generated-metadata/graphql';

import { FieldMetadataItem } from '../types/FieldMetadataItem';
import { FieldMetadataOption } from '../types/FieldMetadataOption';
import { formatFieldMetadataItemInput } from '../utils/formatFieldMetadataItemInput';

import { useCreateOneFieldMetadataItem } from './useCreateOneFieldMetadataItem';
Expand All @@ -17,22 +16,22 @@ export const useFieldMetadataItem = () => {
const { deleteOneFieldMetadataItem } = useDeleteOneFieldMetadataItem();

const createMetadataField = (
input: Pick<Field, 'label' | 'icon' | 'description' | 'defaultValue'> & {
defaultValue?: unknown;
input: Pick<
Field,
'label' | 'icon' | 'description' | 'defaultValue' | 'type' | 'options'
> & {
objectMetadataId: string;
options?: Omit<FieldMetadataOption, 'id'>[];
type: FieldMetadataType;
},
) => {
const formatedInput = formatFieldMetadataItemInput(input);
const formattedInput = formatFieldMetadataItemInput(input);
const defaultValue = input.defaultValue
? typeof input.defaultValue == 'string'
? `'${input.defaultValue}'`
: input.defaultValue
: formatedInput.defaultValue ?? undefined;
: formattedInput.defaultValue ?? undefined;

return createOneFieldMetadataItem({
...formatedInput,
...formattedInput,
defaultValue,
objectMetadataId: input.objectMetadataId,
type: input.type,
Expand All @@ -42,17 +41,21 @@ export const useFieldMetadataItem = () => {
const editMetadataField = (
input: Pick<
Field,
'id' | 'label' | 'icon' | 'description' | 'defaultValue'
> & {
options?: FieldMetadataOption[];
},
| 'id'
| 'label'
| 'icon'
| 'description'
| 'defaultValue'
| 'type'
| 'options'
>,
) => {
const formatedInput = formatFieldMetadataItemInput(input);
const formattedInput = formatFieldMetadataItemInput(input);
const defaultValue = input.defaultValue
? typeof input.defaultValue == 'string'
? `'${input.defaultValue}'`
: input.defaultValue
: formatedInput.defaultValue ?? undefined;
: formattedInput.defaultValue ?? undefined;

return updateOneFieldMetadataItem({
fieldMetadataIdToUpdate: input.id,
Expand All @@ -61,7 +64,7 @@ export const useFieldMetadataItem = () => {
defaultValue,
// In Edit mode, all options need an id,
// so we generate an id for newly created options.
options: input.options?.map((option) =>
options: input.options?.map((option: FieldMetadataOption) =>
option.id ? option : { ...option, id: v4() },
),
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { FieldMetadataType } from '~/generated-metadata/graphql.ts';

import {
formatFieldMetadataItemInput,
getOptionValueFromLabel,
Expand Down Expand Up @@ -46,6 +48,7 @@ describe('formatFieldMetadataItemInput', () => {
const input = {
label: 'Example Label',
icon: 'example-icon',
type: FieldMetadataType.Select,
description: 'Example description',
options: [
{ id: '1', label: 'Option 1', color: 'red' as const, isDefault: true },
Expand Down Expand Up @@ -86,6 +89,70 @@ describe('formatFieldMetadataItemInput', () => {
const input = {
label: 'Example Label',
icon: 'example-icon',
type: FieldMetadataType.Select,
description: 'Example description',
};

const expected = {
description: 'Example description',
icon: 'example-icon',
label: 'Example Label',
name: 'exampleLabel',
options: undefined,
defaultValue: undefined,
};

const result = formatFieldMetadataItemInput(input);

expect(result).toEqual(expected);
});

it('should format the field metadata item multi select input correctly', () => {
const input = {
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 },
],
};

const expected = {
description: 'Example description',
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',
},
],
defaultValue: ["'OPTION_1'", "'OPTION_2'"],
};

const result = formatFieldMetadataItemInput(input);

expect(result).toEqual(expected);
});

it('should handle multi select input without options', () => {
const input = {
label: 'Example Label',
icon: 'example-icon',
type: FieldMetadataType.MultiSelect,
description: 'Example description',
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import toCamelCase from 'lodash.camelcase';
import toSnakeCase from 'lodash.snakecase';

import { Field } from '~/generated-metadata/graphql';
import { Field, FieldMetadataType } from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined.ts';

import { FieldMetadataOption } from '../types/FieldMetadataOption';

Expand All @@ -20,20 +21,36 @@ export const getOptionValueFromLabel = (label: string) => {
};

export const formatFieldMetadataItemInput = (
input: Pick<Field, 'label' | 'icon' | 'description' | 'defaultValue'> & {
options?: FieldMetadataOption[];
},
input: Pick<
Field,
'label' | 'icon' | 'description' | 'defaultValue' | 'type' | 'options'
>,
) => {
const defaultOption = input.options?.find((option) => option.isDefault);
const options = input.options as FieldMetadataOption[];
let defaultValue = input.defaultValue;
if (input.type === FieldMetadataType.MultiSelect) {
const defaultOptions = options?.filter((option) => option.isDefault);
if (isDefined(defaultOptions)) {
defaultValue = defaultOptions.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 (input.options !== undefined) {
if (options !== undefined) {
// Compute the values based on the label
const values = input.options.map((option) =>
const values = options.map((option) =>
getOptionValueFromLabel(option.label),
);

if (new Set(values).size !== input.options.length) {
if (new Set(values).size !== options.length) {
throw new Error(
`Options must have unique values, but contains the following duplicates ${values.join(
', ',
Expand All @@ -43,14 +60,12 @@ export const formatFieldMetadataItemInput = (
}

return {
defaultValue: defaultOption
? `'${getOptionValueFromLabel(defaultOption.label)}'`
: input.defaultValue,
defaultValue,
description: input.description?.trim() ?? null,
icon: input.icon,
label: input.label.trim(),
name: toCamelCase(input.label.trim()),
options: input.options?.map((option, index) => ({
options: options?.map((option, index) => ({
color: option.color,
id: option.id,
label: option.label.trim(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const formatFieldMetadataItemsAsFilterDefinitions = ({
FieldMetadataType.Address,
FieldMetadataType.Relation,
FieldMetadataType.Select,
FieldMetadataType.MultiSelect,
FieldMetadataType.Currency,
].includes(field.type)
) {
Expand Down Expand Up @@ -76,6 +77,8 @@ export const getFilterTypeFromFieldType = (fieldType: FieldMetadataType) => {
return 'RELATION';
case FieldMetadataType.Select:
return 'SELECT';
case FieldMetadataType.MultiSelect:
return 'MULTI_SELECT';
case FieldMetadataType.Address:
return 'ADDRESS';
default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import { formatFieldMetadataItemInput } from './formatFieldMetadataItemInput';

export type FormatRelationMetadataInputParams = {
relationType: RelationType;
field: Pick<Field, 'label' | 'icon' | 'description'>;
field: Pick<Field, 'label' | 'icon' | 'description' | 'type'>;
objectMetadataId: string;
connect: {
field: Pick<Field, 'label' | 'icon'>;
field: Pick<Field, 'label' | 'icon' | 'type'>;
objectMetadataId: string;
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const mapFieldMetadataToGraphQLQuery = ({
'BOOLEAN',
'RATING',
'SELECT',
'MULTI_SELECT',
'POSITION',
'RAW_JSON',
] as FieldMetadataType[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,20 @@ export const getRecordFromRecordNode = <T extends ObjectRecord>({
return [fieldName, value];
}

if (typeof value === 'object' && isDefined(value.edges)) {
return [
fieldName,
getRecordsFromRecordConnection({ recordConnection: value }),
];
if (Array.isArray(value)) {
return [fieldName, value];
}

if (typeof value === 'object' && !isDefined(value.edges)) {
return [fieldName, getRecordFromRecordNode<T>({ recordNode: value })];
if (typeof value !== 'object') {
return [fieldName, value];
}

return [fieldName, value];
return isDefined(value.edges)
? [
fieldName,
getRecordsFromRecordConnection({ recordConnection: value }),
]
: [fieldName, getRecordFromRecordNode<T>({ recordNode: value })];
}),
),
id: recordNode.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ import { getNodeTypename } from '@/object-record/cache/utils/getNodeTypename';
import { getObjectTypename } from '@/object-record/cache/utils/getObjectTypename';
import { getRecordConnectionFromRecords } from '@/object-record/cache/utils/getRecordConnectionFromRecords';
import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { FieldMetadataType } from '~/generated-metadata/graphql';
import {
FieldMetadataType,
RelationDefinitionType,
} from '~/generated-metadata/graphql';
import { isDefined } from '~/utils/isDefined';
import { lowerAndCapitalize } from '~/utils/string/lowerAndCapitalize';

Expand Down Expand Up @@ -65,20 +68,24 @@ export const getRecordNodeFromRecord = <T extends ObjectRecord>({
return undefined;
}

if (Array.isArray(value)) {
const objectMetadataItem = objectMetadataItems.find(
(objectMetadataItem) => objectMetadataItem.namePlural === fieldName,
if (
field.type === FieldMetadataType.Relation &&
field.relationDefinition?.direction ===
RelationDefinitionType.OneToMany
) {
const oneToManyObjectMetadataItem = objectMetadataItems.find(
(item) => item.namePlural === fieldName,
);

if (!objectMetadataItem) {
if (!oneToManyObjectMetadataItem) {
return undefined;
}

return [
fieldName,
getRecordConnectionFromRecords({
objectMetadataItems,
objectMetadataItem: objectMetadataItem,
objectMetadataItem: oneToManyObjectMetadataItem,
records: value as ObjectRecord[],
queryFields:
queryFields?.[fieldName] === true ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ export type FilterType =
| 'LINK'
| 'RELATION'
| 'ADDRESS'
| 'SELECT';
| 'SELECT'
| 'MULTI_SELECT';
Loading

0 comments on commit a7fcc5d

Please sign in to comment.