Compare commits

..
Author SHA1 Message Date
Sonarly Claude Code 7f9e2a70d9 fix: move IMAP folder sync outside database transaction in processAccount
https://sonarly.com/issue/18767?type=bug

The `SaveImapSmtpCaldavAccount` mutation fails with `QueryRunnerAlreadyReleasedError` because external IMAP folder discovery (slow network I/O) runs inside a database transaction, causing the PostgreSQL connection to be terminated during the idle period.

Fix: Moved createMessageChannelService.createMessageChannel() and createCalendarChannelService.createCalendarChannel() outside the workspaceDataSource.transaction() block in ImapSmtpCalDavAPIService.processAccount(). The transaction now only wraps connectedAccountDataAccessService.save().

This eliminates the window where a PG connection sits idle in a transaction while IMAP folder discovery performs multiple network round trips (connect to mail server, list folders, check UID validity per folder). That idle window was causing the PG connection to be terminated by idle_in_transaction_session_timeout, leading to QueryRunnerAlreadyReleasedError when TypeORM attempted to COMMIT.

This is safe because WorkspaceEntityManager.save() always creates its own query runner via createQueryRunnerForEntityPersistExecutor() and releases it in .finally() — it never actually participated in the outer transaction. The change preserves identical data flow while eliminating the timeout risk.
2026-03-26 16:52:43 +00:00
515 changed files with 8351 additions and 18685 deletions
+2 -15
View File
@@ -36,9 +36,6 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -53,16 +50,10 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
clickhouse:
image: clickhouse/clickhouse-server:25.8.8
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
CLICKHOUSE_PASSWORD: clickhousePassword
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
@@ -152,6 +143,7 @@ jobs:
set_env_var "CLICKHOUSE_PASSWORD" "clickhousePassword"
npx nx run twenty-server:database:init:prod
npx nx run twenty-server:database:migrate:prod
- name: Seed current branch database with test data
run: |
@@ -304,6 +296,7 @@ jobs:
set_env_var "CLICKHOUSE_PASSWORD" "clickhousePassword"
npx nx run twenty-server:database:init:prod
npx nx run twenty-server:database:migrate:prod
- name: Seed main branch database with test data
run: |
@@ -402,12 +395,6 @@ jobs:
# Clean up temp directory
rm -rf /tmp/current-branch-files
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Install OpenAPI Diff Tool
run: |
# Using the Java-based OpenAPITools/openapi-diff via Docker
-6
View File
@@ -37,9 +37,6 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -54,9 +51,6 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
env:
-6
View File
@@ -26,9 +26,6 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -43,9 +40,6 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
steps:
-6
View File
@@ -57,9 +57,6 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -74,9 +71,6 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
env:
+1 -15
View File
@@ -84,9 +84,6 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -101,9 +98,6 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
steps:
@@ -128,6 +122,7 @@ jobs:
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "default";'
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
npx nx run twenty-server:database:init:prod
npx nx run twenty-server:database:migrate:prod
- name: Worker / Run
run: |
timeout 30s npx nx run twenty-server:worker || exit_code=$?
@@ -232,9 +227,6 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -249,16 +241,10 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
clickhouse:
image: clickhouse/clickhouse-server:25.8.8
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
CLICKHOUSE_PASSWORD: clickhousePassword
CLICKHOUSE_URL: "http://default:clickhousePassword@localhost:8123/twenty"
@@ -27,11 +27,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Run compose
run: |
echo "Patching docker-compose.yml..."
@@ -102,11 +97,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Create frontend placeholder
run: |
mkdir -p packages/twenty-front/build
-3
View File
@@ -27,9 +27,6 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
-6
View File
@@ -30,9 +30,6 @@ jobs:
services:
postgres:
image: twentycrm/twenty-postgres-spilo
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
env:
PGUSER_SUPERUSER: postgres
PGPASSWORD_SUPERUSER: postgres
@@ -47,9 +44,6 @@ jobs:
--health-retries 5
redis:
image: redis
credentials:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
ports:
- 6379:6379
steps:
@@ -17,12 +17,6 @@ jobs:
with:
ref: ${{ github.event.client_payload.pr_head_sha }}
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_PASSWORD }}
- name: Run compose setup
run: |
echo "Patching docker-compose.yml..."
-1
View File
@@ -203,7 +203,6 @@
"packages/twenty-utils",
"packages/twenty-zapier",
"packages/twenty-website",
"packages/twenty-website-new",
"packages/twenty-docs",
"packages/twenty-e2e-testing",
"packages/twenty-shared",
@@ -343,7 +343,6 @@ type Field {
defaultValue: JSON
options: JSON
settings: JSON
objectMetadataId: UUID!
isLabelSyncedWithName: Boolean
morphId: UUID
createdAt: DateTime!
@@ -541,7 +540,6 @@ input FieldFilter {
isActive: BooleanFieldComparison
isSystem: BooleanFieldComparison
isUIReadOnly: BooleanFieldComparison
objectMetadataId: UUIDFilterComparison
}
input IndexFilter {
@@ -2275,11 +2273,6 @@ type FieldConnection {
edges: [FieldEdge!]!
}
type LogicFunctionLogs {
"""Execution Logs"""
logs: String!
}
type DeleteTwoFactorAuthenticationMethod {
"""Boolean that confirms query was dispatched"""
success: Boolean!
@@ -2600,6 +2593,11 @@ type UsageAnalytics {
userDailyUsage: UsageUserDaily
}
type LogicFunctionLogs {
"""Execution Logs"""
logs: String!
}
type FrontComponent {
id: UUID!
name: String!
@@ -3092,7 +3090,6 @@ enum AllMetadataName {
navigationMenuItem
permissionFlag
objectPermission
fieldPermission
frontComponent
webhook
}
@@ -3504,7 +3501,6 @@ type Mutation {
createViewGroup(input: CreateViewGroupInput!): ViewGroup!
createManyViewGroups(inputs: [CreateViewGroupInput!]!): [ViewGroup!]!
updateViewGroup(input: UpdateViewGroupInput!): ViewGroup!
updateManyViewGroups(inputs: [UpdateViewGroupInput!]!): [ViewGroup!]!
deleteViewGroup(input: DeleteViewGroupInput!): ViewGroup!
destroyViewGroup(input: DestroyViewGroupInput!): ViewGroup!
updateMessageFolder(input: UpdateMessageFolderInput!): MessageFolder!
@@ -4276,8 +4272,8 @@ input CreateFieldInput {
defaultValue: JSON
options: JSON
settings: JSON
objectMetadataId: UUID!
isLabelSyncedWithName: Boolean
objectMetadataId: UUID!
isRemoteCreation: Boolean
relationCreationPayload: JSON
morphRelationsCreationPayload: [JSON!]
@@ -4305,7 +4301,6 @@ input UpdateFieldInput {
defaultValue: JSON
options: JSON
settings: JSON
objectMetadataId: UUID
isLabelSyncedWithName: Boolean
morphRelationsUpdatePayload: [JSON!]
}
@@ -296,7 +296,6 @@ export interface Field {
defaultValue?: Scalars['JSON']
options?: Scalars['JSON']
settings?: Scalars['JSON']
objectMetadataId: Scalars['UUID']
isLabelSyncedWithName?: Scalars['Boolean']
morphId?: Scalars['UUID']
createdAt: Scalars['DateTime']
@@ -1938,12 +1937,6 @@ export interface FieldConnection {
__typename: 'FieldConnection'
}
export interface LogicFunctionLogs {
/** Execution Logs */
logs: Scalars['String']
__typename: 'LogicFunctionLogs'
}
export interface DeleteTwoFactorAuthenticationMethod {
/** Boolean that confirms query was dispatched */
success: Scalars['Boolean']
@@ -2305,6 +2298,12 @@ export interface UsageAnalytics {
__typename: 'UsageAnalytics'
}
export interface LogicFunctionLogs {
/** Execution Logs */
logs: Scalars['String']
__typename: 'LogicFunctionLogs'
}
export interface FrontComponent {
id: Scalars['UUID']
name: Scalars['String']
@@ -2677,7 +2676,7 @@ export interface CollectionHash {
__typename: 'CollectionHash'
}
export type AllMetadataName = 'fieldMetadata' | 'objectMetadata' | 'view' | 'viewField' | 'viewFieldGroup' | 'viewGroup' | 'viewSort' | 'rowLevelPermissionPredicate' | 'rowLevelPermissionPredicateGroup' | 'viewFilterGroup' | 'index' | 'logicFunction' | 'viewFilter' | 'role' | 'roleTarget' | 'agent' | 'skill' | 'pageLayout' | 'pageLayoutWidget' | 'pageLayoutTab' | 'commandMenuItem' | 'navigationMenuItem' | 'permissionFlag' | 'objectPermission' | 'fieldPermission' | 'frontComponent' | 'webhook'
export type AllMetadataName = 'fieldMetadata' | 'objectMetadata' | 'view' | 'viewField' | 'viewFieldGroup' | 'viewGroup' | 'viewSort' | 'rowLevelPermissionPredicate' | 'rowLevelPermissionPredicateGroup' | 'viewFilterGroup' | 'index' | 'logicFunction' | 'viewFilter' | 'role' | 'roleTarget' | 'agent' | 'skill' | 'pageLayout' | 'pageLayoutWidget' | 'pageLayoutTab' | 'commandMenuItem' | 'navigationMenuItem' | 'permissionFlag' | 'objectPermission' | 'frontComponent' | 'webhook'
export interface MinimalObjectMetadata {
id: Scalars['UUID']
@@ -2955,7 +2954,6 @@ export interface Mutation {
createViewGroup: ViewGroup
createManyViewGroups: ViewGroup[]
updateViewGroup: ViewGroup
updateManyViewGroups: ViewGroup[]
deleteViewGroup: ViewGroup
destroyViewGroup: ViewGroup
updateMessageFolder: MessageFolder
@@ -3362,7 +3360,6 @@ export interface FieldGenqlSelection{
defaultValue?: boolean | number
options?: boolean | number
settings?: boolean | number
objectMetadataId?: boolean | number
isLabelSyncedWithName?: boolean | number
morphId?: boolean | number
createdAt?: boolean | number
@@ -3479,7 +3476,7 @@ export interface ObjectGenqlSelection{
__scalar?: boolean | number
}
export interface FieldFilter {and?: (FieldFilter[] | null),or?: (FieldFilter[] | null),id?: (UUIDFilterComparison | null),isCustom?: (BooleanFieldComparison | null),isActive?: (BooleanFieldComparison | null),isSystem?: (BooleanFieldComparison | null),isUIReadOnly?: (BooleanFieldComparison | null),objectMetadataId?: (UUIDFilterComparison | null)}
export interface FieldFilter {and?: (FieldFilter[] | null),or?: (FieldFilter[] | null),id?: (UUIDFilterComparison | null),isCustom?: (BooleanFieldComparison | null),isActive?: (BooleanFieldComparison | null),isSystem?: (BooleanFieldComparison | null),isUIReadOnly?: (BooleanFieldComparison | null)}
export interface IndexFilter {and?: (IndexFilter[] | null),or?: (IndexFilter[] | null),id?: (UUIDFilterComparison | null),isCustom?: (BooleanFieldComparison | null)}
@@ -5099,13 +5096,6 @@ export interface FieldConnectionGenqlSelection{
__scalar?: boolean | number
}
export interface LogicFunctionLogsGenqlSelection{
/** Execution Logs */
logs?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface DeleteTwoFactorAuthenticationMethodGenqlSelection{
/** Boolean that confirms query was dispatched */
success?: boolean | number
@@ -5511,6 +5501,13 @@ export interface UsageAnalyticsGenqlSelection{
__scalar?: boolean | number
}
export interface LogicFunctionLogsGenqlSelection{
/** Execution Logs */
logs?: boolean | number
__typename?: boolean | number
__scalar?: boolean | number
}
export interface FrontComponentGenqlSelection{
id?: boolean | number
name?: boolean | number
@@ -6210,7 +6207,6 @@ export interface MutationGenqlSelection{
createViewGroup?: (ViewGroupGenqlSelection & { __args: {input: CreateViewGroupInput} })
createManyViewGroups?: (ViewGroupGenqlSelection & { __args: {inputs: CreateViewGroupInput[]} })
updateViewGroup?: (ViewGroupGenqlSelection & { __args: {input: UpdateViewGroupInput} })
updateManyViewGroups?: (ViewGroupGenqlSelection & { __args: {inputs: UpdateViewGroupInput[]} })
deleteViewGroup?: (ViewGroupGenqlSelection & { __args: {input: DeleteViewGroupInput} })
destroyViewGroup?: (ViewGroupGenqlSelection & { __args: {input: DestroyViewGroupInput} })
updateMessageFolder?: (MessageFolderGenqlSelection & { __args: {input: UpdateMessageFolderInput} })
@@ -6531,7 +6527,7 @@ export interface CreateOneFieldMetadataInput {
/** The record to create */
field: CreateFieldInput}
export interface CreateFieldInput {type: FieldMetadataType,name: Scalars['String'],label: Scalars['String'],description?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),isCustom?: (Scalars['Boolean'] | null),isActive?: (Scalars['Boolean'] | null),isSystem?: (Scalars['Boolean'] | null),isUIReadOnly?: (Scalars['Boolean'] | null),isNullable?: (Scalars['Boolean'] | null),isUnique?: (Scalars['Boolean'] | null),defaultValue?: (Scalars['JSON'] | null),options?: (Scalars['JSON'] | null),settings?: (Scalars['JSON'] | null),objectMetadataId: Scalars['UUID'],isLabelSyncedWithName?: (Scalars['Boolean'] | null),isRemoteCreation?: (Scalars['Boolean'] | null),relationCreationPayload?: (Scalars['JSON'] | null),morphRelationsCreationPayload?: (Scalars['JSON'][] | null)}
export interface CreateFieldInput {type: FieldMetadataType,name: Scalars['String'],label: Scalars['String'],description?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),isCustom?: (Scalars['Boolean'] | null),isActive?: (Scalars['Boolean'] | null),isSystem?: (Scalars['Boolean'] | null),isUIReadOnly?: (Scalars['Boolean'] | null),isNullable?: (Scalars['Boolean'] | null),isUnique?: (Scalars['Boolean'] | null),defaultValue?: (Scalars['JSON'] | null),options?: (Scalars['JSON'] | null),settings?: (Scalars['JSON'] | null),isLabelSyncedWithName?: (Scalars['Boolean'] | null),objectMetadataId: Scalars['UUID'],isRemoteCreation?: (Scalars['Boolean'] | null),relationCreationPayload?: (Scalars['JSON'] | null),morphRelationsCreationPayload?: (Scalars['JSON'][] | null)}
export interface UpdateOneFieldMetadataInput {
/** The id of the record to update */
@@ -6539,7 +6535,7 @@ id: Scalars['UUID'],
/** The record to update */
update: UpdateFieldInput}
export interface UpdateFieldInput {universalIdentifier?: (Scalars['String'] | null),name?: (Scalars['String'] | null),label?: (Scalars['String'] | null),description?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),isActive?: (Scalars['Boolean'] | null),isSystem?: (Scalars['Boolean'] | null),isUIReadOnly?: (Scalars['Boolean'] | null),isNullable?: (Scalars['Boolean'] | null),isUnique?: (Scalars['Boolean'] | null),defaultValue?: (Scalars['JSON'] | null),options?: (Scalars['JSON'] | null),settings?: (Scalars['JSON'] | null),objectMetadataId?: (Scalars['UUID'] | null),isLabelSyncedWithName?: (Scalars['Boolean'] | null),morphRelationsUpdatePayload?: (Scalars['JSON'][] | null)}
export interface UpdateFieldInput {universalIdentifier?: (Scalars['String'] | null),name?: (Scalars['String'] | null),label?: (Scalars['String'] | null),description?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),isActive?: (Scalars['Boolean'] | null),isSystem?: (Scalars['Boolean'] | null),isUIReadOnly?: (Scalars['Boolean'] | null),isNullable?: (Scalars['Boolean'] | null),isUnique?: (Scalars['Boolean'] | null),defaultValue?: (Scalars['JSON'] | null),options?: (Scalars['JSON'] | null),settings?: (Scalars['JSON'] | null),isLabelSyncedWithName?: (Scalars['Boolean'] | null),morphRelationsUpdatePayload?: (Scalars['JSON'][] | null)}
export interface DeleteOneFieldInput {
/** The id of the field to delete. */
@@ -8051,14 +8047,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const LogicFunctionLogs_possibleTypes: string[] = ['LogicFunctionLogs']
export const isLogicFunctionLogs = (obj?: { __typename?: any } | null): obj is LogicFunctionLogs => {
if (!obj?.__typename) throw new Error('__typename is missing in "isLogicFunctionLogs"')
return LogicFunctionLogs_possibleTypes.includes(obj.__typename)
}
const DeleteTwoFactorAuthenticationMethod_possibleTypes: string[] = ['DeleteTwoFactorAuthenticationMethod']
export const isDeleteTwoFactorAuthenticationMethod = (obj?: { __typename?: any } | null): obj is DeleteTwoFactorAuthenticationMethod => {
if (!obj?.__typename) throw new Error('__typename is missing in "isDeleteTwoFactorAuthenticationMethod"')
@@ -8443,6 +8431,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
const LogicFunctionLogs_possibleTypes: string[] = ['LogicFunctionLogs']
export const isLogicFunctionLogs = (obj?: { __typename?: any } | null): obj is LogicFunctionLogs => {
if (!obj?.__typename) throw new Error('__typename is missing in "isLogicFunctionLogs"')
return LogicFunctionLogs_possibleTypes.includes(obj.__typename)
}
const FrontComponent_possibleTypes: string[] = ['FrontComponent']
export const isFrontComponent = (obj?: { __typename?: any } | null): obj is FrontComponent => {
if (!obj?.__typename) throw new Error('__typename is missing in "isFrontComponent"')
@@ -9429,7 +9425,6 @@ export const enumAllMetadataName = {
navigationMenuItem: 'navigationMenuItem' as const,
permissionFlag: 'permissionFlag' as const,
objectPermission: 'objectPermission' as const,
fieldPermission: 'fieldPermission' as const,
frontComponent: 'frontComponent' as const,
webhook: 'webhook' as const
}
@@ -63,8 +63,8 @@ export default {
210,
227,
244,
282,
283,
284,
299,
300,
325,
@@ -777,9 +777,6 @@ export default {
"settings": [
15
],
"objectMetadataId": [
3
],
"isLabelSyncedWithName": [
6
],
@@ -1167,9 +1164,6 @@ export default {
"isUIReadOnly": [
43
],
"objectMetadataId": [
42
],
"__typename": [
1
]
@@ -4464,14 +4458,6 @@ export default {
1
]
},
"LogicFunctionLogs": {
"logs": [
1
],
"__typename": [
1
]
},
"DeleteTwoFactorAuthenticationMethod": {
"success": [
6
@@ -4517,10 +4503,10 @@ export default {
},
"AuthTokenPair": {
"accessOrWorkspaceAgnosticToken": [
251
250
],
"refreshToken": [
251
250
],
"__typename": [
1
@@ -4528,7 +4514,7 @@ export default {
},
"AvailableWorkspacesAndAccessTokens": {
"tokens": [
252
251
],
"availableWorkspaces": [
224
@@ -4580,10 +4566,10 @@ export default {
},
"SignUp": {
"loginToken": [
251
250
],
"workspace": [
257
256
],
"__typename": [
1
@@ -4591,7 +4577,7 @@ export default {
},
"TransientToken": {
"transientToken": [
251
250
],
"__typename": [
1
@@ -4613,7 +4599,7 @@ export default {
},
"VerifyEmailAndGetLoginToken": {
"loginToken": [
251
250
],
"workspaceUrls": [
157
@@ -4632,7 +4618,7 @@ export default {
},
"AuthTokens": {
"tokens": [
252
251
],
"__typename": [
1
@@ -4640,7 +4626,7 @@ export default {
},
"LoginToken": {
"loginToken": [
251
250
],
"__typename": [
1
@@ -4670,10 +4656,10 @@ export default {
},
"Impersonate": {
"loginToken": [
251
250
],
"workspace": [
257
256
],
"__typename": [
1
@@ -4703,10 +4689,10 @@ export default {
},
"ApplicationTokenPair": {
"applicationAccessToken": [
251
250
],
"applicationRefreshToken": [
251
250
],
"__typename": [
1
@@ -4778,7 +4764,7 @@ export default {
1
],
"fields": [
272
271
],
"__typename": [
1
@@ -4875,10 +4861,10 @@ export default {
6
],
"objectPermissions": [
276
275
],
"fieldPermissions": [
277
276
],
"permissionFlags": [
1
@@ -4928,19 +4914,19 @@ export default {
1
],
"objects": [
273
],
"fields": [
272
],
"fields": [
271
],
"logicFunctions": [
274
273
],
"frontComponents": [
275
274
],
"defaultRole": [
278
277
],
"sourcePackage": [
1
@@ -5000,13 +4986,13 @@ export default {
1
],
"driver": [
283
282
],
"status": [
284
283
],
"verificationRecords": [
281
280
],
"verifiedAt": [
4
@@ -5053,7 +5039,7 @@ export default {
1
],
"location": [
286
285
],
"__typename": [
1
@@ -5081,13 +5067,13 @@ export default {
},
"ImapSmtpCaldavConnectionParameters": {
"IMAP": [
288
287
],
"SMTP": [
288
287
],
"CALDAV": [
288
287
],
"__typename": [
1
@@ -5107,7 +5093,7 @@ export default {
3
],
"connectionParameters": [
289
288
],
"__typename": [
1
@@ -5171,7 +5157,7 @@ export default {
1
],
"dailyUsage": [
294
293
],
"__typename": [
1
@@ -5179,13 +5165,13 @@ export default {
},
"UsageAnalytics": {
"usageByUser": [
293
292
],
"usageByOperationType": [
293
292
],
"timeSeries": [
294
293
],
"periodStart": [
4
@@ -5194,7 +5180,15 @@ export default {
4
],
"userDailyUsage": [
295
294
],
"__typename": [
1
]
},
"LogicFunctionLogs": {
"logs": [
1
],
"__typename": [
1
@@ -5241,7 +5235,7 @@ export default {
6
],
"applicationTokenPair": [
270
269
],
"__typename": [
1
@@ -6662,7 +6656,7 @@ export default {
}
],
"checkUserExists": [
265,
264,
{
"email": [
1,
@@ -6674,7 +6668,7 @@ export default {
}
],
"checkWorkspaceInviteHashIsValid": [
266,
265,
{
"inviteHash": [
1,
@@ -6692,7 +6686,7 @@ export default {
}
],
"validatePasswordResetToken": [
260,
259,
{
"passwordResetToken": [
1,
@@ -6775,7 +6769,7 @@ export default {
220
],
"getConnectedImapSmtpCaldavAccount": [
290,
289,
{
"id": [
3,
@@ -6784,7 +6778,7 @@ export default {
}
],
"getAutoCompleteAddress": [
285,
284,
{
"address": [
1,
@@ -6803,7 +6797,7 @@ export default {
}
],
"getAddressDetails": [
287,
286,
{
"placeId": [
1,
@@ -6895,19 +6889,19 @@ export default {
}
],
"getPostgresCredentials": [
292
291
],
"findManyPublicDomains": [
280
],
"getEmailingDomains": [
282
],
"findManyMarketplaceApps": [
279
],
"getEmailingDomains": [
281
],
"findManyMarketplaceApps": [
278
],
"findOneMarketplaceApp": [
279,
278,
{
"universalIdentifier": [
1,
@@ -6930,7 +6924,7 @@ export default {
}
],
"getUsageAnalytics": [
296,
295,
{
"input": [
365
@@ -8136,15 +8130,6 @@ export default {
]
}
],
"updateManyViewGroups": [
56,
{
"inputs": [
450,
"[UpdateViewGroupInput!]!"
]
}
],
"deleteViewGroup": [
56,
{
@@ -8315,7 +8300,7 @@ export default {
}
],
"getAuthorizationUrlForSSO": [
255,
254,
{
"input": [
466,
@@ -8324,7 +8309,7 @@ export default {
}
],
"getLoginTokenFromCredentials": [
264,
263,
{
"email": [
1,
@@ -8350,7 +8335,7 @@ export default {
}
],
"signIn": [
253,
252,
{
"email": [
1,
@@ -8372,7 +8357,7 @@ export default {
}
],
"verifyEmailAndGetLoginToken": [
261,
260,
{
"emailVerificationToken": [
1,
@@ -8392,7 +8377,7 @@ export default {
}
],
"verifyEmailAndGetWorkspaceAgnosticToken": [
253,
252,
{
"emailVerificationToken": [
1,
@@ -8408,7 +8393,7 @@ export default {
}
],
"getAuthTokensFromOTP": [
263,
262,
{
"otp": [
1,
@@ -8428,7 +8413,7 @@ export default {
}
],
"signUp": [
253,
252,
{
"email": [
1,
@@ -8450,7 +8435,7 @@ export default {
}
],
"signUpInWorkspace": [
258,
257,
{
"email": [
1,
@@ -8481,13 +8466,13 @@ export default {
}
],
"signUpInNewWorkspace": [
258
257
],
"generateTransientToken": [
259
258
],
"getAuthTokensFromLoginToken": [
263,
262,
{
"loginToken": [
1,
@@ -8500,7 +8485,7 @@ export default {
}
],
"authorizeApp": [
250,
249,
{
"clientId": [
1,
@@ -8522,7 +8507,7 @@ export default {
}
],
"renewToken": [
263,
262,
{
"appToken": [
1,
@@ -8531,7 +8516,7 @@ export default {
}
],
"generateApiKeyToken": [
262,
261,
{
"apiKeyId": [
3,
@@ -8544,7 +8529,7 @@ export default {
}
],
"emailPasswordResetLink": [
254,
253,
{
"email": [
1,
@@ -8556,7 +8541,7 @@ export default {
}
],
"updatePasswordViaResetToken": [
256,
255,
{
"passwordResetToken": [
1,
@@ -8657,7 +8642,7 @@ export default {
}
],
"initiateOTPProvisioning": [
248,
247,
{
"loginToken": [
1,
@@ -8670,10 +8655,10 @@ export default {
}
],
"initiateOTPProvisioningForAuthenticatedUser": [
248
247
],
"deleteTwoFactorAuthenticationMethod": [
247,
246,
{
"twoFactorAuthenticationMethodId": [
3,
@@ -8682,7 +8667,7 @@ export default {
}
],
"verifyTwoFactorAuthenticationMethodForAuthenticatedUser": [
249,
248,
{
"otp": [
1,
@@ -8788,7 +8773,7 @@ export default {
}
],
"impersonate": [
267,
266,
{
"userId": [
3,
@@ -8810,7 +8795,7 @@ export default {
}
],
"saveImapSmtpCaldavAccount": [
291,
290,
{
"accountOwnerId": [
3,
@@ -9013,13 +8998,13 @@ export default {
}
],
"enablePostgresProxy": [
292
291
],
"disablePostgresProxy": [
292
291
],
"createPublicDomain": [
280,
279,
{
"domain": [
1,
@@ -9046,14 +9031,14 @@ export default {
}
],
"createEmailingDomain": [
282,
281,
{
"domain": [
1,
"String!"
],
"driver": [
283,
282,
"EmailingDomainDriver!"
]
}
@@ -9068,7 +9053,7 @@ export default {
}
],
"verifyEmailingDomain": [
282,
281,
{
"id": [
1,
@@ -9145,7 +9130,7 @@ export default {
}
],
"createDevelopmentApplication": [
268,
267,
{
"universalIdentifier": [
1,
@@ -9158,7 +9143,7 @@ export default {
}
],
"generateApplicationToken": [
270,
269,
{
"applicationId": [
3,
@@ -9167,7 +9152,7 @@ export default {
}
],
"syncApplication": [
269,
268,
{
"manifest": [
15,
@@ -9176,7 +9161,7 @@ export default {
}
],
"uploadApplicationFile": [
271,
270,
{
"file": [
372,
@@ -9210,7 +9195,7 @@ export default {
}
],
"renewApplicationToken": [
270,
269,
{
"applicationRefreshToken": [
1,
@@ -10760,12 +10745,12 @@ export default {
"settings": [
15
],
"objectMetadataId": [
3
],
"isLabelSyncedWithName": [
6
],
"objectMetadataId": [
3
],
"isRemoteCreation": [
6
],
@@ -10830,9 +10815,6 @@ export default {
"settings": [
15
],
"objectMetadataId": [
3
],
"isLabelSyncedWithName": [
6
],
@@ -11473,7 +11455,7 @@ export default {
}
],
"logicFunctionLogs": [
246,
296,
{
"input": [
490,
@@ -32,11 +32,11 @@ has_schema=$(PGPASSWORD=twenty psql -h localhost -U twenty -d default -tAc \
if [ "$has_schema" = "f" ]; then
echo "Database appears to be empty, running initial setup..."
NODE_OPTIONS="--max-old-space-size=1500" node ./dist/database/scripts/setup-db.js
NODE_OPTIONS="--max-old-space-size=1500" node ./dist/scripts/setup-db.js
fi
# Always run migrations (idempotent — skips already-applied ones)
yarn database:migrate:prod --force
yarn database:migrate:prod
yarn command:prod cache:flush
yarn command:prod upgrade
+6
View File
@@ -38,6 +38,12 @@ RUN npx nx run twenty-server:lingui:extract && \
RUN npx nx run twenty-server:build
# Bundle setup-db script into a standalone JS file so the final image
# doesn't need tsx or the TypeScript source tree at runtime.
RUN npx esbuild packages/twenty-server/scripts/setup-db.ts \
--bundle --platform=node --outfile=packages/twenty-server/dist/scripts/setup-db.js \
--external:typeorm --external:dotenv --external:pg
# Clean server build output (type declarations and compiled tests are not needed at runtime;
# source maps are kept because twenty-infra extracts them from the image for Sentry uploads)
RUN find /app/packages/twenty-server/dist -name '*.d.ts' -delete \
+2 -1
View File
@@ -13,7 +13,8 @@ setup_and_migrate_db() {
has_schema=$(psql -tAc "SELECT EXISTS (SELECT 1 FROM information_schema.schemata WHERE schema_name = 'core')" ${PG_DATABASE_URL})
if [ "$has_schema" = "f" ]; then
echo "Database appears to be empty, running migrations."
yarn database:init:prod
NODE_OPTIONS="--max-old-space-size=1500" node ./dist/scripts/setup-db.js
yarn database:migrate:prod
fi
yarn command:prod cache:flush
File diff suppressed because one or more lines are too long
@@ -6686,11 +6686,6 @@ msgstr "Veldnaam"
msgid "Field on destination"
msgstr "Veld op bestemming"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "اسم الحقل"
msgid "Field on destination"
msgstr "حقل في الوجهة"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Nom del camp"
msgid "Field on destination"
msgstr "Camp a la destinació"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Název pole"
msgid "Field on destination"
msgstr "Pole v cíli"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Feltnavn"
msgid "Field on destination"
msgstr "Felt på destination"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Feldname"
msgid "Field on destination"
msgstr "Feld am Ziel"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Όνομα Πεδίου"
msgid "Field on destination"
msgstr "Πεδίο στον προορισμό"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
-5
View File
@@ -6681,11 +6681,6 @@ msgstr "Field Name"
msgid "Field on destination"
msgstr "Field on destination"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr "field permission"
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Nombre del campo"
msgid "Field on destination"
msgstr "Campo en destino"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Kentän nimi"
msgid "Field on destination"
msgstr "Kenttä kohteessa"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Nom du champ"
msgid "Field on destination"
msgstr "Champ sur la destination"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -6686,11 +6686,6 @@ msgstr "שם שדה"
msgid "Field on destination"
msgstr "שדה ביעד"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Mező neve"
msgid "Field on destination"
msgstr "Mező a célállomáson"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Nome del campo"
msgid "Field on destination"
msgstr "Campo sulla destinazione"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "フィールド名"
msgid "Field on destination"
msgstr "宛先のフィールド"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "필드 이름"
msgid "Field on destination"
msgstr "대상 필드"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Veldnaam"
msgid "Field on destination"
msgstr "Veld bij bestemming"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Feltnavn"
msgid "Field on destination"
msgstr "Felt på destinasjon"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Nazwa pola"
msgid "Field on destination"
msgstr "Pole na miejscu docelowym"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6681,11 +6681,6 @@ msgstr ""
msgid "Field on destination"
msgstr ""
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Nome do campo"
msgid "Field on destination"
msgstr "Campo no destino"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Nome do Campo"
msgid "Field on destination"
msgstr "Campo no destino"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Nume câmp"
msgid "Field on destination"
msgstr "Câmp pe destinație"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
Binary file not shown.
@@ -6686,11 +6686,6 @@ msgstr "Име поља"
msgid "Field on destination"
msgstr "Поље на одредишту"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Fältnamn"
msgid "Field on destination"
msgstr "Fält på destinationen"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Alan Adı"
msgid "Field on destination"
msgstr "Hedefteki alan"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Назва поля"
msgid "Field on destination"
msgstr "Поле на призначенні"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "Tên Trường"
msgid "Field on destination"
msgstr "Trường ở điểm đến"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "字段名称"
msgid "Field on destination"
msgstr "目的地字段"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -6686,11 +6686,6 @@ msgstr "欄位名稱"
msgid "Field on destination"
msgstr "目的地資料欄位"
#. js-lingui-id: P8JCfj
#: src/modules/metadata-error-handler/hooks/useMetadataErrorHandler.ts
msgid "field permission"
msgstr ""
#. js-lingui-id: RnX5oc
#: src/pages/settings/applications/SettingsAvailableApplicationDetails.tsx
msgid "fields"
@@ -31,6 +31,8 @@ import { useCallback, useState } from 'react';
import { type ExtendedUIMessage } from 'twenty-shared/ai';
import { isDefined } from 'twenty-shared/utils';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { cookieStorage } from '~/utils/cookie-storage';
export const useAgentChat = (
uiMessages: ExtendedUIMessage[],
ensureThreadIdForSend: () => Promise<string | null>,
@@ -92,6 +94,7 @@ export const useAgentChat = (
return null;
}
cookieStorage.setItem('tokenPair', JSON.stringify(renewedTokens));
setTokenPair(renewedTokens);
const updatedHeaders = new Headers(init?.headers ?? {});
@@ -34,6 +34,7 @@ import {
import isEmpty from 'lodash.isempty';
import { getGenericOperationName, isDefined } from 'twenty-shared/utils';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { cookieStorage } from '~/utils/cookie-storage';
import { isUndefinedOrNull } from '~/utils/isUndefinedOrNull';
const logger = loggerLink(() => 'Twenty');
@@ -174,6 +175,7 @@ export class ApolloFactory implements ApolloManager {
if (isDefined(tokens)) {
onTokenPairChange?.(tokens);
cookieStorage.setItem('tokenPair', JSON.stringify(tokens));
}
};
@@ -32,7 +32,6 @@ export const useMetadataErrorHandler = () => {
logicFunction: t`logic function`,
permissionFlag: t`permission flag`,
objectPermission: t`object permission`,
fieldPermission: t`field permission`,
role: t`role`,
roleTarget: t`role target`,
agent: t`agent`,
@@ -6,13 +6,11 @@ import { splitViewWithRelated } from '@/metadata-store/utils/splitViewWithRelate
import { FIND_MANY_OBJECT_METADATA_ITEMS } from '@/object-metadata/graphql/queries';
import { transformPageLayout } from '@/page-layout/utils/transformPageLayout';
import { logicFunctionsState } from '@/settings/logic-functions/states/logicFunctionsState';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useApolloClient } from '@apollo/client/react';
import { useStore } from 'jotai';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
import {
FeatureFlagKey,
FindAllViewsDocument,
FindManyCommandMenuItemsDocument,
FindAllRecordPageLayoutsDocument,
@@ -57,9 +55,6 @@ export const useLoadStaleMetadataEntities = () => {
const client = useApolloClient();
const store = useStore();
const { replaceDraft, applyChanges } = useUpdateMetadataStoreDraft();
const isCommandMenuItemEnabled = useIsFeatureEnabled(
FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
);
const loadStaleMetadataEntities = useCallback(
async (staleEntityKeys: MetadataEntityKey[]) => {
@@ -199,10 +194,7 @@ export const useLoadStaleMetadataEntities = () => {
);
}
if (
staleEntityKeys.includes('commandMenuItems') &&
isCommandMenuItemEnabled
) {
if (staleEntityKeys.includes('commandMenuItems')) {
fetchPromises.push(
client
.query({
@@ -222,7 +214,7 @@ export const useLoadStaleMetadataEntities = () => {
await Promise.all(fetchPromises);
applyChanges();
},
[client, store, replaceDraft, applyChanges, isCommandMenuItemEnabled],
[client, store, replaceDraft, applyChanges],
);
return { loadStaleMetadataEntities };
@@ -1,8 +0,0 @@
import type { DropDestination } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitDropDestination';
export type NavigationMenuItemDropResult = {
source: DropDestination;
destination: DropDestination | null;
draggableId: string;
insertBeforeItemId?: string | null;
};
@@ -1,14 +1,14 @@
import { NAVIGATION_MENU_ITEM_SECTION_DROPPABLE_CONFIG } from '@/navigation-menu-item/common/constants/NavigationMenuItemSectionDroppableConfig';
import type { DropDestination } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitDropDestination';
import type { NavigationMenuItemSection } from '@/navigation-menu-item/common/types/NavigationMenuItemSection';
import { canNavigationMenuItemBeDroppedIn } from '@/navigation-menu-item/common/utils/canNavigationMenuItemBeDroppedIn';
import type { DropResult } from '@hello-pangea/dnd';
export const getDropTargetIdFromDestination = ({
navigationMenuItemSection,
destination,
}: {
navigationMenuItemSection: NavigationMenuItemSection;
destination: DropDestination | null;
destination: DropResult['destination'];
}): string | null => {
if (
!destination ||
@@ -0,0 +1,69 @@
import { Droppable } from '@hello-pangea/dnd';
import { styled } from '@linaria/react';
import { themeCssVariables } from 'twenty-ui/theme-constants';
import { useIsDropDisabledForSection } from '@/navigation-menu-item/display/dnd/hooks/useIsDropDisabledForSection';
type NavigationMenuItemDroppableProps = {
droppableId: string;
children: React.ReactNode;
isDragIndicatorVisible?: boolean;
showDropLine?: boolean;
isWorkspaceSection?: boolean;
};
const StyledDroppableWrapper = styled.div`
position: relative;
transition: all 150ms ease-in-out;
width: 100%;
&[data-dragging-over='true'] {
background-color: ${themeCssVariables.background.transparent.blue};
}
&[data-dragging-over='true'][data-show-drop-line='true']::before {
background-color: ${themeCssVariables.color.blue};
border-radius: ${themeCssVariables.border.radius.sm}
${themeCssVariables.border.radius.sm} 0 0;
bottom: 0;
content: '';
height: 2px;
left: 0;
position: absolute;
width: 100%;
}
`;
export const NavigationMenuItemDroppable = ({
droppableId,
children,
isDragIndicatorVisible = true,
showDropLine = true,
isWorkspaceSection = false,
}: NavigationMenuItemDroppableProps) => {
const isDropDisabled = useIsDropDisabledForSection(isWorkspaceSection);
return (
<Droppable droppableId={droppableId} isDropDisabled={isDropDisabled}>
{(provided, snapshot) => (
<StyledDroppableWrapper
data-dragging-over={
snapshot.isDraggingOver && isDragIndicatorVisible
? 'true'
: undefined
}
data-show-drop-line={showDropLine ? 'true' : undefined}
>
<div
ref={provided.innerRef}
// oxlint-disable-next-line react/jsx-props-no-spreading
{...provided.droppableProps}
>
{children}
{provided.placeholder}
</div>
</StyledDroppableWrapper>
)}
</Droppable>
);
};
@@ -0,0 +1,6 @@
export const DROP_RESULT_OPTIONS = {
reason: 'DROP' as const,
combine: null,
mode: 'FLUID' as const,
type: 'DEFAULT' as const,
};
@@ -1,3 +1,4 @@
import type { DropResult, ResponderProvided } from '@hello-pangea/dnd';
import { t } from '@lingui/core/macro';
import { useCallback } from 'react';
import { isDefined } from 'twenty-shared/utils';
@@ -27,8 +28,6 @@ import { viewsSelector } from '@/views/states/selectors/viewsSelector';
import { useStore } from 'jotai';
import { NavigationMenuItemType } from 'twenty-shared/types';
import type { NavigationMenuItemDropResult } from '@/navigation-menu-item/common/types/navigationMenuItemDropResult';
export const useHandleAddToNavigationDrop = () => {
const store = useStore();
const { addObjectToDraft } = useAddObjectToNavigationMenuDraft();
@@ -51,7 +50,7 @@ export const useHandleAddToNavigationDrop = () => {
);
const handleAddToNavigationDrop = useCallback(
(result: NavigationMenuItemDropResult) => {
(result: DropResult, _provided: ResponderProvided) => {
const { source, destination, draggableId } = result;
if (
source.droppableId !== ADD_TO_NAV_SOURCE_DROPPABLE_ID ||
@@ -1,9 +1,8 @@
import { type OnDragEndResponder } from '@hello-pangea/dnd';
import { useStore } from 'jotai';
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
import { isDefined } from 'twenty-shared/utils';
import type { NavigationMenuItemDropResult } from '@/navigation-menu-item/common/types/navigationMenuItemDropResult';
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
import { useUpdateManyNavigationMenuItems } from '@/navigation-menu-item/common/hooks/useUpdateManyNavigationMenuItems';
import { NAVIGATION_MENU_ITEM_SECTION_DROPPABLE_CONFIG } from '@/navigation-menu-item/common/constants/NavigationMenuItemSectionDroppableConfig';
@@ -116,8 +115,10 @@ export const useHandleNavigationMenuItemDragAndDrop = (
});
};
const handleNavigationMenuItemDragAndDrop = async (
result: NavigationMenuItemDropResult,
const handleNavigationMenuItemDragAndDrop: OnDragEndResponder = async (
result: Parameters<OnDragEndResponder>[0] & {
insertBeforeItemId?: string | null;
},
) => {
const { destination, source, draggableId } = result;
@@ -1,5 +1,6 @@
import { type DragDropProvider } from '@dnd-kit/react';
import { isSortable } from '@dnd-kit/react/sortable';
import type { ResponderProvided } from '@hello-pangea/dnd';
import { useStore } from 'jotai';
import { type ComponentProps, useCallback, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
@@ -10,16 +11,17 @@ import { NavigationSections } from '@/navigation-menu-item/common/constants/Navi
import { addToNavPayloadRegistryState } from '@/navigation-menu-item/common/states/addToNavPayloadRegistryState';
import type { DraggableData } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitDraggableData';
import type { DropDestination } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitDropDestination';
import type { NavigationMenuItemDropResult } from '@/navigation-menu-item/common/types/navigationMenuItemDropResult';
import type { SortableTargetDestination } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitSortableTargetDestination';
import type { NavigationMenuItemSection } from '@/navigation-menu-item/common/types/NavigationMenuItemSection';
import { canNavigationMenuItemBeDroppedIn } from '@/navigation-menu-item/common/utils/canNavigationMenuItemBeDroppedIn';
import { extractFolderIdFromDroppableId } from '@/navigation-menu-item/common/utils/extractFolderIdFromDroppableId';
import { getDndKitDropTargetId } from '@/navigation-menu-item/common/utils/getDndKitDropTargetId';
import { isNavigationMenuItemFolder } from '@/navigation-menu-item/common/utils/isNavigationMenuItemFolder';
import { DROP_RESULT_OPTIONS } from '@/navigation-menu-item/display/dnd/constants/navigationMenuItemDndKitDropResultOptions';
import { useHandleAddToNavigationDrop } from '@/navigation-menu-item/display/dnd/hooks/useHandleAddToNavigationDrop';
import { useHandleNavigationMenuItemDragAndDrop } from '@/navigation-menu-item/display/dnd/hooks/useHandleNavigationMenuItemDragAndDrop';
import { resolveDropTarget } from '@/navigation-menu-item/display/dnd/utils/navigationMenuItemDndKitResolveDropTarget';
import { toDropResult } from '@/navigation-menu-item/display/dnd/utils/navigationMenuItemDndKitToDropResult';
import { useNavigationMenuItemsData } from '@/navigation-menu-item/display/hooks/useNavigationMenuItemsData';
import { useSortedNavigationMenuItems } from '@/navigation-menu-item/display/hooks/useSortedNavigationMenuItems';
import { useNavigationMenuItemsDraftState } from '@/navigation-menu-item/edit/hooks/useNavigationMenuItemsDraftState';
@@ -170,13 +172,23 @@ export const useNavigationMenuItemDndKit = (
destination: DropDestination,
insertBeforeItemId?: string | null,
) => {
const result: NavigationMenuItemDropResult = {
draggableId: id,
source,
const result = toDropResult(
id,
{
sourceDroppableId: source.droppableId,
sourceIndex: source.index,
},
destination,
...(insertBeforeItemId != null && { insertBeforeItemId }),
};
handleNavigationMenuItemDragAndDrop(result);
);
const provided: ResponderProvided = { announce: () => {} };
handleNavigationMenuItemDragAndDrop(
{
...result,
...DROP_RESULT_OPTIONS,
...(insertBeforeItemId != null && { insertBeforeItemId }),
},
provided,
);
},
[handleNavigationMenuItemDragAndDrop],
);
@@ -339,18 +351,12 @@ export const useNavigationMenuItemDndKit = (
destination = fallback;
}
const dropResult: NavigationMenuItemDropResult = {
draggableId,
source: {
droppableId: data?.sourceDroppableId ?? '',
index: data?.sourceIndex ?? 0,
},
destination,
...(insertBeforeItemId != null && { insertBeforeItemId }),
};
const result = toDropResult(draggableId, data, destination);
const provided: ResponderProvided = { announce: () => {} };
const dropResult = { ...result, ...DROP_RESULT_OPTIONS };
if (sourceId === ADD_TO_NAV_SOURCE_DROPPABLE_ID) {
handleAddToNavigationDrop(dropResult);
handleAddToNavigationDrop(dropResult, provided);
return;
}
@@ -380,7 +386,7 @@ export const useNavigationMenuItemDndKit = (
return;
}
handleNavigationMenuItemDragAndDrop(dropResult);
handleNavigationMenuItemDragAndDrop(dropResult, provided);
};
const contextValues: NavigationMenuItemDndKitContextValues = {
@@ -0,0 +1,20 @@
import type { DropDestination } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitDropDestination';
import type { DraggableData } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitDraggableData';
export const toDropResult = (
draggableId: string,
data: DraggableData | undefined,
destination: DropDestination | null,
): {
source: DropDestination;
destination: DropDestination | null;
draggableId: string;
} => {
const sourceDroppableId = data?.sourceDroppableId ?? '';
const sourceIndex = data?.sourceIndex ?? 0;
return {
source: { droppableId: sourceDroppableId, index: sourceIndex },
destination,
draggableId,
};
};
@@ -0,0 +1,102 @@
import {
type DraggableProvided,
type DraggableRubric,
type DraggableStateSnapshot,
} from '@hello-pangea/dnd';
import { useContext } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { ThemeContext } from 'twenty-ui/theme-constants';
import { type NavigationMenuItem } from '~/generated-metadata/graphql';
import { NavigationMenuItemIcon } from '@/navigation-menu-item/display/components/NavigationMenuItemIcon';
import { getNavigationMenuItemLabel } from '@/navigation-menu-item/display/utils/getNavigationMenuItemLabel';
import { getNavigationMenuItemObjectNameSingular } from '@/navigation-menu-item/display/object/utils/getNavigationMenuItemObjectNameSingular';
import { getObjectNavigationMenuItemSecondaryLabel } from '@/navigation-menu-item/display/object/utils/getObjectNavigationMenuItemSecondaryLabel';
import { objectMetadataItemsSelector } from '@/object-metadata/states/objectMetadataItemsSelector';
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
import { NavigationDrawerSubItem } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerSubItem';
import { getNavigationSubItemLeftAdornment } from '@/ui/navigation/navigation-drawer/utils/getNavigationSubItemLeftAdornment';
import { viewsSelector } from '@/views/states/selectors/viewsSelector';
import { ViewKey } from '@/views/types/ViewKey';
type WorkspaceNavigationMenuItemFolderDragCloneProps = {
draggableProvided: DraggableProvided;
draggableSnapshot: DraggableStateSnapshot;
rubric: DraggableRubric;
navigationMenuItems: NavigationMenuItem[];
navigationMenuItemFolderContentLength: number;
selectedNavigationMenuItemIndex: number;
};
export const WorkspaceNavigationMenuItemFolderDragClone = ({
draggableProvided,
draggableSnapshot,
rubric,
navigationMenuItems,
navigationMenuItemFolderContentLength,
selectedNavigationMenuItemIndex,
}: WorkspaceNavigationMenuItemFolderDragCloneProps) => {
const { theme } = useContext(ThemeContext);
const objectMetadataItems = useAtomStateValue(objectMetadataItemsSelector);
const views = useAtomStateValue(viewsSelector);
const navigationMenuItem = navigationMenuItems[rubric.source.index];
if (!isDefined(navigationMenuItem)) {
return null;
}
const label = getNavigationMenuItemLabel(
navigationMenuItem,
objectMetadataItems,
views,
);
const objectNameSingular = getNavigationMenuItemObjectNameSingular(
navigationMenuItem,
objectMetadataItems,
views,
);
const view = isDefined(navigationMenuItem.viewId)
? views.find((view) => view.id === navigationMenuItem.viewId)
: undefined;
const isIndexView = view?.key === ViewKey.INDEX;
return (
<div
ref={draggableProvided.innerRef}
// oxlint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided.draggableProps}
// oxlint-disable-next-line react/jsx-props-no-spreading
{...draggableProvided.dragHandleProps}
style={{
...draggableProvided.draggableProps.style,
background: draggableSnapshot.isDragging
? theme.background.transparent.light
: undefined,
}}
>
<NavigationDrawerSubItem
secondaryLabel={
isIndexView
? undefined
: getObjectNavigationMenuItemSecondaryLabel({
objectMetadataItems,
navigationMenuItemObjectNameSingular: objectNameSingular ?? '',
})
}
label={label}
Icon={() => (
<NavigationMenuItemIcon navigationMenuItem={navigationMenuItem} />
)}
to={undefined}
active={false}
isDragging={true}
subItemState={getNavigationSubItemLeftAdornment({
index: rubric.source.index,
arrayLength: navigationMenuItemFolderContentLength,
selectedIndex: selectedNavigationMenuItemIndex,
})}
triggerEvent="CLICK"
/>
</div>
);
};
@@ -20,14 +20,12 @@ export type FieldMetadataItem = Omit<
| '__typename'
| 'applicationId'
| 'defaultValue'
| 'objectMetadataId'
| 'options'
| 'relation'
| 'morphRelations'
> & {
__typename?: string;
applicationId?: string;
objectMetadataId?: string;
defaultValue?: any;
options?: FieldMetadataItemOption[] | null;
relation?: FieldMetadataItemRelation | null;
@@ -8,17 +8,14 @@ const StyledContainer = styled.div`
align-items: center;
background: ${themeCssVariables.background.transparent.light};
border-radius: ${themeCssVariables.border.radius.sm};
border-radius: 4px;
color: ${themeCssVariables.font.color.tertiary};
display: inline-flex;
font-size: ${themeCssVariables.font.size.md};
font-weight: ${themeCssVariables.font.weight.regular};
gap: ${themeCssVariables.spacing[1]};
height: ${themeCssVariables.spacing[3]};
overflow: hidden;
padding: ${themeCssVariables.spacing[1]};
user-select: none;
`;
export const ForbiddenFieldDisplay = () => {
@@ -91,20 +91,13 @@ export const MultiItemFieldInput = <T,>({
) {
return;
}
const { isValid, updatedItems } = validateInputAndComputeUpdatedItems();
if (isInputDisplayed) {
const { isValid, updatedItems } = validateInputAndComputeUpdatedItems();
if (!isValid) {
return;
}
onChange(updatedItems);
onClickOutside(updatedItems, event);
if (!isValid) {
return;
}
onChange(updatedItems);
onClickOutside(items, event);
},
listenerId: instanceId,
@@ -31,10 +31,6 @@ export const RecordTableCellEditModePortal = () => {
{recordTableCellEditModePosition && (
<RecordTableCellPortalRootContainer
zIndex={TABLE_Z_INDEX.cell.editMode}
topOffset={0}
leftOffset={0}
widthExpansion={0}
heightExpansion={0}
>
<RecordTableCellEditMode>
<RecordTableCellFieldInput />
@@ -26,23 +26,9 @@ export const RecordTableCellFocusedPortal = () => {
return null;
}
const isTouchingHeader = recordTableFocusPosition.row === 0;
const isTouchingFirstColumn = recordTableFocusPosition.column === 1;
const topOffset = isTouchingHeader ? 0 : -1;
const leftOffset = isTouchingFirstColumn ? 0 : -1;
const widthExpansion = isTouchingFirstColumn ? 1 : 2;
const heightExpansion = isTouchingHeader ? 1 : 2;
return (
<RecordTableCellPortalWrapper position={recordTableFocusPosition}>
<RecordTableCellPortalRootContainer
zIndex={TABLE_Z_INDEX.hoverPortal}
topOffset={topOffset}
leftOffset={leftOffset}
widthExpansion={widthExpansion}
heightExpansion={heightExpansion}
>
<RecordTableCellPortalRootContainer zIndex={TABLE_Z_INDEX.hoverPortal}>
<RecordTableCellFocusedPortalContent />
</RecordTableCellPortalRootContainer>
</RecordTableCellPortalWrapper>
@@ -1,4 +1,5 @@
import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDisplay';
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
import { useRecordTableBodyContextOrThrow } from '@/object-record/record-table/contexts/RecordTableBodyContext';
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableCellDisplayMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellDisplayMode';
@@ -15,6 +16,7 @@ import { themeCssVariables } from 'twenty-ui/theme-constants';
const StyledRecordTableCellFocusPortalContent = styled.div<{
isRecordTableRowActive: boolean;
}>`
align-items: center;
background: ${themeCssVariables.background.transparent.secondary};
background-color: ${({ isRecordTableRowActive }) =>
isRecordTableRowActive
@@ -22,32 +24,16 @@ const StyledRecordTableCellFocusPortalContent = styled.div<{
: themeCssVariables.background.primary};
border-radius: ${themeCssVariables.border.radius.sm};
box-sizing: border-box;
display: flex;
height: 100%;
height: ${RECORD_TABLE_ROW_HEIGHT}px;
outline: 1px solid ${themeCssVariables.color.blue8};
outline-offset: -1px;
position: relative;
user-select: none;
`;
const StyledFocusedContentWrapper = styled.div<{
contentTop: number;
contentLeft: number;
contentWidth: string;
contentHeight: string;
}>`
align-items: center;
box-sizing: border-box;
display: flex;
height: ${({ contentHeight }) => contentHeight};
left: ${({ contentLeft }) => contentLeft}px;
position: absolute;
top: ${({ contentTop }) => contentTop}px;
width: ${({ contentWidth }) => contentWidth};
`;
export const RecordTableCellFocusedPortalContent = () => {
const { rowIndex } = useRecordTableRowContextOrThrow();
const { onMoveHoverToCurrentCell } = useRecordTableBodyContextOrThrow();
@@ -75,33 +61,14 @@ export const RecordTableCellFocusedPortalContent = () => {
}
};
const isTouchingHeader = recordTableFocusPosition?.row === 0;
const isTouchingFirstColumn = recordTableFocusPosition?.column === 1;
const contentTop = isTouchingHeader ? 0 : 1;
const contentLeft = isTouchingFirstColumn ? 0 : 1;
const contentWidth = isTouchingFirstColumn
? 'calc(100% - 1px)'
: 'calc(100% - 2px)';
const contentHeight = isTouchingHeader
? 'calc(100% - 1px)'
: 'calc(100% - 2px)';
return (
<StyledRecordTableCellFocusPortalContent
isRecordTableRowActive={isRecordTableRowActive}
onMouseMove={handleContainerMouseMove}
>
<StyledFocusedContentWrapper
contentTop={contentTop}
contentLeft={contentLeft}
contentWidth={contentWidth}
contentHeight={contentHeight}
>
<RecordTableCellDisplayMode>
<FieldDisplay />
</RecordTableCellDisplayMode>
</StyledFocusedContentWrapper>
<RecordTableCellDisplayMode>
<FieldDisplay />
</RecordTableCellDisplayMode>
</StyledRecordTableCellFocusPortalContent>
);
};
@@ -17,13 +17,7 @@ export const RecordTableCellHoveredPortal = () => {
return (
<RecordTableCellPortalWrapper position={recordTableHoverPosition}>
<RecordTableCellPortalRootContainer
zIndex={TABLE_Z_INDEX.hoverPortal}
topOffset={0}
leftOffset={0}
widthExpansion={0}
heightExpansion={0}
>
<RecordTableCellPortalRootContainer zIndex={TABLE_Z_INDEX.hoverPortal}>
<RecordTableCellHoveredPortalContent />
</RecordTableCellPortalRootContainer>
</RecordTableCellPortalWrapper>
@@ -2,6 +2,7 @@ import { FieldDisplay } from '@/object-record/record-field/ui/components/FieldDi
import { FieldContext } from '@/object-record/record-field/ui/contexts/FieldContext';
import { FieldFocusStaticFocusedProvider } from '@/object-record/record-field/ui/contexts/FieldFocusContextProvider';
import { useIsFieldInputOnly } from '@/object-record/record-field/ui/hooks/useIsFieldInputOnly';
import { RECORD_TABLE_ROW_HEIGHT } from '@/object-record/record-table/constants/RecordTableRowHeight';
import { useRecordTableRowContextOrThrow } from '@/object-record/record-table/contexts/RecordTableRowContext';
import { RecordTableCellDisplayMode } from '@/object-record/record-table/record-table-cell/components/RecordTableCellDisplayMode';
import { RecordTableCellEditButton } from '@/object-record/record-table/record-table-cell/components/RecordTableCellEditButton';
@@ -20,31 +21,21 @@ import { useIsMobile } from 'twenty-ui/utilities';
const StyledRecordTableCellHoveredPortalContent = styled.div<{
showInteractiveStyle: boolean;
isRecordTableRowActive: boolean;
topExpansion: number;
leftExpansion: number;
rightExpansion: number;
bottomExpansion: number;
}>`
align-items: center;
background: ${themeCssVariables.background.transparent.secondary};
background-color: ${({ isRecordTableRowActive, showInteractiveStyle }) =>
background-color: ${({ isRecordTableRowActive }) =>
isRecordTableRowActive
? themeCssVariables.accent.quaternary
: showInteractiveStyle
? themeCssVariables.background.primary
: themeCssVariables.background.transparent.lighter};
: themeCssVariables.background.primary};
border-radius: ${({ showInteractiveStyle }) =>
showInteractiveStyle ? themeCssVariables.border.radius.sm : 'none'};
box-sizing: border-box;
cursor: ${({ showInteractiveStyle }) =>
showInteractiveStyle ? 'pointer' : 'default'};
display: flex;
height: calc(
100% +
${({ topExpansion, bottomExpansion }) => topExpansion + bottomExpansion}px
);
margin-left: ${({ leftExpansion }) => -leftExpansion}px;
margin-top: ${({ topExpansion }) => -topExpansion}px;
height: ${RECORD_TABLE_ROW_HEIGHT}px;
outline: ${({ showInteractiveStyle, isRecordTableRowActive }) =>
isRecordTableRowActive
@@ -54,28 +45,7 @@ const StyledRecordTableCellHoveredPortalContent = styled.div<{
: `1px solid ${themeCssVariables.border.color.medium}`};
outline-offset: -1px;
position: relative;
user-select: none;
width: calc(
100% +
${({ leftExpansion, rightExpansion }) => leftExpansion + rightExpansion}px
);
`;
const StyledContentWrapper = styled.div<{
contentTop: number;
contentLeft: number;
contentWidth: string;
contentHeight: string;
}>`
align-items: center;
box-sizing: border-box;
display: flex;
height: ${({ contentHeight }) => contentHeight};
left: ${({ contentLeft }) => contentLeft}px;
position: absolute;
top: ${({ contentTop }) => contentTop}px;
width: ${({ contentWidth }) => contentWidth};
`;
export const RecordTableCellHoveredPortalContent = () => {
@@ -105,51 +75,23 @@ export const RecordTableCellHoveredPortalContent = () => {
rowIndex,
);
const isTouchingHeader = recordTableHoverPosition?.row === 0;
const isTouchingFirstColumn = recordTableHoverPosition?.column === 1;
const topExpansion = showInteractiveStyle ? (isTouchingHeader ? 0 : 1) : 1;
const leftExpansion = showInteractiveStyle
? isTouchingFirstColumn
? 0
: 1
: 1;
const rightExpansion = 1;
const bottomExpansion = 1;
const contentTop = topExpansion;
const contentLeft = leftExpansion;
const contentWidth = `calc(100% - ${leftExpansion + rightExpansion}px)`;
const contentHeight = `calc(100% - ${topExpansion + bottomExpansion}px)`;
return (
<StyledRecordTableCellHoveredPortalContent
showInteractiveStyle={showInteractiveStyle}
isRecordTableRowActive={isRecordTableRowActive}
topExpansion={topExpansion}
leftExpansion={leftExpansion}
rightExpansion={rightExpansion}
bottomExpansion={bottomExpansion}
>
<StyledContentWrapper
contentTop={contentTop}
contentLeft={contentLeft}
contentWidth={contentWidth}
contentHeight={contentHeight}
>
<FieldFocusStaticFocusedProvider>
{isFieldInputOnly ? (
<RecordTableCellEditMode>
<RecordTableCellFieldInput />
</RecordTableCellEditMode>
) : (
<RecordTableCellDisplayMode>
<FieldDisplay />
</RecordTableCellDisplayMode>
)}
</FieldFocusStaticFocusedProvider>
{showButton && <RecordTableCellEditButton />}
</StyledContentWrapper>
<FieldFocusStaticFocusedProvider>
{isFieldInputOnly ? (
<RecordTableCellEditMode>
<RecordTableCellFieldInput />
</RecordTableCellEditMode>
) : (
<RecordTableCellDisplayMode>
<FieldDisplay />
</RecordTableCellDisplayMode>
)}
</FieldFocusStaticFocusedProvider>
{showButton && <RecordTableCellEditButton />}
</StyledRecordTableCellHoveredPortalContent>
);
};

Some files were not shown because too many files have changed in this diff Show More