Logic function refactorization (#17861)

As title
This commit is contained in:
martmull
2026-02-12 11:40:49 +01:00
committed by GitHub
parent b456f79167
commit a4ed043d43
122 changed files with 1441 additions and 1897 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "create-twenty-app",
"version": "0.5.0",
"version": "0.5.1",
"description": "Command-line interface to create Twenty application",
"main": "dist/cli.cjs",
"bin": "dist/cli.cjs",
@@ -70,7 +70,7 @@ describe('copyBaseApplicationProject', () => {
const packageJson = await fs.readJson(packageJsonPath);
expect(packageJson.name).toBe('my-test-app');
expect(packageJson.version).toBe('0.1.0');
expect(packageJson.dependencies['twenty-sdk']).toBe('0.5.0');
expect(packageJson.dependencies['twenty-sdk']).toBe('0.5.1');
expect(packageJson.scripts['app:dev']).toBe('twenty app:dev');
});
@@ -277,7 +277,7 @@ const createPackageJson = async ({
'lint:fix': 'eslint --fix',
},
dependencies: {
'twenty-sdk': '0.5.0',
'twenty-sdk': '0.5.1',
},
devDependencies: {
typescript: '^5.9.3',
@@ -987,14 +987,6 @@ export type CreateCommandMenuItemInput = {
workflowVersionId?: InputMaybe<Scalars['UUID']>;
};
export type CreateDefaultLogicFunctionInput = {
description?: InputMaybe<Scalars['String']>;
isTool?: InputMaybe<Scalars['Boolean']>;
name: Scalars['String'];
timeoutSeconds?: InputMaybe<Scalars['Float']>;
toolInputSchema?: InputMaybe<Scalars['JSON']>;
};
export type CreateFieldInput = {
defaultValue?: InputMaybe<Scalars['JSON']>;
description?: InputMaybe<Scalars['String']>;
@@ -1027,6 +1019,20 @@ export type CreateFrontComponentInput = {
sourceComponentPath: Scalars['String'];
};
export type CreateLogicFunctionFromSourceInput = {
cronTriggerSettings?: InputMaybe<Scalars['JSON']>;
databaseEventTriggerSettings?: InputMaybe<Scalars['JSON']>;
description?: InputMaybe<Scalars['String']>;
httpRouteTriggerSettings?: InputMaybe<Scalars['JSON']>;
id?: InputMaybe<Scalars['UUID']>;
isTool?: InputMaybe<Scalars['Boolean']>;
name: Scalars['String'];
source?: InputMaybe<Scalars['JSON']>;
timeoutSeconds?: InputMaybe<Scalars['Float']>;
toolInputSchema?: InputMaybe<Scalars['JSON']>;
universalIdentifier?: InputMaybe<Scalars['UUID']>;
};
export type CreateNavigationMenuItemInput = {
folderId?: InputMaybe<Scalars['UUID']>;
link?: InputMaybe<Scalars['String']>;
@@ -1444,8 +1450,6 @@ export type EventWithQueryIds = {
};
export type ExecuteOneLogicFunctionInput = {
/** Force rebuild from source before executing */
forceRebuild?: InputMaybe<Scalars['Boolean']>;
/** Id of the logic function to execute */
id: Scalars['UUID'];
/** Payload in JSON format */
@@ -1955,7 +1959,6 @@ export type Location = {
export type LogicFunction = {
__typename?: 'LogicFunction';
applicationId?: Maybe<Scalars['UUID']>;
builtHandlerPath: Scalars['String'];
createdAt: Scalars['DateTime'];
cronTriggerSettings?: Maybe<Scalars['JSON']>;
databaseEventTriggerSettings?: Maybe<Scalars['JSON']>;
@@ -2070,7 +2073,6 @@ export type Mutation = {
createCoreViewGroup: CoreViewGroup;
createCoreViewSort: CoreViewSort;
createDatabaseConfigVariable: Scalars['Boolean'];
createDefaultLogicFunction: LogicFunction;
createEmailingDomain: EmailingDomain;
createFile: File;
createFrontComponent: FrontComponent;
@@ -2084,6 +2086,7 @@ export type Mutation = {
createOneAppToken: AppToken;
createOneApplication: Application;
createOneField: Field;
createOneLogicFunction: LogicFunction;
createOneObject: Object;
createOneRole: Role;
createPageLayout: PageLayout;
@@ -2188,11 +2191,11 @@ export type Mutation = {
updateDatabaseConfigVariable: Scalars['Boolean'];
updateFrontComponent: FrontComponent;
updateLabPublicFeatureFlag: FeatureFlagDto;
updateLogicFunctionSource: Scalars['Boolean'];
updateNavigationMenuItem: NavigationMenuItem;
updateOneAgent: Agent;
updateOneApplicationVariable: Scalars['Boolean'];
updateOneField: Field;
updateOneLogicFunction: Scalars['Boolean'];
updateOneObject: Object;
updateOneRole: Role;
updatePageLayout: PageLayout;
@@ -2329,11 +2332,6 @@ export type MutationCreateDatabaseConfigVariableArgs = {
};
export type MutationCreateDefaultLogicFunctionArgs = {
input: CreateDefaultLogicFunctionInput;
};
export type MutationCreateEmailingDomainArgs = {
domain: Scalars['String'];
driver: EmailingDomainDriver;
@@ -2403,6 +2401,11 @@ export type MutationCreateOneFieldArgs = {
};
export type MutationCreateOneLogicFunctionArgs = {
input: CreateLogicFunctionFromSourceInput;
};
export type MutationCreateOneObjectArgs = {
input: CreateOneObjectInput;
};
@@ -2900,11 +2903,6 @@ export type MutationUpdateLabPublicFeatureFlagArgs = {
};
export type MutationUpdateLogicFunctionSourceArgs = {
input: UpdateLogicFunctionSourceInput;
};
export type MutationUpdateNavigationMenuItemArgs = {
input: UpdateOneNavigationMenuItemInput;
};
@@ -2927,6 +2925,11 @@ export type MutationUpdateOneFieldArgs = {
};
export type MutationUpdateOneLogicFunctionArgs = {
input: UpdateLogicFunctionFromSourceInput;
};
export type MutationUpdateOneObjectArgs = {
input: UpdateOneObjectInput;
};
@@ -3567,7 +3570,7 @@ export type Query = {
getDatabaseConfigVariable: ConfigVariable;
getEmailingDomains: Array<EmailingDomain>;
getIndicatorHealthStatus: AdminPanelHealthServiceData;
getLogicFunctionSourceCode?: Maybe<Scalars['JSON']>;
getLogicFunctionSourceCode?: Maybe<Scalars['String']>;
getMeteredProductsUsage: Array<BillingMeteredProductUsageOutput>;
getPageLayout?: Maybe<PageLayout>;
getPageLayoutTab: PageLayoutTab;
@@ -4395,11 +4398,27 @@ export type UpdateLabPublicFeatureFlagInput = {
value: Scalars['Boolean'];
};
export type UpdateLogicFunctionSourceInput = {
/** The source code (Sources) to write. Only updates source files. */
code: Scalars['JSON'];
/** The id of the logic function. */
export type UpdateLogicFunctionFromSourceInput = {
/** Id of the logic function to update */
id: Scalars['UUID'];
/** The logic function updates */
update: UpdateLogicFunctionFromSourceInputUpdates;
};
export type UpdateLogicFunctionFromSourceInputUpdates = {
checksum?: InputMaybe<Scalars['String']>;
cronTriggerSettings?: InputMaybe<Scalars['JSON']>;
databaseEventTriggerSettings?: InputMaybe<Scalars['JSON']>;
description?: InputMaybe<Scalars['String']>;
handlerName?: InputMaybe<Scalars['String']>;
httpRouteTriggerSettings?: InputMaybe<Scalars['JSON']>;
isBuildUpToDate?: InputMaybe<Scalars['Boolean']>;
isTool?: InputMaybe<Scalars['Boolean']>;
name?: InputMaybe<Scalars['String']>;
sourceHandlerCode?: InputMaybe<Scalars['String']>;
sourceHandlerPath?: InputMaybe<Scalars['String']>;
timeoutSeconds?: InputMaybe<Scalars['Float']>;
toolInputSchema?: InputMaybe<Scalars['JSON']>;
};
export type UpdateNavigationMenuItemInput = {
@@ -5240,7 +5259,7 @@ export type UpdateOneApplicationVariableMutationVariables = Exact<{
export type UpdateOneApplicationVariableMutation = { __typename?: 'Mutation', updateOneApplicationVariable: boolean };
export type ApplicationFieldsFragment = { __typename?: 'Application', id: string, name: string, description: string, version: string, universalIdentifier: string, canBeUninstalled: boolean, availablePackages: any, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array<string>, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, logicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, builtHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string }> };
export type ApplicationFieldsFragment = { __typename?: 'Application', id: string, name: string, description: string, version: string, universalIdentifier: string, canBeUninstalled: boolean, availablePackages: any, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array<string>, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, logicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string }> };
export type FindManyApplicationsQueryVariables = Exact<{ [key: string]: never; }>;
@@ -5252,7 +5271,7 @@ export type FindOneApplicationQueryVariables = Exact<{
}>;
export type FindOneApplicationQuery = { __typename?: 'Query', findOneApplication: { __typename?: 'Application', id: string, name: string, description: string, version: string, universalIdentifier: string, canBeUninstalled: boolean, availablePackages: any, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array<string>, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, logicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, builtHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string }> } };
export type FindOneApplicationQuery = { __typename?: 'Query', findOneApplication: { __typename?: 'Application', id: string, name: string, description: string, version: string, universalIdentifier: string, canBeUninstalled: boolean, availablePackages: any, applicationVariables: Array<{ __typename?: 'ApplicationVariable', id: string, key: string, value: string, description: string, isSecret: boolean }>, agents: Array<{ __typename?: 'Agent', id: string, name: string, label: string, description?: string | null, icon?: string | null, prompt: string, modelId: string, responseFormat?: any | null, roleId?: string | null, isCustom: boolean, modelConfiguration?: any | null, evaluationInputs: Array<string>, applicationId?: string | null, createdAt: string, updatedAt: string }>, objects: Array<{ __typename?: 'Object', id: string, nameSingular: string, namePlural: string, labelSingular: string, labelPlural: string, description?: string | null, icon?: string | null, isCustom: boolean, isRemote: boolean, isActive: boolean, isSystem: boolean, isUIReadOnly: boolean, createdAt: string, updatedAt: string, labelIdentifierFieldMetadataId?: string | null, imageIdentifierFieldMetadataId?: string | null, applicationId: string, shortcut?: string | null, isLabelSyncedWithName: boolean, isSearchable: boolean, duplicateCriteria?: Array<Array<string>> | null, indexMetadataList: Array<{ __typename?: 'Index', id: string, createdAt: string, updatedAt: string, name: string, indexWhereClause?: string | null, indexType: IndexType, isUnique: boolean, isCustom?: boolean | null, indexFieldMetadataList: Array<{ __typename?: 'IndexField', id: string, fieldMetadataId: string, createdAt: string, updatedAt: string, order: number }> }>, fieldsList: Array<{ __typename?: 'Field', id: string, type: FieldMetadataType, name: string, label: string, description?: string | null, icon?: string | null, isCustom?: boolean | null, isActive?: boolean | null, isSystem?: boolean | null, isUIReadOnly?: boolean | null, isNullable?: boolean | null, isUnique?: boolean | null, createdAt: string, updatedAt: string, defaultValue?: any | null, options?: any | null, settings?: any | null, isLabelSyncedWithName?: boolean | null, morphId?: string | null, applicationId: string, relation?: { __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } } | null, morphRelations?: Array<{ __typename?: 'Relation', type: RelationType, sourceObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, targetObjectMetadata: { __typename?: 'Object', id: string, nameSingular: string, namePlural: string }, sourceFieldMetadata: { __typename?: 'Field', id: string, name: string }, targetFieldMetadata: { __typename?: 'Field', id: string, name: string } }> | null }> }>, logicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string }> } };
export type UploadFileMutationVariables = Exact<{
file: Scalars['Upload'];
@@ -5595,6 +5614,22 @@ export type UploadFilesFieldFileMutationVariables = Exact<{
export type UploadFilesFieldFileMutation = { __typename?: 'Mutation', uploadFilesFieldFile: { __typename?: 'File', id: string, path: string, size: number, createdAt: string } };
export type LogicFunctionFieldsFragment = { __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string };
export type CreateOneLogicFunctionMutationVariables = Exact<{
input: CreateLogicFunctionFromSourceInput;
}>;
export type CreateOneLogicFunctionMutation = { __typename?: 'Mutation', createOneLogicFunction: { __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string } };
export type DeleteOneLogicFunctionMutationVariables = Exact<{
input: LogicFunctionIdInput;
}>;
export type DeleteOneLogicFunctionMutation = { __typename?: 'Mutation', deleteOneLogicFunction: { __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string } };
export type ExecuteOneLogicFunctionMutationVariables = Exact<{
input: ExecuteOneLogicFunctionInput;
}>;
@@ -5602,19 +5637,38 @@ export type ExecuteOneLogicFunctionMutationVariables = Exact<{
export type ExecuteOneLogicFunctionMutation = { __typename?: 'Mutation', executeOneLogicFunction: { __typename?: 'LogicFunctionExecutionResult', data?: any | null, logs: string, duration: number, status: LogicFunctionExecutionStatus, error?: any | null } };
export type UpdateLogicFunctionSourceMutationVariables = Exact<{
input: UpdateLogicFunctionSourceInput;
export type UpdateOneLogicFunctionMutationVariables = Exact<{
input: UpdateLogicFunctionFromSourceInput;
}>;
export type UpdateLogicFunctionSourceMutation = { __typename?: 'Mutation', updateLogicFunctionSource: boolean };
export type UpdateOneLogicFunctionMutation = { __typename?: 'Mutation', updateOneLogicFunction: boolean };
export type FindManyAvailablePackagesQueryVariables = Exact<{
input: LogicFunctionIdInput;
}>;
export type FindManyAvailablePackagesQuery = { __typename?: 'Query', getAvailablePackages: any };
export type FindManyLogicFunctionsQueryVariables = Exact<{ [key: string]: never; }>;
export type FindManyLogicFunctionsQuery = { __typename?: 'Query', findManyLogicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string }> };
export type FindOneLogicFunctionQueryVariables = Exact<{
input: LogicFunctionIdInput;
}>;
export type FindOneLogicFunctionQuery = { __typename?: 'Query', findOneLogicFunction: { __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string } };
export type GetLogicFunctionSourceCodeQueryVariables = Exact<{
input: LogicFunctionIdInput;
}>;
export type GetLogicFunctionSourceCodeQuery = { __typename?: 'Query', getLogicFunctionSourceCode?: any | null };
export type GetLogicFunctionSourceCodeQuery = { __typename?: 'Query', getLogicFunctionSourceCode?: string | null };
export type FindManyMarketplaceAppsQueryVariables = Exact<{ [key: string]: never; }>;
@@ -6026,41 +6080,6 @@ export type UpdateLabPublicFeatureFlagMutationVariables = Exact<{
export type UpdateLabPublicFeatureFlagMutation = { __typename?: 'Mutation', updateLabPublicFeatureFlag: { __typename?: 'FeatureFlagDTO', key: FeatureFlagKey, value: boolean } };
export type LogicFunctionFieldsFragment = { __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, builtHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string };
export type CreateDefaultLogicFunctionItemMutationVariables = Exact<{
input: CreateDefaultLogicFunctionInput;
}>;
export type CreateDefaultLogicFunctionItemMutation = { __typename?: 'Mutation', createDefaultLogicFunction: { __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, builtHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string } };
export type DeleteOneLogicFunctionMutationVariables = Exact<{
input: LogicFunctionIdInput;
}>;
export type DeleteOneLogicFunctionMutation = { __typename?: 'Mutation', deleteOneLogicFunction: { __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, builtHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string } };
export type FindManyAvailablePackagesQueryVariables = Exact<{
input: LogicFunctionIdInput;
}>;
export type FindManyAvailablePackagesQuery = { __typename?: 'Query', getAvailablePackages: any };
export type GetManyLogicFunctionsQueryVariables = Exact<{ [key: string]: never; }>;
export type GetManyLogicFunctionsQuery = { __typename?: 'Query', findManyLogicFunctions: Array<{ __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, builtHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string }> };
export type GetOneLogicFunctionQueryVariables = Exact<{
input: LogicFunctionIdInput;
}>;
export type GetOneLogicFunctionQuery = { __typename?: 'Query', findOneLogicFunction: { __typename?: 'LogicFunction', id: string, name: string, description?: string | null, runtime: string, timeoutSeconds: number, sourceHandlerPath: string, builtHandlerPath: string, handlerName: string, toolInputSchema?: any | null, isTool: boolean, applicationId?: string | null, createdAt: string, updatedAt: string } };
export type UploadWorkspaceMemberProfilePictureMutationVariables = Exact<{
file: Scalars['Upload'];
}>;
@@ -6751,7 +6770,6 @@ export const LogicFunctionFieldsFragmentDoc = gql`
runtime
timeoutSeconds
sourceHandlerPath
builtHandlerPath
handlerName
toolInputSchema
isTool
@@ -10171,6 +10189,72 @@ export function useUploadFilesFieldFileMutation(baseOptions?: Apollo.MutationHoo
export type UploadFilesFieldFileMutationHookResult = ReturnType<typeof useUploadFilesFieldFileMutation>;
export type UploadFilesFieldFileMutationResult = Apollo.MutationResult<UploadFilesFieldFileMutation>;
export type UploadFilesFieldFileMutationOptions = Apollo.BaseMutationOptions<UploadFilesFieldFileMutation, UploadFilesFieldFileMutationVariables>;
export const CreateOneLogicFunctionDocument = gql`
mutation CreateOneLogicFunction($input: CreateLogicFunctionFromSourceInput!) {
createOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
${LogicFunctionFieldsFragmentDoc}`;
export type CreateOneLogicFunctionMutationFn = Apollo.MutationFunction<CreateOneLogicFunctionMutation, CreateOneLogicFunctionMutationVariables>;
/**
* __useCreateOneLogicFunctionMutation__
*
* To run a mutation, you first call `useCreateOneLogicFunctionMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateOneLogicFunctionMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createOneLogicFunctionMutation, { data, loading, error }] = useCreateOneLogicFunctionMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useCreateOneLogicFunctionMutation(baseOptions?: Apollo.MutationHookOptions<CreateOneLogicFunctionMutation, CreateOneLogicFunctionMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateOneLogicFunctionMutation, CreateOneLogicFunctionMutationVariables>(CreateOneLogicFunctionDocument, options);
}
export type CreateOneLogicFunctionMutationHookResult = ReturnType<typeof useCreateOneLogicFunctionMutation>;
export type CreateOneLogicFunctionMutationResult = Apollo.MutationResult<CreateOneLogicFunctionMutation>;
export type CreateOneLogicFunctionMutationOptions = Apollo.BaseMutationOptions<CreateOneLogicFunctionMutation, CreateOneLogicFunctionMutationVariables>;
export const DeleteOneLogicFunctionDocument = gql`
mutation DeleteOneLogicFunction($input: LogicFunctionIdInput!) {
deleteOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
${LogicFunctionFieldsFragmentDoc}`;
export type DeleteOneLogicFunctionMutationFn = Apollo.MutationFunction<DeleteOneLogicFunctionMutation, DeleteOneLogicFunctionMutationVariables>;
/**
* __useDeleteOneLogicFunctionMutation__
*
* To run a mutation, you first call `useDeleteOneLogicFunctionMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteOneLogicFunctionMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [deleteOneLogicFunctionMutation, { data, loading, error }] = useDeleteOneLogicFunctionMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useDeleteOneLogicFunctionMutation(baseOptions?: Apollo.MutationHookOptions<DeleteOneLogicFunctionMutation, DeleteOneLogicFunctionMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<DeleteOneLogicFunctionMutation, DeleteOneLogicFunctionMutationVariables>(DeleteOneLogicFunctionDocument, options);
}
export type DeleteOneLogicFunctionMutationHookResult = ReturnType<typeof useDeleteOneLogicFunctionMutation>;
export type DeleteOneLogicFunctionMutationResult = Apollo.MutationResult<DeleteOneLogicFunctionMutation>;
export type DeleteOneLogicFunctionMutationOptions = Apollo.BaseMutationOptions<DeleteOneLogicFunctionMutation, DeleteOneLogicFunctionMutationVariables>;
export const ExecuteOneLogicFunctionDocument = gql`
mutation ExecuteOneLogicFunction($input: ExecuteOneLogicFunctionInput!) {
executeOneLogicFunction(input: $input) {
@@ -10208,37 +10292,139 @@ export function useExecuteOneLogicFunctionMutation(baseOptions?: Apollo.Mutation
export type ExecuteOneLogicFunctionMutationHookResult = ReturnType<typeof useExecuteOneLogicFunctionMutation>;
export type ExecuteOneLogicFunctionMutationResult = Apollo.MutationResult<ExecuteOneLogicFunctionMutation>;
export type ExecuteOneLogicFunctionMutationOptions = Apollo.BaseMutationOptions<ExecuteOneLogicFunctionMutation, ExecuteOneLogicFunctionMutationVariables>;
export const UpdateLogicFunctionSourceDocument = gql`
mutation UpdateLogicFunctionSource($input: UpdateLogicFunctionSourceInput!) {
updateLogicFunctionSource(input: $input)
export const UpdateOneLogicFunctionDocument = gql`
mutation UpdateOneLogicFunction($input: UpdateLogicFunctionFromSourceInput!) {
updateOneLogicFunction(input: $input)
}
`;
export type UpdateLogicFunctionSourceMutationFn = Apollo.MutationFunction<UpdateLogicFunctionSourceMutation, UpdateLogicFunctionSourceMutationVariables>;
export type UpdateOneLogicFunctionMutationFn = Apollo.MutationFunction<UpdateOneLogicFunctionMutation, UpdateOneLogicFunctionMutationVariables>;
/**
* __useUpdateLogicFunctionSourceMutation__
* __useUpdateOneLogicFunctionMutation__
*
* To run a mutation, you first call `useUpdateLogicFunctionSourceMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateLogicFunctionSourceMutation` returns a tuple that includes:
* To run a mutation, you first call `useUpdateOneLogicFunctionMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useUpdateOneLogicFunctionMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [updateLogicFunctionSourceMutation, { data, loading, error }] = useUpdateLogicFunctionSourceMutation({
* const [updateOneLogicFunctionMutation, { data, loading, error }] = useUpdateOneLogicFunctionMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useUpdateLogicFunctionSourceMutation(baseOptions?: Apollo.MutationHookOptions<UpdateLogicFunctionSourceMutation, UpdateLogicFunctionSourceMutationVariables>) {
export function useUpdateOneLogicFunctionMutation(baseOptions?: Apollo.MutationHookOptions<UpdateOneLogicFunctionMutation, UpdateOneLogicFunctionMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<UpdateLogicFunctionSourceMutation, UpdateLogicFunctionSourceMutationVariables>(UpdateLogicFunctionSourceDocument, options);
return Apollo.useMutation<UpdateOneLogicFunctionMutation, UpdateOneLogicFunctionMutationVariables>(UpdateOneLogicFunctionDocument, options);
}
export type UpdateLogicFunctionSourceMutationHookResult = ReturnType<typeof useUpdateLogicFunctionSourceMutation>;
export type UpdateLogicFunctionSourceMutationResult = Apollo.MutationResult<UpdateLogicFunctionSourceMutation>;
export type UpdateLogicFunctionSourceMutationOptions = Apollo.BaseMutationOptions<UpdateLogicFunctionSourceMutation, UpdateLogicFunctionSourceMutationVariables>;
export type UpdateOneLogicFunctionMutationHookResult = ReturnType<typeof useUpdateOneLogicFunctionMutation>;
export type UpdateOneLogicFunctionMutationResult = Apollo.MutationResult<UpdateOneLogicFunctionMutation>;
export type UpdateOneLogicFunctionMutationOptions = Apollo.BaseMutationOptions<UpdateOneLogicFunctionMutation, UpdateOneLogicFunctionMutationVariables>;
export const FindManyAvailablePackagesDocument = gql`
query FindManyAvailablePackages($input: LogicFunctionIdInput!) {
getAvailablePackages(input: $input)
}
`;
/**
* __useFindManyAvailablePackagesQuery__
*
* To run a query within a React component, call `useFindManyAvailablePackagesQuery` and pass it any options that fit your needs.
* When your component renders, `useFindManyAvailablePackagesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useFindManyAvailablePackagesQuery({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useFindManyAvailablePackagesQuery(baseOptions: Apollo.QueryHookOptions<FindManyAvailablePackagesQuery, FindManyAvailablePackagesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<FindManyAvailablePackagesQuery, FindManyAvailablePackagesQueryVariables>(FindManyAvailablePackagesDocument, options);
}
export function useFindManyAvailablePackagesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindManyAvailablePackagesQuery, FindManyAvailablePackagesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<FindManyAvailablePackagesQuery, FindManyAvailablePackagesQueryVariables>(FindManyAvailablePackagesDocument, options);
}
export type FindManyAvailablePackagesQueryHookResult = ReturnType<typeof useFindManyAvailablePackagesQuery>;
export type FindManyAvailablePackagesLazyQueryHookResult = ReturnType<typeof useFindManyAvailablePackagesLazyQuery>;
export type FindManyAvailablePackagesQueryResult = Apollo.QueryResult<FindManyAvailablePackagesQuery, FindManyAvailablePackagesQueryVariables>;
export const FindManyLogicFunctionsDocument = gql`
query FindManyLogicFunctions {
findManyLogicFunctions {
...LogicFunctionFields
}
}
${LogicFunctionFieldsFragmentDoc}`;
/**
* __useFindManyLogicFunctionsQuery__
*
* To run a query within a React component, call `useFindManyLogicFunctionsQuery` and pass it any options that fit your needs.
* When your component renders, `useFindManyLogicFunctionsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useFindManyLogicFunctionsQuery({
* variables: {
* },
* });
*/
export function useFindManyLogicFunctionsQuery(baseOptions?: Apollo.QueryHookOptions<FindManyLogicFunctionsQuery, FindManyLogicFunctionsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<FindManyLogicFunctionsQuery, FindManyLogicFunctionsQueryVariables>(FindManyLogicFunctionsDocument, options);
}
export function useFindManyLogicFunctionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindManyLogicFunctionsQuery, FindManyLogicFunctionsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<FindManyLogicFunctionsQuery, FindManyLogicFunctionsQueryVariables>(FindManyLogicFunctionsDocument, options);
}
export type FindManyLogicFunctionsQueryHookResult = ReturnType<typeof useFindManyLogicFunctionsQuery>;
export type FindManyLogicFunctionsLazyQueryHookResult = ReturnType<typeof useFindManyLogicFunctionsLazyQuery>;
export type FindManyLogicFunctionsQueryResult = Apollo.QueryResult<FindManyLogicFunctionsQuery, FindManyLogicFunctionsQueryVariables>;
export const FindOneLogicFunctionDocument = gql`
query FindOneLogicFunction($input: LogicFunctionIdInput!) {
findOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
${LogicFunctionFieldsFragmentDoc}`;
/**
* __useFindOneLogicFunctionQuery__
*
* To run a query within a React component, call `useFindOneLogicFunctionQuery` and pass it any options that fit your needs.
* When your component renders, `useFindOneLogicFunctionQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useFindOneLogicFunctionQuery({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useFindOneLogicFunctionQuery(baseOptions: Apollo.QueryHookOptions<FindOneLogicFunctionQuery, FindOneLogicFunctionQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<FindOneLogicFunctionQuery, FindOneLogicFunctionQueryVariables>(FindOneLogicFunctionDocument, options);
}
export function useFindOneLogicFunctionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindOneLogicFunctionQuery, FindOneLogicFunctionQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<FindOneLogicFunctionQuery, FindOneLogicFunctionQueryVariables>(FindOneLogicFunctionDocument, options);
}
export type FindOneLogicFunctionQueryHookResult = ReturnType<typeof useFindOneLogicFunctionQuery>;
export type FindOneLogicFunctionLazyQueryHookResult = ReturnType<typeof useFindOneLogicFunctionLazyQuery>;
export type FindOneLogicFunctionQueryResult = Apollo.QueryResult<FindOneLogicFunctionQuery, FindOneLogicFunctionQueryVariables>;
export const GetLogicFunctionSourceCodeDocument = gql`
query GetLogicFunctionSourceCode($input: LogicFunctionIdInput!) {
getLogicFunctionSourceCode(input: $input)
@@ -12583,174 +12769,6 @@ export function useUpdateLabPublicFeatureFlagMutation(baseOptions?: Apollo.Mutat
export type UpdateLabPublicFeatureFlagMutationHookResult = ReturnType<typeof useUpdateLabPublicFeatureFlagMutation>;
export type UpdateLabPublicFeatureFlagMutationResult = Apollo.MutationResult<UpdateLabPublicFeatureFlagMutation>;
export type UpdateLabPublicFeatureFlagMutationOptions = Apollo.BaseMutationOptions<UpdateLabPublicFeatureFlagMutation, UpdateLabPublicFeatureFlagMutationVariables>;
export const CreateDefaultLogicFunctionItemDocument = gql`
mutation CreateDefaultLogicFunctionItem($input: CreateDefaultLogicFunctionInput!) {
createDefaultLogicFunction(input: $input) {
...LogicFunctionFields
}
}
${LogicFunctionFieldsFragmentDoc}`;
export type CreateDefaultLogicFunctionItemMutationFn = Apollo.MutationFunction<CreateDefaultLogicFunctionItemMutation, CreateDefaultLogicFunctionItemMutationVariables>;
/**
* __useCreateDefaultLogicFunctionItemMutation__
*
* To run a mutation, you first call `useCreateDefaultLogicFunctionItemMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useCreateDefaultLogicFunctionItemMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [createDefaultLogicFunctionItemMutation, { data, loading, error }] = useCreateDefaultLogicFunctionItemMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useCreateDefaultLogicFunctionItemMutation(baseOptions?: Apollo.MutationHookOptions<CreateDefaultLogicFunctionItemMutation, CreateDefaultLogicFunctionItemMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<CreateDefaultLogicFunctionItemMutation, CreateDefaultLogicFunctionItemMutationVariables>(CreateDefaultLogicFunctionItemDocument, options);
}
export type CreateDefaultLogicFunctionItemMutationHookResult = ReturnType<typeof useCreateDefaultLogicFunctionItemMutation>;
export type CreateDefaultLogicFunctionItemMutationResult = Apollo.MutationResult<CreateDefaultLogicFunctionItemMutation>;
export type CreateDefaultLogicFunctionItemMutationOptions = Apollo.BaseMutationOptions<CreateDefaultLogicFunctionItemMutation, CreateDefaultLogicFunctionItemMutationVariables>;
export const DeleteOneLogicFunctionDocument = gql`
mutation DeleteOneLogicFunction($input: LogicFunctionIdInput!) {
deleteOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
${LogicFunctionFieldsFragmentDoc}`;
export type DeleteOneLogicFunctionMutationFn = Apollo.MutationFunction<DeleteOneLogicFunctionMutation, DeleteOneLogicFunctionMutationVariables>;
/**
* __useDeleteOneLogicFunctionMutation__
*
* To run a mutation, you first call `useDeleteOneLogicFunctionMutation` within a React component and pass it any options that fit your needs.
* When your component renders, `useDeleteOneLogicFunctionMutation` returns a tuple that includes:
* - A mutate function that you can call at any time to execute the mutation
* - An object with fields that represent the current status of the mutation's execution
*
* @param baseOptions options that will be passed into the mutation, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options-2;
*
* @example
* const [deleteOneLogicFunctionMutation, { data, loading, error }] = useDeleteOneLogicFunctionMutation({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useDeleteOneLogicFunctionMutation(baseOptions?: Apollo.MutationHookOptions<DeleteOneLogicFunctionMutation, DeleteOneLogicFunctionMutationVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useMutation<DeleteOneLogicFunctionMutation, DeleteOneLogicFunctionMutationVariables>(DeleteOneLogicFunctionDocument, options);
}
export type DeleteOneLogicFunctionMutationHookResult = ReturnType<typeof useDeleteOneLogicFunctionMutation>;
export type DeleteOneLogicFunctionMutationResult = Apollo.MutationResult<DeleteOneLogicFunctionMutation>;
export type DeleteOneLogicFunctionMutationOptions = Apollo.BaseMutationOptions<DeleteOneLogicFunctionMutation, DeleteOneLogicFunctionMutationVariables>;
export const FindManyAvailablePackagesDocument = gql`
query FindManyAvailablePackages($input: LogicFunctionIdInput!) {
getAvailablePackages(input: $input)
}
`;
/**
* __useFindManyAvailablePackagesQuery__
*
* To run a query within a React component, call `useFindManyAvailablePackagesQuery` and pass it any options that fit your needs.
* When your component renders, `useFindManyAvailablePackagesQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useFindManyAvailablePackagesQuery({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useFindManyAvailablePackagesQuery(baseOptions: Apollo.QueryHookOptions<FindManyAvailablePackagesQuery, FindManyAvailablePackagesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<FindManyAvailablePackagesQuery, FindManyAvailablePackagesQueryVariables>(FindManyAvailablePackagesDocument, options);
}
export function useFindManyAvailablePackagesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<FindManyAvailablePackagesQuery, FindManyAvailablePackagesQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<FindManyAvailablePackagesQuery, FindManyAvailablePackagesQueryVariables>(FindManyAvailablePackagesDocument, options);
}
export type FindManyAvailablePackagesQueryHookResult = ReturnType<typeof useFindManyAvailablePackagesQuery>;
export type FindManyAvailablePackagesLazyQueryHookResult = ReturnType<typeof useFindManyAvailablePackagesLazyQuery>;
export type FindManyAvailablePackagesQueryResult = Apollo.QueryResult<FindManyAvailablePackagesQuery, FindManyAvailablePackagesQueryVariables>;
export const GetManyLogicFunctionsDocument = gql`
query GetManyLogicFunctions {
findManyLogicFunctions {
...LogicFunctionFields
}
}
${LogicFunctionFieldsFragmentDoc}`;
/**
* __useGetManyLogicFunctionsQuery__
*
* To run a query within a React component, call `useGetManyLogicFunctionsQuery` and pass it any options that fit your needs.
* When your component renders, `useGetManyLogicFunctionsQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetManyLogicFunctionsQuery({
* variables: {
* },
* });
*/
export function useGetManyLogicFunctionsQuery(baseOptions?: Apollo.QueryHookOptions<GetManyLogicFunctionsQuery, GetManyLogicFunctionsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetManyLogicFunctionsQuery, GetManyLogicFunctionsQueryVariables>(GetManyLogicFunctionsDocument, options);
}
export function useGetManyLogicFunctionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetManyLogicFunctionsQuery, GetManyLogicFunctionsQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetManyLogicFunctionsQuery, GetManyLogicFunctionsQueryVariables>(GetManyLogicFunctionsDocument, options);
}
export type GetManyLogicFunctionsQueryHookResult = ReturnType<typeof useGetManyLogicFunctionsQuery>;
export type GetManyLogicFunctionsLazyQueryHookResult = ReturnType<typeof useGetManyLogicFunctionsLazyQuery>;
export type GetManyLogicFunctionsQueryResult = Apollo.QueryResult<GetManyLogicFunctionsQuery, GetManyLogicFunctionsQueryVariables>;
export const GetOneLogicFunctionDocument = gql`
query GetOneLogicFunction($input: LogicFunctionIdInput!) {
findOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
${LogicFunctionFieldsFragmentDoc}`;
/**
* __useGetOneLogicFunctionQuery__
*
* To run a query within a React component, call `useGetOneLogicFunctionQuery` and pass it any options that fit your needs.
* When your component renders, `useGetOneLogicFunctionQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useGetOneLogicFunctionQuery({
* variables: {
* input: // value for 'input'
* },
* });
*/
export function useGetOneLogicFunctionQuery(baseOptions: Apollo.QueryHookOptions<GetOneLogicFunctionQuery, GetOneLogicFunctionQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GetOneLogicFunctionQuery, GetOneLogicFunctionQueryVariables>(GetOneLogicFunctionDocument, options);
}
export function useGetOneLogicFunctionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GetOneLogicFunctionQuery, GetOneLogicFunctionQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GetOneLogicFunctionQuery, GetOneLogicFunctionQueryVariables>(GetOneLogicFunctionDocument, options);
}
export type GetOneLogicFunctionQueryHookResult = ReturnType<typeof useGetOneLogicFunctionQuery>;
export type GetOneLogicFunctionLazyQueryHookResult = ReturnType<typeof useGetOneLogicFunctionLazyQuery>;
export type GetOneLogicFunctionQueryResult = Apollo.QueryResult<GetOneLogicFunctionQuery, GetOneLogicFunctionQueryVariables>;
export const UploadWorkspaceMemberProfilePictureDocument = gql`
mutation UploadWorkspaceMemberProfilePicture($file: Upload!) {
uploadWorkspaceMemberProfilePicture(file: $file) {
@@ -1,6 +1,6 @@
import { AGENT_FRAGMENT } from '@/ai/graphql/fragments/agentFragment';
import { OBJECT_METADATA_FRAGMENT } from '@/object-metadata/graphql/fragment';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
import { LOGIC_FUNCTION_FRAGMENT } from '@/logic-functions/graphql/fragments/logicFunctionFragment';
import { gql } from '@apollo/client';
export const APPLICATION_FRAGMENT = gql`
@@ -1 +0,0 @@
export const INDEX_FILE_NAME = 'index.ts';
@@ -1 +0,0 @@
export const SOURCE_FOLDER_NAME = 'src';
@@ -8,7 +8,6 @@ export const LOGIC_FUNCTION_FRAGMENT = gql`
runtime
timeoutSeconds
sourceHandlerPath
builtHandlerPath
handlerName
toolInputSchema
isTool
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/logic-functions/graphql/fragments/logicFunctionFragment';
export const CREATE_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
mutation CreateOneLogicFunction($input: CreateLogicFunctionFromSourceInput!) {
createOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -1,5 +1,5 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
import { LOGIC_FUNCTION_FRAGMENT } from '@/logic-functions/graphql/fragments/logicFunctionFragment';
export const DELETE_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
@@ -1,7 +0,0 @@
import { gql } from '@apollo/client';
export const UPDATE_LOGIC_FUNCTION_SOURCE = gql`
mutation UpdateLogicFunctionSource($input: UpdateLogicFunctionSourceInput!) {
updateLogicFunctionSource(input: $input)
}
`;
@@ -0,0 +1,7 @@
import { gql } from '@apollo/client';
export const UPDATE_ONE_LOGIC_FUNCTION = gql`
mutation UpdateOneLogicFunction($input: UpdateLogicFunctionFromSourceInput!) {
updateOneLogicFunction(input: $input)
}
`;
@@ -1,9 +1,9 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
import { LOGIC_FUNCTION_FRAGMENT } from '@/logic-functions/graphql/fragments/logicFunctionFragment';
export const FIND_MANY_LOGIC_FUNCTIONS = gql`
${LOGIC_FUNCTION_FRAGMENT}
query GetManyLogicFunctions {
query FindManyLogicFunctions {
findManyLogicFunctions {
...LogicFunctionFields
}
@@ -1,9 +1,9 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
import { LOGIC_FUNCTION_FRAGMENT } from '@/logic-functions/graphql/fragments/logicFunctionFragment';
export const FIND_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
query GetOneLogicFunction($input: LogicFunctionIdInput!) {
query FindOneLogicFunction($input: LogicFunctionIdInput!) {
findOneLogicFunction(input: $input) {
...LogicFunctionFields
}
@@ -1,21 +1,36 @@
import { useLogicFunctionUpdateFormState } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { useLogicFunctionUpdateFormState } from '@/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
jest.mock('@/settings/logic-functions/hooks/useGetOneLogicFunction', () => ({
jest.mock('@/logic-functions/hooks/useGetOneLogicFunction', () => ({
useGetOneLogicFunction: jest.fn(),
}));
jest.mock('@/logic-functions/hooks/useGetLogicFunctionSourceCode', () => ({
useGetLogicFunctionSourceCode: jest.fn(),
}));
const mockCode = 'export const main = async (): Promise<void> => { return; }';
describe('useLogicFunctionUpdateFormState', () => {
test('should return a form', () => {
const logicFunctionId = 'logicFunctionId';
const useGetOneLogicFunctionMock = jest.requireMock(
'@/settings/logic-functions/hooks/useGetOneLogicFunction',
'@/logic-functions/hooks/useGetOneLogicFunction',
);
const useGetLogicFunctionSourceCodeMock = jest.requireMock(
'@/logic-functions/hooks/useGetLogicFunctionSourceCode',
);
useGetOneLogicFunctionMock.useGetOneLogicFunction.mockReturnValue({
logicFunction: { name: 'name' },
loading: false,
});
useGetLogicFunctionSourceCodeMock.useGetLogicFunctionSourceCode.mockReturnValue(
{
code: mockCode,
loading: false,
},
);
const { result } = renderHook(
() => useLogicFunctionUpdateFormState({ logicFunctionId }),
{
@@ -28,7 +43,7 @@ describe('useLogicFunctionUpdateFormState', () => {
expect(formValues).toEqual({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
code: mockCode,
});
});
});
@@ -1,5 +1,4 @@
import { EXECUTE_ONE_LOGIC_FUNCTION } from '@/logic-functions/graphql/mutations/executeOneLogicFunction';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { logicFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import { useMutation } from '@apollo/client';
import { useState } from 'react';
@@ -11,7 +10,6 @@ import { sleep } from '~/utils/sleep';
type ExecuteOneLogicFunctionInput = {
id: string;
payload: object;
forceRebuild?: boolean;
};
type ExecuteOneLogicFunctionResult = {
@@ -34,21 +32,16 @@ export const useExecuteLogicFunction = ({
callback?: (result: object) => void;
}) => {
const [isExecuting, setIsExecuting] = useState(false);
const apolloMetadataClient = useApolloCoreClient();
const [executeOneLogicFunctionMutation] = useMutation<
{ executeOneLogicFunction: ExecuteOneLogicFunctionResult },
{ input: ExecuteOneLogicFunctionInput }
>(EXECUTE_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
>(EXECUTE_ONE_LOGIC_FUNCTION);
const [logicFunctionTestData, setLogicFunctionTestData] = useRecoilState(
logicFunctionTestDataFamilyState(logicFunctionId),
);
const executeLogicFunction = async ({
forceRebuild = false,
}: { forceRebuild?: boolean } = {}) => {
const executeLogicFunction = async () => {
try {
setIsExecuting(true);
await sleep(200); // Delay artificially to avoid flashing the UI
@@ -57,7 +50,6 @@ export const useExecuteLogicFunction = ({
input: {
id: logicFunctionId,
payload: logicFunctionTestData.input,
forceRebuild,
},
},
});
@@ -1,6 +1,5 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useQuery } from '@apollo/client';
import { FIND_MANY_AVAILABLE_PACKAGES } from '@/settings/logic-functions/graphql/queries/findManyAvailablePackages';
import { FIND_MANY_AVAILABLE_PACKAGES } from '@/logic-functions/graphql/queries/findManyAvailablePackages';
import {
type FindManyAvailablePackagesQuery,
type FindManyAvailablePackagesQueryVariables,
@@ -8,12 +7,10 @@ import {
} from '~/generated-metadata/graphql';
export const useGetAvailablePackages = (input: LogicFunctionIdInput) => {
const apolloMetadataClient = useApolloCoreClient();
const { data } = useQuery<
FindManyAvailablePackagesQuery,
FindManyAvailablePackagesQueryVariables
>(FIND_MANY_AVAILABLE_PACKAGES, {
client: apolloMetadataClient ?? undefined,
variables: {
input,
},
@@ -1,7 +1,5 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { GET_LOGIC_FUNCTION_SOURCE_CODE } from '@/logic-functions/graphql/queries/getLogicFunctionSourceCode';
import { useQuery } from '@apollo/client';
import { type Sources } from 'twenty-shared/types';
import {
type GetLogicFunctionSourceCodeQuery,
type GetLogicFunctionSourceCodeQueryVariables,
@@ -11,24 +9,16 @@ export const useGetLogicFunctionSourceCode = ({
logicFunctionId,
}: {
logicFunctionId: string;
}): { code: Sources | null; loading: boolean } => {
const apolloMetadataClient = useApolloCoreClient();
}) => {
const { data, loading } = useQuery<
GetLogicFunctionSourceCodeQuery,
GetLogicFunctionSourceCodeQueryVariables
>(GET_LOGIC_FUNCTION_SOURCE_CODE, {
client: apolloMetadataClient ?? undefined,
variables: {
input: { id: logicFunctionId },
},
skip: !logicFunctionId,
});
const raw = data?.getLogicFunctionSourceCode;
const code =
raw != null && typeof raw === 'object' && !Array.isArray(raw)
? (raw as Sources)
: null;
return { code, loading };
return { code: data?.getLogicFunctionSourceCode, loading };
};
@@ -0,0 +1,28 @@
import { FIND_ONE_LOGIC_FUNCTION } from '@/logic-functions/graphql/queries/findOneLogicFunction';
import { useQuery } from '@apollo/client';
import {
type FindOneLogicFunctionQuery,
type FindOneLogicFunctionQueryVariables,
type LogicFunctionIdInput,
} from '~/generated-metadata/graphql';
export const useGetOneLogicFunction = ({
id,
onCompleted,
}: LogicFunctionIdInput & {
onCompleted?: (data: FindOneLogicFunctionQuery) => void;
}) => {
const { data, loading } = useQuery<
FindOneLogicFunctionQuery,
FindOneLogicFunctionQueryVariables
>(FIND_ONE_LOGIC_FUNCTION, {
variables: {
input: { id },
},
onCompleted,
});
return {
logicFunction: data?.findOneLogicFunction || null,
loading,
};
};
@@ -1,11 +1,11 @@
import { useGetOneLogicFunction } from '@/settings/logic-functions/hooks/useGetOneLogicFunction';
import { type Dispatch, type SetStateAction, useState } from 'react';
import { type Sources } from 'twenty-shared/types';
import { useGetOneLogicFunction } from '@/logic-functions/hooks/useGetOneLogicFunction';
import { type Dispatch, type SetStateAction, useEffect, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import {
type GetOneLogicFunctionQuery,
type FindOneLogicFunctionQuery,
type LogicFunction,
} from '~/generated-metadata/graphql';
import { useGetLogicFunctionSourceCode } from '@/logic-functions/hooks/useGetLogicFunctionSourceCode';
export type LogicFunctionNewFormValues = {
name: string;
@@ -13,7 +13,7 @@ export type LogicFunctionNewFormValues = {
};
export type LogicFunctionFormValues = LogicFunctionNewFormValues & {
code: Sources;
code: string;
};
type SetLogicFunctionFormValues = Dispatch<
@@ -33,13 +33,18 @@ export const useLogicFunctionUpdateFormState = ({
const [formValues, setFormValues] = useState<LogicFunctionFormValues>({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
code: '',
});
const { code: codeFromApi, loading: logicFunctionSourceCodeLoading } =
useGetLogicFunctionSourceCode({
logicFunctionId,
});
const { logicFunction, loading: logicFunctionLoading } =
useGetOneLogicFunction({
id: logicFunctionId,
onCompleted: (data: GetOneLogicFunctionQuery) => {
onCompleted: (data: FindOneLogicFunctionQuery) => {
const fn = data?.findOneLogicFunction;
if (isDefined(fn)) {
@@ -52,10 +57,16 @@ export const useLogicFunctionUpdateFormState = ({
},
});
useEffect(() => {
if (isDefined(codeFromApi)) {
setFormValues((prev) => ({ ...prev, code: codeFromApi }));
}
}, [codeFromApi]);
return {
formValues,
setFormValues,
logicFunction,
loading: logicFunctionLoading,
loading: logicFunctionLoading || logicFunctionSourceCodeLoading,
};
};
@@ -1,65 +1,55 @@
import { useCallback } from 'react';
import { UPDATE_LOGIC_FUNCTION_SOURCE } from '@/logic-functions/graphql/mutations/updateLogicFunctionSource';
import { UPDATE_ONE_LOGIC_FUNCTION } from '@/logic-functions/graphql/mutations/updateOneLogicFunction';
import { GET_LOGIC_FUNCTION_SOURCE_CODE } from '@/logic-functions/graphql/queries/getLogicFunctionSourceCode';
import { useMetadataErrorHandler } from '@/metadata-error-handler/hooks/useMetadataErrorHandler';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { type MetadataRequestResult } from '@/object-metadata/types/MetadataRequestResult.type';
import { CREATE_DEFAULT_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/mutations/createDefaultLogicFunction';
import { DELETE_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/mutations/deleteOneLogicFunction';
import { FIND_MANY_LOGIC_FUNCTIONS } from '@/settings/logic-functions/graphql/queries/findManyLogicFunctions';
import { CREATE_ONE_LOGIC_FUNCTION } from '@/logic-functions/graphql/mutations/createOneLogicFunction';
import { DELETE_ONE_LOGIC_FUNCTION } from '@/logic-functions/graphql/mutations/deleteOneLogicFunction';
import { FIND_MANY_LOGIC_FUNCTIONS } from '@/logic-functions/graphql/queries/findManyLogicFunctions';
import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { ApolloError, useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import { t } from '@lingui/core/macro';
import { type Sources, CrudOperationType } from 'twenty-shared/types';
import { CrudOperationType } from 'twenty-shared/types';
import {
type CreateDefaultLogicFunctionItemMutation,
type CreateDefaultLogicFunctionItemMutationVariables,
type CreateOneLogicFunctionMutation,
type CreateOneLogicFunctionMutationVariables,
type DeleteOneLogicFunctionMutation,
type DeleteOneLogicFunctionMutationVariables,
type UpdateOneLogicFunctionMutation,
type UpdateOneLogicFunctionMutationVariables,
} from '~/generated-metadata/graphql';
type UpdateLogicFunctionSourceMutationVariables = {
input: { id: string; code: Sources };
};
export const usePersistLogicFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const [createDefaultLogicFunctionMutation] = useMutation<
CreateDefaultLogicFunctionItemMutation,
CreateDefaultLogicFunctionItemMutationVariables
>(CREATE_DEFAULT_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
const [createLogicFunctionMutation] = useMutation<
CreateOneLogicFunctionMutation,
CreateOneLogicFunctionMutationVariables
>(CREATE_ONE_LOGIC_FUNCTION);
const [deleteLogicFunctionMutation] = useMutation<
DeleteOneLogicFunctionMutation,
DeleteOneLogicFunctionMutationVariables
>(DELETE_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
>(DELETE_ONE_LOGIC_FUNCTION);
const [updateLogicFunctionSourceMutation] = useMutation<
{ updateLogicFunctionSource: boolean },
UpdateLogicFunctionSourceMutationVariables
>(UPDATE_LOGIC_FUNCTION_SOURCE, {
client: apolloMetadataClient,
});
UpdateOneLogicFunctionMutation,
UpdateOneLogicFunctionMutationVariables
>(UPDATE_ONE_LOGIC_FUNCTION);
const createLogicFunction = useCallback(
async (
variables: CreateDefaultLogicFunctionItemMutationVariables,
variables: CreateOneLogicFunctionMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof createDefaultLogicFunctionMutation>>
Awaited<ReturnType<typeof createLogicFunctionMutation>>
>
> => {
try {
const result = await createDefaultLogicFunctionMutation({
const result = await createLogicFunctionMutation({
variables,
awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_LOGIC_FUNCTIONS) ?? ''],
@@ -85,16 +75,12 @@ export const usePersistLogicFunction = () => {
};
}
},
[
createDefaultLogicFunctionMutation,
handleMetadataError,
enqueueErrorSnackBar,
],
[createLogicFunctionMutation, handleMetadataError, enqueueErrorSnackBar],
);
const updateLogicFunctionSource = useCallback(
const updateLogicFunction = useCallback(
async (
variables: UpdateLogicFunctionSourceMutationVariables,
variables: UpdateOneLogicFunctionMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof updateLogicFunctionSourceMutation>>
@@ -174,7 +160,7 @@ export const usePersistLogicFunction = () => {
return {
createLogicFunction,
updateLogicFunctionSource,
updateLogicFunction,
deleteLogicFunction,
};
};
@@ -1,146 +0,0 @@
import { computeNewSources } from '@/logic-functions/utils/computeNewSources';
describe('computeNewSources', () => {
it('should compute new code input root 0', () => {
const previousCodeInput = {
'index.ts': 'export const toto = () => {}',
};
const filePath = 'index.ts';
const value = 'export const totoUpdated = () => {}';
const expectedResult = {
'index.ts': 'export const totoUpdated = () => {}',
};
expect(
computeNewSources({ previousCode: previousCodeInput, filePath, value }),
).toEqual(expectedResult);
});
it('should compute new code input root 0 file changed', () => {
const previousCodeInput = {
'.env': 'ENV=env',
'index.ts': 'export const toto = () => {}',
};
const filePath = '.env';
const value = 'ENV=env\nENV2=env2';
const expectedResult = {
'.env': 'ENV=env\nENV2=env2',
'index.ts': 'export const toto = () => {}',
};
expect(
computeNewSources({ previousCode: previousCodeInput, filePath, value }),
).toEqual(expectedResult);
});
it('should compute new code input root 0 with multiple files', () => {
const previousCodeInput = {
'index.ts': 'export const toto = () => {}',
'.env': 'ENV',
};
const filePath = 'index.ts';
const value = 'export const totoUpdated = () => {}';
const expectedResult = {
'index.ts': 'export const totoUpdated = () => {}',
'.env': 'ENV',
};
expect(
computeNewSources({ previousCode: previousCodeInput, filePath, value }),
).toEqual(expectedResult);
});
it('should compute new code input root 1', () => {
const previousCodeInput = {
src: { 'index.ts': 'export const toto = () => {}' },
};
const filePath = 'src/index.ts';
const value = 'export const totoUpdated = () => {}';
const expectedResult = {
src: { 'index.ts': 'export const totoUpdated = () => {}' },
};
expect(
computeNewSources({ previousCode: previousCodeInput, filePath, value }),
).toEqual(expectedResult);
});
it('should compute new code input root 1 with multiple files', () => {
const previousCodeInput = {
src: {
'index.ts': 'export const toto = () => {}',
'index2.ts': 'export const toto2 = () => {}',
},
};
const filePath = 'src/index.ts';
const value = 'export const totoUpdated = () => {}';
const expectedResult = {
src: {
'index.ts': 'export const totoUpdated = () => {}',
'index2.ts': 'export const toto2 = () => {}',
},
};
expect(
computeNewSources({ previousCode: previousCodeInput, filePath, value }),
).toEqual(expectedResult);
});
it('should compute new code input root 1 with added files', () => {
const previousCodeInput = {
src: {
'index.ts': 'export const toto = () => {}',
},
};
const filePath = 'src/index2.ts';
const value = 'export const toto2 = () => {}';
const expectedResult = {
src: {
'index.ts': 'export const toto = () => {}',
'index2.ts': 'export const toto2 = () => {}',
},
};
expect(
computeNewSources({ previousCode: previousCodeInput, filePath, value }),
).toEqual(expectedResult);
});
it('should compute new code input multiple roots', () => {
const previousCodeInput = {
'.env': 'ENV=env',
src: { 'index.ts': 'export const toto = () => {}' },
};
const filePath = 'src/index.ts';
const value = 'export const totoUpdated = () => {}';
const expectedResult = {
src: { 'index.ts': 'export const totoUpdated = () => {}' },
'.env': 'ENV=env',
};
expect(
computeNewSources({ previousCode: previousCodeInput, filePath, value }),
).toEqual(expectedResult);
});
});
@@ -1,59 +0,0 @@
// IA Generated
import { flattenSources } from '@/logic-functions/utils/flattenSources';
import { type Sources } from 'twenty-shared/types';
describe('flattenSources', () => {
it('flattens nested sources with root files', () => {
const input: Sources = {
'.env': 'KEY=VALUE',
src: {
'index.ts': 'export const a = 1',
lib: {
'util.ts': 'export const util = () => {}',
},
},
docs: {
'README.md': '# Hello',
},
};
const result = flattenSources(input);
expect(result).toEqual([
{ path: '.env', content: 'KEY=VALUE' },
{ path: 'docs/README.md', content: '# Hello' },
{ path: 'src/index.ts', content: 'export const a = 1' },
{ path: 'src/lib/util.ts', content: 'export const util = () => {}' },
]);
});
it('handles deep nesting and preserves file contents', () => {
const input: Sources = {
a: { b: { c: { d: { 'file.ts': 'content' } } } },
};
expect(flattenSources(input)).toEqual([
{ path: 'a/b/c/d/file.ts', content: 'content' },
]);
});
it('ignores empty folders and non-string leaves', () => {
const input: Sources = {
empty: {},
weird: {
oops: 42,
} as unknown as Sources,
file: 'ok',
};
const res = flattenSources(input);
expect(res).toEqual([{ path: 'file', content: 'ok' }]);
});
it('accepts a custom basePath prefix', () => {
const input: Sources = { src: { 'index.ts': 'x' } };
const res = flattenSources(input, 'pkg');
expect(res).toEqual([{ path: 'pkg/src/index.ts', content: 'x' }]);
});
});
@@ -1,48 +0,0 @@
import { type Sources } from 'twenty-shared/types';
export const computeNewSources = ({
previousCode,
filePath,
value,
}: {
previousCode: Sources;
filePath: string;
value: string;
}): Sources => {
const result = { ...previousCode };
const parts = filePath.split('/').filter(Boolean);
if (parts.length === 0) {
return result;
}
if (parts.length === 1) {
result[filePath] = value;
return result;
}
const [root, ...rest] = parts;
const newFilePath = rest.join('/');
if (
typeof result?.[root] === 'string' ||
typeof previousCode[root] === 'string'
) {
throw Error('Cannot compute new code input');
}
return {
...previousCode,
[root]: {
...previousCode[root],
...computeNewSources({
previousCode: result?.[root] ?? {},
filePath: newFilePath,
value,
}),
},
};
};
@@ -1,28 +0,0 @@
// IA Generated
import { type Sources } from 'twenty-shared/types';
type FlatSource = { path: string; content: string };
export const flattenSources = (
sources: Sources,
basePath = '',
): FlatSource[] => {
const out: FlatSource[] = [];
const join = (a: string, b: string) => (a ? `${a}/${b}` : b);
const walk = (node: Sources, prefix: string) => {
for (const [name, value] of Object.entries(node)) {
if (typeof value === 'string') {
out.push({ path: join(prefix, name), content: value });
} else if (value && typeof value === 'object') {
walk(value as Sources, join(prefix, name));
}
}
};
walk(sources, basePath);
out.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
return out;
};
@@ -1,4 +1,4 @@
import { useGetAvailablePackages } from '@/settings/logic-functions/hooks/useGetAvailablePackages';
import { useGetAvailablePackages } from '@/logic-functions/hooks/useGetAvailablePackages';
import { type EditorProps, type Monaco } from '@monaco-editor/react';
import { type editor } from 'monaco-editor';
import { AutoTypings } from 'monaco-editor-auto-typings';
@@ -1,4 +1,4 @@
import { type LogicFunctionNewFormValues } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { type LogicFunctionNewFormValues } from '@/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { TextArea } from '@/ui/input/components/TextArea';
import styled from '@emotion/styled';
@@ -21,13 +21,11 @@ export const SettingsLogicFunctionCodeEditorTab = ({
handleExecute,
onChange,
isTesting = false,
isManaged = false,
}: {
files: File[];
handleExecute: () => void;
onChange: (filePath: string, value: string) => void;
isTesting?: boolean;
isManaged?: boolean;
}) => {
const activeTabId = useRecoilComponentValue(
activeTabIdComponentState,
@@ -68,16 +66,6 @@ export const SettingsLogicFunctionCodeEditorTab = ({
onChange={(newCodeValue: string) =>
onChange(activeTabId, newCodeValue)
}
options={
isManaged
? {
readOnly: true,
readOnlyMessage: {
value: t`Managed logic functions are not editable`,
},
}
: undefined
}
/>
)}
</Section>
@@ -1,6 +1,6 @@
import { SettingsLogicFunctionNewForm } from '@/settings/logic-functions/components/SettingsLogicFunctionNewForm';
import { SettingsLogicFunctionTabEnvironmentVariablesSection } from '@/settings/logic-functions/components/SettingsLogicFunctionTabEnvironmentVariablesSection';
import { type LogicFunctionFormValues } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { type LogicFunctionFormValues } from '@/logic-functions/hooks/useLogicFunctionUpdateFormState';
export const SettingsLogicFunctionSettingsTab = ({
formValues,
@@ -5,21 +5,24 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { H2Title } from 'twenty-ui/display';
import { H2Title, OverflowingTextWithTooltip } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { isDefined } from 'twenty-shared/utils';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { REACT_APP_SERVER_BASE_URL } from '~/config';
import { Tag } from 'twenty-ui/components';
import { type LogicFunction } from '~/generated-metadata/graphql';
export const StyledRouteTriggerTableRow = styled(TableRow)`
grid-template-columns: 1fr 120px 120px;
`;
// TODO: @Charles put back with new sources
// const StyledTableCell = styled(TableCell)`
// color: ${({ theme }) => theme.font.color.tertiary};
// gap: ${({ theme }) => theme.spacing(2)};
// min-width: 0;
// overflow: hidden;
// `;
const StyledTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.tertiary};
gap: ${({ theme }) => theme.spacing(2)};
min-width: 0;
overflow: hidden;
`;
const StyledRouteTriggerTableHeaderRow = styled(StyledRouteTriggerTableRow)`
margin-bottom: ${({ theme }) => theme.spacing(2)};
@@ -36,22 +39,31 @@ const StyledEmptyState = styled.div`
`;
export const SettingsLogicFunctionTriggersTab = ({
logicFunction: _logicFunction,
logicFunction,
}: {
logicFunction: LogicFunction;
}) => {
const { t } = useLingui();
const cronTriggers: [] = [];
const cronTrigger = logicFunction.cronTriggerSettings;
const routeTriggers: [] = [];
const routeTrigger = logicFunction.httpRouteTriggerSettings;
const databaseEvents: [] = [];
const databaseEventTriggerSettings =
logicFunction.databaseEventTriggerSettings;
const hasNoTriggers =
databaseEvents.length === 0 &&
cronTriggers.length === 0 &&
routeTriggers.length === 0;
let databaseEventTrigger = undefined;
if (isDefined(databaseEventTriggerSettings)) {
const [object, action]: [string, string] =
databaseEventTriggerSettings.eventName.split('.');
databaseEventTrigger = {
object,
action,
updatedFields: databaseEventTriggerSettings.updatedFields,
};
}
const hasNoTriggers = !cronTrigger && !routeTrigger && !databaseEventTrigger;
if (hasNoTriggers) {
return (
@@ -69,37 +81,37 @@ export const SettingsLogicFunctionTriggersTab = ({
return (
<>
{databaseEvents.length > 0 && (
{isDefined(databaseEventTrigger) && (
<Section>
<H2Title
title={t`Database event`}
description={t`Select the events that should trigger the function`}
/>
<SettingsDatabaseEventsForm events={databaseEvents} disabled />
<SettingsDatabaseEventsForm
events={[databaseEventTrigger]}
disabled
/>
</Section>
)}
{cronTriggers.length > 0 && (
{isDefined(cronTrigger) && (
<Section>
<H2Title
title={t`Cron`}
description={t`Triggers the function at regular intervals`}
/>
{cronTriggers.map((cronTrigger, index) => (
<FormTextFieldInput
key={index}
label={t`Expression`}
placeholder="0 */1 * * *"
hint={t`Format: [Minute] [Hour] [Day of Month] [Month] [Day of Week]`}
onChange={() => {}}
readonly
defaultValue={cronTrigger}
/>
))}
<FormTextFieldInput
label={t`Expression`}
placeholder="0 */1 * * *"
hint={t`Format: [Minute] [Hour] [Day of Month] [Month] [Day of Week]`}
onChange={() => {}}
readonly
defaultValue={cronTrigger.pattern}
/>
</Section>
)}
{routeTriggers.length > 0 && (
{isDefined(routeTrigger) && (
<Section>
<H2Title
title={t`Http`}
@@ -111,24 +123,21 @@ export const SettingsLogicFunctionTriggersTab = ({
<TableHeader>{t`Method`}</TableHeader>
<TableHeader>{t`Auth Required`}</TableHeader>
</StyledRouteTriggerTableHeaderRow>
{routeTriggers.map((_, _index) => (
<></>
// <StyledRouteTriggerTableRow key={index}>
// <StyledTableCell>
// <OverflowingTextWithTooltip
// text={`${REACT_APP_SERVER_BASE_URL}/s${routeTrigger.path}`}
// />
// </StyledTableCell>
// <StyledTableCell>{routeTrigger.httpMethod}</StyledTableCell>
// <StyledTableCell>
// <Tag
// text={routeTrigger.isAuthRequired ? t`True` : t`False`}
// color={routeTrigger.isAuthRequired ? 'green' : 'orange'}
// weight="medium"
// />
// </StyledTableCell>
// </StyledRouteTriggerTableRow>
))}
<StyledRouteTriggerTableRow>
<StyledTableCell>
<OverflowingTextWithTooltip
text={`${REACT_APP_SERVER_BASE_URL}/s${routeTrigger.path}`}
/>
</StyledTableCell>
<StyledTableCell>{routeTrigger.httpMethod}</StyledTableCell>
<StyledTableCell>
<Tag
text={routeTrigger.isAuthRequired ? t`True` : t`False`}
color={routeTrigger.isAuthRequired ? 'green' : 'orange'}
weight="medium"
/>
</StyledTableCell>
</StyledRouteTriggerTableRow>
</Table>
</Section>
)}
@@ -1,13 +0,0 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const CREATE_DEFAULT_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
mutation CreateDefaultLogicFunctionItem(
$input: CreateDefaultLogicFunctionInput!
) {
createDefaultLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -1,31 +0,0 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/queries/findOneLogicFunction';
import { useQuery } from '@apollo/client';
import {
type GetOneLogicFunctionQuery,
type GetOneLogicFunctionQueryVariables,
type LogicFunctionIdInput,
} from '~/generated-metadata/graphql';
export const useGetOneLogicFunction = ({
id,
onCompleted,
}: LogicFunctionIdInput & {
onCompleted?: (data: GetOneLogicFunctionQuery) => void;
}) => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading } = useQuery<
GetOneLogicFunctionQuery,
GetOneLogicFunctionQueryVariables
>(FIND_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient ?? undefined,
variables: {
input: { id },
},
onCompleted,
});
return {
logicFunction: data?.findOneLogicFunction || null,
loading,
};
};
@@ -1,8 +1,8 @@
import { createState } from 'twenty-ui/utilities';
import { type GetManyLogicFunctionsQuery } from '~/generated-metadata/graphql';
import { type FindManyLogicFunctionsQuery } from '~/generated-metadata/graphql';
export type LogicFunction =
GetManyLogicFunctionsQuery['findManyLogicFunctions'][number];
FindManyLogicFunctionsQuery['findManyLogicFunctions'][number];
export const logicFunctionsState = createState<LogicFunction[]>({
key: 'logicFunctionsState',
@@ -29,7 +29,7 @@ import {
useFindAllCoreViewsQuery,
useFindAllRecordPageLayoutsQuery,
useGetCurrentUserQuery,
useGetManyLogicFunctionsQuery,
useFindManyLogicFunctionsQuery,
} from '~/generated-metadata/graphql';
import { dateLocaleState } from '~/localization/states/dateLocaleState';
import { dynamicActivate } from '~/utils/i18n/dynamicActivate';
@@ -131,7 +131,7 @@ export const MetadataProviderEffect = () => {
},
);
const { data: logicFunctionsData } = useGetManyLogicFunctionsQuery({
const { data: logicFunctionsData } = useFindManyLogicFunctionsQuery({
skip: !isLoggedIn,
});
@@ -1,6 +1,8 @@
import { useGetLogicFunctionSourceCode } from '@/logic-functions/hooks/useGetLogicFunctionSourceCode';
import { useGetAvailablePackages } from '@/settings/logic-functions/hooks/useGetAvailablePackages';
import { type LogicFunctionFormValues } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { useGetAvailablePackages } from '@/logic-functions/hooks/useGetAvailablePackages';
import {
type LogicFunctionFormValues,
useLogicFunctionUpdateFormState,
} from '@/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { useFullScreenModal } from '@/ui/layout/fullscreen/hooks/useFullScreenModal';
import { type BreadcrumbProps } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { useGetUpdatableWorkflowVersionOrThrow } from '@/workflow/hooks/useGetUpdatableWorkflowVersionOrThrow';
@@ -11,7 +13,6 @@ import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/code-
import { CmdEnterActionButton } from '@/action-menu/components/CmdEnterActionButton';
import { LogicFunctionExecutionResult } from '@/logic-functions/components/LogicFunctionExecutionResult';
import { INDEX_FILE_NAME } from '@/logic-functions/constants/IndexFileName';
import { getFunctionInputFromSourceCode } from '@/logic-functions/utils/getFunctionInputFromSourceCode';
import { mergeDefaultFunctionInputAndFunctionInput } from '@/logic-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
import { InputLabel } from '@/ui/input/components/InputLabel';
@@ -35,10 +36,8 @@ import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { SOURCE_FOLDER_NAME } from '@/logic-functions/constants/SourceFolderName';
import { useExecuteLogicFunction } from '@/logic-functions/hooks/useExecuteLogicFunction';
import { usePersistLogicFunction } from '@/logic-functions/hooks/usePersistLogicFunction';
import { computeNewSources } from '@/logic-functions/utils/computeNewSources';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
import { CODE_ACTION } from '@/workflow/workflow-steps/workflow-actions/constants/actions/CodeAction';
import { type Monaco } from '@monaco-editor/react';
@@ -104,7 +103,7 @@ export const WorkflowEditActionCode = ({
activeTabIdComponentState,
WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID,
);
const { updateLogicFunctionSource } = usePersistLogicFunction();
const { updateLogicFunction } = usePersistLogicFunction();
const { getUpdatableWorkflowVersion } =
useGetUpdatableWorkflowVersionOrThrow();
@@ -126,21 +125,8 @@ export const WorkflowEditActionCode = ({
action.settings.input.logicFunctionInput,
);
const { code: codeFromApi, loading } = useGetLogicFunctionSourceCode({
logicFunctionId,
});
const [formValues, setFormValues] = useState<LogicFunctionFormValues>({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
});
useEffect(() => {
if (isDefined(codeFromApi)) {
setFormValues((prev) => ({ ...prev, code: codeFromApi }));
}
}, [codeFromApi]);
const { formValues, setFormValues, loading } =
useLogicFunctionUpdateFormState({ logicFunctionId });
const updateOutputSchemaFromTestResult = async (testResult: object) => {
if (actionOptions.readonly === true) {
@@ -159,10 +145,12 @@ export const WorkflowEditActionCode = ({
});
const handleSave = useDebouncedCallback(async () => {
await updateLogicFunctionSource({
await updateLogicFunction({
input: {
id: logicFunctionId,
code: formValues.code,
update: {
sourceHandlerCode: formValues.code,
},
},
});
}, 500);
@@ -174,11 +162,7 @@ export const WorkflowEditActionCode = ({
setFormValues((prevState: LogicFunctionFormValues) => {
return {
...prevState,
code: computeNewSources({
previousCode: prevState['code'],
filePath: `${SOURCE_FOLDER_NAME}/${INDEX_FILE_NAME}`,
value: newCode,
}),
code: newCode,
};
});
await handleSave();
@@ -273,7 +257,7 @@ export const WorkflowEditActionCode = ({
}
if (!isExecuting) {
await executeLogicFunction({ forceRebuild: true });
await executeLogicFunction();
}
};
@@ -387,12 +371,6 @@ export const WorkflowEditActionCode = ({
setIsFullScreen(false);
};
const indexFileContent =
typeof formValues.code?.[SOURCE_FOLDER_NAME] !== 'string' &&
typeof formValues.code[SOURCE_FOLDER_NAME][INDEX_FILE_NAME] === 'string'
? formValues.code[SOURCE_FOLDER_NAME][INDEX_FILE_NAME]
: '';
const fullScreenOverlay = renderFullScreenModal(
<div data-globally-prevent-click-outside="true">
<WorkflowEditActionCodeFields
@@ -404,7 +382,7 @@ export const WorkflowEditActionCode = ({
<StyledFullScreenCodeEditorContainer>
<CodeEditor
height="100%"
value={indexFileContent}
value={formValues.code}
language="typescript"
onChange={handleCodeChange}
onMount={handleEditorDidMount}
@@ -439,7 +417,7 @@ export const WorkflowEditActionCode = ({
readonly={actionOptions.readonly}
/>
<WorkflowCodeEditor
value={indexFileContent}
value={formValues.code}
onChange={handleCodeChange}
onMount={handleEditorDidMount}
options={{
@@ -1,9 +1,7 @@
import { useGetAvailablePackages } from '@/settings/logic-functions/hooks/useGetAvailablePackages';
import { useGetAvailablePackages } from '@/logic-functions/hooks/useGetAvailablePackages';
import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
import { useGetLogicFunctionSourceCode } from '@/logic-functions/hooks/useGetLogicFunctionSourceCode';
import { INDEX_FILE_NAME } from '@/logic-functions/constants/IndexFileName';
import { SOURCE_FOLDER_NAME } from '@/logic-functions/constants/SourceFolderName';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowEditActionCodeFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCodeFields';
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWrongExportedFunctionMarkers';
@@ -52,13 +50,6 @@ export const WorkflowReadonlyActionCode = ({
return null;
}
const indexFileContent =
code != null &&
typeof code?.[SOURCE_FOLDER_NAME] !== 'string' &&
typeof code[SOURCE_FOLDER_NAME]?.[INDEX_FILE_NAME] === 'string'
? code[SOURCE_FOLDER_NAME][INDEX_FILE_NAME]
: '';
return (
<>
<WorkflowStepBody>
@@ -69,7 +60,7 @@ export const WorkflowReadonlyActionCode = ({
<StyledCodeEditorContainer>
<CodeEditor
height={343}
value={indexFileContent}
value={code ?? undefined}
language="typescript"
onMount={handleEditorDidMount}
setMarkers={getWrongExportedFunctionMarkers}
@@ -1,6 +1,6 @@
import { getDefaultFunctionInputFromInputSchema } from '@/logic-functions/utils/getDefaultFunctionInputFromInputSchema';
import { mergeDefaultFunctionInputAndFunctionInput } from '@/logic-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
import { useGetOneLogicFunction } from '@/settings/logic-functions/hooks/useGetOneLogicFunction';
import { useGetOneLogicFunction } from '@/logic-functions/hooks/useGetOneLogicFunction';
import { type WorkflowLogicFunctionAction } from '@/workflow/types/Workflow';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowStepFooter } from '@/workflow/workflow-steps/components/WorkflowStepFooter';
@@ -131,24 +131,24 @@ export const SettingsToolsTable = () => {
});
if (result.status === 'successful' && isDefined(result.response?.data)) {
const newFunction = result.response.data.createDefaultLogicFunction;
const newLogicFunction = result.response.data.createOneLogicFunction;
enqueueSuccessSnackBar({ message: t`Tool created` });
// Navigate to the logic function detail page
// The applicationId might be null for workspace-level functions
const applicationId = (newFunction as { applicationId?: string })
const applicationId = (newLogicFunction as { applicationId?: string })
.applicationId;
if (isDefined(applicationId)) {
navigate(
getSettingsPath(SettingsPath.ApplicationLogicFunctionDetail, {
applicationId,
logicFunctionId: newFunction.id,
logicFunctionId: newLogicFunction.id,
}),
);
} else {
navigate(
getSettingsPath(SettingsPath.LogicFunctionDetail, {
logicFunctionId: newFunction.id,
logicFunctionId: newLogicFunction.id,
}),
);
}
@@ -1,3 +1,6 @@
import { getToolInputSchemaFromSourceCode } from '@/logic-functions/utils/getToolInputSchemaFromSourceCode';
import { useNavigate, useParams } from 'react-router-dom';
import { useExecuteLogicFunction } from '@/logic-functions/hooks/useExecuteLogicFunction';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { SettingsLogicFunctionLabelContainer } from '@/settings/logic-functions/components/SettingsLogicFunctionLabelContainer';
@@ -7,23 +10,35 @@ import { SettingsLogicFunctionTriggersTab } from '@/settings/logic-functions/com
import {
type LogicFunctionFormValues,
useLogicFunctionUpdateFormState,
} from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
} from '@/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { t } from '@lingui/core/macro';
import { useParams } from 'react-router-dom';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import { IconBolt, IconPlayerPlay, IconSettings } from 'twenty-ui/display';
import {
IconBolt,
IconCode,
IconPlayerPlay,
IconSettings,
} from 'twenty-ui/display';
import { useFindOneApplicationQuery } from '~/generated-metadata/graphql';
import { currentWorkspaceState } from '@/auth/states/currentWorkspaceState';
import { useRecoilValue } from 'recoil';
import { useDebouncedCallback } from 'use-debounce';
import { usePersistLogicFunction } from '@/logic-functions/hooks/usePersistLogicFunction';
import { SettingsLogicFunctionCodeEditorTab } from '@/settings/logic-functions/components/tabs/SettingsLogicFunctionCodeEditorTab';
const LOGIC_FUNCTION_DETAIL_ID = 'logic-function-detail';
export const SettingsLogicFunctionDetail = () => {
const { logicFunctionId = '', applicationId = '' } = useParams();
const navigate = useNavigate();
const currentWorkspace = useRecoilValue(currentWorkspaceState);
const { data, loading: applicationLoading } = useFindOneApplicationQuery({
variables: { id: applicationId },
skip: !applicationId,
@@ -31,6 +46,11 @@ export const SettingsLogicFunctionDetail = () => {
const applicationName = data?.findOneApplication?.name;
const workspaceCustomApplicationId =
currentWorkspace?.workspaceCustomApplication?.id;
const isManaged = applicationId !== workspaceCustomApplicationId;
const instanceId = `${LOGIC_FUNCTION_DETAIL_ID}-${logicFunctionId}`;
const activeTabId = useRecoilComponentValue(
@@ -41,14 +61,33 @@ export const SettingsLogicFunctionDetail = () => {
const { formValues, setFormValues, logicFunction, loading } =
useLogicFunctionUpdateFormState({ logicFunctionId });
const { updateLogicFunction } = usePersistLogicFunction();
const { executeLogicFunction, isExecuting } = useExecuteLogicFunction({
logicFunctionId,
});
const handleExecute = async () => {
await executeLogicFunction({ forceRebuild: false });
await executeLogicFunction();
};
const handleSave = useDebouncedCallback(
async (toolInputSchema?: object | null) => {
await updateLogicFunction({
input: {
id: logicFunctionId,
update: {
name: formValues.name,
description: formValues.description,
sourceHandlerCode: formValues.code,
...(toolInputSchema !== undefined && { toolInputSchema }),
},
},
});
},
500,
);
const onChange = (key: string) => {
return (value: string) => {
setFormValues((prevState: LogicFunctionFormValues) => ({
@@ -58,12 +97,43 @@ export const SettingsLogicFunctionDetail = () => {
};
};
const onCodeChange = async (filePath: string, value: string) => {
setFormValues((prevState: LogicFunctionFormValues) => {
return {
...prevState,
code: value,
};
});
// Parse and save schema if editing the handler file
let toolInputSchema: object | null | undefined;
if (filePath === logicFunction?.sourceHandlerPath) {
toolInputSchema = await getToolInputSchemaFromSourceCode(value);
}
await handleSave(toolInputSchema);
};
const handleTestFunction = async () => {
navigate('#test');
await executeLogicFunction();
};
const tabs = [
{
id: 'editor',
title: t`Editor`,
Icon: IconCode,
disabled: isManaged,
hide: isManaged,
},
{ id: 'settings', title: t`Settings`, Icon: IconSettings },
{ id: 'test', title: t`Test`, Icon: IconPlayerPlay },
{ id: 'triggers', title: t`Triggers`, Icon: IconBolt },
];
const isEditorTab = activeTabId === 'editor';
const isTriggersTab = activeTabId === 'triggers';
const isSettingsTab = activeTabId === 'settings';
const isTestTab = activeTabId === 'test';
@@ -104,6 +174,14 @@ export const SettingsLogicFunctionDetail = () => {
{ children: `${logicFunction?.name}` },
];
const files = [
{
path: 'index.ts',
content: formValues.code,
language: 'typescript',
},
];
return (
!loading &&
!applicationLoading && (
@@ -118,6 +196,14 @@ export const SettingsLogicFunctionDetail = () => {
>
<SettingsPageContainer>
<TabList tabs={tabs} componentInstanceId={instanceId} />
{isEditorTab && (
<SettingsLogicFunctionCodeEditorTab
files={files}
handleExecute={handleTestFunction}
onChange={onCodeChange}
isTesting={isExecuting}
/>
)}
{isTriggersTab && logicFunction && (
<SettingsLogicFunctionTriggersTab logicFunction={logicFunction} />
)}
@@ -14,13 +14,17 @@ import { sleep } from '~/utils/sleep';
const SOURCE_CODE_FULL_PATH =
'logic-function/20202020-1c25-4d02-bf25-6aeccf7ea419/adb4bd21-7670-4c81-9f74-1fc196fe87ea/source.ts';
const APPLICATION_ID = '20202020-64aa-4b6f-b003-9c74b97cee20';
const meta: Meta<PageDecoratorArgs> = {
title: 'Pages/Settings/LogicFunctions/SettingsLogicFunctionDetail',
component: SettingsLogicFunctionDetail,
decorators: [PageDecorator],
args: {
routePath: '/settings/function/',
routePath:
'/settings/applications/:applicationId/logicFunctions/:logicFunctionId',
routeParams: {
':applicationId': APPLICATION_ID,
':logicFunctionId': 'adb4bd21-7670-4c81-9f74-1fc196fe87ea',
},
},
@@ -34,6 +38,7 @@ const meta: Meta<PageDecoratorArgs> = {
logicFunction: {
__typename: 'LogicFunction',
id: 'adb4bd21-7670-4c81-9f74-1fc196fe87ea',
applicationId: APPLICATION_ID,
name: 'Logic Function Name',
description: '',
runtime: 'nodejs22.x',
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "twenty-sdk",
"version": "0.5.0",
"version": "0.5.1",
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
@@ -0,0 +1 @@
generated
@@ -3,7 +3,6 @@ import type { Manifest } from 'twenty-shared/application';
import { PermissionFlagType } from 'twenty-shared/constants';
export const EXPECTED_MANIFEST: Manifest = {
sources: {},
publicAssets: [
{
checksum: '99496069dcc2a1488e1cae9f826d2707',
@@ -2,7 +2,6 @@ import { FieldType } from '@/sdk';
import type { Manifest } from 'twenty-shared/application';
export const EXPECTED_MANIFEST: Manifest = {
sources: {},
application: {
universalIdentifier: 'e1e2e3e4-e5e6-4000-8000-000000000001',
displayName: 'Root App',
@@ -26,5 +26,4 @@ export const normalizeManifestForComparison = <T extends JsonManifestInput>(
? '[checksum]'
: '',
})),
sources: {}, // removing sources for now, waiting compressed file implementation
});
@@ -574,7 +574,7 @@ export class ApiService {
);
const response: AxiosResponse = await this.client.post(
'/graphql',
'/metadata',
formData,
);
@@ -32,7 +32,6 @@ const validManifest: Manifest = {
logicFunctions: [],
roles: [],
publicAssets: [],
sources: {},
};
describe('manifestValidate', () => {
@@ -11,9 +11,8 @@ import {
type LogicFunctionConfig,
} from '@/sdk';
import { glob } from 'fast-glob';
import * as fs from 'fs-extra';
import { readFile } from 'fs-extra';
import { basename, extname, relative, sep } from 'path';
import { basename, extname, relative } from 'path';
import {
type ApplicationManifest,
type AssetManifest,
@@ -25,7 +24,6 @@ import {
type ObjectManifest,
type RoleManifest,
} from 'twenty-shared/application';
import { type Sources } from 'twenty-shared/types';
import { assertUnreachable } from 'twenty-shared/utils';
const loadSources = async (appPath: string): Promise<string[]> => {
@@ -44,32 +42,6 @@ const loadAssets = async (appPath: string) => {
});
};
const computeSources = async (
appPath: string,
sourceFilePaths: string[],
): Promise<Sources> => {
const sources: Sources = {};
for (const filepath of sourceFilePaths) {
const relPath = relative(appPath, filepath);
const parts = relPath.split(sep);
const content = await fs.readFile(filepath, 'utf8');
let current: Sources = sources;
for (let i = 0; i < parts.length; i++) {
const part = parts[i];
if (i === parts.length - 1) {
current[part] = content;
} else {
current[part] = (current[part] ?? {}) as Sources;
current = current[part] as Sources;
}
}
}
return sources;
};
export const buildManifest = async (
appPath: string,
): Promise<{
@@ -240,7 +212,6 @@ export const buildManifest = async (
logicFunctions,
frontComponents,
publicAssets,
sources: await computeSources(appPath, filePaths),
};
const entityFilePaths: EntityFilePaths = {
+2 -2
View File
@@ -21,11 +21,11 @@
"outDir": "dist/assets"
},
{
"include": "engine/core-modules/application-layer/constants/seed-dependencies/**",
"include": "engine/core-modules/application/constants/seed-dependencies/**",
"outDir": "dist/assets"
},
{
"include": "engine/core-modules/application-layer/constants/yarn-engine/**",
"include": "engine/core-modules/application/constants/yarn-engine/**",
"outDir": "dist/assets"
},
{
@@ -10,7 +10,7 @@ import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application-layer/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { FileSettings } from 'src/engine/core-modules/file/types/file-settings.types';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
@@ -214,12 +214,13 @@ export class MigrateWorkflowCodeStepsCommand extends ActiveOrSuspendedWorkspaces
name: oldLogicFunction.name,
description: oldLogicFunction.description ?? undefined,
timeoutSeconds: oldLogicFunction.timeoutSeconds ?? 300,
toolInputSchema: oldLogicFunction.toolInputSchema ?? undefined,
toolInputSchema: oldLogicFunction.toolInputSchema ?? {},
isTool: oldLogicFunction.isTool ?? false,
handlerName: oldLogicFunction.handlerName,
sourceHandlerPath: `${NEW_WORKFLOW_RESOURCE_PREFIX}/${newLogicFunctionId}/src/index.ts`,
builtHandlerPath: `${NEW_WORKFLOW_RESOURCE_PREFIX}/${newLogicFunctionId}/src/index.mjs`,
handlerName: oldLogicFunction.handlerName,
checksum,
isBuildUpToDate: true,
},
workspaceId,
ownerFlatApplication: oldLogicFunction.application,
@@ -13,9 +13,6 @@ import { RecordPositionService } from 'src/engine/core-modules/record-position/s
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import {
DEFAULT_BUILT_HANDLER_PATH,
DEFAULT_HANDLER_NAME,
DEFAULT_SOURCE_HANDLER_PATH,
LogicFunctionEntity,
LogicFunctionRuntime,
} from 'src/engine/metadata-modules/logic-function/logic-function.entity';
@@ -24,6 +21,11 @@ import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-obje
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
import { WorkflowActionType } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action-type.enum';
import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types/workflow-trigger.type';
import {
DEFAULT_BUILT_HANDLER_PATH,
DEFAULT_HANDLER_NAME,
DEFAULT_SOURCE_HANDLER_PATH,
} from 'src/engine/metadata-modules/logic-function/constants/handler.contant';
const OLD_BUILT_FOLDER = 'built-function';
const OLD_SOURCE_FOLDER = 'serverless-function';
@@ -0,0 +1,37 @@
import { type MigrationInterface, type QueryRunner } from 'typeorm';
export class AddLogicFunctionIsBuildUpToDateColumn1770725043111
implements MigrationInterface
{
name = 'AddLogicFunctionIsBuildUpToDateColumn1770725043111';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."logicFunction" ADD "isBuildUpToDate" boolean NOT NULL DEFAULT true`,
);
await queryRunner.query(
`ALTER TABLE "core"."logicFunction" ALTER COLUMN "sourceHandlerPath" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "core"."logicFunction" ALTER COLUMN "builtHandlerPath" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "core"."logicFunction" ALTER COLUMN "handlerName" DROP DEFAULT`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."logicFunction" ALTER COLUMN "handlerName" SET DEFAULT 'main'`,
);
await queryRunner.query(
`ALTER TABLE "core"."logicFunction" ALTER COLUMN "builtHandlerPath" SET DEFAULT 'src/index.mjs'`,
);
await queryRunner.query(
`ALTER TABLE "core"."logicFunction" ALTER COLUMN "sourceHandlerPath" SET DEFAULT 'src/index.ts'`,
);
await queryRunner.query(
`ALTER TABLE "core"."logicFunction" DROP COLUMN "isBuildUpToDate"`,
);
}
}
@@ -1,12 +0,0 @@
import { Module } from '@nestjs/common';
import { ApplicationLayerService } from 'src/engine/core-modules/application-layer/application-layer.service';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { FileModule } from 'src/engine/core-modules/file/file.module';
@Module({
imports: [WorkspaceCacheModule, FileModule],
providers: [ApplicationLayerService],
exports: [ApplicationLayerService],
})
export class ApplicationLayerModule {}
@@ -1,46 +0,0 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
@Injectable()
export class ApplicationLayerService {
constructor(
private readonly fileStorageService: FileStorageService,
private readonly workspaceCacheService: WorkspaceCacheService,
) {}
async getAvailablePackages({
logicFunctionId,
workspaceId,
}: {
logicFunctionId: string;
workspaceId: string;
}) {
const { flatLogicFunctionMaps, flatApplicationMaps } =
await this.workspaceCacheService.getOrRecompute(workspaceId, [
'flatLogicFunctionMaps',
'flatApplicationMaps',
]);
const logicFunctionUniversalIdentifier =
flatLogicFunctionMaps.universalIdentifierById[logicFunctionId];
if (!logicFunctionUniversalIdentifier) {
return {};
}
const logicFunction =
flatLogicFunctionMaps.byUniversalIdentifier[
logicFunctionUniversalIdentifier
];
if (!logicFunction) {
return {};
}
const application = flatApplicationMaps.byId[logicFunction.applicationId];
return application?.availablePackages ?? {};
}
}
@@ -6,6 +6,6 @@ export const SEED_DEPENDENCIES_DIRNAME = path.resolve(
__dirname,
path.join(
ASSET_PATH,
'engine/core-modules/application-layer/constants/seed-dependencies',
'engine/core-modules/application/constants/seed-dependencies',
),
);
@@ -6,6 +6,6 @@ export const YARN_ENGINE_DIRNAME = path.resolve(
__dirname,
path.join(
ASSET_PATH,
'engine/core-modules/application-layer/constants/yarn-engine',
'engine/core-modules/application/constants/yarn-engine',
),
);
@@ -4,7 +4,6 @@ import { FileFolder } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { PackageJson } from 'type-fest';
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application-layer/utils/get-default-application-package-fields.util';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import {
ApplicationException,
@@ -15,6 +14,7 @@ import { ApplicationInput } from 'src/engine/core-modules/application/dtos/appli
import { ApplicationManifestMigrationService } from 'src/engine/core-modules/application/services/application-manifest-migration.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application/utils/get-default-application-package-fields.util';
import { getEmptyApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-empty-application-manifest-all-universal-flat-entity-maps.util';
import { getSubApplicationFromToAllFlatEntityMaps } from 'src/engine/core-modules/application/utils/get-sub-application-from-to-all-flat-entity-maps.util';
import { ApplicationVariableEntityService } from 'src/engine/core-modules/applicationVariable/application-variable.service';
@@ -10,8 +10,8 @@ import {
ApplicationException,
ApplicationExceptionCode,
} from 'src/engine/core-modules/application/application.exception';
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application-layer/utils/get-default-application-package-fields.util';
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application-layer/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
import { getDefaultApplicationPackageFields } from 'src/engine/core-modules/application/utils/get-default-application-package-fields.util';
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { ALL_FLAT_ENTITY_MAPS_PROPERTIES } from 'src/engine/metadata-modules/flat-entity/constant/all-flat-entity-maps-properties.constant';
@@ -3,7 +3,7 @@ import { promises as fs, statSync } from 'fs';
import { join } from 'path';
import { promisify } from 'util';
import { YARN_ENGINE_DIRNAME } from 'src/engine/core-modules/application-layer/constants/yarn-engine-dirname';
import { YARN_ENGINE_DIRNAME } from 'src/engine/core-modules/application/constants/yarn-engine-dirname';
const execFilePromise = promisify(execFile);
@@ -35,6 +35,7 @@ export const fromLogicFunctionManifestToUniversalFlatLogicFunction = ({
logicFunctionManifest.databaseEventTriggerSettings ?? null,
httpRouteTriggerSettings:
logicFunctionManifest.httpRouteTriggerSettings ?? null,
isBuildUpToDate: true,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -1,8 +1,8 @@
import { readFile } from 'fs/promises';
import path from 'path';
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application-layer/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
import { SEED_DEPENDENCIES_DIRNAME } from 'src/engine/core-modules/application-layer/constants/seed-dependencies-dirname';
import { parseAvailablePackagesFromPackageJsonAndYarnLock } from 'src/engine/core-modules/application/utils/parse-available-packages-from-package-json-and-yarn-lock.util';
import { SEED_DEPENDENCIES_DIRNAME } from 'src/engine/core-modules/application/constants/seed-dependencies-dirname';
// To regenerate: use logicFunctionCreateHash from logic-function-create-hash.utils.
// package.json: hash(JSON.stringify(JSON.parse(content))). yarn.lock: hash(content).
@@ -314,7 +314,7 @@ export class CacheStorageService {
const formattedKey = `${this.namespace}:${key}`;
if (process.env.NODE_ENV === 'test') {
return `integration-tests:${formattedKey}`;
return `${CacheStorageNamespace.IntegrationTests}:${formattedKey}`;
}
return formattedKey;
@@ -7,4 +7,5 @@ export enum CacheStorageNamespace {
EngineHealth = 'engine:health',
EngineMetrics = 'engine:metrics',
EngineSubscriptions = 'engine:subscriptions',
IntegrationTests = 'integration-tests',
}
@@ -66,7 +66,6 @@ import { TrashCleanupModule } from 'src/engine/trash-cleanup/trash-cleanup.modul
import { WorkspaceEventEmitterModule } from 'src/engine/workspace-event-emitter/workspace-event-emitter.module';
import { ChannelSyncModule } from 'src/modules/connected-account/channel-sync/channel-sync.module';
import { DashboardModule } from 'src/modules/dashboard/dashboard.module';
import { ApplicationLayerModule } from 'src/engine/core-modules/application-layer/application-layer.module';
import { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service';
import { AuditModule } from './audit/audit.module';
@@ -89,7 +88,6 @@ import { FileModule } from './file/file.module';
RowLevelPermissionModule,
OpenApiModule,
ApplicationModule,
ApplicationLayerModule,
ApplicationSyncModule,
AppTokenModule,
TimelineMessagingModule,
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { mkdir, readdir, readFile, stat } from 'fs/promises';
import { join } from 'path';
import { basename, dirname, join } from 'path';
import { type Readable } from 'stream';
import { isObject } from '@sniptt/guards';
@@ -197,45 +197,6 @@ export class FileStorageService {
return sources;
}
async readFolder(params: ResourceIdentifier): Promise<Sources> {
const driver = this.fileStorageDriverFactory.getCurrentDriver();
const onStoragePath = this.buildOnStoragePath(params);
const tempDir = `/tmp/twenty-read-folder-${Date.now()}`;
await mkdir(tempDir, { recursive: true });
await driver.downloadFolder({
onStoragePath,
localPath: tempDir,
});
return this.readLocalFolderToSources(tempDir);
}
uploadFolder(
params: ResourceIdentifier & { localPath: string },
): Promise<void> {
const driver = this.fileStorageDriverFactory.getCurrentDriver();
const onStoragePath = this.buildOnStoragePath(params);
return driver.uploadFolder({
localPath: params.localPath,
onStoragePath,
});
}
downloadFolder(
params: ResourceIdentifier & { localPath: string },
): Promise<void> {
const driver = this.fileStorageDriverFactory.getCurrentDriver();
const onStoragePath = this.buildOnStoragePath(params);
return driver.downloadFolder({
onStoragePath,
localPath: params.localPath,
});
}
downloadFile(
params: ResourceIdentifier & { localPath: string },
): Promise<void> {
@@ -339,7 +300,7 @@ export class FileStorageService {
return driver.copy(params);
}
copy({
async copy({
from,
to,
}: {
@@ -348,68 +309,21 @@ export class FileStorageService {
}): Promise<void> {
const driver = this.fileStorageDriverFactory.getCurrentDriver();
const fromPath = this.buildOnStoragePath(from);
const toPath = this.buildOnStoragePath(to);
const isFile = await driver.checkFileExists({ filePath: fromPath });
if (isFile) {
return driver.copy({
from: { folderPath: dirname(fromPath), filename: basename(fromPath) },
to: { folderPath: dirname(toPath), filename: basename(toPath) },
});
}
return driver.copy({
from: { folderPath: this.buildOnStoragePath(from) },
to: { folderPath: this.buildOnStoragePath(to) },
});
}
async moveFile({
from,
to,
workspaceId,
}: {
from: {
applicationId: string;
fileFolder: FileFolder;
destinationPath: string;
};
to: {
applicationId: string;
fileFolder: FileFolder;
destinationPath: string;
};
workspaceId: string;
}): Promise<void> {
const file = await this.fileRepository.findOneOrFail({
where: {
workspaceId,
applicationId: from.applicationId,
path: `${from.fileFolder}/${from.destinationPath}`,
},
});
const driver = this.fileStorageDriverFactory.getCurrentDriver();
await driver.move({
from: {
folderPath: `${file.workspaceId}/${from.applicationId}/${from.fileFolder}`,
filename: from.destinationPath,
},
to: {
folderPath: `${file.workspaceId}/${to.applicationId}/${to.fileFolder}`,
filename: to.destinationPath,
},
});
await this.fileRepository.update(file.id, {
applicationId: to.applicationId,
path: `${to.fileFolder}/${to.destinationPath}`,
});
}
move({
from,
to,
}: {
from: ResourceIdentifier;
to: ResourceIdentifier;
}): Promise<void> {
const driver = this.fileStorageDriverFactory.getCurrentDriver();
return driver.move({
from: { folderPath: this.buildOnStoragePath(from) },
to: { folderPath: this.buildOnStoragePath(to) },
from: { folderPath: fromPath },
to: { folderPath: toPath },
});
}
@@ -425,11 +339,4 @@ export class FileStorageService {
return driver.checkFileExists({ filePath: onStoragePath });
}
checkFolderExists(params: ResourceIdentifier): Promise<boolean> {
const driver = this.fileStorageDriverFactory.getCurrentDriver();
const onStoragePath = this.buildOnStoragePath(params);
return driver.checkFolderExists({ folderPath: onStoragePath });
}
}
@@ -1,25 +0,0 @@
import { readS3FolderContent } from 'src/engine/core-modules/file-storage/utils/read-s3-folder-content';
describe('read-s3-folder-content', () => {
it('should format files to sources properly', () => {
const files = [
{ path: 'f1/file1.ts', fileContent: 'content1' },
{ path: 'f1/f11/file11.ts', fileContent: 'content11' },
{ path: 'f1/file2.ts', fileContent: 'content2' },
{ path: 'file3.ts', fileContent: 'content3' },
];
const expectedResult = {
'file3.ts': 'content3',
f1: {
'file1.ts': 'content1',
'file2.ts': 'content2',
f11: { 'file11.ts': 'content11' },
},
};
const result = readS3FolderContent(files);
expect(result).toEqual(expectedResult);
});
});
@@ -1,31 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
import { isObject } from '@sniptt/guards';
import { type Sources } from 'twenty-shared/types';
export const readS3FolderContent = (
files: { path: string; fileContent: string }[],
) => {
const result: Sources = {};
for (const { path, fileContent } of files) {
const segments = path.split('/');
const fileName = segments.pop();
if (!isDefined(fileName)) {
continue;
}
let cursor: Sources = result;
for (const segment of segments) {
if (!isObject(cursor[segment])) {
cursor[segment] = {};
}
cursor = cursor[segment] as Sources;
}
cursor[fileName] = fileContent;
}
return result;
};
@@ -38,8 +38,9 @@ import {
LogicFunctionExceptionCode,
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application-layer/utils/copy-yarn-engine-and-build-dependencies';
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application/utils/copy-yarn-engine-and-build-dependencies';
import { type LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service';
import { callWithTimeout } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/call-with-timeout';
const UPDATE_FUNCTION_DURATION_TIMEOUT_IN_SECONDS = 60;
const CREDENTIALS_DURATION_IN_SECONDS = 60 * 60; // 1h
@@ -322,6 +323,7 @@ export class LambdaDriver implements LogicFunctionDriver {
applicationUniversalIdentifier,
payload,
env,
timeoutMs = 900_000,
}: LogicFunctionExecuteParams): Promise<LogicFunctionExecuteResult> {
await this.build({
flatLogicFunction,
@@ -355,7 +357,12 @@ export class LambdaDriver implements LogicFunctionDriver {
const command = new InvokeCommand(params);
try {
const result = await (await this.getLambdaClient()).send(command);
const lambdaClient = await this.getLambdaClient();
const result = await callWithTimeout({
callback: () => lambdaClient.send(command),
timeoutMs,
});
const parsedResult = result.Payload
? JSON.parse(result.Payload.transformToString())
@@ -13,7 +13,7 @@ import { LOGIC_FUNCTION_EXECUTOR_TMPDIR_FOLDER } from 'src/engine/core-modules/l
import { ConsoleListener } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/intercept-console';
import { TemporaryDirManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/temporary-dir-manager';
import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application-layer/utils/copy-yarn-engine-and-build-dependencies';
import { copyYarnEngineAndBuildDependencies } from 'src/engine/core-modules/application/utils/copy-yarn-engine-and-build-dependencies';
import type { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service';
export interface LocalDriverOptions {
@@ -76,6 +76,7 @@ export class LocalDriver implements LogicFunctionDriver {
applicationUniversalIdentifier,
payload,
env,
timeoutMs = 900_000,
}: LogicFunctionExecuteParams): Promise<LogicFunctionExecuteResult> {
await this.build({
flatApplication,
@@ -89,17 +90,13 @@ export class LocalDriver implements LogicFunctionDriver {
try {
const { sourceTemporaryDir } = await temporaryDirManager.init();
const inMemoryBuiltHandlerPath = join(
sourceTemporaryDir,
flatLogicFunction.builtHandlerPath,
);
await this.logicFunctionResourceService.copyBuiltCodeInMemory({
workspaceId: flatLogicFunction.workspaceId,
applicationUniversalIdentifier,
builtHandlerPath: flatLogicFunction.builtHandlerPath,
inMemoryDestinationPath: inMemoryBuiltHandlerPath,
});
const inMemoryBuiltHandlerPath =
await this.logicFunctionResourceService.copyBuiltCodeInMemory({
workspaceId: flatLogicFunction.workspaceId,
applicationUniversalIdentifier,
builtHandlerPath: flatLogicFunction.builtHandlerPath,
inMemoryDestinationPath: sourceTemporaryDir,
});
try {
await fs.symlink(
@@ -161,7 +158,7 @@ export class LocalDriver implements LogicFunctionDriver {
runnerPath,
env: env ?? {},
payload,
timeoutMs: 900_000, // timeout is handled by the logic function service
timeoutMs,
});
if (stdout)
@@ -26,6 +26,7 @@ export type LogicFunctionExecuteParams = {
applicationUniversalIdentifier: string;
payload: object;
env?: Record<string, string>;
timeoutMs?: number;
};
export interface LogicFunctionDriver {
@@ -0,0 +1,14 @@
export const callWithTimeout = async <T>({
callback,
timeoutMs,
}: {
callback: () => Promise<T>;
timeoutMs: number;
}): Promise<T> => {
return Promise.race([
callback(),
new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error('Execution timed out')), timeoutMs),
),
]);
};
@@ -83,16 +83,13 @@ export class LogicFunctionExecutorService {
flatLogicFunction,
});
const resultLogicFunction = await this.callWithTimeout({
callback: () =>
this.driver.execute({
flatLogicFunction,
flatApplication,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
payload,
env: envVariables,
}),
timeoutMs: flatLogicFunction.timeoutSeconds * 1000,
const resultLogicFunction = await this.driver.execute({
flatLogicFunction,
flatApplication,
applicationUniversalIdentifier: flatApplication.universalIdentifier,
payload,
env: envVariables,
timeoutMs: flatLogicFunction.timeoutSeconds * 1_000,
});
await this.handleExecutionResult({
@@ -121,21 +118,6 @@ export class LogicFunctionExecutorService {
}
}
private async callWithTimeout<T>({
callback,
timeoutMs,
}: {
callback: () => Promise<T>;
timeoutMs: number;
}): Promise<T> {
return Promise.race([
callback(),
new Promise<T>((_, reject) =>
setTimeout(() => reject(new Error('Execution timed out')), timeoutMs),
),
]);
}
private async getFlatEntitiesOrThrow({
workspaceId,
logicFunctionId,
@@ -4,9 +4,8 @@ import crypto from 'crypto';
import fs from 'fs/promises';
import { dirname, join } from 'path';
import { isObject } from '@sniptt/guards';
import { build } from 'esbuild';
import { FileFolder, Sources } from 'twenty-shared/types';
import { FileFolder } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { NODE_ESM_CJS_BANNER } from 'twenty-shared/application';
@@ -14,64 +13,56 @@ import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/i
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { TemporaryDirManager } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/temporary-dir-manager';
import {
getLogicFunctionBaseFolderPath,
getRelativePathFromBase,
} from 'src/engine/core-modules/logic-function/logic-function-resource/utils/get-logic-function-handler-path.util';
import {
getLogicFunctionSeedProjectFiles,
LogicFunctionSeedProjectFile,
} from 'src/engine/core-modules/logic-function/logic-function-resource/utils/get-logic-function-seed-project-files.util';
import {
DEFAULT_BUILT_HANDLER_PATH,
DEFAULT_SOURCE_HANDLER_PATH,
} from 'src/engine/metadata-modules/logic-function/logic-function.entity';
import {
LogicFunctionException,
LogicFunctionExceptionCode,
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
import { streamToBuffer } from 'src/utils/stream-to-buffer';
type SeedSourceFilesParams = {
type Identifier = {
workspaceId: string;
applicationUniversalIdentifier: string;
sourceSubfolder: string;
};
type SeedSourceFilesParams = Identifier & {
sourceHandlerPath: string;
builtHandlerPath: string;
};
type SeedSourceFilesResult = {
sourceHandlerPath: string;
builtHandlerPath: string;
handlerName: string;
checksum: string;
};
type UpdateSourceFilesParams = {
sourceHandlerPath: string;
workspaceId: string;
applicationUniversalIdentifier: string;
code: Sources;
type UpdateSourceFilesParams = Omit<
SeedSourceFilesParams,
'builtHandlerPath'
> & {
sourceHandlerCode: string;
};
type BuildFromSourceParams = {
type BuildFromSourceParams = Identifier & {
sourceHandlerPath: string;
builtHandlerPath: string;
workspaceId: string;
applicationUniversalIdentifier: string;
};
type GetSourceCodeParams = {
type GetSourceCodeParams = Identifier & {
sourceHandlerPath: string;
workspaceId: string;
applicationUniversalIdentifier: string;
};
type CopySourceParams = {
type GetBuiltCodeParams = Identifier & {
builtHandlerPath: string;
};
type CopySourceParams = Identifier & {
fromSourceHandlerPath: string;
fromBuiltHandlerPath: string;
toSourceHandlerPath: string;
fromBuiltHandlerPath: string;
toBuiltHandlerPath: string;
workspaceId: string;
applicationUniversalIdentifier: string;
};
@Injectable()
@@ -81,11 +72,9 @@ export class LogicFunctionResourceService {
async seedSourceFiles({
workspaceId,
applicationUniversalIdentifier,
sourceSubfolder,
sourceHandlerPath,
builtHandlerPath,
}: SeedSourceFilesParams): Promise<SeedSourceFilesResult> {
const sourceHandlerPath = `${sourceSubfolder}/${DEFAULT_SOURCE_HANDLER_PATH}`;
const builtHandlerPath = `${sourceSubfolder}/${DEFAULT_BUILT_HANDLER_PATH}`;
const seedProjectFiles = await getLogicFunctionSeedProjectFiles();
const sourceFiles = seedProjectFiles.filter(
@@ -138,40 +127,28 @@ export class LogicFunctionResourceService {
return {
handlerName: 'main',
sourceHandlerPath,
builtHandlerPath,
checksum,
};
}
async updateSourceFiles({
async uploadSourceFile({
sourceHandlerPath,
workspaceId,
applicationUniversalIdentifier,
code,
sourceHandlerCode,
}: UpdateSourceFilesParams): Promise<void> {
const temporaryDirManager = new TemporaryDirManager();
try {
const { sourceTemporaryDir } = await temporaryDirManager.init();
await this.writeSourcesToLocalFolder(code, sourceTemporaryDir);
const baseFolderPath = getLogicFunctionBaseFolderPath(sourceHandlerPath);
await this.fileStorageService.uploadFolder({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: baseFolderPath,
localPath: sourceTemporaryDir,
});
} finally {
await temporaryDirManager.clean();
}
await this.fileStorageService.writeFile({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: sourceHandlerPath,
sourceFile: sourceHandlerCode,
settings: { isTemporaryFile: false, toDelete: false },
mimeType: 'application/typescript',
});
}
async buildFromSource({
async buildFromSourceFile({
sourceHandlerPath,
builtHandlerPath,
workspaceId,
@@ -182,29 +159,18 @@ export class LogicFunctionResourceService {
try {
const { sourceTemporaryDir } = await temporaryDirManager.init();
const baseFolderPath = getLogicFunctionBaseFolderPath(sourceHandlerPath);
await this.fileStorageService.downloadFolder({
await this.fileStorageService.downloadFile({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: baseFolderPath,
localPath: sourceTemporaryDir,
resourcePath: sourceHandlerPath,
localPath: join(sourceTemporaryDir, sourceHandlerPath),
});
const relativeSourcePath = getRelativePathFromBase(
sourceHandlerPath,
baseFolderPath,
);
const relativeBuiltPath = getRelativePathFromBase(
builtHandlerPath,
baseFolderPath,
);
const builtBundleFilePath = await this.buildInMemory({
sourceTemporaryDir,
sourceHandlerPath: relativeSourcePath,
builtHandlerPath: relativeBuiltPath,
sourceHandlerPath,
builtHandlerPath,
});
const builtFile = await fs.readFile(builtBundleFilePath, 'utf-8');
@@ -230,20 +196,22 @@ export class LogicFunctionResourceService {
}
}
async getSourceCode({
async getSourceFile({
sourceHandlerPath,
workspaceId,
applicationUniversalIdentifier,
}: GetSourceCodeParams): Promise<Sources | null> {
const baseFolderPath = getLogicFunctionBaseFolderPath(sourceHandlerPath);
}: GetSourceCodeParams): Promise<string | null> {
try {
return await this.fileStorageService.readFolder({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: baseFolderPath,
});
return (
await streamToBuffer(
await this.fileStorageService.readFile({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: sourceHandlerPath,
}),
)
).toString('utf-8');
} catch (error) {
if (
isDefined(error) &&
@@ -259,34 +227,24 @@ export class LogicFunctionResourceService {
async copyResources({
fromSourceHandlerPath,
fromBuiltHandlerPath,
toSourceHandlerPath,
fromBuiltHandlerPath,
toBuiltHandlerPath,
workspaceId,
applicationUniversalIdentifier,
}: CopySourceParams): Promise<void> {
const fromSourceBaseFolderPath = getLogicFunctionBaseFolderPath(
fromSourceHandlerPath,
);
const toSourceBaseFolderPath =
getLogicFunctionBaseFolderPath(toSourceHandlerPath);
const fromBuiltBaseFolderPath =
getLogicFunctionBaseFolderPath(fromBuiltHandlerPath);
const toBuiltBaseFolderPath =
getLogicFunctionBaseFolderPath(toBuiltHandlerPath);
await this.fileStorageService.copy({
from: {
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: fromSourceBaseFolderPath,
resourcePath: fromSourceHandlerPath,
},
to: {
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.Source,
resourcePath: toSourceBaseFolderPath,
resourcePath: toSourceHandlerPath,
},
});
@@ -295,13 +253,13 @@ export class LogicFunctionResourceService {
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.BuiltLogicFunction,
resourcePath: fromBuiltBaseFolderPath,
resourcePath: fromBuiltHandlerPath,
},
to: {
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.BuiltLogicFunction,
resourcePath: toBuiltBaseFolderPath,
resourcePath: toBuiltHandlerPath,
},
});
}
@@ -337,11 +295,7 @@ export class LogicFunctionResourceService {
builtHandlerPath,
workspaceId,
applicationUniversalIdentifier,
}: {
builtHandlerPath: string;
workspaceId: string;
applicationUniversalIdentifier: string;
}): Promise<string> {
}: GetBuiltCodeParams): Promise<string> {
return (
await streamToBuffer(
await this.fileStorageService.readFile({
@@ -359,36 +313,20 @@ export class LogicFunctionResourceService {
workspaceId,
applicationUniversalIdentifier,
inMemoryDestinationPath,
}: {
builtHandlerPath: string;
workspaceId: string;
applicationUniversalIdentifier: string;
}: GetBuiltCodeParams & {
inMemoryDestinationPath: string;
}): Promise<void> {
}): Promise<string> {
const localPath = join(inMemoryDestinationPath, builtHandlerPath);
await this.fileStorageService.downloadFile({
workspaceId,
applicationUniversalIdentifier,
fileFolder: FileFolder.BuiltLogicFunction,
resourcePath: builtHandlerPath,
localPath: inMemoryDestinationPath,
localPath,
});
}
async writeSourcesToLocalFolder(
sources: Sources,
localPath: string,
): Promise<void> {
for (const key of Object.keys(sources)) {
const filePath = join(localPath, key);
const value = sources[key];
if (isObject(value)) {
await this.writeSourcesToLocalFolder(value as Sources, filePath);
continue;
}
await fs.mkdir(dirname(filePath), { recursive: true });
await fs.writeFile(filePath, value);
}
return localPath;
}
private async buildInMemory({
@@ -84,6 +84,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
"handlerName",
"toolInputSchema",
"isTool",
"isBuildUpToDate",
"deletedAt",
"cronTriggerSettings",
"databaseEventTriggerSettings",
@@ -149,6 +149,7 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
handlerName: { toStringify: false, universalProperty: undefined },
toolInputSchema: { toStringify: true, universalProperty: undefined },
isTool: { toStringify: false, universalProperty: undefined },
isBuildUpToDate: { toStringify: false, universalProperty: undefined },
deletedAt: { toStringify: false, universalProperty: undefined },
cronTriggerSettings: { toStringify: true, universalProperty: undefined },
databaseEventTriggerSettings: {
@@ -12,4 +12,5 @@ export const FLAT_LOGIC_FUNCTION_EDITABLE_PROPERTIES = [
'cronTriggerSettings',
'databaseEventTriggerSettings',
'httpRouteTriggerSettings',
] as const satisfies (MetadataEntityPropertyName<'logicFunction'> | 'code')[];
'isBuildUpToDate',
] as const satisfies MetadataEntityPropertyName<'logicFunction'>[];
@@ -0,0 +1,3 @@
export const DEFAULT_BUILT_HANDLER_PATH = 'src/index.mjs';
export const DEFAULT_SOURCE_HANDLER_PATH = 'src/index.ts';
export const DEFAULT_HANDLER_NAME = 'main';
@@ -1,52 +0,0 @@
import { Field, HideField, InputType } from '@nestjs/graphql';
import {
IsBoolean,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
Max,
Min,
} from 'class-validator';
import graphqlTypeJson from 'graphql-type-json';
@InputType()
export class CreateDefaultLogicFunctionInput {
@IsString()
@IsNotEmpty()
@Field()
name: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
description?: string;
@IsNumber()
@Field({ nullable: true })
@Min(1)
@Max(900)
@IsOptional()
timeoutSeconds?: number;
@HideField()
applicationId?: string;
@HideField()
universalIdentifier?: string;
@HideField()
id?: string;
@Field(() => graphqlTypeJson, { nullable: true })
@IsObject()
@IsOptional()
toolInputSchema?: object;
@IsBoolean()
@Field({ nullable: true })
@IsOptional()
isTool?: boolean;
}
@@ -0,0 +1,83 @@
import { Field, InputType } from '@nestjs/graphql';
import {
IsBoolean,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
IsUUID,
Max,
Min,
} from 'class-validator';
import graphqlTypeJson from 'graphql-type-json';
import {
CronTriggerSettings,
DatabaseEventTriggerSettings,
HttpRouteTriggerSettings,
} from 'twenty-shared/application';
import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { LogicFunctionSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-source.input';
@InputType()
export class CreateLogicFunctionFromSourceInput {
@IsUUID()
@IsOptional()
@Field(() => UUIDScalarType, { nullable: true })
id?: string;
@IsUUID()
@IsOptional()
@Field(() => UUIDScalarType, { nullable: true })
universalIdentifier?: string;
@IsString()
@IsNotEmpty()
@Field()
name: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
description?: string;
@IsNumber()
@Field({ nullable: true })
@Min(1)
@Max(900)
@IsOptional()
timeoutSeconds?: number;
@Field(() => graphqlTypeJson, { nullable: true })
@IsObject()
@IsOptional()
toolInputSchema?: object;
@IsBoolean()
@Field({ nullable: true })
@IsOptional()
isTool?: boolean;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
source?: JsonbProperty<LogicFunctionSourceInput>;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
cronTriggerSettings?: JsonbProperty<CronTriggerSettings>;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
databaseEventTriggerSettings?: JsonbProperty<DatabaseEventTriggerSettings>;
@IsObject()
@Field(() => graphqlTypeJson, { nullable: true })
@IsOptional()
httpRouteTriggerSettings?: JsonbProperty<HttpRouteTriggerSettings>;
}
@@ -1,6 +1,16 @@
import { Field, HideField, InputType } from '@nestjs/graphql';
import { Field, InputType } from '@nestjs/graphql';
import { IsObject, IsOptional, IsString } from 'class-validator';
import {
IsBoolean,
IsNotEmpty,
IsNumber,
IsObject,
IsOptional,
IsString,
IsUUID,
Max,
Min,
} from 'class-validator';
import graphqlTypeJson from 'graphql-type-json';
import {
CronTriggerSettings,
@@ -9,12 +19,54 @@ import {
} from 'twenty-shared/application';
import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
import { CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class CreateLogicFunctionInput extends CreateDefaultLogicFunctionInput {
@HideField()
checksum: string;
export class CreateLogicFunction {
@IsUUID()
@IsOptional()
@Field(() => UUIDScalarType, { nullable: true })
id?: string;
@IsUUID()
@IsOptional()
@Field(() => UUIDScalarType, { nullable: true })
universalIdentifier?: string;
@IsString()
@IsNotEmpty()
@Field()
name: string;
@IsString()
@IsOptional()
@Field({ nullable: true })
description?: string;
@IsNumber()
@Field({ nullable: true })
@Min(1)
@Max(900)
@IsOptional()
timeoutSeconds?: number;
@Field(() => graphqlTypeJson, { nullable: false })
@IsObject()
toolInputSchema: object;
@IsBoolean()
@Field({ nullable: true })
@IsOptional()
isTool?: boolean;
@IsBoolean()
@Field({ nullable: false })
isBuildUpToDate: boolean;
@IsString()
@Field({ nullable: true })
@IsOptional()
checksum?: string;
@IsString()
@Field({ nullable: false })
@@ -1,12 +1,6 @@
import { Field, InputType } from '@nestjs/graphql';
import {
IsBoolean,
IsNotEmpty,
IsObject,
IsOptional,
IsUUID,
} from 'class-validator';
import { IsNotEmpty, IsObject, IsUUID } from 'class-validator';
import graphqlTypeJson from 'graphql-type-json';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@@ -25,12 +19,4 @@ export class ExecuteOneLogicFunctionInput {
})
@IsObject()
payload: JSON;
@Field(() => Boolean, {
description: 'Force rebuild from source before executing',
nullable: true,
})
@IsOptional()
@IsBoolean()
forceRebuild?: boolean;
}
@@ -0,0 +1,19 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsObject, IsString } from 'class-validator';
import graphqlTypeJson from 'graphql-type-json';
@InputType()
export class LogicFunctionSourceInput {
@IsString()
@Field({ nullable: false })
sourceHandlerCode: string;
@Field(() => graphqlTypeJson, { nullable: false })
@IsObject()
toolInputSchema: object;
@IsString()
@Field({ nullable: false })
handlerName: string;
}
@@ -62,10 +62,6 @@ export class LogicFunctionDTO {
@Field()
sourceHandlerPath: string;
@IsString()
@Field()
builtHandlerPath: string;
@IsString()
@Field()
handlerName: string;
@@ -24,9 +24,9 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/
import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
@InputType()
class UpdateLogicFunctionInputUpdates {
class UpdateLogicFunctionFromSourceInputUpdates {
@IsString()
@Field()
@Field({ nullable: true })
@IsOptional()
name?: string;
@@ -42,6 +42,16 @@ class UpdateLogicFunctionInputUpdates {
@IsOptional()
timeoutSeconds?: number;
@IsString()
@Field({ nullable: true })
@IsOptional()
sourceHandlerCode?: string;
@Field(() => graphqlTypeJson, { nullable: true })
@IsObject()
@IsOptional()
toolInputSchema?: object;
@IsString()
@Field({ nullable: true })
@IsOptional()
@@ -52,21 +62,16 @@ class UpdateLogicFunctionInputUpdates {
@IsOptional()
sourceHandlerPath?: string;
@IsString()
@Field({ nullable: true })
@IsOptional()
builtHandlerPath?: string;
@Field(() => graphqlTypeJson, { nullable: true })
@IsObject()
@IsOptional()
toolInputSchema?: object;
@IsBoolean()
@Field({ nullable: true })
@IsOptional()
isTool?: boolean;
@IsBoolean()
@Field({ nullable: true })
@IsOptional()
isBuildUpToDate?: boolean;
@IsString()
@Field({ nullable: true })
@IsOptional()
@@ -89,7 +94,7 @@ class UpdateLogicFunctionInputUpdates {
}
@InputType()
export class UpdateLogicFunctionSourceInput {
export class UpdateLogicFunctionFromSourceInput {
@Field(() => UUIDScalarType, {
description: 'Id of the logic function to update',
})
@@ -97,10 +102,10 @@ export class UpdateLogicFunctionSourceInput {
@IsUUID()
id: string;
@Type(() => UpdateLogicFunctionInputUpdates)
@Type(() => UpdateLogicFunctionFromSourceInputUpdates)
@ValidateNested()
@Field(() => UpdateLogicFunctionInputUpdates, {
@Field(() => UpdateLogicFunctionFromSourceInputUpdates, {
description: 'The logic function updates',
})
update: UpdateLogicFunctionInputUpdates;
update: UpdateLogicFunctionFromSourceInputUpdates;
}
@@ -1,25 +0,0 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty, IsObject, IsUUID } from 'class-validator';
import graphqlTypeJson from 'graphql-type-json';
import type { Sources } from 'twenty-shared/types';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class UpdateLogicFunctionSourceInput {
@Field(() => UUIDScalarType, {
description: 'The id of the logic function.',
})
@IsNotEmpty()
@IsUUID()
id!: string;
@Field(() => graphqlTypeJson, {
description:
'The source code (Sources) to write. Only updates source files.',
})
@IsObject()
code!: Sources;
}
@@ -24,10 +24,6 @@ export enum LogicFunctionRuntime {
NODE22 = 'nodejs22.x',
}
export const DEFAULT_SOURCE_HANDLER_PATH = 'src/index.ts';
export const DEFAULT_BUILT_HANDLER_PATH = 'src/index.mjs';
export const DEFAULT_HANDLER_NAME = 'main';
@Entity('logicFunction')
@Index('IDX_LOGIC_FUNCTION_ID_DELETED_AT', ['id', 'deletedAt'])
export class LogicFunctionEntity
@@ -40,13 +36,13 @@ export class LogicFunctionEntity
@Column({ nullable: false })
name: string;
@Column({ nullable: false, default: DEFAULT_SOURCE_HANDLER_PATH })
@Column({ nullable: false })
sourceHandlerPath: string;
@Column({ nullable: false, default: DEFAULT_BUILT_HANDLER_PATH })
@Column({ nullable: false })
builtHandlerPath: string;
@Column({ nullable: false, default: DEFAULT_HANDLER_NAME })
@Column({ nullable: false })
handlerName: string;
@Column({ nullable: true, type: 'varchar' })
@@ -68,6 +64,9 @@ export class LogicFunctionEntity
@Column({ nullable: false, default: false })
isTool: boolean;
@Column({ nullable: false, type: 'boolean', default: true })
isBuildUpToDate: boolean;
@Column({ nullable: true, type: 'jsonb' })
cronTriggerSettings: JsonbProperty<CronTriggerSettings> | null;
@@ -3,7 +3,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { ApplicationLayerModule } from 'src/engine/core-modules/application-layer/application-layer.module';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
@@ -19,7 +18,7 @@ import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-func
import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
import { LogicFunctionResolver } from 'src/engine/metadata-modules/logic-function/logic-function.resolver';
import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service';
import { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service';
import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service';
import { WorkspaceFlatLogicFunctionMapCacheService } from 'src/engine/metadata-modules/logic-function/services/workspace-flat-logic-function-map-cache.service';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { SubscriptionsModule } from 'src/engine/subscriptions/subscriptions.module';
@@ -32,7 +31,6 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
TypeOrmModule.forFeature([ApplicationEntity, FeatureFlagEntity]),
ThrottlerModule,
ApplicationModule,
ApplicationLayerModule,
AuditModule,
FeatureFlagModule,
PermissionsModule,
@@ -46,10 +44,10 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
],
providers: [
LogicFunctionMetadataService,
LogicFunctionService,
LogicFunctionFromSourceService,
LogicFunctionResolver,
WorkspaceFlatLogicFunctionMapCacheService,
],
exports: [LogicFunctionMetadataService, LogicFunctionService],
exports: [LogicFunctionMetadataService, LogicFunctionFromSourceService],
})
export class LogicFunctionModule {}
@@ -14,18 +14,19 @@ import { FeatureFlagGuard } from 'src/engine/guards/feature-flag.guard';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input';
import { ExecuteOneLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/execute-logic-function.input';
import { LogicFunctionExecutionResultDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
import { LogicFunctionIdInput } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-id.input';
import { LogicFunctionLogsDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-logs.dto';
import { LogicFunctionLogsInput } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-logs.input';
import { LogicFunctionDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function.dto';
import { UpdateLogicFunctionSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-source.input';
import { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service';
import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service';
import { logicFunctionGraphQLApiExceptionHandler } from 'src/engine/metadata-modules/logic-function/utils/logic-function-graphql-api-exception-handler.utils';
import { SubscriptionChannel } from 'src/engine/subscriptions/enums/subscription-channel.enum';
import { SubscriptionService } from 'src/engine/subscriptions/subscription.service';
import { CreateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input';
import { UpdateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input';
import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service';
@UseGuards(WorkspaceAuthGuard, FeatureFlagGuard, NoPermissionGuard)
@MetadataResolver()
@@ -33,7 +34,8 @@ import { SubscriptionService } from 'src/engine/subscriptions/subscription.servi
@UseFilters(PreventNestToAutoLogGraphqlErrorsFilter)
export class LogicFunctionResolver {
constructor(
private readonly logicFunctionService: LogicFunctionService,
private readonly logicFunctionFromSourceService: LogicFunctionFromSourceService,
private readonly logicFunctionMetadataService: LogicFunctionMetadataService,
private readonly subscriptionService: SubscriptionService,
) {}
@@ -43,7 +45,10 @@ export class LogicFunctionResolver {
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<LogicFunctionDTO> {
try {
return await this.logicFunctionService.findOne({ id, workspaceId });
return await this.logicFunctionMetadataService.findOne({
id,
workspaceId,
});
} catch (error) {
return logicFunctionGraphQLApiExceptionHandler(error);
}
@@ -54,7 +59,9 @@ export class LogicFunctionResolver {
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<LogicFunctionDTO[]> {
try {
return await this.logicFunctionService.findMany({ workspaceId });
return await this.logicFunctionMetadataService.findMany({
workspaceId,
});
} catch (error) {
return logicFunctionGraphQLApiExceptionHandler(error);
}
@@ -67,8 +74,8 @@ export class LogicFunctionResolver {
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
) {
try {
return await this.logicFunctionService.getAvailablePackages({
id,
return await this.logicFunctionMetadataService.getAvailablePackages({
logicFunctionId: id,
workspaceId,
});
} catch (error) {
@@ -83,7 +90,10 @@ export class LogicFunctionResolver {
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<LogicFunctionDTO> {
try {
return await this.logicFunctionService.deleteOne({ id, workspaceId });
return await this.logicFunctionFromSourceService.deleteOne({
id,
workspaceId,
});
} catch (error) {
return logicFunctionGraphQLApiExceptionHandler(error);
}
@@ -91,12 +101,12 @@ export class LogicFunctionResolver {
@Mutation(() => LogicFunctionDTO)
@UseGuards(SettingsPermissionGuard(PermissionFlagType.WORKFLOWS))
async createDefaultLogicFunction(
@Args('input') input: CreateDefaultLogicFunctionInput,
async createOneLogicFunction(
@Args('input') input: CreateLogicFunctionFromSourceInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<LogicFunctionDTO> {
try {
return await this.logicFunctionService.createDefault({
return await this.logicFunctionFromSourceService.createOne({
input,
workspaceId,
});
@@ -108,14 +118,13 @@ export class LogicFunctionResolver {
@Mutation(() => LogicFunctionExecutionResultDTO)
@UseGuards(SettingsPermissionGuard(PermissionFlagType.WORKFLOWS))
async executeOneLogicFunction(
@Args('input') { id, payload, forceRebuild }: ExecuteOneLogicFunctionInput,
@Args('input') { id, payload }: ExecuteOneLogicFunctionInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<LogicFunctionExecutionResultDTO> {
try {
return await this.logicFunctionService.executeOne({
return await this.logicFunctionFromSourceService.executeOne({
id,
payload,
forceRebuild,
workspaceId,
});
} catch (error) {
@@ -123,14 +132,14 @@ export class LogicFunctionResolver {
}
}
@Query(() => graphqlTypeJson, { nullable: true })
@Query(() => String, { nullable: true })
@UseGuards(SettingsPermissionGuard(PermissionFlagType.WORKFLOWS))
async getLogicFunctionSourceCode(
@Args('input') { id }: LogicFunctionIdInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
) {
try {
return await this.logicFunctionService.getSourceCode({
return await this.logicFunctionFromSourceService.getSourceCode({
id,
workspaceId,
});
@@ -141,14 +150,14 @@ export class LogicFunctionResolver {
@Mutation(() => Boolean)
@UseGuards(SettingsPermissionGuard(PermissionFlagType.WORKFLOWS))
async updateLogicFunctionSource(
@Args('input') { id, code }: UpdateLogicFunctionSourceInput,
async updateOneLogicFunction(
@Args('input') { id, update }: UpdateLogicFunctionFromSourceInput,
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
): Promise<boolean> {
try {
await this.logicFunctionService.updateLogicFunctionSource({
await this.logicFunctionFromSourceService.updateOne({
id,
code,
update,
workspaceId,
});
@@ -1,93 +1,37 @@
import { Injectable } from '@nestjs/common';
import { Sources } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { join } from 'path';
import { v4 } from 'uuid';
import { ApplicationLayerService } from 'src/engine/core-modules/application-layer/application-layer.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { LogicFunctionExecutorService } from 'src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service';
import { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input';
import { LogicFunctionExecutionResultDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto';
import { LogicFunctionDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function.dto';
import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service';
import { FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util';
import { fromFlatLogicFunctionToLogicFunctionDto } from 'src/engine/metadata-modules/logic-function/utils/from-flat-logic-function-to-logic-function-dto.util';
import { CreateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input';
import { SEED_LOGIC_FUNCTION_INPUT_SCHEMA } from 'src/engine/core-modules/logic-function/logic-function-resource/constants/seed-logic-function-input-schema';
import { UpdateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input';
import { getLogicFunctionSubfolderForFromSource } from 'src/engine/metadata-modules/logic-function/utils/get-logic-function-subfolder-for-from-source';
import {
DEFAULT_BUILT_HANDLER_PATH,
DEFAULT_SOURCE_HANDLER_PATH,
} from 'src/engine/metadata-modules/logic-function/constants/handler.contant';
@Injectable()
export class LogicFunctionService {
export class LogicFunctionFromSourceService {
constructor(
private readonly logicFunctionExecutorService: LogicFunctionExecutorService,
private readonly logicFunctionMetadataService: LogicFunctionMetadataService,
private readonly logicFunctionResourceService: LogicFunctionResourceService,
private readonly applicationService: ApplicationService,
private readonly applicationLayerService: ApplicationLayerService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
) {}
async findOne({
id,
workspaceId,
}: {
id: string;
workspaceId: string;
}): Promise<LogicFunctionDTO> {
const { flatLogicFunctionMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatLogicFunctionMaps'],
},
);
const flatLogicFunction = findFlatLogicFunctionOrThrow({
id,
flatLogicFunctionMaps,
});
return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction });
}
async findMany({
workspaceId,
}: {
workspaceId: string;
}): Promise<LogicFunctionDTO[]> {
const { flatLogicFunctionMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatLogicFunctionMaps'],
},
);
return Object.values(flatLogicFunctionMaps.byUniversalIdentifier)
.filter(
(flatLogicFunction): flatLogicFunction is FlatLogicFunction =>
isDefined(flatLogicFunction) &&
!isDefined(flatLogicFunction.deletedAt),
)
.map((flatLogicFunction) =>
fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }),
);
}
async getAvailablePackages({
id,
workspaceId,
}: {
id: string;
workspaceId: string;
}) {
return this.applicationLayerService.getAvailablePackages({
logicFunctionId: id,
workspaceId,
});
}
async deleteOne({
id,
workspaceId,
@@ -104,11 +48,27 @@ export class LogicFunctionService {
return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction });
}
async createDefault({
private getHandlerPaths(logicFunctionId: string) {
const logicFunctionSubfolder =
getLogicFunctionSubfolderForFromSource(logicFunctionId);
return {
sourceHandlerPath: join(
logicFunctionSubfolder,
DEFAULT_SOURCE_HANDLER_PATH,
),
builtHandlerPath: join(
logicFunctionSubfolder,
DEFAULT_BUILT_HANDLER_PATH,
),
};
}
async createOne({
input,
workspaceId,
}: {
input: CreateDefaultLogicFunctionInput;
input: CreateLogicFunctionFromSourceInput;
workspaceId: string;
}): Promise<LogicFunctionDTO> {
const { workspaceCustomFlatApplication } =
@@ -118,9 +78,40 @@ export class LogicFunctionService {
const logicFunctionId = input.id ?? v4();
const { sourceHandlerPath, builtHandlerPath, handlerName, checksum } =
const { sourceHandlerPath, builtHandlerPath } =
this.getHandlerPaths(logicFunctionId);
if (input.source) {
await this.logicFunctionResourceService.uploadSourceFile({
sourceHandlerPath,
sourceHandlerCode: input.source.sourceHandlerCode,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
workspaceId,
});
const flatLogicFunction =
await this.logicFunctionMetadataService.createOne({
input: {
...input,
handlerName: input.source.handlerName,
toolInputSchema: input.source.toolInputSchema,
sourceHandlerPath,
builtHandlerPath,
id: logicFunctionId,
isBuildUpToDate: false,
},
workspaceId,
ownerFlatApplication: workspaceCustomFlatApplication,
});
return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction });
}
const { handlerName, checksum } =
await this.logicFunctionResourceService.seedSourceFiles({
sourceSubfolder: logicFunctionId,
sourceHandlerPath,
builtHandlerPath,
workspaceId,
applicationUniversalIdentifier:
workspaceCustomFlatApplication.universalIdentifier,
@@ -135,6 +126,8 @@ export class LogicFunctionService {
builtHandlerPath,
handlerName,
checksum,
toolInputSchema: SEED_LOGIC_FUNCTION_INPUT_SCHEMA,
isBuildUpToDate: true,
},
workspaceId,
ownerFlatApplication: workspaceCustomFlatApplication,
@@ -144,35 +137,84 @@ export class LogicFunctionService {
return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction });
}
// TODO: remove forceRebuild parameter and add a column called shouldRebuild or isBuiltUpToDate
async updateOne({
id,
update,
workspaceId,
}: {
id: string;
update: UpdateLogicFunctionFromSourceInput['update'];
workspaceId: string;
}): Promise<void> {
const { applicationUniversalIdentifier, flatLogicFunction } =
await this.getLogicFunctionContext({ id, workspaceId });
let formattedUpdate: UpdateLogicFunctionFromSourceInput['update'] = {
...update,
};
if (update.sourceHandlerCode) {
await this.logicFunctionResourceService.uploadSourceFile({
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
sourceHandlerCode: update.sourceHandlerCode,
applicationUniversalIdentifier,
workspaceId,
});
formattedUpdate = {
...formattedUpdate,
isBuildUpToDate: false,
};
}
await this.logicFunctionMetadataService.updateOne({
id,
update: formattedUpdate,
workspaceId,
});
}
async buildOneFromSource({
id,
workspaceId,
}: {
id: string;
workspaceId: string;
}): Promise<void> {
const { flatLogicFunction, applicationUniversalIdentifier } =
await this.getLogicFunctionContext({ id, workspaceId });
const { checksum } =
await this.logicFunctionResourceService.buildFromSourceFile({
workspaceId,
applicationUniversalIdentifier,
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
builtHandlerPath: flatLogicFunction.builtHandlerPath,
});
await this.logicFunctionMetadataService.updateOne({
id,
update: { checksum, isBuildUpToDate: true },
workspaceId,
});
}
async executeOne({
id,
payload,
forceRebuild,
workspaceId,
}: {
id: string;
payload: object;
forceRebuild?: boolean;
workspaceId: string;
}): Promise<LogicFunctionExecutionResultDTO> {
if (forceRebuild) {
const { flatLogicFunction, applicationUniversalIdentifier } =
await this.getLogicFunctionContext({ id, workspaceId });
const { flatLogicFunction } = await this.getLogicFunctionContext({
id,
workspaceId,
});
const { checksum } =
await this.logicFunctionResourceService.buildFromSource({
workspaceId,
applicationUniversalIdentifier,
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
builtHandlerPath: flatLogicFunction.builtHandlerPath,
});
await this.logicFunctionMetadataService.updateChecksum({
id,
checksum,
workspaceId,
});
if (!flatLogicFunction.isBuildUpToDate) {
await this.buildOneFromSource({ workspaceId, id });
}
const result = await this.logicFunctionExecutorService.execute({
@@ -204,37 +246,17 @@ export class LogicFunctionService {
}: {
id: string;
workspaceId: string;
}): Promise<Sources | null> {
}): Promise<string | null> {
const { flatLogicFunction, applicationUniversalIdentifier } =
await this.getLogicFunctionContext({ id, workspaceId });
return this.logicFunctionResourceService.getSourceCode({
return this.logicFunctionResourceService.getSourceFile({
workspaceId,
applicationUniversalIdentifier,
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
});
}
async updateLogicFunctionSource({
id,
code,
workspaceId,
}: {
id: string;
code: Sources;
workspaceId: string;
}): Promise<void> {
const { flatLogicFunction, applicationUniversalIdentifier } =
await this.getLogicFunctionContext({ id, workspaceId });
await this.logicFunctionResourceService.updateSourceFiles({
workspaceId,
applicationUniversalIdentifier,
sourceHandlerPath: flatLogicFunction.sourceHandlerPath,
code,
});
}
private async getLogicFunctionContext({
id,
workspaceId,
@@ -7,18 +7,20 @@ import { type FlatApplication } from 'src/engine/core-modules/application/types/
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import type { CreateLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function.input';
import type { UpdateLogicFunctionSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function.input';
import {
LogicFunctionException,
LogicFunctionExceptionCode,
} from 'src/engine/metadata-modules/logic-function/logic-function.exception';
import { FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
import { findFlatLogicFunctionOrThrow } from 'src/engine/metadata-modules/logic-function/utils/find-flat-logic-function-or-throw.util';
import { fromCreateLogicFunctionInputToFlatLogicFunction } from 'src/engine/metadata-modules/logic-function/utils/from-create-logic-function-input-to-flat-logic-function.util';
import { fromCreateLogicFunctionInputToFlatLogicFunction } from 'src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-flat-logic-function.util';
import { fromUpdateLogicFunctionInputToFlatLogicFunctionToUpdateOrThrow } from 'src/engine/metadata-modules/logic-function/utils/from-update-logic-function-input-to-flat-logic-function-to-update-or-throw.util';
import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
import { LogicFunctionDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function.dto';
import { fromFlatLogicFunctionToLogicFunctionDto } from 'src/engine/metadata-modules/logic-function/utils/from-flat-logic-function-to-logic-function-dto.util';
import { CreateLogicFunction } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function.input';
import { UpdateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input';
@Injectable()
export class LogicFunctionMetadataService {
@@ -28,12 +30,59 @@ export class LogicFunctionMetadataService {
private readonly applicationService: ApplicationService,
) {}
async findOne({
id,
workspaceId,
}: {
id: string;
workspaceId: string;
}): Promise<LogicFunctionDTO> {
const { flatLogicFunctionMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatLogicFunctionMaps'],
},
);
const flatLogicFunction = findFlatLogicFunctionOrThrow({
id,
flatLogicFunctionMaps,
});
return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction });
}
async findMany({
workspaceId,
}: {
workspaceId: string;
}): Promise<LogicFunctionDTO[]> {
const { flatLogicFunctionMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatLogicFunctionMaps'],
},
);
return Object.values(flatLogicFunctionMaps.byUniversalIdentifier)
.filter(
(flatLogicFunction): flatLogicFunction is FlatLogicFunction =>
isDefined(flatLogicFunction) &&
!isDefined(flatLogicFunction.deletedAt),
)
.map((flatLogicFunction) =>
fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }),
);
}
async createOne({
input,
workspaceId,
ownerFlatApplication,
}: {
input: Omit<CreateLogicFunctionInput, 'applicationId'>;
input: CreateLogicFunction;
ownerFlatApplication: FlatApplication;
workspaceId: string;
}) {
@@ -86,7 +135,7 @@ export class LogicFunctionMetadataService {
ownerFlatApplication,
}: {
id: string;
update: UpdateLogicFunctionSourceInput['update'];
update: Omit<UpdateLogicFunctionFromSourceInput['update'], 'source'>;
workspaceId: string;
ownerFlatApplication?: FlatApplication;
}) {
@@ -142,64 +191,6 @@ export class LogicFunctionMetadataService {
);
}
async updateChecksum({
id,
checksum,
workspaceId,
}: {
id: string;
checksum: string;
workspaceId: string;
}) {
const resolvedOwnerFlatApplication = (
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{ workspaceId },
)
).workspaceCustomFlatApplication;
const { flatLogicFunctionMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatLogicFunctionMaps'],
},
);
const flatLogicFunction = findFlatLogicFunctionOrThrow({
id,
flatLogicFunctionMaps,
});
const optimisticallyUpdatedFlatLogicFunction = {
...flatLogicFunction,
checksum,
};
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
{
allFlatEntityOperationByMetadataName: {
logicFunction: {
flatEntityToCreate: [],
flatEntityToDelete: [],
flatEntityToUpdate: [optimisticallyUpdatedFlatLogicFunction],
},
},
workspaceId,
isSystemBuild: false,
applicationUniversalIdentifier:
resolvedOwnerFlatApplication.universalIdentifier,
},
);
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderException(
validateAndBuildResult,
'Multiple validation errors occurred while updating logic function checksum',
);
}
}
private async getFlatLogicFunctionAfterUpdate(
id: string,
workspaceId: string,
@@ -285,4 +276,40 @@ export class LogicFunctionMetadataService {
return existingFlatLogicFunction;
}
async getAvailablePackages({
logicFunctionId,
workspaceId,
}: {
logicFunctionId: string;
workspaceId: string;
}) {
const { flatLogicFunctionMaps, flatApplicationMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
workspaceId,
flatMapsKeys: ['flatLogicFunctionMaps', 'flatApplicationMaps'],
},
);
const logicFunctionUniversalIdentifier =
flatLogicFunctionMaps.universalIdentifierById[logicFunctionId];
if (!logicFunctionUniversalIdentifier) {
return {};
}
const logicFunction =
flatLogicFunctionMaps.byUniversalIdentifier[
logicFunctionUniversalIdentifier
];
if (!logicFunction) {
return {};
}
const application = flatApplicationMaps.byId[logicFunction.applicationId];
return application?.availablePackages ?? {};
}
}
@@ -1,8 +1,4 @@
import { type Sources } from 'twenty-shared/types';
import { type FlatEntityFrom } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
import { type LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
export type FlatLogicFunction = FlatEntityFrom<LogicFunctionEntity> & {
code?: Sources;
};
export type FlatLogicFunction = FlatEntityFrom<LogicFunctionEntity>;
@@ -1,15 +1,12 @@
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type CreateLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function.input';
import {
DEFAULT_HANDLER_NAME,
LogicFunctionRuntime,
} from 'src/engine/metadata-modules/logic-function/logic-function.entity';
import { LogicFunctionRuntime } from 'src/engine/metadata-modules/logic-function/logic-function.entity';
import { type FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
import { type CreateLogicFunction } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function.input';
export type FromCreateLogicFunctionInputToFlatLogicFunctionArgs = {
createLogicFunctionInput: Omit<CreateLogicFunctionInput, 'applicationId'>;
createLogicFunctionInput: CreateLogicFunction;
workspaceId: string;
ownerFlatApplication: FlatApplication;
};
@@ -22,14 +19,9 @@ export const fromCreateLogicFunctionInputToFlatLogicFunction = ({
const id = rawCreateLogicFunctionInput.id ?? v4();
const currentDate = new Date();
const sourceHandlerPath = rawCreateLogicFunctionInput.sourceHandlerPath;
const builtHandlerPath = rawCreateLogicFunctionInput.builtHandlerPath;
const universalIdentifier =
rawCreateLogicFunctionInput.universalIdentifier ?? v4();
const checksum = rawCreateLogicFunctionInput.checksum;
return {
id,
cronTriggerSettings:
@@ -40,10 +32,9 @@ export const fromCreateLogicFunctionInputToFlatLogicFunction = ({
rawCreateLogicFunctionInput.httpRouteTriggerSettings ?? null,
name: rawCreateLogicFunctionInput.name,
description: rawCreateLogicFunctionInput.description ?? null,
sourceHandlerPath,
handlerName:
rawCreateLogicFunctionInput.handlerName ?? DEFAULT_HANDLER_NAME,
builtHandlerPath,
sourceHandlerPath: rawCreateLogicFunctionInput.sourceHandlerPath,
handlerName: rawCreateLogicFunctionInput.handlerName,
builtHandlerPath: rawCreateLogicFunctionInput.builtHandlerPath,
universalIdentifier,
createdAt: currentDate.toISOString(),
updatedAt: currentDate.toISOString(),
@@ -52,9 +43,10 @@ export const fromCreateLogicFunctionInputToFlatLogicFunction = ({
runtime: LogicFunctionRuntime.NODE22,
timeoutSeconds: rawCreateLogicFunctionInput.timeoutSeconds ?? 300,
workspaceId,
checksum,
checksum: rawCreateLogicFunctionInput.checksum ?? null,
toolInputSchema: rawCreateLogicFunctionInput.toolInputSchema ?? null,
isTool: rawCreateLogicFunctionInput?.isTool ?? false,
isBuildUpToDate: rawCreateLogicFunctionInput.isBuildUpToDate,
applicationUniversalIdentifier: ownerFlatApplication.universalIdentifier,
};
};

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