From e6399b180e58cd330840054f902b8c388aed4863 Mon Sep 17 00:00:00 2001 From: Kartik Pant Date: Tue, 5 May 2026 01:12:12 +0530 Subject: [PATCH] Fix/workspace member avatars 20193 (#20200) Fixes #20193 **Bug Description:** Previously, workspace member avatars failed to render correctly in table views and relation chips (such as the Account Owner field). While the avatar picker dropdown correctly fetched fresh GraphQL data, table views and chips relied on the cached defaultAvatarUrl or avatarUrl fields, which were frequently resolving to empty strings or failing to parse external OAuth URLs correctly. **Root Cause:** - Empty String Defaults: Deleting an avatar or failing to retrieve one defaulted the database state to an empty string ("") instead of null, which caused frontend image components to break rather than render their fallback states. - Missing Permanent URLs: The WorkspaceMemberTranspiler was strictly expecting internal signed URLs. If an avatar was an external OAuth URL, it incorrectly returned an empty string, breaking SSO profile pictures. - Missing Fallbacks: New users lacked a proper Gravatar fallback assignment upon workspace creation. **Changes Made:** - user-workspace.service.ts: Updated the avatar computation logic during user creation to implement a reliable Gravatar fallback and correctly set missing avatars to null instead of empty strings. Updated the storage to use permanent file URLs. - file-url.service.ts: Implemented a getRawFileUrl method to support rendering permanent, non-expiring file URLs for avatars. - workspace-member-transpiler.service.ts: Refactored the URL transpilation logic to gracefully pass through external OAuth URLs (e.g., Google/Microsoft profile pictures) instead of stripping them. - WorkspaceMemberPictureUploader.tsx: Fixed the frontend removal logic so that deleting a profile picture sets the avatarUrl to null (consistent with the backend) rather than an empty string. **Testing:** - Verified that avatars correctly display in relation chips and table views. - Verified that external OAuth avatars load properly. - Verified that deleting an avatar correctly resets the UI to the fallback initials component. Co-authored-by: Charles Bochet Co-authored-by: Cursor --- .../src/metadata/generated/schema.graphql | 1 - .../src/metadata/generated/schema.ts | 2 - .../src/metadata/generated/types.ts | 3 - .../src/generated-metadata/graphql.ts | 5 +- .../uploadWorkspaceMemberProfilePicture.ts | 1 + .../WorkspaceMemberPictureUploader.tsx | 24 ++- ...15958318-remove-user-default-avatar-url.ts | 21 +++ .../2-3/2-3-upgrade-version-command.module.ts | 15 +- ...ge-identifier-field-metadata-id.command.ts | 139 ++++++++++++++++++ .../instance-commands.constant.ts | 2 + ...pace-member-query-result-getter.handler.ts | 5 +- .../admin-panel-statistics.service.ts | 4 +- .../admin-panel-user-lookup.service.ts | 4 +- ...th-context-user-select-fields.constants.ts | 1 - .../services/refresh-token.service.spec.ts | 1 - .../file/file-url/file-url.service.ts | 12 ++ ...two-factor-authentication.resolver.spec.ts | 1 - .../user-workspace.service.spec.ts | 1 - .../user-workspace/user-workspace.service.ts | 16 +- .../engine/core-modules/user/user.entity.ts | 4 - .../utils/from-user-entity-to-flat.util.ts | 1 - ...ompare-and-stringify.constant.spec.ts.snap | 1 + ...configuration-by-metadata-name.constant.ts | 5 +- .../__tests__/flat-entity-update.type-test.ts | 3 + ...eate-standard-object-flat-metadata.util.ts | 10 +- .../universal-flat-entity-update.test-type.ts | 1 + .../update-object-action-handler.service.ts | 23 ++- ...uccessful-sign-up.integration-spec.ts.snap | 2 - .../graphql/utils/get-current-user.util.ts | 1 - ...e-object-metadata.integration-spec.ts.snap | 19 ++- ...te-one-object-metadata.integration-spec.ts | 15 +- 31 files changed, 299 insertions(+), 44 deletions(-) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777915958318-remove-user-default-avatar-url.ts create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/2-3/2-3-workspace-command-1777920000000-backfill-image-identifier-field-metadata-id.command.ts diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index 404f91bb3e..814444f70e 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -863,7 +863,6 @@ type User { firstName: String! lastName: String! email: String! - defaultAvatarUrl: String isEmailVerified: Boolean! disabled: Boolean canImpersonate: Boolean! diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index fb3692a3eb..748387e124 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -648,7 +648,6 @@ export interface User { firstName: Scalars['String'] lastName: Scalars['String'] email: Scalars['String'] - defaultAvatarUrl?: Scalars['String'] isEmailVerified: Scalars['Boolean'] disabled?: Scalars['Boolean'] canImpersonate: Scalars['Boolean'] @@ -3543,7 +3542,6 @@ export interface UserGenqlSelection{ firstName?: boolean | number lastName?: boolean | number email?: boolean | number - defaultAvatarUrl?: boolean | number isEmailVerified?: boolean | number disabled?: boolean | number canImpersonate?: boolean | number diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index 16392b13a3..8de449535f 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -1830,9 +1830,6 @@ export default { "email": [ 1 ], - "defaultAvatarUrl": [ - 1 - ], "isEmailVerified": [ 6 ], diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index b76672d4bf..1f05f2f534 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -5482,7 +5482,6 @@ export type User = { createdAt: Scalars['DateTime']; currentUserWorkspace?: Maybe; currentWorkspace?: Maybe; - defaultAvatarUrl?: Maybe; deletedAt?: Maybe; deletedWorkspaceMembers?: Maybe>; disabled?: Maybe; @@ -7235,7 +7234,7 @@ export type UploadWorkspaceMemberProfilePictureMutationVariables = Exact<{ }>; -export type UploadWorkspaceMemberProfilePictureMutation = { __typename?: 'Mutation', uploadWorkspaceMemberProfilePicture: { __typename?: 'FileWithSignedUrl', url: string } }; +export type UploadWorkspaceMemberProfilePictureMutation = { __typename?: 'Mutation', uploadWorkspaceMemberProfilePicture: { __typename?: 'FileWithSignedUrl', id: string, url: string } }; export type UpdateUserEmailMutationVariables = Exact<{ newEmail: Scalars['String']; @@ -8068,7 +8067,7 @@ export const EnterpriseCheckoutSessionDocument = {"kind":"Document","definitions export const EnterprisePortalSessionDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EnterprisePortalSession"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"returnUrlPath"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enterprisePortalSession"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"returnUrlPath"},"value":{"kind":"Variable","name":{"kind":"Name","value":"returnUrlPath"}}}]}]}}]} as unknown as DocumentNode; export const EnterpriseSubscriptionStatusDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"EnterpriseSubscriptionStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"enterpriseSubscriptionStatus"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"licensee"}},{"kind":"Field","name":{"kind":"Name","value":"expiresAt"}},{"kind":"Field","name":{"kind":"Name","value":"cancelAt"}},{"kind":"Field","name":{"kind":"Name","value":"currentPeriodEnd"}},{"kind":"Field","name":{"kind":"Name","value":"isCancellationScheduled"}}]}}]}}]} as unknown as DocumentNode; export const UpdateLabPublicFeatureFlagDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateLabPublicFeatureFlag"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateLabPublicFeatureFlagInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateLabPublicFeatureFlag"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"key"}},{"kind":"Field","name":{"kind":"Name","value":"value"}}]}}]}}]} as unknown as DocumentNode; -export const UploadWorkspaceMemberProfilePictureDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UploadWorkspaceMemberProfilePicture"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"file"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Upload"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uploadWorkspaceMemberProfilePicture"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"file"},"value":{"kind":"Variable","name":{"kind":"Name","value":"file"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]} as unknown as DocumentNode; +export const UploadWorkspaceMemberProfilePictureDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UploadWorkspaceMemberProfilePicture"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"file"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"Upload"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"uploadWorkspaceMemberProfilePicture"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"file"},"value":{"kind":"Variable","name":{"kind":"Name","value":"file"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"url"}}]}}]}}]} as unknown as DocumentNode; export const UpdateUserEmailDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateUserEmail"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"newEmail"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"verifyEmailRedirectPath"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateUserEmail"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"newEmail"},"value":{"kind":"Variable","name":{"kind":"Name","value":"newEmail"}}},{"kind":"Argument","name":{"kind":"Name","value":"verifyEmailRedirectPath"},"value":{"kind":"Variable","name":{"kind":"Name","value":"verifyEmailRedirectPath"}}}]}]}}]} as unknown as DocumentNode; export const UpdateWorkspaceMemberSettingsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateWorkspaceMemberSettings"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateWorkspaceMemberSettingsInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateWorkspaceMemberSettings"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const CreateOneRoleDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"CreateOneRole"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"createRoleInput"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"CreateRoleInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"createOneRole"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"createRoleInput"},"value":{"kind":"Variable","name":{"kind":"Name","value":"createRoleInput"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"RoleFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RoleFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Role"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateAllSettings"}},{"kind":"Field","name":{"kind":"Name","value":"canAccessAllTools"}},{"kind":"Field","name":{"kind":"Name","value":"isEditable"}},{"kind":"Field","name":{"kind":"Name","value":"canReadAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canUpdateAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canSoftDeleteAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canDestroyAllObjectRecords"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToUsers"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToAgents"}},{"kind":"Field","name":{"kind":"Name","value":"canBeAssignedToApiKeys"}}]}}]} as unknown as DocumentNode; diff --git a/packages/twenty-front/src/modules/settings/members/graphql/mutations/uploadWorkspaceMemberProfilePicture.ts b/packages/twenty-front/src/modules/settings/members/graphql/mutations/uploadWorkspaceMemberProfilePicture.ts index 073498c459..5958e8c38c 100644 --- a/packages/twenty-front/src/modules/settings/members/graphql/mutations/uploadWorkspaceMemberProfilePicture.ts +++ b/packages/twenty-front/src/modules/settings/members/graphql/mutations/uploadWorkspaceMemberProfilePicture.ts @@ -3,6 +3,7 @@ import { gql } from '@apollo/client'; export const UPLOAD_WORKSPACE_MEMBER_PROFILE_PICTURE = gql` mutation UploadWorkspaceMemberProfilePicture($file: Upload!) { uploadWorkspaceMemberProfilePicture(file: $file) { + id url } } diff --git a/packages/twenty-front/src/modules/settings/workspace-member/components/WorkspaceMemberPictureUploader.tsx b/packages/twenty-front/src/modules/settings/workspace-member/components/WorkspaceMemberPictureUploader.tsx index a91d64c449..869a34037d 100644 --- a/packages/twenty-front/src/modules/settings/workspace-member/components/WorkspaceMemberPictureUploader.tsx +++ b/packages/twenty-front/src/modules/settings/workspace-member/components/WorkspaceMemberPictureUploader.tsx @@ -6,9 +6,12 @@ import { useCanEditProfileField } from '@/settings/profile/hooks/useCanEditProfi import { useUpdateWorkspaceMemberSettings } from '@/settings/profile/hooks/useUpdateWorkspaceMemberSettings'; import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar'; import { ImageInput } from '@/ui/input/components/ImageInput'; +import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState'; import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue'; import { useMutation } from '@apollo/client/react'; +import { FileFolder } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { UploadWorkspaceMemberProfilePictureDocument } from '~/generated-metadata/graphql'; import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull'; @@ -32,6 +35,9 @@ export const WorkspaceMemberPictureUploader = ({ useState(null); const currentWorkspaceMember = useAtomStateValue(currentWorkspaceMemberState); + const setCurrentWorkspaceMember = useSetAtomState( + currentWorkspaceMemberState, + ); const [uploadPicture] = useMutation( UploadWorkspaceMemberProfilePictureDocument, @@ -66,19 +72,27 @@ export const WorkspaceMemberPictureUploader = ({ }, }); - const signedFile = data?.uploadWorkspaceMemberProfilePicture; - if (!isDefined(signedFile)) { + const uploadedFile = data?.uploadWorkspaceMemberProfilePicture; + if (!isDefined(uploadedFile)) { throw new Error('Avatar upload failed'); } - newAvatarUrl = signedFile.url; + newAvatarUrl = `${REACT_APP_SERVER_BASE_URL}/file/${FileFolder.CorePicture}/${uploadedFile.id}`; await updateWorkspaceMemberSettings({ workspaceMemberId, update: { avatarUrl: newAvatarUrl }, }); + const signedUrl = uploadedFile.url; + + if (isDefined(signedUrl) && isEditingSelf) { + setCurrentWorkspaceMember((previous) => + previous ? { ...previous, avatarUrl: signedUrl } : previous, + ); + } + if (isDefined(onAvatarUpdated)) { - onAvatarUpdated(newAvatarUrl); + onAvatarUpdated(signedUrl ?? newAvatarUrl); } setUploadController(null); @@ -104,7 +118,7 @@ export const WorkspaceMemberPictureUploader = ({ try { await updateWorkspaceMemberSettings({ workspaceMemberId, - update: { avatarUrl: '' }, + update: { avatarUrl: null }, }); if (isDefined(onAvatarUpdated)) { diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777915958318-remove-user-default-avatar-url.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777915958318-remove-user-default-avatar-url.ts new file mode 100644 index 0000000000..a5a720b992 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777915958318-remove-user-default-avatar-url.ts @@ -0,0 +1,21 @@ +import { QueryRunner } from 'typeorm'; + +import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator'; +import { FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface'; + +@RegisteredInstanceCommand('2.3.0', 1777915958318) +export class RemoveUserDefaultAvatarUrlFastInstanceCommand + implements FastInstanceCommand +{ + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "core"."user" DROP COLUMN "defaultAvatarUrl"', + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + 'ALTER TABLE "core"."user" ADD "defaultAvatarUrl" character varying', + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-3/2-3-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-3/2-3-upgrade-version-command.module.ts index db90d5e394..7d68dd2094 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-3/2-3-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-3/2-3-upgrade-version-command.module.ts @@ -1,7 +1,18 @@ import { Module } from '@nestjs/common'; +import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module'; +import { BackfillImageIdentifierFieldMetadataIdCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-workspace-command-1777920000000-backfill-image-identifier-field-metadata-id.command'; +import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; +import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; +import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module'; + @Module({ - imports: [], - providers: [], + imports: [ + ApplicationModule, + WorkspaceCacheModule, + WorkspaceIteratorModule, + WorkspaceMigrationModule, + ], + providers: [BackfillImageIdentifierFieldMetadataIdCommand], }) export class V2_3_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-3/2-3-workspace-command-1777920000000-backfill-image-identifier-field-metadata-id.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-3/2-3-workspace-command-1777920000000-backfill-image-identifier-field-metadata-id.command.ts new file mode 100644 index 0000000000..89a42c492c --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-3/2-3-workspace-command-1777920000000-backfill-image-identifier-field-metadata-id.command.ts @@ -0,0 +1,139 @@ +import { Command } from 'nest-commander'; +import { STANDARD_OBJECTS } from 'twenty-shared/metadata'; +import { isDefined } from 'twenty-shared/utils'; + +import { ActiveOrSuspendedWorkspaceCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspace.command-runner'; +import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service'; +import { type RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspace.command-runner'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { RegisteredWorkspaceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-workspace-command.decorator'; +import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; +import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; +import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; +import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; + +const WORKSPACE_MEMBER_UNIVERSAL_IDENTIFIER = + STANDARD_OBJECTS.workspaceMember.universalIdentifier; + +const AVATAR_URL_FIELD_UNIVERSAL_IDENTIFIER = + STANDARD_OBJECTS.workspaceMember.fields.avatarUrl.universalIdentifier; + +@RegisteredWorkspaceCommand('2.3.0', 1777920000000) +@Command({ + name: 'upgrade:2-3:backfill-image-identifier-field-metadata-id', + description: + 'Backfill imageIdentifierFieldMetadataId on workspaceMember for workspaces where it was never set.', +}) +export class BackfillImageIdentifierFieldMetadataIdCommand extends ActiveOrSuspendedWorkspaceCommandRunner { + constructor( + protected readonly workspaceIteratorService: WorkspaceIteratorService, + private readonly applicationService: ApplicationService, + private readonly workspaceCacheService: WorkspaceCacheService, + private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, + ) { + super(workspaceIteratorService); + } + + override async runOnWorkspace({ + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + const isDryRun = options.dryRun ?? false; + + const { flatObjectMetadataMaps, flatFieldMetadataMaps } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatObjectMetadataMaps', + 'flatFieldMetadataMaps', + ]); + + const existingObject = + findFlatEntityByUniversalIdentifier({ + flatEntityMaps: flatObjectMetadataMaps, + universalIdentifier: WORKSPACE_MEMBER_UNIVERSAL_IDENTIFIER, + }); + + if (!isDefined(existingObject)) { + this.logger.log( + `workspaceMember object not found for workspace ${workspaceId}, skipping`, + ); + + return; + } + + if ( + existingObject.imageIdentifierFieldMetadataUniversalIdentifier === + AVATAR_URL_FIELD_UNIVERSAL_IDENTIFIER + ) { + this.logger.log( + `imageIdentifierFieldMetadataId already set for workspace ${workspaceId}, skipping`, + ); + + return; + } + + const existingField = + findFlatEntityByUniversalIdentifier({ + flatEntityMaps: flatFieldMetadataMaps, + universalIdentifier: AVATAR_URL_FIELD_UNIVERSAL_IDENTIFIER, + }); + + if (!isDefined(existingField)) { + this.logger.log( + `avatarUrl field not found for workspace ${workspaceId}, skipping`, + ); + + return; + } + + if (isDryRun) { + this.logger.log( + `[DRY RUN] Would backfill imageIdentifierFieldMetadataId on workspaceMember for workspace ${workspaceId}`, + ); + + return; + } + + const { twentyStandardFlatApplication } = + await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( + { workspaceId }, + ); + + const validateAndBuildResult = + await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration( + { + allFlatEntityOperationByMetadataName: { + objectMetadata: { + flatEntityToCreate: [], + flatEntityToDelete: [], + flatEntityToUpdate: [ + { + ...existingObject, + imageIdentifierFieldMetadataUniversalIdentifier: + AVATAR_URL_FIELD_UNIVERSAL_IDENTIFIER, + }, + ], + }, + }, + workspaceId, + isSystemBuild: true, + applicationUniversalIdentifier: + twentyStandardFlatApplication.universalIdentifier, + }, + ); + + if (validateAndBuildResult.status === 'fail') { + this.logger.error( + `Failed to backfill imageIdentifierFieldMetadataId for workspace ${workspaceId}:\n${JSON.stringify(validateAndBuildResult, null, 2)}`, + ); + + throw new Error( + `Failed to backfill imageIdentifierFieldMetadataId for workspace ${workspaceId}`, + ); + } + + this.logger.log( + `Backfilled imageIdentifierFieldMetadataId on workspaceMember for workspace ${workspaceId}`, + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts index 0e734e6c4b..db50b39235 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/instance-commands.constant.ts @@ -22,6 +22,7 @@ import { AddUpgradeMigrationWorkspaceIdIndexFastInstanceCommand } from 'src/data import { AddCacheTokensToAgentChatThreadFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-2/2-2-instance-command-fast-1777455269302-add-cache-tokens-to-agent-chat-thread'; import { AddLogoToApplicationFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-2/2-2-instance-command-fast-1777539664664-add-logo-to-application'; import { AddDeletedAtToAgentChatThreadFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777682000000-add-deleted-at-to-agent-chat-thread'; +import { RemoveUserDefaultAvatarUrlFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-3/2-3-instance-command-fast-1777915958318-remove-user-default-avatar-url'; export const INSTANCE_COMMANDS = [ AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand, @@ -46,4 +47,5 @@ export const INSTANCE_COMMANDS = [ AddCacheTokensToAgentChatThreadFastInstanceCommand, AddLogoToApplicationFastInstanceCommand, AddDeletedAtToAgentChatThreadFastInstanceCommand, + RemoveUserDefaultAvatarUrlFastInstanceCommand, ]; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler.ts index e6cad17142..af5f5c3777 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/handlers/workspace-member-query-result-getter.handler.ts @@ -26,7 +26,10 @@ export class WorkspaceMemberQueryResultGetterHandler ); if (!isDefined(fileId)) { - return workspaceMember; + return { + ...workspaceMember, + avatarUrl: '', + }; } const signedUrl = this.fileUrlService.signFileByIdUrl({ diff --git a/packages/twenty-server/src/engine/core-modules/admin-panel/services/admin-panel-statistics.service.ts b/packages/twenty-server/src/engine/core-modules/admin-panel/services/admin-panel-statistics.service.ts index 070333153e..eafbf98eab 100644 --- a/packages/twenty-server/src/engine/core-modules/admin-panel/services/admin-panel-statistics.service.ts +++ b/packages/twenty-server/src/engine/core-modules/admin-panel/services/admin-panel-statistics.service.ts @@ -153,7 +153,7 @@ export class AdminPanelStatisticsService { >(); for (const user of users) { - signedAvatarUrlByUserId.set(user.id, user.defaultAvatarUrl ?? null); + signedAvatarUrlByUserId.set(user.id, null); for (const userWorkspace of user.userWorkspaces) { const workspace = userWorkspace.workspace; @@ -169,7 +169,7 @@ export class AdminPanelStatisticsService { entry.fallbackAvatarUrlsByUserId.set( user.id, - userWorkspace.defaultAvatarUrl ?? user.defaultAvatarUrl ?? null, + userWorkspace.defaultAvatarUrl ?? null, ); contextsByWorkspaceId.set(workspace.id, entry); } diff --git a/packages/twenty-server/src/engine/core-modules/admin-panel/services/admin-panel-user-lookup.service.ts b/packages/twenty-server/src/engine/core-modules/admin-panel/services/admin-panel-user-lookup.service.ts index 77a1a896a7..253f65a087 100644 --- a/packages/twenty-server/src/engine/core-modules/admin-panel/services/admin-panel-user-lookup.service.ts +++ b/packages/twenty-server/src/engine/core-modules/admin-panel/services/admin-panel-user-lookup.service.ts @@ -44,9 +44,7 @@ export class AdminPanelUserLookupService { .filter((workspaceUser) => isDefined(workspaceUser.user)) .map((workspaceUser) => [ workspaceUser.user.id, - workspaceUser.defaultAvatarUrl ?? - workspaceUser.user.defaultAvatarUrl ?? - null, + workspaceUser.defaultAvatarUrl ?? null, ]), ); } diff --git a/packages/twenty-server/src/engine/core-modules/auth/constants/auth-context-user-select-fields.constants.ts b/packages/twenty-server/src/engine/core-modules/auth/constants/auth-context-user-select-fields.constants.ts index a8fb37f52d..6987b2f76b 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/constants/auth-context-user-select-fields.constants.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/constants/auth-context-user-select-fields.constants.ts @@ -5,7 +5,6 @@ export const AUTH_CONTEXT_USER_SELECT_FIELDS = [ 'firstName', 'lastName', 'email', - 'defaultAvatarUrl', 'isEmailVerified', 'disabled', 'canImpersonate', diff --git a/packages/twenty-server/src/engine/core-modules/auth/token/services/refresh-token.service.spec.ts b/packages/twenty-server/src/engine/core-modules/auth/token/services/refresh-token.service.spec.ts index e3029f7350..fd116bb4eb 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/token/services/refresh-token.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/token/services/refresh-token.service.spec.ts @@ -85,7 +85,6 @@ describe('RefreshTokenService', () => { firstName: 'John', lastName: 'Doe', email: 'john.doe@example.com', - defaultAvatarUrl: '', } as UserEntity; jest diff --git a/packages/twenty-server/src/engine/core-modules/file/file-url/file-url.service.ts b/packages/twenty-server/src/engine/core-modules/file/file-url/file-url.service.ts index 11cb50ce26..6072a21954 100644 --- a/packages/twenty-server/src/engine/core-modules/file/file-url/file-url.service.ts +++ b/packages/twenty-server/src/engine/core-modules/file/file-url/file-url.service.ts @@ -66,4 +66,16 @@ export class FileUrlService { return `${serverUrl}/file/${fileFolder}/${fileId}?token=${token}`; } + + getLegacyWorkspaceMemberAvatarUrl({ + fileId, + fileFolder, + }: { + fileId: string; + fileFolder: FileFolder; + }): string { + const serverUrl = this.twentyConfigService.get('SERVER_URL'); + + return `${serverUrl}/file/${fileFolder}/${fileId}`; + } } diff --git a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.resolver.spec.ts b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.resolver.spec.ts index 96f9dc8bf7..52c7297a0f 100644 --- a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.resolver.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.resolver.spec.ts @@ -61,7 +61,6 @@ describe('TwoFactorAuthenticationResolver', () => { firstName: '', lastName: '', email: 'test@example.com', - defaultAvatarUrl: '', isEmailVerified: true, disabled: false, canImpersonate: false, diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts index 47688d54cd..72ce01aea9 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.spec.ts @@ -248,7 +248,6 @@ describe('UserWorkspaceService', () => { email: 'test@example.com', firstName: 'John', lastName: 'Doe', - defaultAvatarUrl: 'avatar-url', locale: 'en', isEmailVerified: false, disabled: false, diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts index c9d9ab6415..4cc32042e9 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.service.ts @@ -162,7 +162,7 @@ export class UserWorkspaceService extends TypeOrmQueryService ({ firstName: entity.firstName, lastName: entity.lastName, email: entity.email, - defaultAvatarUrl: entity.defaultAvatarUrl, isEmailVerified: entity.isEmailVerified, disabled: entity.disabled, canImpersonate: entity.canImpersonate, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap index 2a072bc3a4..1b3b1c980c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap @@ -145,6 +145,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma "labelIdentifierFieldMetadataUniversalIdentifier", "standardOverrides", "isSearchable", + "imageIdentifierFieldMetadataUniversalIdentifier", ], "propertiesToStringify": [ "standardOverrides", diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts index 5ecc4b4fab..01f501d70e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts @@ -242,9 +242,10 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = { universalProperty: undefined, }, imageIdentifierFieldMetadataId: { - toCompare: false, + toCompare: true, toStringify: false, - universalProperty: undefined, + // @ts-expect-error remove once https://github.com/twentyhq/core-team-issues/issues/2172 has been resolved + universalProperty: 'imageIdentifierFieldMetadataUniversalIdentifier', }, targetTableName: { toCompare: false, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/__tests__/flat-entity-update.type-test.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/__tests__/flat-entity-update.type-test.ts index f396fa8b2c..3e82b3e7df 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/__tests__/flat-entity-update.type-test.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/types/__tests__/flat-entity-update.type-test.ts @@ -49,6 +49,8 @@ type Assertions = [ | 'labelPlural' | 'labelIdentifierFieldMetadataId' | 'labelIdentifierFieldMetadataUniversalIdentifier' + | 'imageIdentifierFieldMetadataId' + | 'imageIdentifierFieldMetadataUniversalIdentifier' | 'isSearchable' > >, @@ -58,6 +60,7 @@ type Assertions = [ FlatEntityUpdate<'objectMetadata'>, { labelIdentifierFieldMetadataUniversalIdentifier?: never; + imageIdentifierFieldMetadataUniversalIdentifier?: never; } > >, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-object-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-object-flat-metadata.util.ts index 680212639a..95e02c1011 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-object-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/object-metadata/create-standard-object-flat-metadata.util.ts @@ -61,6 +61,14 @@ export const createStandardObjectFlatMetadata = < labelIdentifierFieldMetadataName ].universalIdentifier; + const imageIdentifierFieldMetadataUniversalIdentifier = + imageIdentifierFieldMetadataName + ? // @ts-expect-error ignore + STANDARD_OBJECTS[nameSingular as keyof typeof STANDARD_OBJECTS].fields[ + imageIdentifierFieldMetadataName + ].universalIdentifier + : null; + return { universalIdentifier, applicationId: twentyStandardApplicationId, @@ -108,6 +116,6 @@ export const createStandardObjectFlatMetadata = < viewUniversalIdentifiers: [], indexMetadataUniversalIdentifiers: [], labelIdentifierFieldMetadataUniversalIdentifier, - imageIdentifierFieldMetadataUniversalIdentifier: null, + imageIdentifierFieldMetadataUniversalIdentifier, }; }; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/__tests__/universal-flat-entity-update.test-type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/__tests__/universal-flat-entity-update.test-type.ts index a8f20cc309..1568ea4b6f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/__tests__/universal-flat-entity-update.test-type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/__tests__/universal-flat-entity-update.test-type.ts @@ -35,6 +35,7 @@ type Assertions = [ | 'labelSingular' | 'labelPlural' | 'labelIdentifierFieldMetadataUniversalIdentifier' + | 'imageIdentifierFieldMetadataUniversalIdentifier' | 'isSearchable' > >, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/object/services/update-object-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/object/services/update-object-action-handler.service.ts index ca3ce0daee..5602af10a8 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/object/services/update-object-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/object/services/update-object-action-handler.service.ts @@ -56,8 +56,11 @@ export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerAc }); // TODO remove once https://github.com/twentyhq/core-team-issues/issues/2172 has been resolved - const { labelIdentifierFieldMetadataUniversalIdentifier, ...restUpdate } = - action.update; + const { + labelIdentifierFieldMetadataUniversalIdentifier, + imageIdentifierFieldMetadataUniversalIdentifier, + ...restUpdate + } = action.update; const transpiledUpdate: FlatEntityUpdate<'objectMetadata'> = { ...restUpdate, @@ -79,6 +82,22 @@ export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerAc transpiledUpdate.labelIdentifierFieldMetadataId = flatFieldMetadata.id; } + if (isDefined(imageIdentifierFieldMetadataUniversalIdentifier)) { + const flatFieldMetadata = findFlatEntityByUniversalIdentifier({ + flatEntityMaps: allFlatEntityMaps.flatFieldMetadataMaps, + universalIdentifier: imageIdentifierFieldMetadataUniversalIdentifier, + }); + + if (!isDefined(flatFieldMetadata)) { + throw new FlatEntityMapsException( + `Could not resolve imageIdentifierFieldMetadataUniversalIdentifier to imageIdentifierFieldMetadataId: no fieldMetadata found for universal identifier ${imageIdentifierFieldMetadataUniversalIdentifier}`, + FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND, + ); + } + + transpiledUpdate.imageIdentifierFieldMetadataId = flatFieldMetadata.id; + } + return { type: 'update', metadataName: 'objectMetadata', diff --git a/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/__snapshots__/successful-sign-up.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/__snapshots__/successful-sign-up.integration-spec.ts.snap index 4a801914d7..3d5883addb 100644 --- a/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/__snapshots__/successful-sign-up.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/__snapshots__/successful-sign-up.integration-spec.ts.snap @@ -7,7 +7,6 @@ exports[`Successful User Sign Up (integration) should sign up, delete and signup "createdAt": Any, "currentUserWorkspace": null, "currentWorkspace": null, - "defaultAvatarUrl": null, "deletedAt": null, "disabled": false, "email": "test-123@example.com", @@ -41,7 +40,6 @@ exports[`Successful User Sign Up (integration) should sign up, delete and signup "createdAt": Any, "currentUserWorkspace": null, "currentWorkspace": null, - "defaultAvatarUrl": null, "deletedAt": null, "disabled": false, "email": "test-123@example.com", diff --git a/packages/twenty-server/test/integration/graphql/utils/get-current-user.util.ts b/packages/twenty-server/test/integration/graphql/utils/get-current-user.util.ts index b95fca3dcb..01a255047c 100644 --- a/packages/twenty-server/test/integration/graphql/utils/get-current-user.util.ts +++ b/packages/twenty-server/test/integration/graphql/utils/get-current-user.util.ts @@ -24,7 +24,6 @@ export const getCurrentUser = async ({ email firstName lastName - defaultAvatarUrl isEmailVerified disabled canImpersonate diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap index 13144da032..4bbac4eef7 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-object-metadata.integration-spec.ts.snap @@ -1,4 +1,21 @@ -// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Object metadata update should fail when imageIdentifier is not a uuid 1`] = ` +[ + { + "extensions": { + "code": "GRAPHQL_VALIDATION_FAILED", + "http": { + "status": 400, + }, + "userFriendlyMessage": "An error occurred.", + "value": "not-a-uuid", + }, + "message": "Invalid UUID: 'not-a-uuid'", + "name": "ValidationError", + }, +] +`; exports[`Object metadata update should fail when labelIdentifier is not a TEXT or NAME field 1`] = ` [ diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-update-one-object-metadata.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-update-one-object-metadata.integration-spec.ts index 8f2f7183e5..e0166a4b2d 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-update-one-object-metadata.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/failing-update-one-object-metadata.integration-spec.ts @@ -43,7 +43,20 @@ const labelIdentifierFailingTestsUseCase: CreateOneObjectMetadataItemTestingCont }, ]; -const allTestsUseCases = [...labelIdentifierFailingTestsUseCase]; +const imageIdentifierFailingTestsUseCase: CreateOneObjectMetadataItemTestingContext = + [ + { + title: 'when imageIdentifier is not a uuid', + context: { + imageIdentifierFieldMetadataId: 'not-a-uuid', + }, + }, + ]; + +const allTestsUseCases = [ + ...labelIdentifierFailingTestsUseCase, + ...imageIdentifierFailingTestsUseCase, +]; describe('Object metadata update should fail', () => { let objectMetadataId: string;