Compare commits
17
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e259d75e8 | ||
|
|
c96c034908 | ||
|
|
5efe69f8d3 | ||
|
|
c2b058a6a7 | ||
|
|
22c9693ce5 | ||
|
|
50ea560e57 | ||
|
|
56056b885d | ||
|
|
db9da3194f | ||
|
|
68f5e70ade | ||
|
|
08077476f3 | ||
|
|
6f0ac88e20 | ||
|
|
17424320e3 | ||
|
|
6360fb3bce | ||
|
|
da1b1f1cbc | ||
|
|
31718d163c | ||
|
|
f47608de07 | ||
|
|
695518a15e |
@@ -203,6 +203,7 @@
|
||||
"packages/twenty-utils",
|
||||
"packages/twenty-zapier",
|
||||
"packages/twenty-website",
|
||||
"packages/twenty-website-new",
|
||||
"packages/twenty-docs",
|
||||
"packages/twenty-e2e-testing",
|
||||
"packages/twenty-shared",
|
||||
|
||||
@@ -343,6 +343,7 @@ type Field {
|
||||
defaultValue: JSON
|
||||
options: JSON
|
||||
settings: JSON
|
||||
objectMetadataId: UUID!
|
||||
isLabelSyncedWithName: Boolean
|
||||
morphId: UUID
|
||||
createdAt: DateTime!
|
||||
@@ -540,6 +541,7 @@ input FieldFilter {
|
||||
isActive: BooleanFieldComparison
|
||||
isSystem: BooleanFieldComparison
|
||||
isUIReadOnly: BooleanFieldComparison
|
||||
objectMetadataId: UUIDFilterComparison
|
||||
}
|
||||
|
||||
input IndexFilter {
|
||||
@@ -2273,6 +2275,11 @@ type FieldConnection {
|
||||
edges: [FieldEdge!]!
|
||||
}
|
||||
|
||||
type LogicFunctionLogs {
|
||||
"""Execution Logs"""
|
||||
logs: String!
|
||||
}
|
||||
|
||||
type DeleteTwoFactorAuthenticationMethod {
|
||||
"""Boolean that confirms query was dispatched"""
|
||||
success: Boolean!
|
||||
@@ -2593,11 +2600,6 @@ type UsageAnalytics {
|
||||
userDailyUsage: UsageUserDaily
|
||||
}
|
||||
|
||||
type LogicFunctionLogs {
|
||||
"""Execution Logs"""
|
||||
logs: String!
|
||||
}
|
||||
|
||||
type FrontComponent {
|
||||
id: UUID!
|
||||
name: String!
|
||||
@@ -3090,6 +3092,7 @@ enum AllMetadataName {
|
||||
navigationMenuItem
|
||||
permissionFlag
|
||||
objectPermission
|
||||
fieldPermission
|
||||
frontComponent
|
||||
webhook
|
||||
}
|
||||
@@ -3501,6 +3504,7 @@ 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!
|
||||
@@ -4272,8 +4276,8 @@ input CreateFieldInput {
|
||||
defaultValue: JSON
|
||||
options: JSON
|
||||
settings: JSON
|
||||
isLabelSyncedWithName: Boolean
|
||||
objectMetadataId: UUID!
|
||||
isLabelSyncedWithName: Boolean
|
||||
isRemoteCreation: Boolean
|
||||
relationCreationPayload: JSON
|
||||
morphRelationsCreationPayload: [JSON!]
|
||||
@@ -4301,6 +4305,7 @@ input UpdateFieldInput {
|
||||
defaultValue: JSON
|
||||
options: JSON
|
||||
settings: JSON
|
||||
objectMetadataId: UUID
|
||||
isLabelSyncedWithName: Boolean
|
||||
morphRelationsUpdatePayload: [JSON!]
|
||||
}
|
||||
|
||||
@@ -296,6 +296,7 @@ export interface Field {
|
||||
defaultValue?: Scalars['JSON']
|
||||
options?: Scalars['JSON']
|
||||
settings?: Scalars['JSON']
|
||||
objectMetadataId: Scalars['UUID']
|
||||
isLabelSyncedWithName?: Scalars['Boolean']
|
||||
morphId?: Scalars['UUID']
|
||||
createdAt: Scalars['DateTime']
|
||||
@@ -1937,6 +1938,12 @@ 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']
|
||||
@@ -2298,12 +2305,6 @@ export interface UsageAnalytics {
|
||||
__typename: 'UsageAnalytics'
|
||||
}
|
||||
|
||||
export interface LogicFunctionLogs {
|
||||
/** Execution Logs */
|
||||
logs: Scalars['String']
|
||||
__typename: 'LogicFunctionLogs'
|
||||
}
|
||||
|
||||
export interface FrontComponent {
|
||||
id: Scalars['UUID']
|
||||
name: Scalars['String']
|
||||
@@ -2676,7 +2677,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' | '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' | 'fieldPermission' | 'frontComponent' | 'webhook'
|
||||
|
||||
export interface MinimalObjectMetadata {
|
||||
id: Scalars['UUID']
|
||||
@@ -2954,6 +2955,7 @@ export interface Mutation {
|
||||
createViewGroup: ViewGroup
|
||||
createManyViewGroups: ViewGroup[]
|
||||
updateViewGroup: ViewGroup
|
||||
updateManyViewGroups: ViewGroup[]
|
||||
deleteViewGroup: ViewGroup
|
||||
destroyViewGroup: ViewGroup
|
||||
updateMessageFolder: MessageFolder
|
||||
@@ -3360,6 +3362,7 @@ export interface FieldGenqlSelection{
|
||||
defaultValue?: boolean | number
|
||||
options?: boolean | number
|
||||
settings?: boolean | number
|
||||
objectMetadataId?: boolean | number
|
||||
isLabelSyncedWithName?: boolean | number
|
||||
morphId?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
@@ -3476,7 +3479,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)}
|
||||
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 IndexFilter {and?: (IndexFilter[] | null),or?: (IndexFilter[] | null),id?: (UUIDFilterComparison | null),isCustom?: (BooleanFieldComparison | null)}
|
||||
|
||||
@@ -5096,6 +5099,13 @@ 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
|
||||
@@ -5501,13 +5511,6 @@ 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
|
||||
@@ -6207,6 +6210,7 @@ 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} })
|
||||
@@ -6527,7 +6531,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),isLabelSyncedWithName?: (Scalars['Boolean'] | null),objectMetadataId: Scalars['UUID'],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),objectMetadataId: Scalars['UUID'],isLabelSyncedWithName?: (Scalars['Boolean'] | null),isRemoteCreation?: (Scalars['Boolean'] | null),relationCreationPayload?: (Scalars['JSON'] | null),morphRelationsCreationPayload?: (Scalars['JSON'][] | null)}
|
||||
|
||||
export interface UpdateOneFieldMetadataInput {
|
||||
/** The id of the record to update */
|
||||
@@ -6535,7 +6539,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),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),objectMetadataId?: (Scalars['UUID'] | null),isLabelSyncedWithName?: (Scalars['Boolean'] | null),morphRelationsUpdatePayload?: (Scalars['JSON'][] | null)}
|
||||
|
||||
export interface DeleteOneFieldInput {
|
||||
/** The id of the field to delete. */
|
||||
@@ -8047,6 +8051,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 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"')
|
||||
@@ -8431,14 +8443,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 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"')
|
||||
@@ -9425,6 +9429,7 @@ 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,6 +777,9 @@ export default {
|
||||
"settings": [
|
||||
15
|
||||
],
|
||||
"objectMetadataId": [
|
||||
3
|
||||
],
|
||||
"isLabelSyncedWithName": [
|
||||
6
|
||||
],
|
||||
@@ -1164,6 +1167,9 @@ export default {
|
||||
"isUIReadOnly": [
|
||||
43
|
||||
],
|
||||
"objectMetadataId": [
|
||||
42
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
@@ -4458,6 +4464,14 @@ export default {
|
||||
1
|
||||
]
|
||||
},
|
||||
"LogicFunctionLogs": {
|
||||
"logs": [
|
||||
1
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"DeleteTwoFactorAuthenticationMethod": {
|
||||
"success": [
|
||||
6
|
||||
@@ -4503,10 +4517,10 @@ export default {
|
||||
},
|
||||
"AuthTokenPair": {
|
||||
"accessOrWorkspaceAgnosticToken": [
|
||||
250
|
||||
251
|
||||
],
|
||||
"refreshToken": [
|
||||
250
|
||||
251
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -4514,7 +4528,7 @@ export default {
|
||||
},
|
||||
"AvailableWorkspacesAndAccessTokens": {
|
||||
"tokens": [
|
||||
251
|
||||
252
|
||||
],
|
||||
"availableWorkspaces": [
|
||||
224
|
||||
@@ -4566,10 +4580,10 @@ export default {
|
||||
},
|
||||
"SignUp": {
|
||||
"loginToken": [
|
||||
250
|
||||
251
|
||||
],
|
||||
"workspace": [
|
||||
256
|
||||
257
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -4577,7 +4591,7 @@ export default {
|
||||
},
|
||||
"TransientToken": {
|
||||
"transientToken": [
|
||||
250
|
||||
251
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -4599,7 +4613,7 @@ export default {
|
||||
},
|
||||
"VerifyEmailAndGetLoginToken": {
|
||||
"loginToken": [
|
||||
250
|
||||
251
|
||||
],
|
||||
"workspaceUrls": [
|
||||
157
|
||||
@@ -4618,7 +4632,7 @@ export default {
|
||||
},
|
||||
"AuthTokens": {
|
||||
"tokens": [
|
||||
251
|
||||
252
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -4626,7 +4640,7 @@ export default {
|
||||
},
|
||||
"LoginToken": {
|
||||
"loginToken": [
|
||||
250
|
||||
251
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -4656,10 +4670,10 @@ export default {
|
||||
},
|
||||
"Impersonate": {
|
||||
"loginToken": [
|
||||
250
|
||||
251
|
||||
],
|
||||
"workspace": [
|
||||
256
|
||||
257
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -4689,10 +4703,10 @@ export default {
|
||||
},
|
||||
"ApplicationTokenPair": {
|
||||
"applicationAccessToken": [
|
||||
250
|
||||
251
|
||||
],
|
||||
"applicationRefreshToken": [
|
||||
250
|
||||
251
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -4764,7 +4778,7 @@ export default {
|
||||
1
|
||||
],
|
||||
"fields": [
|
||||
271
|
||||
272
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -4861,10 +4875,10 @@ export default {
|
||||
6
|
||||
],
|
||||
"objectPermissions": [
|
||||
275
|
||||
276
|
||||
],
|
||||
"fieldPermissions": [
|
||||
276
|
||||
277
|
||||
],
|
||||
"permissionFlags": [
|
||||
1
|
||||
@@ -4914,19 +4928,19 @@ export default {
|
||||
1
|
||||
],
|
||||
"objects": [
|
||||
272
|
||||
],
|
||||
"fields": [
|
||||
271
|
||||
],
|
||||
"logicFunctions": [
|
||||
273
|
||||
],
|
||||
"frontComponents": [
|
||||
"fields": [
|
||||
272
|
||||
],
|
||||
"logicFunctions": [
|
||||
274
|
||||
],
|
||||
"frontComponents": [
|
||||
275
|
||||
],
|
||||
"defaultRole": [
|
||||
277
|
||||
278
|
||||
],
|
||||
"sourcePackage": [
|
||||
1
|
||||
@@ -4986,13 +5000,13 @@ export default {
|
||||
1
|
||||
],
|
||||
"driver": [
|
||||
282
|
||||
],
|
||||
"status": [
|
||||
283
|
||||
],
|
||||
"status": [
|
||||
284
|
||||
],
|
||||
"verificationRecords": [
|
||||
280
|
||||
281
|
||||
],
|
||||
"verifiedAt": [
|
||||
4
|
||||
@@ -5039,7 +5053,7 @@ export default {
|
||||
1
|
||||
],
|
||||
"location": [
|
||||
285
|
||||
286
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -5067,13 +5081,13 @@ export default {
|
||||
},
|
||||
"ImapSmtpCaldavConnectionParameters": {
|
||||
"IMAP": [
|
||||
287
|
||||
288
|
||||
],
|
||||
"SMTP": [
|
||||
287
|
||||
288
|
||||
],
|
||||
"CALDAV": [
|
||||
287
|
||||
288
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -5093,7 +5107,7 @@ export default {
|
||||
3
|
||||
],
|
||||
"connectionParameters": [
|
||||
288
|
||||
289
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -5157,7 +5171,7 @@ export default {
|
||||
1
|
||||
],
|
||||
"dailyUsage": [
|
||||
293
|
||||
294
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -5165,13 +5179,13 @@ export default {
|
||||
},
|
||||
"UsageAnalytics": {
|
||||
"usageByUser": [
|
||||
292
|
||||
293
|
||||
],
|
||||
"usageByOperationType": [
|
||||
292
|
||||
293
|
||||
],
|
||||
"timeSeries": [
|
||||
293
|
||||
294
|
||||
],
|
||||
"periodStart": [
|
||||
4
|
||||
@@ -5180,15 +5194,7 @@ export default {
|
||||
4
|
||||
],
|
||||
"userDailyUsage": [
|
||||
294
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"LogicFunctionLogs": {
|
||||
"logs": [
|
||||
1
|
||||
295
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -5235,7 +5241,7 @@ export default {
|
||||
6
|
||||
],
|
||||
"applicationTokenPair": [
|
||||
269
|
||||
270
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -6656,7 +6662,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"checkUserExists": [
|
||||
264,
|
||||
265,
|
||||
{
|
||||
"email": [
|
||||
1,
|
||||
@@ -6668,7 +6674,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"checkWorkspaceInviteHashIsValid": [
|
||||
265,
|
||||
266,
|
||||
{
|
||||
"inviteHash": [
|
||||
1,
|
||||
@@ -6686,7 +6692,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"validatePasswordResetToken": [
|
||||
259,
|
||||
260,
|
||||
{
|
||||
"passwordResetToken": [
|
||||
1,
|
||||
@@ -6769,7 +6775,7 @@ export default {
|
||||
220
|
||||
],
|
||||
"getConnectedImapSmtpCaldavAccount": [
|
||||
289,
|
||||
290,
|
||||
{
|
||||
"id": [
|
||||
3,
|
||||
@@ -6778,7 +6784,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"getAutoCompleteAddress": [
|
||||
284,
|
||||
285,
|
||||
{
|
||||
"address": [
|
||||
1,
|
||||
@@ -6797,7 +6803,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"getAddressDetails": [
|
||||
286,
|
||||
287,
|
||||
{
|
||||
"placeId": [
|
||||
1,
|
||||
@@ -6889,19 +6895,19 @@ export default {
|
||||
}
|
||||
],
|
||||
"getPostgresCredentials": [
|
||||
291
|
||||
292
|
||||
],
|
||||
"findManyPublicDomains": [
|
||||
279
|
||||
280
|
||||
],
|
||||
"getEmailingDomains": [
|
||||
281
|
||||
282
|
||||
],
|
||||
"findManyMarketplaceApps": [
|
||||
278
|
||||
279
|
||||
],
|
||||
"findOneMarketplaceApp": [
|
||||
278,
|
||||
279,
|
||||
{
|
||||
"universalIdentifier": [
|
||||
1,
|
||||
@@ -6924,7 +6930,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"getUsageAnalytics": [
|
||||
295,
|
||||
296,
|
||||
{
|
||||
"input": [
|
||||
365
|
||||
@@ -8130,6 +8136,15 @@ export default {
|
||||
]
|
||||
}
|
||||
],
|
||||
"updateManyViewGroups": [
|
||||
56,
|
||||
{
|
||||
"inputs": [
|
||||
450,
|
||||
"[UpdateViewGroupInput!]!"
|
||||
]
|
||||
}
|
||||
],
|
||||
"deleteViewGroup": [
|
||||
56,
|
||||
{
|
||||
@@ -8300,7 +8315,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"getAuthorizationUrlForSSO": [
|
||||
254,
|
||||
255,
|
||||
{
|
||||
"input": [
|
||||
466,
|
||||
@@ -8309,7 +8324,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"getLoginTokenFromCredentials": [
|
||||
263,
|
||||
264,
|
||||
{
|
||||
"email": [
|
||||
1,
|
||||
@@ -8335,7 +8350,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"signIn": [
|
||||
252,
|
||||
253,
|
||||
{
|
||||
"email": [
|
||||
1,
|
||||
@@ -8357,7 +8372,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"verifyEmailAndGetLoginToken": [
|
||||
260,
|
||||
261,
|
||||
{
|
||||
"emailVerificationToken": [
|
||||
1,
|
||||
@@ -8377,7 +8392,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"verifyEmailAndGetWorkspaceAgnosticToken": [
|
||||
252,
|
||||
253,
|
||||
{
|
||||
"emailVerificationToken": [
|
||||
1,
|
||||
@@ -8393,7 +8408,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"getAuthTokensFromOTP": [
|
||||
262,
|
||||
263,
|
||||
{
|
||||
"otp": [
|
||||
1,
|
||||
@@ -8413,7 +8428,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"signUp": [
|
||||
252,
|
||||
253,
|
||||
{
|
||||
"email": [
|
||||
1,
|
||||
@@ -8435,7 +8450,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"signUpInWorkspace": [
|
||||
257,
|
||||
258,
|
||||
{
|
||||
"email": [
|
||||
1,
|
||||
@@ -8466,13 +8481,13 @@ export default {
|
||||
}
|
||||
],
|
||||
"signUpInNewWorkspace": [
|
||||
257
|
||||
],
|
||||
"generateTransientToken": [
|
||||
258
|
||||
],
|
||||
"generateTransientToken": [
|
||||
259
|
||||
],
|
||||
"getAuthTokensFromLoginToken": [
|
||||
262,
|
||||
263,
|
||||
{
|
||||
"loginToken": [
|
||||
1,
|
||||
@@ -8485,7 +8500,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"authorizeApp": [
|
||||
249,
|
||||
250,
|
||||
{
|
||||
"clientId": [
|
||||
1,
|
||||
@@ -8507,7 +8522,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"renewToken": [
|
||||
262,
|
||||
263,
|
||||
{
|
||||
"appToken": [
|
||||
1,
|
||||
@@ -8516,7 +8531,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"generateApiKeyToken": [
|
||||
261,
|
||||
262,
|
||||
{
|
||||
"apiKeyId": [
|
||||
3,
|
||||
@@ -8529,7 +8544,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"emailPasswordResetLink": [
|
||||
253,
|
||||
254,
|
||||
{
|
||||
"email": [
|
||||
1,
|
||||
@@ -8541,7 +8556,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"updatePasswordViaResetToken": [
|
||||
255,
|
||||
256,
|
||||
{
|
||||
"passwordResetToken": [
|
||||
1,
|
||||
@@ -8642,7 +8657,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"initiateOTPProvisioning": [
|
||||
247,
|
||||
248,
|
||||
{
|
||||
"loginToken": [
|
||||
1,
|
||||
@@ -8655,10 +8670,10 @@ export default {
|
||||
}
|
||||
],
|
||||
"initiateOTPProvisioningForAuthenticatedUser": [
|
||||
247
|
||||
248
|
||||
],
|
||||
"deleteTwoFactorAuthenticationMethod": [
|
||||
246,
|
||||
247,
|
||||
{
|
||||
"twoFactorAuthenticationMethodId": [
|
||||
3,
|
||||
@@ -8667,7 +8682,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"verifyTwoFactorAuthenticationMethodForAuthenticatedUser": [
|
||||
248,
|
||||
249,
|
||||
{
|
||||
"otp": [
|
||||
1,
|
||||
@@ -8773,7 +8788,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"impersonate": [
|
||||
266,
|
||||
267,
|
||||
{
|
||||
"userId": [
|
||||
3,
|
||||
@@ -8795,7 +8810,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"saveImapSmtpCaldavAccount": [
|
||||
290,
|
||||
291,
|
||||
{
|
||||
"accountOwnerId": [
|
||||
3,
|
||||
@@ -8998,13 +9013,13 @@ export default {
|
||||
}
|
||||
],
|
||||
"enablePostgresProxy": [
|
||||
291
|
||||
292
|
||||
],
|
||||
"disablePostgresProxy": [
|
||||
291
|
||||
292
|
||||
],
|
||||
"createPublicDomain": [
|
||||
279,
|
||||
280,
|
||||
{
|
||||
"domain": [
|
||||
1,
|
||||
@@ -9031,14 +9046,14 @@ export default {
|
||||
}
|
||||
],
|
||||
"createEmailingDomain": [
|
||||
281,
|
||||
282,
|
||||
{
|
||||
"domain": [
|
||||
1,
|
||||
"String!"
|
||||
],
|
||||
"driver": [
|
||||
282,
|
||||
283,
|
||||
"EmailingDomainDriver!"
|
||||
]
|
||||
}
|
||||
@@ -9053,7 +9068,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"verifyEmailingDomain": [
|
||||
281,
|
||||
282,
|
||||
{
|
||||
"id": [
|
||||
1,
|
||||
@@ -9130,7 +9145,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"createDevelopmentApplication": [
|
||||
267,
|
||||
268,
|
||||
{
|
||||
"universalIdentifier": [
|
||||
1,
|
||||
@@ -9143,7 +9158,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"generateApplicationToken": [
|
||||
269,
|
||||
270,
|
||||
{
|
||||
"applicationId": [
|
||||
3,
|
||||
@@ -9152,7 +9167,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"syncApplication": [
|
||||
268,
|
||||
269,
|
||||
{
|
||||
"manifest": [
|
||||
15,
|
||||
@@ -9161,7 +9176,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"uploadApplicationFile": [
|
||||
270,
|
||||
271,
|
||||
{
|
||||
"file": [
|
||||
372,
|
||||
@@ -9195,7 +9210,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"renewApplicationToken": [
|
||||
269,
|
||||
270,
|
||||
{
|
||||
"applicationRefreshToken": [
|
||||
1,
|
||||
@@ -10745,12 +10760,12 @@ export default {
|
||||
"settings": [
|
||||
15
|
||||
],
|
||||
"isLabelSyncedWithName": [
|
||||
6
|
||||
],
|
||||
"objectMetadataId": [
|
||||
3
|
||||
],
|
||||
"isLabelSyncedWithName": [
|
||||
6
|
||||
],
|
||||
"isRemoteCreation": [
|
||||
6
|
||||
],
|
||||
@@ -10815,6 +10830,9 @@ export default {
|
||||
"settings": [
|
||||
15
|
||||
],
|
||||
"objectMetadataId": [
|
||||
3
|
||||
],
|
||||
"isLabelSyncedWithName": [
|
||||
6
|
||||
],
|
||||
@@ -11455,7 +11473,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"logicFunctionLogs": [
|
||||
296,
|
||||
246,
|
||||
{
|
||||
"input": [
|
||||
490,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6686,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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"
|
||||
|
||||
@@ -6681,6 +6681,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6681,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,6 +6686,11 @@ 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,8 +31,6 @@ 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>,
|
||||
@@ -94,7 +92,6 @@ export const useAgentChat = (
|
||||
return null;
|
||||
}
|
||||
|
||||
cookieStorage.setItem('tokenPair', JSON.stringify(renewedTokens));
|
||||
setTokenPair(renewedTokens);
|
||||
|
||||
const updatedHeaders = new Headers(init?.headers ?? {});
|
||||
|
||||
@@ -34,7 +34,6 @@ 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');
|
||||
@@ -175,7 +174,6 @@ export class ApolloFactory implements ApolloManager {
|
||||
|
||||
if (isDefined(tokens)) {
|
||||
onTokenPairChange?.(tokens);
|
||||
cookieStorage.setItem('tokenPair', JSON.stringify(tokens));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+1
@@ -32,6 +32,7 @@ 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`,
|
||||
|
||||
+10
-2
@@ -6,11 +6,13 @@ 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,
|
||||
@@ -55,6 +57,9 @@ 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[]) => {
|
||||
@@ -194,7 +199,10 @@ export const useLoadStaleMetadataEntities = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (staleEntityKeys.includes('commandMenuItems')) {
|
||||
if (
|
||||
staleEntityKeys.includes('commandMenuItems') &&
|
||||
isCommandMenuItemEnabled
|
||||
) {
|
||||
fetchPromises.push(
|
||||
client
|
||||
.query({
|
||||
@@ -214,7 +222,7 @@ export const useLoadStaleMetadataEntities = () => {
|
||||
await Promise.all(fetchPromises);
|
||||
applyChanges();
|
||||
},
|
||||
[client, store, replaceDraft, applyChanges],
|
||||
[client, store, replaceDraft, applyChanges, isCommandMenuItemEnabled],
|
||||
);
|
||||
|
||||
return { loadStaleMetadataEntities };
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import type { DropDestination } from '@/navigation-menu-item/common/types/navigationMenuItemDndKitDropDestination';
|
||||
|
||||
export type NavigationMenuItemDropResult = {
|
||||
source: DropDestination;
|
||||
destination: DropDestination | null;
|
||||
draggableId: string;
|
||||
insertBeforeItemId?: string | null;
|
||||
};
|
||||
+2
-2
@@ -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: DropResult['destination'];
|
||||
destination: DropDestination | null;
|
||||
}): string | null => {
|
||||
if (
|
||||
!destination ||
|
||||
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
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>
|
||||
);
|
||||
};
|
||||
-6
@@ -1,6 +0,0 @@
|
||||
export const DROP_RESULT_OPTIONS = {
|
||||
reason: 'DROP' as const,
|
||||
combine: null,
|
||||
mode: 'FLUID' as const,
|
||||
type: 'DEFAULT' as const,
|
||||
};
|
||||
+3
-2
@@ -1,4 +1,3 @@
|
||||
import type { DropResult, ResponderProvided } from '@hello-pangea/dnd';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useCallback } from 'react';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
@@ -28,6 +27,8 @@ 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();
|
||||
@@ -50,7 +51,7 @@ export const useHandleAddToNavigationDrop = () => {
|
||||
);
|
||||
|
||||
const handleAddToNavigationDrop = useCallback(
|
||||
(result: DropResult, _provided: ResponderProvided) => {
|
||||
(result: NavigationMenuItemDropResult) => {
|
||||
const { source, destination, draggableId } = result;
|
||||
if (
|
||||
source.droppableId !== ADD_TO_NAV_SOURCE_DROPPABLE_ID ||
|
||||
|
||||
+4
-5
@@ -1,8 +1,9 @@
|
||||
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';
|
||||
@@ -115,10 +116,8 @@ export const useHandleNavigationMenuItemDragAndDrop = (
|
||||
});
|
||||
};
|
||||
|
||||
const handleNavigationMenuItemDragAndDrop: OnDragEndResponder = async (
|
||||
result: Parameters<OnDragEndResponder>[0] & {
|
||||
insertBeforeItemId?: string | null;
|
||||
},
|
||||
const handleNavigationMenuItemDragAndDrop = async (
|
||||
result: NavigationMenuItemDropResult,
|
||||
) => {
|
||||
const { destination, source, draggableId } = result;
|
||||
|
||||
|
||||
+18
-24
@@ -1,6 +1,5 @@
|
||||
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';
|
||||
@@ -11,17 +10,16 @@ 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';
|
||||
@@ -172,23 +170,13 @@ export const useNavigationMenuItemDndKit = (
|
||||
destination: DropDestination,
|
||||
insertBeforeItemId?: string | null,
|
||||
) => {
|
||||
const result = toDropResult(
|
||||
id,
|
||||
{
|
||||
sourceDroppableId: source.droppableId,
|
||||
sourceIndex: source.index,
|
||||
},
|
||||
const result: NavigationMenuItemDropResult = {
|
||||
draggableId: id,
|
||||
source,
|
||||
destination,
|
||||
);
|
||||
const provided: ResponderProvided = { announce: () => {} };
|
||||
handleNavigationMenuItemDragAndDrop(
|
||||
{
|
||||
...result,
|
||||
...DROP_RESULT_OPTIONS,
|
||||
...(insertBeforeItemId != null && { insertBeforeItemId }),
|
||||
},
|
||||
provided,
|
||||
);
|
||||
...(insertBeforeItemId != null && { insertBeforeItemId }),
|
||||
};
|
||||
handleNavigationMenuItemDragAndDrop(result);
|
||||
},
|
||||
[handleNavigationMenuItemDragAndDrop],
|
||||
);
|
||||
@@ -351,12 +339,18 @@ export const useNavigationMenuItemDndKit = (
|
||||
destination = fallback;
|
||||
}
|
||||
|
||||
const result = toDropResult(draggableId, data, destination);
|
||||
const provided: ResponderProvided = { announce: () => {} };
|
||||
const dropResult = { ...result, ...DROP_RESULT_OPTIONS };
|
||||
const dropResult: NavigationMenuItemDropResult = {
|
||||
draggableId,
|
||||
source: {
|
||||
droppableId: data?.sourceDroppableId ?? '',
|
||||
index: data?.sourceIndex ?? 0,
|
||||
},
|
||||
destination,
|
||||
...(insertBeforeItemId != null && { insertBeforeItemId }),
|
||||
};
|
||||
|
||||
if (sourceId === ADD_TO_NAV_SOURCE_DROPPABLE_ID) {
|
||||
handleAddToNavigationDrop(dropResult, provided);
|
||||
handleAddToNavigationDrop(dropResult);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -386,7 +380,7 @@ export const useNavigationMenuItemDndKit = (
|
||||
return;
|
||||
}
|
||||
|
||||
handleNavigationMenuItemDragAndDrop(dropResult, provided);
|
||||
handleNavigationMenuItemDragAndDrop(dropResult);
|
||||
};
|
||||
|
||||
const contextValues: NavigationMenuItemDndKitContextValues = {
|
||||
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
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,
|
||||
};
|
||||
};
|
||||
-102
@@ -1,102 +0,0 @@
|
||||
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,12 +20,14 @@ 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;
|
||||
|
||||
+4
-1
@@ -8,14 +8,17 @@ const StyledContainer = styled.div`
|
||||
align-items: center;
|
||||
background: ${themeCssVariables.background.transparent.light};
|
||||
|
||||
border-radius: 4px;
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
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 = () => {
|
||||
|
||||
+10
-3
@@ -91,13 +91,20 @@ export const MultiItemFieldInput = <T,>({
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const { isValid, updatedItems } = validateInputAndComputeUpdatedItems();
|
||||
|
||||
if (!isValid) {
|
||||
if (isInputDisplayed) {
|
||||
const { isValid, updatedItems } = validateInputAndComputeUpdatedItems();
|
||||
|
||||
if (!isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(updatedItems);
|
||||
onClickOutside(updatedItems, event);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
onChange(updatedItems);
|
||||
onClickOutside(items, event);
|
||||
},
|
||||
listenerId: instanceId,
|
||||
|
||||
@@ -76,6 +76,12 @@ export const useEditPageLayoutWidget = (pageLayoutIdFromProps?: string) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (widgetType === WidgetType.STANDALONE_RICH_TEXT) {
|
||||
setPageLayoutEditingWidgetId(widgetId);
|
||||
closeSidePanelMenu();
|
||||
return;
|
||||
}
|
||||
|
||||
setSidePanelPage(SidePanelPages.CommandMenuDisplay);
|
||||
closeSidePanelMenu();
|
||||
},
|
||||
|
||||
+29
-13
@@ -1,8 +1,20 @@
|
||||
import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { addToNavPayloadRegistryState } from '@/navigation-menu-item/common/states/addToNavPayloadRegistryState';
|
||||
import { pendingInsertionNavigationMenuItemState } from '@/navigation-menu-item/common/states/pendingInsertionNavigationMenuItemState';
|
||||
import { selectedNavigationMenuItemIdInEditModeState } from '@/navigation-menu-item/common/states/selectedNavigationMenuItemIdInEditModeState';
|
||||
import { viewableRecordIdState } from '@/object-record/record-side-panel/states/viewableRecordIdState';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
|
||||
import { SIDE_PANEL_COMPONENT_INSTANCE_ID } from '@/side-panel/constants/SidePanelComponentInstanceId';
|
||||
import { SIDE_PANEL_CONTEXT_CHIP_GROUPS_DROPDOWN_ID } from '@/side-panel/constants/SidePanelContextChipGroupsDropdownId';
|
||||
import { SIDE_PANEL_SELECTABLE_LIST_ID } from '@/side-panel/constants/SidePanelSelectableListId';
|
||||
import { SIDE_PANEL_PREVIOUS_COMPONENT_INSTANCE_ID } from '@/side-panel/constants/SidePanelPreviousComponentInstanceId';
|
||||
import { useResetContextStoreStates } from '@/command-menu/hooks/useResetContextStoreStates';
|
||||
import { SIDE_PANEL_SELECTABLE_LIST_ID } from '@/side-panel/constants/SidePanelSelectableListId';
|
||||
import { hasUserSelectedSidePanelListItemState } from '@/side-panel/states/hasUserSelectedSidePanelListItemState';
|
||||
import { isSidePanelClosingState } from '@/side-panel/states/isSidePanelClosingState';
|
||||
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
|
||||
import { sidePanelNavigationMorphItemsByPageState } from '@/side-panel/states/sidePanelNavigationMorphItemsByPageState';
|
||||
import { sidePanelNavigationStackState } from '@/side-panel/states/sidePanelNavigationStackState';
|
||||
import { sidePanelPageInfoState } from '@/side-panel/states/sidePanelPageInfoState';
|
||||
@@ -10,19 +22,10 @@ import { sidePanelPageState } from '@/side-panel/states/sidePanelPageState';
|
||||
import { sidePanelSearchObjectFilterState } from '@/side-panel/states/sidePanelSearchObjectFilterState';
|
||||
import { sidePanelSearchState } from '@/side-panel/states/sidePanelSearchState';
|
||||
import { sidePanelShowHiddenObjectsState } from '@/side-panel/states/sidePanelShowHiddenObjectsState';
|
||||
import { hasUserSelectedSidePanelListItemState } from '@/side-panel/states/hasUserSelectedSidePanelListItemState';
|
||||
import { isSidePanelClosingState } from '@/side-panel/states/isSidePanelClosingState';
|
||||
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
|
||||
import { contextStoreTargetedRecordsRuleComponentState } from '@/context-store/states/contextStoreTargetedRecordsRuleComponentState';
|
||||
import { viewableRecordIdState } from '@/object-record/record-side-panel/states/viewableRecordIdState';
|
||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||
import { pageLayoutDraggedAreaComponentState } from '@/page-layout/states/pageLayoutDraggedAreaComponentState';
|
||||
import { pageLayoutEditingWidgetIdComponentState } from '@/page-layout/states/pageLayoutEditingWidgetIdComponentState';
|
||||
import { pageLayoutTabSettingsOpenTabIdComponentState } from '@/page-layout/states/pageLayoutTabSettingsOpenTabIdComponentState';
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { emitSidePanelCloseEvent } from '@/ui/layout/side-panel/utils/emitSidePanelCloseEvent';
|
||||
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
|
||||
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
|
||||
import { emitSidePanelCloseEvent } from '@/ui/layout/side-panel/utils/emitSidePanelCloseEvent';
|
||||
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
|
||||
import { WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowLogicFunctionTabListComponentId';
|
||||
import { WorkflowLogicFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowLogicFunctionTabId';
|
||||
@@ -41,6 +44,12 @@ export const useSidePanelCloseAnimationCompleteCleanup = () => {
|
||||
|
||||
const { closeDropdown } = useCloseDropdown();
|
||||
|
||||
const resetNavigationMenuItemState = () => {
|
||||
store.set(selectedNavigationMenuItemIdInEditModeState.atom, null);
|
||||
store.set(pendingInsertionNavigationMenuItemState.atom, null);
|
||||
store.set(addToNavPayloadRegistryState.atom, new Map());
|
||||
};
|
||||
|
||||
const sidePanelCloseAnimationCompleteCleanup = useCallback(
|
||||
(options?: { emitSidePanelCloseEvent?: boolean }) => {
|
||||
closeDropdown(SIDE_PANEL_CONTEXT_CHIP_GROUPS_DROPDOWN_ID);
|
||||
@@ -110,6 +119,7 @@ export const useSidePanelCloseAnimationCompleteCleanup = () => {
|
||||
store.set(sidePanelShowHiddenObjectsState.atom, false);
|
||||
store.set(sidePanelNavigationMorphItemsByPageState.atom, new Map());
|
||||
store.set(sidePanelNavigationStackState.atom, []);
|
||||
resetNavigationMenuItemState();
|
||||
resetSelectedItem();
|
||||
store.set(hasUserSelectedSidePanelListItemState.atom, false);
|
||||
|
||||
@@ -136,7 +146,13 @@ export const useSidePanelCloseAnimationCompleteCleanup = () => {
|
||||
);
|
||||
}
|
||||
},
|
||||
[closeDropdown, resetContextStoreStates, resetSelectedItem, store],
|
||||
[
|
||||
closeDropdown,
|
||||
resetContextStoreStates,
|
||||
resetNavigationMenuItemState,
|
||||
resetSelectedItem,
|
||||
store,
|
||||
],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { isLayoutCustomizationModeEnabledState } from '@/layout-customization/states/isLayoutCustomizationModeEnabledState';
|
||||
import { addToNavPayloadRegistryState } from '@/navigation-menu-item/common/states/addToNavPayloadRegistryState';
|
||||
import { selectedNavigationMenuItemIdInEditModeState } from '@/navigation-menu-item/common/states/selectedNavigationMenuItemIdInEditModeState';
|
||||
import { SIDE_PANEL_FOCUS_ID } from '@/side-panel/constants/SidePanelFocusId';
|
||||
import { useNavigateSidePanel } from '@/side-panel/hooks/useNavigateSidePanel';
|
||||
@@ -29,7 +28,6 @@ export const useSidePanelMenu = () => {
|
||||
const isSidePanelOpened = store.get(isSidePanelOpenedState.atom);
|
||||
|
||||
if (isSidePanelOpened) {
|
||||
store.set(addToNavPayloadRegistryState.atom, new Map());
|
||||
store.set(isSidePanelOpenedState.atom, false);
|
||||
store.set(isSidePanelClosingState.atom, true);
|
||||
closeAnyOpenDropdown();
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import { VIEW_GROUP_FRAGMENT } from '@/views/graphql/fragments/viewGroupFragment';
|
||||
import { gql } from '@apollo/client';
|
||||
|
||||
export const UPDATE_MANY_VIEW_GROUPS = gql`
|
||||
${VIEW_GROUP_FRAGMENT}
|
||||
mutation UpdateManyViewGroups($inputs: [UpdateViewGroupInput!]!) {
|
||||
updateManyViewGroups(inputs: $inputs) {
|
||||
...ViewGroupFragment
|
||||
}
|
||||
}
|
||||
`;
|
||||
+19
-18
@@ -8,43 +8,44 @@ import { t } from '@lingui/core/macro';
|
||||
import { CrudOperationType } from 'twenty-shared/types';
|
||||
import { useMutation } from '@apollo/client/react';
|
||||
import {
|
||||
type UpdateViewGroupMutationVariables,
|
||||
UpdateViewGroupDocument,
|
||||
type UpdateManyViewGroupsMutationVariables,
|
||||
UpdateManyViewGroupsDocument,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const usePerformViewGroupAPIPersist = () => {
|
||||
const [updateViewGroupMutation] = useMutation(UpdateViewGroupDocument);
|
||||
const [updateManyViewGroupsMutation] = useMutation(
|
||||
UpdateManyViewGroupsDocument,
|
||||
);
|
||||
|
||||
const { handleMetadataError } = useMetadataErrorHandler();
|
||||
const { enqueueErrorSnackBar } = useSnackBar();
|
||||
|
||||
const performViewGroupAPIUpdate = useCallback(
|
||||
async (
|
||||
updateViewGroupInputs: UpdateViewGroupMutationVariables[],
|
||||
updateViewGroupInputs: UpdateManyViewGroupsMutationVariables,
|
||||
): Promise<
|
||||
MetadataRequestResult<
|
||||
Awaited<ReturnType<typeof updateViewGroupMutation>>[]
|
||||
>
|
||||
MetadataRequestResult<Awaited<
|
||||
ReturnType<typeof updateManyViewGroupsMutation>
|
||||
> | null>
|
||||
> => {
|
||||
if (updateViewGroupInputs.length === 0) {
|
||||
if (
|
||||
!Array.isArray(updateViewGroupInputs.inputs) ||
|
||||
updateViewGroupInputs.inputs.length === 0
|
||||
) {
|
||||
return {
|
||||
status: 'successful',
|
||||
response: [],
|
||||
response: null,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const results = await Promise.all(
|
||||
updateViewGroupInputs.map((variables) =>
|
||||
updateViewGroupMutation({
|
||||
variables,
|
||||
}),
|
||||
),
|
||||
);
|
||||
const result = await updateManyViewGroupsMutation({
|
||||
variables: updateViewGroupInputs,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'successful',
|
||||
response: results,
|
||||
response: result,
|
||||
};
|
||||
} catch (error) {
|
||||
if (CombinedGraphQLErrors.is(error)) {
|
||||
@@ -62,7 +63,7 @@ export const usePerformViewGroupAPIPersist = () => {
|
||||
};
|
||||
}
|
||||
},
|
||||
[updateViewGroupMutation, handleMetadataError, enqueueErrorSnackBar],
|
||||
[updateManyViewGroupsMutation, handleMetadataError, enqueueErrorSnackBar],
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
@@ -67,9 +67,9 @@ export const useSaveCurrentViewGroups = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
await performViewGroupAPIUpdate([
|
||||
{
|
||||
input: {
|
||||
await performViewGroupAPIUpdate({
|
||||
inputs: [
|
||||
{
|
||||
id: existingField.id,
|
||||
update: {
|
||||
isVisible: viewGroupToSave.isVisible,
|
||||
@@ -77,8 +77,8 @@ export const useSaveCurrentViewGroups = () => {
|
||||
fieldValue: viewGroupToSave.fieldValue,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
],
|
||||
});
|
||||
},
|
||||
[
|
||||
store,
|
||||
@@ -109,7 +109,7 @@ export const useSaveCurrentViewGroups = () => {
|
||||
|
||||
const currentViewGroups = view.viewGroups;
|
||||
|
||||
const viewGroupsToUpdate = viewGroupsToSave
|
||||
const viewGroupInputsToUpdate = viewGroupsToSave
|
||||
.map((viewGroupToSave) => {
|
||||
const existingField = currentViewGroups.find(
|
||||
(currentViewGroup) =>
|
||||
@@ -136,13 +136,11 @@ export const useSaveCurrentViewGroups = () => {
|
||||
}
|
||||
|
||||
return {
|
||||
input: {
|
||||
id: existingField.id,
|
||||
update: {
|
||||
isVisible: viewGroupToSave.isVisible,
|
||||
position: viewGroupToSave.position,
|
||||
fieldValue: viewGroupToSave.fieldValue,
|
||||
},
|
||||
id: existingField.id,
|
||||
update: {
|
||||
isVisible: viewGroupToSave.isVisible,
|
||||
position: viewGroupToSave.position,
|
||||
fieldValue: viewGroupToSave.fieldValue,
|
||||
},
|
||||
};
|
||||
})
|
||||
@@ -152,7 +150,7 @@ export const useSaveCurrentViewGroups = () => {
|
||||
throw new Error('mainGroupByFieldMetadataId is required');
|
||||
}
|
||||
|
||||
await performViewGroupAPIUpdate(viewGroupsToUpdate);
|
||||
await performViewGroupAPIUpdate({ inputs: viewGroupInputsToUpdate });
|
||||
},
|
||||
[
|
||||
store,
|
||||
|
||||
-431
@@ -1,431 +0,0 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import {
|
||||
CoreObjectNameSingular,
|
||||
FieldMetadataType,
|
||||
PageLayoutTabLayoutMode,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
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 { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { FieldDisplayMode } from 'src/engine/metadata-modules/page-layout-widget/enums/field-display-mode.enum';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import {
|
||||
GRID_POSITIONS,
|
||||
VERTICAL_LIST_LAYOUT_POSITIONS,
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
const HOME_TAB_POSITION = 10;
|
||||
|
||||
const isActivityTargetField = (
|
||||
fieldName: string,
|
||||
objectNameSingular: string,
|
||||
): boolean =>
|
||||
(objectNameSingular === CoreObjectNameSingular.Note &&
|
||||
fieldName === 'noteTargets') ||
|
||||
(objectNameSingular === CoreObjectNameSingular.Task &&
|
||||
fieldName === 'taskTargets');
|
||||
|
||||
const isJunctionRelationField = (field: FlatFieldMetadata): boolean => {
|
||||
const settings = field.settings as
|
||||
| { junctionTargetFieldId?: string }
|
||||
| undefined;
|
||||
|
||||
return (
|
||||
isDefined(settings?.junctionTargetFieldId) &&
|
||||
typeof settings?.junctionTargetFieldId === 'string' &&
|
||||
settings.junctionTargetFieldId.length > 0
|
||||
);
|
||||
};
|
||||
|
||||
const isRelationTargetAvailable = (
|
||||
targetObject: FlatObjectMetadata | undefined,
|
||||
): boolean => {
|
||||
if (!isDefined(targetObject)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetObject.isRemote) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
targetObject.isSystem &&
|
||||
targetObject.nameSingular !== CoreObjectNameSingular.WorkspaceMember
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-20:backfill-field-widgets',
|
||||
description:
|
||||
'Backfill FIELD widgets for relation fields in existing page layouts',
|
||||
})
|
||||
export class BackfillFieldWidgetsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Starting backfill of FIELD widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatPageLayoutMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatPageLayoutMaps',
|
||||
'flatPageLayoutTabMaps',
|
||||
'flatPageLayoutWidgetMaps',
|
||||
]);
|
||||
|
||||
// Build a set of fieldMetadataIds that already have a FIELD widget
|
||||
const existingFieldWidgetFieldIds = new Set<string>();
|
||||
|
||||
for (const widget of Object.values(
|
||||
flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (
|
||||
isDefined(widget) &&
|
||||
widget.configuration?.configurationType ===
|
||||
WidgetConfigurationType.FIELD &&
|
||||
isDefined(widget.configuration.fieldMetadataId)
|
||||
) {
|
||||
existingFieldWidgetFieldIds.add(widget.configuration.fieldMetadataId);
|
||||
}
|
||||
}
|
||||
|
||||
// Build object ID → objectMetadata map
|
||||
const objectById = new Map<string, FlatObjectMetadata>();
|
||||
|
||||
for (const obj of Object.values(
|
||||
flatObjectMetadataMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (isDefined(obj)) {
|
||||
objectById.set(obj.id, obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Build object ID → RECORD_PAGE layout map
|
||||
const recordPageLayoutByObjectId = new Map<
|
||||
string,
|
||||
{ id: string; universalIdentifier: string }
|
||||
>();
|
||||
|
||||
for (const layout of Object.values(
|
||||
flatPageLayoutMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (
|
||||
isDefined(layout) &&
|
||||
layout.type === PageLayoutType.RECORD_PAGE &&
|
||||
isDefined(layout.objectMetadataId)
|
||||
) {
|
||||
recordPageLayoutByObjectId.set(layout.objectMetadataId, {
|
||||
id: layout.id,
|
||||
universalIdentifier: layout.universalIdentifier,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Build pageLayoutId → home tab map
|
||||
const homeTabByPageLayoutId = new Map<
|
||||
string,
|
||||
{
|
||||
id: string;
|
||||
universalIdentifier: string;
|
||||
widgetCount: number;
|
||||
}
|
||||
>();
|
||||
|
||||
for (const tab of Object.values(
|
||||
flatPageLayoutTabMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (
|
||||
isDefined(tab) &&
|
||||
tab.position === HOME_TAB_POSITION &&
|
||||
tab.layoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST
|
||||
) {
|
||||
homeTabByPageLayoutId.set(tab.pageLayoutId, {
|
||||
id: tab.id,
|
||||
universalIdentifier: tab.universalIdentifier,
|
||||
widgetCount: tab.widgetIds?.length ?? 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Group fields by objectMetadataId
|
||||
const fieldsByObjectId = new Map<string, FlatFieldMetadata[]>();
|
||||
|
||||
// Map morphId → all field IDs sharing that morphId (for dedup)
|
||||
const fieldIdsByMorphId = new Map<string, string[]>();
|
||||
|
||||
for (const field of Object.values(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (!isDefined(field) || !isDefined(field.objectMetadataId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const list = fieldsByObjectId.get(field.objectMetadataId) ?? [];
|
||||
|
||||
list.push(field);
|
||||
fieldsByObjectId.set(field.objectMetadataId, list);
|
||||
|
||||
if (
|
||||
field.type === FieldMetadataType.MORPH_RELATION &&
|
||||
isDefined(field.morphId)
|
||||
) {
|
||||
const morphFieldIds = fieldIdsByMorphId.get(field.morphId) ?? [];
|
||||
|
||||
morphFieldIds.push(field.id);
|
||||
fieldIdsByMorphId.set(field.morphId, morphFieldIds);
|
||||
}
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const standardWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const customWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const processedMorphIds = new Set<string>();
|
||||
|
||||
for (const object of objectById.values()) {
|
||||
const pageLayout = recordPageLayoutByObjectId.get(object.id);
|
||||
|
||||
if (!isDefined(pageLayout)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const homeTab = homeTabByPageLayoutId.get(pageLayout.id);
|
||||
|
||||
if (!isDefined(homeTab)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const flatApplication: FlatApplication = object.isCustom
|
||||
? workspaceCustomFlatApplication
|
||||
: twentyStandardFlatApplication;
|
||||
|
||||
const targetList: FlatPageLayoutWidget[] = object.isCustom
|
||||
? customWidgetsToCreate
|
||||
: standardWidgetsToCreate;
|
||||
|
||||
const fields = fieldsByObjectId.get(object.id) ?? [];
|
||||
let nextWidgetIndex = homeTab.widgetCount;
|
||||
|
||||
for (const field of fields) {
|
||||
if (
|
||||
field.type !== FieldMetadataType.RELATION &&
|
||||
field.type !== FieldMetadataType.MORPH_RELATION
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isActivityTargetField(field.name, object.nameSingular)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isJunctionRelationField(field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.RELATION) {
|
||||
const targetObject = isDefined(field.relationTargetObjectMetadataId)
|
||||
? objectById.get(field.relationTargetObjectMetadataId)
|
||||
: undefined;
|
||||
|
||||
if (!isRelationTargetAvailable(targetObject)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// For morph relations, skip if any sibling field (same morphId)
|
||||
// already has a widget or was already processed in this run
|
||||
if (
|
||||
field.type === FieldMetadataType.MORPH_RELATION &&
|
||||
isDefined(field.morphId)
|
||||
) {
|
||||
if (processedMorphIds.has(field.morphId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const siblingFieldIds = fieldIdsByMorphId.get(field.morphId) ?? [];
|
||||
const alreadyHasWidget = siblingFieldIds.some((id) =>
|
||||
existingFieldWidgetFieldIds.has(id),
|
||||
);
|
||||
|
||||
if (alreadyHasWidget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
processedMorphIds.add(field.morphId);
|
||||
}
|
||||
|
||||
if (existingFieldWidgetFieldIds.has(field.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const widget: FlatPageLayoutWidget = {
|
||||
id: v4(),
|
||||
universalIdentifier: v4(),
|
||||
applicationId: flatApplication.id,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
workspaceId,
|
||||
pageLayoutTabId: homeTab.id,
|
||||
pageLayoutTabUniversalIdentifier: homeTab.universalIdentifier,
|
||||
title: field.label,
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: { ...GRID_POSITIONS.FULL_WIDTH },
|
||||
position: {
|
||||
...VERTICAL_LIST_LAYOUT_POSITIONS.SECOND,
|
||||
index: nextWidgetIndex,
|
||||
},
|
||||
configuration: {
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: field.id,
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
universalConfiguration: {
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: field.universalIdentifier,
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
objectMetadataId: object.id,
|
||||
objectMetadataUniversalIdentifier: object.universalIdentifier,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
conditionalDisplay: null,
|
||||
overrides: null,
|
||||
};
|
||||
|
||||
targetList.push(widget);
|
||||
existingFieldWidgetFieldIds.add(field.id);
|
||||
nextWidgetIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
const totalWidgets =
|
||||
standardWidgetsToCreate.length + customWidgetsToCreate.length;
|
||||
|
||||
if (totalWidgets === 0) {
|
||||
this.logger.log(
|
||||
`All FIELD widgets already exist for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Found ${totalWidgets} FIELD widget(s) to create for workspace ${workspaceId} (${standardWidgetsToCreate.length} standard, ${customWidgetsToCreate.length} custom)`,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would create ${totalWidgets} FIELD widget(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (standardWidgetsToCreate.length > 0) {
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: standardWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create standard FIELD widgets:\n${JSON.stringify(result, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create standard FIELD widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (customWidgetsToCreate.length > 0) {
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: customWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create custom FIELD widgets:\n${JSON.stringify(result, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create custom FIELD widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully created ${totalWidgets} FIELD widget(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+868
@@ -0,0 +1,868 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import {
|
||||
CoreObjectNameSingular,
|
||||
FeatureFlagKey,
|
||||
FieldMetadataType,
|
||||
PageLayoutTabLayoutMode,
|
||||
ViewType,
|
||||
} from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { 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 { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
|
||||
import { computeFlatDefaultRecordPageLayoutToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-default-record-page-layout-to-create.util';
|
||||
import { computeFlatRecordPageFieldsViewToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-record-page-fields-view-to-create.util';
|
||||
import { computeFlatViewFieldsToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util';
|
||||
import { FieldDisplayMode } from 'src/engine/metadata-modules/page-layout-widget/enums/field-display-mode.enum';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { WidgetType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-type.enum';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import {
|
||||
GRID_POSITIONS,
|
||||
VERTICAL_LIST_LAYOUT_POSITIONS,
|
||||
} from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout-tabs.template';
|
||||
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type';
|
||||
import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type';
|
||||
|
||||
const HOME_TAB_POSITION = 10;
|
||||
|
||||
const isActivityTargetField = (
|
||||
fieldName: string,
|
||||
objectNameSingular: string,
|
||||
): boolean =>
|
||||
(objectNameSingular === CoreObjectNameSingular.Note &&
|
||||
fieldName === 'noteTargets') ||
|
||||
(objectNameSingular === CoreObjectNameSingular.Task &&
|
||||
fieldName === 'taskTargets');
|
||||
|
||||
const isJunctionRelationField = (field: FlatFieldMetadata): boolean => {
|
||||
const settings = field.settings as
|
||||
| { junctionTargetFieldId?: string }
|
||||
| undefined;
|
||||
|
||||
return (
|
||||
isDefined(settings?.junctionTargetFieldId) &&
|
||||
typeof settings?.junctionTargetFieldId === 'string' &&
|
||||
settings.junctionTargetFieldId.length > 0
|
||||
);
|
||||
};
|
||||
|
||||
const isRelationTargetAvailable = (
|
||||
targetObject: FlatObjectMetadata | undefined,
|
||||
): boolean => {
|
||||
if (!isDefined(targetObject)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetObject.isRemote) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
targetObject.isSystem &&
|
||||
targetObject.nameSingular !== CoreObjectNameSingular.WorkspaceMember
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-20:backfill-page-layouts-and-fields-widget-view-fields',
|
||||
description:
|
||||
'Backfill RECORD_PAGE page layouts, sync FIELDS_WIDGET view fields, create FIELD widgets, and enable IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
|
||||
})
|
||||
export class BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Starting backfill of page layouts and field widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const isAlreadyEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isAlreadyEnabled) {
|
||||
this.logger.log(
|
||||
`IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED already enabled for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (isDryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would create page layouts, sync view fields, create FIELD widgets and enable IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
await this.backfillStandardPageLayoutsAndSyncViews({
|
||||
workspaceId,
|
||||
twentyStandardFlatApplication,
|
||||
});
|
||||
|
||||
await this.backfillCustomObjectPageLayouts({
|
||||
workspaceId,
|
||||
workspaceCustomFlatApplication,
|
||||
});
|
||||
|
||||
await this.backfillFieldWidgets({
|
||||
workspaceId,
|
||||
twentyStandardFlatApplication,
|
||||
workspaceCustomFlatApplication,
|
||||
});
|
||||
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Successfully backfilled page layouts and field widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async backfillStandardPageLayoutsAndSyncViews({
|
||||
workspaceId,
|
||||
twentyStandardFlatApplication,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
twentyStandardFlatApplication: FlatApplication;
|
||||
}): Promise<void> {
|
||||
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
|
||||
computeTwentyStandardApplicationAllFlatEntityMaps({
|
||||
shouldIncludeRecordPageLayouts: true,
|
||||
now: new Date().toISOString(),
|
||||
workspaceId,
|
||||
twentyStandardApplicationId: twentyStandardFlatApplication.id,
|
||||
});
|
||||
|
||||
const recordPageLayoutUniversalIdentifiers = new Set<string>();
|
||||
|
||||
const recordPageLayoutsFromStandard = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((pageLayout) => {
|
||||
if (pageLayout.type !== PageLayoutType.RECORD_PAGE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recordPageLayoutUniversalIdentifiers.add(
|
||||
pageLayout.universalIdentifier,
|
||||
);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const tabUniversalIdentifiers = new Set<string>();
|
||||
|
||||
const tabsFromStandard = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutTabMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((tab) => {
|
||||
if (
|
||||
!recordPageLayoutUniversalIdentifiers.has(
|
||||
tab.pageLayoutUniversalIdentifier,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tabUniversalIdentifiers.add(tab.universalIdentifier);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const widgetsFromStandard = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((widget) =>
|
||||
tabUniversalIdentifiers.has(widget.pageLayoutTabUniversalIdentifier),
|
||||
);
|
||||
|
||||
const {
|
||||
flatPageLayoutMaps: existingFlatPageLayoutMaps,
|
||||
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
|
||||
flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps,
|
||||
flatViewMaps: existingFlatViewMaps,
|
||||
flatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatPageLayoutMaps',
|
||||
'flatPageLayoutTabMaps',
|
||||
'flatPageLayoutWidgetMaps',
|
||||
'flatViewMaps',
|
||||
'flatViewFieldMaps',
|
||||
'flatViewFieldGroupMaps',
|
||||
]);
|
||||
|
||||
// Filter out page layouts, tabs, and widgets that already exist in the workspace
|
||||
const pageLayoutsToCreate = recordPageLayoutsFromStandard.filter(
|
||||
(pageLayout) =>
|
||||
!isDefined(
|
||||
existingFlatPageLayoutMaps.byUniversalIdentifier[
|
||||
pageLayout.universalIdentifier
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
const pageLayoutTabsToCreate = tabsFromStandard.filter(
|
||||
(tab) =>
|
||||
!isDefined(
|
||||
existingFlatPageLayoutTabMaps.byUniversalIdentifier[
|
||||
tab.universalIdentifier
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
const pageLayoutWidgetsToCreate = widgetsFromStandard.filter(
|
||||
(widget) =>
|
||||
!isDefined(
|
||||
existingFlatPageLayoutWidgetMaps.byUniversalIdentifier[
|
||||
widget.universalIdentifier
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// Collect all standard FIELDS_WIDGET view universal identifiers
|
||||
// This includes both new views to create AND existing views (created by 1-19)
|
||||
const allStandardFieldsWidgetViewUniversalIds = new Set<string>();
|
||||
|
||||
const viewsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatViewMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((view) => {
|
||||
if (view.type !== ViewType.FIELDS_WIDGET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
allStandardFieldsWidgetViewUniversalIds.add(view.universalIdentifier);
|
||||
|
||||
if (
|
||||
isDefined(
|
||||
existingFlatViewMaps.byUniversalIdentifier[
|
||||
view.universalIdentifier
|
||||
],
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Delete all existing viewFields for standard FIELDS_WIDGET views and recreate from current standard.
|
||||
// This ensures positions, visibility, and groups match the current standard definition.
|
||||
const viewFieldsToDelete = Object.values(
|
||||
existingFlatViewFieldMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((viewField) =>
|
||||
allStandardFieldsWidgetViewUniversalIds.has(
|
||||
viewField.viewUniversalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
const viewFieldsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatViewFieldMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((viewField) =>
|
||||
allStandardFieldsWidgetViewUniversalIds.has(
|
||||
viewField.viewUniversalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
// Same delete-and-recreate approach for viewFieldGroups
|
||||
const viewFieldGroupsToDelete = Object.values(
|
||||
existingFlatViewFieldGroupMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((viewFieldGroup) =>
|
||||
allStandardFieldsWidgetViewUniversalIds.has(
|
||||
viewFieldGroup.viewUniversalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
const viewFieldGroupsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatViewFieldGroupMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((viewFieldGroup) =>
|
||||
allStandardFieldsWidgetViewUniversalIds.has(
|
||||
viewFieldGroup.viewUniversalIdentifier,
|
||||
),
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Creating ${pageLayoutsToCreate.length} page layouts, ${viewsToCreate.length} views, deleting ${viewFieldsToDelete.length} and creating ${viewFieldsToCreate.length} view fields, deleting ${viewFieldGroupsToDelete.length} and creating ${viewFieldGroupsToCreate.length} view field groups for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayout: {
|
||||
flatEntityToCreate: pageLayoutsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: pageLayoutTabsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: pageLayoutWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
view: {
|
||||
flatEntityToCreate: viewsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: viewFieldsToCreate,
|
||||
flatEntityToDelete: viewFieldsToDelete,
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewFieldGroup: {
|
||||
flatEntityToCreate: viewFieldGroupsToCreate,
|
||||
flatEntityToDelete: viewFieldGroupsToDelete,
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (validateAndBuildResult.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create standard page layouts and sync view fields:\n${JSON.stringify(validateAndBuildResult, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create standard page layouts and sync view fields for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async backfillCustomObjectPageLayouts({
|
||||
workspaceId,
|
||||
workspaceCustomFlatApplication,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceCustomFlatApplication: FlatApplication;
|
||||
}): Promise<void> {
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatPageLayoutMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatPageLayoutMaps',
|
||||
]);
|
||||
|
||||
const existingPageLayouts = Object.values(
|
||||
flatPageLayoutMaps.byUniversalIdentifier,
|
||||
).filter(isDefined);
|
||||
|
||||
const objectIdsWithRecordPageLayout = new Set(
|
||||
existingPageLayouts
|
||||
.filter(
|
||||
(layout: FlatPageLayout) =>
|
||||
layout.type === PageLayoutType.RECORD_PAGE &&
|
||||
isDefined(layout.objectMetadataId),
|
||||
)
|
||||
.map((layout: FlatPageLayout) => layout.objectMetadataId),
|
||||
);
|
||||
|
||||
const customObjectsWithoutPageLayout = Object.values(
|
||||
flatObjectMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(objectMetadata) =>
|
||||
objectMetadata.isCustom &&
|
||||
!objectMetadata.isRemote &&
|
||||
!objectIdsWithRecordPageLayout.has(objectMetadata.id),
|
||||
);
|
||||
|
||||
if (customObjectsWithoutPageLayout.length === 0) {
|
||||
this.logger.log(
|
||||
`No custom objects without page layouts found for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Creating page layouts for ${customObjectsWithoutPageLayout.length} custom object(s) in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const allCustomPageLayoutsToCreate: FlatPageLayout[] = [];
|
||||
const allCustomPageLayoutTabsToCreate: FlatPageLayoutTab[] = [];
|
||||
const allCustomPageLayoutWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const allCustomViewsToCreate: (UniversalFlatView & { id: string })[] = [];
|
||||
const allCustomViewFieldsToCreate: UniversalFlatViewField[] = [];
|
||||
|
||||
for (const customObject of customObjectsWithoutPageLayout) {
|
||||
const flatRecordPageFieldsView = computeFlatRecordPageFieldsViewToCreate({
|
||||
objectMetadata: customObject,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
});
|
||||
|
||||
const objectFieldMetadatas = Object.values(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((field) => field.objectMetadataId === customObject.id);
|
||||
|
||||
const viewFields = computeFlatViewFieldsToCreate({
|
||||
objectFlatFieldMetadatas: objectFieldMetadatas,
|
||||
viewUniversalIdentifier: flatRecordPageFieldsView.universalIdentifier,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
customObject.labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
excludeLabelIdentifier: true,
|
||||
});
|
||||
|
||||
const { pageLayouts, pageLayoutTabs, pageLayoutWidgets } =
|
||||
computeFlatDefaultRecordPageLayoutToCreate({
|
||||
objectMetadata: customObject,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
recordPageFieldsView: flatRecordPageFieldsView,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
allCustomPageLayoutsToCreate.push(...pageLayouts);
|
||||
allCustomPageLayoutTabsToCreate.push(...pageLayoutTabs);
|
||||
allCustomPageLayoutWidgetsToCreate.push(...pageLayoutWidgets);
|
||||
allCustomViewsToCreate.push(flatRecordPageFieldsView);
|
||||
allCustomViewFieldsToCreate.push(...viewFields);
|
||||
}
|
||||
|
||||
const customValidateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayout: {
|
||||
flatEntityToCreate: allCustomPageLayoutsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: allCustomPageLayoutTabsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: allCustomPageLayoutWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
view: {
|
||||
flatEntityToCreate: allCustomViewsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: allCustomViewFieldsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (customValidateAndBuildResult.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create custom object page layouts:\n${JSON.stringify(customValidateAndBuildResult, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create custom object page layouts for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully created page layouts for ${customObjectsWithoutPageLayout.length} custom object(s) in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async backfillFieldWidgets({
|
||||
workspaceId,
|
||||
twentyStandardFlatApplication,
|
||||
workspaceCustomFlatApplication,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
twentyStandardFlatApplication: FlatApplication;
|
||||
workspaceCustomFlatApplication: FlatApplication;
|
||||
}): Promise<void> {
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatPageLayoutMaps,
|
||||
flatPageLayoutTabMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatPageLayoutMaps',
|
||||
'flatPageLayoutTabMaps',
|
||||
'flatPageLayoutWidgetMaps',
|
||||
]);
|
||||
|
||||
// Build a set of fieldMetadataIds that already have a FIELD widget
|
||||
const existingFieldWidgetFieldIds = new Set<string>();
|
||||
|
||||
for (const widget of Object.values(
|
||||
flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (
|
||||
isDefined(widget) &&
|
||||
widget.configuration?.configurationType ===
|
||||
WidgetConfigurationType.FIELD &&
|
||||
isDefined(widget.configuration.fieldMetadataId)
|
||||
) {
|
||||
existingFieldWidgetFieldIds.add(widget.configuration.fieldMetadataId);
|
||||
}
|
||||
}
|
||||
|
||||
// Build object ID → objectMetadata map
|
||||
const objectById = new Map<string, FlatObjectMetadata>();
|
||||
|
||||
for (const obj of Object.values(
|
||||
flatObjectMetadataMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (isDefined(obj)) {
|
||||
objectById.set(obj.id, obj);
|
||||
}
|
||||
}
|
||||
|
||||
// Build object ID → RECORD_PAGE layout map
|
||||
const recordPageLayoutByObjectId = new Map<
|
||||
string,
|
||||
{ id: string; universalIdentifier: string }
|
||||
>();
|
||||
|
||||
for (const layout of Object.values(
|
||||
flatPageLayoutMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (
|
||||
isDefined(layout) &&
|
||||
layout.type === PageLayoutType.RECORD_PAGE &&
|
||||
isDefined(layout.objectMetadataId)
|
||||
) {
|
||||
recordPageLayoutByObjectId.set(layout.objectMetadataId, {
|
||||
id: layout.id,
|
||||
universalIdentifier: layout.universalIdentifier,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Build pageLayoutId → home tab map
|
||||
const homeTabByPageLayoutId = new Map<
|
||||
string,
|
||||
{
|
||||
id: string;
|
||||
universalIdentifier: string;
|
||||
widgetCount: number;
|
||||
}
|
||||
>();
|
||||
|
||||
for (const tab of Object.values(
|
||||
flatPageLayoutTabMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (
|
||||
isDefined(tab) &&
|
||||
tab.position === HOME_TAB_POSITION &&
|
||||
tab.layoutMode === PageLayoutTabLayoutMode.VERTICAL_LIST
|
||||
) {
|
||||
homeTabByPageLayoutId.set(tab.pageLayoutId, {
|
||||
id: tab.id,
|
||||
universalIdentifier: tab.universalIdentifier,
|
||||
widgetCount: tab.widgetIds?.length ?? 0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Group fields by objectMetadataId
|
||||
const fieldsByObjectId = new Map<string, FlatFieldMetadata[]>();
|
||||
|
||||
// Map morphId → all field IDs sharing that morphId (for dedup)
|
||||
const fieldIdsByMorphId = new Map<string, string[]>();
|
||||
|
||||
for (const field of Object.values(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier,
|
||||
)) {
|
||||
if (!isDefined(field) || !isDefined(field.objectMetadataId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const list = fieldsByObjectId.get(field.objectMetadataId) ?? [];
|
||||
|
||||
list.push(field);
|
||||
fieldsByObjectId.set(field.objectMetadataId, list);
|
||||
|
||||
if (
|
||||
field.type === FieldMetadataType.MORPH_RELATION &&
|
||||
isDefined(field.morphId)
|
||||
) {
|
||||
const morphFieldIds = fieldIdsByMorphId.get(field.morphId) ?? [];
|
||||
|
||||
morphFieldIds.push(field.id);
|
||||
fieldIdsByMorphId.set(field.morphId, morphFieldIds);
|
||||
}
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const standardWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const customWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const processedMorphIds = new Set<string>();
|
||||
|
||||
for (const object of objectById.values()) {
|
||||
const pageLayout = recordPageLayoutByObjectId.get(object.id);
|
||||
|
||||
if (!isDefined(pageLayout)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const homeTab = homeTabByPageLayoutId.get(pageLayout.id);
|
||||
|
||||
if (!isDefined(homeTab)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const flatApplication: FlatApplication = object.isCustom
|
||||
? workspaceCustomFlatApplication
|
||||
: twentyStandardFlatApplication;
|
||||
|
||||
const targetList: FlatPageLayoutWidget[] = object.isCustom
|
||||
? customWidgetsToCreate
|
||||
: standardWidgetsToCreate;
|
||||
|
||||
const fields = fieldsByObjectId.get(object.id) ?? [];
|
||||
let nextWidgetIndex = homeTab.widgetCount;
|
||||
|
||||
for (const field of fields) {
|
||||
if (
|
||||
field.type !== FieldMetadataType.RELATION &&
|
||||
field.type !== FieldMetadataType.MORPH_RELATION
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isActivityTargetField(field.name, object.nameSingular)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isJunctionRelationField(field)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.type === FieldMetadataType.RELATION) {
|
||||
const targetObject = isDefined(field.relationTargetObjectMetadataId)
|
||||
? objectById.get(field.relationTargetObjectMetadataId)
|
||||
: undefined;
|
||||
|
||||
if (!isRelationTargetAvailable(targetObject)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// For morph relations, skip if any sibling field (same morphId)
|
||||
// already has a widget or was already processed in this run
|
||||
if (
|
||||
field.type === FieldMetadataType.MORPH_RELATION &&
|
||||
isDefined(field.morphId)
|
||||
) {
|
||||
if (processedMorphIds.has(field.morphId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const siblingFieldIds = fieldIdsByMorphId.get(field.morphId) ?? [];
|
||||
const alreadyHasWidget = siblingFieldIds.some((id) =>
|
||||
existingFieldWidgetFieldIds.has(id),
|
||||
);
|
||||
|
||||
if (alreadyHasWidget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
processedMorphIds.add(field.morphId);
|
||||
}
|
||||
|
||||
if (existingFieldWidgetFieldIds.has(field.id)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const widget: FlatPageLayoutWidget = {
|
||||
id: v4(),
|
||||
universalIdentifier: v4(),
|
||||
applicationId: flatApplication.id,
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
workspaceId,
|
||||
pageLayoutTabId: homeTab.id,
|
||||
pageLayoutTabUniversalIdentifier: homeTab.universalIdentifier,
|
||||
title: field.label,
|
||||
type: WidgetType.FIELD,
|
||||
gridPosition: { ...GRID_POSITIONS.FULL_WIDTH },
|
||||
position: {
|
||||
...VERTICAL_LIST_LAYOUT_POSITIONS.SECOND,
|
||||
index: nextWidgetIndex,
|
||||
},
|
||||
configuration: {
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: field.id,
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
universalConfiguration: {
|
||||
configurationType: WidgetConfigurationType.FIELD,
|
||||
fieldMetadataId: field.universalIdentifier,
|
||||
fieldDisplayMode: FieldDisplayMode.CARD,
|
||||
},
|
||||
objectMetadataId: object.id,
|
||||
objectMetadataUniversalIdentifier: object.universalIdentifier,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
deletedAt: null,
|
||||
conditionalDisplay: null,
|
||||
overrides: null,
|
||||
};
|
||||
|
||||
targetList.push(widget);
|
||||
existingFieldWidgetFieldIds.add(field.id);
|
||||
nextWidgetIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
const totalWidgets =
|
||||
standardWidgetsToCreate.length + customWidgetsToCreate.length;
|
||||
|
||||
if (totalWidgets === 0) {
|
||||
this.logger.log(
|
||||
`All FIELD widgets already exist for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Found ${totalWidgets} FIELD widget(s) to create for workspace ${workspaceId} (${standardWidgetsToCreate.length} standard, ${customWidgetsToCreate.length} custom)`,
|
||||
);
|
||||
|
||||
if (standardWidgetsToCreate.length > 0) {
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: standardWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create standard FIELD widgets:\n${JSON.stringify(result, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create standard FIELD widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (customWidgetsToCreate.length > 0) {
|
||||
const result =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: customWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (result.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create custom FIELD widgets:\n${JSON.stringify(result, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create custom FIELD widgets for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully created ${totalWidgets} FIELD widget(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
-485
@@ -1,485 +0,0 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { FeatureFlagKey, ViewType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { type FlatPageLayout } from 'src/engine/metadata-modules/flat-page-layout/types/flat-page-layout.type';
|
||||
import { computeFlatDefaultRecordPageLayoutToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-default-record-page-layout-to-create.util';
|
||||
import { computeFlatRecordPageFieldsViewToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-record-page-fields-view-to-create.util';
|
||||
import { computeFlatViewFieldsToCreate } from 'src/engine/metadata-modules/object-metadata/utils/compute-flat-view-fields-to-create.util';
|
||||
import { PageLayoutType } from 'src/engine/metadata-modules/page-layout/enums/page-layout-type.enum';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
import { type UniversalFlatViewField } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view-field.type';
|
||||
import { type UniversalFlatView } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-view.type';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-20:backfill-page-layouts',
|
||||
description:
|
||||
'Backfill RECORD_PAGE page layouts for legacy workspaces and enable the IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED feature flag',
|
||||
})
|
||||
export class BackfillPageLayoutsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Starting backfill of page layouts for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const isAlreadyEnabled = await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isAlreadyEnabled) {
|
||||
this.logger.log(
|
||||
`IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED already enabled for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
await this.backfillStandardObjectPageLayouts({
|
||||
workspaceId,
|
||||
twentyStandardFlatApplication,
|
||||
isDryRun,
|
||||
});
|
||||
|
||||
await this.backfillCustomObjectPageLayouts({
|
||||
workspaceId,
|
||||
workspaceCustomFlatApplication,
|
||||
isDryRun,
|
||||
});
|
||||
|
||||
if (!isDryRun) {
|
||||
await this.featureFlagService.enableFeatureFlags(
|
||||
[FeatureFlagKey.IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED],
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
isDryRun
|
||||
? `[DRY RUN] Would create page layouts and enable IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED for workspace ${workspaceId}`
|
||||
: `Successfully created page layouts and enabled IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async backfillStandardObjectPageLayouts({
|
||||
workspaceId,
|
||||
twentyStandardFlatApplication,
|
||||
isDryRun,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
twentyStandardFlatApplication: FlatApplication;
|
||||
isDryRun: boolean;
|
||||
}): Promise<void> {
|
||||
const { allFlatEntityMaps: standardAllFlatEntityMaps } =
|
||||
computeTwentyStandardApplicationAllFlatEntityMaps({
|
||||
shouldIncludeRecordPageLayouts: true,
|
||||
now: new Date().toISOString(),
|
||||
workspaceId,
|
||||
twentyStandardApplicationId: twentyStandardFlatApplication.id,
|
||||
});
|
||||
|
||||
const {
|
||||
flatPageLayoutMaps: existingFlatPageLayoutMaps,
|
||||
flatPageLayoutTabMaps: existingFlatPageLayoutTabMaps,
|
||||
flatPageLayoutWidgetMaps: existingFlatPageLayoutWidgetMaps,
|
||||
flatViewMaps: existingFlatViewMaps,
|
||||
flatViewFieldMaps: existingFlatViewFieldMaps,
|
||||
flatViewFieldGroupMaps: existingFlatViewFieldGroupMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatPageLayoutMaps',
|
||||
'flatPageLayoutTabMaps',
|
||||
'flatPageLayoutWidgetMaps',
|
||||
'flatViewMaps',
|
||||
'flatViewFieldMaps',
|
||||
'flatViewFieldGroupMaps',
|
||||
]);
|
||||
|
||||
const recordPageLayoutUniversalIdentifiers = new Set<string>();
|
||||
|
||||
const pageLayoutsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((pageLayout) => {
|
||||
if (pageLayout.type !== PageLayoutType.RECORD_PAGE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
recordPageLayoutUniversalIdentifiers.add(
|
||||
pageLayout.universalIdentifier,
|
||||
);
|
||||
|
||||
if (
|
||||
isDefined(
|
||||
existingFlatPageLayoutMaps.byUniversalIdentifier[
|
||||
pageLayout.universalIdentifier
|
||||
],
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const tabUniversalIdentifiers = new Set<string>();
|
||||
|
||||
const pageLayoutTabsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutTabMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((tab) => {
|
||||
if (
|
||||
!recordPageLayoutUniversalIdentifiers.has(
|
||||
tab.pageLayoutUniversalIdentifier,
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
tabUniversalIdentifiers.add(tab.universalIdentifier);
|
||||
|
||||
if (
|
||||
isDefined(
|
||||
existingFlatPageLayoutTabMaps.byUniversalIdentifier[
|
||||
tab.universalIdentifier
|
||||
],
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const pageLayoutWidgetsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatPageLayoutWidgetMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(widget) =>
|
||||
tabUniversalIdentifiers.has(
|
||||
widget.pageLayoutTabUniversalIdentifier,
|
||||
) &&
|
||||
!isDefined(
|
||||
existingFlatPageLayoutWidgetMaps.byUniversalIdentifier[
|
||||
widget.universalIdentifier
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
const viewUniversalIdentifiers = new Set<string>();
|
||||
|
||||
const viewsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatViewMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((view) => {
|
||||
if (view.type !== ViewType.FIELDS_WIDGET) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(
|
||||
existingFlatViewMaps.byUniversalIdentifier[
|
||||
view.universalIdentifier
|
||||
],
|
||||
)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
viewUniversalIdentifiers.add(view.universalIdentifier);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const viewFieldsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatViewFieldMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(viewField) =>
|
||||
viewUniversalIdentifiers.has(viewField.viewUniversalIdentifier) &&
|
||||
!isDefined(
|
||||
existingFlatViewFieldMaps.byUniversalIdentifier[
|
||||
viewField.universalIdentifier
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
const viewFieldGroupsToCreate = Object.values(
|
||||
standardAllFlatEntityMaps.flatViewFieldGroupMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(viewFieldGroup) =>
|
||||
viewUniversalIdentifiers.has(
|
||||
viewFieldGroup.viewUniversalIdentifier,
|
||||
) &&
|
||||
!isDefined(
|
||||
existingFlatViewFieldGroupMaps.byUniversalIdentifier[
|
||||
viewFieldGroup.universalIdentifier
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Found standard entities to create for workspace ${workspaceId}: ` +
|
||||
`${pageLayoutsToCreate.length} page layout(s), ` +
|
||||
`${pageLayoutTabsToCreate.length} tab(s), ` +
|
||||
`${pageLayoutWidgetsToCreate.length} widget(s), ` +
|
||||
`${viewsToCreate.length} view(s), ` +
|
||||
`${viewFieldsToCreate.length} view field(s), ` +
|
||||
`${viewFieldGroupsToCreate.length} view field group(s)`,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const validateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayout: {
|
||||
flatEntityToCreate: pageLayoutsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: pageLayoutTabsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: pageLayoutWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
view: {
|
||||
flatEntityToCreate: viewsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: viewFieldsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewFieldGroup: {
|
||||
flatEntityToCreate: viewFieldGroupsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
twentyStandardFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (validateAndBuildResult.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create standard page layouts:\n${JSON.stringify(validateAndBuildResult, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create standard page layouts for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async backfillCustomObjectPageLayouts({
|
||||
workspaceId,
|
||||
workspaceCustomFlatApplication,
|
||||
isDryRun,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
workspaceCustomFlatApplication: FlatApplication;
|
||||
isDryRun: boolean;
|
||||
}): Promise<void> {
|
||||
const {
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatPageLayoutMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatPageLayoutMaps',
|
||||
]);
|
||||
|
||||
const existingPageLayouts = Object.values(
|
||||
flatPageLayoutMaps.byUniversalIdentifier,
|
||||
).filter(isDefined);
|
||||
|
||||
const objectIdsWithRecordPageLayout = new Set(
|
||||
existingPageLayouts
|
||||
.filter(
|
||||
(layout: FlatPageLayout) =>
|
||||
layout.type === PageLayoutType.RECORD_PAGE &&
|
||||
isDefined(layout.objectMetadataId),
|
||||
)
|
||||
.map((layout: FlatPageLayout) => layout.objectMetadataId),
|
||||
);
|
||||
|
||||
const customObjectsWithoutPageLayout = Object.values(
|
||||
flatObjectMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter(
|
||||
(objectMetadata) =>
|
||||
objectMetadata.isCustom &&
|
||||
!objectMetadata.isRemote &&
|
||||
!objectIdsWithRecordPageLayout.has(objectMetadata.id),
|
||||
);
|
||||
|
||||
if (customObjectsWithoutPageLayout.length === 0) {
|
||||
this.logger.log(
|
||||
`No custom objects without page layouts found for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const allCustomPageLayoutsToCreate: FlatPageLayout[] = [];
|
||||
const allCustomPageLayoutTabsToCreate: FlatPageLayoutTab[] = [];
|
||||
const allCustomPageLayoutWidgetsToCreate: FlatPageLayoutWidget[] = [];
|
||||
const allCustomViewsToCreate: (UniversalFlatView & { id: string })[] = [];
|
||||
const allCustomViewFieldsToCreate: UniversalFlatViewField[] = [];
|
||||
|
||||
for (const customObject of customObjectsWithoutPageLayout) {
|
||||
const flatRecordPageFieldsView = computeFlatRecordPageFieldsViewToCreate({
|
||||
objectMetadata: customObject,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
});
|
||||
|
||||
const objectFieldMetadatas = Object.values(
|
||||
flatFieldMetadataMaps.byUniversalIdentifier,
|
||||
)
|
||||
.filter(isDefined)
|
||||
.filter((field) => field.objectMetadataId === customObject.id);
|
||||
|
||||
const viewFields = computeFlatViewFieldsToCreate({
|
||||
objectFlatFieldMetadatas: objectFieldMetadatas,
|
||||
viewUniversalIdentifier: flatRecordPageFieldsView.universalIdentifier,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
customObject.labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
});
|
||||
|
||||
const { pageLayouts, pageLayoutTabs, pageLayoutWidgets } =
|
||||
computeFlatDefaultRecordPageLayoutToCreate({
|
||||
objectMetadata: customObject,
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
recordPageFieldsView: flatRecordPageFieldsView,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
allCustomPageLayoutsToCreate.push(...pageLayouts);
|
||||
allCustomPageLayoutTabsToCreate.push(...pageLayoutTabs);
|
||||
allCustomPageLayoutWidgetsToCreate.push(...pageLayoutWidgets);
|
||||
allCustomViewsToCreate.push(flatRecordPageFieldsView);
|
||||
allCustomViewFieldsToCreate.push(...viewFields);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`${isDryRun ? '[DRY RUN] ' : ''}Found custom entities to create for ${customObjectsWithoutPageLayout.length} custom object(s) in workspace ${workspaceId}: ` +
|
||||
`${allCustomPageLayoutsToCreate.length} page layout(s), ` +
|
||||
`${allCustomPageLayoutTabsToCreate.length} tab(s), ` +
|
||||
`${allCustomPageLayoutWidgetsToCreate.length} widget(s), ` +
|
||||
`${allCustomViewsToCreate.length} view(s), ` +
|
||||
`${allCustomViewFieldsToCreate.length} view field(s)`,
|
||||
);
|
||||
|
||||
if (isDryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const customValidateAndBuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
allFlatEntityOperationByMetadataName: {
|
||||
pageLayout: {
|
||||
flatEntityToCreate: allCustomPageLayoutsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutTab: {
|
||||
flatEntityToCreate: allCustomPageLayoutTabsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
pageLayoutWidget: {
|
||||
flatEntityToCreate: allCustomPageLayoutWidgetsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
view: {
|
||||
flatEntityToCreate: allCustomViewsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
viewField: {
|
||||
flatEntityToCreate: allCustomViewFieldsToCreate,
|
||||
flatEntityToDelete: [],
|
||||
flatEntityToUpdate: [],
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier:
|
||||
workspaceCustomFlatApplication.universalIdentifier,
|
||||
},
|
||||
);
|
||||
|
||||
if (customValidateAndBuildResult.status === 'fail') {
|
||||
this.logger.error(
|
||||
`Failed to create custom object page layouts:\n${JSON.stringify(customValidateAndBuildResult, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create custom object page layouts for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully created page layouts for ${customObjectsWithoutPageLayout.length} custom object(s) in workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
-67
@@ -1,67 +0,0 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { SdkClientGenerationService } from 'src/engine/core-modules/sdk-client/sdk-client-generation.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-20:generate-application-sdk-clients',
|
||||
description:
|
||||
'Generate SDK client archives for all existing applications so drivers do not crash with ARCHIVE_NOT_FOUND',
|
||||
})
|
||||
export class GenerateApplicationSdkClientsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
private readonly sdkClientGenerationService: SdkClientGenerationService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const dryRun = options.dryRun ?? false;
|
||||
|
||||
const applications = await this.applicationRepository.find({
|
||||
where: { workspaceId },
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Found ${applications.length} application(s) in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
for (const application of applications) {
|
||||
if (dryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would generate SDK client for application ${application.universalIdentifier} (${application.id})`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.sdkClientGenerationService.generateSdkClientForApplication({
|
||||
workspaceId,
|
||||
applicationId: application.id,
|
||||
applicationUniversalIdentifier: application.universalIdentifier,
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to generate SDK client for application ${application.universalIdentifier} (${application.id}): ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { DataSource, IsNull, type Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-20:identify-field-permission-metadata',
|
||||
description:
|
||||
'Identify field permission metadata (backfill universalIdentifier and applicationId)',
|
||||
})
|
||||
export class IdentifyFieldPermissionMetadataCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
private hasRunOnce = false;
|
||||
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
if (this.hasRunOnce) {
|
||||
this.logger.log(
|
||||
'Skipping has already been run once IdentifyFieldPermissionMetadataCommand',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
const repository = queryRunner.manager.getRepository(
|
||||
FieldPermissionEntity,
|
||||
);
|
||||
const withNullApplicationId = await repository.find({
|
||||
where: { applicationId: IsNull() },
|
||||
relations: ['role'],
|
||||
});
|
||||
|
||||
const toUpdate = withNullApplicationId.filter((fieldPermission) =>
|
||||
isDefined(fieldPermission.role?.applicationId),
|
||||
);
|
||||
const toRemove = withNullApplicationId.filter(
|
||||
(fieldPermission) => !isDefined(fieldPermission.role?.applicationId),
|
||||
);
|
||||
|
||||
for (const fieldPermission of toUpdate) {
|
||||
fieldPermission.applicationId = fieldPermission.role!.applicationId;
|
||||
fieldPermission.universalIdentifier =
|
||||
fieldPermission.universalIdentifier ?? v4();
|
||||
}
|
||||
|
||||
if (toUpdate.length > 0) {
|
||||
await repository.save(toUpdate);
|
||||
}
|
||||
if (toRemove.length > 0) {
|
||||
await repository.remove(toRemove);
|
||||
}
|
||||
|
||||
const withNullUniversalIdentifier = await repository.find({
|
||||
where: { universalIdentifier: IsNull() },
|
||||
});
|
||||
|
||||
for (const fieldPermission of withNullUniversalIdentifier) {
|
||||
fieldPermission.universalIdentifier = v4();
|
||||
}
|
||||
|
||||
if (withNullUniversalIdentifier.length > 0) {
|
||||
await repository.save(withNullUniversalIdentifier);
|
||||
}
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
this.logger.log(
|
||||
'Successfully run IdentifyFieldPermissionMetadataCommand',
|
||||
);
|
||||
this.hasRunOnce = true;
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error(
|
||||
`Rolling back IdentifyFieldPermissionMetadataCommand: ${error.message}`,
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { DataSource, type Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { makeFieldPermissionUniversalIdentifierAndApplicationIdNotNullQueries } from 'src/database/typeorm/core/migrations/utils/1773400000000-make-field-permission-universal-identifier-and-application-id-not-null.util';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-20:make-field-permission-universal-identifier-and-application-id-not-nullable-migration',
|
||||
description:
|
||||
'Set NOT NULL on fieldPermission universalIdentifier and applicationId, add unique index and FK (run identify-field-permission-metadata first)',
|
||||
})
|
||||
export class MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
private hasRunOnce = false;
|
||||
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
if (this.hasRunOnce) {
|
||||
this.logger.log(
|
||||
'Skipping has already been run once MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
await makeFieldPermissionUniversalIdentifierAndApplicationIdNotNullQueries(
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
this.logger.log(
|
||||
'Successfully run MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand',
|
||||
);
|
||||
this.hasRunOnce = true;
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error(
|
||||
`Rolling back MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: ${error.message}`,
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-13
@@ -2,14 +2,14 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
|
||||
import { BackfillFieldWidgetsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-field-widgets.command';
|
||||
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
|
||||
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command';
|
||||
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts-and-fields-widget-view-fields.command';
|
||||
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
|
||||
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
|
||||
import { GenerateApplicationSdkClientsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-generate-application-sdk-clients.command';
|
||||
import { IdentifyFieldPermissionMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-field-permission-metadata.command';
|
||||
import { IdentifyObjectPermissionMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-object-permission-metadata.command';
|
||||
import { IdentifyPermissionFlagMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-permission-flag-metadata.command';
|
||||
import { MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-field-permission-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-object-permission-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-permission-flag-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakeWorkflowSearchableCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-workflow-searchable.command';
|
||||
@@ -18,10 +18,8 @@ import { MigrateRichTextToTextCommand } from 'src/database/commands/upgrade-vers
|
||||
import { SeedCliApplicationRegistrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-seed-cli-application-registration.command';
|
||||
import { UpdateStandardIndexViewNamesCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-update-standard-index-view-names.command';
|
||||
import { ApplicationRegistrationModule } from 'src/engine/core-modules/application/application-registration/application-registration.module';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { SdkClientModule } from 'src/engine/core-modules/sdk-client/sdk-client.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { CalendarChannelEntity } from 'src/engine/metadata-modules/calendar-channel/entities/calendar-channel.entity';
|
||||
@@ -41,7 +39,6 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
WorkspaceEntity,
|
||||
ApplicationEntity,
|
||||
ConnectedAccountEntity,
|
||||
MessageChannelEntity,
|
||||
CalendarChannelEntity,
|
||||
@@ -58,7 +55,6 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
ApplicationRegistrationModule,
|
||||
WorkspaceMigrationModule,
|
||||
FeatureFlagModule,
|
||||
SdkClientModule,
|
||||
WorkflowCommonModule,
|
||||
],
|
||||
providers: [
|
||||
@@ -66,13 +62,13 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
IdentifyObjectPermissionMetadataCommand,
|
||||
MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
IdentifyFieldPermissionMetadataCommand,
|
||||
MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
BackfillCommandMenuItemsCommand,
|
||||
BackfillFieldWidgetsCommand,
|
||||
BackfillNavigationMenuItemTypeCommand,
|
||||
BackfillPageLayoutsCommand,
|
||||
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
|
||||
BackfillSelectFieldOptionIdsCommand,
|
||||
DeleteOrphanNavigationMenuItemsCommand,
|
||||
GenerateApplicationSdkClientsCommand,
|
||||
SeedCliApplicationRegistrationCommand,
|
||||
MigrateRichTextToTextCommand,
|
||||
MigrateMessagingInfrastructureToMetadataCommand,
|
||||
@@ -84,13 +80,13 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
IdentifyObjectPermissionMetadataCommand,
|
||||
MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
IdentifyFieldPermissionMetadataCommand,
|
||||
MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
BackfillCommandMenuItemsCommand,
|
||||
BackfillFieldWidgetsCommand,
|
||||
BackfillNavigationMenuItemTypeCommand,
|
||||
BackfillPageLayoutsCommand,
|
||||
BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
|
||||
BackfillSelectFieldOptionIdsCommand,
|
||||
DeleteOrphanNavigationMenuItemsCommand,
|
||||
GenerateApplicationSdkClientsCommand,
|
||||
SeedCliApplicationRegistrationCommand,
|
||||
MigrateRichTextToTextCommand,
|
||||
MigrateMessagingInfrastructureToMetadataCommand,
|
||||
|
||||
+11
-10
@@ -34,19 +34,19 @@ import { BackfillSystemFieldsIsSystemCommand } from 'src/database/commands/upgra
|
||||
import { FixInvalidStandardUniversalIdentifiersCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-fix-invalid-standard-universal-identifiers.command';
|
||||
import { SeedServerIdCommand } from 'src/database/commands/upgrade-version-command/1-19/1-19-seed-server-id.command';
|
||||
import { BackfillCommandMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-command-menu-items.command';
|
||||
import { BackfillFieldWidgetsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-field-widgets.command';
|
||||
import { BackfillNavigationMenuItemTypeCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-navigation-menu-item-type.command';
|
||||
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
|
||||
import { BackfillPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts.command';
|
||||
import { BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-page-layouts-and-fields-widget-view-fields.command';
|
||||
import { IdentifyFieldPermissionMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-field-permission-metadata.command';
|
||||
import { BackfillSelectFieldOptionIdsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-backfill-select-field-option-ids.command';
|
||||
import { DeleteOrphanNavigationMenuItemsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-delete-orphan-navigation-menu-items.command';
|
||||
import { IdentifyObjectPermissionMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-object-permission-metadata.command';
|
||||
import { IdentifyPermissionFlagMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-permission-flag-metadata.command';
|
||||
import { MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-field-permission-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-object-permission-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-permission-flag-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakeWorkflowSearchableCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-workflow-searchable.command';
|
||||
import { MigrateMessagingInfrastructureToMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-messaging-infrastructure-to-metadata.command';
|
||||
import { MigrateRichTextToTextCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-rich-text-to-text.command';
|
||||
import { GenerateApplicationSdkClientsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-generate-application-sdk-clients.command';
|
||||
import { SeedCliApplicationRegistrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-seed-cli-application-registration.command';
|
||||
import { UpdateStandardIndexViewNamesCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-update-standard-index-view-names.command';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
@@ -103,15 +103,15 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
protected readonly makePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
protected readonly identifyObjectPermissionMetadataCommand: IdentifyObjectPermissionMetadataCommand,
|
||||
protected readonly makeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
protected readonly identifyFieldPermissionMetadataCommand: IdentifyFieldPermissionMetadataCommand,
|
||||
protected readonly makeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
protected readonly backfillNavigationMenuItemTypeCommand: BackfillNavigationMenuItemTypeCommand,
|
||||
protected readonly backfillCommandMenuItemsCommand: BackfillCommandMenuItemsCommand,
|
||||
protected readonly deleteOrphanNavigationMenuItemsCommand: DeleteOrphanNavigationMenuItemsCommand,
|
||||
protected readonly backfillPageLayoutsCommand: BackfillPageLayoutsCommand,
|
||||
protected readonly generateApplicationSdkClientsCommand: GenerateApplicationSdkClientsCommand,
|
||||
protected readonly backfillPageLayoutsAndFieldsWidgetViewFieldsCommand: BackfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
|
||||
protected readonly seedCliApplicationRegistrationCommand: SeedCliApplicationRegistrationCommand,
|
||||
protected readonly migrateRichTextToTextCommand: MigrateRichTextToTextCommand,
|
||||
protected readonly migrateMessagingInfrastructureToMetadataCommand: MigrateMessagingInfrastructureToMetadataCommand,
|
||||
protected readonly backfillFieldWidgetsCommand: BackfillFieldWidgetsCommand,
|
||||
protected readonly backfillSelectFieldOptionIdsCommand: BackfillSelectFieldOptionIdsCommand,
|
||||
protected readonly updateStandardIndexViewNamesCommand: UpdateStandardIndexViewNamesCommand,
|
||||
protected readonly makeWorkflowSearchableCommand: MakeWorkflowSearchableCommand,
|
||||
@@ -168,18 +168,19 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
this.identifyObjectPermissionMetadataCommand,
|
||||
this
|
||||
.makeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
this.identifyFieldPermissionMetadataCommand,
|
||||
this
|
||||
.makeFieldPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
this.backfillNavigationMenuItemTypeCommand,
|
||||
this.migrateRichTextToTextCommand,
|
||||
this.deleteOrphanNavigationMenuItemsCommand,
|
||||
this.backfillCommandMenuItemsCommand,
|
||||
this.backfillPageLayoutsCommand,
|
||||
this.backfillPageLayoutsAndFieldsWidgetViewFieldsCommand,
|
||||
this.seedCliApplicationRegistrationCommand,
|
||||
this.migrateMessagingInfrastructureToMetadataCommand,
|
||||
this.backfillFieldWidgetsCommand,
|
||||
this.backfillSelectFieldOptionIdsCommand,
|
||||
this.updateStandardIndexViewNamesCommand,
|
||||
this.makeWorkflowSearchableCommand,
|
||||
this.generateApplicationSdkClientsCommand,
|
||||
];
|
||||
|
||||
this.allCommands = {
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
export class AddUniversalIdentifierAndApplicationIdToFieldPermission1773400000000
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'AddUniversalIdentifierAndApplicationIdToFieldPermission1773400000000';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."fieldPermission" ADD "universalIdentifier" uuid`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."fieldPermission" ADD "applicationId" uuid`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."fieldPermission" DROP COLUMN IF EXISTS "applicationId"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."fieldPermission" DROP COLUMN IF EXISTS "universalIdentifier"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
import { makeFieldPermissionUniversalIdentifierAndApplicationIdNotNullQueries } from 'src/database/typeorm/core/migrations/utils/1773400000000-make-field-permission-universal-identifier-and-application-id-not-null.util';
|
||||
|
||||
export class MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNull1773400000001
|
||||
implements MigrationInterface
|
||||
{
|
||||
name =
|
||||
'MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNull1773400000001';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const savepointName =
|
||||
'sp_make_field_permission_universal_identifier_and_application_id_not_null';
|
||||
|
||||
try {
|
||||
await queryRunner.query(`SAVEPOINT ${savepointName}`);
|
||||
|
||||
await makeFieldPermissionUniversalIdentifierAndApplicationIdNotNullQueries(
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await queryRunner.query(`RELEASE SAVEPOINT ${savepointName}`);
|
||||
} catch (e) {
|
||||
try {
|
||||
await queryRunner.query(`ROLLBACK TO SAVEPOINT ${savepointName}`);
|
||||
await queryRunner.query(`RELEASE SAVEPOINT ${savepointName}`);
|
||||
} catch (rollbackError) {
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error(
|
||||
'Failed to rollback to savepoint in MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNull1773400000001',
|
||||
rollbackError,
|
||||
);
|
||||
throw rollbackError;
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line no-console
|
||||
console.error(
|
||||
'Swallowing MakeFieldPermissionUniversalIdentifierAndApplicationIdNotNull1773400000001 error',
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."fieldPermission" DROP CONSTRAINT IF EXISTS "FK_71cc60c4a1c9f8a7c434d91d38c"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX IF EXISTS "core"."IDX_0dedb90c717e179ef653c512b9"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."fieldPermission" ALTER COLUMN "applicationId" DROP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."fieldPermission" ALTER COLUMN "universalIdentifier" DROP NOT NULL`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
export const makeFieldPermissionUniversalIdentifierAndApplicationIdNotNullQueries =
|
||||
async (queryRunner: QueryRunner): Promise<void> => {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."fieldPermission" ALTER COLUMN "universalIdentifier" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."fieldPermission" ALTER COLUMN "applicationId" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX IF EXISTS "core"."IDX_0dedb90c717e179ef653c512b9"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE UNIQUE INDEX "IDX_0dedb90c717e179ef653c512b9" ON "core"."fieldPermission" ("workspaceId", "universalIdentifier")`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."fieldPermission" ADD CONSTRAINT "FK_71cc60c4a1c9f8a7c434d91d38c" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
};
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user