diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index cbb47a4eae..0bf19901dc 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1452,6 +1452,7 @@ export enum FeatureFlagKey { IS_AI_ENABLED = 'IS_AI_ENABLED', IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED', IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED = 'IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED', + IS_ATTACHMENT_MIGRATED = 'IS_ATTACHMENT_MIGRATED', IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED', IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED', IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED', diff --git a/packages/twenty-front/src/generated/graphql.ts b/packages/twenty-front/src/generated/graphql.ts index 1249b47ee4..b43ccdd27d 100644 --- a/packages/twenty-front/src/generated/graphql.ts +++ b/packages/twenty-front/src/generated/graphql.ts @@ -1419,6 +1419,7 @@ export enum FeatureFlagKey { IS_AI_ENABLED = 'IS_AI_ENABLED', IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED', IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED = 'IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED', + IS_ATTACHMENT_MIGRATED = 'IS_ATTACHMENT_MIGRATED', IS_COMMAND_MENU_ITEM_ENABLED = 'IS_COMMAND_MENU_ITEM_ENABLED', IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED', IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED', diff --git a/packages/twenty-front/src/modules/activities/components/ActivityRichTextEditor.tsx b/packages/twenty-front/src/modules/activities/components/ActivityRichTextEditor.tsx index 7c6bd62f84..db0f29f3fb 100644 --- a/packages/twenty-front/src/modules/activities/components/ActivityRichTextEditor.tsx +++ b/packages/twenty-front/src/modules/activities/components/ActivityRichTextEditor.tsx @@ -5,6 +5,7 @@ import { v4 } from 'uuid'; import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile'; import { useUpsertActivity } from '@/activities/hooks/useUpsertActivity'; import { canCreateActivityState } from '@/activities/states/canCreateActivityState'; +import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName'; import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { modifyRecordFromCache } from '@/object-record/cache/utils/modifyRecordFromCache'; @@ -35,11 +36,13 @@ import { usePushFocusItemToFocusStack } from '@/ui/utilities/focus/hooks/usePush import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks/useRemoveFocusItemFromFocusStackById'; import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotkeysOnFocusedElement'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import '@blocknote/core/fonts/inter.css'; import '@blocknote/mantine/style.css'; import { useCreateBlockNote } from '@blocknote/react'; import '@blocknote/react/style.css'; import { isDefined } from 'twenty-shared/utils'; +import { FeatureFlagKey } from '~/generated/graphql'; type ActivityRichTextEditorProps = { activityId: string; @@ -70,21 +73,21 @@ export const ActivityRichTextEditor = ({ const { removeFocusItemFromFocusStackById } = useRemoveFocusItemFromFocusStackById(); + const isAttachmentMigrated = useIsFeatureEnabled( + FeatureFlagKey.IS_ATTACHMENT_MIGRATED, + ); + + const attachmentTargetFieldIdName = getActivityTargetObjectFieldIdName({ + nameSingular: activityObjectNameSingular, + isMorphRelation: isAttachmentMigrated, + }); + const { records: attachments } = useFindManyRecords({ objectNameSingular: CoreObjectNameSingular.Attachment, filter: { - or: [ - { - noteId: { - eq: activityId, - }, - }, - { - taskId: { - eq: activityId, - }, - }, - ], + [attachmentTargetFieldIdName]: { + eq: activityId, + }, }, }); diff --git a/packages/twenty-front/src/modules/activities/files/hooks/__tests__/useAttachments.test.ts b/packages/twenty-front/src/modules/activities/files/hooks/__tests__/useAttachments.test.ts index 42ebbefdb4..31bf961b21 100644 --- a/packages/twenty-front/src/modules/activities/files/hooks/__tests__/useAttachments.test.ts +++ b/packages/twenty-front/src/modules/activities/files/hooks/__tests__/useAttachments.test.ts @@ -5,6 +5,9 @@ import { useAttachments } from '@/activities/files/hooks/useAttachments'; jest.mock('@/object-record/hooks/useFindManyRecords', () => ({ useFindManyRecords: jest.fn(), })); +jest.mock('@/workspace/hooks/useIsFeatureEnabled', () => ({ + useIsFeatureEnabled: jest.fn(), +})); describe('useAttachments', () => { afterEach(() => { @@ -24,9 +27,13 @@ describe('useAttachments', () => { const useFindManyRecordsMock = jest.requireMock( '@/object-record/hooks/useFindManyRecords', ); + const useIsFeatureEnabledMock = jest.requireMock( + '@/workspace/hooks/useIsFeatureEnabled', + ); useFindManyRecordsMock.useFindManyRecords.mockReturnValue({ records: mockAttachments, }); + useIsFeatureEnabledMock.useIsFeatureEnabled.mockReturnValue(false); const { result } = renderHook(() => useAttachments(mockTargetableObject)); @@ -42,7 +49,11 @@ describe('useAttachments', () => { const useFindManyRecordsMock = jest.requireMock( '@/object-record/hooks/useFindManyRecords', ); + const useIsFeatureEnabledMock = jest.requireMock( + '@/workspace/hooks/useIsFeatureEnabled', + ); useFindManyRecordsMock.useFindManyRecords.mockReturnValue({ records: [] }); + useIsFeatureEnabledMock.useIsFeatureEnabled.mockReturnValue(false); const { result } = renderHook(() => useAttachments(mockTargetableObject)); diff --git a/packages/twenty-front/src/modules/activities/files/hooks/useAttachments.tsx b/packages/twenty-front/src/modules/activities/files/hooks/useAttachments.tsx index bf8576da91..848bf8b9e5 100644 --- a/packages/twenty-front/src/modules/activities/files/hooks/useAttachments.tsx +++ b/packages/twenty-front/src/modules/activities/files/hooks/useAttachments.tsx @@ -3,10 +3,17 @@ import { type ActivityTargetableObject } from '@/activities/types/ActivityTarget import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; +import { FeatureFlagKey } from '~/generated/graphql'; export const useAttachments = (targetableObject: ActivityTargetableObject) => { + const isAttachmentMigrated = useIsFeatureEnabled( + FeatureFlagKey.IS_ATTACHMENT_MIGRATED, + ); + const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({ nameSingular: targetableObject.targetObjectNameSingular, + isMorphRelation: isAttachmentMigrated, }); const { records: attachments, loading } = useFindManyRecords({ diff --git a/packages/twenty-front/src/modules/activities/files/hooks/useUploadAttachmentFile.tsx b/packages/twenty-front/src/modules/activities/files/hooks/useUploadAttachmentFile.tsx index 1012af116b..b00e61cb03 100644 --- a/packages/twenty-front/src/modules/activities/files/hooks/useUploadAttachmentFile.tsx +++ b/packages/twenty-front/src/modules/activities/files/hooks/useUploadAttachmentFile.tsx @@ -5,15 +5,20 @@ import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivi import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { isDefined } from 'twenty-shared/utils'; import { FileFolder, useUploadFileMutation, } from '~/generated-metadata/graphql'; +import { FeatureFlagKey } from '~/generated/graphql'; export const useUploadAttachmentFile = () => { const coreClient = useApolloCoreClient(); const [uploadFile] = useUploadFileMutation({ client: coreClient }); + const isAttachmentMigrated = useIsFeatureEnabled( + FeatureFlagKey.IS_ATTACHMENT_MIGRATED, + ); const { createOneRecord: createOneAttachment } = useCreateOneRecord({ @@ -42,6 +47,7 @@ export const useUploadAttachmentFile = () => { const targetableObjectFieldIdName = getActivityTargetObjectFieldIdName({ nameSingular: targetableObject.targetObjectNameSingular, + isMorphRelation: isAttachmentMigrated, }); const attachmentToCreate = { diff --git a/packages/twenty-front/src/modules/activities/files/types/Attachment.ts b/packages/twenty-front/src/modules/activities/files/types/Attachment.ts index d947e85732..70422f5731 100644 --- a/packages/twenty-front/src/modules/activities/files/types/Attachment.ts +++ b/packages/twenty-front/src/modules/activities/files/types/Attachment.ts @@ -7,8 +7,20 @@ export type Attachment = { name: string; fullPath: string; fileCategory: AttachmentFileCategory; - companyId: string; - personId: string; + companyId?: string | null; + personId?: string | null; + taskId?: string | null; + noteId?: string | null; + opportunityId?: string | null; + dashboardId?: string | null; + workflowId?: string | null; + targetCompanyId?: string | null; + targetPersonId?: string | null; + targetTaskId?: string | null; + targetNoteId?: string | null; + targetOpportunityId?: string | null; + targetDashboardId?: string | null; + targetWorkflowId?: string | null; createdBy?: { source: string; workspaceMemberId: string | null; diff --git a/packages/twenty-front/src/modules/activities/utils/getActivityTargetObjectFieldIdName.ts b/packages/twenty-front/src/modules/activities/utils/getActivityTargetObjectFieldIdName.ts index 6932fbf123..71384a2099 100644 --- a/packages/twenty-front/src/modules/activities/utils/getActivityTargetObjectFieldIdName.ts +++ b/packages/twenty-front/src/modules/activities/utils/getActivityTargetObjectFieldIdName.ts @@ -1,7 +1,15 @@ +import { capitalize } from 'twenty-shared/utils'; + export const getActivityTargetObjectFieldIdName = ({ nameSingular, + isMorphRelation = false, }: { nameSingular: string; + isMorphRelation?: boolean; }) => { + if (isMorphRelation) { + return `target${capitalize(nameSingular)}Id`; + } + return `${nameSingular}Id`; }; diff --git a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts b/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts index 3f60141ffa..79bfcc0e05 100644 --- a/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts +++ b/packages/twenty-front/src/modules/object-record/record-filter/utils/isRecordMatchingFilter.ts @@ -1,6 +1,5 @@ import { isObject } from '@sniptt/guards'; -import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { FieldMetadataType, type ActorFilter, @@ -46,6 +45,10 @@ import { isMatchingUUIDFilter, } from 'twenty-shared/utils'; +import { type FieldMetadataItem } from '@/object-metadata/types/FieldMetadataItem'; +import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; +import { computePossibleMorphGqlFieldForFieldName } from '@/object-record/cache/utils/computePossibleMorphGqlFieldForFieldName'; + const isLeafFilter = ( filter: RecordGqlOperationFilter, ): filter is LeafObjectRecordFilter => { @@ -59,6 +62,27 @@ const isAndFilter = ( const isImplicitAndFilter = (filter: RecordGqlOperationFilter) => Object.keys(filter).length > 1; +const isMorphRelationJoinColumnKey = ({ + fieldMetadataItem, + key, +}: { + fieldMetadataItem: FieldMetadataItem; + key: string; +}): boolean => { + if (!fieldMetadataItem.morphRelations?.length) { + return false; + } + + const possibleJoinColumnNames = computePossibleMorphGqlFieldForFieldName({ + fieldMetadata: { + morphRelations: fieldMetadataItem.morphRelations, + fieldName: fieldMetadataItem.name, + }, + }).map((name) => `${name}Id`); + + return possibleJoinColumnNames.includes(key); +}; + const isOrFilter = ( filter: RecordGqlOperationFilter, ): filter is OrObjectRecordFilter => 'or' in filter && !!filter.or; @@ -175,8 +199,17 @@ export const isRecordMatchingFilter = ({ objectMetadataItem.fields.find((field) => field.name === filterKey) ?? objectMetadataItem.fields.find( (field) => - field.type === FieldMetadataType.RELATION && + (field.type === FieldMetadataType.RELATION || + field.type === FieldMetadataType.MORPH_RELATION) && field.settings?.joinColumnName === filterKey, + ) ?? + objectMetadataItem.fields.find( + (field) => + field.type === FieldMetadataType.MORPH_RELATION && + isMorphRelationJoinColumnKey({ + fieldMetadataItem: field, + key: filterKey, + }), ); if (!isDefined(objectMetadataField)) { @@ -366,9 +399,15 @@ export const isRecordMatchingFilter = ({ }); }); } - case FieldMetadataType.RELATION: { + case FieldMetadataType.RELATION: + case FieldMetadataType.MORPH_RELATION: { const isJoinColumn = - objectMetadataField.settings?.joinColumnName === filterKey; + objectMetadataField.settings?.joinColumnName === filterKey || + (objectMetadataField.type === FieldMetadataType.MORPH_RELATION && + isMorphRelationJoinColumnKey({ + fieldMetadataItem: objectMetadataField, + key: filterKey, + })); if (isJoinColumn) { return isMatchingUUIDFilter({ diff --git a/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidget.tsx b/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidget.tsx index 855182ebfd..308b4b2d04 100644 --- a/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidget.tsx +++ b/packages/twenty-front/src/modules/page-layout/widgets/standalone-rich-text/components/StandaloneRichTextWidget.tsx @@ -3,6 +3,7 @@ import { useCallback, useMemo, useRef } from 'react'; import { BLOCK_SCHEMA } from '@/activities/blocks/constants/Schema'; import { useUploadAttachmentFile } from '@/activities/files/hooks/useUploadAttachmentFile'; import { type Attachment } from '@/activities/files/types/Attachment'; +import { getActivityTargetObjectFieldIdName } from '@/activities/utils/getActivityTargetObjectFieldIdName'; import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular'; import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords'; import { useUpdatePageLayoutWidget } from '@/page-layout/hooks/useUpdatePageLayoutWidget'; @@ -20,6 +21,7 @@ import { useRemoveFocusItemFromFocusStackById } from '@/ui/utilities/focus/hooks import { FocusComponentType } from '@/ui/utilities/focus/types/FocusComponentType'; import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper'; import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue'; +import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import '@blocknote/core/fonts/inter.css'; import '@blocknote/mantine/style.css'; import { useCreateBlockNote } from '@blocknote/react'; @@ -28,6 +30,7 @@ import styled from '@emotion/styled'; import { isDefined } from 'twenty-shared/utils'; import { useDebouncedCallback } from 'use-debounce'; import { + FeatureFlagKey, PageLayoutType, WidgetConfigurationType, type StandaloneRichTextConfiguration, @@ -63,6 +66,9 @@ export const StandaloneRichTextWidget = ({ const { updatePageLayoutWidget } = useUpdatePageLayoutWidget(); const { targetRecordIdentifier, layoutType } = useLayoutRenderingContext(); const { uploadAttachmentFile } = useUploadAttachmentFile(); + const isAttachmentMigrated = useIsFeatureEnabled( + FeatureFlagKey.IS_ATTACHMENT_MIGRATED, + ); const { pushFocusItemToFocusStack } = usePushFocusItemToFocusStack(); const { removeFocusItemFromFocusStackById } = @@ -70,6 +76,10 @@ export const StandaloneRichTextWidget = ({ const isDashboard = layoutType === PageLayoutType.DASHBOARD; const dashboardId = isDashboard ? targetRecordIdentifier?.id : undefined; + const attachmentTargetFieldIdName = getActivityTargetObjectFieldIdName({ + nameSingular: CoreObjectNameSingular.Dashboard, + isMorphRelation: isAttachmentMigrated, + }); const configuration = widget.configuration as | StandaloneRichTextConfiguration @@ -80,7 +90,7 @@ export const StandaloneRichTextWidget = ({ const { records: attachments } = useFindManyRecords({ objectNameSingular: CoreObjectNameSingular.Attachment, filter: isDefined(dashboardId) - ? { dashboardId: { eq: dashboardId } } + ? { [attachmentTargetFieldIdName]: { eq: dashboardId } } : undefined, skip: !isDefined(dashboardId), }); diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/constants/standard-index-field-names.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/constants/standard-index-field-names.constant.ts index f45d8c3249..e7fa7fa7fc 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/constants/standard-index-field-names.constant.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-16/constants/standard-index-field-names.constant.ts @@ -6,22 +6,26 @@ export const STANDARD_INDEX_FIELD_UNIVERSAL_IDENTIFIERS: Record< Record > = { attachment: { - taskIdIndex: [STANDARD_OBJECTS.attachment.fields.task.universalIdentifier], - noteIdIndex: [STANDARD_OBJECTS.attachment.fields.note.universalIdentifier], + taskIdIndex: [ + STANDARD_OBJECTS.attachment.fields.targetTask.universalIdentifier, + ], + noteIdIndex: [ + STANDARD_OBJECTS.attachment.fields.targetNote.universalIdentifier, + ], personIdIndex: [ - STANDARD_OBJECTS.attachment.fields.person.universalIdentifier, + STANDARD_OBJECTS.attachment.fields.targetPerson.universalIdentifier, ], companyIdIndex: [ - STANDARD_OBJECTS.attachment.fields.company.universalIdentifier, + STANDARD_OBJECTS.attachment.fields.targetCompany.universalIdentifier, ], opportunityIdIndex: [ - STANDARD_OBJECTS.attachment.fields.opportunity.universalIdentifier, + STANDARD_OBJECTS.attachment.fields.targetOpportunity.universalIdentifier, ], dashboardIdIndex: [ - STANDARD_OBJECTS.attachment.fields.dashboard.universalIdentifier, + STANDARD_OBJECTS.attachment.fields.targetDashboard.universalIdentifier, ], workflowIdIndex: [ - STANDARD_OBJECTS.attachment.fields.workflow.universalIdentifier, + STANDARD_OBJECTS.attachment.fields.targetWorkflow.universalIdentifier, ], }, blocklist: { diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command.ts new file mode 100644 index 0000000000..cb6a6acb52 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command.ts @@ -0,0 +1,258 @@ +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { capitalize } from 'twenty-shared/utils'; +import { DataSource, Repository } from 'typeorm'; + +import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner'; +import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; +import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; +import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; +import { computeMorphOrRelationFieldJoinColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-morph-or-relation-field-join-column-name.util'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; +import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; +import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { getWorkspaceSchemaName } from 'src/engine/workspace-datasource/utils/get-workspace-schema-name.util'; +import { ATTACHMENT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-migration/constant/standard-field-ids'; + +@Command({ + name: 'upgrade:1-17:migrate-attachment-to-morph-relations', + description: + 'Migrate attachment relations to morph relation fields and set feature flag', +}) +export class MigrateAttachmentToMorphRelationsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(WorkspaceEntity) + protected readonly workspaceRepository: Repository, + private readonly featureFlagService: FeatureFlagService, + @InjectRepository(ObjectMetadataEntity) + private readonly objectMetadataRepository: Repository, + @InjectDataSource() + private readonly coreDataSource: DataSource, + private readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager, + protected readonly dataSourceService: DataSourceService, + private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, + private readonly workspaceCacheService: WorkspaceCacheService, + private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, + ) { + super(workspaceRepository, twentyORMGlobalManager, dataSourceService); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + const isMigrated = await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IS_ATTACHMENT_MIGRATED, + workspaceId, + ); + + this.logger.log(`Migrating attachments for workspace ${workspaceId}`); + + if (isMigrated) { + this.logger.log(`Attachment migration already completed. Skipping...`); + + return; + } + + if (options.dryRun) { + this.logger.log( + `Would have migrated attachments for workspace ${workspaceId}. Skipping...`, + ); + + return; + } + + const queryRunner = this.coreDataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + const schemaName = getWorkspaceSchemaName(workspaceId); + const tableName = 'attachment'; + + const customObjectMetadata = await this.objectMetadataRepository.find({ + where: { + workspaceId, + isCustom: true, + }, + }); + const customObjectMetadataNames = customObjectMetadata.map( + (objectMetadata) => objectMetadata.nameSingular, + ); + + const fieldNameMigrations = [ + { old: 'task', new: 'targetTask' }, + { old: 'note', new: 'targetNote' }, + { old: 'person', new: 'targetPerson' }, + { old: 'company', new: 'targetCompany' }, + { old: 'opportunity', new: 'targetOpportunity' }, + { old: 'dashboard', new: 'targetDashboard' }, + { old: 'workflow', new: 'targetWorkflow' }, + ...customObjectMetadataNames.map((customObjectName) => ({ + old: `${customObjectName}`, + new: `target${capitalize(customObjectName)}`, + })), + ]; + + const fieldMigrations = fieldNameMigrations.map( + ({ old: oldFieldName, new: newFieldName }) => ({ + old: `${oldFieldName}Id`, + new: `${newFieldName}Id`, + }), + ); + + const attachmentObjectMetadata = + await this.objectMetadataRepository.findOne({ + where: { + workspaceId, + nameSingular: 'attachment', + }, + relations: ['fields'], + }); + + if (!attachmentObjectMetadata) { + this.logger.error( + `🟥 Attachment object metadata not found for workspace ${workspaceId}`, + ); + + return; + } + + for (const { new: newField, old: oldField } of fieldMigrations) { + try { + await queryRunner.query( + `ALTER TABLE "${schemaName}"."${tableName}" + RENAME COLUMN "${oldField}" TO "${newField}"`, + ); + this.logger.log( + `Renamed column "${oldField}" to "${newField}" for "${tableName}"`, + ); + } catch (error) { + this.logger.error( + `Error renaming column "${oldField}" to "${newField}" for "${tableName}" in workspace ${workspaceId}`, + error, + ); + + throw error; + } + } + + this.logger.log(`✅ Successfully migrated attachment records`); + + const objectNamesToMigrate = fieldNameMigrations.map( + ({ old: oldFieldName }) => oldFieldName, + ); + + const relatedObjectMetadata = await this.objectMetadataRepository.find({ + where: { + workspaceId, + }, + }); + + const relatedObjectMetadataMap = new Map( + relatedObjectMetadata.map((obj) => [obj.nameSingular, obj]), + ); + + const morphId = ATTACHMENT_STANDARD_FIELD_IDS.targetMorphId; + + for (const objectName of objectNamesToMigrate) { + const relatedObject = relatedObjectMetadataMap.get(objectName); + + if (!relatedObject) { + this.logger.warn( + `Related object metadata not found for ${objectName} in workspace ${workspaceId}`, + ); + continue; + } + + const fieldToMigrate = attachmentObjectMetadata.fields.find( + (field) => + field.type === 'RELATION' && + field.relationTargetObjectMetadataId === relatedObject.id, + ); + + if (!fieldToMigrate) { + this.logger.log( + `No RELATION field found for ${objectName} in attachment`, + ); + continue; + } + + const newFieldName = `target${capitalize(fieldToMigrate.name)}`; + + const settings = { + ...fieldToMigrate.settings, + joinColumnName: computeMorphOrRelationFieldJoinColumnName({ + name: newFieldName, + }), + }; + + try { + const result = await queryRunner.query( + `UPDATE core."fieldMetadata" + SET name = $1, type = 'MORPH_RELATION', "morphId" = $3, settings = $4 + WHERE id = $2`, + [newFieldName, fieldToMigrate.id, morphId, settings], + ); + + const rowsUpdated = result[1] || 0; + + if (rowsUpdated > 0) { + this.logger.log( + `Updated fieldMetadata: ${fieldToMigrate.name} → ${newFieldName} (type: MORPH_RELATION)`, + ); + } + } catch (error) { + this.logger.error( + `Error updating fieldMetadata for ${objectName} in workspace ${workspaceId}`, + error, + ); + + throw error; + } + } + + this.logger.log(`✅ Successfully migrated attachment fieldmetadata`); + + await queryRunner.commitTransaction(); + + await this.featureFlagService.enableFeatureFlags( + [FeatureFlagKey.IS_ATTACHMENT_MIGRATED], + workspaceId, + ); + + await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [ + 'flatFieldMetadataMaps', + 'flatObjectMetadataMaps', + 'featureFlagsMap', + ]); + + await this.workspaceMetadataVersionService.incrementMetadataVersion( + workspaceId, + ); + this.logger.log(`Cache flushed`); + + this.logger.log( + `Set IS_ATTACHMENT_MIGRATED feature flag for workspace ${workspaceId}`, + ); + + this.logger.log(`Flush cache for workspace ${workspaceId}`); + await this.workspaceCacheStorageService.flush(workspaceId); + } catch (error) { + await queryRunner.rollbackTransaction(); + this.logger.error( + `Error migrating attachment to morph relations (rolled transaction back on ${workspaceId})`, + error, + ); + throw error; + } finally { + await queryRunner.release(); + } + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-upgrade-version-command.module.ts new file mode 100644 index 0000000000..10df6f0620 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-upgrade-version-command.module.ts @@ -0,0 +1,40 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; + +import { MigrateAttachmentToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command'; +import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; +import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; +import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; +import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; +import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; +import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; +import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; +import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + WorkspaceEntity, + ObjectMetadataEntity, + FieldMetadataEntity, + FeatureFlagEntity, + AttachmentWorkspaceEntity, + ]), + DataSourceModule, + WorkspaceCacheStorageModule, + WorkspaceMetadataVersionModule, + FeatureFlagModule, + WorkspaceCacheModule, + FieldMetadataModule, + ObjectMetadataModule, + ApplicationModule, + ], + providers: [MigrateAttachmentToMorphRelationsCommand], + exports: [MigrateAttachmentToMorphRelationsCommand], +}) +export class V1_17_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts index 9eff74051a..6cfcea8aac 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts @@ -5,6 +5,7 @@ import { V1_13_UpgradeVersionCommandModule } from 'src/database/commands/upgrade import { V1_14_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-14/1-14-upgrade-version-command.module'; import { V1_15_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-15/1-15-upgrade-version-command.module'; import { V1_16_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-16/1-16-upgrade-version-command.module'; +import { V1_17_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-17/1-17-upgrade-version-command.module'; import { UpgradeCommand } from 'src/database/commands/upgrade-version-command/upgrade.command'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; @@ -16,6 +17,7 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s V1_14_UpgradeVersionCommandModule, V1_15_UpgradeVersionCommandModule, V1_16_UpgradeVersionCommandModule, + V1_17_UpgradeVersionCommandModule, DataSourceModule, ], providers: [UpgradeCommand], diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts index c96eb18a05..09fc327337 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts @@ -45,6 +45,7 @@ import { MakeViewFilterUniversalIdentifierAndApplicationIdNotNullableMigrationCo import { MakeViewGroupUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-view-group-universal-identifier-and-application-id-not-nullable-migration.command'; import { MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-view-universal-identifier-and-application-id-not-nullable-migration.command'; import { UpdateTaskOnDeleteActionCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-update-task-on-delete-action.command'; +import { MigrateAttachmentToMorphRelationsCommand } from 'src/database/commands/upgrade-version-command/1-17/1-17-migrate-attachment-to-morph-relations.command'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; @@ -107,6 +108,9 @@ export class UpgradeCommand extends UpgradeCommandRunner { protected readonly makeIndexMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeIndexMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, protected readonly identifyRemainingEntitiesMetadataCommand: IdentifyRemainingEntitiesMetadataCommand, protected readonly makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, + + // 1.17 Commands + protected readonly migrateAttachmentToMorphRelationsCommand: MigrateAttachmentToMorphRelationsCommand, ) { super( workspaceRepository, @@ -176,12 +180,17 @@ export class UpgradeCommand extends UpgradeCommandRunner { .makeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand, ]; + const commands_1170: VersionCommands = [ + this.migrateAttachmentToMorphRelationsCommand, + ]; + this.allCommands = { '1.12.0': commands_1120, '1.13.0': commands_1130, '1.14.0': commands_1140, '1.15.0': commands_1150, '1.16.0': commands_1160, + '1.17.0': commands_1170, }; } diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts index 1da6db90fe..4710bdae20 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts @@ -9,6 +9,7 @@ export enum FeatureFlagKey { IS_EMAILING_DOMAIN_ENABLED = 'IS_EMAILING_DOMAIN_ENABLED', IS_DASHBOARD_V2_ENABLED = 'IS_DASHBOARD_V2_ENABLED', IS_TIMELINE_ACTIVITY_MIGRATED = 'IS_TIMELINE_ACTIVITY_MIGRATED', + IS_ATTACHMENT_MIGRATED = 'IS_ATTACHMENT_MIGRATED', IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED = 'IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED', IS_JUNCTION_RELATIONS_ENABLED = 'IS_JUNCTION_RELATIONS_ENABLED', IS_SSE_DB_EVENTS_ENABLED = 'IS_SSE_DB_EVENTS_ENABLED', diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts index c553ad4c40..949cbe6223 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/build-default-relation-flat-field-metadatas-for-custom-object.util.ts @@ -18,6 +18,7 @@ import { } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; import { CUSTOM_OBJECT_STANDARD_FIELD_IDS, + ATTACHMENT_STANDARD_FIELD_IDS, TIMELINE_ACTIVITY_STANDARD_FIELD_IDS, } from 'src/engine/workspace-manager/workspace-migration/constant/standard-field-ids'; import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-migration/constant/standard-object-icons'; @@ -33,7 +34,7 @@ const DEFAULT_RELATIONS_OBJECTS_STANDARD_IDS = [ const morphIdByRelationObjectNameSingular = { timelineActivity: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.targetMorphId, favorite: null, - attachment: null, + attachment: ATTACHMENT_STANDARD_FIELD_IDS.targetMorphId, noteTarget: null, taskTarget: null, } satisfies Record< @@ -96,7 +97,9 @@ export const buildDefaultRelationFlatFieldMetadatasForCustomObject = ({ (objectMetadataNameSingular === 'timelineActivity' && existingFeatureFlagsMap[ FeatureFlagKey.IS_TIMELINE_ACTIVITY_MIGRATED - ]) ?? + ]) || + (objectMetadataNameSingular === 'attachment' && + existingFeatureFlagsMap[FeatureFlagKey.IS_ATTACHMENT_MIGRATED]) || false; const isObjectMigratedToMorphRelations = isObjectMigratedFromOlderReleases || isFeatureFlagEnabled; diff --git a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts index 907aef2847..7ad06902ce 100644 --- a/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts +++ b/packages/twenty-server/src/engine/twenty-orm/entity-manager/workspace-entity-manager.spec.ts @@ -216,6 +216,7 @@ describe('WorkspaceEntityManager', () => { IS_PUBLIC_DOMAIN_ENABLED: false, IS_EMAILING_DOMAIN_ENABLED: false, IS_DASHBOARD_V2_ENABLED: false, + IS_ATTACHMENT_MIGRATED: false, IS_TIMELINE_ACTIVITY_MIGRATED: false, IS_GLOBAL_WORKSPACE_DATASOURCE_ENABLED: false, IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED: false, diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts index b67eaa6a0e..3c9b53d1ef 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/core/utils/seed-feature-flags.util.ts @@ -61,6 +61,11 @@ export const seedFeatureFlags = async ({ workspaceId: workspaceId, value: true, }, + { + key: FeatureFlagKey.IS_ATTACHMENT_MIGRATED, + workspaceId: workspaceId, + value: true, + }, { key: FeatureFlagKey.IS_ROW_LEVEL_PERMISSION_PREDICATES_ENABLED, workspaceId: workspaceId, diff --git a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/attachment-data-seeds.constant.ts b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/attachment-data-seeds.constant.ts index 7cad4d5945..35095c0541 100644 --- a/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/attachment-data-seeds.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/dev-seeder/data/constants/attachment-data-seeds.constant.ts @@ -18,11 +18,11 @@ type AttachmentDataSeed = { updatedBySource: string; updatedByWorkspaceMemberId: string; updatedByName: string; - personId: string | null; - companyId: string | null; - noteId: string | null; - taskId: string | null; - opportunityId: string | null; + targetPersonId: string | null; + targetCompanyId: string | null; + targetNoteId: string | null; + targetTaskId: string | null; + targetOpportunityId: string | null; }; export const ATTACHMENT_DATA_SEED_COLUMNS: (keyof AttachmentDataSeed)[] = [ @@ -36,11 +36,11 @@ export const ATTACHMENT_DATA_SEED_COLUMNS: (keyof AttachmentDataSeed)[] = [ 'updatedBySource', 'updatedByWorkspaceMemberId', 'updatedByName', - 'personId', - 'companyId', - 'noteId', - 'taskId', - 'opportunityId', + 'targetPersonId', + 'targetCompanyId', + 'targetNoteId', + 'targetTaskId', + 'targetOpportunityId', ]; const GENERATE_ATTACHMENT_IDS = (): Record => { @@ -219,33 +219,34 @@ const GENERATE_ATTACHMENT_SEEDS = (): AttachmentDataSeed[] => { // Determine which entity this attachment belongs to // Distribution: ~30% person, ~30% company, ~20% note, ~15% task, ~5% opportunity - let personId: string | null = null; - let companyId: string | null = null; - let noteId: string | null = null; - let taskId: string | null = null; - let opportunityId: string | null = null; + let targetPersonId: string | null = null; + let targetCompanyId: string | null = null; + let targetNoteId: string | null = null; + let targetTaskId: string | null = null; + let targetOpportunityId: string | null = null; const DISTRIBUTION_VALUE = INDEX % 100; if (DISTRIBUTION_VALUE < 30) { // 30% Person attachments - personId = PERSON_IDS[entityIndex % PERSON_IDS.length]; + targetPersonId = PERSON_IDS[entityIndex % PERSON_IDS.length]; entityIndex++; } else if (DISTRIBUTION_VALUE < 60) { // 30% Company attachments - companyId = COMPANY_IDS[entityIndex % COMPANY_IDS.length]; + targetCompanyId = COMPANY_IDS[entityIndex % COMPANY_IDS.length]; entityIndex++; } else if (DISTRIBUTION_VALUE < 80) { // 20% Note attachments - noteId = NOTE_IDS[entityIndex % NOTE_IDS.length]; + targetNoteId = NOTE_IDS[entityIndex % NOTE_IDS.length]; entityIndex++; } else if (DISTRIBUTION_VALUE < 95) { // 15% Task attachments - taskId = TASK_IDS[entityIndex % TASK_IDS.length]; + targetTaskId = TASK_IDS[entityIndex % TASK_IDS.length]; entityIndex++; } else { // 5% Opportunity attachments - opportunityId = OPPORTUNITY_IDS[entityIndex % OPPORTUNITY_IDS.length]; + targetOpportunityId = + OPPORTUNITY_IDS[entityIndex % OPPORTUNITY_IDS.length]; entityIndex++; } @@ -260,11 +261,11 @@ const GENERATE_ATTACHMENT_SEEDS = (): AttachmentDataSeed[] => { updatedBySource: FieldActorSource.MANUAL, updatedByWorkspaceMemberId: WORKSPACE_MEMBER_DATA_SEED_IDS.TIM, updatedByName: 'Tim A', - personId, - companyId, - noteId, - taskId, - opportunityId, + targetPersonId, + targetCompanyId, + targetNoteId, + targetTaskId, + targetOpportunityId, }); } diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts index 30b15486eb..268f3e00da 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant.ts @@ -57,17 +57,27 @@ export const STANDARD_OBJECTS = { updatedBy: { universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.updatedBy, }, - task: { universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.task }, - note: { universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.note }, - person: { universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.person }, - company: { universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.company }, - opportunity: { - universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.opportunity, + targetTask: { + universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.targetTask, }, - dashboard: { - universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.dashboard, + targetNote: { + universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.targetNote, + }, + targetPerson: { + universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.targetPerson, + }, + targetCompany: { + universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.targetCompany, + }, + targetOpportunity: { + universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.targetOpportunity, + }, + targetDashboard: { + universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.targetDashboard, + }, + targetWorkflow: { + universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.targetWorkflow, }, - workflow: { universalIdentifier: ATTACHMENT_STANDARD_FIELD_IDS.workflow }, }, indexes: { taskIdIndex: { diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap index 4e9ff9250d..c2bb9d6f8f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/__snapshots__/get-standard-object-metadata-related-entity-ids.util.spec.ts.snap @@ -1,21 +1,15 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP exports[`getStandardObjectMetadataRelatedEntityIds should return standard object metadata related entity ids 1`] = ` { "attachment": { "fields": { - "company": { - "id": "00000000-0000-0000-0000-000000000013", - }, "createdAt": { "id": "00000000-0000-0000-0000-000000000002", }, "createdBy": { "id": "00000000-0000-0000-0000-000000000008", }, - "dashboard": { - "id": "00000000-0000-0000-0000-000000000015", - }, "deletedAt": { "id": "00000000-0000-0000-0000-000000000004", }, @@ -31,27 +25,33 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object "name": { "id": "00000000-0000-0000-0000-000000000005", }, - "note": { + "targetCompany": { + "id": "00000000-0000-0000-0000-000000000013", + }, + "targetDashboard": { + "id": "00000000-0000-0000-0000-000000000015", + }, + "targetNote": { "id": "00000000-0000-0000-0000-000000000011", }, - "opportunity": { + "targetOpportunity": { "id": "00000000-0000-0000-0000-000000000014", }, - "person": { + "targetPerson": { "id": "00000000-0000-0000-0000-000000000012", }, - "task": { + "targetTask": { "id": "00000000-0000-0000-0000-000000000010", }, + "targetWorkflow": { + "id": "00000000-0000-0000-0000-000000000016", + }, "updatedAt": { "id": "00000000-0000-0000-0000-000000000003", }, "updatedBy": { "id": "00000000-0000-0000-0000-000000000009", }, - "workflow": { - "id": "00000000-0000-0000-0000-000000000016", - }, }, "id": "00000000-0000-0000-0000-000000000017", "views": undefined, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-attachment-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-attachment-standard-flat-field-metadata.util.ts index 7175aec8d5..5e9ffc8885 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-attachment-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-attachment-standard-flat-field-metadata.util.ts @@ -6,6 +6,7 @@ import { } from 'twenty-shared/types'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { ATTACHMENT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-migration/constant/standard-field-ids'; import { type AllStandardObjectFieldName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-field-name.type'; import { type CreateStandardFieldArgs, @@ -231,13 +232,13 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ }), // Relation fields - task: createStandardRelationFieldFlatMetadata({ + targetTask: createStandardRelationFieldFlatMetadata({ objectName, workspaceId, context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'task', + type: FieldMetadataType.MORPH_RELATION, + morphId: ATTACHMENT_STANDARD_FIELD_IDS.targetMorphId, + fieldName: 'targetTask', label: 'Task', description: 'Attachment task', icon: 'IconNotes', @@ -248,7 +249,7 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ settings: { relationType: RelationType.MANY_TO_ONE, onDelete: RelationOnDeleteAction.SET_NULL, - joinColumnName: 'taskId', + joinColumnName: 'targetTaskId', }, }, standardObjectMetadataRelatedEntityIds, @@ -256,13 +257,13 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), - note: createStandardRelationFieldFlatMetadata({ + targetNote: createStandardRelationFieldFlatMetadata({ objectName, workspaceId, context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'note', + type: FieldMetadataType.MORPH_RELATION, + morphId: ATTACHMENT_STANDARD_FIELD_IDS.targetMorphId, + fieldName: 'targetNote', label: 'Note', description: 'Attachment note', icon: 'IconNotes', @@ -273,7 +274,7 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ settings: { relationType: RelationType.MANY_TO_ONE, onDelete: RelationOnDeleteAction.SET_NULL, - joinColumnName: 'noteId', + joinColumnName: 'targetNoteId', }, }, standardObjectMetadataRelatedEntityIds, @@ -281,13 +282,13 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), - person: createStandardRelationFieldFlatMetadata({ + targetPerson: createStandardRelationFieldFlatMetadata({ objectName, workspaceId, context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'person', + type: FieldMetadataType.MORPH_RELATION, + morphId: ATTACHMENT_STANDARD_FIELD_IDS.targetMorphId, + fieldName: 'targetPerson', label: 'Person', description: 'Attachment person', icon: 'IconUser', @@ -298,7 +299,7 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ settings: { relationType: RelationType.MANY_TO_ONE, onDelete: RelationOnDeleteAction.CASCADE, - joinColumnName: 'personId', + joinColumnName: 'targetPersonId', }, }, standardObjectMetadataRelatedEntityIds, @@ -306,13 +307,13 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), - company: createStandardRelationFieldFlatMetadata({ + targetCompany: createStandardRelationFieldFlatMetadata({ objectName, workspaceId, context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'company', + type: FieldMetadataType.MORPH_RELATION, + morphId: ATTACHMENT_STANDARD_FIELD_IDS.targetMorphId, + fieldName: 'targetCompany', label: 'Company', description: 'Attachment company', icon: 'IconBuildingSkyscraper', @@ -323,7 +324,7 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ settings: { relationType: RelationType.MANY_TO_ONE, onDelete: RelationOnDeleteAction.CASCADE, - joinColumnName: 'companyId', + joinColumnName: 'targetCompanyId', }, }, standardObjectMetadataRelatedEntityIds, @@ -331,13 +332,13 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), - opportunity: createStandardRelationFieldFlatMetadata({ + targetOpportunity: createStandardRelationFieldFlatMetadata({ objectName, workspaceId, context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'opportunity', + type: FieldMetadataType.MORPH_RELATION, + morphId: ATTACHMENT_STANDARD_FIELD_IDS.targetMorphId, + fieldName: 'targetOpportunity', label: 'Opportunity', description: 'Attachment opportunity', icon: 'IconBuildingSkyscraper', @@ -348,7 +349,7 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ settings: { relationType: RelationType.MANY_TO_ONE, onDelete: RelationOnDeleteAction.CASCADE, - joinColumnName: 'opportunityId', + joinColumnName: 'targetOpportunityId', }, }, standardObjectMetadataRelatedEntityIds, @@ -356,13 +357,13 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), - dashboard: createStandardRelationFieldFlatMetadata({ + targetDashboard: createStandardRelationFieldFlatMetadata({ objectName, workspaceId, context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'dashboard', + type: FieldMetadataType.MORPH_RELATION, + morphId: ATTACHMENT_STANDARD_FIELD_IDS.targetMorphId, + fieldName: 'targetDashboard', label: 'Dashboard', description: 'Attachment dashboard', icon: 'IconLayout', @@ -373,7 +374,7 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ settings: { relationType: RelationType.MANY_TO_ONE, onDelete: RelationOnDeleteAction.CASCADE, - joinColumnName: 'dashboardId', + joinColumnName: 'targetDashboardId', }, }, standardObjectMetadataRelatedEntityIds, @@ -381,13 +382,13 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ twentyStandardApplicationId, now, }), - workflow: createStandardRelationFieldFlatMetadata({ + targetWorkflow: createStandardRelationFieldFlatMetadata({ objectName, workspaceId, context: { - type: FieldMetadataType.RELATION, - morphId: null, - fieldName: 'workflow', + type: FieldMetadataType.MORPH_RELATION, + morphId: ATTACHMENT_STANDARD_FIELD_IDS.targetMorphId, + fieldName: 'targetWorkflow', label: 'Workflow', description: 'Attachment workflow', icon: 'IconSettingsAutomation', @@ -398,7 +399,7 @@ export const buildAttachmentStandardFlatFieldMetadatas = ({ settings: { relationType: RelationType.MANY_TO_ONE, onDelete: RelationOnDeleteAction.CASCADE, - joinColumnName: 'workflowId', + joinColumnName: 'targetWorkflowId', }, }, standardObjectMetadataRelatedEntityIds, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-company-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-company-standard-flat-field-metadata.util.ts index 0942ab400c..099ad9e741 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-company-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-company-standard-flat-field-metadata.util.ts @@ -484,7 +484,7 @@ export const buildCompanyStandardFlatFieldMetadatas = ({ isSystem: true, isNullable: true, targetObjectName: 'attachment', - targetFieldName: 'company', + targetFieldName: 'targetCompany', settings: { relationType: RelationType.ONE_TO_MANY, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-dashboard-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-dashboard-standard-flat-field-metadata.util.ts index 406623fa57..62f7311dd0 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-dashboard-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-dashboard-standard-flat-field-metadata.util.ts @@ -282,7 +282,7 @@ export const buildDashboardStandardFlatFieldMetadatas = ({ isSystem: true, isNullable: true, targetObjectName: 'attachment', - targetFieldName: 'dashboard', + targetFieldName: 'targetDashboard', settings: { relationType: RelationType.ONE_TO_MANY, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-note-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-note-standard-flat-field-metadata.util.ts index 25f8d1422e..a0b56e6632 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-note-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-note-standard-flat-field-metadata.util.ts @@ -264,7 +264,7 @@ export const buildNoteStandardFlatFieldMetadatas = ({ isSystem: true, isNullable: true, targetObjectName: 'attachment', - targetFieldName: 'note', + targetFieldName: 'targetNote', settings: { relationType: RelationType.ONE_TO_MANY, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-opportunity-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-opportunity-standard-flat-field-metadata.util.ts index 43bcf6be8a..8874454a72 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-opportunity-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-opportunity-standard-flat-field-metadata.util.ts @@ -409,7 +409,7 @@ export const buildOpportunityStandardFlatFieldMetadatas = ({ isSystem: true, isNullable: true, targetObjectName: 'attachment', - targetFieldName: 'opportunity', + targetFieldName: 'targetOpportunity', settings: { relationType: RelationType.ONE_TO_MANY, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts index 623b49fec3..04d14fd736 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-person-standard-flat-field-metadata.util.ts @@ -439,7 +439,7 @@ export const buildPersonStandardFlatFieldMetadatas = ({ isSystem: true, isNullable: true, targetObjectName: 'attachment', - targetFieldName: 'person', + targetFieldName: 'targetPerson', settings: { relationType: RelationType.ONE_TO_MANY, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-task-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-task-standard-flat-field-metadata.util.ts index f371e0c6e3..97f78602e9 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-task-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-task-standard-flat-field-metadata.util.ts @@ -308,7 +308,7 @@ export const buildTaskStandardFlatFieldMetadatas = ({ isSystem: true, isNullable: true, targetObjectName: 'attachment', - targetFieldName: 'task', + targetFieldName: 'targetTask', settings: { relationType: RelationType.ONE_TO_MANY, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-standard-flat-field-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-standard-flat-field-metadata.util.ts index b0a0f11f6a..9bdcd6e116 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-standard-flat-field-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/field-metadata/compute-workflow-standard-flat-field-metadata.util.ts @@ -374,7 +374,7 @@ export const buildWorkflowStandardFlatFieldMetadatas = ({ isSystem: true, isNullable: false, targetObjectName: 'attachment', - targetFieldName: 'workflow', + targetFieldName: 'targetWorkflow', settings: { relationType: RelationType.ONE_TO_MANY, }, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-attachment-standard-flat-index-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-attachment-standard-flat-index-metadata.util.ts index 503186457f..121504fa4f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-attachment-standard-flat-index-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/index/compute-attachment-standard-flat-index-metadata.util.ts @@ -21,7 +21,7 @@ export const buildAttachmentStandardFlatIndexMetadatas = ({ workspaceId, context: { indexName: 'taskIdIndex', - relatedFieldNames: ['task'], + relatedFieldNames: ['targetTask'], }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, @@ -33,7 +33,7 @@ export const buildAttachmentStandardFlatIndexMetadatas = ({ workspaceId, context: { indexName: 'noteIdIndex', - relatedFieldNames: ['note'], + relatedFieldNames: ['targetNote'], }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, @@ -45,7 +45,7 @@ export const buildAttachmentStandardFlatIndexMetadatas = ({ workspaceId, context: { indexName: 'personIdIndex', - relatedFieldNames: ['person'], + relatedFieldNames: ['targetPerson'], }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, @@ -57,7 +57,7 @@ export const buildAttachmentStandardFlatIndexMetadatas = ({ workspaceId, context: { indexName: 'companyIdIndex', - relatedFieldNames: ['company'], + relatedFieldNames: ['targetCompany'], }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, @@ -69,7 +69,7 @@ export const buildAttachmentStandardFlatIndexMetadatas = ({ workspaceId, context: { indexName: 'opportunityIdIndex', - relatedFieldNames: ['opportunity'], + relatedFieldNames: ['targetOpportunity'], }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, @@ -81,7 +81,7 @@ export const buildAttachmentStandardFlatIndexMetadatas = ({ workspaceId, context: { indexName: 'dashboardIdIndex', - relatedFieldNames: ['dashboard'], + relatedFieldNames: ['targetDashboard'], }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, @@ -93,7 +93,7 @@ export const buildAttachmentStandardFlatIndexMetadatas = ({ workspaceId, context: { indexName: 'workflowIdIndex', - relatedFieldNames: ['workflow'], + relatedFieldNames: ['targetWorkflow'], }, standardObjectMetadataRelatedEntityIds, dependencyFlatEntityMaps, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/default-feature-flags.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/default-feature-flags.ts index ea92f74cd1..48651d321c 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/default-feature-flags.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/default-feature-flags.ts @@ -2,4 +2,5 @@ import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/featu export const DEFAULT_FEATURE_FLAGS = [ FeatureFlagKey.IS_TIMELINE_ACTIVITY_MIGRATED, + FeatureFlagKey.IS_ATTACHMENT_MIGRATED, ] as const satisfies FeatureFlagKey[]; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/standard-field-ids.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/standard-field-ids.ts index 3c4c2fc41a..4c717c6377 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/standard-field-ids.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/constant/standard-field-ids.ts @@ -38,14 +38,15 @@ export const ATTACHMENT_STANDARD_FIELD_IDS = { updatedBy: '376239d1-3e65-4cb6-b5d8-e0917d43cc93', author: '20202020-6501-4ac5-a4ef-b2f8522ef6cd', activity: '20202020-b569-481b-a13f-9b94e47e54fe', - task: '20202020-51e5-4621-9cf8-215487951c4b', - note: '20202020-4f4b-4503-a6fc-6b982f3dffb5', - person: '20202020-0158-4aa2-965c-5cdafe21ffa2', - company: '20202020-ceab-4a28-b546-73b06b4c08d5', - opportunity: '20202020-7374-499d-bea3-9354890755b5', - dashboard: '20202020-5324-43f3-9dbf-1a33e7de0ce6', - workflow: '20202020-f1e8-4c9d-8a7b-3f5e1d2c9a8b', - custom: '20202020-302d-43b3-9aea-aa4f89282a9f', + targetTask: '20202020-51e5-4621-9cf8-215487951c4b', + targetNote: '20202020-4f4b-4503-a6fc-6b982f3dffb5', + targetPerson: '20202020-0158-4aa2-965c-5cdafe21ffa2', + targetCompany: '20202020-ceab-4a28-b546-73b06b4c08d5', + targetOpportunity: '20202020-7374-499d-bea3-9354890755b5', + targetDashboard: '20202020-5324-43f3-9dbf-1a33e7de0ce6', + targetWorkflow: '20202020-f1e8-4c9d-8a7b-3f5e1d2c9a8b', + targetCustom: '20202020-302d-43b3-9aea-aa4f89282a9f', + targetMorphId: '20202020-f634-435d-ab8d-e1168b375c69', } as const; export const BASE_OBJECT_STANDARD_FIELD_IDS = { diff --git a/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts b/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts index b76c5c91e8..34200a08a7 100644 --- a/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts +++ b/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts @@ -23,19 +23,19 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity { /** @deprecated */ author: EntityRelation | null; authorId: string | null; - task: EntityRelation | null; - taskId: string | null; - note: EntityRelation | null; - noteId: string | null; - person: EntityRelation | null; - personId: string | null; - company: EntityRelation | null; - companyId: string | null; - opportunity: EntityRelation | null; - opportunityId: string | null; - dashboard: EntityRelation | null; - dashboardId: string | null; - workflow: EntityRelation | null; - workflowId: string | null; + targetTask: EntityRelation | null; + targetTaskId: string | null; + targetNote: EntityRelation | null; + targetNoteId: string | null; + targetPerson: EntityRelation | null; + targetPersonId: string | null; + targetCompany: EntityRelation | null; + targetCompanyId: string | null; + targetOpportunity: EntityRelation | null; + targetOpportunityId: string | null; + targetDashboard: EntityRelation | null; + targetDashboardId: string | null; + targetWorkflow: EntityRelation | null; + targetWorkflowId: string | null; custom: EntityRelation; } diff --git a/packages/twenty-server/test/integration/graphql/suites/object-generated/attachments.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/object-generated/attachments.integration-spec.ts index 6a1c1a99ef..6a036eee63 100644 --- a/packages/twenty-server/test/integration/graphql/suites/object-generated/attachments.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/object-generated/attachments.integration-spec.ts @@ -16,13 +16,13 @@ describe('attachmentsResolver (e2e)', () => { createdAt updatedAt deletedAt - taskId - noteId - personId - companyId - opportunityId - petId - surveyResultId + targetTaskId + targetNoteId + targetPersonId + targetCompanyId + targetOpportunityId + targetPetId + targetSurveyResultId } } } @@ -56,13 +56,13 @@ describe('attachmentsResolver (e2e)', () => { expect(attachments).toHaveProperty('createdAt'); expect(attachments).toHaveProperty('updatedAt'); expect(attachments).toHaveProperty('deletedAt'); - expect(attachments).toHaveProperty('taskId'); - expect(attachments).toHaveProperty('noteId'); - expect(attachments).toHaveProperty('personId'); - expect(attachments).toHaveProperty('companyId'); - expect(attachments).toHaveProperty('opportunityId'); - expect(attachments).toHaveProperty('petId'); - expect(attachments).toHaveProperty('surveyResultId'); + expect(attachments).toHaveProperty('targetTaskId'); + expect(attachments).toHaveProperty('targetNoteId'); + expect(attachments).toHaveProperty('targetPersonId'); + expect(attachments).toHaveProperty('targetCompanyId'); + expect(attachments).toHaveProperty('targetOpportunityId'); + expect(attachments).toHaveProperty('targetPetId'); + expect(attachments).toHaveProperty('targetSurveyResultId'); } }); }); diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-create-one-object-metadata-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-create-one-object-metadata-v2.integration-spec.ts.snap index f0a6a9d6ec..043abfe4f1 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-create-one-object-metadata-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-create-one-object-metadata-v2.integration-spec.ts.snap @@ -304,7 +304,7 @@ exports[`Object metadata creation should fail v2 when labelPlural contains only ], "flatEntityMinimalInformation": { "id": Any, - "name": "listinga", + "name": "targetListinga", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -1011,7 +1011,7 @@ exports[`Object metadata creation should fail v2 when labelPlural exceeds maximu ], "flatEntityMinimalInformation": { "id": Any, - "name": "listinga", + "name": "targetListinga", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -1729,7 +1729,7 @@ exports[`Object metadata creation should fail v2 when labelSingular contains onl ], "flatEntityMinimalInformation": { "id": Any, - "name": "listinga", + "name": "targetListinga", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -2436,7 +2436,7 @@ exports[`Object metadata creation should fail v2 when labelSingular exceeds maxi ], "flatEntityMinimalInformation": { "id": Any, - "name": "listinga", + "name": "targetListinga", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -3154,7 +3154,7 @@ exports[`Object metadata creation should fail v2 when labels are identical 1`] = ], "flatEntityMinimalInformation": { "id": Any, - "name": "listinga", + "name": "targetListinga", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -3861,7 +3861,7 @@ exports[`Object metadata creation should fail v2 when labels with whitespaces re ], "flatEntityMinimalInformation": { "id": Any, - "name": "listinga", + "name": "targetListinga", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -4576,7 +4576,7 @@ exports[`Object metadata creation should fail v2 when name exceeds maximum lengt "code": "INVALID_FIELD_INPUT", "message": "Name is too long", "userFriendlyMessage": "Name is too long", - "value": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "value": "targetAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", }, { "code": "OBJECT_METADATA_NOT_FOUND", @@ -4586,7 +4586,7 @@ exports[`Object metadata creation should fail v2 when name exceeds maximum lengt ], "flatEntityMinimalInformation": { "id": Any, - "name": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "name": "targetAaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -5305,7 +5305,7 @@ exports[`Object metadata creation should fail v2 when namePlural has invalid cha ], "flatEntityMinimalInformation": { "id": Any, - "name": "listinga", + "name": "targetListinga", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -6012,7 +6012,7 @@ exports[`Object metadata creation should fail v2 when namePlural is a reserved k ], "flatEntityMinimalInformation": { "id": Any, - "name": "listinga", + "name": "targetListinga", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -6730,7 +6730,7 @@ exports[`Object metadata creation should fail v2 when namePlural is not camelCas ], "flatEntityMinimalInformation": { "id": Any, - "name": "listinga", + "name": "targetListinga", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -7463,13 +7463,13 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only "code": "INVALID_FIELD_INPUT", "message": "Name should be in camelCase", "userFriendlyMessage": "Name should be in camelCase", - "value": "a a", + "value": "targetA a", }, { "code": "INVALID_FIELD_INPUT", "message": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", "userFriendlyMessage": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", - "value": "a a", + "value": "targetA a", }, { "code": "OBJECT_METADATA_NOT_FOUND", @@ -7479,7 +7479,7 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only ], "flatEntityMinimalInformation": { "id": Any, - "name": "a a", + "name": "targetA a", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -8220,18 +8220,6 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only }, { "errors": [ - { - "code": "INVALID_FIELD_INPUT", - "message": "Name is too short", - "userFriendlyMessage": "Name is too short", - "value": "", - }, - { - "code": "INVALID_FIELD_INPUT", - "message": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", - "userFriendlyMessage": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", - "value": "", - }, { "code": "OBJECT_METADATA_NOT_FOUND", "message": "Relation target object metadata not found", @@ -8240,7 +8228,7 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only ], "flatEntityMinimalInformation": { "id": Any, - "name": "", + "name": "target", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -8987,11 +8975,17 @@ exports[`Object metadata creation should fail v2 when nameSingular has invalid c }, { "errors": [ + { + "code": "INVALID_FIELD_INPUT", + "message": "Name should be in camelCase", + "userFriendlyMessage": "Name should be in camelCase", + "value": "targetΜ", + }, { "code": "INVALID_FIELD_INPUT", "message": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", "userFriendlyMessage": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", - "value": "μ", + "value": "targetΜ", }, { "code": "OBJECT_METADATA_NOT_FOUND", @@ -9001,7 +8995,7 @@ exports[`Object metadata creation should fail v2 when nameSingular has invalid c ], "flatEntityMinimalInformation": { "id": Any, - "name": "μ", + "name": "targetΜ", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -9718,12 +9712,6 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved }, { "errors": [ - { - "code": "INVALID_FIELD_INPUT", - "message": "This name is reserved. Use a different name or the system will add "Custom" suffix.", - "userFriendlyMessage": "This name is reserved. Use a different name or the system will add "Custom" suffix.", - "value": "user", - }, { "code": "OBJECT_METADATA_NOT_FOUND", "message": "Relation target object metadata not found", @@ -9732,7 +9720,7 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved ], "flatEntityMinimalInformation": { "id": Any, - "name": "user", + "name": "targetUser", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -10482,13 +10470,13 @@ exports[`Object metadata creation should fail v2 when nameSingular is not camelC "code": "INVALID_FIELD_INPUT", "message": "Name should be in camelCase", "userFriendlyMessage": "Name should be in camelCase", - "value": "Not_Camel_Case", + "value": "targetNot_Camel_Case", }, { "code": "INVALID_FIELD_INPUT", "message": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", "userFriendlyMessage": "Name is not valid: it must start with lowercase letter and contain only alphanumeric letters", - "value": "Not_Camel_Case", + "value": "targetNot_Camel_Case", }, { "code": "OBJECT_METADATA_NOT_FOUND", @@ -10498,7 +10486,7 @@ exports[`Object metadata creation should fail v2 when nameSingular is not camelC ], "flatEntityMinimalInformation": { "id": Any, - "name": "Not_Camel_Case", + "name": "targetNot_Camel_Case", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -11235,7 +11223,7 @@ exports[`Object metadata creation should fail v2 when names are identical 1`] = ], "flatEntityMinimalInformation": { "id": Any, - "name": "fooBar", + "name": "targetFooBar", "objectMetadataId": Any, "universalIdentifier": Any, }, @@ -11942,7 +11930,7 @@ exports[`Object metadata creation should fail v2 when names with whitespaces res ], "flatEntityMinimalInformation": { "id": Any, - "name": "fooBar", + "name": "targetFooBar", "objectMetadataId": Any, "universalIdentifier": Any, },