From a4ed043d4384f8d9f6091d5ed2ea0c3391e380a3 Mon Sep 17 00:00:00 2001 From: martmull Date: Thu, 12 Feb 2026 11:40:49 +0100 Subject: [PATCH] Logic function refactorization (#17861) As title --- packages/create-twenty-app/package.json | 2 +- .../src/utils/__tests__/app-template.spec.ts | 2 +- .../src/utils/app-template.ts | 2 +- .../src/generated-metadata/graphql.ts | 520 +++++++++--------- .../graphql/fragments/applicationFragment.ts | 2 +- .../constants/IndexFileName.ts | 1 - .../constants/SourceFolderName.ts | 1 - .../fragments/logicFunctionFragment.ts | 1 - .../mutations/createOneLogicFunction.ts | 11 + .../mutations/deleteOneLogicFunction.ts | 2 +- .../mutations/updateLogicFunctionSource.ts | 7 - .../mutations/updateOneLogicFunction.ts | 7 + .../queries/findManyAvailablePackages.ts | 0 .../graphql/queries/findManyLogicFunctions.ts | 4 +- .../graphql/queries/findOneLogicFunction.ts | 4 +- .../useLogicFunctionUpdateFormState.test.ts | 23 +- .../hooks/useExecuteLogicFunction.ts | 12 +- .../hooks/useGetAvailablePackages.ts | 5 +- .../hooks/useGetLogicFunctionSourceCode.ts | 14 +- .../hooks/useGetOneLogicFunction.ts | 28 + .../hooks/useLogicFunctionUpdateFormState.ts | 27 +- .../hooks/usePersistLogicFunction.ts | 62 +-- .../utils/__tests__/computeNewSources.test.ts | 146 ----- .../utils/__tests__/flattenSources.test.ts | 59 -- .../utils/computeNewSources.ts | 48 -- .../logic-functions/utils/flattenSources.ts | 28 - .../SettingsLogicFunctionCodeEditor.tsx | 2 +- .../SettingsLogicFunctionNewForm.tsx | 2 +- .../SettingsLogicFunctionCodeEditorTab.tsx | 12 - .../tabs/SettingsLogicFunctionSettingsTab.tsx | 2 +- .../tabs/SettingsLogicFunctionTriggersTab.tsx | 107 ++-- .../mutations/createDefaultLogicFunction.ts | 13 - .../hooks/useGetOneLogicFunction.ts | 31 -- .../states/logicFunctionsState.ts | 4 +- .../components/MetadataProviderEffect.tsx | 4 +- .../components/WorkflowEditActionCode.tsx | 54 +- .../components/WorkflowReadonlyActionCode.tsx | 13 +- .../WorkflowEditActionLogicFunction.tsx | 2 +- .../ai/components/SettingsToolsTable.tsx | 8 +- .../SettingsLogicFunctionDetail.tsx | 94 +++- .../SettingsLogicFunctionDetail.stories.tsx | 7 +- packages/twenty-sdk/package.json | 2 +- .../cli/__tests__/apps/rich-app/.gitignore | 1 + .../app-dev/expected-manifest.ts | 1 - .../app-dev/expected-manifest.ts | 1 - .../utils/normalize-manifest.util.ts | 1 - .../src/cli/utilities/api/api-service.ts | 2 +- .../__tests__/manifest-validate.spec.ts | 1 - .../build/manifest/manifest-build.ts | 31 +- packages/twenty-server/nest-cli.json | 4 +- ...kfill-application-package-files.command.ts | 2 +- ...-17-migrate-workflow-code-steps.command.ts | 5 +- .../1-17/1-17-seed-workflow-v1-16.command.ts | 8 +- ...1-addLogicFunctionIsBuildUpToDateColumn.ts | 37 ++ .../application-layer.module.ts | 12 - .../application-layer.service.ts | 46 -- .../constants/seed-dependencies-dirname.ts | 2 +- .../constants/seed-dependencies/package.json | 0 .../constants/seed-dependencies/yarn.lock | 0 .../constants/yarn-engine-dirname.ts | 2 +- .../yarn-engine/.yarn/releases/yarn-4.9.2.cjs | 0 .../constants/yarn-engine/.yarnrc.yml | 0 .../services/application-sync.service.ts | 2 +- .../services/application.service.ts | 4 +- ...copy-yarn-engine-and-build-dependencies.ts | 2 +- ...t-to-universal-flat-logic-function.util.ts | 1 + ...default-application-package-fields.util.ts | 4 +- ...es-from-package-json-and-yarn-lock.util.ts | 0 .../services/cache-storage.service.ts | 2 +- .../types/cache-storage-namespace.enum.ts | 1 + .../engine/core-modules/core-engine.module.ts | 2 - .../file-storage/file-storage.service.ts | 125 +---- .../__tests__/read-s3-folder-content.spec.ts | 25 - .../utils/read-s3-folder-content.ts | 31 -- .../drivers/lambda.driver.ts | 11 +- .../drivers/local.driver.ts | 23 +- .../logic-function-driver.interface.ts | 1 + .../utils/call-with-timeout.ts | 14 + .../logic-function-executor.service.ts | 32 +- .../logic-function-resource.service.ts | 188 +++---- ...mpare-and-stringify.constant.spec.ts.snap} | 1 + ...to-compare-and-stringify.constant.spec.ts} | 0 ...configuration-by-metadata-name.constant.ts | 1 + ...c-function-editable-properties.constant.ts | 3 +- .../constants/handler.contant.ts | 3 + .../create-default-logic-function.input.ts | 52 -- ...create-logic-function-from-source.input.ts | 83 +++ .../dtos/create-logic-function.input.ts | 64 ++- .../dtos/execute-logic-function.input.ts | 16 +- .../dtos/logic-function-source.input.ts | 19 + .../logic-function/dtos/logic-function.dto.ts | 4 - ...pdate-logic-function-from-source.input.ts} | 37 +- .../update-logic-function-source.input.ts | 25 - .../logic-function/logic-function.entity.ts | 13 +- .../logic-function/logic-function.module.ts | 8 +- .../logic-function/logic-function.resolver.ts | 51 +- ... => logic-function-from-source.service.ts} | 246 +++++---- .../logic-function-metadata.service.ts | 153 +++--- .../types/flat-logic-function.type.ts | 6 +- ...urce-input-to-flat-logic-function.util.ts} | 24 +- ...gic-function-to-logic-function-dto.util.ts | 1 - ...-logic-function-to-update-or-throw.util.ts | 4 +- ...ogic-function-subfolder-for-from-source.ts | 5 + .../universal-flat-logic-function.type.ts | 6 +- ...ce-migration-logic-function-action.type.ts | 6 +- ...-logic-function-actions-builder.service.ts | 16 +- ...e-logic-function-action-handler.service.ts | 28 +- ...e-logic-function-action-handler.service.ts | 10 + ...e-logic-function-action-handler.service.ts | 51 +- ...-step-operations.workspace-service.spec.ts | 7 +- .../services/code-step-build.service.ts | 101 +--- .../workflow-tool.workspace-service.ts | 6 +- .../update-logic-function-source.tool.ts | 27 +- .../types/workflow-tool-dependencies.type.ts | 4 +- ...and-workspace-creation.integration-spec.ts | 4 +- ...gic-function-execution.integration-spec.ts | 150 +++-- ...fault-logic-function-query-factory.util.ts | 32 -- ...reate-logic-function-query-factory.util.ts | 32 ++ ....util.ts => create-logic-function.util.ts} | 14 +- ...ogic-function-source-query-factory.util.ts | 16 +- .../update-logic-function-source.util.ts | 8 +- .../src/application/manifestType.ts | 2 - 122 files changed, 1441 insertions(+), 1897 deletions(-) delete mode 100644 packages/twenty-front/src/modules/logic-functions/constants/IndexFileName.ts delete mode 100644 packages/twenty-front/src/modules/logic-functions/constants/SourceFolderName.ts rename packages/twenty-front/src/modules/{settings => }/logic-functions/graphql/fragments/logicFunctionFragment.ts (93%) create mode 100644 packages/twenty-front/src/modules/logic-functions/graphql/mutations/createOneLogicFunction.ts rename packages/twenty-front/src/modules/{settings => }/logic-functions/graphql/mutations/deleteOneLogicFunction.ts (70%) delete mode 100644 packages/twenty-front/src/modules/logic-functions/graphql/mutations/updateLogicFunctionSource.ts create mode 100644 packages/twenty-front/src/modules/logic-functions/graphql/mutations/updateOneLogicFunction.ts rename packages/twenty-front/src/modules/{settings => }/logic-functions/graphql/queries/findManyAvailablePackages.ts (100%) rename packages/twenty-front/src/modules/{settings => }/logic-functions/graphql/queries/findManyLogicFunctions.ts (56%) rename packages/twenty-front/src/modules/{settings => }/logic-functions/graphql/queries/findOneLogicFunction.ts (53%) rename packages/twenty-front/src/modules/{settings => }/logic-functions/hooks/__tests__/useLogicFunctionUpdateFormState.test.ts (50%) rename packages/twenty-front/src/modules/{settings => }/logic-functions/hooks/useGetAvailablePackages.ts (64%) create mode 100644 packages/twenty-front/src/modules/logic-functions/hooks/useGetOneLogicFunction.ts rename packages/twenty-front/src/modules/{settings => }/logic-functions/hooks/useLogicFunctionUpdateFormState.ts (61%) delete mode 100644 packages/twenty-front/src/modules/logic-functions/utils/__tests__/computeNewSources.test.ts delete mode 100644 packages/twenty-front/src/modules/logic-functions/utils/__tests__/flattenSources.test.ts delete mode 100644 packages/twenty-front/src/modules/logic-functions/utils/computeNewSources.ts delete mode 100644 packages/twenty-front/src/modules/logic-functions/utils/flattenSources.ts delete mode 100644 packages/twenty-front/src/modules/settings/logic-functions/graphql/mutations/createDefaultLogicFunction.ts delete mode 100644 packages/twenty-front/src/modules/settings/logic-functions/hooks/useGetOneLogicFunction.ts create mode 100644 packages/twenty-sdk/src/cli/__tests__/apps/rich-app/.gitignore create mode 100644 packages/twenty-server/src/database/typeorm/core/migrations/common/1770725043111-addLogicFunctionIsBuildUpToDateColumn.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/application-layer/application-layer.module.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/application-layer/application-layer.service.ts rename packages/twenty-server/src/engine/core-modules/{application-layer => application}/constants/seed-dependencies-dirname.ts (72%) rename packages/twenty-server/src/engine/core-modules/{application-layer => application}/constants/seed-dependencies/package.json (100%) rename packages/twenty-server/src/engine/core-modules/{application-layer => application}/constants/seed-dependencies/yarn.lock (100%) rename packages/twenty-server/src/engine/core-modules/{application-layer => application}/constants/yarn-engine-dirname.ts (73%) rename packages/twenty-server/src/engine/core-modules/{application-layer => application}/constants/yarn-engine/.yarn/releases/yarn-4.9.2.cjs (100%) rename packages/twenty-server/src/engine/core-modules/{application-layer => application}/constants/yarn-engine/.yarnrc.yml (100%) rename packages/twenty-server/src/engine/core-modules/{application-layer => application}/utils/copy-yarn-engine-and-build-dependencies.ts (96%) rename packages/twenty-server/src/engine/core-modules/{application-layer => application}/utils/get-default-application-package-fields.util.ts (89%) rename packages/twenty-server/src/engine/core-modules/{application-layer => application}/utils/parse-available-packages-from-package-json-and-yarn-lock.util.ts (100%) delete mode 100644 packages/twenty-server/src/engine/core-modules/file-storage/utils/__tests__/read-s3-folder-content.spec.ts delete mode 100644 packages/twenty-server/src/engine/core-modules/file-storage/utils/read-s3-folder-content.ts create mode 100644 packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/utils/call-with-timeout.ts rename packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/{all-universal-flat-entity-properties-to-compare-and-stringify.constant.constant.spec.ts.snap => all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap} (99%) rename packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/{all-universal-flat-entity-properties-to-compare-and-stringify.constant.constant.spec.ts => all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts} (100%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/logic-function/constants/handler.contant.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/logic-function-source.input.ts rename packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/{update-logic-function.input.ts => update-logic-function-from-source.input.ts} (83%) delete mode 100644 packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function-source.input.ts rename packages/twenty-server/src/engine/metadata-modules/logic-function/services/{logic-function.service.ts => logic-function-from-source.service.ts} (58%) rename packages/twenty-server/src/engine/metadata-modules/logic-function/utils/{from-create-logic-function-input-to-flat-logic-function.util.ts => from-create-logic-function-from-source-input-to-flat-logic-function.util.ts} (71%) create mode 100644 packages/twenty-server/src/engine/metadata-modules/logic-function/utils/get-logic-function-subfolder-for-from-source.ts delete mode 100644 packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-default-logic-function-query-factory.util.ts create mode 100644 packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-logic-function-query-factory.util.ts rename packages/twenty-server/test/integration/metadata/suites/logic-function/utils/{create-default-logic-function.util.ts => create-logic-function.util.ts} (70%) diff --git a/packages/create-twenty-app/package.json b/packages/create-twenty-app/package.json index 47bb2efea0c..00af37b72d7 100644 --- a/packages/create-twenty-app/package.json +++ b/packages/create-twenty-app/package.json @@ -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", diff --git a/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts b/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts index e1ccbe2654a..df4ff379ebf 100644 --- a/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts +++ b/packages/create-twenty-app/src/utils/__tests__/app-template.spec.ts @@ -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'); }); diff --git a/packages/create-twenty-app/src/utils/app-template.ts b/packages/create-twenty-app/src/utils/app-template.ts index 4905472fcff..36ef91c91ae 100644 --- a/packages/create-twenty-app/src/utils/app-template.ts +++ b/packages/create-twenty-app/src/utils/app-template.ts @@ -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', diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 4639498df1d..65dda9a31ec 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -987,14 +987,6 @@ export type CreateCommandMenuItemInput = { workflowVersionId?: InputMaybe; }; -export type CreateDefaultLogicFunctionInput = { - description?: InputMaybe; - isTool?: InputMaybe; - name: Scalars['String']; - timeoutSeconds?: InputMaybe; - toolInputSchema?: InputMaybe; -}; - export type CreateFieldInput = { defaultValue?: InputMaybe; description?: InputMaybe; @@ -1027,6 +1019,20 @@ export type CreateFrontComponentInput = { sourceComponentPath: Scalars['String']; }; +export type CreateLogicFunctionFromSourceInput = { + cronTriggerSettings?: InputMaybe; + databaseEventTriggerSettings?: InputMaybe; + description?: InputMaybe; + httpRouteTriggerSettings?: InputMaybe; + id?: InputMaybe; + isTool?: InputMaybe; + name: Scalars['String']; + source?: InputMaybe; + timeoutSeconds?: InputMaybe; + toolInputSchema?: InputMaybe; + universalIdentifier?: InputMaybe; +}; + export type CreateNavigationMenuItemInput = { folderId?: InputMaybe; link?: InputMaybe; @@ -1444,8 +1450,6 @@ export type EventWithQueryIds = { }; export type ExecuteOneLogicFunctionInput = { - /** Force rebuild from source before executing */ - forceRebuild?: InputMaybe; /** 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; - builtHandlerPath: Scalars['String']; createdAt: Scalars['DateTime']; cronTriggerSettings?: Maybe; databaseEventTriggerSettings?: Maybe; @@ -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; getIndicatorHealthStatus: AdminPanelHealthServiceData; - getLogicFunctionSourceCode?: Maybe; + getLogicFunctionSourceCode?: Maybe; getMeteredProductsUsage: Array; getPageLayout?: Maybe; 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; + cronTriggerSettings?: InputMaybe; + databaseEventTriggerSettings?: InputMaybe; + description?: InputMaybe; + handlerName?: InputMaybe; + httpRouteTriggerSettings?: InputMaybe; + isBuildUpToDate?: InputMaybe; + isTool?: InputMaybe; + name?: InputMaybe; + sourceHandlerCode?: InputMaybe; + sourceHandlerPath?: InputMaybe; + timeoutSeconds?: InputMaybe; + toolInputSchema?: InputMaybe; }; 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, 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> | 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, 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> | 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, 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> | 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, 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> | 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; export type UploadFilesFieldFileMutationResult = Apollo.MutationResult; export type UploadFilesFieldFileMutationOptions = Apollo.BaseMutationOptions; +export const CreateOneLogicFunctionDocument = gql` + mutation CreateOneLogicFunction($input: CreateLogicFunctionFromSourceInput!) { + createOneLogicFunction(input: $input) { + ...LogicFunctionFields + } +} + ${LogicFunctionFieldsFragmentDoc}`; +export type CreateOneLogicFunctionMutationFn = Apollo.MutationFunction; + +/** + * __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) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(CreateOneLogicFunctionDocument, options); + } +export type CreateOneLogicFunctionMutationHookResult = ReturnType; +export type CreateOneLogicFunctionMutationResult = Apollo.MutationResult; +export type CreateOneLogicFunctionMutationOptions = Apollo.BaseMutationOptions; +export const DeleteOneLogicFunctionDocument = gql` + mutation DeleteOneLogicFunction($input: LogicFunctionIdInput!) { + deleteOneLogicFunction(input: $input) { + ...LogicFunctionFields + } +} + ${LogicFunctionFieldsFragmentDoc}`; +export type DeleteOneLogicFunctionMutationFn = Apollo.MutationFunction; + +/** + * __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) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useMutation(DeleteOneLogicFunctionDocument, options); + } +export type DeleteOneLogicFunctionMutationHookResult = ReturnType; +export type DeleteOneLogicFunctionMutationResult = Apollo.MutationResult; +export type DeleteOneLogicFunctionMutationOptions = Apollo.BaseMutationOptions; 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; export type ExecuteOneLogicFunctionMutationResult = Apollo.MutationResult; export type ExecuteOneLogicFunctionMutationOptions = Apollo.BaseMutationOptions; -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; +export type UpdateOneLogicFunctionMutationFn = Apollo.MutationFunction; /** - * __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) { +export function useUpdateOneLogicFunctionMutation(baseOptions?: Apollo.MutationHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(UpdateLogicFunctionSourceDocument, options); + return Apollo.useMutation(UpdateOneLogicFunctionDocument, options); } -export type UpdateLogicFunctionSourceMutationHookResult = ReturnType; -export type UpdateLogicFunctionSourceMutationResult = Apollo.MutationResult; -export type UpdateLogicFunctionSourceMutationOptions = Apollo.BaseMutationOptions; +export type UpdateOneLogicFunctionMutationHookResult = ReturnType; +export type UpdateOneLogicFunctionMutationResult = Apollo.MutationResult; +export type UpdateOneLogicFunctionMutationOptions = Apollo.BaseMutationOptions; +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) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(FindManyAvailablePackagesDocument, options); + } +export function useFindManyAvailablePackagesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(FindManyAvailablePackagesDocument, options); + } +export type FindManyAvailablePackagesQueryHookResult = ReturnType; +export type FindManyAvailablePackagesLazyQueryHookResult = ReturnType; +export type FindManyAvailablePackagesQueryResult = Apollo.QueryResult; +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) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(FindManyLogicFunctionsDocument, options); + } +export function useFindManyLogicFunctionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(FindManyLogicFunctionsDocument, options); + } +export type FindManyLogicFunctionsQueryHookResult = ReturnType; +export type FindManyLogicFunctionsLazyQueryHookResult = ReturnType; +export type FindManyLogicFunctionsQueryResult = Apollo.QueryResult; +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) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useQuery(FindOneLogicFunctionDocument, options); + } +export function useFindOneLogicFunctionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { + const options = {...defaultOptions, ...baseOptions} + return Apollo.useLazyQuery(FindOneLogicFunctionDocument, options); + } +export type FindOneLogicFunctionQueryHookResult = ReturnType; +export type FindOneLogicFunctionLazyQueryHookResult = ReturnType; +export type FindOneLogicFunctionQueryResult = Apollo.QueryResult; 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; export type UpdateLabPublicFeatureFlagMutationResult = Apollo.MutationResult; export type UpdateLabPublicFeatureFlagMutationOptions = Apollo.BaseMutationOptions; -export const CreateDefaultLogicFunctionItemDocument = gql` - mutation CreateDefaultLogicFunctionItem($input: CreateDefaultLogicFunctionInput!) { - createDefaultLogicFunction(input: $input) { - ...LogicFunctionFields - } -} - ${LogicFunctionFieldsFragmentDoc}`; -export type CreateDefaultLogicFunctionItemMutationFn = Apollo.MutationFunction; - -/** - * __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) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(CreateDefaultLogicFunctionItemDocument, options); - } -export type CreateDefaultLogicFunctionItemMutationHookResult = ReturnType; -export type CreateDefaultLogicFunctionItemMutationResult = Apollo.MutationResult; -export type CreateDefaultLogicFunctionItemMutationOptions = Apollo.BaseMutationOptions; -export const DeleteOneLogicFunctionDocument = gql` - mutation DeleteOneLogicFunction($input: LogicFunctionIdInput!) { - deleteOneLogicFunction(input: $input) { - ...LogicFunctionFields - } -} - ${LogicFunctionFieldsFragmentDoc}`; -export type DeleteOneLogicFunctionMutationFn = Apollo.MutationFunction; - -/** - * __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) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useMutation(DeleteOneLogicFunctionDocument, options); - } -export type DeleteOneLogicFunctionMutationHookResult = ReturnType; -export type DeleteOneLogicFunctionMutationResult = Apollo.MutationResult; -export type DeleteOneLogicFunctionMutationOptions = Apollo.BaseMutationOptions; -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) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(FindManyAvailablePackagesDocument, options); - } -export function useFindManyAvailablePackagesLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(FindManyAvailablePackagesDocument, options); - } -export type FindManyAvailablePackagesQueryHookResult = ReturnType; -export type FindManyAvailablePackagesLazyQueryHookResult = ReturnType; -export type FindManyAvailablePackagesQueryResult = Apollo.QueryResult; -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) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(GetManyLogicFunctionsDocument, options); - } -export function useGetManyLogicFunctionsLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(GetManyLogicFunctionsDocument, options); - } -export type GetManyLogicFunctionsQueryHookResult = ReturnType; -export type GetManyLogicFunctionsLazyQueryHookResult = ReturnType; -export type GetManyLogicFunctionsQueryResult = Apollo.QueryResult; -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) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(GetOneLogicFunctionDocument, options); - } -export function useGetOneLogicFunctionLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { - const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(GetOneLogicFunctionDocument, options); - } -export type GetOneLogicFunctionQueryHookResult = ReturnType; -export type GetOneLogicFunctionLazyQueryHookResult = ReturnType; -export type GetOneLogicFunctionQueryResult = Apollo.QueryResult; export const UploadWorkspaceMemberProfilePictureDocument = gql` mutation UploadWorkspaceMemberProfilePicture($file: Upload!) { uploadWorkspaceMemberProfilePicture(file: $file) { diff --git a/packages/twenty-front/src/modules/applications/graphql/fragments/applicationFragment.ts b/packages/twenty-front/src/modules/applications/graphql/fragments/applicationFragment.ts index aab85c8e3ff..a037cff98f8 100644 --- a/packages/twenty-front/src/modules/applications/graphql/fragments/applicationFragment.ts +++ b/packages/twenty-front/src/modules/applications/graphql/fragments/applicationFragment.ts @@ -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` diff --git a/packages/twenty-front/src/modules/logic-functions/constants/IndexFileName.ts b/packages/twenty-front/src/modules/logic-functions/constants/IndexFileName.ts deleted file mode 100644 index d32b30a4927..00000000000 --- a/packages/twenty-front/src/modules/logic-functions/constants/IndexFileName.ts +++ /dev/null @@ -1 +0,0 @@ -export const INDEX_FILE_NAME = 'index.ts'; diff --git a/packages/twenty-front/src/modules/logic-functions/constants/SourceFolderName.ts b/packages/twenty-front/src/modules/logic-functions/constants/SourceFolderName.ts deleted file mode 100644 index cc32cfe3957..00000000000 --- a/packages/twenty-front/src/modules/logic-functions/constants/SourceFolderName.ts +++ /dev/null @@ -1 +0,0 @@ -export const SOURCE_FOLDER_NAME = 'src'; diff --git a/packages/twenty-front/src/modules/settings/logic-functions/graphql/fragments/logicFunctionFragment.ts b/packages/twenty-front/src/modules/logic-functions/graphql/fragments/logicFunctionFragment.ts similarity index 93% rename from packages/twenty-front/src/modules/settings/logic-functions/graphql/fragments/logicFunctionFragment.ts rename to packages/twenty-front/src/modules/logic-functions/graphql/fragments/logicFunctionFragment.ts index 7cd81e97f8c..b53f98cd6c0 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/graphql/fragments/logicFunctionFragment.ts +++ b/packages/twenty-front/src/modules/logic-functions/graphql/fragments/logicFunctionFragment.ts @@ -8,7 +8,6 @@ export const LOGIC_FUNCTION_FRAGMENT = gql` runtime timeoutSeconds sourceHandlerPath - builtHandlerPath handlerName toolInputSchema isTool diff --git a/packages/twenty-front/src/modules/logic-functions/graphql/mutations/createOneLogicFunction.ts b/packages/twenty-front/src/modules/logic-functions/graphql/mutations/createOneLogicFunction.ts new file mode 100644 index 00000000000..03127dadb92 --- /dev/null +++ b/packages/twenty-front/src/modules/logic-functions/graphql/mutations/createOneLogicFunction.ts @@ -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 + } + } +`; diff --git a/packages/twenty-front/src/modules/settings/logic-functions/graphql/mutations/deleteOneLogicFunction.ts b/packages/twenty-front/src/modules/logic-functions/graphql/mutations/deleteOneLogicFunction.ts similarity index 70% rename from packages/twenty-front/src/modules/settings/logic-functions/graphql/mutations/deleteOneLogicFunction.ts rename to packages/twenty-front/src/modules/logic-functions/graphql/mutations/deleteOneLogicFunction.ts index 3cd87aedc1c..d6f07af2133 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/graphql/mutations/deleteOneLogicFunction.ts +++ b/packages/twenty-front/src/modules/logic-functions/graphql/mutations/deleteOneLogicFunction.ts @@ -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} diff --git a/packages/twenty-front/src/modules/logic-functions/graphql/mutations/updateLogicFunctionSource.ts b/packages/twenty-front/src/modules/logic-functions/graphql/mutations/updateLogicFunctionSource.ts deleted file mode 100644 index 598cbe1807f..00000000000 --- a/packages/twenty-front/src/modules/logic-functions/graphql/mutations/updateLogicFunctionSource.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { gql } from '@apollo/client'; - -export const UPDATE_LOGIC_FUNCTION_SOURCE = gql` - mutation UpdateLogicFunctionSource($input: UpdateLogicFunctionSourceInput!) { - updateLogicFunctionSource(input: $input) - } -`; diff --git a/packages/twenty-front/src/modules/logic-functions/graphql/mutations/updateOneLogicFunction.ts b/packages/twenty-front/src/modules/logic-functions/graphql/mutations/updateOneLogicFunction.ts new file mode 100644 index 00000000000..5054216db40 --- /dev/null +++ b/packages/twenty-front/src/modules/logic-functions/graphql/mutations/updateOneLogicFunction.ts @@ -0,0 +1,7 @@ +import { gql } from '@apollo/client'; + +export const UPDATE_ONE_LOGIC_FUNCTION = gql` + mutation UpdateOneLogicFunction($input: UpdateLogicFunctionFromSourceInput!) { + updateOneLogicFunction(input: $input) + } +`; diff --git a/packages/twenty-front/src/modules/settings/logic-functions/graphql/queries/findManyAvailablePackages.ts b/packages/twenty-front/src/modules/logic-functions/graphql/queries/findManyAvailablePackages.ts similarity index 100% rename from packages/twenty-front/src/modules/settings/logic-functions/graphql/queries/findManyAvailablePackages.ts rename to packages/twenty-front/src/modules/logic-functions/graphql/queries/findManyAvailablePackages.ts diff --git a/packages/twenty-front/src/modules/settings/logic-functions/graphql/queries/findManyLogicFunctions.ts b/packages/twenty-front/src/modules/logic-functions/graphql/queries/findManyLogicFunctions.ts similarity index 56% rename from packages/twenty-front/src/modules/settings/logic-functions/graphql/queries/findManyLogicFunctions.ts rename to packages/twenty-front/src/modules/logic-functions/graphql/queries/findManyLogicFunctions.ts index f003c61527d..3581b467dda 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/graphql/queries/findManyLogicFunctions.ts +++ b/packages/twenty-front/src/modules/logic-functions/graphql/queries/findManyLogicFunctions.ts @@ -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 } diff --git a/packages/twenty-front/src/modules/settings/logic-functions/graphql/queries/findOneLogicFunction.ts b/packages/twenty-front/src/modules/logic-functions/graphql/queries/findOneLogicFunction.ts similarity index 53% rename from packages/twenty-front/src/modules/settings/logic-functions/graphql/queries/findOneLogicFunction.ts rename to packages/twenty-front/src/modules/logic-functions/graphql/queries/findOneLogicFunction.ts index 57969326e66..2670668aa3f 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/graphql/queries/findOneLogicFunction.ts +++ b/packages/twenty-front/src/modules/logic-functions/graphql/queries/findOneLogicFunction.ts @@ -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 } diff --git a/packages/twenty-front/src/modules/settings/logic-functions/hooks/__tests__/useLogicFunctionUpdateFormState.test.ts b/packages/twenty-front/src/modules/logic-functions/hooks/__tests__/useLogicFunctionUpdateFormState.test.ts similarity index 50% rename from packages/twenty-front/src/modules/settings/logic-functions/hooks/__tests__/useLogicFunctionUpdateFormState.test.ts rename to packages/twenty-front/src/modules/logic-functions/hooks/__tests__/useLogicFunctionUpdateFormState.test.ts index a1bfb330d34..910d32f5ecc 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/hooks/__tests__/useLogicFunctionUpdateFormState.test.ts +++ b/packages/twenty-front/src/modules/logic-functions/hooks/__tests__/useLogicFunctionUpdateFormState.test.ts @@ -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 => { 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, }); }); }); diff --git a/packages/twenty-front/src/modules/logic-functions/hooks/useExecuteLogicFunction.ts b/packages/twenty-front/src/modules/logic-functions/hooks/useExecuteLogicFunction.ts index 7b81495e9cb..fc945fa23b7 100644 --- a/packages/twenty-front/src/modules/logic-functions/hooks/useExecuteLogicFunction.ts +++ b/packages/twenty-front/src/modules/logic-functions/hooks/useExecuteLogicFunction.ts @@ -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, }, }, }); diff --git a/packages/twenty-front/src/modules/settings/logic-functions/hooks/useGetAvailablePackages.ts b/packages/twenty-front/src/modules/logic-functions/hooks/useGetAvailablePackages.ts similarity index 64% rename from packages/twenty-front/src/modules/settings/logic-functions/hooks/useGetAvailablePackages.ts rename to packages/twenty-front/src/modules/logic-functions/hooks/useGetAvailablePackages.ts index aadac8ea154..80930944b39 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/hooks/useGetAvailablePackages.ts +++ b/packages/twenty-front/src/modules/logic-functions/hooks/useGetAvailablePackages.ts @@ -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, }, diff --git a/packages/twenty-front/src/modules/logic-functions/hooks/useGetLogicFunctionSourceCode.ts b/packages/twenty-front/src/modules/logic-functions/hooks/useGetLogicFunctionSourceCode.ts index 4754c93867c..557fa5e37d3 100644 --- a/packages/twenty-front/src/modules/logic-functions/hooks/useGetLogicFunctionSourceCode.ts +++ b/packages/twenty-front/src/modules/logic-functions/hooks/useGetLogicFunctionSourceCode.ts @@ -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 }; }; diff --git a/packages/twenty-front/src/modules/logic-functions/hooks/useGetOneLogicFunction.ts b/packages/twenty-front/src/modules/logic-functions/hooks/useGetOneLogicFunction.ts new file mode 100644 index 00000000000..7b58fdc35de --- /dev/null +++ b/packages/twenty-front/src/modules/logic-functions/hooks/useGetOneLogicFunction.ts @@ -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, + }; +}; diff --git a/packages/twenty-front/src/modules/settings/logic-functions/hooks/useLogicFunctionUpdateFormState.ts b/packages/twenty-front/src/modules/logic-functions/hooks/useLogicFunctionUpdateFormState.ts similarity index 61% rename from packages/twenty-front/src/modules/settings/logic-functions/hooks/useLogicFunctionUpdateFormState.ts rename to packages/twenty-front/src/modules/logic-functions/hooks/useLogicFunctionUpdateFormState.ts index f370ed8e74c..3c90fb50e31 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/hooks/useLogicFunctionUpdateFormState.ts +++ b/packages/twenty-front/src/modules/logic-functions/hooks/useLogicFunctionUpdateFormState.ts @@ -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({ 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, }; }; diff --git a/packages/twenty-front/src/modules/logic-functions/hooks/usePersistLogicFunction.ts b/packages/twenty-front/src/modules/logic-functions/hooks/usePersistLogicFunction.ts index 9282dc5bae1..8e2b8606f57 100644 --- a/packages/twenty-front/src/modules/logic-functions/hooks/usePersistLogicFunction.ts +++ b/packages/twenty-front/src/modules/logic-functions/hooks/usePersistLogicFunction.ts @@ -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> + Awaited> > > => { 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> @@ -174,7 +160,7 @@ export const usePersistLogicFunction = () => { return { createLogicFunction, - updateLogicFunctionSource, + updateLogicFunction, deleteLogicFunction, }; }; diff --git a/packages/twenty-front/src/modules/logic-functions/utils/__tests__/computeNewSources.test.ts b/packages/twenty-front/src/modules/logic-functions/utils/__tests__/computeNewSources.test.ts deleted file mode 100644 index 3907ec1ee4d..00000000000 --- a/packages/twenty-front/src/modules/logic-functions/utils/__tests__/computeNewSources.test.ts +++ /dev/null @@ -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); - }); -}); diff --git a/packages/twenty-front/src/modules/logic-functions/utils/__tests__/flattenSources.test.ts b/packages/twenty-front/src/modules/logic-functions/utils/__tests__/flattenSources.test.ts deleted file mode 100644 index dccd103ac37..00000000000 --- a/packages/twenty-front/src/modules/logic-functions/utils/__tests__/flattenSources.test.ts +++ /dev/null @@ -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' }]); - }); -}); diff --git a/packages/twenty-front/src/modules/logic-functions/utils/computeNewSources.ts b/packages/twenty-front/src/modules/logic-functions/utils/computeNewSources.ts deleted file mode 100644 index b68c096b3d5..00000000000 --- a/packages/twenty-front/src/modules/logic-functions/utils/computeNewSources.ts +++ /dev/null @@ -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, - }), - }, - }; -}; diff --git a/packages/twenty-front/src/modules/logic-functions/utils/flattenSources.ts b/packages/twenty-front/src/modules/logic-functions/utils/flattenSources.ts deleted file mode 100644 index 7eb78167113..00000000000 --- a/packages/twenty-front/src/modules/logic-functions/utils/flattenSources.ts +++ /dev/null @@ -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; -}; diff --git a/packages/twenty-front/src/modules/settings/logic-functions/components/SettingsLogicFunctionCodeEditor.tsx b/packages/twenty-front/src/modules/settings/logic-functions/components/SettingsLogicFunctionCodeEditor.tsx index 0bd50311959..d29bdbe14e5 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/components/SettingsLogicFunctionCodeEditor.tsx +++ b/packages/twenty-front/src/modules/settings/logic-functions/components/SettingsLogicFunctionCodeEditor.tsx @@ -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'; diff --git a/packages/twenty-front/src/modules/settings/logic-functions/components/SettingsLogicFunctionNewForm.tsx b/packages/twenty-front/src/modules/settings/logic-functions/components/SettingsLogicFunctionNewForm.tsx index 782db737be9..6c545a1513c 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/components/SettingsLogicFunctionNewForm.tsx +++ b/packages/twenty-front/src/modules/settings/logic-functions/components/SettingsLogicFunctionNewForm.tsx @@ -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'; diff --git a/packages/twenty-front/src/modules/settings/logic-functions/components/tabs/SettingsLogicFunctionCodeEditorTab.tsx b/packages/twenty-front/src/modules/settings/logic-functions/components/tabs/SettingsLogicFunctionCodeEditorTab.tsx index 6ba90364237..19fd3fce97d 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/components/tabs/SettingsLogicFunctionCodeEditorTab.tsx +++ b/packages/twenty-front/src/modules/settings/logic-functions/components/tabs/SettingsLogicFunctionCodeEditorTab.tsx @@ -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 - } /> )} diff --git a/packages/twenty-front/src/modules/settings/logic-functions/components/tabs/SettingsLogicFunctionSettingsTab.tsx b/packages/twenty-front/src/modules/settings/logic-functions/components/tabs/SettingsLogicFunctionSettingsTab.tsx index 502434f0caf..7e7398537ad 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/components/tabs/SettingsLogicFunctionSettingsTab.tsx +++ b/packages/twenty-front/src/modules/settings/logic-functions/components/tabs/SettingsLogicFunctionSettingsTab.tsx @@ -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, diff --git a/packages/twenty-front/src/modules/settings/logic-functions/components/tabs/SettingsLogicFunctionTriggersTab.tsx b/packages/twenty-front/src/modules/settings/logic-functions/components/tabs/SettingsLogicFunctionTriggersTab.tsx index f7b36736e4e..e0f8204f059 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/components/tabs/SettingsLogicFunctionTriggersTab.tsx +++ b/packages/twenty-front/src/modules/settings/logic-functions/components/tabs/SettingsLogicFunctionTriggersTab.tsx @@ -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) && (
- +
)} - {cronTriggers.length > 0 && ( + {isDefined(cronTrigger) && (
- {cronTriggers.map((cronTrigger, index) => ( - {}} - readonly - defaultValue={cronTrigger} - /> - ))} + {}} + readonly + defaultValue={cronTrigger.pattern} + />
)} - {routeTriggers.length > 0 && ( + {isDefined(routeTrigger) && (
{t`Method`} {t`Auth Required`} - {routeTriggers.map((_, _index) => ( - <> - // - // - // - // - // {routeTrigger.httpMethod} - // - // - // - // - ))} + + + + + {routeTrigger.httpMethod} + + + +
)} diff --git a/packages/twenty-front/src/modules/settings/logic-functions/graphql/mutations/createDefaultLogicFunction.ts b/packages/twenty-front/src/modules/settings/logic-functions/graphql/mutations/createDefaultLogicFunction.ts deleted file mode 100644 index 026952a3b96..00000000000 --- a/packages/twenty-front/src/modules/settings/logic-functions/graphql/mutations/createDefaultLogicFunction.ts +++ /dev/null @@ -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 - } - } -`; diff --git a/packages/twenty-front/src/modules/settings/logic-functions/hooks/useGetOneLogicFunction.ts b/packages/twenty-front/src/modules/settings/logic-functions/hooks/useGetOneLogicFunction.ts deleted file mode 100644 index a348d60dcac..00000000000 --- a/packages/twenty-front/src/modules/settings/logic-functions/hooks/useGetOneLogicFunction.ts +++ /dev/null @@ -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, - }; -}; diff --git a/packages/twenty-front/src/modules/settings/logic-functions/states/logicFunctionsState.ts b/packages/twenty-front/src/modules/settings/logic-functions/states/logicFunctionsState.ts index 149efd828fb..a0d81543e76 100644 --- a/packages/twenty-front/src/modules/settings/logic-functions/states/logicFunctionsState.ts +++ b/packages/twenty-front/src/modules/settings/logic-functions/states/logicFunctionsState.ts @@ -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({ key: 'logicFunctionsState', diff --git a/packages/twenty-front/src/modules/users/components/MetadataProviderEffect.tsx b/packages/twenty-front/src/modules/users/components/MetadataProviderEffect.tsx index ce5b915a88f..fb91302ddc4 100644 --- a/packages/twenty-front/src/modules/users/components/MetadataProviderEffect.tsx +++ b/packages/twenty-front/src/modules/users/components/MetadataProviderEffect.tsx @@ -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, }); diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCode.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCode.tsx index 0c6b05fa1b2..891a840c30e 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCode.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionCode.tsx @@ -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({ - 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(
@@ -69,7 +60,7 @@ export const WorkflowReadonlyActionCode = ({ { }); 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, }), ); } diff --git a/packages/twenty-front/src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx b/packages/twenty-front/src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx index 94a5115e667..e3f1d42139e 100644 --- a/packages/twenty-front/src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx +++ b/packages/twenty-front/src/pages/settings/logic-functions/SettingsLogicFunctionDetail.tsx @@ -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 = () => { > + {isEditorTab && ( + + )} {isTriggersTab && logicFunction && ( )} diff --git a/packages/twenty-front/src/pages/settings/logic-functions/__stories__/SettingsLogicFunctionDetail.stories.tsx b/packages/twenty-front/src/pages/settings/logic-functions/__stories__/SettingsLogicFunctionDetail.stories.tsx index 84a2fee014f..d499c90f4f7 100644 --- a/packages/twenty-front/src/pages/settings/logic-functions/__stories__/SettingsLogicFunctionDetail.stories.tsx +++ b/packages/twenty-front/src/pages/settings/logic-functions/__stories__/SettingsLogicFunctionDetail.stories.tsx @@ -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 = { 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 = { logicFunction: { __typename: 'LogicFunction', id: 'adb4bd21-7670-4c81-9f74-1fc196fe87ea', + applicationId: APPLICATION_ID, name: 'Logic Function Name', description: '', runtime: 'nodejs22.x', diff --git a/packages/twenty-sdk/package.json b/packages/twenty-sdk/package.json index 3705f26537a..31fc3aae7f4 100644 --- a/packages/twenty-sdk/package.json +++ b/packages/twenty-sdk/package.json @@ -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", diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/.gitignore b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/.gitignore new file mode 100644 index 00000000000..86d4c2dd380 --- /dev/null +++ b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/.gitignore @@ -0,0 +1 @@ +generated diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts index 9101807f9a6..19f89f27d73 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts @@ -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', diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/root-app/__integration__/app-dev/expected-manifest.ts b/packages/twenty-sdk/src/cli/__tests__/apps/root-app/__integration__/app-dev/expected-manifest.ts index 5771e8b8cc1..47e955f1683 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/root-app/__integration__/app-dev/expected-manifest.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/root-app/__integration__/app-dev/expected-manifest.ts @@ -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', diff --git a/packages/twenty-sdk/src/cli/__tests__/integration/utils/normalize-manifest.util.ts b/packages/twenty-sdk/src/cli/__tests__/integration/utils/normalize-manifest.util.ts index 911449e7bd9..5959c58285c 100644 --- a/packages/twenty-sdk/src/cli/__tests__/integration/utils/normalize-manifest.util.ts +++ b/packages/twenty-sdk/src/cli/__tests__/integration/utils/normalize-manifest.util.ts @@ -26,5 +26,4 @@ export const normalizeManifestForComparison = ( ? '[checksum]' : '', })), - sources: {}, // removing sources for now, waiting compressed file implementation }); diff --git a/packages/twenty-sdk/src/cli/utilities/api/api-service.ts b/packages/twenty-sdk/src/cli/utilities/api/api-service.ts index 22eb911ffb5..99b70e071f2 100644 --- a/packages/twenty-sdk/src/cli/utilities/api/api-service.ts +++ b/packages/twenty-sdk/src/cli/utilities/api/api-service.ts @@ -574,7 +574,7 @@ export class ApiService { ); const response: AxiosResponse = await this.client.post( - '/graphql', + '/metadata', formData, ); diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-validate.spec.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-validate.spec.ts index d1ec754637a..9d19629d779 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-validate.spec.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/__tests__/manifest-validate.spec.ts @@ -32,7 +32,6 @@ const validManifest: Manifest = { logicFunctions: [], roles: [], publicAssets: [], - sources: {}, }; describe('manifestValidate', () => { diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts index 01a7e71ae6d..069780b1e59 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts @@ -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 => { @@ -44,32 +42,6 @@ const loadAssets = async (appPath: string) => { }); }; -const computeSources = async ( - appPath: string, - sourceFilePaths: string[], -): Promise => { - 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 = { diff --git a/packages/twenty-server/nest-cli.json b/packages/twenty-server/nest-cli.json index e79349e9d72..2017554dcb6 100644 --- a/packages/twenty-server/nest-cli.json +++ b/packages/twenty-server/nest-cli.json @@ -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" }, { diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-backfill-application-package-files.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-backfill-application-package-files.command.ts index 5d9994a30e3..83fa7cc883b 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-backfill-application-package-files.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-backfill-application-package-files.command.ts @@ -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'; diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-workflow-code-steps.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-workflow-code-steps.command.ts index f1eadfda035..4c6c6a7d6af 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-workflow-code-steps.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-migrate-workflow-code-steps.command.ts @@ -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, diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-seed-workflow-v1-16.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-seed-workflow-v1-16.command.ts index d55e817a4bb..05566de6de9 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-seed-workflow-v1-16.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-17/1-17-seed-workflow-v1-16.command.ts @@ -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'; diff --git a/packages/twenty-server/src/database/typeorm/core/migrations/common/1770725043111-addLogicFunctionIsBuildUpToDateColumn.ts b/packages/twenty-server/src/database/typeorm/core/migrations/common/1770725043111-addLogicFunctionIsBuildUpToDateColumn.ts new file mode 100644 index 00000000000..c260077dcc3 --- /dev/null +++ b/packages/twenty-server/src/database/typeorm/core/migrations/common/1770725043111-addLogicFunctionIsBuildUpToDateColumn.ts @@ -0,0 +1,37 @@ +import { type MigrationInterface, type QueryRunner } from 'typeorm'; + +export class AddLogicFunctionIsBuildUpToDateColumn1770725043111 + implements MigrationInterface +{ + name = 'AddLogicFunctionIsBuildUpToDateColumn1770725043111'; + + public async up(queryRunner: QueryRunner): Promise { + 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 { + 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"`, + ); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/application-layer/application-layer.module.ts b/packages/twenty-server/src/engine/core-modules/application-layer/application-layer.module.ts deleted file mode 100644 index ba95a036846..00000000000 --- a/packages/twenty-server/src/engine/core-modules/application-layer/application-layer.module.ts +++ /dev/null @@ -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 {} diff --git a/packages/twenty-server/src/engine/core-modules/application-layer/application-layer.service.ts b/packages/twenty-server/src/engine/core-modules/application-layer/application-layer.service.ts deleted file mode 100644 index 3d045be4fe4..00000000000 --- a/packages/twenty-server/src/engine/core-modules/application-layer/application-layer.service.ts +++ /dev/null @@ -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 ?? {}; - } -} diff --git a/packages/twenty-server/src/engine/core-modules/application-layer/constants/seed-dependencies-dirname.ts b/packages/twenty-server/src/engine/core-modules/application/constants/seed-dependencies-dirname.ts similarity index 72% rename from packages/twenty-server/src/engine/core-modules/application-layer/constants/seed-dependencies-dirname.ts rename to packages/twenty-server/src/engine/core-modules/application/constants/seed-dependencies-dirname.ts index 29c06e7e14f..8311c7a796f 100644 --- a/packages/twenty-server/src/engine/core-modules/application-layer/constants/seed-dependencies-dirname.ts +++ b/packages/twenty-server/src/engine/core-modules/application/constants/seed-dependencies-dirname.ts @@ -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', ), ); diff --git a/packages/twenty-server/src/engine/core-modules/application-layer/constants/seed-dependencies/package.json b/packages/twenty-server/src/engine/core-modules/application/constants/seed-dependencies/package.json similarity index 100% rename from packages/twenty-server/src/engine/core-modules/application-layer/constants/seed-dependencies/package.json rename to packages/twenty-server/src/engine/core-modules/application/constants/seed-dependencies/package.json diff --git a/packages/twenty-server/src/engine/core-modules/application-layer/constants/seed-dependencies/yarn.lock b/packages/twenty-server/src/engine/core-modules/application/constants/seed-dependencies/yarn.lock similarity index 100% rename from packages/twenty-server/src/engine/core-modules/application-layer/constants/seed-dependencies/yarn.lock rename to packages/twenty-server/src/engine/core-modules/application/constants/seed-dependencies/yarn.lock diff --git a/packages/twenty-server/src/engine/core-modules/application-layer/constants/yarn-engine-dirname.ts b/packages/twenty-server/src/engine/core-modules/application/constants/yarn-engine-dirname.ts similarity index 73% rename from packages/twenty-server/src/engine/core-modules/application-layer/constants/yarn-engine-dirname.ts rename to packages/twenty-server/src/engine/core-modules/application/constants/yarn-engine-dirname.ts index e9676efc450..61da3c1b916 100644 --- a/packages/twenty-server/src/engine/core-modules/application-layer/constants/yarn-engine-dirname.ts +++ b/packages/twenty-server/src/engine/core-modules/application/constants/yarn-engine-dirname.ts @@ -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', ), ); diff --git a/packages/twenty-server/src/engine/core-modules/application-layer/constants/yarn-engine/.yarn/releases/yarn-4.9.2.cjs b/packages/twenty-server/src/engine/core-modules/application/constants/yarn-engine/.yarn/releases/yarn-4.9.2.cjs similarity index 100% rename from packages/twenty-server/src/engine/core-modules/application-layer/constants/yarn-engine/.yarn/releases/yarn-4.9.2.cjs rename to packages/twenty-server/src/engine/core-modules/application/constants/yarn-engine/.yarn/releases/yarn-4.9.2.cjs diff --git a/packages/twenty-server/src/engine/core-modules/application-layer/constants/yarn-engine/.yarnrc.yml b/packages/twenty-server/src/engine/core-modules/application/constants/yarn-engine/.yarnrc.yml similarity index 100% rename from packages/twenty-server/src/engine/core-modules/application-layer/constants/yarn-engine/.yarnrc.yml rename to packages/twenty-server/src/engine/core-modules/application/constants/yarn-engine/.yarnrc.yml diff --git a/packages/twenty-server/src/engine/core-modules/application/services/application-sync.service.ts b/packages/twenty-server/src/engine/core-modules/application/services/application-sync.service.ts index 6627f9ced92..371070c04bd 100644 --- a/packages/twenty-server/src/engine/core-modules/application/services/application-sync.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/services/application-sync.service.ts @@ -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'; diff --git a/packages/twenty-server/src/engine/core-modules/application/services/application.service.ts b/packages/twenty-server/src/engine/core-modules/application/services/application.service.ts index f6ad4f5fdd2..2a6b60af5ea 100644 --- a/packages/twenty-server/src/engine/core-modules/application/services/application.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/services/application.service.ts @@ -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'; diff --git a/packages/twenty-server/src/engine/core-modules/application-layer/utils/copy-yarn-engine-and-build-dependencies.ts b/packages/twenty-server/src/engine/core-modules/application/utils/copy-yarn-engine-and-build-dependencies.ts similarity index 96% rename from packages/twenty-server/src/engine/core-modules/application-layer/utils/copy-yarn-engine-and-build-dependencies.ts rename to packages/twenty-server/src/engine/core-modules/application/utils/copy-yarn-engine-and-build-dependencies.ts index 11a58eea370..096d1c0539a 100644 --- a/packages/twenty-server/src/engine/core-modules/application-layer/utils/copy-yarn-engine-and-build-dependencies.ts +++ b/packages/twenty-server/src/engine/core-modules/application/utils/copy-yarn-engine-and-build-dependencies.ts @@ -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); diff --git a/packages/twenty-server/src/engine/core-modules/application/utils/from-logic-function-manifest-to-universal-flat-logic-function.util.ts b/packages/twenty-server/src/engine/core-modules/application/utils/from-logic-function-manifest-to-universal-flat-logic-function.util.ts index 1309246477a..bde3afd51ed 100644 --- a/packages/twenty-server/src/engine/core-modules/application/utils/from-logic-function-manifest-to-universal-flat-logic-function.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/utils/from-logic-function-manifest-to-universal-flat-logic-function.util.ts @@ -35,6 +35,7 @@ export const fromLogicFunctionManifestToUniversalFlatLogicFunction = ({ logicFunctionManifest.databaseEventTriggerSettings ?? null, httpRouteTriggerSettings: logicFunctionManifest.httpRouteTriggerSettings ?? null, + isBuildUpToDate: true, createdAt: now, updatedAt: now, deletedAt: null, diff --git a/packages/twenty-server/src/engine/core-modules/application-layer/utils/get-default-application-package-fields.util.ts b/packages/twenty-server/src/engine/core-modules/application/utils/get-default-application-package-fields.util.ts similarity index 89% rename from packages/twenty-server/src/engine/core-modules/application-layer/utils/get-default-application-package-fields.util.ts rename to packages/twenty-server/src/engine/core-modules/application/utils/get-default-application-package-fields.util.ts index 0319f058b36..472507593bb 100644 --- a/packages/twenty-server/src/engine/core-modules/application-layer/utils/get-default-application-package-fields.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/utils/get-default-application-package-fields.util.ts @@ -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). diff --git a/packages/twenty-server/src/engine/core-modules/application-layer/utils/parse-available-packages-from-package-json-and-yarn-lock.util.ts b/packages/twenty-server/src/engine/core-modules/application/utils/parse-available-packages-from-package-json-and-yarn-lock.util.ts similarity index 100% rename from packages/twenty-server/src/engine/core-modules/application-layer/utils/parse-available-packages-from-package-json-and-yarn-lock.util.ts rename to packages/twenty-server/src/engine/core-modules/application/utils/parse-available-packages-from-package-json-and-yarn-lock.util.ts diff --git a/packages/twenty-server/src/engine/core-modules/cache-storage/services/cache-storage.service.ts b/packages/twenty-server/src/engine/core-modules/cache-storage/services/cache-storage.service.ts index b2b32d67506..cfed6d37542 100644 --- a/packages/twenty-server/src/engine/core-modules/cache-storage/services/cache-storage.service.ts +++ b/packages/twenty-server/src/engine/core-modules/cache-storage/services/cache-storage.service.ts @@ -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; diff --git a/packages/twenty-server/src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum.ts b/packages/twenty-server/src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum.ts index 1630028bb9a..b6be2af0994 100644 --- a/packages/twenty-server/src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum.ts @@ -7,4 +7,5 @@ export enum CacheStorageNamespace { EngineHealth = 'engine:health', EngineMetrics = 'engine:metrics', EngineSubscriptions = 'engine:subscriptions', + IntegrationTests = 'integration-tests', } diff --git a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts index 8de9307d180..5f3e6eea19b 100644 --- a/packages/twenty-server/src/engine/core-modules/core-engine.module.ts +++ b/packages/twenty-server/src/engine/core-modules/core-engine.module.ts @@ -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, diff --git a/packages/twenty-server/src/engine/core-modules/file-storage/file-storage.service.ts b/packages/twenty-server/src/engine/core-modules/file-storage/file-storage.service.ts index ed5b71e388c..4ac30014ff4 100644 --- a/packages/twenty-server/src/engine/core-modules/file-storage/file-storage.service.ts +++ b/packages/twenty-server/src/engine/core-modules/file-storage/file-storage.service.ts @@ -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 { - 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 { - const driver = this.fileStorageDriverFactory.getCurrentDriver(); - const onStoragePath = this.buildOnStoragePath(params); - - return driver.uploadFolder({ - localPath: params.localPath, - onStoragePath, - }); - } - - downloadFolder( - params: ResourceIdentifier & { localPath: string }, - ): Promise { - const driver = this.fileStorageDriverFactory.getCurrentDriver(); - const onStoragePath = this.buildOnStoragePath(params); - - return driver.downloadFolder({ - onStoragePath, - localPath: params.localPath, - }); - } - downloadFile( params: ResourceIdentifier & { localPath: string }, ): Promise { @@ -339,7 +300,7 @@ export class FileStorageService { return driver.copy(params); } - copy({ + async copy({ from, to, }: { @@ -348,68 +309,21 @@ export class FileStorageService { }): Promise { 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 { - 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 { - 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 { - const driver = this.fileStorageDriverFactory.getCurrentDriver(); - const onStoragePath = this.buildOnStoragePath(params); - - return driver.checkFolderExists({ folderPath: onStoragePath }); - } } diff --git a/packages/twenty-server/src/engine/core-modules/file-storage/utils/__tests__/read-s3-folder-content.spec.ts b/packages/twenty-server/src/engine/core-modules/file-storage/utils/__tests__/read-s3-folder-content.spec.ts deleted file mode 100644 index 5c78077f56d..00000000000 --- a/packages/twenty-server/src/engine/core-modules/file-storage/utils/__tests__/read-s3-folder-content.spec.ts +++ /dev/null @@ -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); - }); -}); diff --git a/packages/twenty-server/src/engine/core-modules/file-storage/utils/read-s3-folder-content.ts b/packages/twenty-server/src/engine/core-modules/file-storage/utils/read-s3-folder-content.ts deleted file mode 100644 index 1884fe1e7e7..00000000000 --- a/packages/twenty-server/src/engine/core-modules/file-storage/utils/read-s3-folder-content.ts +++ /dev/null @@ -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; -}; diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda.driver.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda.driver.ts index 49b4aba3b69..33930a777b9 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/lambda.driver.ts @@ -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 { 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()) diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/local.driver.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/local.driver.ts index e588a1064e6..094989abe8e 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/local.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/drivers/local.driver.ts @@ -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 { 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) diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/interfaces/logic-function-driver.interface.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/interfaces/logic-function-driver.interface.ts index 285e54521ce..dafeec42d4a 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/interfaces/logic-function-driver.interface.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/interfaces/logic-function-driver.interface.ts @@ -26,6 +26,7 @@ export type LogicFunctionExecuteParams = { applicationUniversalIdentifier: string; payload: object; env?: Record; + timeoutMs?: number; }; export interface LogicFunctionDriver { diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/utils/call-with-timeout.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/utils/call-with-timeout.ts new file mode 100644 index 00000000000..9f1f5973aaa --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-drivers/utils/call-with-timeout.ts @@ -0,0 +1,14 @@ +export const callWithTimeout = async ({ + callback, + timeoutMs, +}: { + callback: () => Promise; + timeoutMs: number; +}): Promise => { + return Promise.race([ + callback(), + new Promise((_, reject) => + setTimeout(() => reject(new Error('Execution timed out')), timeoutMs), + ), + ]); +}; diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service.ts index cdb0c2283f5..7716af1dbf7 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-executor/logic-function-executor.service.ts @@ -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({ - callback, - timeoutMs, - }: { - callback: () => Promise; - timeoutMs: number; - }): Promise { - return Promise.race([ - callback(), - new Promise((_, reject) => - setTimeout(() => reject(new Error('Execution timed out')), timeoutMs), - ), - ]); - } - private async getFlatEntitiesOrThrow({ workspaceId, logicFunctionId, diff --git a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service.ts b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service.ts index 5936a85669c..e8e45356e2e 100644 --- a/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service.ts +++ b/packages/twenty-server/src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service.ts @@ -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 { - 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 { - 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 { - const baseFolderPath = getLogicFunctionBaseFolderPath(sourceHandlerPath); - + }: GetSourceCodeParams): Promise { 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 { - 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 { + }: GetBuiltCodeParams): Promise { 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 { + }): Promise { + 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 { - 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({ diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.constant.spec.ts.snap b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap similarity index 99% rename from packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.constant.spec.ts.snap rename to packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap index f95b103a644..553930681e3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.constant.spec.ts.snap +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/__snapshots__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts.snap @@ -84,6 +84,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma "handlerName", "toolInputSchema", "isTool", + "isBuildUpToDate", "deletedAt", "cronTriggerSettings", "databaseEventTriggerSettings", diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.constant.spec.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts similarity index 100% rename from packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.constant.spec.ts rename to packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/__tests__/all-universal-flat-entity-properties-to-compare-and-stringify.constant.spec.ts diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts index 21a37e1982a..428ee6a0b31 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts @@ -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: { diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/constants/flat-logic-function-editable-properties.constant.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/constants/flat-logic-function-editable-properties.constant.ts index a00c3efaa29..eeaccb91953 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/constants/flat-logic-function-editable-properties.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/constants/flat-logic-function-editable-properties.constant.ts @@ -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'>[]; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/constants/handler.contant.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/constants/handler.contant.ts new file mode 100644 index 00000000000..66c52b98972 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/constants/handler.contant.ts @@ -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'; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input.ts deleted file mode 100644 index c93c9023044..00000000000 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input.ts +++ /dev/null @@ -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; -} diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input.ts new file mode 100644 index 00000000000..720a323aa7f --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input.ts @@ -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; + + @IsObject() + @Field(() => graphqlTypeJson, { nullable: true }) + @IsOptional() + cronTriggerSettings?: JsonbProperty; + + @IsObject() + @Field(() => graphqlTypeJson, { nullable: true }) + @IsOptional() + databaseEventTriggerSettings?: JsonbProperty; + + @IsObject() + @Field(() => graphqlTypeJson, { nullable: true }) + @IsOptional() + httpRouteTriggerSettings?: JsonbProperty; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-logic-function.input.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-logic-function.input.ts index dcba86ea20e..94c0ce6cd5f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-logic-function.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/create-logic-function.input.ts @@ -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 }) diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/execute-logic-function.input.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/execute-logic-function.input.ts index 3e36e8a6669..deca55a6652 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/execute-logic-function.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/execute-logic-function.input.ts @@ -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; } diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/logic-function-source.input.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/logic-function-source.input.ts new file mode 100644 index 00000000000..f287e7b8c6c --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/logic-function-source.input.ts @@ -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; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/logic-function.dto.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/logic-function.dto.ts index 9ce47ed2904..c19150e8c69 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/logic-function.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/logic-function.dto.ts @@ -62,10 +62,6 @@ export class LogicFunctionDTO { @Field() sourceHandlerPath: string; - @IsString() - @Field() - builtHandlerPath: string; - @IsString() @Field() handlerName: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function.input.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input.ts similarity index 83% rename from packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function.input.ts rename to packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input.ts index 3786fc1d5cd..f188ab21aaa 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input.ts @@ -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; } diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function-source.input.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function-source.input.ts deleted file mode 100644 index 9b799c0a023..00000000000 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/dtos/update-logic-function-source.input.ts +++ /dev/null @@ -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; -} diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.entity.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.entity.ts index 182810c5b80..286582e094e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.entity.ts @@ -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 | null; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.module.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.module.ts index a05a99f6091..7d52dd4ec9f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.module.ts @@ -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 {} diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.resolver.ts index da5e3487c59..78300287d63 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/logic-function.resolver.ts @@ -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 { 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 { 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 { 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 { 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 { 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 { try { - await this.logicFunctionService.updateLogicFunctionSource({ + await this.logicFunctionFromSourceService.updateOne({ id, - code, + update, workspaceId, }); diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function.service.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-from-source.service.ts similarity index 58% rename from packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function.service.ts rename to packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-from-source.service.ts index 24cc6e07bb3..c711323a090 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-from-source.service.ts @@ -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 { - const { flatLogicFunctionMaps } = - await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatLogicFunctionMaps'], - }, - ); - - const flatLogicFunction = findFlatLogicFunctionOrThrow({ - id, - flatLogicFunctionMaps, - }); - - return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }); - } - - async findMany({ - workspaceId, - }: { - workspaceId: string; - }): Promise { - 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 { 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 { + 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 { + 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 { - 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 { + }): Promise { 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 { - const { flatLogicFunction, applicationUniversalIdentifier } = - await this.getLogicFunctionContext({ id, workspaceId }); - - await this.logicFunctionResourceService.updateSourceFiles({ - workspaceId, - applicationUniversalIdentifier, - sourceHandlerPath: flatLogicFunction.sourceHandlerPath, - code, - }); - } - private async getLogicFunctionContext({ id, workspaceId, diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-metadata.service.ts index 925c9cd7125..90c21c5e69f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/services/logic-function-metadata.service.ts @@ -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 { + const { flatLogicFunctionMaps } = + await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatLogicFunctionMaps'], + }, + ); + + const flatLogicFunction = findFlatLogicFunctionOrThrow({ + id, + flatLogicFunctionMaps, + }); + + return fromFlatLogicFunctionToLogicFunctionDto({ flatLogicFunction }); + } + + async findMany({ + workspaceId, + }: { + workspaceId: string; + }): Promise { + 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; + input: CreateLogicFunction; ownerFlatApplication: FlatApplication; workspaceId: string; }) { @@ -86,7 +135,7 @@ export class LogicFunctionMetadataService { ownerFlatApplication, }: { id: string; - update: UpdateLogicFunctionSourceInput['update']; + update: Omit; 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 ?? {}; + } } diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/types/flat-logic-function.type.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/types/flat-logic-function.type.ts index 21fb45965e5..10e31413f09 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/types/flat-logic-function.type.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/types/flat-logic-function.type.ts @@ -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 & { - code?: Sources; -}; +export type FlatLogicFunction = FlatEntityFrom; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-input-to-flat-logic-function.util.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-flat-logic-function.util.ts similarity index 71% rename from packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-input-to-flat-logic-function.util.ts rename to packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-flat-logic-function.util.ts index a7a26e2fab9..b7aa567e86f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-input-to-flat-logic-function.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-create-logic-function-from-source-input-to-flat-logic-function.util.ts @@ -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: 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, }; }; diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-flat-logic-function-to-logic-function-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-flat-logic-function-to-logic-function-dto.util.ts index 5e912940180..82262fdf81a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-flat-logic-function-to-logic-function-dto.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-flat-logic-function-to-logic-function-dto.util.ts @@ -14,7 +14,6 @@ export const fromFlatLogicFunctionToLogicFunctionDto = ({ runtime: flatLogicFunction.runtime, timeoutSeconds: flatLogicFunction.timeoutSeconds, sourceHandlerPath: flatLogicFunction.sourceHandlerPath, - builtHandlerPath: flatLogicFunction.builtHandlerPath, handlerName: flatLogicFunction.handlerName, toolInputSchema: flatLogicFunction.toolInputSchema ?? undefined, isTool: flatLogicFunction.isTool, diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-update-logic-function-input-to-flat-logic-function-to-update-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-update-logic-function-input-to-flat-logic-function-to-update-or-throw.util.ts index 02f942292d2..aa97ef3eb9c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-update-logic-function-input-to-flat-logic-function-to-update-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/from-update-logic-function-input-to-flat-logic-function-to-update-or-throw.util.ts @@ -2,16 +2,16 @@ import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'tw import { type MetadataFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity-maps.type'; import { FLAT_LOGIC_FUNCTION_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/logic-function/constants/flat-logic-function-editable-properties.constant'; -import { type UpdateLogicFunctionSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function.input'; import { type 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 { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util'; +import { type UpdateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input'; export const fromUpdateLogicFunctionInputToFlatLogicFunctionToUpdateOrThrow = ({ updateLogicFunctionInput: rawUpdateLogicFunctionInput, flatLogicFunctionMaps, }: { - updateLogicFunctionInput: UpdateLogicFunctionSourceInput; + updateLogicFunctionInput: UpdateLogicFunctionFromSourceInput; flatLogicFunctionMaps: MetadataFlatEntityMaps<'logicFunction'>; }): FlatLogicFunction => { const { id: logicFunctionToUpdateId } = diff --git a/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/get-logic-function-subfolder-for-from-source.ts b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/get-logic-function-subfolder-for-from-source.ts new file mode 100644 index 00000000000..1f60e09c2c8 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/logic-function/utils/get-logic-function-subfolder-for-from-source.ts @@ -0,0 +1,5 @@ +export const getLogicFunctionSubfolderForFromSource = ( + logicFunctionId: string, +) => { + return logicFunctionId; +}; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-logic-function.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-logic-function.type.ts index f13592b1e66..697290f2851 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-logic-function.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-logic-function.type.ts @@ -1,11 +1,7 @@ -import { type Sources } from 'twenty-shared/types'; - import { type LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type'; export type UniversalFlatLogicFunction = UniversalFlatEntityFrom< LogicFunctionEntity, 'logicFunction' -> & { - code?: Sources; -}; +>; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/types/workspace-migration-logic-function-action.type.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/types/workspace-migration-logic-function-action.type.ts index 51bb552c792..7303ec115c8 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/types/workspace-migration-logic-function-action.type.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/types/workspace-migration-logic-function-action.type.ts @@ -1,5 +1,3 @@ -import { type Sources } from 'twenty-shared/types'; - import { type BaseFlatCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-create-workspace-migration-action.type'; import { type BaseFlatDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-delete-workspace-migration-action.type'; import { type BaseFlatUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-update-workspace-migration-action.type'; @@ -14,9 +12,7 @@ export type UniversalCreateLogicFunctionAction = BaseUniversalCreateWorkspaceMigrationAction<'logicFunction'>; export type FlatUpdateLogicFunctionAction = - BaseFlatUpdateWorkspaceMigrationAction<'logicFunction'> & { - code?: Sources; - }; + BaseFlatUpdateWorkspaceMigrationAction<'logicFunction'>; export type UniversalUpdateLogicFunctionAction = BaseUniversalUpdateWorkspaceMigrationAction<'logicFunction'>; diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/workspace-migration-logic-function-actions-builder.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/workspace-migration-logic-function-actions-builder.service.ts index af1b654ae15..c62db4bfc57 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/workspace-migration-logic-function-actions-builder.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/workspace-migration-logic-function-actions-builder.service.ts @@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common'; import { ALL_METADATA_NAME } from 'twenty-shared/metadata'; -import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; import { UniversalUpdateLogicFunctionAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/logic-function/types/workspace-migration-logic-function-action.type'; import { ValidateAndBuildArgs, @@ -27,7 +26,6 @@ export class WorkspaceMigrationLogicFunctionActionsBuilderService extends Worksp public async validateAndBuild( args: ValidateAndBuildArgs, ): ValidateAndBuildReturnType { - const { to: toFlatEntityMaps } = args; const baseResult = await super.validateAndBuild(args); if (baseResult.status === 'fail') { @@ -35,19 +33,7 @@ export class WorkspaceMigrationLogicFunctionActionsBuilderService extends Worksp } const updatedActions = baseResult.actions.update.map((action) => { - if (action.type !== 'update') { - return action; - } - - const toLogicFunction = findFlatEntityByUniversalIdentifier({ - universalIdentifier: action.universalIdentifier, - flatEntityMaps: toFlatEntityMaps, - }); - - return { - ...action, - code: toLogicFunction?.code, - }; + return action; }); return { diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/create-logic-function-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/create-logic-function-action-handler.service.ts index 0ea1350a03a..052249b08dc 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/create-logic-function-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/create-logic-function-action-handler.service.ts @@ -1,16 +1,10 @@ import { Injectable } from '@nestjs/common'; -import { FileFolder } from 'twenty-shared/types'; import { v4 } from 'uuid'; import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; -import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; -import { - LogicFunctionException, - LogicFunctionExceptionCode, -} from 'src/engine/metadata-modules/logic-function/logic-function.exception'; import { FlatCreateLogicFunctionAction, UniversalCreateLogicFunctionAction, @@ -25,10 +19,6 @@ export class CreateLogicFunctionActionHandlerService extends WorkspaceMigrationR 'create', 'logicFunction', ) { - constructor(private readonly fileStorageService: FileStorageService) { - super(); - } - override async transpileUniversalActionToFlatAction({ action, flatApplication, @@ -48,25 +38,9 @@ export class CreateLogicFunctionActionHandlerService extends WorkspaceMigrationR async executeForMetadata( context: WorkspaceMigrationActionRunnerContext, ): Promise { - const { flatAction, queryRunner, workspaceId, flatApplication } = context; + const { flatAction, queryRunner, workspaceId } = context; const { flatEntity: logicFunction } = flatAction; - const applicationUniversalIdentifier = flatApplication.universalIdentifier; - - const builtExists = await this.fileStorageService.checkFileExists({ - workspaceId, - applicationUniversalIdentifier, - fileFolder: FileFolder.BuiltLogicFunction, - resourcePath: logicFunction.builtHandlerPath, - }); - - if (!builtExists) { - throw new LogicFunctionException( - 'Logic function built file missing before create', - LogicFunctionExceptionCode.LOGIC_FUNCTION_CREATE_FAILED, - ); - } - const logicFunctionRepository = queryRunner.manager.getRepository( LogicFunctionEntity, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/delete-logic-function-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/delete-logic-function-action-handler.service.ts index 32a244428c3..b5215d256d5 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/delete-logic-function-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/delete-logic-function-action-handler.service.ts @@ -15,6 +15,7 @@ import { WorkspaceMigrationActionRunnerArgs, WorkspaceMigrationActionRunnerContext, } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type'; +import { getLogicFunctionSubfolderForFromSource } from 'src/engine/metadata-modules/logic-function/utils/get-logic-function-subfolder-for-from-source'; @Injectable() export class DeleteLogicFunctionActionHandlerService extends WorkspaceMigrationRunnerActionHandler( @@ -59,6 +60,15 @@ export class DeleteLogicFunctionActionHandlerService extends WorkspaceMigrationR const applicationUniversalIdentifier = flatApplication.universalIdentifier; + await this.fileStorageService.delete({ + workspaceId, + applicationUniversalIdentifier, + fileFolder: FileFolder.Source, + resourcePath: getLogicFunctionSubfolderForFromSource( + flatLogicFunction.id, + ), + }); + await this.fileStorageService.delete({ workspaceId, applicationUniversalIdentifier, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/update-logic-function-action-handler.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/update-logic-function-action-handler.service.ts index c06688a9d87..0da5d3f0827 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/update-logic-function-action-handler.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/logic-function/services/update-logic-function-action-handler.service.ts @@ -1,18 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { FileFolder } from 'twenty-shared/types'; - import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface'; -import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service'; -import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util'; import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util'; import { LogicFunctionEntity } from 'src/engine/metadata-modules/logic-function/logic-function.entity'; -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 { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util'; import { FlatUpdateLogicFunctionAction, @@ -28,10 +19,6 @@ export class UpdateLogicFunctionActionHandlerService extends WorkspaceMigrationR 'update', 'logicFunction', ) { - constructor(private readonly fileStorageService: FileStorageService) { - super(); - } - override async transpileUniversalActionToFlatAction( context: WorkspaceMigrationActionRunnerArgs, ): Promise { @@ -59,7 +46,7 @@ export class UpdateLogicFunctionActionHandlerService extends WorkspaceMigrationR async executeForMetadata( context: WorkspaceMigrationActionRunnerContext, ): Promise { - const { flatAction, queryRunner, flatApplication, workspaceId } = context; + const { flatAction, queryRunner, workspaceId } = context; const { entityId, update } = flatAction; const logicFunctionRepository = @@ -68,41 +55,5 @@ export class UpdateLogicFunctionActionHandlerService extends WorkspaceMigrationR ); await logicFunctionRepository.update({ id: entityId, workspaceId }, update); - - const flatLogicFunction = findFlatEntityByIdInFlatEntityMapsOrThrow({ - flatEntityId: entityId, - flatEntityMaps: context.allFlatEntityMaps.flatLogicFunctionMaps, - }); - - const applicationUniversalIdentifier = flatApplication.universalIdentifier; - - if (update.checksum) { - await this.verifySourceAndBuiltFilesExist({ - flatLogicFunction, - applicationUniversalIdentifier, - }); - } - } - - private async verifySourceAndBuiltFilesExist({ - flatLogicFunction, - applicationUniversalIdentifier, - }: { - flatLogicFunction: FlatLogicFunction; - applicationUniversalIdentifier: string; - }): Promise { - const builtExists = await this.fileStorageService.checkFileExists({ - workspaceId: flatLogicFunction.workspaceId, - applicationUniversalIdentifier, - fileFolder: FileFolder.BuiltLogicFunction, - resourcePath: flatLogicFunction.builtHandlerPath, - }); - - if (!builtExists) { - throw new LogicFunctionException( - 'Logic function source or built file missing before update', - LogicFunctionExceptionCode.LOGIC_FUNCTION_NOT_READY, - ); - } } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts index 8c8c6b7115d..2bc6bcdc592 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/__tests__/workflow-version-step-operations.workspace-service.spec.ts @@ -310,7 +310,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { runtime: LogicFunctionRuntime.NODE22, timeoutSeconds: 30, sourceHandlerPath: 'src/index.ts', - builtHandlerPath: 'index.mjs', + builtHandlerPath: 'src/index.mjs', handlerName: 'main', checksum: null, toolInputSchema: null, @@ -321,6 +321,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { cronTriggerSettings: null, databaseEventTriggerSettings: null, httpRouteTriggerSettings: null, + isBuildUpToDate: false, }; logicFunctionMetadataService.createOne.mockResolvedValue( @@ -394,6 +395,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { cronTriggerSettings: null, databaseEventTriggerSettings: null, httpRouteTriggerSettings: null, + isBuildUpToDate: true, }; const mockNewFlatLogicFunction: FlatLogicFunction = { @@ -407,7 +409,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { runtime: LogicFunctionRuntime.NODE22, timeoutSeconds: 30, sourceHandlerPath: 'src/index.ts', - builtHandlerPath: 'index.mjs', + builtHandlerPath: 'src/index.mjs', handlerName: 'main', checksum: null, toolInputSchema: null, @@ -418,6 +420,7 @@ describe('WorkflowVersionStepOperationsWorkspaceService', () => { cronTriggerSettings: null, databaseEventTriggerSettings: null, httpRouteTriggerSettings: null, + isBuildUpToDate: false, }; const emptyMaps = createEmptyAllFlatEntityMaps(); diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/code-step/services/code-step-build.service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/code-step/services/code-step-build.service.ts index 7e01db557f3..42ffaaca2dd 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/code-step/services/code-step-build.service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version-step/code-step/services/code-step-build.service.ts @@ -14,30 +14,25 @@ import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/ import { LogicFunctionMetadataService } from 'src/engine/metadata-modules/logic-function/services/logic-function-metadata.service'; import { type 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 { WorkflowActionType, type WorkflowAction, } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type'; import { LogicFunctionResourceService } from 'src/engine/core-modules/logic-function/logic-function-resource/logic-function-resource.service'; -import { SEED_LOGIC_FUNCTION_INPUT_SCHEMA } from 'src/engine/core-modules/logic-function/logic-function-resource/constants/seed-logic-function-input-schema'; import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type'; - -const WORKFLOW_BASE_FOLDER_PREFIX = 'workflow'; +import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; @Injectable() export class CodeStepBuildService { constructor( private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, private readonly logicFunctionMetadataService: LogicFunctionMetadataService, + private readonly logicFunctionFromSourceService: LogicFunctionFromSourceService, private readonly applicationService: ApplicationService, private readonly logicFunctionResourceService: LogicFunctionResourceService, ) {} - private getSourceSubfolderForCodeStep(logicFunctionId: string) { - return `${WORKFLOW_BASE_FOLDER_PREFIX}/${logicFunctionId}`; - } - async createCodeStepLogicFunction({ logicFunctionId, workspaceId, @@ -45,33 +40,13 @@ export class CodeStepBuildService { logicFunctionId: string; workspaceId: string; }) { - const { workspaceCustomFlatApplication } = - await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow( - { - workspaceId, - }, - ); - const { sourceHandlerPath, builtHandlerPath, handlerName, checksum } = - await this.logicFunctionResourceService.seedSourceFiles({ - sourceSubfolder: this.getSourceSubfolderForCodeStep(logicFunctionId), - workspaceId, - applicationUniversalIdentifier: - workspaceCustomFlatApplication.universalIdentifier, - }); - - return await this.logicFunctionMetadataService.createOne({ + return await this.logicFunctionFromSourceService.createOne({ input: { id: logicFunctionId, - name: 'A Logic Function Code Workflow Step', + name: 'A Code Step', description: '', - sourceHandlerPath, - builtHandlerPath, - handlerName, - checksum, - toolInputSchema: SEED_LOGIC_FUNCTION_INPUT_SCHEMA, }, workspaceId, - ownerFlatApplication: workspaceCustomFlatApplication, }); } @@ -114,7 +89,6 @@ export class CodeStepBuildService { existingLogicFunction.id, newId, ); - const toBuiltHandlerPath = builtHandlerPath.replace( existingLogicFunction.id, newId, @@ -122,9 +96,9 @@ export class CodeStepBuildService { await this.logicFunctionResourceService.copyResources({ fromSourceHandlerPath: sourceHandlerPath, - fromBuiltHandlerPath: builtHandlerPath, toSourceHandlerPath, - toBuiltHandlerPath, + fromBuiltHandlerPath: builtHandlerPath, + toBuiltHandlerPath: toBuiltHandlerPath, workspaceId, applicationUniversalIdentifier, }); @@ -136,8 +110,8 @@ export class CodeStepBuildService { id: newId, universalIdentifier: newUniversalIdentifier, description: existingLogicFunction.description ?? undefined, - builtHandlerPath: toBuiltHandlerPath, sourceHandlerPath: toSourceHandlerPath, + builtHandlerPath: toBuiltHandlerPath, toolInputSchema: existingLogicFunction.toolInputSchema ?? {}, checksum: existingLogicFunction.checksum ?? '[default-checksum]', // TODO: checksum should never be null, update column in logicFunction entity to set it non nullable cronTriggerSettings: existingLogicFunction.cronTriggerSettings as @@ -230,7 +204,7 @@ export class CodeStepBuildService { if ( !isDefined(flatLogicFunction) || flatLogicFunction.deletedAt || - !this.isWorkflowCodeStepLogicFunction(flatLogicFunction) + flatLogicFunction.isBuildUpToDate ) { continue; } @@ -246,63 +220,10 @@ export class CodeStepBuildService { continue; } - const { checksum } = - await this.logicFunctionResourceService.buildFromSource({ - sourceHandlerPath: flatLogicFunction.sourceHandlerPath, - builtHandlerPath: flatLogicFunction.builtHandlerPath, - workspaceId, - applicationUniversalIdentifier, - }); - - await this.logicFunctionMetadataService.updateChecksum({ - id: flatLogicFunction.id, - checksum, + await this.logicFunctionFromSourceService.buildOneFromSource({ workspaceId, + id: logicFunctionId, }); } } - - isWorkflowCodeStepLogicFunction( - flatLogicFunction: FlatLogicFunction, - ): boolean { - return ( - flatLogicFunction.sourceHandlerPath.startsWith( - `${WORKFLOW_BASE_FOLDER_PREFIX}/`, - ) || - flatLogicFunction.builtHandlerPath.startsWith( - `${WORKFLOW_BASE_FOLDER_PREFIX}/`, - ) - ); - } - - async getFlatLogicFunctionForCodeStepOrNull({ - logicFunctionId, - workspaceId, - }: { - logicFunctionId: string; - workspaceId: string; - }): Promise { - const { flatLogicFunctionMaps } = - await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( - { - workspaceId, - flatMapsKeys: ['flatLogicFunctionMaps'], - }, - ); - - const flatLogicFunction = findFlatEntityByIdInFlatEntityMaps({ - flatEntityId: logicFunctionId, - flatEntityMaps: flatLogicFunctionMaps, - }); - - if ( - !isDefined(flatLogicFunction) || - flatLogicFunction.deletedAt || - !this.isWorkflowCodeStepLogicFunction(flatLogicFunction) - ) { - return null; - } - - return flatLogicFunction; - } } diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts index a8eb5ff3071..24e4fd53a33 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/services/workflow-tool.workspace-service.ts @@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common'; import { type ToolSet } from 'ai'; import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; +import { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; -import { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { type RolePermissionConfig } from 'src/engine/twenty-orm/types/role-permission-config'; import { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service'; @@ -43,7 +43,7 @@ export class WorkflowToolWorkspaceService { workflowSchemaService: WorkflowSchemaWorkspaceService, globalWorkspaceOrmManager: GlobalWorkspaceOrmManager, recordPositionService: RecordPositionService, - logicFunctionService: LogicFunctionService, + logicFunctionFromSourceService: LogicFunctionFromSourceService, flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, ) { this.deps = { @@ -55,7 +55,7 @@ export class WorkflowToolWorkspaceService { workflowSchemaService, globalWorkspaceOrmManager, recordPositionService, - logicFunctionService, + logicFunctionFromSourceService, flatEntityMapsCacheService, }; } diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool.ts index 558f345e35b..02849fe8f10 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/tools/update-logic-function-source.tool.ts @@ -13,24 +13,14 @@ const updateLogicFunctionSourceSchema = z.object({ 'The ID of the logic function to update (from the code step settings.input.logicFunctionId)', ), code: z - .object({ - src: z - .object({ - 'index.ts': z - .string() - .describe( - 'The TypeScript source code for the logic function. Must export a main function.', - ), - }) - .describe('Source folder containing the index.ts file'), - }) + .string() .describe( - 'The source code structure. Use { src: { "index.ts": "your code here" } }', + 'The TypeScript source code for the logic function. Must export a main function.', ), }); export const createUpdateLogicFunctionSourceTool = ( - deps: Pick, + deps: Pick, context: WorkflowToolContext, ) => ({ name: 'update_logic_function_source' as const, @@ -55,17 +45,16 @@ export const main = async (params: { url: string }) => { To find the logicFunctionId, look at the code step's settings.input.logicFunctionId field.`, inputSchema: updateLogicFunctionSourceSchema, - execute: async (parameters: { - logicFunctionId: string; - code: { src: { 'index.ts': string } }; - }) => { + execute: async (parameters: { logicFunctionId: string; code: string }) => { try { const { logicFunctionId, code } = parameters; const { workspaceId } = context; - await deps.logicFunctionService.updateLogicFunctionSource({ + await deps.logicFunctionFromSourceService.updateOne({ id: logicFunctionId, - code, + update: { + sourceHandlerCode: code, + }, workspaceId, }); diff --git a/packages/twenty-server/src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type.ts b/packages/twenty-server/src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type.ts index 33f48bd6343..11072365e88 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-tools/types/workflow-tool-dependencies.type.ts @@ -1,6 +1,6 @@ import type { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service'; +import type { LogicFunctionFromSourceService } from 'src/engine/metadata-modules/logic-function/services/logic-function-from-source.service'; import type { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; -import type { LogicFunctionService } from 'src/engine/metadata-modules/logic-function/services/logic-function.service'; import type { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import type { WorkflowSchemaWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-schema/workflow-schema.workspace-service'; import type { WorkflowVersionEdgeWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version-edge/workflow-version-edge.workspace-service'; @@ -18,7 +18,7 @@ export type WorkflowToolDependencies = { workflowSchemaService: WorkflowSchemaWorkspaceService; globalWorkspaceOrmManager: GlobalWorkspaceOrmManager; recordPositionService: RecordPositionService; - logicFunctionService: LogicFunctionService; + logicFunctionFromSourceService: LogicFunctionFromSourceService; flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService; }; diff --git a/packages/twenty-server/test/integration/graphql/suites/workspace/successful-user-and-workspace-creation.integration-spec.ts b/packages/twenty-server/test/integration/graphql/suites/workspace/successful-user-and-workspace-creation.integration-spec.ts index 76445e61c9c..241b6e195c1 100644 --- a/packages/twenty-server/test/integration/graphql/suites/workspace/successful-user-and-workspace-creation.integration-spec.ts +++ b/packages/twenty-server/test/integration/graphql/suites/workspace/successful-user-and-workspace-creation.integration-spec.ts @@ -7,7 +7,7 @@ import { getAuthTokensFromLoginToken } from 'test/integration/graphql/utils/get- import { getCurrentUser } from 'test/integration/graphql/utils/get-current-user.util'; import { signUpInNewWorkspace } from 'test/integration/graphql/utils/sign-up-in-new-workspace.util'; import { signUp } from 'test/integration/graphql/utils/sign-up.util'; -import { createDefaultLogicFunction } from 'test/integration/metadata/suites/logic-function/utils/create-default-logic-function.util'; +import { createOneLogicFunction } from 'test/integration/metadata/suites/logic-function/utils/create-logic-function.util'; import { createOneObjectMetadata } from 'test/integration/metadata/suites/object-metadata/utils/create-one-object-metadata.util'; import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util.test'; import { isDefined } from 'twenty-shared/utils'; @@ -190,7 +190,7 @@ describe('Successful user and workspace creation', () => { }); // Create a logic function for workspace deletion test - await createDefaultLogicFunction({ + await createOneLogicFunction({ input: { name: 'test-function-for-deletion', description: 'A test logic function for workspace deletion test', diff --git a/packages/twenty-server/test/integration/metadata/suites/logic-function/logic-function-execution.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/logic-function/logic-function-execution.integration-spec.ts index fa3f0c95250..4005fe5843f 100644 --- a/packages/twenty-server/test/integration/metadata/suites/logic-function/logic-function-execution.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/logic-function/logic-function-execution.integration-spec.ts @@ -1,4 +1,4 @@ -import { createDefaultLogicFunction } from 'test/integration/metadata/suites/logic-function/utils/create-default-logic-function.util'; +import { createOneLogicFunction } from 'test/integration/metadata/suites/logic-function/utils/create-logic-function.util'; import { deleteLogicFunction } from 'test/integration/metadata/suites/logic-function/utils/delete-logic-function.util'; import { executeLogicFunction } from 'test/integration/metadata/suites/logic-function/utils/execute-logic-function.util'; import { updateLogicFunctionSource } from 'test/integration/metadata/suites/logic-function/utils/update-logic-function-source.util'; @@ -6,15 +6,12 @@ import { updateLogicFunctionSource } from 'test/integration/metadata/suites/logi import { LogicFunctionExecutionStatus } from 'src/engine/metadata-modules/logic-function/dtos/logic-function-execution-result.dto'; // Default template function code that matches the expected behavior -const DEFAULT_TEMPLATE_FUNCTION_CODE = { - 'src/index.ts': `export const main = async (params: { a: string; b: number }): Promise => { - return { message: \`Hello, input: \${params.a} and \${params.b}\` }; -};`, -}; +const DEFAULT_TEMPLATE_FUNCTION_CODE = `export const main = async (params: { a: string; b: number }): Promise => { + return { message: \`Toto: \${params.a} and \${params.b}\` }; +};`; // Test function using external packages from default layer (lodash.groupby) -const EXTERNAL_PACKAGES_FUNCTION_CODE = { - 'src/index.ts': `import groupBy from 'lodash.groupby'; +const EXTERNAL_PACKAGES_FUNCTION_CODE = `import groupBy from 'lodash.groupby'; export const main = async (params: { items: Array<{ category: string; name: string }> }): Promise => { const grouped = groupBy(params.items, 'category'); @@ -22,18 +19,15 @@ export const main = async (params: { items: Array<{ category: string; name: stri grouped, categories: Object.keys(grouped), }; -};`, -}; +};`; // Test function that throws an error -const ERROR_FUNCTION_CODE = { - 'src/index.ts': `export const main = async (params: { shouldFail: boolean }): Promise => { +const ERROR_FUNCTION_CODE = `export const main = async (params: { shouldFail: boolean }): Promise => { if (params.shouldFail) { throw new Error('Intentional test error'); } return { success: true }; -};`, -}; +};`; describe('Logic Function Execution', () => { const createdFunctionIds: string[] = []; @@ -54,22 +48,28 @@ describe('Logic Function Execution', () => { it('should execute the default logic function template', async () => { // Create the function with default template code - const { data: createData } = await createDefaultLogicFunction({ + const { data: createData } = await createOneLogicFunction({ input: { name: 'Test Default Function', }, expectToFail: false, }); - const functionId = createData?.createDefaultLogicFunction?.id; + const functionId = createData?.createOneLogicFunction?.id; expect(functionId).toBeDefined(); createdFunctionIds.push(functionId); await updateLogicFunctionSource({ input: { - id: createData.createDefaultLogicFunction.id, - code: DEFAULT_TEMPLATE_FUNCTION_CODE, + id: createData.createOneLogicFunction.id, + update: { + sourceHandlerCode: DEFAULT_TEMPLATE_FUNCTION_CODE, + toolInputSchema: { + type: 'object', + properties: { message: { type: 'string' } }, + }, + }, }, expectToFail: false, }); @@ -79,7 +79,6 @@ describe('Logic Function Execution', () => { input: { id: functionId, payload: { a: 'hello', b: 42 }, - forceRebuild: true, }, expectToFail: false, }); @@ -92,29 +91,35 @@ describe('Logic Function Execution', () => { expect(result?.status).toBe(LogicFunctionExecutionStatus.SUCCESS); expect(result?.data).toMatchObject({ - message: 'Hello, input: hello and 42', + message: 'Toto: hello and 42', }); expect(result?.duration).toBeGreaterThan(0); }); it('should execute a function with external packages (lodash.groupby)', async () => { // Create the function with the external packages code - const { data: createData } = await createDefaultLogicFunction({ + const { data: createData } = await createOneLogicFunction({ input: { name: 'External Packages Test', }, expectToFail: false, }); - const functionId = createData?.createDefaultLogicFunction?.id; + const functionId = createData?.createOneLogicFunction?.id; expect(functionId).toBeDefined(); createdFunctionIds.push(functionId); await updateLogicFunctionSource({ input: { - id: createData.createDefaultLogicFunction.id, - code: EXTERNAL_PACKAGES_FUNCTION_CODE, + id: createData.createOneLogicFunction.id, + update: { + sourceHandlerCode: EXTERNAL_PACKAGES_FUNCTION_CODE, + toolInputSchema: { + type: 'object', + properties: { message: { type: 'string' } }, + }, + }, }, expectToFail: false, }); @@ -130,7 +135,6 @@ describe('Logic Function Execution', () => { { category: 'fruit', name: 'banana' }, ], }, - forceRebuild: true, }, expectToFail: false, }); @@ -160,24 +164,110 @@ describe('Logic Function Execution', () => { ); }); + it('should create logic function without source', async () => { + // Create the function with default template code + const { data: createData } = await createOneLogicFunction({ + input: { + name: 'Test Default Function', + }, + expectToFail: false, + }); + + const functionId = createData?.createOneLogicFunction?.id; + + expect(functionId).toBeDefined(); + createdFunctionIds.push(functionId); + + // Execute with the default template's expected params: { a: string, b: number } + const { data: executeData } = await executeLogicFunction({ + input: { + id: functionId, + payload: { a: 'hello', b: 42 }, + }, + expectToFail: false, + }); + + const result = executeData?.executeOneLogicFunction; + + if (result?.status !== LogicFunctionExecutionStatus.SUCCESS) { + throw new Error(JSON.stringify(result?.error, null, 2)); + } + + expect(result?.status).toBe(LogicFunctionExecutionStatus.SUCCESS); + expect(result?.data).toMatchObject({ + message: 'Hello, input: hello and 42', + }); + expect(result?.duration).toBeGreaterThan(0); + }); + + it('should create logic function with source', async () => { + // Create the function with default template code + const { data: createData } = await createOneLogicFunction({ + input: { + name: 'Test Default Function', + source: { + sourceHandlerCode: DEFAULT_TEMPLATE_FUNCTION_CODE, + toolInputSchema: { + type: 'object', + properties: { message: { type: 'string' } }, + }, + handlerName: 'main', + }, + }, + expectToFail: false, + }); + + const functionId = createData?.createOneLogicFunction?.id; + + expect(functionId).toBeDefined(); + createdFunctionIds.push(functionId); + + // Execute with the default template's expected params: { a: string, b: number } + const { data: executeData } = await executeLogicFunction({ + input: { + id: functionId, + payload: { a: 'hello', b: 42 }, + }, + expectToFail: false, + }); + + const result = executeData?.executeOneLogicFunction; + + if (result?.status !== LogicFunctionExecutionStatus.SUCCESS) { + throw new Error(JSON.stringify(result?.error, null, 2)); + } + + expect(result?.status).toBe(LogicFunctionExecutionStatus.SUCCESS); + expect(result?.data).toMatchObject({ + message: 'Toto: hello and 42', + }); + expect(result?.duration).toBeGreaterThan(0); + }); + it('should handle errors thrown by logic functions', async () => { // Create the function with error-throwing code - const { data: createData } = await createDefaultLogicFunction({ + const { data: createData } = await createOneLogicFunction({ input: { name: 'Error Test Function', }, expectToFail: false, }); - const functionId = createData?.createDefaultLogicFunction?.id; + const functionId = createData?.createOneLogicFunction?.id; expect(functionId).toBeDefined(); createdFunctionIds.push(functionId); await updateLogicFunctionSource({ input: { - id: createData.createDefaultLogicFunction.id, - code: ERROR_FUNCTION_CODE, + id: createData.createOneLogicFunction.id, + update: { + sourceHandlerCode: ERROR_FUNCTION_CODE, + toolInputSchema: { + type: 'object', + properties: { message: { type: 'string' } }, + }, + }, }, expectToFail: false, }); @@ -187,7 +277,6 @@ describe('Logic Function Execution', () => { input: { id: functionId, payload: { shouldFail: false }, - forceRebuild: true, }, expectToFail: false, }); @@ -204,7 +293,6 @@ describe('Logic Function Execution', () => { input: { id: functionId, payload: { shouldFail: true }, - forceRebuild: true, }, expectToFail: false, // The GraphQL call succeeds, but the function execution fails }); diff --git a/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-default-logic-function-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-default-logic-function-query-factory.util.ts deleted file mode 100644 index 5f46171b1c9..00000000000 --- a/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-default-logic-function-query-factory.util.ts +++ /dev/null @@ -1,32 +0,0 @@ -import gql from 'graphql-tag'; -import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; - -import { type CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input'; - -export type CreateDefaultLogicFunctionFactoryInput = - CreateDefaultLogicFunctionInput; - -const DEFAULT_LOGIC_FUNCTION_GQL_FIELDS = ` - id - name - description - runtime - createdAt - updatedAt -`; - -export const createDefaultLogicFunctionQueryFactory = ({ - input, - gqlFields = DEFAULT_LOGIC_FUNCTION_GQL_FIELDS, -}: PerformMetadataQueryParams) => ({ - query: gql` - mutation CreateDefaultOneLogicFunction($input: CreateDefaultLogicFunctionInput!) { - createDefaultLogicFunction(input: $input) { - ${gqlFields} - } - } - `, - variables: { - input, - }, -}); diff --git a/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-logic-function-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-logic-function-query-factory.util.ts new file mode 100644 index 00000000000..e999743782e --- /dev/null +++ b/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-logic-function-query-factory.util.ts @@ -0,0 +1,32 @@ +import gql from 'graphql-tag'; +import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; + +import { type CreateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input'; + +export type CreateLogicFunctionFactoryInput = + CreateLogicFunctionFromSourceInput; + +const LOGIC_FUNCTION_GQL_FIELDS = ` + id + name + description + runtime + createdAt + updatedAt +`; + +export const createOneLogicFunctionQueryFactory = ({ + input, + gqlFields = LOGIC_FUNCTION_GQL_FIELDS, +}: PerformMetadataQueryParams) => ({ + query: gql` + mutation CreateOneLogicFunction($input: CreateLogicFunctionFromSourceInput!) { + createOneLogicFunction(input: $input) { + ${gqlFields} + } + } + `, + variables: { + input, + }, +}); diff --git a/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-default-logic-function.util.ts b/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-logic-function.util.ts similarity index 70% rename from packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-default-logic-function.util.ts rename to packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-logic-function.util.ts index a395fc5fa56..2b6722869ee 100644 --- a/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-default-logic-function.util.ts +++ b/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/create-logic-function.util.ts @@ -1,7 +1,4 @@ -import { - type CreateDefaultLogicFunctionFactoryInput, - createDefaultLogicFunctionQueryFactory, -} from 'test/integration/metadata/suites/logic-function/utils/create-default-logic-function-query-factory.util'; +import { createOneLogicFunctionQueryFactory } from 'test/integration/metadata/suites/logic-function/utils/create-logic-function-query-factory.util'; import { makeMetadataAPIRequest } from 'test/integration/metadata/suites/utils/make-metadata-api-request.util'; import { type CommonResponseBody } from 'test/integration/metadata/types/common-response-body.type'; import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; @@ -9,16 +6,17 @@ import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; import { type LogicFunctionDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function.dto'; +import { type CreateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function-from-source.input'; -export const createDefaultLogicFunction = async ({ +export const createOneLogicFunction = async ({ input, gqlFields, expectToFail = false, token, -}: PerformMetadataQueryParams): CommonResponseBody<{ - createDefaultLogicFunction: LogicFunctionDTO; +}: PerformMetadataQueryParams): CommonResponseBody<{ + createOneLogicFunction: LogicFunctionDTO; }> => { - const graphqlOperation = createDefaultLogicFunctionQueryFactory({ + const graphqlOperation = createOneLogicFunctionQueryFactory({ input, gqlFields, }); diff --git a/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/update-logic-function-source-query-factory.util.ts b/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/update-logic-function-source-query-factory.util.ts index b7a62437d5e..098d5c4031b 100644 --- a/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/update-logic-function-source-query-factory.util.ts +++ b/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/update-logic-function-source-query-factory.util.ts @@ -1,19 +1,19 @@ import gql from 'graphql-tag'; import { type PerformMetadataQueryParams } from 'test/integration/metadata/types/perform-metadata-query.type'; -import { type UpdateLogicFunctionSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-source.input'; +import { type UpdateLogicFunctionFromSourceInput } from 'src/engine/metadata-modules/logic-function/dtos/update-logic-function-from-source.input'; -export type UpdateLogicFunctionSourceFactoryInput = - UpdateLogicFunctionSourceInput; +export type UpdateLogicFunctionFromSourceFactoryInput = + UpdateLogicFunctionFromSourceInput; -export const updateLogicFunctionSourceQueryFactory = ({ +export const updateLogicFunctionFromSourceQueryFactory = ({ input, -}: PerformMetadataQueryParams) => ({ +}: PerformMetadataQueryParams) => ({ query: gql` - mutation UpdateLogicFunctionSource( - $input: UpdateLogicFunctionSourceInput! + mutation UpdateOneLogicFunction( + $input: UpdateLogicFunctionFromSourceInput! ) { - updateLogicFunctionSource(input: $input) + updateOneLogicFunction(input: $input) } `, variables: { diff --git a/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/update-logic-function-source.util.ts b/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/update-logic-function-source.util.ts index 737d835d334..4a45327f93c 100644 --- a/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/update-logic-function-source.util.ts +++ b/packages/twenty-server/test/integration/metadata/suites/logic-function/utils/update-logic-function-source.util.ts @@ -4,8 +4,8 @@ import { type PerformMetadataQueryParams } from 'test/integration/metadata/types import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util'; import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util'; import { - type UpdateLogicFunctionSourceFactoryInput, - updateLogicFunctionSourceQueryFactory, + type UpdateLogicFunctionFromSourceFactoryInput, + updateLogicFunctionFromSourceQueryFactory, } from 'test/integration/metadata/suites/logic-function/utils/update-logic-function-source-query-factory.util'; import { type LogicFunctionDTO } from 'src/engine/metadata-modules/logic-function/dtos/logic-function.dto'; @@ -15,10 +15,10 @@ export const updateLogicFunctionSource = async ({ gqlFields, expectToFail = false, token, -}: PerformMetadataQueryParams): CommonResponseBody<{ +}: PerformMetadataQueryParams): CommonResponseBody<{ updateOneLogicFunction: LogicFunctionDTO; }> => { - const graphqlOperation = updateLogicFunctionSourceQueryFactory({ + const graphqlOperation = updateLogicFunctionFromSourceQueryFactory({ input, gqlFields, }); diff --git a/packages/twenty-shared/src/application/manifestType.ts b/packages/twenty-shared/src/application/manifestType.ts index 220533d0a2c..5d2524d18df 100644 --- a/packages/twenty-shared/src/application/manifestType.ts +++ b/packages/twenty-shared/src/application/manifestType.ts @@ -1,4 +1,3 @@ -import { type Sources } from '@/types'; import { type ApplicationManifest } from './applicationType'; import { type AssetManifest } from './assetManifestType'; import { type FieldManifest } from './fieldManifestType'; @@ -15,5 +14,4 @@ export type Manifest = { frontComponents: FrontComponentManifest[]; roles: RoleManifest[]; publicAssets: AssetManifest[]; - sources: Sources; };