feat(connections): run a logic function on connection provider connect (#23167)
## What Adds an optional `onConnectLogicFunctionUniversalIdentifier` field to the connection provider manifest. When set, the referenced logic function is dispatched right after an OAuth connection is successfully established for that provider. This gives apps a first-class "on connect" hook — e.g. the Slack app can resolve the workspace's `team_id` via `auth.test` and claim the `team_id -> workspaceId` mapping in the SERVER key-value store immediately on connect, instead of racing against later events. Follow-up to the app key-value store PR (#23089). ## How - **twenty-shared**: add `onConnectLogicFunctionUniversalIdentifier` to `ConnectionProviderManifest`. - **twenty-sdk**: expose the field in `defineConnectionProvider` and validate it is a UUID `universalIdentifier`. - **twenty-server**: - add a nullable `onConnectLogicFunctionUniversalIdentifier` column to `ConnectionProviderEntity` (+ fast instance command / migration). - map the field through the <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23167?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -1389,44 +1389,6 @@ type ApplicationConnectionProvider {
|
||||
oauth: ApplicationConnectionProviderOAuthConfig
|
||||
}
|
||||
|
||||
type EnterpriseLicenseInfoDTO {
|
||||
isValid: Boolean!
|
||||
licensee: String
|
||||
expiresAt: DateTime
|
||||
subscriptionId: String
|
||||
}
|
||||
|
||||
type EnterpriseSubscriptionStatusDTO {
|
||||
status: String!
|
||||
licensee: String
|
||||
expiresAt: DateTime
|
||||
cancelAt: DateTime
|
||||
currentPeriodEnd: DateTime
|
||||
isCancellationScheduled: Boolean!
|
||||
}
|
||||
|
||||
type ApprovedAccessDomain {
|
||||
id: UUID!
|
||||
domain: String!
|
||||
isValidated: Boolean!
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
type FileWithSignedUrl {
|
||||
id: UUID!
|
||||
path: String!
|
||||
size: Float!
|
||||
createdAt: DateTime!
|
||||
url: String!
|
||||
}
|
||||
|
||||
type FileUploadTarget {
|
||||
fileId: UUID!
|
||||
uploadUrl: String!
|
||||
contentType: String!
|
||||
expiresAt: DateTime!
|
||||
}
|
||||
|
||||
type BillingSubscriptionSchedulePhaseItem {
|
||||
price: String!
|
||||
quantity: Float
|
||||
@@ -1518,11 +1480,6 @@ type BillingSubscriptionItem {
|
||||
billingProduct: BillingProductDTO!
|
||||
}
|
||||
|
||||
type BillingCustomer {
|
||||
id: UUID!
|
||||
hasPaymentMethod: Boolean
|
||||
}
|
||||
|
||||
type BillingSubscription {
|
||||
id: UUID!
|
||||
status: SubscriptionStatus!
|
||||
@@ -1545,6 +1502,73 @@ enum SubscriptionStatus {
|
||||
Unpaid
|
||||
}
|
||||
|
||||
type BillingCustomer {
|
||||
id: UUID!
|
||||
hasPaymentMethod: Boolean
|
||||
}
|
||||
|
||||
type LogicFunctionExecutionResult {
|
||||
"""Execution result in JSON format"""
|
||||
data: JSON
|
||||
|
||||
"""Execution Logs"""
|
||||
logs: String!
|
||||
|
||||
"""Execution duration in milliseconds"""
|
||||
duration: Float!
|
||||
|
||||
"""Execution status"""
|
||||
status: LogicFunctionExecutionStatus!
|
||||
|
||||
"""Execution error in JSON format"""
|
||||
error: JSON
|
||||
}
|
||||
|
||||
"""Status of the logic function execution"""
|
||||
enum LogicFunctionExecutionStatus {
|
||||
IDLE
|
||||
SUCCESS
|
||||
ERROR
|
||||
}
|
||||
|
||||
type EnterpriseLicenseInfoDTO {
|
||||
isValid: Boolean!
|
||||
licensee: String
|
||||
expiresAt: DateTime
|
||||
subscriptionId: String
|
||||
}
|
||||
|
||||
type EnterpriseSubscriptionStatusDTO {
|
||||
status: String!
|
||||
licensee: String
|
||||
expiresAt: DateTime
|
||||
cancelAt: DateTime
|
||||
currentPeriodEnd: DateTime
|
||||
isCancellationScheduled: Boolean!
|
||||
}
|
||||
|
||||
type ApprovedAccessDomain {
|
||||
id: UUID!
|
||||
domain: String!
|
||||
isValidated: Boolean!
|
||||
createdAt: DateTime!
|
||||
}
|
||||
|
||||
type FileWithSignedUrl {
|
||||
id: UUID!
|
||||
path: String!
|
||||
size: Float!
|
||||
createdAt: DateTime!
|
||||
url: String!
|
||||
}
|
||||
|
||||
type FileUploadTarget {
|
||||
fileId: UUID!
|
||||
uploadUrl: String!
|
||||
contentType: String!
|
||||
expiresAt: DateTime!
|
||||
}
|
||||
|
||||
type BillingEndTrialPeriod {
|
||||
"""Updated subscription status"""
|
||||
status: SubscriptionStatus
|
||||
@@ -1704,30 +1728,6 @@ type EventSubscription {
|
||||
metadataEvents: [MetadataEvent!]!
|
||||
}
|
||||
|
||||
type LogicFunctionExecutionResult {
|
||||
"""Execution result in JSON format"""
|
||||
data: JSON
|
||||
|
||||
"""Execution Logs"""
|
||||
logs: String!
|
||||
|
||||
"""Execution duration in milliseconds"""
|
||||
duration: Float!
|
||||
|
||||
"""Execution status"""
|
||||
status: LogicFunctionExecutionStatus!
|
||||
|
||||
"""Execution error in JSON format"""
|
||||
error: JSON
|
||||
}
|
||||
|
||||
"""Status of the logic function execution"""
|
||||
enum LogicFunctionExecutionStatus {
|
||||
IDLE
|
||||
SUCCESS
|
||||
ERROR
|
||||
}
|
||||
|
||||
type FeatureFlag {
|
||||
key: FeatureFlagKey!
|
||||
value: Boolean!
|
||||
|
||||
@@ -1063,49 +1063,6 @@ export interface ApplicationConnectionProvider {
|
||||
__typename: 'ApplicationConnectionProvider'
|
||||
}
|
||||
|
||||
export interface EnterpriseLicenseInfoDTO {
|
||||
isValid: Scalars['Boolean']
|
||||
licensee?: Scalars['String']
|
||||
expiresAt?: Scalars['DateTime']
|
||||
subscriptionId?: Scalars['String']
|
||||
__typename: 'EnterpriseLicenseInfoDTO'
|
||||
}
|
||||
|
||||
export interface EnterpriseSubscriptionStatusDTO {
|
||||
status: Scalars['String']
|
||||
licensee?: Scalars['String']
|
||||
expiresAt?: Scalars['DateTime']
|
||||
cancelAt?: Scalars['DateTime']
|
||||
currentPeriodEnd?: Scalars['DateTime']
|
||||
isCancellationScheduled: Scalars['Boolean']
|
||||
__typename: 'EnterpriseSubscriptionStatusDTO'
|
||||
}
|
||||
|
||||
export interface ApprovedAccessDomain {
|
||||
id: Scalars['UUID']
|
||||
domain: Scalars['String']
|
||||
isValidated: Scalars['Boolean']
|
||||
createdAt: Scalars['DateTime']
|
||||
__typename: 'ApprovedAccessDomain'
|
||||
}
|
||||
|
||||
export interface FileWithSignedUrl {
|
||||
id: Scalars['UUID']
|
||||
path: Scalars['String']
|
||||
size: Scalars['Float']
|
||||
createdAt: Scalars['DateTime']
|
||||
url: Scalars['String']
|
||||
__typename: 'FileWithSignedUrl'
|
||||
}
|
||||
|
||||
export interface FileUploadTarget {
|
||||
fileId: Scalars['UUID']
|
||||
uploadUrl: Scalars['String']
|
||||
contentType: Scalars['String']
|
||||
expiresAt: Scalars['DateTime']
|
||||
__typename: 'FileUploadTarget'
|
||||
}
|
||||
|
||||
export interface BillingSubscriptionSchedulePhaseItem {
|
||||
price: Scalars['String']
|
||||
quantity?: Scalars['Float']
|
||||
@@ -1197,12 +1154,6 @@ export interface BillingSubscriptionItem {
|
||||
__typename: 'BillingSubscriptionItem'
|
||||
}
|
||||
|
||||
export interface BillingCustomer {
|
||||
id: Scalars['UUID']
|
||||
hasPaymentMethod?: Scalars['Boolean']
|
||||
__typename: 'BillingCustomer'
|
||||
}
|
||||
|
||||
export interface BillingSubscription {
|
||||
id: Scalars['UUID']
|
||||
status: SubscriptionStatus
|
||||
@@ -1217,6 +1168,73 @@ export interface BillingSubscription {
|
||||
|
||||
export type SubscriptionStatus = 'Active' | 'Canceled' | 'Incomplete' | 'IncompleteExpired' | 'PastDue' | 'Paused' | 'Trialing' | 'Unpaid'
|
||||
|
||||
export interface BillingCustomer {
|
||||
id: Scalars['UUID']
|
||||
hasPaymentMethod?: Scalars['Boolean']
|
||||
__typename: 'BillingCustomer'
|
||||
}
|
||||
|
||||
export interface LogicFunctionExecutionResult {
|
||||
/** Execution result in JSON format */
|
||||
data?: Scalars['JSON']
|
||||
/** Execution Logs */
|
||||
logs: Scalars['String']
|
||||
/** Execution duration in milliseconds */
|
||||
duration: Scalars['Float']
|
||||
/** Execution status */
|
||||
status: LogicFunctionExecutionStatus
|
||||
/** Execution error in JSON format */
|
||||
error?: Scalars['JSON']
|
||||
__typename: 'LogicFunctionExecutionResult'
|
||||
}
|
||||
|
||||
|
||||
/** Status of the logic function execution */
|
||||
export type LogicFunctionExecutionStatus = 'IDLE' | 'SUCCESS' | 'ERROR'
|
||||
|
||||
export interface EnterpriseLicenseInfoDTO {
|
||||
isValid: Scalars['Boolean']
|
||||
licensee?: Scalars['String']
|
||||
expiresAt?: Scalars['DateTime']
|
||||
subscriptionId?: Scalars['String']
|
||||
__typename: 'EnterpriseLicenseInfoDTO'
|
||||
}
|
||||
|
||||
export interface EnterpriseSubscriptionStatusDTO {
|
||||
status: Scalars['String']
|
||||
licensee?: Scalars['String']
|
||||
expiresAt?: Scalars['DateTime']
|
||||
cancelAt?: Scalars['DateTime']
|
||||
currentPeriodEnd?: Scalars['DateTime']
|
||||
isCancellationScheduled: Scalars['Boolean']
|
||||
__typename: 'EnterpriseSubscriptionStatusDTO'
|
||||
}
|
||||
|
||||
export interface ApprovedAccessDomain {
|
||||
id: Scalars['UUID']
|
||||
domain: Scalars['String']
|
||||
isValidated: Scalars['Boolean']
|
||||
createdAt: Scalars['DateTime']
|
||||
__typename: 'ApprovedAccessDomain'
|
||||
}
|
||||
|
||||
export interface FileWithSignedUrl {
|
||||
id: Scalars['UUID']
|
||||
path: Scalars['String']
|
||||
size: Scalars['Float']
|
||||
createdAt: Scalars['DateTime']
|
||||
url: Scalars['String']
|
||||
__typename: 'FileWithSignedUrl'
|
||||
}
|
||||
|
||||
export interface FileUploadTarget {
|
||||
fileId: Scalars['UUID']
|
||||
uploadUrl: Scalars['String']
|
||||
contentType: Scalars['String']
|
||||
expiresAt: Scalars['DateTime']
|
||||
__typename: 'FileUploadTarget'
|
||||
}
|
||||
|
||||
export interface BillingEndTrialPeriod {
|
||||
/** Updated subscription status */
|
||||
status?: SubscriptionStatus
|
||||
@@ -1372,24 +1390,6 @@ export interface EventSubscription {
|
||||
__typename: 'EventSubscription'
|
||||
}
|
||||
|
||||
export interface LogicFunctionExecutionResult {
|
||||
/** Execution result in JSON format */
|
||||
data?: Scalars['JSON']
|
||||
/** Execution Logs */
|
||||
logs: Scalars['String']
|
||||
/** Execution duration in milliseconds */
|
||||
duration: Scalars['Float']
|
||||
/** Execution status */
|
||||
status: LogicFunctionExecutionStatus
|
||||
/** Execution error in JSON format */
|
||||
error?: Scalars['JSON']
|
||||
__typename: 'LogicFunctionExecutionResult'
|
||||
}
|
||||
|
||||
|
||||
/** Status of the logic function execution */
|
||||
export type LogicFunctionExecutionStatus = 'IDLE' | 'SUCCESS' | 'ERROR'
|
||||
|
||||
export interface FeatureFlag {
|
||||
key: FeatureFlagKey
|
||||
value: Scalars['Boolean']
|
||||
@@ -4177,54 +4177,6 @@ export interface ApplicationConnectionProviderGenqlSelection{
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface EnterpriseLicenseInfoDTOGenqlSelection{
|
||||
isValid?: boolean | number
|
||||
licensee?: boolean | number
|
||||
expiresAt?: boolean | number
|
||||
subscriptionId?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface EnterpriseSubscriptionStatusDTOGenqlSelection{
|
||||
status?: boolean | number
|
||||
licensee?: boolean | number
|
||||
expiresAt?: boolean | number
|
||||
cancelAt?: boolean | number
|
||||
currentPeriodEnd?: boolean | number
|
||||
isCancellationScheduled?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface ApprovedAccessDomainGenqlSelection{
|
||||
id?: boolean | number
|
||||
domain?: boolean | number
|
||||
isValidated?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface FileWithSignedUrlGenqlSelection{
|
||||
id?: boolean | number
|
||||
path?: boolean | number
|
||||
size?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
url?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface FileUploadTargetGenqlSelection{
|
||||
fileId?: boolean | number
|
||||
uploadUrl?: boolean | number
|
||||
contentType?: boolean | number
|
||||
expiresAt?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface BillingSubscriptionSchedulePhaseItemGenqlSelection{
|
||||
price?: boolean | number
|
||||
quantity?: boolean | number
|
||||
@@ -4314,13 +4266,6 @@ export interface BillingSubscriptionItemGenqlSelection{
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface BillingCustomerGenqlSelection{
|
||||
id?: boolean | number
|
||||
hasPaymentMethod?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface BillingSubscriptionGenqlSelection{
|
||||
id?: boolean | number
|
||||
status?: boolean | number
|
||||
@@ -4334,6 +4279,76 @@ export interface BillingSubscriptionGenqlSelection{
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface BillingCustomerGenqlSelection{
|
||||
id?: boolean | number
|
||||
hasPaymentMethod?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface LogicFunctionExecutionResultGenqlSelection{
|
||||
/** Execution result in JSON format */
|
||||
data?: boolean | number
|
||||
/** Execution Logs */
|
||||
logs?: boolean | number
|
||||
/** Execution duration in milliseconds */
|
||||
duration?: boolean | number
|
||||
/** Execution status */
|
||||
status?: boolean | number
|
||||
/** Execution error in JSON format */
|
||||
error?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface EnterpriseLicenseInfoDTOGenqlSelection{
|
||||
isValid?: boolean | number
|
||||
licensee?: boolean | number
|
||||
expiresAt?: boolean | number
|
||||
subscriptionId?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface EnterpriseSubscriptionStatusDTOGenqlSelection{
|
||||
status?: boolean | number
|
||||
licensee?: boolean | number
|
||||
expiresAt?: boolean | number
|
||||
cancelAt?: boolean | number
|
||||
currentPeriodEnd?: boolean | number
|
||||
isCancellationScheduled?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface ApprovedAccessDomainGenqlSelection{
|
||||
id?: boolean | number
|
||||
domain?: boolean | number
|
||||
isValidated?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface FileWithSignedUrlGenqlSelection{
|
||||
id?: boolean | number
|
||||
path?: boolean | number
|
||||
size?: boolean | number
|
||||
createdAt?: boolean | number
|
||||
url?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface FileUploadTargetGenqlSelection{
|
||||
fileId?: boolean | number
|
||||
uploadUrl?: boolean | number
|
||||
contentType?: boolean | number
|
||||
expiresAt?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface BillingEndTrialPeriodGenqlSelection{
|
||||
/** Updated subscription status */
|
||||
status?: boolean | number
|
||||
@@ -4496,21 +4511,6 @@ export interface EventSubscriptionGenqlSelection{
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface LogicFunctionExecutionResultGenqlSelection{
|
||||
/** Execution result in JSON format */
|
||||
data?: boolean | number
|
||||
/** Execution Logs */
|
||||
logs?: boolean | number
|
||||
/** Execution duration in milliseconds */
|
||||
duration?: boolean | number
|
||||
/** Execution status */
|
||||
status?: boolean | number
|
||||
/** Execution error in JSON format */
|
||||
error?: boolean | number
|
||||
__typename?: boolean | number
|
||||
__scalar?: boolean | number
|
||||
}
|
||||
|
||||
export interface FeatureFlagGenqlSelection{
|
||||
key?: boolean | number
|
||||
value?: boolean | number
|
||||
@@ -7325,46 +7325,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
|
||||
|
||||
|
||||
|
||||
const EnterpriseLicenseInfoDTO_possibleTypes: string[] = ['EnterpriseLicenseInfoDTO']
|
||||
export const isEnterpriseLicenseInfoDTO = (obj?: { __typename?: any } | null): obj is EnterpriseLicenseInfoDTO => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isEnterpriseLicenseInfoDTO"')
|
||||
return EnterpriseLicenseInfoDTO_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const EnterpriseSubscriptionStatusDTO_possibleTypes: string[] = ['EnterpriseSubscriptionStatusDTO']
|
||||
export const isEnterpriseSubscriptionStatusDTO = (obj?: { __typename?: any } | null): obj is EnterpriseSubscriptionStatusDTO => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isEnterpriseSubscriptionStatusDTO"')
|
||||
return EnterpriseSubscriptionStatusDTO_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const ApprovedAccessDomain_possibleTypes: string[] = ['ApprovedAccessDomain']
|
||||
export const isApprovedAccessDomain = (obj?: { __typename?: any } | null): obj is ApprovedAccessDomain => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isApprovedAccessDomain"')
|
||||
return ApprovedAccessDomain_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const FileWithSignedUrl_possibleTypes: string[] = ['FileWithSignedUrl']
|
||||
export const isFileWithSignedUrl = (obj?: { __typename?: any } | null): obj is FileWithSignedUrl => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isFileWithSignedUrl"')
|
||||
return FileWithSignedUrl_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const FileUploadTarget_possibleTypes: string[] = ['FileUploadTarget']
|
||||
export const isFileUploadTarget = (obj?: { __typename?: any } | null): obj is FileUploadTarget => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isFileUploadTarget"')
|
||||
return FileUploadTarget_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const BillingSubscriptionSchedulePhaseItem_possibleTypes: string[] = ['BillingSubscriptionSchedulePhaseItem']
|
||||
export const isBillingSubscriptionSchedulePhaseItem = (obj?: { __typename?: any } | null): obj is BillingSubscriptionSchedulePhaseItem => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isBillingSubscriptionSchedulePhaseItem"')
|
||||
@@ -7445,6 +7405,14 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
|
||||
|
||||
|
||||
|
||||
const BillingSubscription_possibleTypes: string[] = ['BillingSubscription']
|
||||
export const isBillingSubscription = (obj?: { __typename?: any } | null): obj is BillingSubscription => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isBillingSubscription"')
|
||||
return BillingSubscription_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const BillingCustomer_possibleTypes: string[] = ['BillingCustomer']
|
||||
export const isBillingCustomer = (obj?: { __typename?: any } | null): obj is BillingCustomer => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isBillingCustomer"')
|
||||
@@ -7453,10 +7421,50 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
|
||||
|
||||
|
||||
|
||||
const BillingSubscription_possibleTypes: string[] = ['BillingSubscription']
|
||||
export const isBillingSubscription = (obj?: { __typename?: any } | null): obj is BillingSubscription => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isBillingSubscription"')
|
||||
return BillingSubscription_possibleTypes.includes(obj.__typename)
|
||||
const LogicFunctionExecutionResult_possibleTypes: string[] = ['LogicFunctionExecutionResult']
|
||||
export const isLogicFunctionExecutionResult = (obj?: { __typename?: any } | null): obj is LogicFunctionExecutionResult => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isLogicFunctionExecutionResult"')
|
||||
return LogicFunctionExecutionResult_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const EnterpriseLicenseInfoDTO_possibleTypes: string[] = ['EnterpriseLicenseInfoDTO']
|
||||
export const isEnterpriseLicenseInfoDTO = (obj?: { __typename?: any } | null): obj is EnterpriseLicenseInfoDTO => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isEnterpriseLicenseInfoDTO"')
|
||||
return EnterpriseLicenseInfoDTO_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const EnterpriseSubscriptionStatusDTO_possibleTypes: string[] = ['EnterpriseSubscriptionStatusDTO']
|
||||
export const isEnterpriseSubscriptionStatusDTO = (obj?: { __typename?: any } | null): obj is EnterpriseSubscriptionStatusDTO => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isEnterpriseSubscriptionStatusDTO"')
|
||||
return EnterpriseSubscriptionStatusDTO_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const ApprovedAccessDomain_possibleTypes: string[] = ['ApprovedAccessDomain']
|
||||
export const isApprovedAccessDomain = (obj?: { __typename?: any } | null): obj is ApprovedAccessDomain => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isApprovedAccessDomain"')
|
||||
return ApprovedAccessDomain_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const FileWithSignedUrl_possibleTypes: string[] = ['FileWithSignedUrl']
|
||||
export const isFileWithSignedUrl = (obj?: { __typename?: any } | null): obj is FileWithSignedUrl => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isFileWithSignedUrl"')
|
||||
return FileWithSignedUrl_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const FileUploadTarget_possibleTypes: string[] = ['FileUploadTarget']
|
||||
export const isFileUploadTarget = (obj?: { __typename?: any } | null): obj is FileUploadTarget => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isFileUploadTarget"')
|
||||
return FileUploadTarget_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
@@ -7597,14 +7605,6 @@ export interface LogicFunctionLogsInput {applicationId?: (Scalars['UUID'] | null
|
||||
|
||||
|
||||
|
||||
const LogicFunctionExecutionResult_possibleTypes: string[] = ['LogicFunctionExecutionResult']
|
||||
export const isLogicFunctionExecutionResult = (obj?: { __typename?: any } | null): obj is LogicFunctionExecutionResult => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isLogicFunctionExecutionResult"')
|
||||
return LogicFunctionExecutionResult_possibleTypes.includes(obj.__typename)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const FeatureFlag_possibleTypes: string[] = ['FeatureFlag']
|
||||
export const isFeatureFlag = (obj?: { __typename?: any } | null): obj is FeatureFlag => {
|
||||
if (!obj?.__typename) throw new Error('__typename is missing in "isFeatureFlag"')
|
||||
@@ -9236,6 +9236,12 @@ export const enumSubscriptionStatus = {
|
||||
Unpaid: 'Unpaid' as const
|
||||
}
|
||||
|
||||
export const enumLogicFunctionExecutionStatus = {
|
||||
IDLE: 'IDLE' as const,
|
||||
SUCCESS: 'SUCCESS' as const,
|
||||
ERROR: 'ERROR' as const
|
||||
}
|
||||
|
||||
export const enumNavigationMenuItemType = {
|
||||
VIEW: 'VIEW' as const,
|
||||
FOLDER: 'FOLDER' as const,
|
||||
@@ -9260,12 +9266,6 @@ export const enumDatabaseEventAction = {
|
||||
UPSERTED: 'UPSERTED' as const
|
||||
}
|
||||
|
||||
export const enumLogicFunctionExecutionStatus = {
|
||||
IDLE: 'IDLE' as const,
|
||||
SUCCESS: 'SUCCESS' as const,
|
||||
ERROR: 'ERROR' as const
|
||||
}
|
||||
|
||||
export const enumFeatureFlagKey = {
|
||||
IS_APP_CLAIMING_ENABLED: 'IS_APP_CLAIMING_ENABLED' as const,
|
||||
IS_UNIQUE_INDEXES_ENABLED: 'IS_UNIQUE_INDEXES_ENABLED' as const,
|
||||
|
||||
@@ -44,15 +44,15 @@ export default {
|
||||
101,
|
||||
107,
|
||||
121,
|
||||
132,
|
||||
133,
|
||||
134,
|
||||
136,
|
||||
145,
|
||||
158,
|
||||
161,
|
||||
127,
|
||||
128,
|
||||
129,
|
||||
131,
|
||||
139,
|
||||
142,
|
||||
160,
|
||||
163,
|
||||
167,
|
||||
165,
|
||||
169,
|
||||
176,
|
||||
177,
|
||||
@@ -97,13 +97,13 @@ export default {
|
||||
1
|
||||
],
|
||||
"metadata": [
|
||||
131
|
||||
126
|
||||
],
|
||||
"on_BillingLicensedProduct": [
|
||||
140
|
||||
135
|
||||
],
|
||||
"on_BillingMeteredProduct": [
|
||||
141
|
||||
136
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -1731,16 +1731,16 @@ export default {
|
||||
168
|
||||
],
|
||||
"billingSubscriptions": [
|
||||
144
|
||||
138
|
||||
],
|
||||
"installedApplications": [
|
||||
50
|
||||
],
|
||||
"currentBillingSubscription": [
|
||||
144
|
||||
138
|
||||
],
|
||||
"billingCustomer": [
|
||||
143
|
||||
140
|
||||
],
|
||||
"billingEntitlements": [
|
||||
231
|
||||
@@ -2707,6 +2707,239 @@ export default {
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingSubscriptionSchedulePhaseItem": {
|
||||
"price": [
|
||||
1
|
||||
],
|
||||
"quantity": [
|
||||
16
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingSubscriptionSchedulePhase": {
|
||||
"start_date": [
|
||||
16
|
||||
],
|
||||
"end_date": [
|
||||
16
|
||||
],
|
||||
"items": [
|
||||
124
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingProductMetadata": {
|
||||
"planKey": [
|
||||
127
|
||||
],
|
||||
"priceUsageBased": [
|
||||
128
|
||||
],
|
||||
"productKey": [
|
||||
129
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingPlanKey": {},
|
||||
"BillingUsageType": {},
|
||||
"BillingProductKey": {},
|
||||
"BillingPriceLicensed": {
|
||||
"recurringInterval": [
|
||||
131
|
||||
],
|
||||
"unitAmount": [
|
||||
16
|
||||
],
|
||||
"stripePriceId": [
|
||||
1
|
||||
],
|
||||
"priceUsageType": [
|
||||
128
|
||||
],
|
||||
"creditAmount": [
|
||||
16
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"SubscriptionInterval": {},
|
||||
"BillingPriceTier": {
|
||||
"upTo": [
|
||||
16
|
||||
],
|
||||
"flatAmount": [
|
||||
16
|
||||
],
|
||||
"unitAmount": [
|
||||
16
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingPriceMetered": {
|
||||
"tiers": [
|
||||
132
|
||||
],
|
||||
"recurringInterval": [
|
||||
131
|
||||
],
|
||||
"stripePriceId": [
|
||||
1
|
||||
],
|
||||
"priceUsageType": [
|
||||
128
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingProduct": {
|
||||
"name": [
|
||||
1
|
||||
],
|
||||
"description": [
|
||||
1
|
||||
],
|
||||
"images": [
|
||||
1
|
||||
],
|
||||
"metadata": [
|
||||
126
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingLicensedProduct": {
|
||||
"name": [
|
||||
1
|
||||
],
|
||||
"description": [
|
||||
1
|
||||
],
|
||||
"images": [
|
||||
1
|
||||
],
|
||||
"metadata": [
|
||||
126
|
||||
],
|
||||
"prices": [
|
||||
130
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingMeteredProduct": {
|
||||
"name": [
|
||||
1
|
||||
],
|
||||
"description": [
|
||||
1
|
||||
],
|
||||
"images": [
|
||||
1
|
||||
],
|
||||
"metadata": [
|
||||
126
|
||||
],
|
||||
"prices": [
|
||||
133
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingSubscriptionItem": {
|
||||
"id": [
|
||||
4
|
||||
],
|
||||
"hasReachedCurrentPeriodCap": [
|
||||
3
|
||||
],
|
||||
"quantity": [
|
||||
16
|
||||
],
|
||||
"stripePriceId": [
|
||||
1
|
||||
],
|
||||
"billingProduct": [
|
||||
0
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingSubscription": {
|
||||
"id": [
|
||||
4
|
||||
],
|
||||
"status": [
|
||||
139
|
||||
],
|
||||
"interval": [
|
||||
131
|
||||
],
|
||||
"billingSubscriptionItems": [
|
||||
137
|
||||
],
|
||||
"currentPeriodEnd": [
|
||||
6
|
||||
],
|
||||
"metadata": [
|
||||
5
|
||||
],
|
||||
"phases": [
|
||||
125
|
||||
],
|
||||
"cancelAt": [
|
||||
6
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"SubscriptionStatus": {},
|
||||
"BillingCustomer": {
|
||||
"id": [
|
||||
4
|
||||
],
|
||||
"hasPaymentMethod": [
|
||||
3
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"LogicFunctionExecutionResult": {
|
||||
"data": [
|
||||
5
|
||||
],
|
||||
"logs": [
|
||||
1
|
||||
],
|
||||
"duration": [
|
||||
16
|
||||
],
|
||||
"status": [
|
||||
142
|
||||
],
|
||||
"error": [
|
||||
5
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"LogicFunctionExecutionStatus": {},
|
||||
"EnterpriseLicenseInfoDTO": {
|
||||
"isValid": [
|
||||
3
|
||||
@@ -2801,221 +3034,9 @@ export default {
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingSubscriptionSchedulePhaseItem": {
|
||||
"price": [
|
||||
1
|
||||
],
|
||||
"quantity": [
|
||||
16
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingSubscriptionSchedulePhase": {
|
||||
"start_date": [
|
||||
16
|
||||
],
|
||||
"end_date": [
|
||||
16
|
||||
],
|
||||
"items": [
|
||||
129
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingProductMetadata": {
|
||||
"planKey": [
|
||||
132
|
||||
],
|
||||
"priceUsageBased": [
|
||||
133
|
||||
],
|
||||
"productKey": [
|
||||
134
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingPlanKey": {},
|
||||
"BillingUsageType": {},
|
||||
"BillingProductKey": {},
|
||||
"BillingPriceLicensed": {
|
||||
"recurringInterval": [
|
||||
136
|
||||
],
|
||||
"unitAmount": [
|
||||
16
|
||||
],
|
||||
"stripePriceId": [
|
||||
1
|
||||
],
|
||||
"priceUsageType": [
|
||||
133
|
||||
],
|
||||
"creditAmount": [
|
||||
16
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"SubscriptionInterval": {},
|
||||
"BillingPriceTier": {
|
||||
"upTo": [
|
||||
16
|
||||
],
|
||||
"flatAmount": [
|
||||
16
|
||||
],
|
||||
"unitAmount": [
|
||||
16
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingPriceMetered": {
|
||||
"tiers": [
|
||||
137
|
||||
],
|
||||
"recurringInterval": [
|
||||
136
|
||||
],
|
||||
"stripePriceId": [
|
||||
1
|
||||
],
|
||||
"priceUsageType": [
|
||||
133
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingProduct": {
|
||||
"name": [
|
||||
1
|
||||
],
|
||||
"description": [
|
||||
1
|
||||
],
|
||||
"images": [
|
||||
1
|
||||
],
|
||||
"metadata": [
|
||||
131
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingLicensedProduct": {
|
||||
"name": [
|
||||
1
|
||||
],
|
||||
"description": [
|
||||
1
|
||||
],
|
||||
"images": [
|
||||
1
|
||||
],
|
||||
"metadata": [
|
||||
131
|
||||
],
|
||||
"prices": [
|
||||
135
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingMeteredProduct": {
|
||||
"name": [
|
||||
1
|
||||
],
|
||||
"description": [
|
||||
1
|
||||
],
|
||||
"images": [
|
||||
1
|
||||
],
|
||||
"metadata": [
|
||||
131
|
||||
],
|
||||
"prices": [
|
||||
138
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingSubscriptionItem": {
|
||||
"id": [
|
||||
4
|
||||
],
|
||||
"hasReachedCurrentPeriodCap": [
|
||||
3
|
||||
],
|
||||
"quantity": [
|
||||
16
|
||||
],
|
||||
"stripePriceId": [
|
||||
1
|
||||
],
|
||||
"billingProduct": [
|
||||
0
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingCustomer": {
|
||||
"id": [
|
||||
4
|
||||
],
|
||||
"hasPaymentMethod": [
|
||||
3
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"BillingSubscription": {
|
||||
"id": [
|
||||
4
|
||||
],
|
||||
"status": [
|
||||
145
|
||||
],
|
||||
"interval": [
|
||||
136
|
||||
],
|
||||
"billingSubscriptionItems": [
|
||||
142
|
||||
],
|
||||
"currentPeriodEnd": [
|
||||
6
|
||||
],
|
||||
"metadata": [
|
||||
5
|
||||
],
|
||||
"phases": [
|
||||
130
|
||||
],
|
||||
"cancelAt": [
|
||||
6
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"SubscriptionStatus": {},
|
||||
"BillingEndTrialPeriod": {
|
||||
"status": [
|
||||
145
|
||||
139
|
||||
],
|
||||
"hasPaymentMethod": [
|
||||
3
|
||||
@@ -3029,7 +3050,7 @@ export default {
|
||||
},
|
||||
"BillingResourceCreditUsage": {
|
||||
"productKey": [
|
||||
134
|
||||
129
|
||||
],
|
||||
"periodStart": [
|
||||
6
|
||||
@@ -3058,16 +3079,16 @@ export default {
|
||||
},
|
||||
"BillingPlan": {
|
||||
"planKey": [
|
||||
132
|
||||
127
|
||||
],
|
||||
"baseProducts": [
|
||||
140
|
||||
135
|
||||
],
|
||||
"resourceCreditProducts": [
|
||||
140
|
||||
135
|
||||
],
|
||||
"meteredProducts": [
|
||||
141
|
||||
136
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -3094,10 +3115,10 @@ export default {
|
||||
},
|
||||
"BillingUpdate": {
|
||||
"currentBillingSubscription": [
|
||||
144
|
||||
138
|
||||
],
|
||||
"billingSubscriptions": [
|
||||
144
|
||||
138
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -3147,7 +3168,7 @@ export default {
|
||||
1
|
||||
],
|
||||
"result": [
|
||||
154
|
||||
156
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -3184,7 +3205,7 @@ export default {
|
||||
4
|
||||
],
|
||||
"type": [
|
||||
158
|
||||
160
|
||||
],
|
||||
"name": [
|
||||
1
|
||||
@@ -3217,7 +3238,7 @@ export default {
|
||||
6
|
||||
],
|
||||
"targetRecordIdentifier": [
|
||||
156
|
||||
158
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -3243,7 +3264,7 @@ export default {
|
||||
},
|
||||
"MetadataEvent": {
|
||||
"type": [
|
||||
161
|
||||
163
|
||||
],
|
||||
"metadataName": [
|
||||
1
|
||||
@@ -3252,7 +3273,7 @@ export default {
|
||||
1
|
||||
],
|
||||
"properties": [
|
||||
159
|
||||
161
|
||||
],
|
||||
"updatedCollectionHash": [
|
||||
1
|
||||
@@ -3264,7 +3285,7 @@ export default {
|
||||
"MetadataEventAction": {},
|
||||
"ObjectRecordEvent": {
|
||||
"action": [
|
||||
163
|
||||
165
|
||||
],
|
||||
"objectNameSingular": [
|
||||
1
|
||||
@@ -3279,7 +3300,7 @@ export default {
|
||||
1
|
||||
],
|
||||
"properties": [
|
||||
159
|
||||
161
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -3291,7 +3312,7 @@ export default {
|
||||
1
|
||||
],
|
||||
"objectRecordEvent": [
|
||||
162
|
||||
164
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
@@ -3302,36 +3323,15 @@ export default {
|
||||
1
|
||||
],
|
||||
"objectRecordEventsWithQueryIds": [
|
||||
164
|
||||
166
|
||||
],
|
||||
"metadataEvents": [
|
||||
160
|
||||
162
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"LogicFunctionExecutionResult": {
|
||||
"data": [
|
||||
5
|
||||
],
|
||||
"logs": [
|
||||
1
|
||||
],
|
||||
"duration": [
|
||||
16
|
||||
],
|
||||
"status": [
|
||||
167
|
||||
],
|
||||
"error": [
|
||||
5
|
||||
],
|
||||
"__typename": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"LogicFunctionExecutionStatus": {},
|
||||
"FeatureFlag": {
|
||||
"key": [
|
||||
169
|
||||
@@ -6085,10 +6085,10 @@ export default {
|
||||
},
|
||||
"Query": {
|
||||
"navigationMenuItems": [
|
||||
157
|
||||
159
|
||||
],
|
||||
"navigationMenuItem": [
|
||||
157,
|
||||
159,
|
||||
{
|
||||
"id": [
|
||||
4,
|
||||
@@ -6122,7 +6122,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"enterpriseSubscriptionStatus": [
|
||||
125
|
||||
144
|
||||
],
|
||||
"getViewFilterGroups": [
|
||||
56,
|
||||
@@ -6248,7 +6248,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"getInviteSuggestions": [
|
||||
152
|
||||
154
|
||||
],
|
||||
"applicationConnectionProviders": [
|
||||
123,
|
||||
@@ -6260,7 +6260,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"billingPortalSession": [
|
||||
150,
|
||||
152,
|
||||
{
|
||||
"returnUrlPath": [
|
||||
1
|
||||
@@ -6271,16 +6271,16 @@ export default {
|
||||
}
|
||||
],
|
||||
"listPlans": [
|
||||
148
|
||||
150
|
||||
],
|
||||
"getResourceCreditUsage": [
|
||||
147
|
||||
149
|
||||
],
|
||||
"findWorkspaceInvitations": [
|
||||
154
|
||||
156
|
||||
],
|
||||
"getApprovedAccessDomains": [
|
||||
126
|
||||
145
|
||||
],
|
||||
"getPageLayoutTabs": [
|
||||
119,
|
||||
@@ -7138,7 +7138,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"createManyNavigationMenuItems": [
|
||||
157,
|
||||
159,
|
||||
{
|
||||
"inputs": [
|
||||
359,
|
||||
@@ -7147,7 +7147,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"createNavigationMenuItem": [
|
||||
157,
|
||||
159,
|
||||
{
|
||||
"input": [
|
||||
359,
|
||||
@@ -7156,7 +7156,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"updateManyNavigationMenuItems": [
|
||||
157,
|
||||
159,
|
||||
{
|
||||
"inputs": [
|
||||
360,
|
||||
@@ -7165,7 +7165,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"updateNavigationMenuItem": [
|
||||
157,
|
||||
159,
|
||||
{
|
||||
"input": [
|
||||
360,
|
||||
@@ -7174,7 +7174,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"deleteManyNavigationMenuItems": [
|
||||
157,
|
||||
159,
|
||||
{
|
||||
"ids": [
|
||||
4,
|
||||
@@ -7183,7 +7183,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"deleteNavigationMenuItem": [
|
||||
157,
|
||||
159,
|
||||
{
|
||||
"id": [
|
||||
4,
|
||||
@@ -7192,7 +7192,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"createFileUpload": [
|
||||
128,
|
||||
147,
|
||||
{
|
||||
"filename": [
|
||||
1,
|
||||
@@ -7215,7 +7215,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"completeFileUpload": [
|
||||
127,
|
||||
146,
|
||||
{
|
||||
"fileId": [
|
||||
1,
|
||||
@@ -7227,10 +7227,10 @@ export default {
|
||||
3
|
||||
],
|
||||
"releaseEnterpriseServerBinding": [
|
||||
124
|
||||
143
|
||||
],
|
||||
"setEnterpriseKey": [
|
||||
124,
|
||||
143,
|
||||
{
|
||||
"enterpriseKey": [
|
||||
1,
|
||||
@@ -7239,7 +7239,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"uploadEmailAttachmentFile": [
|
||||
127,
|
||||
146,
|
||||
{
|
||||
"file": [
|
||||
363,
|
||||
@@ -7248,7 +7248,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"uploadAiChatFile": [
|
||||
127,
|
||||
146,
|
||||
{
|
||||
"file": [
|
||||
363,
|
||||
@@ -7257,7 +7257,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"uploadWorkflowFile": [
|
||||
127,
|
||||
146,
|
||||
{
|
||||
"file": [
|
||||
363,
|
||||
@@ -7266,7 +7266,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"uploadWorkspaceLogo": [
|
||||
127,
|
||||
146,
|
||||
{
|
||||
"file": [
|
||||
363,
|
||||
@@ -7275,7 +7275,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"uploadWorkspaceMemberProfilePicture": [
|
||||
127,
|
||||
146,
|
||||
{
|
||||
"file": [
|
||||
363,
|
||||
@@ -7284,7 +7284,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"uploadFilesFieldFile": [
|
||||
127,
|
||||
146,
|
||||
{
|
||||
"file": [
|
||||
363,
|
||||
@@ -7297,7 +7297,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"uploadFilesFieldFileByUniversalIdentifier": [
|
||||
127,
|
||||
146,
|
||||
{
|
||||
"file": [
|
||||
363,
|
||||
@@ -7610,10 +7610,10 @@ export default {
|
||||
}
|
||||
],
|
||||
"skipSyncEmailOnboardingStep": [
|
||||
153
|
||||
155
|
||||
],
|
||||
"triggerInstallAppsOnboardingStep": [
|
||||
153,
|
||||
155,
|
||||
{
|
||||
"universalIdentifiers": [
|
||||
1,
|
||||
@@ -7639,14 +7639,14 @@ export default {
|
||||
}
|
||||
],
|
||||
"checkoutSession": [
|
||||
150,
|
||||
152,
|
||||
{
|
||||
"recurringInterval": [
|
||||
136,
|
||||
131,
|
||||
"SubscriptionInterval!"
|
||||
],
|
||||
"plan": [
|
||||
132,
|
||||
127,
|
||||
"BillingPlanKey!"
|
||||
],
|
||||
"requirePaymentMethod": [
|
||||
@@ -7659,14 +7659,14 @@ export default {
|
||||
}
|
||||
],
|
||||
"createSubscriptionPaymentIntent": [
|
||||
149,
|
||||
151,
|
||||
{
|
||||
"recurringInterval": [
|
||||
136,
|
||||
131,
|
||||
"SubscriptionInterval!"
|
||||
],
|
||||
"plan": [
|
||||
132,
|
||||
127,
|
||||
"BillingPlanKey!"
|
||||
],
|
||||
"requirePaymentMethod": [
|
||||
@@ -7683,22 +7683,22 @@ export default {
|
||||
}
|
||||
],
|
||||
"createBillingPaymentMethodSetupIntent": [
|
||||
149
|
||||
151
|
||||
],
|
||||
"switchSubscriptionInterval": [
|
||||
151
|
||||
153
|
||||
],
|
||||
"switchBillingPlan": [
|
||||
151
|
||||
153
|
||||
],
|
||||
"cancelSwitchBillingPlan": [
|
||||
151
|
||||
153
|
||||
],
|
||||
"cancelSwitchBillingInterval": [
|
||||
151
|
||||
153
|
||||
],
|
||||
"setResourceCreditSubscriptionPrice": [
|
||||
151,
|
||||
153,
|
||||
{
|
||||
"priceId": [
|
||||
1,
|
||||
@@ -7707,10 +7707,10 @@ export default {
|
||||
}
|
||||
],
|
||||
"endSubscriptionTrialPeriod": [
|
||||
146
|
||||
148
|
||||
],
|
||||
"cancelSwitchResourceCreditPrice": [
|
||||
151
|
||||
153
|
||||
],
|
||||
"deleteWorkspaceInvitation": [
|
||||
1,
|
||||
@@ -7722,7 +7722,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"resendWorkspaceInvitation": [
|
||||
155,
|
||||
157,
|
||||
{
|
||||
"appTokenId": [
|
||||
1,
|
||||
@@ -7731,7 +7731,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"sendInvitations": [
|
||||
155,
|
||||
157,
|
||||
{
|
||||
"emails": [
|
||||
1,
|
||||
@@ -7743,7 +7743,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"createApprovedAccessDomain": [
|
||||
126,
|
||||
145,
|
||||
{
|
||||
"input": [
|
||||
400,
|
||||
@@ -7761,7 +7761,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"validateApprovedAccessDomain": [
|
||||
126,
|
||||
145,
|
||||
{
|
||||
"input": [
|
||||
402,
|
||||
@@ -7993,7 +7993,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"executeOneLogicFunction": [
|
||||
166,
|
||||
141,
|
||||
{
|
||||
"input": [
|
||||
425,
|
||||
@@ -8989,7 +8989,7 @@ export default {
|
||||
}
|
||||
],
|
||||
"uploadNewWorkspaceLogo": [
|
||||
127,
|
||||
146,
|
||||
{
|
||||
"workspaceId": [
|
||||
1,
|
||||
@@ -9458,7 +9458,7 @@ export default {
|
||||
4
|
||||
],
|
||||
"type": [
|
||||
158
|
||||
160
|
||||
],
|
||||
"name": [
|
||||
1
|
||||
@@ -12102,7 +12102,7 @@ export default {
|
||||
},
|
||||
"Subscription": {
|
||||
"onEventSubscription": [
|
||||
165,
|
||||
167,
|
||||
{
|
||||
"eventStreamId": [
|
||||
1,
|
||||
|
||||
@@ -42,6 +42,9 @@ export default defineConnectionProvider({
|
||||
// Optional: provider's RFC 7009 token revocation endpoint, called on disconnect.
|
||||
// revokeEndpoint: 'https://example.com/oauth/revoke',
|
||||
},
|
||||
// Optional: a logic function in this app to run right after a connection is
|
||||
// established. See "Run a logic function on connect".
|
||||
// onConnectLogicFunction: { universalIdentifier: '3a2b1c0d-...-...' },
|
||||
});
|
||||
```
|
||||
|
||||
@@ -87,6 +90,40 @@ https://<your-twenty-server>/auth/apps/callback
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Run a logic function on connect" description="React the moment a connection is established">
|
||||
|
||||
Some providers hand you data at connect time that you need to persist before the connection is usable — the classic example is Slack, where the OAuth response identifies the workspace's `team_id` that inbound events will be keyed by. Set `onConnectLogicFunction` to reference a logic function in the same app (by its `universalIdentifier`), and it runs right after the `ConnectedAccount` is created.
|
||||
|
||||
```ts src/connection-providers/slack-connection.ts
|
||||
export default defineConnectionProvider({
|
||||
universalIdentifier: '...',
|
||||
name: 'slack',
|
||||
displayName: 'Slack',
|
||||
type: 'oauth',
|
||||
oauth: {
|
||||
/* ... */
|
||||
},
|
||||
// Runs claimSlackTeam after every successful Slack connection.
|
||||
onConnectLogicFunction: {
|
||||
universalIdentifier: '3a2b1c0d-1111-4222-8333-444455556666',
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
The hook runs **asynchronously in the connecting workspace** (it is enqueued, not awaited), so a slow or failing hook never blocks or breaks the OAuth callback — make it idempotent and handle its own retries. The handler receives:
|
||||
|
||||
```ts
|
||||
type OnConnectPayload = {
|
||||
connectionProviderId: string;
|
||||
connectionProviderName: string; // e.g. 'slack'
|
||||
connectedAccountId: string;
|
||||
};
|
||||
```
|
||||
|
||||
From there use `getConnection(connectedAccountId)` to read the fresh access token and call the provider's API (e.g. Slack `auth.test`) or persist a mapping with the [key-value store](/developers/extend/apps/logic/key-value-store).
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="listConnections / getConnection" description="Use connections from a logic function">
|
||||
|
||||
Inside a logic function handler, `listConnections({ providerName })` returns this app's `ConnectedAccount` rows for the given provider, with refreshed access tokens.
|
||||
|
||||
+31
@@ -196,6 +196,37 @@ describe('manifestValidate', () => {
|
||||
'Duplicate universal identifiers: 550e8400-e29b-41d4-a716-446655440001',
|
||||
);
|
||||
});
|
||||
|
||||
it('should not flag a connection provider referencing a logic function via onConnectLogicFunction as a duplicate', () => {
|
||||
const logicFunctionId = '550e8400-e29b-41d4-a716-446655440040';
|
||||
|
||||
const logicFunction = {
|
||||
universalIdentifier: logicFunctionId,
|
||||
name: 'onConnect',
|
||||
sourceHandlerPath: 'src/logic-functions/on-connect.ts',
|
||||
builtHandlerPath: 'dist/on-connect.js',
|
||||
builtHandlerChecksum: '00000000-0000-4000-8000-000000000000',
|
||||
handlerName: 'handler',
|
||||
} as unknown as Manifest['logicFunctions'][number];
|
||||
|
||||
const connectionProvider = {
|
||||
universalIdentifier: '550e8400-e29b-41d4-a716-446655440041',
|
||||
name: 'slack',
|
||||
displayName: 'Slack',
|
||||
type: 'oauth',
|
||||
oauth: {},
|
||||
onConnectLogicFunction: { universalIdentifier: logicFunctionId },
|
||||
} as unknown as NonNullable<Manifest['connectionProviders']>[number];
|
||||
|
||||
const result = manifestValidate({
|
||||
...validManifest,
|
||||
logicFunctions: [logicFunction],
|
||||
connectionProviders: [connectionProvider],
|
||||
});
|
||||
|
||||
expect(result.isValid).toBe(true);
|
||||
expect(result.errors).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('relation field validation', () => {
|
||||
|
||||
@@ -72,7 +72,8 @@ const findUniversalIdentifiers = (obj: object): string[] => {
|
||||
|
||||
if (
|
||||
key === 'postInstallLogicFunction' ||
|
||||
key === 'preInstallLogicFunction'
|
||||
key === 'preInstallLogicFunction' ||
|
||||
key === 'onConnectLogicFunction'
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
+24
@@ -82,4 +82,28 @@ describe('defineConnectionProvider', () => {
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
});
|
||||
|
||||
it('accepts a valid onConnectLogicFunction', () => {
|
||||
const result = defineConnectionProvider({
|
||||
...baseValidConfig,
|
||||
onConnectLogicFunction: {
|
||||
universalIdentifier: 'b648f87b-1d26-4961-b974-0908fd991061',
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.errors).toEqual([]);
|
||||
});
|
||||
|
||||
it('rejects a non-UUID onConnectLogicFunction universalIdentifier', () => {
|
||||
const result = defineConnectionProvider({
|
||||
...baseValidConfig,
|
||||
onConnectLogicFunction: { universalIdentifier: 'claim-team-id' },
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(
|
||||
result.errors.some((error) => error.includes('onConnectLogicFunction')),
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,6 +36,15 @@ export const defineConnectionProvider: DefineEntity<
|
||||
errors.push('Connection provider must have a displayName');
|
||||
}
|
||||
|
||||
if (
|
||||
config.onConnectLogicFunction &&
|
||||
!UUID_PATTERN.test(config.onConnectLogicFunction.universalIdentifier)
|
||||
) {
|
||||
errors.push(
|
||||
`Connection provider onConnectLogicFunction.universalIdentifier "${config.onConnectLogicFunction.universalIdentifier}" must be the UUID universalIdentifier of a logic function in this app.`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!config.type) {
|
||||
errors.push("Connection provider must declare a `type` (e.g. 'oauth')");
|
||||
} else if (!(SUPPORTED_TYPES as readonly string[]).includes(config.type)) {
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
|
||||
import { type FastInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/fast-instance-command.interface';
|
||||
|
||||
@RegisteredInstanceCommand('2.24.0', 1784712843602)
|
||||
export class AddOnConnectLogicFunctionToConnectionProviderFastInstanceCommand
|
||||
implements FastInstanceCommand
|
||||
{
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."connectionProvider" ADD COLUMN IF NOT EXISTS "onConnectLogicFunctionUniversalIdentifier" uuid',
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
'ALTER TABLE "core"."connectionProvider" DROP COLUMN IF EXISTS "onConnectLogicFunctionUniversalIdentifier"',
|
||||
);
|
||||
}
|
||||
}
|
||||
+2
@@ -120,6 +120,7 @@ import { AddApplicationIdToKeyValuePairFastInstanceCommand } from './2-23/2-23-i
|
||||
import { BackfillCreatedWorkspaceActivationStatusSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784286705000-backfill-created-workspace-activation-status';
|
||||
import { UnlistUnclaimedNpmApplicationRegistrationsSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784322591746-unlist-unclaimed-npm-application-registrations';
|
||||
import { AddStatusesToBillingSubscriptionIndexSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784650048045-add-statuses-to-billing-subscription-index';
|
||||
import { AddOnConnectLogicFunctionToConnectionProviderFastInstanceCommand } from './2-24/2-24-instance-command-fast-1784712843602-add-on-connect-logic-function-to-connection-provider';
|
||||
|
||||
export const INSTANCE_COMMANDS = [
|
||||
AddViewFieldGroupIdIndexOnViewFieldFastInstanceCommand,
|
||||
@@ -242,4 +243,5 @@ export const INSTANCE_COMMANDS = [
|
||||
AddApplicationIdToKeyValuePairFastInstanceCommand,
|
||||
AddSdkClientCoreChecksumToApplicationFastInstanceCommand,
|
||||
AddStatusesToBillingSubscriptionIndexSlowInstanceCommand,
|
||||
AddOnConnectLogicFunctionToConnectionProviderFastInstanceCommand,
|
||||
];
|
||||
|
||||
+21
@@ -50,11 +50,32 @@ describe('fromConnectionProviderManifestToUniversalFlatConnectionProvider', () =
|
||||
tokenRequestContentType: 'json',
|
||||
usePkce: true,
|
||||
},
|
||||
onConnectLogicFunctionUniversalIdentifier: null,
|
||||
createdAt: NOW,
|
||||
updatedAt: NOW,
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves the onConnectLogicFunction universalIdentifier into the flat field when provided', () => {
|
||||
const onConnectLogicFunctionUniversalIdentifier =
|
||||
'c1c1c1c1-c1c1-4c1c-c1c1-c1c1c1c1c1c1';
|
||||
|
||||
const result =
|
||||
fromConnectionProviderManifestToUniversalFlatConnectionProvider({
|
||||
connectionProviderManifest: buildManifest({
|
||||
onConnectLogicFunction: {
|
||||
universalIdentifier: onConnectLogicFunctionUniversalIdentifier,
|
||||
},
|
||||
}),
|
||||
applicationUniversalIdentifier: APP_UID,
|
||||
now: NOW,
|
||||
});
|
||||
|
||||
expect(result.onConnectLogicFunctionUniversalIdentifier).toBe(
|
||||
onConnectLogicFunctionUniversalIdentifier,
|
||||
);
|
||||
});
|
||||
|
||||
it('passes through optional oauth config when provided', () => {
|
||||
const result =
|
||||
fromConnectionProviderManifestToUniversalFlatConnectionProvider({
|
||||
|
||||
+3
@@ -43,6 +43,9 @@ export const fromConnectionProviderManifestToUniversalFlatConnectionProvider =
|
||||
displayName: connectionProviderManifest.displayName,
|
||||
type: connectionProviderManifest.type,
|
||||
oauthConfig,
|
||||
onConnectLogicFunctionUniversalIdentifier:
|
||||
connectionProviderManifest.onConnectLogicFunction
|
||||
?.universalIdentifier ?? null,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
+128
@@ -19,12 +19,16 @@ import { type ConnectionProviderEntity } from 'src/engine/core-modules/applicati
|
||||
import { ConnectionProviderOAuthFlowService } from 'src/engine/core-modules/application/connection-provider/connection-provider-oauth-flow.service';
|
||||
import { ConnectionProviderService } from 'src/engine/core-modules/application/connection-provider/connection-provider.service';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util';
|
||||
import { SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX } from 'src/engine/core-modules/secret-encryption/constants/secret-encryption.constant';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { ConnectedAccountTokenEncryptionService } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
|
||||
const FAKE_CIPHER_PREFIX = `${SECRET_ENCRYPTION_ENVELOPE_V2_PREFIX}keyid:`;
|
||||
|
||||
@@ -47,6 +51,9 @@ describe('ConnectionProviderOAuthFlowService', () => {
|
||||
findOne: jest.Mock;
|
||||
findOneByOrFail: jest.Mock;
|
||||
};
|
||||
let workspaceCacheService: { getOrRecompute: jest.Mock };
|
||||
let messageQueueService: { add: jest.Mock };
|
||||
let exceptionHandlerService: { captureExceptions: jest.Mock };
|
||||
|
||||
const baseProvider: ConnectionProviderEntity = {
|
||||
id: 'provider-1',
|
||||
@@ -95,6 +102,13 @@ describe('ConnectionProviderOAuthFlowService', () => {
|
||||
provider: ConnectedAccountProvider.APP,
|
||||
})),
|
||||
};
|
||||
workspaceCacheService = {
|
||||
getOrRecompute: jest.fn(async () => ({
|
||||
flatLogicFunctionMaps: { byUniversalIdentifier: {} },
|
||||
})),
|
||||
};
|
||||
messageQueueService = { add: jest.fn() };
|
||||
exceptionHandlerService = { captureExceptions: jest.fn() };
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
@@ -113,6 +127,18 @@ describe('ConnectionProviderOAuthFlowService', () => {
|
||||
provide: getRepositoryToken(ConnectedAccountEntity),
|
||||
useValue: connectedAccountRepository,
|
||||
},
|
||||
{
|
||||
provide: WorkspaceCacheService,
|
||||
useValue: workspaceCacheService,
|
||||
},
|
||||
{
|
||||
provide: getQueueToken(MessageQueue.logicFunctionQueue),
|
||||
useValue: messageQueueService,
|
||||
},
|
||||
{
|
||||
provide: ExceptionHandlerService,
|
||||
useValue: exceptionHandlerService,
|
||||
},
|
||||
{
|
||||
// Real prefix/round-trip behavior is asserted in
|
||||
// connected-account-token-encryption.service.spec.ts; here we use a
|
||||
@@ -437,5 +463,107 @@ describe('ConnectionProviderOAuthFlowService', () => {
|
||||
}),
|
||||
).rejects.toThrow(/state/);
|
||||
});
|
||||
|
||||
describe('on-connect hook', () => {
|
||||
const ON_CONNECT_UID = 'c1c1c1c1-c1c1-4c1c-c1c1-c1c1c1c1c1c1';
|
||||
|
||||
it('does not dispatch a hook when the provider declares none', async () => {
|
||||
await service.completeAuthorizationFlow({
|
||||
code: 'auth_code',
|
||||
state: 'signed-state',
|
||||
});
|
||||
|
||||
expect(workspaceCacheService.getOrRecompute).not.toHaveBeenCalled();
|
||||
expect(messageQueueService.add).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('enqueues the declared on-connect logic function in the connecting workspace', async () => {
|
||||
connectionProviderService.findOneByIdOrThrow.mockResolvedValue({
|
||||
...baseProvider,
|
||||
onConnectLogicFunctionUniversalIdentifier: ON_CONNECT_UID,
|
||||
});
|
||||
workspaceCacheService.getOrRecompute.mockResolvedValue({
|
||||
flatLogicFunctionMaps: {
|
||||
byUniversalIdentifier: {
|
||||
[ON_CONNECT_UID]: { id: 'logic-function-1' },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.completeAuthorizationFlow({
|
||||
code: 'auth_code',
|
||||
state: 'signed-state',
|
||||
});
|
||||
|
||||
expect(workspaceCacheService.getOrRecompute).toHaveBeenCalledWith(
|
||||
'workspace-1',
|
||||
['flatLogicFunctionMaps'],
|
||||
);
|
||||
expect(messageQueueService.add).toHaveBeenCalledWith(
|
||||
'LogicFunctionTriggerJob',
|
||||
[
|
||||
{
|
||||
logicFunctionId: 'logic-function-1',
|
||||
workspaceId: 'workspace-1',
|
||||
payload: {
|
||||
connectionProviderId: 'provider-1',
|
||||
connectionProviderName: 'linear',
|
||||
connectedAccountId: result.connectedAccountId,
|
||||
},
|
||||
},
|
||||
],
|
||||
{ retryLimit: 3 },
|
||||
);
|
||||
});
|
||||
|
||||
it('reports to Sentry without failing the connection when the hook function is missing', async () => {
|
||||
connectionProviderService.findOneByIdOrThrow.mockResolvedValue({
|
||||
...baseProvider,
|
||||
onConnectLogicFunctionUniversalIdentifier: ON_CONNECT_UID,
|
||||
});
|
||||
workspaceCacheService.getOrRecompute.mockResolvedValue({
|
||||
flatLogicFunctionMaps: { byUniversalIdentifier: {} },
|
||||
});
|
||||
|
||||
const result = await service.completeAuthorizationFlow({
|
||||
code: 'auth_code',
|
||||
state: 'signed-state',
|
||||
});
|
||||
|
||||
expect(result.connectedAccountId).toBe('new-account-id');
|
||||
expect(messageQueueService.add).not.toHaveBeenCalled();
|
||||
expect(exceptionHandlerService.captureExceptions).toHaveBeenCalledTimes(
|
||||
1,
|
||||
);
|
||||
});
|
||||
|
||||
it('treats a soft-deleted hook function as missing instead of enqueuing it', async () => {
|
||||
connectionProviderService.findOneByIdOrThrow.mockResolvedValue({
|
||||
...baseProvider,
|
||||
onConnectLogicFunctionUniversalIdentifier: ON_CONNECT_UID,
|
||||
});
|
||||
workspaceCacheService.getOrRecompute.mockResolvedValue({
|
||||
flatLogicFunctionMaps: {
|
||||
byUniversalIdentifier: {
|
||||
[ON_CONNECT_UID]: {
|
||||
id: 'logic-function-1',
|
||||
deletedAt: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const result = await service.completeAuthorizationFlow({
|
||||
code: 'auth_code',
|
||||
state: 'signed-state',
|
||||
});
|
||||
|
||||
expect(result.connectedAccountId).toBe('new-account-id');
|
||||
expect(messageQueueService.add).not.toHaveBeenCalled();
|
||||
expect(exceptionHandlerService.captureExceptions).toHaveBeenCalledTimes(
|
||||
1,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+1
@@ -9,4 +9,5 @@ export enum ConnectionProviderExceptionCode {
|
||||
INVALID_CONNECTION_PROVIDER_INPUT = 'INVALID_CONNECTION_PROVIDER_INPUT',
|
||||
CONNECTION_PROVIDER_NOT_FOUND = 'CONNECTION_PROVIDER_NOT_FOUND',
|
||||
CONNECTION_PROVIDER_NAME_ALREADY_EXISTS = 'CONNECTION_PROVIDER_NAME_ALREADY_EXISTS',
|
||||
ON_CONNECT_LOGIC_FUNCTION_NOT_FOUND = 'ON_CONNECT_LOGIC_FUNCTION_NOT_FOUND',
|
||||
}
|
||||
|
||||
+80
@@ -21,11 +21,20 @@ import { exchangeCodeForToken } from 'src/engine/core-modules/application/connec
|
||||
import { generatePkceVerifier } from 'src/engine/core-modules/application/connection-provider/utils/generate-pkce-verifier.util';
|
||||
import { type AppOAuthStateJwtPayload } from 'src/engine/core-modules/auth/types/app-oauth-state-jwt-payload.type';
|
||||
import { JwtTokenTypeEnum } from 'src/engine/core-modules/auth/types/jwt-token-type.enum';
|
||||
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||
import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service';
|
||||
import {
|
||||
LogicFunctionTriggerJob,
|
||||
type LogicFunctionTriggerJobData,
|
||||
} from 'src/engine/core-modules/logic-function/logic-function-trigger/jobs/logic-function-trigger.job';
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||
import { SecureHttpClientService } from 'src/engine/core-modules/secure-http-client/secure-http-client.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { ConnectedAccountTokenEncryptionService } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.service';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
|
||||
const STATE_JWT_EXPIRES_IN = '10m';
|
||||
|
||||
@@ -63,6 +72,10 @@ export class ConnectionProviderOAuthFlowService {
|
||||
private readonly connectedAccountTokenEncryptionService: ConnectedAccountTokenEncryptionService,
|
||||
@InjectRepository(ConnectedAccountEntity)
|
||||
private readonly connectedAccountRepository: Repository<ConnectedAccountEntity>,
|
||||
@InjectMessageQueue(MessageQueue.logicFunctionQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||
) {}
|
||||
|
||||
async startAuthorizationFlow(
|
||||
@@ -186,6 +199,12 @@ export class ConnectionProviderOAuthFlowService {
|
||||
statePayload.reconnectingConnectedAccountId,
|
||||
});
|
||||
|
||||
await this.dispatchOnConnectHook({
|
||||
provider,
|
||||
workspaceId: statePayload.workspaceId,
|
||||
connectedAccountId: connectedAccount.id,
|
||||
});
|
||||
|
||||
return {
|
||||
connectedAccountId: connectedAccount.id,
|
||||
workspaceId: statePayload.workspaceId,
|
||||
@@ -194,6 +213,67 @@ export class ConnectionProviderOAuthFlowService {
|
||||
};
|
||||
}
|
||||
|
||||
private async dispatchOnConnectHook({
|
||||
provider,
|
||||
workspaceId,
|
||||
connectedAccountId,
|
||||
}: {
|
||||
provider: OAuthConnectionProvider;
|
||||
workspaceId: string;
|
||||
connectedAccountId: string;
|
||||
}): Promise<void> {
|
||||
const { onConnectLogicFunctionUniversalIdentifier } = provider;
|
||||
|
||||
if (!isDefined(onConnectLogicFunctionUniversalIdentifier)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The on-connect hook is best-effort: the ConnectedAccount is already
|
||||
// persisted, so a misconfigured or failing hook must not break the OAuth
|
||||
// callback. We still report failures to Sentry so they don't go unnoticed.
|
||||
try {
|
||||
const { flatLogicFunctionMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatLogicFunctionMaps',
|
||||
]);
|
||||
|
||||
const flatLogicFunction =
|
||||
flatLogicFunctionMaps.byUniversalIdentifier[
|
||||
onConnectLogicFunctionUniversalIdentifier
|
||||
];
|
||||
|
||||
if (
|
||||
!isDefined(flatLogicFunction) ||
|
||||
isDefined(flatLogicFunction.deletedAt)
|
||||
) {
|
||||
throw new ConnectionProviderException(
|
||||
`Connection provider ${provider.id} references on-connect logic function ${onConnectLogicFunctionUniversalIdentifier}, which was not found in workspace ${workspaceId}.`,
|
||||
ConnectionProviderExceptionCode.ON_CONNECT_LOGIC_FUNCTION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
await this.messageQueueService.add<LogicFunctionTriggerJobData[]>(
|
||||
LogicFunctionTriggerJob.name,
|
||||
[
|
||||
{
|
||||
logicFunctionId: flatLogicFunction.id,
|
||||
workspaceId,
|
||||
payload: {
|
||||
connectionProviderId: provider.id,
|
||||
connectionProviderName: provider.name,
|
||||
connectedAccountId,
|
||||
},
|
||||
},
|
||||
],
|
||||
{ retryLimit: 3 },
|
||||
);
|
||||
} catch (error) {
|
||||
this.exceptionHandlerService.captureExceptions([error], {
|
||||
workspace: { id: workspaceId },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async signState(payload: AppOAuthStateJwtPayload): Promise<string> {
|
||||
return this.jwtWrapperService.signAsyncOrThrow(payload, {
|
||||
expiresIn: STATE_JWT_EXPIRES_IN,
|
||||
|
||||
+8
@@ -12,6 +12,7 @@ import {
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
|
||||
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
|
||||
@Entity({ name: 'connectionProvider', schema: 'core' })
|
||||
@@ -39,6 +40,13 @@ export class ConnectionProviderEntity
|
||||
@Column({ nullable: true, type: 'jsonb' })
|
||||
oauthConfig: StoredOAuthConnectionProviderConfig | null;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName:
|
||||
'2.24.0_AddOnConnectLogicFunctionToConnectionProviderFastInstanceCommand_1784712843602',
|
||||
})
|
||||
onConnectLogicFunctionUniversalIdentifier: string | null;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt: Date;
|
||||
|
||||
|
||||
+2
@@ -29,6 +29,8 @@ const getConnectionProviderExceptionUserFriendlyMessage = (
|
||||
return msg`Connection provider not found.`;
|
||||
case ConnectionProviderExceptionCode.CONNECTION_PROVIDER_NAME_ALREADY_EXISTS:
|
||||
return msg`A connection provider with this name already exists for this application.`;
|
||||
case ConnectionProviderExceptionCode.ON_CONNECT_LOGIC_FUNCTION_NOT_FOUND:
|
||||
return msg`The logic function to run on connect was not found.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
|
||||
+2
@@ -14,6 +14,7 @@ import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty
|
||||
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
||||
import { ConnectedAccountTokenEncryptionModule } from 'src/engine/metadata-modules/connected-account/services/connected-account-token-encryption.module';
|
||||
import { FlatConnectionProviderModule } from 'src/engine/metadata-modules/flat-connection-provider/flat-connection-provider.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -29,6 +30,7 @@ import { FlatConnectionProviderModule } from 'src/engine/metadata-modules/flat-c
|
||||
TwentyConfigModule,
|
||||
FlatConnectionProviderModule,
|
||||
ConnectedAccountTokenEncryptionModule,
|
||||
WorkspaceCacheModule,
|
||||
],
|
||||
providers: [
|
||||
ConnectionProviderService,
|
||||
|
||||
+1
@@ -60,6 +60,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
|
||||
"displayName",
|
||||
"type",
|
||||
"oauthConfig",
|
||||
"onConnectLogicFunctionUniversalIdentifier",
|
||||
],
|
||||
"propertiesToStringify": [
|
||||
"oauthConfig",
|
||||
|
||||
+5
@@ -1802,6 +1802,11 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
|
||||
toStringify: true,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
onConnectLogicFunctionUniversalIdentifier: {
|
||||
toCompare: true,
|
||||
toStringify: false,
|
||||
universalProperty: undefined,
|
||||
},
|
||||
createdAt: {
|
||||
toCompare: false,
|
||||
toStringify: false,
|
||||
|
||||
+1
@@ -83,6 +83,7 @@ describe('Manifest update - connection providers', () => {
|
||||
tokenRequestContentType: 'json',
|
||||
usePkce: true,
|
||||
},
|
||||
onConnectLogicFunctionUniversalIdentifier: null,
|
||||
});
|
||||
}, 60000);
|
||||
|
||||
|
||||
+2
-1
@@ -9,6 +9,7 @@ type ConnectionProviderRow = {
|
||||
displayName: string;
|
||||
type: string;
|
||||
oauthConfig: StoredOAuthConnectionProviderConfig | null;
|
||||
onConnectLogicFunctionUniversalIdentifier: string | null;
|
||||
};
|
||||
|
||||
export const findConnectionProvidersByApplication = async (
|
||||
@@ -17,7 +18,7 @@ export const findConnectionProvidersByApplication = async (
|
||||
return globalThis.testDataSource.query(
|
||||
`SELECT cp.id, cp."universalIdentifier", cp."applicationId",
|
||||
cp."workspaceId", cp.name, cp."displayName", cp.type,
|
||||
cp."oauthConfig"
|
||||
cp."oauthConfig", cp."onConnectLogicFunctionUniversalIdentifier"
|
||||
FROM core."connectionProvider" cp
|
||||
JOIN core."application" app ON app.id = cp."applicationId"
|
||||
WHERE app."universalIdentifier" = $1
|
||||
|
||||
@@ -6,4 +6,5 @@ export type ConnectionProviderManifest = SyncableEntityOptions & {
|
||||
displayName: string;
|
||||
type: 'oauth';
|
||||
oauth: OAuthConnectionProviderConfig;
|
||||
onConnectLogicFunction?: SyncableEntityOptions;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user