Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e891e13e64 | ||
|
|
e6b7f31fea | ||
|
|
bad1f20012 | ||
|
|
ce8ef261c1 | ||
|
|
1d3d3999e2 | ||
|
|
89579f5225 | ||
|
|
132d997474 | ||
|
|
7c252ff233 | ||
|
|
4ba9c0ca0b |
@@ -3263,7 +3263,7 @@ type Mutation {
|
||||
updateWebhook(input: UpdateWebhookInput!): Webhook!
|
||||
deleteWebhook(id: UUID!): Webhook!
|
||||
createChatThread: AgentChatThread!
|
||||
sendChatMessage(threadId: UUID!, text: String!, messageId: UUID!, browsingContext: JSON, modelId: String, fileIds: [UUID!]): SendChatMessageResult!
|
||||
sendChatMessage(threadId: UUID!, text: String!, messageId: UUID!, browsingContext: JSON, modelId: String, fileAttachments: [FileAttachmentInput!]): SendChatMessageResult!
|
||||
stopAgentChatStream(threadId: UUID!): Boolean!
|
||||
renameChatThread(id: UUID!, title: String!): AgentChatThread!
|
||||
archiveChatThread(id: UUID!): AgentChatThread!
|
||||
@@ -4228,6 +4228,11 @@ input UpdateWebhookInputUpdates {
|
||||
secret: String
|
||||
}
|
||||
|
||||
input FileAttachmentInput {
|
||||
id: UUID!
|
||||
filename: String!
|
||||
}
|
||||
|
||||
input CreateSkillInput {
|
||||
id: UUID
|
||||
name: String!
|
||||
|
||||
@@ -5872,7 +5872,7 @@ export interface MutationGenqlSelection{
|
||||
updateWebhook?: (WebhookGenqlSelection & { __args: {input: UpdateWebhookInput} })
|
||||
deleteWebhook?: (WebhookGenqlSelection & { __args: {id: Scalars['UUID']} })
|
||||
createChatThread?: AgentChatThreadGenqlSelection
|
||||
sendChatMessage?: (SendChatMessageResultGenqlSelection & { __args: {threadId: Scalars['UUID'], text: Scalars['String'], messageId: Scalars['UUID'], browsingContext?: (Scalars['JSON'] | null), modelId?: (Scalars['String'] | null), fileIds?: (Scalars['UUID'][] | null)} })
|
||||
sendChatMessage?: (SendChatMessageResultGenqlSelection & { __args: {threadId: Scalars['UUID'], text: Scalars['String'], messageId: Scalars['UUID'], browsingContext?: (Scalars['JSON'] | null), modelId?: (Scalars['String'] | null), fileAttachments?: (FileAttachmentInput[] | null)} })
|
||||
stopAgentChatStream?: { __args: {threadId: Scalars['UUID']} }
|
||||
renameChatThread?: (AgentChatThreadGenqlSelection & { __args: {id: Scalars['UUID'], title: Scalars['String']} })
|
||||
archiveChatThread?: (AgentChatThreadGenqlSelection & { __args: {id: Scalars['UUID']} })
|
||||
@@ -6259,6 +6259,8 @@ update: UpdateWebhookInputUpdates}
|
||||
|
||||
export interface UpdateWebhookInputUpdates {targetUrl?: (Scalars['String'] | null),operations?: (Scalars['String'][] | null),description?: (Scalars['String'] | null),secret?: (Scalars['String'] | null)}
|
||||
|
||||
export interface FileAttachmentInput {id: Scalars['UUID'],filename: Scalars['String']}
|
||||
|
||||
export interface CreateSkillInput {id?: (Scalars['UUID'] | null),name: Scalars['String'],label: Scalars['String'],icon?: (Scalars['String'] | null),description?: (Scalars['String'] | null),content: Scalars['String']}
|
||||
|
||||
export interface UpdateSkillInput {id: Scalars['UUID'],name?: (Scalars['String'] | null),label?: (Scalars['String'] | null),icon?: (Scalars['String'] | null),description?: (Scalars['String'] | null),content?: (Scalars['String'] | null),isActive?: (Scalars['Boolean'] | null)}
|
||||
|
||||
@@ -78,8 +78,8 @@ export default {
|
||||
335,
|
||||
342,
|
||||
378,
|
||||
454,
|
||||
466
|
||||
455,
|
||||
467
|
||||
],
|
||||
"types": {
|
||||
"BillingProductDTO": {
|
||||
@@ -7876,9 +7876,9 @@ export default {
|
||||
"modelId": [
|
||||
1
|
||||
],
|
||||
"fileIds": [
|
||||
3,
|
||||
"[UUID!]"
|
||||
"fileAttachments": [
|
||||
440,
|
||||
"[FileAttachmentInput!]"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -7944,7 +7944,7 @@ export default {
|
||||
290,
|
||||
{
|
||||
"input": [
|
||||
440,
|
||||
441,
|
||||
"CreateSkillInput!"
|
||||
]
|
||||
}
|
||||
@@ -7953,7 +7953,7 @@ export default {
|
||||
290,
|
||||
{
|
||||
"input": [
|
||||
441,
|
||||
442,
|
||||
"UpdateSkillInput!"
|
||||
]
|
||||
}
|
||||
@@ -8011,7 +8011,7 @@ export default {
|
||||
238,
|
||||
{
|
||||
"input": [
|
||||
442,
|
||||
443,
|
||||
"GetAuthorizationUrlForSSOInput!"
|
||||
]
|
||||
}
|
||||
@@ -8265,7 +8265,7 @@ export default {
|
||||
196,
|
||||
{
|
||||
"input": [
|
||||
443,
|
||||
444,
|
||||
"CreateApplicationRegistrationInput!"
|
||||
]
|
||||
}
|
||||
@@ -8274,7 +8274,7 @@ export default {
|
||||
7,
|
||||
{
|
||||
"input": [
|
||||
444,
|
||||
445,
|
||||
"UpdateApplicationRegistrationInput!"
|
||||
]
|
||||
}
|
||||
@@ -8301,7 +8301,7 @@ export default {
|
||||
5,
|
||||
{
|
||||
"input": [
|
||||
446,
|
||||
447,
|
||||
"CreateApplicationRegistrationVariableInput!"
|
||||
]
|
||||
}
|
||||
@@ -8310,7 +8310,7 @@ export default {
|
||||
5,
|
||||
{
|
||||
"input": [
|
||||
447,
|
||||
448,
|
||||
"UpdateApplicationRegistrationVariableInput!"
|
||||
]
|
||||
}
|
||||
@@ -8399,7 +8399,7 @@ export default {
|
||||
6,
|
||||
{
|
||||
"input": [
|
||||
449,
|
||||
450,
|
||||
"UpdateWorkspaceMemberSettingsInput!"
|
||||
]
|
||||
}
|
||||
@@ -8433,7 +8433,7 @@ export default {
|
||||
75,
|
||||
{
|
||||
"data": [
|
||||
450,
|
||||
451,
|
||||
"ActivateWorkspaceInput!"
|
||||
]
|
||||
}
|
||||
@@ -8442,7 +8442,7 @@ export default {
|
||||
75,
|
||||
{
|
||||
"data": [
|
||||
451,
|
||||
452,
|
||||
"UpdateWorkspaceInput!"
|
||||
]
|
||||
}
|
||||
@@ -8469,7 +8469,7 @@ export default {
|
||||
6,
|
||||
{
|
||||
"workspaceMigration": [
|
||||
452,
|
||||
453,
|
||||
"WorkspaceMigrationInput!"
|
||||
]
|
||||
}
|
||||
@@ -8487,7 +8487,7 @@ export default {
|
||||
220,
|
||||
{
|
||||
"input": [
|
||||
455,
|
||||
456,
|
||||
"SetupOIDCSsoInput!"
|
||||
]
|
||||
}
|
||||
@@ -8496,7 +8496,7 @@ export default {
|
||||
220,
|
||||
{
|
||||
"input": [
|
||||
456,
|
||||
457,
|
||||
"SetupSAMLSsoInput!"
|
||||
]
|
||||
}
|
||||
@@ -8505,7 +8505,7 @@ export default {
|
||||
216,
|
||||
{
|
||||
"input": [
|
||||
457,
|
||||
458,
|
||||
"DeleteSsoInput!"
|
||||
]
|
||||
}
|
||||
@@ -8514,7 +8514,7 @@ export default {
|
||||
217,
|
||||
{
|
||||
"input": [
|
||||
458,
|
||||
459,
|
||||
"EditSsoInput!"
|
||||
]
|
||||
}
|
||||
@@ -8545,7 +8545,7 @@ export default {
|
||||
286,
|
||||
{
|
||||
"input": [
|
||||
459,
|
||||
460,
|
||||
"SendEmailInput!"
|
||||
]
|
||||
}
|
||||
@@ -8571,7 +8571,7 @@ export default {
|
||||
"String!"
|
||||
],
|
||||
"connectionParameters": [
|
||||
461,
|
||||
462,
|
||||
"EmailAccountConnectionParameters!"
|
||||
],
|
||||
"id": [
|
||||
@@ -8583,7 +8583,7 @@ export default {
|
||||
168,
|
||||
{
|
||||
"input": [
|
||||
463,
|
||||
464,
|
||||
"UpdateLabPublicFeatureFlagInput!"
|
||||
]
|
||||
}
|
||||
@@ -8665,7 +8665,7 @@ export default {
|
||||
77,
|
||||
{
|
||||
"input": [
|
||||
464,
|
||||
465,
|
||||
"CreateOneAppTokenInput!"
|
||||
]
|
||||
}
|
||||
@@ -8728,7 +8728,7 @@ export default {
|
||||
"String!"
|
||||
],
|
||||
"fileFolder": [
|
||||
466,
|
||||
467,
|
||||
"FileFolder!"
|
||||
],
|
||||
"filePath": [
|
||||
@@ -10746,6 +10746,17 @@ export default {
|
||||
1
|
||||
]
|
||||
},
|
||||
"FileAttachmentInput": {
|
||||
"id": [
|
||||
3
|
||||
],
|
||||
"filename": [
|
||||
1
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"CreateSkillInput": {
|
||||
"id": [
|
||||
3
|
||||
@@ -10828,7 +10839,7 @@ export default {
|
||||
1
|
||||
],
|
||||
"update": [
|
||||
445
|
||||
446
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -10876,7 +10887,7 @@ export default {
|
||||
1
|
||||
],
|
||||
"update": [
|
||||
448
|
||||
449
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -10994,7 +11005,7 @@ export default {
|
||||
},
|
||||
"WorkspaceMigrationInput": {
|
||||
"actions": [
|
||||
453
|
||||
454
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -11002,7 +11013,7 @@ export default {
|
||||
},
|
||||
"WorkspaceMigrationDeleteActionInput": {
|
||||
"type": [
|
||||
454
|
||||
455
|
||||
],
|
||||
"metadataName": [
|
||||
318
|
||||
@@ -11097,7 +11108,7 @@ export default {
|
||||
1
|
||||
],
|
||||
"files": [
|
||||
460
|
||||
461
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -11116,13 +11127,13 @@ export default {
|
||||
},
|
||||
"EmailAccountConnectionParameters": {
|
||||
"IMAP": [
|
||||
462
|
||||
463
|
||||
],
|
||||
"SMTP": [
|
||||
462
|
||||
463
|
||||
],
|
||||
"CALDAV": [
|
||||
462
|
||||
463
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -11161,7 +11172,7 @@ export default {
|
||||
},
|
||||
"CreateOneAppTokenInput": {
|
||||
"appToken": [
|
||||
465
|
||||
466
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -11190,7 +11201,7 @@ export default {
|
||||
230,
|
||||
{
|
||||
"input": [
|
||||
468,
|
||||
469,
|
||||
"LogicFunctionLogsInput!"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1792,6 +1792,11 @@ export type File = {
|
||||
size: Scalars['Float'];
|
||||
};
|
||||
|
||||
export type FileAttachmentInput = {
|
||||
filename: Scalars['String'];
|
||||
id: Scalars['UUID'];
|
||||
};
|
||||
|
||||
export enum FileFolder {
|
||||
AgentChat = 'AgentChat',
|
||||
AppTarball = 'AppTarball',
|
||||
@@ -3197,7 +3202,7 @@ export type MutationSaveImapSmtpCaldavAccountArgs = {
|
||||
|
||||
export type MutationSendChatMessageArgs = {
|
||||
browsingContext?: InputMaybe<Scalars['JSON']>;
|
||||
fileIds?: InputMaybe<Array<Scalars['UUID']>>;
|
||||
fileAttachments?: InputMaybe<Array<FileAttachmentInput>>;
|
||||
messageId: Scalars['UUID'];
|
||||
modelId?: InputMaybe<Scalars['String']>;
|
||||
text: Scalars['String'];
|
||||
@@ -6148,7 +6153,7 @@ export type SendChatMessageMutationVariables = Exact<{
|
||||
messageId: Scalars['UUID'];
|
||||
browsingContext?: InputMaybe<Scalars['JSON']>;
|
||||
modelId?: InputMaybe<Scalars['String']>;
|
||||
fileIds?: InputMaybe<Array<Scalars['UUID']> | Scalars['UUID']>;
|
||||
fileAttachments?: InputMaybe<Array<FileAttachmentInput> | FileAttachmentInput>;
|
||||
}>;
|
||||
|
||||
|
||||
@@ -7983,7 +7988,7 @@ export const EvaluateAgentTurnDocument = {"kind":"Document","definitions":[{"kin
|
||||
export const RemoveRoleFromAgentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RemoveRoleFromAgent"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"agentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"removeRoleFromAgent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"agentId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"agentId"}}}]}]}}]} as unknown as DocumentNode<RemoveRoleFromAgentMutation, RemoveRoleFromAgentMutationVariables>;
|
||||
export const RenameChatThreadDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RenameChatThread"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"title"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"renameChatThread"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}},{"kind":"Argument","name":{"kind":"Name","value":"title"},"value":{"kind":"Variable","name":{"kind":"Name","value":"title"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<RenameChatThreadMutation, RenameChatThreadMutationVariables>;
|
||||
export const RunEvaluationInputDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"RunEvaluationInput"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"agentId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"runEvaluationInput"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"agentId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"agentId"}}},{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"threadId"}},{"kind":"Field","name":{"kind":"Name","value":"agentId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"evaluations"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"score"}},{"kind":"Field","name":{"kind":"Name","value":"comment"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}}]}}]}}]} as unknown as DocumentNode<RunEvaluationInputMutation, RunEvaluationInputMutationVariables>;
|
||||
export const SendChatMessageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SendChatMessage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"text"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"messageId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"browsingContext"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fileIds"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendChatMessage"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"threadId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}}},{"kind":"Argument","name":{"kind":"Name","value":"text"},"value":{"kind":"Variable","name":{"kind":"Name","value":"text"}}},{"kind":"Argument","name":{"kind":"Name","value":"messageId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"messageId"}}},{"kind":"Argument","name":{"kind":"Name","value":"browsingContext"},"value":{"kind":"Variable","name":{"kind":"Name","value":"browsingContext"}}},{"kind":"Argument","name":{"kind":"Name","value":"modelId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}}},{"kind":"Argument","name":{"kind":"Name","value":"fileIds"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fileIds"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"messageId"}},{"kind":"Field","name":{"kind":"Name","value":"queued"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}}]}}]}}]} as unknown as DocumentNode<SendChatMessageMutation, SendChatMessageMutationVariables>;
|
||||
export const SendChatMessageDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SendChatMessage"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"text"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"messageId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"browsingContext"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"JSON"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}},"type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"fileAttachments"}},"type":{"kind":"ListType","type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"FileAttachmentInput"}}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendChatMessage"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"threadId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}}},{"kind":"Argument","name":{"kind":"Name","value":"text"},"value":{"kind":"Variable","name":{"kind":"Name","value":"text"}}},{"kind":"Argument","name":{"kind":"Name","value":"messageId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"messageId"}}},{"kind":"Argument","name":{"kind":"Name","value":"browsingContext"},"value":{"kind":"Variable","name":{"kind":"Name","value":"browsingContext"}}},{"kind":"Argument","name":{"kind":"Name","value":"modelId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"modelId"}}},{"kind":"Argument","name":{"kind":"Name","value":"fileAttachments"},"value":{"kind":"Variable","name":{"kind":"Name","value":"fileAttachments"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"messageId"}},{"kind":"Field","name":{"kind":"Name","value":"queued"}},{"kind":"Field","name":{"kind":"Name","value":"streamId"}}]}}]}}]} as unknown as DocumentNode<SendChatMessageMutation, SendChatMessageMutationVariables>;
|
||||
export const StopAgentChatStreamDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"StopAgentChatStream"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"stopAgentChatStream"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"threadId"},"value":{"kind":"Variable","name":{"kind":"Name","value":"threadId"}}}]}]}}]} as unknown as DocumentNode<StopAgentChatStreamMutation, StopAgentChatStreamMutationVariables>;
|
||||
export const UnarchiveChatThreadDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UnarchiveChatThread"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"unarchiveChatThread"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"deletedAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]}}]} as unknown as DocumentNode<UnarchiveChatThreadMutation, UnarchiveChatThreadMutationVariables>;
|
||||
export const UpdateOneAgentDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateOneAgent"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateAgentInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateOneAgent"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"AgentFields"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"AgentFields"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Agent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"label"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"icon"}},{"kind":"Field","name":{"kind":"Name","value":"prompt"}},{"kind":"Field","name":{"kind":"Name","value":"modelId"}},{"kind":"Field","name":{"kind":"Name","value":"responseFormat"}},{"kind":"Field","name":{"kind":"Name","value":"roleId"}},{"kind":"Field","name":{"kind":"Name","value":"isCustom"}},{"kind":"Field","name":{"kind":"Name","value":"modelConfiguration"}},{"kind":"Field","name":{"kind":"Name","value":"evaluationInputs"}},{"kind":"Field","name":{"kind":"Name","value":"applicationId"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}},{"kind":"Field","name":{"kind":"Name","value":"updatedAt"}}]}}]} as unknown as DocumentNode<UpdateOneAgentMutation, UpdateOneAgentMutationVariables>;
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Terug na {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Terug na inhoud"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Terug na Instellings"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "العودة إلى {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "العودة إلى المحتوى"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "العودة إلى الإعدادات"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Torna a {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Tornar al contingut"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Torna a Configuració"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Zpět na {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Zpět k obsahu"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Zpět do nastavení"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Tilbage til {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Tilbage til indhold"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Tilbage til Indstillinger"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Zurück zu {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Zurück zum Inhalt"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Zurück zu den Einstellungen"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Πίσω στο {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Επιστροφή στο περιεχόμενο"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Πίσω στις Ρυθμίσεις"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2647,11 +2647,6 @@ msgstr "Back to {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Back to content"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Back to Settings"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Volver a {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Volver al contenido"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Volver a Configuración"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Takaisin kohteeseen {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Takaisin sisältöön"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Takaisin asetuksiin"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Retour à {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Retour au contenu"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Retour aux paramètres"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
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
@@ -2652,11 +2652,6 @@ msgstr "חזרה אל {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "חזרה לתוכן"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "חזרה להגדרות"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Vissza ide: {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Vissza a tartalomhoz"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Vissza a beállításokhoz"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Torna a {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Torna al contenuto"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Torna alle Impostazioni"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "{linkText} に戻る"
|
||||
msgid "Back to content"
|
||||
msgstr "コンテンツに戻る"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "設定に戻る"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "{linkText}(으)로 돌아가기"
|
||||
msgid "Back to content"
|
||||
msgstr "콘텐츠로 돌아가기"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "설정으로 돌아가기"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Terug naar {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Terug naar inhoud"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Terug naar Instellingen"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Tilbake til {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Tilbake til innhold"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Tilbake til Innstillinger"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Powrót do {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Powrót do treści"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Powrót do ustawień"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2647,11 +2647,6 @@ msgstr ""
|
||||
msgid "Back to content"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr ""
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Voltar para {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Voltar ao conteúdo"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Voltar para Configurações"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Voltar a {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Voltar ao conteúdo"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Voltar às Definições"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Înapoi la {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Înapoi la conținut"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Înapoi la Setări"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
Binary file not shown.
@@ -2652,11 +2652,6 @@ msgstr "Назад на {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Назад на садржај"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Назад на Подешавања"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Tillbaka till {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Tillbaka till innehåll"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Tillbaka till inställningar"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "{linkText}'e geri dön"
|
||||
msgid "Back to content"
|
||||
msgstr "İçeriğe geri dön"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Ayarlar'a geri dön"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Назад до {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Повернутися до вмісту"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Назад до налаштувань"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "Quay lại {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "Quay lại nội dung"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "Quay lại Cài đặt"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "返回 {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "返回内容"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "返回设置"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -2652,11 +2652,6 @@ msgstr "返回 {linkText}"
|
||||
msgid "Back to content"
|
||||
msgstr "返回內容"
|
||||
|
||||
#. js-lingui-id: OvBnOM
|
||||
#: src/modules/ui/navigation/bread-crumb/components/MobileBreadcrumb.tsx
|
||||
msgid "Back to Settings"
|
||||
msgstr "返回設定"
|
||||
|
||||
#. js-lingui-id: GtJbUa
|
||||
#: src/modules/error-handler/components/AppRootErrorFallback.tsx
|
||||
msgid "Background"
|
||||
|
||||
@@ -7,7 +7,7 @@ export const SEND_CHAT_MESSAGE = gql`
|
||||
$messageId: UUID!
|
||||
$browsingContext: JSON
|
||||
$modelId: String
|
||||
$fileIds: [UUID!]
|
||||
$fileAttachments: [FileAttachmentInput!]
|
||||
) {
|
||||
sendChatMessage(
|
||||
threadId: $threadId
|
||||
@@ -15,7 +15,7 @@ export const SEND_CHAT_MESSAGE = gql`
|
||||
messageId: $messageId
|
||||
browsingContext: $browsingContext
|
||||
modelId: $modelId
|
||||
fileIds: $fileIds
|
||||
fileAttachments: $fileAttachments
|
||||
) {
|
||||
messageId
|
||||
queued
|
||||
|
||||
@@ -30,7 +30,6 @@ import { useListenToBrowserEvent } from '@/browser-event/hooks/useListenToBrowse
|
||||
import { dispatchBrowserEvent } from '@/browser-event/utils/dispatchBrowserEvent';
|
||||
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useSetAtomState } from '@/ui/utilities/state/jotai/hooks/useSetAtomState';
|
||||
|
||||
export const useAgentChat = (
|
||||
@@ -44,11 +43,9 @@ export const useAgentChat = (
|
||||
const setCurrentAiChatThread = useSetAtomState(currentAiChatThreadState);
|
||||
const store = useStore();
|
||||
|
||||
const agentChatSelectedFiles = useAtomStateValue(agentChatSelectedFilesState);
|
||||
|
||||
const [, setPendingThreadIdAfterFirstSend] = useState<string | null>(null);
|
||||
|
||||
const [agentChatUploadedFiles, setAgentChatUploadedFiles] = useAtomState(
|
||||
const setAgentChatUploadedFiles = useSetAtomState(
|
||||
agentChatUploadedFilesState,
|
||||
);
|
||||
|
||||
@@ -74,12 +71,14 @@ export const useAgentChat = (
|
||||
return;
|
||||
}
|
||||
|
||||
const isLoading = agentChatSelectedFiles.length > 0;
|
||||
const agentChatSelectedFiles = store.get(agentChatSelectedFilesState.atom);
|
||||
|
||||
if (isLoading) {
|
||||
if (agentChatSelectedFiles.length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const agentChatUploadedFiles = store.get(agentChatUploadedFilesState.atom);
|
||||
|
||||
const threadId = await ensureThreadIdForSend();
|
||||
|
||||
if (!isDefined(threadId)) {
|
||||
@@ -131,7 +130,11 @@ export const useAgentChat = (
|
||||
store.set(messagesAtom, [...currentMessages, optimisticUserMessage]);
|
||||
store.set(errorAtom, null);
|
||||
|
||||
const fileIds = agentChatUploadedFiles.map((file) => file.fileId);
|
||||
const fileAttachments = agentChatUploadedFiles.map((file) => ({
|
||||
id: file.fileId,
|
||||
filename: file.filename,
|
||||
}));
|
||||
const uploadedFilesSnapshot = agentChatUploadedFiles;
|
||||
|
||||
setAgentChatUploadedFiles([]);
|
||||
|
||||
@@ -150,7 +153,8 @@ export const useAgentChat = (
|
||||
messageId,
|
||||
browsingContext: browsingContext ?? null,
|
||||
modelId: modelIdForRequest ?? undefined,
|
||||
fileIds: fileIds.length > 0 ? fileIds : undefined,
|
||||
fileAttachments:
|
||||
fileAttachments.length > 0 ? fileAttachments : undefined,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -186,6 +190,7 @@ export const useAgentChat = (
|
||||
? { [AGENT_CHAT_NEW_THREAD_DRAFT_KEY]: '' }
|
||||
: {}),
|
||||
}));
|
||||
setAgentChatUploadedFiles(uploadedFilesSnapshot);
|
||||
|
||||
const latestMessages = store.get(messagesAtom);
|
||||
|
||||
@@ -214,11 +219,9 @@ export const useAgentChat = (
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [
|
||||
store,
|
||||
agentChatSelectedFiles,
|
||||
ensureThreadIdForSend,
|
||||
setAgentChatInput,
|
||||
getBrowsingContext,
|
||||
agentChatUploadedFiles,
|
||||
setAgentChatUploadedFiles,
|
||||
setAgentChatDraftsByThreadId,
|
||||
modelIdForRequest,
|
||||
|
||||
+1
-1
@@ -120,7 +120,7 @@ const StyledNewChatButton = styled.div`
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
padding-inline: ${themeCssVariables.spacing[1]};
|
||||
padding-inline: ${themeCssVariables.spacing[2]};
|
||||
transition:
|
||||
background calc(${themeCssVariables.animation.duration.fast} * 1s) ease,
|
||||
color calc(${themeCssVariables.animation.duration.fast} * 1s) ease;
|
||||
|
||||
@@ -10,6 +10,7 @@ import { useOpenRecordsSearchPageInSidePanel } from '@/side-panel/hooks/useOpenR
|
||||
import { useSidePanelMenu } from '@/side-panel/hooks/useSidePanelMenu';
|
||||
import { isSidePanelOpenedState } from '@/side-panel/states/isSidePanelOpenedState';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { navigationMemorizedUrlState } from '@/ui/navigation/states/navigationMemorizedUrlState';
|
||||
import { useSetAtomComponentState } from '@/ui/utilities/state/jotai/hooks/useSetAtomComponentState';
|
||||
import { useAtomState } from '@/ui/utilities/state/jotai/hooks/useAtomState';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
@@ -29,6 +30,7 @@ export const MobileNavigationBar = () => {
|
||||
const navigate = useNavigate();
|
||||
const { defaultHomePagePath } = useDefaultHomePagePath();
|
||||
const isSidePanelOpened = useAtomStateValue(isSidePanelOpenedState);
|
||||
const navigationMemorizedUrl = useAtomStateValue(navigationMemorizedUrlState);
|
||||
const { closeSidePanelMenu } = useSidePanelMenu();
|
||||
const { openRecordsSearchPage } = useOpenRecordsSearchPageInSidePanel();
|
||||
const isSettingsPage = useIsSettingsPage();
|
||||
@@ -70,7 +72,11 @@ export const MobileNavigationBar = () => {
|
||||
setCurrentMobileNavigationDrawer('main');
|
||||
|
||||
if (isSettingsPage) {
|
||||
navigate(defaultHomePagePath);
|
||||
navigate(
|
||||
navigationMemorizedUrl !== '/'
|
||||
? navigationMemorizedUrl
|
||||
: defaultHomePagePath,
|
||||
);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@ export const useIsSettingsDrawer = () => {
|
||||
const currentMobileNavigationDrawer = useAtomStateValue(
|
||||
currentMobileNavigationDrawerState,
|
||||
);
|
||||
return isMobile
|
||||
? currentMobileNavigationDrawer === 'settings'
|
||||
: isSettingsPage;
|
||||
return (
|
||||
isSettingsPage || (isMobile && currentMobileNavigationDrawer === 'settings')
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useIsSettingsDrawer } from '@/navigation/hooks/useIsSettingsDrawer';
|
||||
import { isNavigationDrawerExpandedState } from '@/ui/navigation/states/isNavigationDrawerExpanded';
|
||||
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
|
||||
export const useNavigationDrawerExpanded = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const isSettingsDrawer = useIsSettingsDrawer();
|
||||
const isNavigationDrawerExpanded = useAtomStateValue(
|
||||
isNavigationDrawerExpandedState,
|
||||
);
|
||||
return isSettingsDrawer || isNavigationDrawerExpanded;
|
||||
return isMobile
|
||||
? isNavigationDrawerExpanded
|
||||
: isSettingsDrawer || isNavigationDrawerExpanded;
|
||||
};
|
||||
|
||||
+52
-3
@@ -1,10 +1,12 @@
|
||||
import { metadataStoreState } from '@/metadata-store/states/metadataStoreState';
|
||||
import { type FlatObjectMetadataItem } from '@/metadata-store/types/FlatObjectMetadataItem';
|
||||
import { useObjectMetadataItems } from '@/object-metadata/hooks/useObjectMetadataItems';
|
||||
import { PageLayoutLeftPanel } from '@/page-layout/components/PageLayoutLeftPanel';
|
||||
import { PageLayoutTabList } from '@/page-layout/components/PageLayoutTabList';
|
||||
import { PageLayoutTabListEffect } from '@/page-layout/components/PageLayoutTabListEffect';
|
||||
import { DEFAULT_RECORD_PAGE_LAYOUT_ID } from '@/page-layout/constants/DefaultRecordPageLayoutId';
|
||||
import { PAGE_LAYOUT_LEFT_PANEL_CONTAINER_WIDTH } from '@/page-layout/constants/PageLayoutLeftPanelContainerWidth';
|
||||
import { WIDGET_TYPE_TO_RELATION_FIELD_NAME } from '@/page-layout/constants/WidgetTypeToRelationFieldName';
|
||||
import { useCurrentPageLayoutOrThrow } from '@/page-layout/hooks/useCurrentPageLayoutOrThrow';
|
||||
import { useIsPageLayoutInEditMode } from '@/page-layout/hooks/useIsPageLayoutInEditMode';
|
||||
import { usePageLayoutAddTabStrategy } from '@/page-layout/hooks/usePageLayoutAddTabStrategy';
|
||||
@@ -22,8 +24,11 @@ import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { useAtomComponentStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomComponentStateValue';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useMemo } from 'react';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { useIsMobile } from 'twenty-ui/utilities';
|
||||
|
||||
const StyledContainer = styled.div<{ hasPinnedTab: boolean }>`
|
||||
display: grid;
|
||||
grid-template-columns: ${({ hasPinnedTab }) =>
|
||||
@@ -69,6 +74,34 @@ export const PageLayoutTabsRenderer = () => {
|
||||
currentPageLayout.id,
|
||||
);
|
||||
|
||||
const { objectMetadataItems } = useObjectMetadataItems();
|
||||
|
||||
const inactiveRelationFieldNames = useMemo(() => {
|
||||
if (!isDefined(targetRecordIdentifier)) {
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
const objectMetadataItem = objectMetadataItems.find(
|
||||
(item) =>
|
||||
item.nameSingular === targetRecordIdentifier.targetObjectNameSingular,
|
||||
);
|
||||
|
||||
if (!isDefined(objectMetadataItem)) {
|
||||
return new Set<string>();
|
||||
}
|
||||
|
||||
return new Set(
|
||||
objectMetadataItem.fields
|
||||
.filter(
|
||||
(field) =>
|
||||
!field.isActive &&
|
||||
(field.type === FieldMetadataType.RELATION ||
|
||||
field.type === FieldMetadataType.MORPH_RELATION),
|
||||
)
|
||||
.map((field) => field.name),
|
||||
);
|
||||
}, [objectMetadataItems, targetRecordIdentifier]);
|
||||
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const metadataStore = useAtomFamilyStateValue(
|
||||
@@ -114,6 +147,22 @@ export const PageLayoutTabsRenderer = () => {
|
||||
|
||||
const sortedTabs = sortTabsByPosition(tabsToRenderInTabList);
|
||||
|
||||
const sortedActiveTabs = useMemo(
|
||||
() =>
|
||||
sortedTabs.filter((tab) => {
|
||||
const widgetTypes = tab.widgets.map((widget) => widget.type);
|
||||
return !widgetTypes.some((widgetType) => {
|
||||
const relationFieldName =
|
||||
WIDGET_TYPE_TO_RELATION_FIELD_NAME[widgetType];
|
||||
return (
|
||||
isDefined(relationFieldName) &&
|
||||
inactiveRelationFieldNames.has(relationFieldName)
|
||||
);
|
||||
});
|
||||
}),
|
||||
[sortedTabs, inactiveRelationFieldNames],
|
||||
);
|
||||
|
||||
const activeTabExistsInCurrentPageLayout = currentPageLayout.tabs.some(
|
||||
(tab) => tab.id === activeTabId,
|
||||
);
|
||||
@@ -126,16 +175,16 @@ export const PageLayoutTabsRenderer = () => {
|
||||
|
||||
<StyledTabsAndDashboardContainer>
|
||||
<PageLayoutTabListEffect
|
||||
tabs={sortedTabs}
|
||||
tabs={sortedActiveTabs}
|
||||
componentInstanceId={tabListInstanceId}
|
||||
defaultTabToFocusOnMobileAndSidePanelId={
|
||||
currentPageLayout.defaultTabToFocusOnMobileAndSidePanelId ??
|
||||
undefined
|
||||
}
|
||||
/>
|
||||
{(sortedTabs.length > 1 || isPageLayoutInEditMode) && (
|
||||
{(sortedActiveTabs.length > 1 || isPageLayoutInEditMode) && (
|
||||
<PageLayoutTabList
|
||||
tabs={sortedTabs}
|
||||
tabs={sortedActiveTabs}
|
||||
behaveAsLinks={!isInSidePanel && !isPageLayoutInEditMode}
|
||||
isInSidePanel={isInSidePanel}
|
||||
componentInstanceId={tabListInstanceId}
|
||||
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { WidgetType } from '~/generated-metadata/graphql';
|
||||
|
||||
export const WIDGET_TYPE_TO_RELATION_FIELD_NAME: Partial<
|
||||
Record<WidgetType, string>
|
||||
> = {
|
||||
[WidgetType.TASKS]: 'taskTargets',
|
||||
[WidgetType.NOTES]: 'noteTargets',
|
||||
[WidgetType.FILES]: 'attachments',
|
||||
[WidgetType.TIMELINE]: 'timelineActivities',
|
||||
[WidgetType.EMAILS]: 'messageParticipants',
|
||||
[WidgetType.CALENDAR]: 'calendarEventParticipants',
|
||||
};
|
||||
@@ -48,6 +48,7 @@ const StyledPageContainerBase = styled.div`
|
||||
flex: 1 1 auto;
|
||||
flex-direction: row;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
`;
|
||||
const StyledPageContainer = motion.create(StyledPageContainerBase);
|
||||
|
||||
@@ -58,6 +59,7 @@ const StyledNavigationDrawerWrapper = styled.div`
|
||||
const StyledMainContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 0 1 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { styled } from '@linaria/react';
|
||||
import { type ReactNode } from 'react';
|
||||
|
||||
import { PagePanel } from './PagePanel';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
type PageBodyProps = {
|
||||
children: ReactNode;
|
||||
@@ -21,6 +21,10 @@ const StyledMainContainer = styled.div`
|
||||
padding-left: 0;
|
||||
padding-right: ${themeCssVariables.spacing[3]};
|
||||
width: 100%;
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
padding-left: ${themeCssVariables.spacing[3]};
|
||||
}
|
||||
`;
|
||||
|
||||
type LeftContainerProps = {
|
||||
|
||||
@@ -3,6 +3,7 @@ import { type ReactNode, useContext } from 'react';
|
||||
|
||||
import { NavigationDrawerCollapseButton } from '@/ui/navigation/navigation-drawer/components/NavigationDrawerCollapseButton';
|
||||
|
||||
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
import { useNavigationDrawerExpanded } from '@/navigation/hooks/useNavigationDrawerExpanded';
|
||||
import { PAGE_ACTION_CONTAINER_CLICK_OUTSIDE_ID } from '@/ui/layout/page/constants/PageActionContainerClickOutsideId';
|
||||
import { PAGE_BAR_MIN_HEIGHT } from '@/ui/layout/page/constants/PageBarMinHeight';
|
||||
@@ -104,6 +105,7 @@ export const PageHeader = ({
|
||||
className,
|
||||
}: PageHeaderProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
const isSettingsPage = useIsSettingsPage();
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const isNavigationDrawerExpanded = useNavigationDrawerExpanded();
|
||||
|
||||
@@ -111,7 +113,7 @@ export const PageHeader = ({
|
||||
<AnimatePresence initial={false}>
|
||||
<StyledTopBarContainer className={className} isMobile={isMobile}>
|
||||
<StyledLeftContainer>
|
||||
{!isMobile && !isNavigationDrawerExpanded && (
|
||||
{!isNavigationDrawerExpanded && (!isMobile || isSettingsPage) && (
|
||||
<NavigationDrawerCollapseButton direction="right" />
|
||||
)}
|
||||
{hasClosePageButton && (
|
||||
|
||||
+6
-14
@@ -1,5 +1,5 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useOpenSettingsMenu } from '@/navigation/hooks/useOpenSettings';
|
||||
import { useIsSettingsPage } from '@/navigation/hooks/useIsSettingsPage';
|
||||
import { styled } from '@linaria/react';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { type ReactNode, useContext } from 'react';
|
||||
@@ -47,14 +47,13 @@ export const MobileBreadcrumb = ({
|
||||
links,
|
||||
}: MobileBreadcrumbProps) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
const { openSettingsMenu } = useOpenSettingsMenu();
|
||||
const isSettingsPage = useIsSettingsPage();
|
||||
|
||||
const handleBackToSettingsClick = () => {
|
||||
openSettingsMenu();
|
||||
};
|
||||
if (isSettingsPage && links.length <= 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const previousLink = links[links.length - 2];
|
||||
const shouldRedirectToSettings = links.length === 2;
|
||||
|
||||
const text = isNonEmptyString(previousLink.children)
|
||||
? previousLink.children
|
||||
@@ -64,14 +63,7 @@ export const MobileBreadcrumb = ({
|
||||
|
||||
return (
|
||||
<StyledWrapper className={className}>
|
||||
{shouldRedirectToSettings ? (
|
||||
<>
|
||||
<IconChevronLeft size={theme.icon.size.md} />
|
||||
<StyledText onClick={handleBackToSettingsClick}>
|
||||
{t`Back to Settings`}
|
||||
</StyledText>
|
||||
</>
|
||||
) : previousLink?.href ? (
|
||||
{previousLink?.href ? (
|
||||
<>
|
||||
<IconChevronLeft size={theme.icon.size.md} />
|
||||
<StyledLinkContainer>
|
||||
|
||||
+8
-14
@@ -47,13 +47,12 @@ const StyledAnimatedContainer = styled.div<{
|
||||
: `${NAVIGATION_DRAWER_COLLAPSED_WIDTH}px`};
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: ${({ isExpanded }) => (isExpanded ? '100%' : '0')};
|
||||
width: ${({ isExpanded }) => (isExpanded ? '100vw' : '0')};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledContainer = styled.div<{
|
||||
isSettings?: boolean;
|
||||
isMobile?: boolean;
|
||||
isExpanded?: boolean;
|
||||
}>`
|
||||
box-sizing: border-box;
|
||||
@@ -61,18 +60,17 @@ const StyledContainer = styled.div<{
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.spacing[3]};
|
||||
height: 100%;
|
||||
padding: ${({ isSettings, isMobile }) =>
|
||||
padding: ${({ isSettings }) =>
|
||||
isSettings
|
||||
? isMobile
|
||||
? `${themeCssVariables.spacing[3]} 0 0 ${themeCssVariables.spacing[8]}`
|
||||
: `${themeCssVariables.spacing[3]} 0 ${themeCssVariables.spacing[4]} 0`
|
||||
? `${themeCssVariables.spacing[3]} 0 ${themeCssVariables.spacing[4]} 0`
|
||||
: `${themeCssVariables.spacing[3]} 0 ${themeCssVariables.spacing[4]} ${themeCssVariables.spacing[2]}`};
|
||||
width: ${({ isExpanded }) =>
|
||||
isExpanded ? `var(${NAVIGATION_DRAWER_WIDTH_VAR})` : '100%'};
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
gap: ${themeCssVariables.spacing[4]};
|
||||
width: 100%;
|
||||
padding-left: ${themeCssVariables.spacing[5]};
|
||||
padding-right: ${themeCssVariables.spacing[5]};
|
||||
padding-left: ${themeCssVariables.spacing[2]};
|
||||
padding-right: ${themeCssVariables.spacing[2]};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -125,12 +123,8 @@ export const NavigationDrawer = ({
|
||||
isExpanded={isExpanded}
|
||||
isResizing={isResizing}
|
||||
>
|
||||
<StyledContainer
|
||||
isSettings={isSettingsDrawer}
|
||||
isMobile={isMobile}
|
||||
isExpanded={isExpanded}
|
||||
>
|
||||
{isSettingsDrawer && title ? (
|
||||
<StyledContainer isSettings={isSettingsDrawer} isExpanded={isExpanded}>
|
||||
{!isMobile && isSettingsDrawer && title ? (
|
||||
<NavigationDrawerBackButton title={title} />
|
||||
) : (
|
||||
<NavigationDrawerHeader showCollapseButton />
|
||||
|
||||
+7
-7
@@ -10,14 +10,14 @@ const StyledFixedContainer = styled.div<{
|
||||
isSettings?: boolean;
|
||||
isMobile?: boolean;
|
||||
}>`
|
||||
padding-left: ${({ isSettings }) =>
|
||||
isSettings ? themeCssVariables.spacing[5] : '0'};
|
||||
padding-left: ${({ isSettings, isMobile }) =>
|
||||
isSettings || isMobile ? themeCssVariables.spacing[5] : '0'};
|
||||
padding-right: ${({ isSettings, isMobile }) =>
|
||||
isSettings
|
||||
? isMobile
|
||||
? themeCssVariables.spacing[5]
|
||||
: themeCssVariables.spacing[8]
|
||||
: '0'};
|
||||
isMobile
|
||||
? themeCssVariables.spacing[5]
|
||||
: isSettings
|
||||
? themeCssVariables.spacing[8]
|
||||
: '0'};
|
||||
`;
|
||||
export const NavigationDrawerFixedContent = ({
|
||||
children,
|
||||
|
||||
+23
-10
@@ -2,7 +2,7 @@ import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconSearch } from 'twenty-ui/display';
|
||||
import { LightIconButton } from 'twenty-ui/input';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
import { useOpenRecordsSearchPageInSidePanel } from '@/side-panel/hooks/useOpenRecordsSearchPageInSidePanel';
|
||||
import { PAGE_BAR_MIN_HEIGHT } from '@/ui/layout/page/constants/PageBarMinHeight';
|
||||
@@ -21,6 +21,11 @@ const StyledContainer = styled.div<{ isExpanded: boolean }>`
|
||||
padding-right: ${themeCssVariables.spacing[2]};
|
||||
transition: gap calc(${themeCssVariables.animation.duration.normal} * 1s) ease;
|
||||
user-select: none;
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
padding-left: ${themeCssVariables.spacing[5]};
|
||||
padding-right: ${themeCssVariables.spacing[5]};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledRightActions = styled.div<{ isExpanded: boolean }>`
|
||||
@@ -40,6 +45,14 @@ const StyledNavigationDrawerCollapseButtonContainer = styled.div`
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
width: ${themeCssVariables.spacing[6]};
|
||||
}
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
> * {
|
||||
height: ${themeCssVariables.spacing[8]};
|
||||
padding-right: 0;
|
||||
width: ${themeCssVariables.spacing[8]};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledWorkspaceDropdownContainer = styled.div`
|
||||
@@ -68,8 +81,8 @@ export const NavigationDrawerHeader = ({
|
||||
<StyledWorkspaceDropdownContainer>
|
||||
<MultiWorkspaceDropdownButton />
|
||||
</StyledWorkspaceDropdownContainer>
|
||||
{!isMobile && (
|
||||
<StyledRightActions isExpanded={isNavigationDrawerExpanded}>
|
||||
<StyledRightActions isExpanded={isNavigationDrawerExpanded}>
|
||||
{!isMobile && (
|
||||
<LightIconButton
|
||||
Icon={IconSearch}
|
||||
accent="secondary"
|
||||
@@ -77,13 +90,13 @@ export const NavigationDrawerHeader = ({
|
||||
onClick={openRecordsSearchPage}
|
||||
aria-label={t`Search`}
|
||||
/>
|
||||
{isNavigationDrawerExpanded && showCollapseButton && (
|
||||
<StyledNavigationDrawerCollapseButtonContainer>
|
||||
<NavigationDrawerCollapseButton direction="left" />
|
||||
</StyledNavigationDrawerCollapseButtonContainer>
|
||||
)}
|
||||
</StyledRightActions>
|
||||
)}
|
||||
)}
|
||||
{isNavigationDrawerExpanded && showCollapseButton && (
|
||||
<StyledNavigationDrawerCollapseButtonContainer>
|
||||
<NavigationDrawerCollapseButton direction="left" />
|
||||
</StyledNavigationDrawerCollapseButtonContainer>
|
||||
)}
|
||||
</StyledRightActions>
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+18
-29
@@ -24,6 +24,7 @@ import {
|
||||
TooltipDelay,
|
||||
TooltipPosition,
|
||||
} from 'twenty-ui/display';
|
||||
import { MenuItemIconBoxContainer } from 'twenty-ui/navigation';
|
||||
import {
|
||||
MOBILE_VIEWPORT,
|
||||
ThemeContext,
|
||||
@@ -140,7 +141,7 @@ const StyledItem = styled.button<StyledItemProps>`
|
||||
}
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
font-size: ${themeCssVariables.font.size.lg};
|
||||
height: ${themeCssVariables.spacing[8]};
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -161,7 +162,7 @@ const StyledLabelParent = styled.div`
|
||||
`;
|
||||
|
||||
const StyledItemLabel = styled.span`
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
font-weight: ${themeCssVariables.font.weight.regular};
|
||||
`;
|
||||
|
||||
const StyledItemSecondaryLabel = styled.span`
|
||||
@@ -193,27 +194,13 @@ const StyledSpacer = styled.span`
|
||||
flex-grow: 1;
|
||||
`;
|
||||
|
||||
const StyledIcon = styled.div<{
|
||||
$backgroundColor?: string;
|
||||
$borderColor?: string;
|
||||
}>`
|
||||
const StyledIcon = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${({ $backgroundColor }) =>
|
||||
$backgroundColor || 'transparent'};
|
||||
border: ${({ $backgroundColor, $borderColor }) =>
|
||||
$backgroundColor && $borderColor ? `1px solid ${$borderColor}` : 'none'};
|
||||
border-radius: ${({ $backgroundColor }) => ($backgroundColor ? '4px' : '0')};
|
||||
box-sizing: ${({ $backgroundColor }) =>
|
||||
$backgroundColor ? 'border-box' : 'content-box'};
|
||||
display: flex;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
height: ${({ $backgroundColor }) =>
|
||||
$backgroundColor ? themeCssVariables.spacing[4] : 'auto'};
|
||||
justify-content: center;
|
||||
margin-right: ${themeCssVariables.spacing[2]};
|
||||
width: ${({ $backgroundColor }) =>
|
||||
$backgroundColor ? themeCssVariables.spacing[4] : 'auto'};
|
||||
`;
|
||||
|
||||
const StyledRightOptionsContainer = styled.div`
|
||||
@@ -362,18 +349,20 @@ export const NavigationDrawerItem = ({
|
||||
</StyledIcon>
|
||||
) : (
|
||||
<StyledIcon>
|
||||
<Icon
|
||||
style={{
|
||||
minWidth: theme.icon.size.md,
|
||||
}}
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.md}
|
||||
color={
|
||||
showBreadcrumb && !isExpanded
|
||||
? theme.font.color.light
|
||||
: 'currentColor'
|
||||
}
|
||||
/>
|
||||
<MenuItemIconBoxContainer>
|
||||
<Icon
|
||||
style={{
|
||||
minWidth: theme.icon.size.md,
|
||||
}}
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.md}
|
||||
color={
|
||||
showBreadcrumb && !isExpanded
|
||||
? theme.font.color.light
|
||||
: 'currentColor'
|
||||
}
|
||||
/>
|
||||
</MenuItemIconBoxContainer>
|
||||
</StyledIcon>
|
||||
))}
|
||||
|
||||
|
||||
+9
-1
@@ -1,6 +1,6 @@
|
||||
import { type NavigationDrawerSubItemState } from '@/ui/navigation/navigation-drawer/types/NavigationDrawerSubItemState';
|
||||
import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { MOBILE_VIEWPORT, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
export type NavigationDrawerItemBreadcrumbProps = {
|
||||
state?: NavigationDrawerSubItemState;
|
||||
@@ -12,6 +12,10 @@ const StyledNavigationDrawerItemBreadcrumbContainer = styled.div`
|
||||
margin-left: 7.5px;
|
||||
margin-right: ${themeCssVariables.spacing[2]};
|
||||
width: 9px;
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
height: ${themeCssVariables.spacing[8]};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledGapVerticalLine = styled.div<{ darker: boolean }>`
|
||||
@@ -37,6 +41,10 @@ const StyledSecondaryFullVerticalBar = styled.div<{ darker: boolean }>`
|
||||
position: relative;
|
||||
top: -17px;
|
||||
width: 1px;
|
||||
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
height: ${themeCssVariables.spacing[8]};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledRoundedProtrusion = styled.div<{ darker: boolean }>`
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ export const NavigationDrawerScrollableContent = ({
|
||||
defaultEnableXScroll={false}
|
||||
>
|
||||
<StyledItemsContainer>
|
||||
{isSettingsDrawer ? (
|
||||
{isSettingsDrawer || isMobile ? (
|
||||
<StyledScrollableInnerContainer isMobile={isMobile}>
|
||||
{children}
|
||||
</StyledScrollableInnerContainer>
|
||||
|
||||
+2
-2
@@ -17,8 +17,8 @@ const StyledTitle = styled.div`
|
||||
justify-content: space-between;
|
||||
padding-bottom: ${themeCssVariables.spacing[1]};
|
||||
padding-left: ${themeCssVariables.spacing[1]};
|
||||
padding-right: ${themeCssVariables.spacing['0.5']};
|
||||
padding-top: ${themeCssVariables.spacing[1]};
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
|
||||
&:hover {
|
||||
background-color: ${themeCssVariables.background.transparent.light};
|
||||
|
||||
+11
-7
@@ -8,14 +8,18 @@ export class RenamePermissionFlagToRolePermissionFlagFastInstanceCommand
|
||||
implements FastInstanceCommand
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
// The legacy permissionFlag table stores per-role grants (`roleId` + `flag`).
|
||||
// Rename it to preserve those rows before creating the new permissionFlag catalog table.
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."permissionFlag" RENAME TO "rolePermissionFlag"`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."rolePermissionFlag" RENAME CONSTRAINT "PK_a02789db60620a1e9f90147b50f" TO "PK_76591adc8035c2e7b0cd6115136"`,
|
||||
`ALTER TABLE "core"."rolePermissionFlag" DROP CONSTRAINT IF EXISTS "PK_a02789db60620a1e9f90147b50f"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."rolePermissionFlag" DROP CONSTRAINT IF EXISTS "PK_8c144a021030d7e3326835a04c8"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."rolePermissionFlag" ADD CONSTRAINT "PK_76591adc8035c2e7b0cd6115136" PRIMARY KEY ("id")`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
@@ -26,9 +30,6 @@ export class RenamePermissionFlagToRolePermissionFlagFastInstanceCommand
|
||||
`ALTER INDEX "core"."IDX_PERMISSION_FLAG_ROLE_ID" RENAME TO "IDX_ROLE_PERMISSION_FLAG_ROLE_ID"`,
|
||||
);
|
||||
|
||||
// Re-hash inherited constraints/indexes so TypeORM's schema diff matches
|
||||
// the renamed table. Original names were derived from "permissionFlag"
|
||||
// and stay free for the new catalog table created by the next migration.
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."rolePermissionFlag" DROP CONSTRAINT "FK_13f8ca9c517976733a1ce4c10eb"`,
|
||||
);
|
||||
@@ -92,7 +93,10 @@ export class RenamePermissionFlagToRolePermissionFlagFastInstanceCommand
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."rolePermissionFlag" RENAME CONSTRAINT "PK_76591adc8035c2e7b0cd6115136" TO "PK_a02789db60620a1e9f90147b50f"`,
|
||||
`ALTER TABLE "core"."rolePermissionFlag" DROP CONSTRAINT IF EXISTS "PK_76591adc8035c2e7b0cd6115136"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."rolePermissionFlag" ADD CONSTRAINT "PK_a02789db60620a1e9f90147b50f" PRIMARY KEY ("id")`,
|
||||
);
|
||||
|
||||
await queryRunner.query(
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { DataSource, type DataSourceOptions } from 'typeorm';
|
||||
|
||||
import { typeORMCoreModuleOptions } from 'src/database/typeorm/core/core.datasource';
|
||||
import { DatabaseGaugeService } from 'src/database/typeorm/database-gauge.service';
|
||||
import { MetricsModule } from 'src/engine/core-modules/metrics/metrics.module';
|
||||
import { installUpgradeAwareRepositoryProxy } from 'src/engine/twenty-orm/upgrade-aware/install-upgrade-aware-repository-proxy';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forRoot(typeORMCoreModuleOptions), MetricsModule],
|
||||
imports: [
|
||||
TypeOrmModule.forRootAsync({
|
||||
useFactory: () => typeORMCoreModuleOptions,
|
||||
dataSourceFactory: async (options) => {
|
||||
const dataSource = new DataSource(options as DataSourceOptions);
|
||||
|
||||
await dataSource.initialize();
|
||||
installUpgradeAwareRepositoryProxy(dataSource);
|
||||
|
||||
return dataSource;
|
||||
},
|
||||
}),
|
||||
MetricsModule,
|
||||
],
|
||||
providers: [DatabaseGaugeService],
|
||||
exports: [],
|
||||
})
|
||||
|
||||
+309
@@ -1,9 +1,12 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { FileStorageDriverFactory } from 'src/engine/core-modules/file-storage/file-storage-driver.factory';
|
||||
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
|
||||
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
|
||||
describe('FileStorageService', () => {
|
||||
@@ -16,6 +19,9 @@ describe('FileStorageService', () => {
|
||||
|
||||
const mockFileRepository = {
|
||||
save: jest.fn(),
|
||||
upsert: jest.fn(),
|
||||
findOneOrFail: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
};
|
||||
|
||||
const mockApplicationRepository = {
|
||||
@@ -63,6 +69,7 @@ describe('FileStorageService', () => {
|
||||
delete: jest.fn(),
|
||||
move: jest.fn(),
|
||||
copy: jest.fn(),
|
||||
downloadFile: jest.fn(),
|
||||
downloadFolder: jest.fn(),
|
||||
uploadFolder: jest.fn(),
|
||||
checkFileExists: jest.fn(),
|
||||
@@ -147,4 +154,306 @@ describe('FileStorageService', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('path traversal protection', () => {
|
||||
let mockDriver: any;
|
||||
|
||||
beforeEach(() => {
|
||||
mockDriver = {
|
||||
writeFile: jest.fn().mockResolvedValue(undefined),
|
||||
readFile: jest.fn().mockResolvedValue('stream'),
|
||||
delete: jest.fn().mockResolvedValue(undefined),
|
||||
copy: jest.fn().mockResolvedValue(undefined),
|
||||
downloadFile: jest.fn().mockResolvedValue(undefined),
|
||||
checkFileExists: jest.fn().mockResolvedValue(true),
|
||||
checkFolderExists: jest.fn().mockResolvedValue(true),
|
||||
getPresignedUrl: jest.fn().mockResolvedValue('https://signed.url'),
|
||||
};
|
||||
|
||||
mockFileStorageDriverFactory.getCurrentDriver.mockReturnValue(mockDriver);
|
||||
mockApplicationRepository.findOneOrFail.mockResolvedValue({
|
||||
id: 'app-id',
|
||||
universalIdentifier: 'app-uid',
|
||||
});
|
||||
mockFileRepository.upsert.mockResolvedValue(undefined);
|
||||
mockFileRepository.findOneOrFail.mockResolvedValue({
|
||||
id: 'file-id',
|
||||
path: 'BuiltFrontComponent/file.mjs',
|
||||
mimeType: 'application/javascript',
|
||||
});
|
||||
});
|
||||
|
||||
const validResourceIdentifier = {
|
||||
workspaceId: 'workspace-123',
|
||||
applicationUniversalIdentifier: 'app-456',
|
||||
fileFolder: FileFolder.BuiltFrontComponent,
|
||||
resourcePath: 'src/components/my-component.mjs',
|
||||
};
|
||||
|
||||
const expectedValidPath =
|
||||
'workspace-123/app-456/built-front-component/src/components/my-component.mjs';
|
||||
|
||||
describe('readFile', () => {
|
||||
it('should allow valid relative paths', async () => {
|
||||
await service.readFile(validResourceIdentifier);
|
||||
|
||||
expect(mockDriver.readFile).toHaveBeenCalledWith({
|
||||
filePath: expectedValidPath,
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject path traversal with ../', () => {
|
||||
expect(() =>
|
||||
service.readFile({
|
||||
...validResourceIdentifier,
|
||||
resourcePath:
|
||||
'../../../victim-ws/victim-app/built-front-component/secret.mjs',
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mockDriver.readFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reject absolute paths', () => {
|
||||
expect(() =>
|
||||
service.readFile({
|
||||
...validResourceIdentifier,
|
||||
resourcePath: '/etc/passwd',
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mockDriver.readFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reject single-level traversal escaping fileFolder', () => {
|
||||
expect(() =>
|
||||
service.readFile({
|
||||
...validResourceIdentifier,
|
||||
resourcePath: '../source/handler.ts',
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mockDriver.readFile).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkFileExists', () => {
|
||||
it('should allow valid relative paths', async () => {
|
||||
await service.checkFileExists(validResourceIdentifier);
|
||||
|
||||
expect(mockDriver.checkFileExists).toHaveBeenCalledWith({
|
||||
filePath: expectedValidPath,
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject path traversal with ../', () => {
|
||||
expect(() =>
|
||||
service.checkFileExists({
|
||||
...validResourceIdentifier,
|
||||
resourcePath:
|
||||
'../../../other-ws/other-app/built-front-component/file.js',
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mockDriver.checkFileExists).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getPresignedUrl', () => {
|
||||
it('should reject path traversal', async () => {
|
||||
await expect(
|
||||
service.getPresignedUrl({
|
||||
...validResourceIdentifier,
|
||||
resourcePath: '../../../other-ws/file.js',
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
});
|
||||
|
||||
expect(mockDriver.getPresignedUrl).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('downloadFile', () => {
|
||||
it('should reject path traversal', () => {
|
||||
expect(() =>
|
||||
service.downloadFile({
|
||||
...validResourceIdentifier,
|
||||
resourcePath: '../../../other-ws/file.js',
|
||||
localPath: '/tmp/download.js',
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mockDriver.downloadFile).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('writeFile', () => {
|
||||
it('should reject path traversal on write', async () => {
|
||||
await expect(
|
||||
service.writeFile({
|
||||
...validResourceIdentifier,
|
||||
resourcePath:
|
||||
'../../../victim-ws/victim-app/built-front-component/overwrite.mjs',
|
||||
sourceFile: Buffer.from('malicious'),
|
||||
mimeType: 'application/javascript',
|
||||
settings: { isTemporaryFile: false, toDelete: false },
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
});
|
||||
|
||||
expect(mockDriver.writeFile).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should allow valid writes', async () => {
|
||||
await service.writeFile({
|
||||
...validResourceIdentifier,
|
||||
sourceFile: Buffer.from('valid content'),
|
||||
mimeType: 'application/javascript',
|
||||
settings: { isTemporaryFile: false, toDelete: false },
|
||||
});
|
||||
|
||||
expect(mockDriver.writeFile).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
filePath: expectedValidPath,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('delete', () => {
|
||||
it('should reject path traversal on delete', async () => {
|
||||
await expect(
|
||||
service.delete({
|
||||
...validResourceIdentifier,
|
||||
resourcePath: '../../../other-ws/other-app/folder',
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
});
|
||||
|
||||
expect(mockDriver.delete).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('copy', () => {
|
||||
it('should reject path traversal in source', async () => {
|
||||
await expect(
|
||||
service.copy({
|
||||
from: {
|
||||
...validResourceIdentifier,
|
||||
resourcePath: '../../../other-ws/secret.mjs',
|
||||
},
|
||||
to: validResourceIdentifier,
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
});
|
||||
|
||||
expect(mockDriver.copy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reject path traversal in destination', async () => {
|
||||
mockDriver.checkFileExists.mockResolvedValue(true);
|
||||
|
||||
await expect(
|
||||
service.copy({
|
||||
from: validResourceIdentifier,
|
||||
to: {
|
||||
...validResourceIdentifier,
|
||||
resourcePath: '../../../other-ws/overwrite.mjs',
|
||||
},
|
||||
}),
|
||||
).rejects.toMatchObject({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should reject traversal with excess .. segments', () => {
|
||||
expect(() =>
|
||||
service.readFile({
|
||||
...validResourceIdentifier,
|
||||
resourcePath: 'foo/../../../../../../etc/passwd',
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should accept deeply nested valid paths', async () => {
|
||||
await service.readFile({
|
||||
...validResourceIdentifier,
|
||||
resourcePath: 'a/b/c/d/e/f/deep-file.mjs',
|
||||
});
|
||||
|
||||
expect(mockDriver.readFile).toHaveBeenCalledWith({
|
||||
filePath:
|
||||
'workspace-123/app-456/built-front-component/a/b/c/d/e/f/deep-file.mjs',
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject exact 3-level traversal to another tenant', () => {
|
||||
expect(() =>
|
||||
service.readFile({
|
||||
...validResourceIdentifier,
|
||||
resourcePath:
|
||||
'../../../target-ws/target-app/built-front-component/file.js',
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should accept paths with dots that are not traversal', async () => {
|
||||
await service.readFile({
|
||||
...validResourceIdentifier,
|
||||
resourcePath: '.hidden/file.name.ext',
|
||||
});
|
||||
|
||||
expect(mockDriver.readFile).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should reject empty resource path', () => {
|
||||
expect(() =>
|
||||
service.readFile({
|
||||
...validResourceIdentifier,
|
||||
resourcePath: '',
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(mockDriver.readFile).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+14
-1
@@ -9,6 +9,8 @@ import { Like, Repository, type QueryRunner } from 'typeorm';
|
||||
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { FileStorageDriverFactory } from 'src/engine/core-modules/file-storage/file-storage-driver.factory';
|
||||
import { assertStoragePathIsWithinWorkspace } from 'src/engine/core-modules/file-storage/utils/assert-storage-path-is-within-workspace.util';
|
||||
import { assertResourcePathIsSafe } from 'src/engine/core-modules/file-storage/utils/assert-resource-path-is-safe.util';
|
||||
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
|
||||
import { FileSettings } from 'src/engine/core-modules/file/types/file-settings.types';
|
||||
|
||||
@@ -35,12 +37,23 @@ export class FileStorageService {
|
||||
fileFolder,
|
||||
resourcePath,
|
||||
}: ResourceIdentifier): string {
|
||||
return join(
|
||||
assertResourcePathIsSafe(resourcePath);
|
||||
|
||||
const onStoragePath = join(
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder,
|
||||
resourcePath,
|
||||
).replace(/\/+/g, '/');
|
||||
|
||||
assertStoragePathIsWithinWorkspace({
|
||||
onStoragePath,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder,
|
||||
});
|
||||
|
||||
return onStoragePath;
|
||||
}
|
||||
|
||||
async writeFile({
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
||||
|
||||
import { assertResourcePathIsSafe } from 'src/engine/core-modules/file-storage/utils/assert-resource-path-is-safe.util';
|
||||
|
||||
describe('assertResourcePathIsSafe', () => {
|
||||
it('should accept valid relative paths', () => {
|
||||
expect(() =>
|
||||
assertResourcePathIsSafe('src/components/test.mjs'),
|
||||
).not.toThrow();
|
||||
expect(() => assertResourcePathIsSafe('file.mjs')).not.toThrow();
|
||||
expect(() => assertResourcePathIsSafe('a/b/c/d.txt')).not.toThrow();
|
||||
});
|
||||
|
||||
it('should reject paths with .. traversal', () => {
|
||||
expect(() => assertResourcePathIsSafe('../../../other-ws/file.js')).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject absolute paths', () => {
|
||||
expect(() => assertResourcePathIsSafe('/etc/passwd')).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject paths with null bytes', () => {
|
||||
expect(() => assertResourcePathIsSafe('file\0.txt')).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject paths with backslashes', () => {
|
||||
expect(() => assertResourcePathIsSafe('..\\..\\etc\\passwd')).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject empty strings', () => {
|
||||
expect(() => assertResourcePathIsSafe('')).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
+86
@@ -0,0 +1,86 @@
|
||||
import { FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
||||
|
||||
import { assertStoragePathIsWithinWorkspace } from 'src/engine/core-modules/file-storage/utils/assert-storage-path-is-within-workspace.util';
|
||||
|
||||
const primitives = {
|
||||
workspaceId: 'workspace-id',
|
||||
applicationUniversalIdentifier: 'app-uid',
|
||||
fileFolder: FileFolder.BuiltFrontComponent,
|
||||
};
|
||||
|
||||
describe('assertStoragePathIsWithinWorkspace', () => {
|
||||
it('should accept paths within the expected prefix', () => {
|
||||
expect(() =>
|
||||
assertStoragePathIsWithinWorkspace({
|
||||
onStoragePath:
|
||||
'workspace-id/app-uid/built-front-component/src/component.mjs',
|
||||
...primitives,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should accept paths directly under the prefix', () => {
|
||||
expect(() =>
|
||||
assertStoragePathIsWithinWorkspace({
|
||||
onStoragePath: 'workspace-id/app-uid/built-front-component/file.mjs',
|
||||
...primitives,
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should reject paths that escape via .. traversal', () => {
|
||||
expect(() =>
|
||||
assertStoragePathIsWithinWorkspace({
|
||||
onStoragePath:
|
||||
'other-workspace/other-app/built-front-component/stolen.mjs',
|
||||
...primitives,
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject paths that escape by one level', () => {
|
||||
expect(() =>
|
||||
assertStoragePathIsWithinWorkspace({
|
||||
onStoragePath: 'workspace-id/app-uid/other-folder/file.mjs',
|
||||
...primitives,
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject the prefix itself without a trailing file', () => {
|
||||
expect(() =>
|
||||
assertStoragePathIsWithinWorkspace({
|
||||
onStoragePath: 'workspace-id/app-uid/built-front-component',
|
||||
...primitives,
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject paths where prefix is a partial match', () => {
|
||||
expect(() =>
|
||||
assertStoragePathIsWithinWorkspace({
|
||||
onStoragePath:
|
||||
'workspace-id/app-uid/built-front-componentMalicious/file.mjs',
|
||||
...primitives,
|
||||
}),
|
||||
).toThrow(
|
||||
expect.objectContaining({
|
||||
code: FileStorageExceptionCode.ACCESS_DENIED,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { isSafeRelativePath } from 'src/engine/core-modules/file-storage/utils/is-safe-relative-path.util';
|
||||
|
||||
describe('isSafeRelativePath', () => {
|
||||
it('should accept valid relative paths', () => {
|
||||
expect(isSafeRelativePath('src/components/my-component.mjs')).toBe(true);
|
||||
expect(isSafeRelativePath('file.mjs')).toBe(true);
|
||||
expect(isSafeRelativePath('a/b/c/d.txt')).toBe(true);
|
||||
expect(isSafeRelativePath('.hidden-file')).toBe(true);
|
||||
expect(isSafeRelativePath('folder/.gitignore')).toBe(true);
|
||||
expect(isSafeRelativePath('file.name.ext')).toBe(true);
|
||||
});
|
||||
|
||||
it('should reject paths with .. traversal segments', () => {
|
||||
expect(isSafeRelativePath('../etc/passwd')).toBe(false);
|
||||
expect(isSafeRelativePath('folder/../../etc/passwd')).toBe(false);
|
||||
expect(isSafeRelativePath('..')).toBe(false);
|
||||
expect(
|
||||
isSafeRelativePath(
|
||||
'../../../other-ws/other-app/BuiltFrontComponent/file.js',
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject paths with null bytes', () => {
|
||||
expect(isSafeRelativePath('file\0.txt')).toBe(false);
|
||||
expect(isSafeRelativePath('folder/\0/file.txt')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject absolute paths', () => {
|
||||
expect(isSafeRelativePath('/etc/passwd')).toBe(false);
|
||||
expect(isSafeRelativePath('/tmp/file.txt')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject paths with backslashes', () => {
|
||||
expect(isSafeRelativePath('folder\\file.txt')).toBe(false);
|
||||
expect(isSafeRelativePath('..\\..\\etc\\passwd')).toBe(false);
|
||||
});
|
||||
|
||||
it('should reject empty strings', () => {
|
||||
expect(isSafeRelativePath('')).toBe(false);
|
||||
});
|
||||
});
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import {
|
||||
FileStorageException,
|
||||
FileStorageExceptionCode,
|
||||
} from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
||||
import { isSafeRelativePath } from 'src/engine/core-modules/file-storage/utils/is-safe-relative-path.util';
|
||||
|
||||
export const assertResourcePathIsSafe = (resourcePath: string): void => {
|
||||
if (!isSafeRelativePath(resourcePath)) {
|
||||
throw new FileStorageException(
|
||||
'Invalid resource path: contains unsafe characters or path traversal',
|
||||
FileStorageExceptionCode.ACCESS_DENIED,
|
||||
);
|
||||
}
|
||||
};
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import { join, normalize } from 'path';
|
||||
|
||||
import { type FileFolder } from 'twenty-shared/types';
|
||||
|
||||
import {
|
||||
FileStorageException,
|
||||
FileStorageExceptionCode,
|
||||
} from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
||||
|
||||
export const assertStoragePathIsWithinWorkspace = ({
|
||||
onStoragePath,
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder,
|
||||
}: {
|
||||
onStoragePath: string;
|
||||
workspaceId: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
fileFolder: FileFolder;
|
||||
}): void => {
|
||||
const expectedPrefix = join(
|
||||
workspaceId,
|
||||
applicationUniversalIdentifier,
|
||||
fileFolder,
|
||||
);
|
||||
|
||||
const normalizedPath = normalize(onStoragePath);
|
||||
const normalizedPrefix = normalize(expectedPrefix + '/');
|
||||
|
||||
if (!normalizedPath.startsWith(normalizedPrefix)) {
|
||||
throw new FileStorageException(
|
||||
'Invalid storage path: resolved path escapes the expected directory',
|
||||
FileStorageExceptionCode.ACCESS_DENIED,
|
||||
);
|
||||
}
|
||||
};
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import { isAbsolute, normalize, sep } from 'path';
|
||||
|
||||
export const isSafeRelativePath = (filePath: string): boolean => {
|
||||
if (filePath.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filePath.includes('\0')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isAbsolute(filePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filePath.includes('\\')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const normalized = normalize(filePath);
|
||||
|
||||
if (normalized.split(sep).includes('..')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
export const defineUpgradeMetadataOnClassOrProperty = <T>({
|
||||
classMetadataKey,
|
||||
propertyMetadataKey,
|
||||
value,
|
||||
target,
|
||||
propertyKey,
|
||||
}: {
|
||||
classMetadataKey: string;
|
||||
propertyMetadataKey: string;
|
||||
value: T;
|
||||
target: object;
|
||||
propertyKey: string | symbol | undefined;
|
||||
}): void => {
|
||||
if (!isDefined(propertyKey)) {
|
||||
Reflect.defineMetadata(classMetadataKey, value, target);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const constructor = (target as { constructor: Function }).constructor;
|
||||
const existing: Record<string, T> =
|
||||
Reflect.getMetadata(propertyMetadataKey, constructor) ?? {};
|
||||
|
||||
Reflect.defineMetadata(
|
||||
propertyMetadataKey,
|
||||
{ ...existing, [String(propertyKey)]: value },
|
||||
constructor,
|
||||
);
|
||||
};
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { defineUpgradeMetadataOnClassOrProperty } from 'src/engine/core-modules/upgrade/decorators/upgrade-decorator-metadata.util';
|
||||
|
||||
export type WasIntroducedInUpgradeOptions = {
|
||||
upgradeCommandName: string;
|
||||
};
|
||||
|
||||
export const WAS_INTRODUCED_IN_UPGRADE_CLASS_METADATA_KEY =
|
||||
'WAS_INTRODUCED_IN_UPGRADE_CLASS';
|
||||
|
||||
export const WAS_INTRODUCED_IN_UPGRADE_PROPERTIES_METADATA_KEY =
|
||||
'WAS_INTRODUCED_IN_UPGRADE_PROPERTIES';
|
||||
|
||||
export type WasIntroducedInUpgradePropertyMap = Record<
|
||||
string,
|
||||
WasIntroducedInUpgradeOptions
|
||||
>;
|
||||
|
||||
export const WasIntroducedInUpgrade =
|
||||
(options: WasIntroducedInUpgradeOptions) =>
|
||||
(target: object, propertyKey?: string | symbol): void => {
|
||||
defineUpgradeMetadataOnClassOrProperty({
|
||||
classMetadataKey: WAS_INTRODUCED_IN_UPGRADE_CLASS_METADATA_KEY,
|
||||
propertyMetadataKey: WAS_INTRODUCED_IN_UPGRADE_PROPERTIES_METADATA_KEY,
|
||||
value: options,
|
||||
target,
|
||||
propertyKey,
|
||||
});
|
||||
};
|
||||
|
||||
export const getWasIntroducedInUpgradeClassMetadata = (
|
||||
target: Function,
|
||||
): WasIntroducedInUpgradeOptions | undefined =>
|
||||
Reflect.getMetadata(WAS_INTRODUCED_IN_UPGRADE_CLASS_METADATA_KEY, target);
|
||||
|
||||
export const getWasIntroducedInUpgradePropertyMetadata = (
|
||||
target: Function,
|
||||
): WasIntroducedInUpgradePropertyMap =>
|
||||
Reflect.getMetadata(
|
||||
WAS_INTRODUCED_IN_UPGRADE_PROPERTIES_METADATA_KEY,
|
||||
target,
|
||||
) ?? {};
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { defineUpgradeMetadataOnClassOrProperty } from 'src/engine/core-modules/upgrade/decorators/upgrade-decorator-metadata.util';
|
||||
|
||||
export type WasRenamedInUpgradeHistoryEntry = {
|
||||
previousName: string;
|
||||
upgradeCommandName: string;
|
||||
};
|
||||
|
||||
export const WAS_RENAMED_IN_UPGRADE_CLASS_METADATA_KEY =
|
||||
'WAS_RENAMED_IN_UPGRADE_CLASS';
|
||||
|
||||
export const WAS_RENAMED_IN_UPGRADE_PROPERTIES_METADATA_KEY =
|
||||
'WAS_RENAMED_IN_UPGRADE_PROPERTIES';
|
||||
|
||||
export type WasRenamedInUpgradePropertyMap = Record<
|
||||
string,
|
||||
WasRenamedInUpgradeHistoryEntry[]
|
||||
>;
|
||||
|
||||
export const WasRenamedInUpgrade =
|
||||
(history: WasRenamedInUpgradeHistoryEntry[]) =>
|
||||
(target: object, propertyKey?: string | symbol): void => {
|
||||
defineUpgradeMetadataOnClassOrProperty({
|
||||
classMetadataKey: WAS_RENAMED_IN_UPGRADE_CLASS_METADATA_KEY,
|
||||
propertyMetadataKey: WAS_RENAMED_IN_UPGRADE_PROPERTIES_METADATA_KEY,
|
||||
value: history,
|
||||
target,
|
||||
propertyKey,
|
||||
});
|
||||
};
|
||||
|
||||
export const getWasRenamedInUpgradeClassMetadata = (
|
||||
target: Function,
|
||||
): WasRenamedInUpgradeHistoryEntry[] | undefined =>
|
||||
Reflect.getMetadata(WAS_RENAMED_IN_UPGRADE_CLASS_METADATA_KEY, target);
|
||||
|
||||
export const getWasRenamedInUpgradePropertyMetadata = (
|
||||
target: Function,
|
||||
): WasRenamedInUpgradePropertyMap =>
|
||||
Reflect.getMetadata(WAS_RENAMED_IN_UPGRADE_PROPERTIES_METADATA_KEY, target) ??
|
||||
{};
|
||||
+29
@@ -18,6 +18,7 @@ import {
|
||||
} from 'src/engine/core-modules/upgrade/services/upgrade-sequence-reader.service';
|
||||
import { WorkspaceCommandRunnerService } from 'src/engine/core-modules/upgrade/services/workspace-command-runner.service';
|
||||
import { formatUpgradeLog } from 'src/engine/core-modules/upgrade/utils/format-upgrade-log.util';
|
||||
import { UpgradeAwareEntityMetadataAdapter } from 'src/engine/twenty-orm/upgrade-aware/upgrade-aware-entity-metadata.adapter';
|
||||
import { WorkspaceVersionService } from 'src/engine/workspace-manager/workspace-version/services/workspace-version.service';
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
@@ -35,6 +36,7 @@ export class UpgradeSequenceRunnerService {
|
||||
private readonly instanceCommandRunnerService: InstanceCommandRunnerService,
|
||||
private readonly workspaceCommandRunnerService: WorkspaceCommandRunnerService,
|
||||
private readonly upgradeSequenceReaderService: UpgradeSequenceReaderService,
|
||||
private readonly upgradeAwareEntityMetadataAdapter: UpgradeAwareEntityMetadataAdapter,
|
||||
private readonly workspaceIteratorService: WorkspaceIteratorService,
|
||||
private readonly workspaceVersionService: WorkspaceVersionService,
|
||||
) {}
|
||||
@@ -50,6 +52,31 @@ export class UpgradeSequenceRunnerService {
|
||||
return { totalSuccesses: 0, totalFailures: 0 };
|
||||
}
|
||||
|
||||
await this.upgradeAwareEntityMetadataAdapter.refresh();
|
||||
|
||||
try {
|
||||
return await this.runInner({ sequence, options });
|
||||
} finally {
|
||||
try {
|
||||
await this.upgradeAwareEntityMetadataAdapter.refresh();
|
||||
} catch (refreshError) {
|
||||
this.logger.error(
|
||||
`Failed to refresh upgrade-aware entity metadata after run`,
|
||||
refreshError instanceof Error
|
||||
? refreshError.stack
|
||||
: String(refreshError),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async runInner({
|
||||
sequence,
|
||||
options,
|
||||
}: {
|
||||
sequence: UpgradeStep[];
|
||||
options: ParsedUpgradeCommandOptions;
|
||||
}): Promise<UpgradeSequenceRunnerReport> {
|
||||
const allActiveOrSuspendedWorkspaceIds =
|
||||
await this.workspaceVersionService.getActiveOrSuspendedWorkspaceIds();
|
||||
|
||||
@@ -107,6 +134,8 @@ export class UpgradeSequenceRunnerService {
|
||||
skipDataMigration: allActiveOrSuspendedWorkspaceIds.length === 0,
|
||||
});
|
||||
|
||||
await this.upgradeAwareEntityMetadataAdapter.refresh();
|
||||
|
||||
cursor++;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import { WorkspaceCommandRunnerService } from 'src/engine/core-modules/upgrade/s
|
||||
import { UpgradeGaugeService } from 'src/engine/core-modules/upgrade/upgrade-gauge.service';
|
||||
import { UpgradeMigrationEntity } from 'src/engine/core-modules/upgrade/upgrade-migration.entity';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { UpgradeAwareEntityMetadataAdapter } from 'src/engine/twenty-orm/upgrade-aware/upgrade-aware-entity-metadata.adapter';
|
||||
import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-version/workspace-version.module';
|
||||
|
||||
@Module({
|
||||
@@ -36,6 +37,7 @@ import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-v
|
||||
InstanceCommandRunnerService,
|
||||
WorkspaceCommandRunnerService,
|
||||
UpgradeCommandRegistryService,
|
||||
UpgradeAwareEntityMetadataAdapter,
|
||||
UpgradeSequenceReaderService,
|
||||
UpgradeSequenceRunnerService,
|
||||
UpgradeStatusService,
|
||||
@@ -47,6 +49,7 @@ import { WorkspaceVersionModule } from 'src/engine/workspace-manager/workspace-v
|
||||
InstanceCommandRunnerService,
|
||||
WorkspaceCommandRunnerService,
|
||||
UpgradeCommandRegistryService,
|
||||
UpgradeAwareEntityMetadataAdapter,
|
||||
UpgradeSequenceReaderService,
|
||||
UpgradeSequenceRunnerService,
|
||||
UpgradeStatusService,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user