From 10fd67ba32a3a5e1581a0ea2a45d5eb13563a50e Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 11 Jan 2024 22:46:43 +0100 Subject: [PATCH] Fix relation creation bug + enable favorite for custom objects (#3392) * Fix relation creation bug * Fix vale CI * Fix comment bug --- .../activities/comment/CommentHeader.tsx | 6 +- .../components/ActivityComments.tsx | 1 + .../activities/hooks/useActivityTargets.ts | 1 + .../activities/tasks/hooks/useTasks.ts | 1 - .../utils/getTargetObjectFilterFieldName.ts | 10 +- .../modules/favorites/hooks/useFavorites.ts | 6 +- .../typeorm-seeds/core/demo/feature-flags.ts | 4 +- .../object-metadata.service.ts | 291 +++++++++++++----- .../user-guide/basics/opportunities.mdx | 2 +- 9 files changed, 223 insertions(+), 99 deletions(-) diff --git a/packages/twenty-front/src/modules/activities/comment/CommentHeader.tsx b/packages/twenty-front/src/modules/activities/comment/CommentHeader.tsx index ff68c5332..a8cbb6b19 100644 --- a/packages/twenty-front/src/modules/activities/comment/CommentHeader.tsx +++ b/packages/twenty-front/src/modules/activities/comment/CommentHeader.tsx @@ -67,8 +67,8 @@ export const CommentHeader = ({ comment, actionBar }: CommentHeaderProps) => { const showDate = beautifiedCreatedAt !== ''; const author = comment.author; - const authorName = author.name.firstName + ' ' + author.name.lastName; - const avatarUrl = author.avatarUrl; + const authorName = author?.name?.firstName + ' ' + author?.name?.lastName; + const avatarUrl = author?.avatarUrl; const commentId = comment.id; return ( @@ -77,7 +77,7 @@ export const CommentHeader = ({ comment, actionBar }: CommentHeaderProps) => { {authorName} diff --git a/packages/twenty-front/src/modules/activities/components/ActivityComments.tsx b/packages/twenty-front/src/modules/activities/components/ActivityComments.tsx index 001c402eb..9d88bcc27 100644 --- a/packages/twenty-front/src/modules/activities/components/ActivityComments.tsx +++ b/packages/twenty-front/src/modules/activities/components/ActivityComments.tsx @@ -86,6 +86,7 @@ export const ActivityComments = ({ createOneComment?.({ id: v4(), authorId: currentWorkspaceMember?.id ?? '', + author: currentWorkspaceMember, activityId: activity?.id ?? '', body: commentText, createdAt: new Date().toISOString(), diff --git a/packages/twenty-front/src/modules/activities/hooks/useActivityTargets.ts b/packages/twenty-front/src/modules/activities/hooks/useActivityTargets.ts index eb8845073..11757793c 100644 --- a/packages/twenty-front/src/modules/activities/hooks/useActivityTargets.ts +++ b/packages/twenty-front/src/modules/activities/hooks/useActivityTargets.ts @@ -20,6 +20,7 @@ export const useActivityTargets = ({ eq: targetableObject.id, }, }, + skip: !targetableObject.id, }); return { diff --git a/packages/twenty-front/src/modules/activities/tasks/hooks/useTasks.ts b/packages/twenty-front/src/modules/activities/tasks/hooks/useTasks.ts index d411b8350..292cec94b 100644 --- a/packages/twenty-front/src/modules/activities/tasks/hooks/useTasks.ts +++ b/packages/twenty-front/src/modules/activities/tasks/hooks/useTasks.ts @@ -24,7 +24,6 @@ export const useTasks = ({ }); const isTargettingObjectRecords = isNonEmptyArray(targetableObjects); - const targetableObjectsFilter = targetableObjects.reduce( (aggregateFilter, targetableObject) => { diff --git a/packages/twenty-front/src/modules/activities/utils/getTargetObjectFilterFieldName.ts b/packages/twenty-front/src/modules/activities/utils/getTargetObjectFilterFieldName.ts index be1256229..6932fbf12 100644 --- a/packages/twenty-front/src/modules/activities/utils/getTargetObjectFilterFieldName.ts +++ b/packages/twenty-front/src/modules/activities/utils/getTargetObjectFilterFieldName.ts @@ -1,15 +1,7 @@ -import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; - export const getActivityTargetObjectFieldIdName = ({ nameSingular, }: { nameSingular: string; }) => { - const isCoreObject = - nameSingular === CoreObjectNameSingular.Company || - nameSingular === CoreObjectNameSingular.Person; - - const objectFieldIdName = `${!isCoreObject ? '_' : ''}${nameSingular}Id`; - - return objectFieldIdName; + return `${nameSingular}Id`; }; diff --git a/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts b/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts index cc98a87bc..f006541bb 100644 --- a/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts +++ b/packages/twenty-front/src/modules/favorites/hooks/useFavorites.ts @@ -91,12 +91,12 @@ export const useFavorites = () => { ]); const createFavorite = ( - targetObject: Record, + targetRecord: Record, targetObjectNameSingular: string, ) => { createOneFavorite({ - [`${targetObjectNameSingular}Id`]: targetObject.id, - [`${targetObjectNameSingular}`]: targetObject, + [`${targetObjectNameSingular}Id`]: targetRecord.id, + [`${targetObjectNameSingular}`]: targetRecord, position: favorites.length + 1, workspaceMemberId: currentWorkspaceMember?.id, }); diff --git a/packages/twenty-server/src/database/typeorm-seeds/core/demo/feature-flags.ts b/packages/twenty-server/src/database/typeorm-seeds/core/demo/feature-flags.ts index 14e223f91..60ed256d0 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/core/demo/feature-flags.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/core/demo/feature-flags.ts @@ -26,12 +26,12 @@ export const seedFeatureFlags = async ( { key: 'IS_RATING_FIELD_TYPE_ENABLED', workspaceId: workspaceId, - value: true, + value: false, }, { key: 'IS_RELATION_FIELD_CARD_ENABLED', workspaceId: workspaceId, - value: true, + value: false, }, ]) .execute(); diff --git a/packages/twenty-server/src/metadata/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/metadata/object-metadata/object-metadata.service.ts index fe90be30c..9cafcb64e 100644 --- a/packages/twenty-server/src/metadata/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/metadata/object-metadata/object-metadata.service.ts @@ -141,88 +141,16 @@ export class ObjectMetadataService extends TypeOrmQueryService { - if (fieldMetadata.type === FieldMetadataType.RELATION) { - acc[fieldMetadata.objectMetadataId] = fieldMetadata; - } - - return acc; - }, - {}, + const { activityTargetObjectMetadata } = + await this.createActivityTargetRelation( + objectMetadataInput.workspaceId, + createdObjectMetadata, ); - await this.relationMetadataRepository.save([ - { - workspaceId: objectMetadataInput.workspaceId, - relationType: RelationMetadataType.ONE_TO_MANY, - fromObjectMetadataId: createdObjectMetadata.id, - toObjectMetadataId: activityTargetObjectMetadata.id, - fromFieldMetadataId: - activityTargetRelationFieldMetadataMap[createdObjectMetadata.id].id, - toFieldMetadataId: - activityTargetRelationFieldMetadataMap[ - activityTargetObjectMetadata.id - ].id, - }, - ]); + const { favoriteObjectMetadata } = await this.createFavoriteRelation( + objectMetadataInput.workspaceId, + createdObjectMetadata, + ); await this.workspaceMigrationService.createCustomMigration( createdObjectMetadata.workspaceId, @@ -256,6 +184,31 @@ export class ObjectMetadataService extends TypeOrmQueryService { + if (fieldMetadata.type === FieldMetadataType.RELATION) { + acc[fieldMetadata.objectMetadataId] = fieldMetadata; + } + + return acc; + }, + {}, + ); + + await this.relationMetadataRepository.save([ + { + workspaceId: workspaceId, + relationType: RelationMetadataType.ONE_TO_MANY, + fromObjectMetadataId: createdObjectMetadata.id, + toObjectMetadataId: activityTargetObjectMetadata.id, + fromFieldMetadataId: + activityTargetRelationFieldMetadataMap[createdObjectMetadata.id].id, + toFieldMetadataId: + activityTargetRelationFieldMetadataMap[ + activityTargetObjectMetadata.id + ].id, + }, + ]); + + return { activityTargetObjectMetadata }; + } + + private async createFavoriteRelation( + workspaceId: string, + createdObjectMetadata: ObjectMetadataEntity, + ) { + const favoriteObjectMetadata = + await this.objectMetadataRepository.findOneByOrFail({ + nameSingular: 'favorite', + workspaceId: workspaceId, + }); + + const favoriteRelationFieldMetadata = + await this.fieldMetadataRepository.save([ + // FROM + { + objectMetadataId: createdObjectMetadata.id, + workspaceId: workspaceId, + isCustom: true, + isActive: true, + type: FieldMetadataType.RELATION, + name: 'favorites', + label: 'Favorites', + targetColumnMap: {}, + description: `Favorites tied to the ${createdObjectMetadata.labelSingular}`, + icon: 'IconHeart', + isNullable: true, + }, + // TO + { + objectMetadataId: favoriteObjectMetadata.id, + workspaceId: workspaceId, + isCustom: true, + isActive: true, + type: FieldMetadataType.RELATION, + name: createdObjectMetadata.nameSingular, + label: createdObjectMetadata.labelSingular, + targetColumnMap: {}, + description: `Favorite ${createdObjectMetadata.labelSingular}`, + icon: 'IconBuildingSkyscraper', + isNullable: true, + }, + // Foreign key + { + objectMetadataId: favoriteObjectMetadata.id, + workspaceId: workspaceId, + isCustom: true, + isActive: true, + type: FieldMetadataType.UUID, + name: `${createdObjectMetadata.nameSingular}Id`, + label: `${createdObjectMetadata.labelSingular} ID (foreign key)`, + targetColumnMap: { + value: `${createdObjectMetadata.targetTableName}Id`, + }, + description: `Favorite ${createdObjectMetadata.labelSingular} id foreign key`, + icon: undefined, + isNullable: true, + isSystem: true, + defaultValue: undefined, + }, + ]); + + const favoriteRelationFieldMetadataMap = + favoriteRelationFieldMetadata.reduce( + (acc, fieldMetadata: FieldMetadataEntity) => { + if (fieldMetadata.type === FieldMetadataType.RELATION) { + acc[fieldMetadata.objectMetadataId] = fieldMetadata; + } + + return acc; + }, + {}, + ); + + await this.relationMetadataRepository.save([ + { + workspaceId: workspaceId, + relationType: RelationMetadataType.ONE_TO_MANY, + fromObjectMetadataId: createdObjectMetadata.id, + toObjectMetadataId: favoriteObjectMetadata.id, + fromFieldMetadataId: + favoriteRelationFieldMetadataMap[createdObjectMetadata.id].id, + toFieldMetadataId: + favoriteRelationFieldMetadataMap[favoriteObjectMetadata.id].id, + }, + ]); + + return { favoriteObjectMetadata }; + } } diff --git a/packages/twenty-website/src/content/user-guide/basics/opportunities.mdx b/packages/twenty-website/src/content/user-guide/basics/opportunities.mdx index 4625bf168..4c4ecab2c 100644 --- a/packages/twenty-website/src/content/user-guide/basics/opportunities.mdx +++ b/packages/twenty-website/src/content/user-guide/basics/opportunities.mdx @@ -10,7 +10,7 @@ All opportunities are presented in a Kanban board, where each column represents ## Add and delete stages -You can add as many stages as you'd like to perfectly capture your entire workflow. To add a new stage, click on Options on the top right, choose Stages, and then click on `+ Add stage`. Name your stage and hit `Enter` to create it. Click on the name of the stage and then on on edit to change the stage's name and color. +You can add as many stages as you'd like to perfectly capture your entire workflow. To add a new stage, click on Options on the top right, choose Stages, and then click on `+ Add stage`. Name your stage and hit `Enter` to create it. Click on the name of the stage and then on edit to change the stage's name and color. To delete a stage, click on the stage name or on the `⋮` icon that appears when you hover over a stage. Click on edit to open the menu and click on Delete at the bottom to permanently delete a stage.