Rename serverlessFunction to logicFunction (#17494)

## Summary

Rename "Serverless Function" to "Logic Function" across the codebase for
clearer naming.

### Environment Variable Changes

| Old | New |
|-----|-----|
| `SERVERLESS_TYPE` | `LOGIC_FUNCTION_TYPE` |
| `SERVERLESS_LAMBDA_REGION` | `LOGIC_FUNCTION_LAMBDA_REGION` |
| `SERVERLESS_LAMBDA_ROLE` | `LOGIC_FUNCTION_LAMBDA_ROLE` |
| `SERVERLESS_LAMBDA_SUBHOSTING_URL` |
`LOGIC_FUNCTION_LAMBDA_SUBHOSTING_URL` |
| `SERVERLESS_LAMBDA_ACCESS_KEY_ID` |
`LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID` |
| `SERVERLESS_LAMBDA_SECRET_ACCESS_KEY` |
`LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY` |

### Breaking Changes

- Environment variables must be updated in production deployments
- Database migration renames `serverlessFunction` → `logicFunction`
tables
This commit is contained in:
Charles Bochet
2026-01-28 01:42:19 +01:00
committed by GitHub
parent 59d123d2b1
commit da6f1bbef3
351 changed files with 5054 additions and 5139 deletions
+1 -1
View File
@@ -50,7 +50,7 @@ jobs:
needs: [changed-files-check, sdk-test]
strategy:
matrix:
task: [test:integration, test:e2e]
task: [test:integration]
if: needs.changed-files-check.outputs.any_changed == 'true'
services:
postgres:
@@ -9,11 +9,11 @@ Apps are currently in alpha testing. The feature is functional but still evolvin
## What Are Apps?
Apps let you build and manage Twenty customizations **as code**. Instead of configuring everything through the UI, you define your data model and serverless functions in code — making it faster to build, maintain, and roll out to multiple workspaces.
Apps let you build and manage Twenty customizations **as code**. Instead of configuring everything through the UI, you define your data model and logic functions in code — making it faster to build, maintain, and roll out to multiple workspaces.
**What you can do today:**
- Define custom objects and fields as code (managed data model)
- Build serverless functions with custom triggers
- Build logic functions with custom triggers
- Deploy the same app across multiple workspaces
**Coming soon:**
@@ -159,9 +159,9 @@ At a high level:
- **README.md**: A short README in the app root with basic instructions.
- **src/**: The main place where you define your application-as-code:
- `application.config.ts`: Global configuration for your app (metadata and runtime wiring). See "Application config" below.
- `*.role.ts`: Role definitions used by your serverless functions. See "Default function role" below.
- `*.role.ts`: Role definitions used by your logic functions. See "Default function role" below.
- `*.object.ts`: Custom object definitions.
- `*.function.ts`: Serverless function definitions.
- `*.function.ts`: Logic function definitions.
- `*.front-component.tsx`: Front component definitions.
Later commands will add more files and folders:
@@ -214,7 +214,7 @@ The SDK provides four helper functions with built-in validation for defining you
|----------|---------|
| `defineApp()` | Configure application metadata |
| `defineObject()` | Define custom objects with fields |
| `defineFunction()` | Define serverless functions with handlers |
| `defineFunction()` | Define logic functions with handlers |
| `defineRole()` | Configure role permissions and object access |
These functions validate your configuration at runtime and provide better IDE autocompletion and type safety.
@@ -344,7 +344,7 @@ Notes:
#### Roles and permissions
Applications can define roles that encapsulate permissions on your workspace's objects and actions. The field `functionRoleUniversalIdentifier` in `application.config.ts` designates the default role used by your app's serverless functions.
Applications can define roles that encapsulate permissions on your workspace's objects and actions. The field `functionRoleUniversalIdentifier` in `application.config.ts` designates the default role used by your app's logic functions.
- The runtime API key injected as `TWENTY_API_KEY` is derived from this default function role.
- The typed client will be restricted to the permissions granted to that role.
@@ -405,7 +405,7 @@ Notes:
- `permissionFlags` control access to platform-level capabilities. Keep them minimal; add only what you need.
- See a working example in the Hello World app: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
### Serverless function config and entrypoint
### Logic function config and entrypoint
Each function file uses `defineFunction()` to export a configuration with a handler and optional triggers. Use the `*.function.ts` file suffix for automatic detection.
@@ -497,7 +497,7 @@ const handler = async (event: RoutePayload) => {
**To migrate existing functions:** Update your handler to destructure from `event.body`, `event.queryStringParameters`, or `event.pathParameters` instead of directly from the params object.
</Warning>
When a route trigger invokes your function, it receives a `RoutePayload` object that follows the AWS HTTP API v2 format. Import the type from `twenty-sdk`:
When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the AWS HTTP API v2 format. Import the type from `twenty-sdk`:
```typescript
import { defineFunction, type RoutePayload } from 'twenty-sdk';
@@ -527,7 +527,7 @@ The `RoutePayload` type has the following structure:
### Forwarding HTTP headers
By default, HTTP headers from incoming requests are **not** passed to your serverless function for security reasons. To access specific headers, explicitly list them in the `forwardedRequestHeaders` array:
By default, HTTP headers from incoming requests are **not** passed to your logic function for security reasons. To access specific headers, explicitly list them in the `forwardedRequestHeaders` array:
```typescript
export default defineFunction({
@@ -581,7 +581,7 @@ const { me } = await client.query({ me: { id: true, displayName: true } });
The client is re-generated by `yarn app:generate`. Re-run after changing your objects or when onboarding to a new workspace.
#### Runtime credentials in serverless functions
#### Runtime credentials in logic functions
When your function runs on Twenty, the platform injects credentials as environment variables before your code executes:
@@ -590,7 +590,7 @@ When your function runs on Twenty, the platform injects credentials as environme
Notes:
- You do not need to pass URL or API key to the generated client. It reads `TWENTY_API_URL` and `TWENTY_API_KEY` from process.env at runtime.
- The API key's permissions are determined by the role referenced in your `application.config.ts` via `functionRoleUniversalIdentifier`. This is the default role used by serverless functions of your application.
- The API key's permissions are determined by the role referenced in your `application.config.ts` via `functionRoleUniversalIdentifier`. This is the default role used by logic functions of your application.
- Applications can define roles to follow leastprivilege. Grant only the permissions your functions need, then point `functionRoleUniversalIdentifier` to that role's universal identifier.
@@ -289,19 +289,19 @@ yarn command:prod cron:workflow:automated-cron-trigger
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
</Warning>
## Serverless Functions
## Logic Functions
Twenty supports serverless functions for workflows and custom logic. The execution environment is configured via the `SERVERLESS_TYPE` environment variable.
Twenty supports logic functions for workflows and custom logic. The execution environment is configured via the `SERVERLESS_TYPE` environment variable.
<Warning>
**Security Notice:** The local serverless driver (`SERVERLESS_TYPE=LOCAL`) runs code directly on the host in a Node.js process with no sandboxing. It should only be used for trusted code in development. For production deployments handling untrusted code, we highly recommend using `SERVERLESS_TYPE=LAMBDA` or `SERVERLESS_TYPE=DISABLED`.
**Security Notice:** The local driver (`SERVERLESS_TYPE=LOCAL`) runs code directly on the host in a Node.js process with no sandboxing. It should only be used for trusted code in development. For production deployments handling untrusted code, we highly recommend using `SERVERLESS_TYPE=LAMBDA` or `SERVERLESS_TYPE=DISABLED`.
</Warning>
### Available Drivers
| Driver | Environment Variable | Use Case | Security Level |
|--------|---------------------|----------|----------------|
| Disabled | `SERVERLESS_TYPE=DISABLED` | Disable serverless functions entirely | N/A |
| Disabled | `SERVERLESS_TYPE=DISABLED` | Disable logic functions entirely | N/A |
| Local | `SERVERLESS_TYPE=LOCAL` | Development and trusted environments | Low (no sandboxing) |
| Lambda | `SERVERLESS_TYPE=LAMBDA` | Production with untrusted code | High (hardware-level isolation) |
@@ -321,11 +321,11 @@ SERVERLESS_LAMBDA_ACCESS_KEY_ID=your-access-key
SERVERLESS_LAMBDA_SECRET_ACCESS_KEY=your-secret-key
```
**To disable serverless functions:**
**To disable logic functions:**
```bash
SERVERLESS_TYPE=DISABLED
```
<Note>
When using `SERVERLESS_TYPE=DISABLED`, any attempt to execute a serverless function will return an error. This is useful if you want to run Twenty without serverless function capabilities.
When using `SERVERLESS_TYPE=DISABLED`, any attempt to execute a logic function will return an error. This is useful if you want to run Twenty without logic function capabilities.
</Note>
@@ -7,7 +7,7 @@ description: Get familiar with essential terminology used in Twenty.
API (Application Programming Interface) allows you to connect Twenty with other software systems and build custom integrations.
## Apps
Apps are custom extensions built as code that can define data models and serverless functions. They enable developers to create reusable customizations that can be deployed across multiple workspaces.
Apps are custom extensions built as code that can define data models and logic functions. They enable developers to create reusable customizations that can be deployed across multiple workspaces.
## Code Actions
Code Actions are workflow steps that let you write custom JavaScript to transform data, make calculations, or perform complex logic that isn't possible with built-in actions.
@@ -135,7 +135,7 @@ You can attach files to emails sent from workflows. The attachment is a static f
| **Event confirmations** | Event details or agenda |
<Note>
Attachments are static—the same file is sent to all recipients. For dynamic documents (like personalized quotes), generate and attach files using a [Serverless Function](/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty).
Attachments are static—the same file is sent to all recipients. For dynamic documents (like personalized quotes), generate and attach files using a [Logic Function](/user-guide/workflows/how-tos/connect-to-other-tools/generate-pdf-from-twenty).
</Note>
## Best Practices
@@ -215,7 +215,7 @@ Runs custom JavaScript within your workflow.
- Test code directly in the step
<Note>
If you need to use external API keys in your code, you must input them directly in the function body. You cannot configure API keys elsewhere and reference them in the serverless function.
If you need to use external API keys in your code, you must input them directly in the function body. You cannot configure API keys elsewhere and reference them in the logic function.
</Note>
<Tip>
@@ -7,7 +7,7 @@ Automatically generate or fetch a PDF and attach it to a record in Twenty. This
## Overview
This workflow uses a **Manual Trigger** so users can generate a PDF on demand for any selected record. A **Serverless Function** handles:
This workflow uses a **Manual Trigger** so users can generate a PDF on demand for any selected record. A **Logic Function** handles:
1. Downloading the PDF from a URL (from a PDF generation service)
2. Uploading the file to Twenty
3. Creating an Attachment linked to the record
@@ -16,7 +16,7 @@ This workflow uses a **Manual Trigger** so users can generate a PDF on demand fo
Before setting up the workflow:
1. **Create an API Key**: Go to **Settings → APIs** and create a new API key. You'll need this token for the serverless function.
1. **Create an API Key**: Go to **Settings → APIs** and create a new API key. You'll need this token for the logic function.
2. **Set up a PDF generation service** (optional): If you want to dynamically generate PDFs (e.g., quotes), use a service like Carbone, PDFMonkey, or DocuSeal to create the PDF and get a download URL.
## Step-by-Step Setup
@@ -31,9 +31,9 @@ Before setting up the workflow:
With a Manual Trigger, users can run this workflow using a button that appears on the top right once a record is selected, to generate and attach a PDF.
</Tip>
### Step 2: Add a Serverless Function
### Step 2: Add a Logic Function
1. Add a **Serverless Function** action
1. Add a **Code** action (logic function)
2. Create a new function with the code below
3. Configure the input parameters
@@ -44,10 +44,10 @@ With a Manual Trigger, users can run this workflow using a button that appears o
| `companyId` | `{{trigger.object.id}}` |
<Note>
If attaching to a different object (Person, Opportunity, etc.), rename the parameter accordingly (e.g., `personId`, `opportunityId`) and update the serverless function.
If attaching to a different object (Person, Opportunity, etc.), rename the parameter accordingly (e.g., `personId`, `opportunityId`) and update the logic function.
</Note>
#### Serverless Function Code
#### Logic Function Code
```typescript
export const main = async (
@@ -170,7 +170,7 @@ Update both the function parameter and the `variables.data` object in the attach
If using a PDF generation service, you can:
1. First make an HTTP Request action to generate the PDF
2. Pass the returned PDF URL to the serverless function as a parameter
2. Pass the returned PDF URL to the logic function as a parameter
```typescript
export const main = async (
@@ -209,7 +209,7 @@ For creating dynamic quotes or invoices:
- **DocuSeal** - Document automation platform
- **Documint** - API-first document generation
Each service provides an API that returns a PDF URL, which you can then pass to the serverless function.
Each service provides an API that returns a PDF URL, which you can then pass to the logic function.
## Troubleshooting
@@ -222,6 +222,6 @@ Each service provides an API that returns a PDF URL, which you can then pass to
## Related
- [Workflow Triggers](/user-guide/workflows/capabilities/workflow-triggers)
- [Serverless Functions](/user-guide/workflows/capabilities/workflow-actions#serverless-function)
- [Logic Functions](/user-guide/workflows/capabilities/workflow-actions#code)
- [Generate a Quote or Invoice from Twenty](/user-guide/workflows/how-tos/connect-to-other-tools/generate-quote-or-invoice-from-twenty)
@@ -125,7 +125,7 @@ description: Frequently asked questions about workflows in Twenty.
</Accordion>
<Accordion title="What's the maximum execution time for Code actions?">
Code actions (serverless functions) have a **default timeout of 5 minutes** (300 seconds).
Code actions (logic functions) have a **default timeout of 5 minutes** (300 seconds).
The maximum configurable timeout is **15 minutes** (900 seconds).
File diff suppressed because it is too large Load Diff
+161 -164
View File
@@ -186,11 +186,10 @@ export enum AggregateOperations {
export enum AllMetadataName {
agent = 'agent',
commandMenuItem = 'commandMenuItem',
cronTrigger = 'cronTrigger',
databaseEventTrigger = 'databaseEventTrigger',
fieldMetadata = 'fieldMetadata',
frontComponent = 'frontComponent',
index = 'index',
logicFunction = 'logicFunction',
navigationMenuItem = 'navigationMenuItem',
objectMetadata = 'objectMetadata',
pageLayout = 'pageLayout',
@@ -198,10 +197,8 @@ export enum AllMetadataName {
pageLayoutWidget = 'pageLayoutWidget',
role = 'role',
roleTarget = 'roleTarget',
routeTrigger = 'routeTrigger',
rowLevelPermissionPredicate = 'rowLevelPermissionPredicate',
rowLevelPermissionPredicateGroup = 'rowLevelPermissionPredicateGroup',
serverlessFunction = 'serverlessFunction',
skill = 'skill',
view = 'view',
viewField = 'viewField',
@@ -273,13 +270,13 @@ export type Application = {
agents: Array<Agent>;
applicationVariables: Array<ApplicationVariable>;
canBeUninstalled: Scalars['Boolean'];
defaultServerlessFunctionRole?: Maybe<Role>;
defaultServerlessFunctionRoleId?: Maybe<Scalars['String']>;
defaultLogicFunctionRole?: Maybe<Role>;
defaultLogicFunctionRoleId?: Maybe<Scalars['String']>;
description: Scalars['String'];
id: Scalars['UUID'];
logicFunctions: Array<LogicFunction>;
name: Scalars['String'];
objects: Array<Object>;
serverlessFunctions: Array<ServerlessFunction>;
universalIdentifier: Scalars['String'];
version: Scalars['String'];
};
@@ -741,11 +738,11 @@ export enum ConfigVariablesGroup {
GOOGLE_AUTH = 'GOOGLE_AUTH',
LLM = 'LLM',
LOGGING = 'LOGGING',
LOGIC_FUNCTION_CONFIG = 'LOGIC_FUNCTION_CONFIG',
METERING = 'METERING',
MICROSOFT_AUTH = 'MICROSOFT_AUTH',
OTHER = 'OTHER',
RATE_LIMITING = 'RATE_LIMITING',
SERVERLESS_CONFIG = 'SERVERLESS_CONFIG',
SERVER_CONFIG = 'SERVER_CONFIG',
SSL = 'SSL',
STORAGE_CONFIG = 'STORAGE_CONFIG',
@@ -957,6 +954,18 @@ export type CreateFieldInput = {
type: FieldMetadataType;
};
export type CreateLogicFunctionInput = {
builtHandlerPath?: InputMaybe<Scalars['String']>;
code?: InputMaybe<Scalars['JSON']>;
description?: InputMaybe<Scalars['String']>;
handlerName?: InputMaybe<Scalars['String']>;
isTool?: InputMaybe<Scalars['Boolean']>;
name: Scalars['String'];
sourceHandlerPath?: InputMaybe<Scalars['String']>;
timeoutSeconds?: InputMaybe<Scalars['Float']>;
toolInputSchema?: InputMaybe<Scalars['JSON']>;
};
export type CreateObjectInput = {
description?: InputMaybe<Scalars['String']>;
icon?: InputMaybe<Scalars['String']>;
@@ -1019,18 +1028,6 @@ export type CreateRoleInput = {
label: Scalars['String'];
};
export type CreateServerlessFunctionInput = {
builtHandlerPath?: InputMaybe<Scalars['String']>;
code?: InputMaybe<Scalars['JSON']>;
description?: InputMaybe<Scalars['String']>;
handlerName?: InputMaybe<Scalars['String']>;
isTool?: InputMaybe<Scalars['Boolean']>;
name: Scalars['String'];
sourceHandlerPath?: InputMaybe<Scalars['String']>;
timeoutSeconds?: InputMaybe<Scalars['Float']>;
toolInputSchema?: InputMaybe<Scalars['JSON']>;
};
export type CreateViewFieldInput = {
aggregateOperation?: InputMaybe<AggregateOperations>;
fieldMetadataId: Scalars['UUID'];
@@ -1348,12 +1345,12 @@ export type EventWithQueryIds = {
queryIds: Array<Scalars['String']>;
};
export type ExecuteServerlessFunctionInput = {
/** Id of the serverless function to execute */
export type ExecuteLogicFunctionInput = {
/** Id of the logic function to execute */
id: Scalars['UUID'];
/** Payload in JSON format */
payload: Scalars['JSON'];
/** Version of the serverless function to execute */
/** Version of the logic function to execute */
version?: Scalars['String'];
};
@@ -1522,10 +1519,10 @@ export enum FileFolder {
BuiltFunction = 'BuiltFunction',
File = 'File',
FilesField = 'FilesField',
LogicFunction = 'LogicFunction',
LogicFunctionToDelete = 'LogicFunctionToDelete',
PersonPicture = 'PersonPicture',
ProfilePicture = 'ProfilePicture',
ServerlessFunction = 'ServerlessFunction',
ServerlessFunctionToDelete = 'ServerlessFunctionToDelete',
Source = 'Source',
TemporaryFilesField = 'TemporaryFilesField',
WorkspaceLogo = 'WorkspaceLogo'
@@ -1606,7 +1603,7 @@ export type GetAuthorizationUrlForSsoOutput = {
type: Scalars['String'];
};
export type GetServerlessFunctionSourceCodeInput = {
export type GetLogicFunctionSourceCodeInput = {
/** The id of the function. */
id: Scalars['ID'];
/** The version of the function */
@@ -1880,6 +1877,77 @@ export type Location = {
lng?: Maybe<Scalars['Float']>;
};
export type LogicFunction = {
__typename?: 'LogicFunction';
applicationId?: Maybe<Scalars['UUID']>;
builtHandlerPath: Scalars['String'];
createdAt: Scalars['DateTime'];
cronTriggerSettings?: Maybe<Scalars['JSON']>;
databaseEventTriggerSettings?: Maybe<Scalars['JSON']>;
description?: Maybe<Scalars['String']>;
handlerName: Scalars['String'];
httpRouteTriggerSettings?: Maybe<Scalars['JSON']>;
id: Scalars['UUID'];
isTool: Scalars['Boolean'];
latestVersion?: Maybe<Scalars['String']>;
name: Scalars['String'];
publishedVersions: Array<Scalars['String']>;
runtime: Scalars['String'];
sourceHandlerPath: Scalars['String'];
timeoutSeconds: Scalars['Float'];
toolInputSchema?: Maybe<Scalars['JSON']>;
universalIdentifier?: Maybe<Scalars['UUID']>;
updatedAt: Scalars['DateTime'];
};
export type LogicFunctionExecutionResult = {
__typename?: 'LogicFunctionExecutionResult';
/** Execution result in JSON format */
data?: Maybe<Scalars['JSON']>;
/** Execution duration in milliseconds */
duration: Scalars['Float'];
/** Execution error in JSON format */
error?: Maybe<Scalars['JSON']>;
/** Execution Logs */
logs: Scalars['String'];
/** Execution status */
status: LogicFunctionExecutionStatus;
};
/** Status of the logic function execution */
export enum LogicFunctionExecutionStatus {
ERROR = 'ERROR',
IDLE = 'IDLE',
SUCCESS = 'SUCCESS'
}
export type LogicFunctionIdInput = {
/** The id of the function. */
id: Scalars['ID'];
};
export type LogicFunctionLayer = {
__typename?: 'LogicFunctionLayer';
applicationId?: Maybe<Scalars['UUID']>;
createdAt: Scalars['DateTime'];
id: Scalars['UUID'];
updatedAt: Scalars['DateTime'];
};
export type LogicFunctionLogs = {
__typename?: 'LogicFunctionLogs';
/** Execution Logs */
logs: Scalars['String'];
};
export type LogicFunctionLogsInput = {
applicationId?: InputMaybe<Scalars['UUID']>;
applicationUniversalIdentifier?: InputMaybe<Scalars['UUID']>;
id?: InputMaybe<Scalars['UUID']>;
name?: InputMaybe<Scalars['String']>;
universalIdentifier?: InputMaybe<Scalars['UUID']>;
};
export type LoginTokenOutput = {
__typename?: 'LoginTokenOutput';
loginToken: AuthToken;
@@ -1934,10 +2002,10 @@ export type Mutation = {
createOneAgent: Agent;
createOneAppToken: AppToken;
createOneField: Field;
createOneLogicFunction: LogicFunction;
createOneLogicFunctionLayer: LogicFunctionLayer;
createOneObject: Object;
createOneRole: Role;
createOneServerlessFunction: ServerlessFunction;
createOneServerlessFunctionLayer: ServerlessFunctionLayer;
createPageLayout: PageLayout;
createPageLayoutTab: PageLayoutTab;
createPageLayoutWidget: PageLayoutWidget;
@@ -1962,9 +2030,9 @@ export type Mutation = {
deleteJobs: DeleteJobsResponse;
deleteOneAgent: Agent;
deleteOneField: Field;
deleteOneLogicFunction: LogicFunction;
deleteOneObject: Object;
deleteOneRole: Scalars['String'];
deleteOneServerlessFunction: ServerlessFunction;
deletePublicDomain: Scalars['Boolean'];
deleteSSOIdentityProvider: DeleteSsoOutput;
deleteTwoFactorAuthenticationMethod: DeleteTwoFactorAuthenticationMethodOutput;
@@ -1992,7 +2060,7 @@ export type Mutation = {
emailPasswordResetLink: EmailPasswordResetLinkOutput;
enablePostgresProxy: PostgresCredentials;
endSubscriptionTrialPeriod: BillingEndTrialPeriodOutput;
executeOneServerlessFunction: ServerlessFunctionExecutionResult;
executeOneLogicFunction: LogicFunctionExecutionResult;
generateApiKeyToken: ApiKeyToken;
generateTransientToken: TransientTokenOutput;
getAuthTokensFromLoginToken: AuthTokens;
@@ -2003,7 +2071,7 @@ export type Mutation = {
initiateOTPProvisioning: InitiateTwoFactorAuthenticationProvisioningOutput;
initiateOTPProvisioningForAuthenticatedUser: InitiateTwoFactorAuthenticationProvisioningOutput;
installApplication: Scalars['Boolean'];
publishServerlessFunction: ServerlessFunction;
publishLogicFunction: LogicFunction;
removeQueryFromEventStream: Scalars['Boolean'];
removeRoleFromAgent: Scalars['Boolean'];
renewToken: AuthTokens;
@@ -2043,9 +2111,9 @@ export type Mutation = {
updateOneAgent: Agent;
updateOneApplicationVariable: Scalars['Boolean'];
updateOneField: Field;
updateOneLogicFunction: LogicFunction;
updateOneObject: Object;
updateOneRole: Role;
updateOneServerlessFunction: ServerlessFunction;
updatePageLayout: PageLayout;
updatePageLayoutTab: PageLayoutTab;
updatePageLayoutWidget: PageLayoutWidget;
@@ -2231,6 +2299,17 @@ export type MutationCreateOneFieldArgs = {
};
export type MutationCreateOneLogicFunctionArgs = {
input: CreateLogicFunctionInput;
};
export type MutationCreateOneLogicFunctionLayerArgs = {
packageJson: Scalars['JSON'];
yarnLock: Scalars['String'];
};
export type MutationCreateOneObjectArgs = {
input: CreateOneObjectInput;
};
@@ -2241,17 +2320,6 @@ export type MutationCreateOneRoleArgs = {
};
export type MutationCreateOneServerlessFunctionArgs = {
input: CreateServerlessFunctionInput;
};
export type MutationCreateOneServerlessFunctionLayerArgs = {
packageJson: Scalars['JSON'];
yarnLock: Scalars['String'];
};
export type MutationCreatePageLayoutArgs = {
input: CreatePageLayoutInput;
};
@@ -2368,6 +2436,11 @@ export type MutationDeleteOneFieldArgs = {
};
export type MutationDeleteOneLogicFunctionArgs = {
input: LogicFunctionIdInput;
};
export type MutationDeleteOneObjectArgs = {
input: DeleteOneObjectInput;
};
@@ -2378,11 +2451,6 @@ export type MutationDeleteOneRoleArgs = {
};
export type MutationDeleteOneServerlessFunctionArgs = {
input: ServerlessFunctionIdInput;
};
export type MutationDeletePublicDomainArgs = {
domain: Scalars['String'];
};
@@ -2499,8 +2567,8 @@ export type MutationEmailPasswordResetLinkArgs = {
};
export type MutationExecuteOneServerlessFunctionArgs = {
input: ExecuteServerlessFunctionInput;
export type MutationExecuteOneLogicFunctionArgs = {
input: ExecuteLogicFunctionInput;
};
@@ -2556,8 +2624,8 @@ export type MutationInstallApplicationArgs = {
};
export type MutationPublishServerlessFunctionArgs = {
input: PublishServerlessFunctionInput;
export type MutationPublishLogicFunctionArgs = {
input: PublishLogicFunctionInput;
};
@@ -2762,6 +2830,11 @@ export type MutationUpdateOneFieldArgs = {
};
export type MutationUpdateOneLogicFunctionArgs = {
input: UpdateLogicFunctionInput;
};
export type MutationUpdateOneObjectArgs = {
input: UpdateOneObjectInput;
};
@@ -2772,11 +2845,6 @@ export type MutationUpdateOneRoleArgs = {
};
export type MutationUpdateOneServerlessFunctionArgs = {
input: UpdateServerlessFunctionInput;
};
export type MutationUpdatePageLayoutArgs = {
id: Scalars['String'];
input: UpdatePageLayoutInput;
@@ -3335,7 +3403,7 @@ export type PublicWorkspaceDataOutput = {
workspaceUrls: WorkspaceUrls;
};
export type PublishServerlessFunctionInput = {
export type PublishLogicFunctionInput = {
/** The id of the function. */
id: Scalars['ID'];
};
@@ -3356,11 +3424,11 @@ export type Query = {
fields: FieldConnection;
findManyAgents: Array<Agent>;
findManyApplications: Array<Application>;
findManyLogicFunctions: Array<LogicFunction>;
findManyPublicDomains: Array<PublicDomain>;
findManyServerlessFunctions: Array<ServerlessFunction>;
findOneAgent: Agent;
findOneApplication: Application;
findOneServerlessFunction: ServerlessFunction;
findOneLogicFunction: LogicFunction;
findWorkspaceFromInviteHash: Workspace;
findWorkspaceInvitations: Array<WorkspaceInvitation>;
getAddressDetails: PlaceDetailsResult;
@@ -3384,6 +3452,7 @@ export type Query = {
getDatabaseConfigVariable: ConfigVariable;
getEmailingDomains: Array<EmailingDomain>;
getIndicatorHealthStatus: AdminPanelHealthServiceData;
getLogicFunctionSourceCode?: Maybe<Scalars['JSON']>;
getMeteredProductsUsage: Array<BillingMeteredProductUsageOutput>;
getPageLayout?: Maybe<PageLayout>;
getPageLayoutTab: PageLayoutTab;
@@ -3397,7 +3466,6 @@ export type Query = {
getQueueMetrics: QueueMetricsData;
getRoles: Array<Role>;
getSSOIdentityProviders: Array<FindAvailableSsoidpOutput>;
getServerlessFunctionSourceCode?: Maybe<Scalars['JSON']>;
getSystemHealthStatus: SystemHealth;
getTimelineCalendarEventsFromCompanyId: TimelineCalendarEventsWithTotal;
getTimelineCalendarEventsFromOpportunityId: TimelineCalendarEventsWithTotal;
@@ -3462,8 +3530,8 @@ export type QueryFindOneApplicationArgs = {
};
export type QueryFindOneServerlessFunctionArgs = {
input: ServerlessFunctionIdInput;
export type QueryFindOneLogicFunctionArgs = {
input: LogicFunctionIdInput;
};
@@ -3487,7 +3555,7 @@ export type QueryGetAutoCompleteAddressArgs = {
export type QueryGetAvailablePackagesArgs = {
input: ServerlessFunctionIdInput;
input: LogicFunctionIdInput;
};
@@ -3566,6 +3634,11 @@ export type QueryGetIndicatorHealthStatusArgs = {
};
export type QueryGetLogicFunctionSourceCodeArgs = {
input: GetLogicFunctionSourceCodeInput;
};
export type QueryGetPageLayoutArgs = {
id: Scalars['String'];
};
@@ -3615,11 +3688,6 @@ export type QueryGetQueueMetricsArgs = {
};
export type QueryGetServerlessFunctionSourceCodeArgs = {
input: GetServerlessFunctionSourceCodeInput;
};
export type QueryGetTimelineCalendarEventsFromCompanyIdArgs = {
companyId: Scalars['UUID'];
page: Scalars['Int'];
@@ -3986,77 +4054,6 @@ export type Sentry = {
release?: Maybe<Scalars['String']>;
};
export type ServerlessFunction = {
__typename?: 'ServerlessFunction';
applicationId?: Maybe<Scalars['UUID']>;
builtHandlerPath: Scalars['String'];
createdAt: Scalars['DateTime'];
cronTriggerSettings?: Maybe<Scalars['JSON']>;
databaseEventTriggerSettings?: Maybe<Scalars['JSON']>;
description?: Maybe<Scalars['String']>;
handlerName: Scalars['String'];
httpRouteTriggerSettings?: Maybe<Scalars['JSON']>;
id: Scalars['UUID'];
isTool: Scalars['Boolean'];
latestVersion?: Maybe<Scalars['String']>;
name: Scalars['String'];
publishedVersions: Array<Scalars['String']>;
runtime: Scalars['String'];
sourceHandlerPath: Scalars['String'];
timeoutSeconds: Scalars['Float'];
toolInputSchema?: Maybe<Scalars['JSON']>;
universalIdentifier?: Maybe<Scalars['UUID']>;
updatedAt: Scalars['DateTime'];
};
export type ServerlessFunctionExecutionResult = {
__typename?: 'ServerlessFunctionExecutionResult';
/** Execution result in JSON format */
data?: Maybe<Scalars['JSON']>;
/** Execution duration in milliseconds */
duration: Scalars['Float'];
/** Execution error in JSON format */
error?: Maybe<Scalars['JSON']>;
/** Execution Logs */
logs: Scalars['String'];
/** Execution status */
status: ServerlessFunctionExecutionStatus;
};
/** Status of the serverless function execution */
export enum ServerlessFunctionExecutionStatus {
ERROR = 'ERROR',
IDLE = 'IDLE',
SUCCESS = 'SUCCESS'
}
export type ServerlessFunctionIdInput = {
/** The id of the function. */
id: Scalars['ID'];
};
export type ServerlessFunctionLayer = {
__typename?: 'ServerlessFunctionLayer';
applicationId?: Maybe<Scalars['UUID']>;
createdAt: Scalars['DateTime'];
id: Scalars['UUID'];
updatedAt: Scalars['DateTime'];
};
export type ServerlessFunctionLogs = {
__typename?: 'ServerlessFunctionLogs';
/** Execution Logs */
logs: Scalars['String'];
};
export type ServerlessFunctionLogsInput = {
applicationId?: InputMaybe<Scalars['UUID']>;
applicationUniversalIdentifier?: InputMaybe<Scalars['UUID']>;
id?: InputMaybe<Scalars['UUID']>;
name?: InputMaybe<Scalars['String']>;
universalIdentifier?: InputMaybe<Scalars['UUID']>;
};
export type SetupOidcSsoInput = {
clientID: Scalars['String'];
clientSecret: Scalars['String'];
@@ -4135,9 +4132,14 @@ export type SubmitFormStepInput = {
export type Subscription = {
__typename?: 'Subscription';
logicFunctionLogs: LogicFunctionLogs;
onDbEvent: OnDbEvent;
onEventSubscription?: Maybe<EventSubscription>;
serverlessFunctionLogs: ServerlessFunctionLogs;
};
export type SubscriptionLogicFunctionLogsArgs = {
input: LogicFunctionLogsInput;
};
@@ -4150,11 +4152,6 @@ export type SubscriptionOnEventSubscriptionArgs = {
eventStreamId: Scalars['String'];
};
export type SubscriptionServerlessFunctionLogsArgs = {
input: ServerlessFunctionLogsInput;
};
export enum SubscriptionInterval {
Month = 'Month',
Year = 'Year'
@@ -4398,6 +4395,24 @@ export type UpdateLabPublicFeatureFlagInput = {
value: Scalars['Boolean'];
};
export type UpdateLogicFunctionInput = {
/** Id of the logic function to update */
id: Scalars['UUID'];
/** The logic function updates */
update: UpdateLogicFunctionInputUpdates;
};
export type UpdateLogicFunctionInputUpdates = {
code: Scalars['JSON'];
description?: InputMaybe<Scalars['String']>;
handlerName?: InputMaybe<Scalars['String']>;
isTool?: InputMaybe<Scalars['Boolean']>;
name: Scalars['String'];
sourceHandlerPath?: InputMaybe<Scalars['String']>;
timeoutSeconds?: InputMaybe<Scalars['Float']>;
toolInputSchema?: InputMaybe<Scalars['JSON']>;
};
export type UpdateObjectPayload = {
description?: InputMaybe<Scalars['String']>;
icon?: InputMaybe<Scalars['String']>;
@@ -4489,24 +4504,6 @@ export type UpdateRolePayload = {
label?: InputMaybe<Scalars['String']>;
};
export type UpdateServerlessFunctionInput = {
/** Id of the serverless function to update */
id: Scalars['UUID'];
/** The serverless function updates */
update: UpdateServerlessFunctionInputUpdates;
};
export type UpdateServerlessFunctionInputUpdates = {
code: Scalars['JSON'];
description?: InputMaybe<Scalars['String']>;
handlerName?: InputMaybe<Scalars['String']>;
isTool?: InputMaybe<Scalars['Boolean']>;
name: Scalars['String'];
sourceHandlerPath?: InputMaybe<Scalars['String']>;
timeoutSeconds?: InputMaybe<Scalars['Float']>;
toolInputSchema?: InputMaybe<Scalars['JSON']>;
};
export type UpdateViewFieldInput = {
/** The id of the view field to update */
id: Scalars['UUID'];
@@ -4,7 +4,7 @@ import { RouterProvider } from 'react-router-dom';
import { useRecoilValue } from 'recoil';
export const AppRouter = () => {
// We want to disable serverless function settings but keep the code for now
// We want to disable logic function settings but keep the code for now
const isFunctionSettingsEnabled = false;
const currentUser = useRecoilValue(currentUserState);
@@ -105,12 +105,12 @@ const SettingsDevelopersApiKeysNew = lazy(() =>
})),
);
const SettingsServerlessFunctionDetail = lazy(() =>
import(
'~/pages/settings/serverless-functions/SettingsServerlessFunctionDetail'
).then((module) => ({
default: module.SettingsServerlessFunctionDetail,
})),
const SettingsLogicFunctionDetail = lazy(() =>
import('~/pages/settings/logic-functions/SettingsLogicFunctionDetail').then(
(module) => ({
default: module.SettingsLogicFunctionDetail,
}),
),
);
const SettingsWorkspace = lazy(() =>
@@ -447,8 +447,8 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
element={<SettingsSkillForm mode="edit" />}
/>
<Route
path={SettingsPath.ServerlessFunctionDetail}
element={<SettingsServerlessFunctionDetail />}
path={SettingsPath.LogicFunctionDetail}
element={<SettingsLogicFunctionDetail />}
/>
<Route path={SettingsPath.Billing} element={<SettingsBilling />} />
<Route path={SettingsPath.Domain} element={<SettingsDomain />} />
@@ -582,8 +582,8 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
element={<SettingsApplicationDetails />}
/>
<Route
path={SettingsPath.ApplicationServerlessFunctionDetail}
element={<SettingsServerlessFunctionDetail />}
path={SettingsPath.ApplicationLogicFunctionDetail}
element={<SettingsLogicFunctionDetail />}
/>
</Route>
@@ -1,11 +1,11 @@
import { gql } from '@apollo/client';
import { AGENT_FRAGMENT } from '@/ai/graphql/fragments/agentFragment';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
import { OBJECT_METADATA_FRAGMENT } from '@/object-metadata/graphql/fragment';
export const APPLICATION_FRAGMENT = gql`
${AGENT_FRAGMENT}
${SERVERLESS_FUNCTION_FRAGMENT}
${LOGIC_FUNCTION_FRAGMENT}
${OBJECT_METADATA_FRAGMENT}
fragment ApplicationFields on Application {
id
@@ -27,8 +27,8 @@ export const APPLICATION_FRAGMENT = gql`
objects {
...ObjectMetadataFields
}
serverlessFunctions {
...ServerlessFunctionFields
logicFunctions {
...LogicFunctionFields
}
}
`;
@@ -23,8 +23,8 @@ import { emitSidePanelCloseEvent } from '@/ui/layout/right-drawer/utils/emitSide
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { getShowPageTabListComponentId } from '@/ui/layout/show-page/utils/getShowPageTabListComponentId';
import { activeTabIdComponentState } from '@/ui/layout/tab-list/states/activeTabIdComponentState';
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowServerlessFunctionTabListComponentId';
import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowServerlessFunctionTabId';
import { WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowLogicFunctionTabListComponentId';
import { WorkflowLogicFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowLogicFunctionTabId';
import { useRecoilCallback } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
@@ -114,9 +114,9 @@ export const useCommandMenuCloseAnimationCompleteCleanup = () => {
set(isCommandMenuClosingState, false);
set(
activeTabIdComponentState.atomFamily({
instanceId: WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
instanceId: WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID,
}),
WorkflowServerlessFunctionTabId.CODE,
WorkflowLogicFunctionTabId.CODE,
);
for (const [pageId, morphItems] of snapshot
@@ -0,0 +1,52 @@
import { t } from '@lingui/core/macro';
import {
type ExecutionStatus,
WorkflowStepExecutionResult,
} from '@/workflow/components/WorkflowStepExecutionResult';
import { type LogicFunctionTestData } from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import { LogicFunctionExecutionStatus } from '~/generated-metadata/graphql';
export const LogicFunctionExecutionResult = ({
logicFunctionTestData,
maxHeight,
isTesting = false,
}: {
logicFunctionTestData: LogicFunctionTestData;
maxHeight?: number;
isTesting?: boolean;
}) => {
const result =
logicFunctionTestData.output.data ||
logicFunctionTestData.output.error ||
'';
const isSuccess =
logicFunctionTestData.output.status ===
LogicFunctionExecutionStatus.SUCCESS;
const isError =
logicFunctionTestData.output.status === LogicFunctionExecutionStatus.ERROR;
const duration = logicFunctionTestData.output.duration;
const status: ExecutionStatus = {
isSuccess,
isError,
successMessage: isSuccess ? t`200 OK - ${duration}ms` : undefined,
errorMessage: isError ? t`500 Error - ${duration}ms` : undefined,
};
return (
<WorkflowStepExecutionResult
result={result}
language={logicFunctionTestData.language}
height={Math.min(
logicFunctionTestData.height,
maxHeight ?? logicFunctionTestData.height,
)}
status={status}
isTesting={isTesting}
loadingMessage={t`Running function`}
idleMessage={t`Output`}
/>
);
};
@@ -0,0 +1,68 @@
import { useExecuteOneLogicFunction } from '@/settings/logic-functions/hooks/useExecuteOneLogicFunction';
import { logicFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import { useState } from 'react';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { sleep } from '~/utils/sleep';
export const useTestLogicFunction = ({
logicFunctionId,
callback,
}: {
logicFunctionId: string;
callback?: (testResult: object) => void;
}) => {
const [isTesting, setIsTesting] = useState(false);
const { executeOneLogicFunction } = useExecuteOneLogicFunction();
const [logicFunctionTestData, setLogicFunctionTestData] = useRecoilState(
logicFunctionTestDataFamilyState(logicFunctionId),
);
const testLogicFunction = async () => {
try {
setIsTesting(true);
await sleep(200); // Delay artificially to avoid flashing the UI
const result = await executeOneLogicFunction({
id: logicFunctionId,
payload: logicFunctionTestData.input,
version: 'draft',
});
setIsTesting(false);
if (isDefined(result?.data?.executeOneLogicFunction?.data)) {
callback?.(result?.data?.executeOneLogicFunction?.data);
}
setLogicFunctionTestData((prev) => ({
...prev,
language: 'json',
height: 300,
output: {
data: result?.data?.executeOneLogicFunction?.data
? JSON.stringify(
result?.data?.executeOneLogicFunction?.data,
null,
4,
)
: undefined,
logs: result?.data?.executeOneLogicFunction?.logs || '',
duration: result?.data?.executeOneLogicFunction?.duration,
status: result?.data?.executeOneLogicFunction?.status,
error: result?.data?.executeOneLogicFunction?.error
? JSON.stringify(
result?.data?.executeOneLogicFunction?.error,
null,
4,
)
: undefined,
},
}));
} catch (error) {
setIsTesting(false);
throw error;
}
};
return { testLogicFunction, isTesting };
};
@@ -1,4 +1,4 @@
import { computeNewSources } from '@/serverless-functions/utils/computeNewSources';
import { computeNewSources } from '@/logic-functions/utils/computeNewSources';
describe('computeNewSources', () => {
it('should compute new code input root 0', () => {
@@ -1,6 +1,6 @@
// IA Generated
import { flattenSources } from '@/serverless-functions/utils/flattenSources';
import { flattenSources } from '@/logic-functions/utils/flattenSources';
import { type Sources } from 'twenty-shared/types';
describe('flattenSources', () => {
@@ -1,5 +1,5 @@
import { type InputSchema } from '@/workflow/types/InputSchema';
import { getDefaultFunctionInputFromInputSchema } from '@/serverless-functions/utils/getDefaultFunctionInputFromInputSchema';
import { getDefaultFunctionInputFromInputSchema } from '@/logic-functions/utils/getDefaultFunctionInputFromInputSchema';
describe('getDefaultFunctionInputFromInputSchema', () => {
it('should init function input properly', () => {
@@ -1,4 +1,4 @@
import { getFunctionInputFromSourceCode } from '@/serverless-functions/utils/getFunctionInputFromSourceCode';
import { getFunctionInputFromSourceCode } from '@/logic-functions/utils/getFunctionInputFromSourceCode';
describe('getFunctionInputFromSourceCode', () => {
it('should return empty input if not parameter', async () => {
@@ -1,4 +1,4 @@
import { getFunctionInputSchema } from '@/serverless-functions/utils/getFunctionInputSchema';
import { getFunctionInputSchema } from '@/logic-functions/utils/getFunctionInputSchema';
describe('getFunctionInputSchema', () => {
it('should analyze a simple function correctly', () => {
@@ -1,4 +1,4 @@
import { mergeDefaultFunctionInputAndFunctionInput } from '@/serverless-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
import { mergeDefaultFunctionInputAndFunctionInput } from '@/logic-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
describe('mergeDefaultFunctionInputAndFunctionInput', () => {
it('should merge properly', () => {
@@ -1,4 +1,4 @@
import { getDefaultFunctionInputFromInputSchema } from '@/serverless-functions/utils/getDefaultFunctionInputFromInputSchema';
import { getDefaultFunctionInputFromInputSchema } from '@/logic-functions/utils/getDefaultFunctionInputFromInputSchema';
import { type FunctionInput } from '@/workflow/workflow-steps/workflow-actions/code-action/types/FunctionInput';
import { isObject } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
@@ -11,7 +11,7 @@ export const getFunctionInputFromSourceCode = async (
}
const { getFunctionInputSchema } = await import(
'@/serverless-functions/utils/getFunctionInputSchema'
'@/logic-functions/utils/getFunctionInputSchema'
);
const functionInputSchema = getFunctionInputSchema(sourceCode);
@@ -6,7 +6,7 @@ export const getToolInputSchemaFromSourceCode = async (
const { getFunctionInputSchema } = await import('./getFunctionInputSchema');
const inputSchema = getFunctionInputSchema(sourceCode);
// Serverless functions take a single params object
// Logic functions take a single params object
const firstParam = inputSchema[0];
if (firstParam?.type === 'object' && isDefined(firstParam.properties)) {
@@ -28,10 +28,7 @@ export const useMetadataErrorHandler = () => {
viewGroup: t`view group`,
viewFilter: t`view filter`,
index: t`index`,
serverlessFunction: t`serverless function`,
cronTrigger: t`cron trigger`,
databaseEventTrigger: t`database trigger`,
routeTrigger: t`route trigger`,
logicFunction: t`logic function`,
role: t`role`,
roleTarget: t`role target`,
agent: t`agent`,
@@ -8,7 +8,7 @@ export const CUSTOM_WORKSPACE_APPLICATION_MOCK = {
description: 'workpace custom application',
name: 'custom',
objects: [],
serverlessFunctions: [],
logicFunctions: [],
universalIdentifier: '66a698b6-f6c1-4d35-a6e7-20aeadc3cd95',
version: '1.0.0',
} as const satisfies Application;
@@ -1,53 +0,0 @@
import { t } from '@lingui/core/macro';
import {
type ExecutionStatus,
WorkflowStepExecutionResult,
} from '@/workflow/components/WorkflowStepExecutionResult';
import { type ServerlessFunctionTestData } from '@/workflow/workflow-steps/workflow-actions/code-action/states/serverlessFunctionTestDataFamilyState';
import { ServerlessFunctionExecutionStatus } from '~/generated-metadata/graphql';
export const ServerlessFunctionExecutionResult = ({
serverlessFunctionTestData,
maxHeight,
isTesting = false,
}: {
serverlessFunctionTestData: ServerlessFunctionTestData;
maxHeight?: number;
isTesting?: boolean;
}) => {
const result =
serverlessFunctionTestData.output.data ||
serverlessFunctionTestData.output.error ||
'';
const isSuccess =
serverlessFunctionTestData.output.status ===
ServerlessFunctionExecutionStatus.SUCCESS;
const isError =
serverlessFunctionTestData.output.status ===
ServerlessFunctionExecutionStatus.ERROR;
const duration = serverlessFunctionTestData.output.duration;
const status: ExecutionStatus = {
isSuccess,
isError,
successMessage: isSuccess ? t`200 OK - ${duration}ms` : undefined,
errorMessage: isError ? t`500 Error - ${duration}ms` : undefined,
};
return (
<WorkflowStepExecutionResult
result={result}
language={serverlessFunctionTestData.language}
height={Math.min(
serverlessFunctionTestData.height,
maxHeight ?? serverlessFunctionTestData.height,
)}
status={status}
isTesting={isTesting}
loadingMessage={t`Running function`}
idleMessage={t`Output`}
/>
);
};
@@ -1,67 +0,0 @@
import { useExecuteOneServerlessFunction } from '@/settings/serverless-functions/hooks/useExecuteOneServerlessFunction';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/serverlessFunctionTestDataFamilyState';
import { useState } from 'react';
import { useRecoilState } from 'recoil';
import { isDefined } from 'twenty-shared/utils';
import { sleep } from '~/utils/sleep';
export const useTestServerlessFunction = ({
serverlessFunctionId,
callback,
}: {
serverlessFunctionId: string;
callback?: (testResult: object) => void;
}) => {
const [isTesting, setIsTesting] = useState(false);
const { executeOneServerlessFunction } = useExecuteOneServerlessFunction();
const [serverlessFunctionTestData, setServerlessFunctionTestData] =
useRecoilState(serverlessFunctionTestDataFamilyState(serverlessFunctionId));
const testServerlessFunction = async () => {
try {
setIsTesting(true);
await sleep(200); // Delay artificially to avoid flashing the UI
const result = await executeOneServerlessFunction({
id: serverlessFunctionId,
payload: serverlessFunctionTestData.input,
version: 'draft',
});
setIsTesting(false);
if (isDefined(result?.data?.executeOneServerlessFunction?.data)) {
callback?.(result?.data?.executeOneServerlessFunction?.data);
}
setServerlessFunctionTestData((prev) => ({
...prev,
language: 'json',
height: 300,
output: {
data: result?.data?.executeOneServerlessFunction?.data
? JSON.stringify(
result?.data?.executeOneServerlessFunction?.data,
null,
4,
)
: undefined,
logs: result?.data?.executeOneServerlessFunction?.logs || '',
duration: result?.data?.executeOneServerlessFunction?.duration,
status: result?.data?.executeOneServerlessFunction?.status,
error: result?.data?.executeOneServerlessFunction?.error
? JSON.stringify(
result?.data?.executeOneServerlessFunction?.error,
null,
4,
)
: undefined,
},
}));
} catch (error) {
setIsTesting(false);
throw error;
}
};
return { testServerlessFunction, isTesting };
};
@@ -1,4 +1,4 @@
import { useGetAvailablePackages } from '@/settings/serverless-functions/hooks/useGetAvailablePackages';
import { useGetAvailablePackages } from '@/settings/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';
@@ -12,25 +12,22 @@ export type File = {
path: string;
};
type SettingsServerlessFunctionCodeEditorProps = Omit<
EditorProps,
'onChange'
> & {
type SettingsLogicFunctionCodeEditorProps = Omit<EditorProps, 'onChange'> & {
currentFilePath: string;
files: File[];
onChange: (value: string) => void;
};
export const SettingsServerlessFunctionCodeEditor = ({
export const SettingsLogicFunctionCodeEditor = ({
currentFilePath,
files,
onChange,
height = 450,
options = undefined,
}: SettingsServerlessFunctionCodeEditorProps) => {
const { serverlessFunctionId = '' } = useParams();
}: SettingsLogicFunctionCodeEditorProps) => {
const { logicFunctionId = '' } = useParams();
const { availablePackages } = useGetAvailablePackages({
id: serverlessFunctionId,
id: logicFunctionId,
});
const currentFile = files.find((file) => file.path === currentFilePath);
@@ -13,19 +13,19 @@ const StyledHeaderTitle = styled.div`
}
`;
type SettingsServerlessFunctionLabelContainerProps = {
type SettingsLogicFunctionLabelContainerProps = {
value: string;
onChange: (value: string) => void;
};
export const SettingsServerlessFunctionLabelContainer = ({
export const SettingsLogicFunctionLabelContainer = ({
value,
onChange,
}: SettingsServerlessFunctionLabelContainerProps) => {
}: SettingsLogicFunctionLabelContainerProps) => {
return (
<StyledHeaderTitle>
<TitleInput
instanceId="serverless-function-name-input"
instanceId="logic-function-name-input"
sizeVariant="md"
value={value}
onChange={onChange}
@@ -1,4 +1,4 @@
import { type ServerlessFunctionNewFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
import { type LogicFunctionNewFormValues } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { TextArea } from '@/ui/input/components/TextArea';
import styled from '@emotion/styled';
@@ -12,12 +12,12 @@ const StyledInputsContainer = styled.div`
gap: ${({ theme }) => theme.spacing(4)};
`;
export const SettingsServerlessFunctionNewForm = ({
export const SettingsLogicFunctionNewForm = ({
formValues,
onChange,
readonly = false,
}: {
formValues: ServerlessFunctionNewFormValues;
formValues: LogicFunctionNewFormValues;
onChange: (key: string) => (value: string) => void;
readonly?: boolean;
}) => {
@@ -7,7 +7,7 @@ import { useParams } from 'react-router-dom';
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
export const SettingsServerlessFunctionTabEnvironmentVariablesSection = () => {
export const SettingsLogicFunctionTabEnvironmentVariablesSection = () => {
const { applicationId = '' } = useParams<{ applicationId: string }>();
return (
<Section>
@@ -1,9 +1,9 @@
import styled from '@emotion/styled';
import { type ServerlessFunction } from '~/generated-metadata/graphql';
import { type LogicFunction } from '~/generated-metadata/graphql';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { useTheme } from '@emotion/react';
import { IconChevronRight } from 'twenty-ui/display';
import { StyledTableRow } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsTable';
import { StyledTableRow } from '@/settings/logic-functions/components/SettingsLogicFunctionsTable';
const StyledNameTableCell = styled(TableCell)`
color: ${({ theme }) => theme.font.color.primary};
@@ -24,21 +24,19 @@ const StyledIconChevronRight = styled(IconChevronRight)`
color: ${({ theme }) => theme.font.color.tertiary};
`;
export const SettingsServerlessFunctionsFieldItemTableRow = ({
serverlessFunction,
export const SettingsLogicFunctionsFieldItemTableRow = ({
logicFunction,
to,
}: {
serverlessFunction: ServerlessFunction;
logicFunction: LogicFunction;
to: string;
}) => {
const theme = useTheme();
return (
<StyledTableRow to={to}>
<StyledNameTableCell>{serverlessFunction.name}</StyledNameTableCell>
<StyledNameTableCell>{logicFunction.name}</StyledNameTableCell>
<StyledNameTableCell></StyledNameTableCell>
<StyledRuntimeTableCell>
{serverlessFunction.runtime}
</StyledRuntimeTableCell>
<StyledRuntimeTableCell>{logicFunction.runtime}</StyledRuntimeTableCell>
<StyledIconTableCell>
<StyledIconChevronRight
size={theme.icon.size.md}
@@ -1,4 +1,4 @@
import { SettingsServerlessFunctionsFieldItemTableRow } from '@/settings/serverless-functions/components/SettingsServerlessFunctionsFieldItemTableRow';
import { SettingsLogicFunctionsFieldItemTableRow } from '@/settings/logic-functions/components/SettingsLogicFunctionsFieldItemTableRow';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
@@ -6,7 +6,7 @@ import { TableRow } from '@/ui/layout/table/components/TableRow';
import styled from '@emotion/styled';
import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath } from 'twenty-shared/utils';
import { type ServerlessFunction } from '~/generated-metadata/graphql';
import { type LogicFunction } from '~/generated-metadata/graphql';
import { useLingui } from '@lingui/react/macro';
import { useParams } from 'react-router-dom';
@@ -18,16 +18,16 @@ const StyledTableBody = styled(TableBody)`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
`;
export const SettingsServerlessFunctionsTable = ({
serverlessFunctions,
export const SettingsLogicFunctionsTable = ({
logicFunctions,
}: {
serverlessFunctions: ServerlessFunction[];
logicFunctions: LogicFunction[];
}) => {
const { applicationId = '' } = useParams();
const { t } = useLingui();
if (serverlessFunctions.length === 0) {
if (logicFunctions.length === 0) {
return null;
}
@@ -40,17 +40,14 @@ export const SettingsServerlessFunctionsTable = ({
<TableHeader></TableHeader>
</StyledTableRow>
<StyledTableBody>
{serverlessFunctions.map((serverlessFunction: ServerlessFunction) => (
<SettingsServerlessFunctionsFieldItemTableRow
key={serverlessFunction.id}
serverlessFunction={serverlessFunction}
to={getSettingsPath(
SettingsPath.ApplicationServerlessFunctionDetail,
{
applicationId,
serverlessFunctionId: serverlessFunction.id,
},
)}
{logicFunctions.map((logicFunction: LogicFunction) => (
<SettingsLogicFunctionsFieldItemTableRow
key={logicFunction.id}
logicFunction={logicFunction}
to={getSettingsPath(SettingsPath.ApplicationLogicFunctionDetail, {
applicationId,
logicFunctionId: logicFunction.id,
})}
/>
))}
</StyledTableBody>
@@ -1,8 +1,8 @@
import {
type File,
SettingsServerlessFunctionCodeEditor,
} from '@/settings/serverless-functions/components/SettingsServerlessFunctionCodeEditor';
import { SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/settings/serverless-functions/constants/SettingsServerlessFunctionTabListComponentId';
SettingsLogicFunctionCodeEditor,
} from '@/settings/logic-functions/components/SettingsLogicFunctionCodeEditor';
import { SETTINGS_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/settings/logic-functions/constants/SettingsLogicFunctionTabListComponentId';
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';
@@ -16,7 +16,7 @@ const StyledTabList = styled(TabList)`
border-bottom: none;
`;
export const SettingsServerlessFunctionCodeEditorTab = ({
export const SettingsLogicFunctionCodeEditorTab = ({
files,
handleExecute,
onChange,
@@ -31,7 +31,7 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
}) => {
const activeTabId = useRecoilComponentValue(
activeTabIdComponentState,
SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
SETTINGS_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID,
);
const TestButton = (
<Button
@@ -50,7 +50,7 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
tabs={files.map((file) => {
return { id: file.path, title: file.path.split('/').at(-1) || '' };
})}
componentInstanceId={SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID}
componentInstanceId={SETTINGS_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID}
/>
);
@@ -62,16 +62,18 @@ export const SettingsServerlessFunctionCodeEditorTab = ({
/>
<CoreEditorHeader leftNodes={[HeaderTabList]} rightNodes={[TestButton]} />
{activeTabId && (
<SettingsServerlessFunctionCodeEditor
<SettingsLogicFunctionCodeEditor
files={files}
currentFilePath={activeTabId}
onChange={(newCodeValue) => onChange(activeTabId, newCodeValue)}
onChange={(newCodeValue: string) =>
onChange(activeTabId, newCodeValue)
}
options={
isManaged
? {
readOnly: true,
readOnlyMessage: {
value: t`Managed serverless functions are not editable`,
value: t`Managed logic functions are not editable`,
},
}
: undefined
@@ -0,0 +1,21 @@
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';
export const SettingsLogicFunctionSettingsTab = ({
formValues,
onChange,
}: {
formValues: LogicFunctionFormValues;
onChange: (key: string) => (value: string) => void;
}) => {
return (
<>
<SettingsLogicFunctionNewForm
formValues={formValues}
onChange={onChange}
/>
<SettingsLogicFunctionTabEnvironmentVariablesSection />
</>
);
};
@@ -28,7 +28,7 @@ const StyledTableRow = styled(TableRow)`
grid-template-columns: 180px 300px 32px;
`;
export const SettingsServerlessFunctionTabEnvironmentVariableTableRow = ({
export const SettingsLogicFunctionTabEnvironmentVariableTableRow = ({
envVariable,
onChange,
onDelete,
@@ -1,5 +1,8 @@
import { ServerlessFunctionExecutionResult } from '@/serverless-functions/components/ServerlessFunctionExecutionResult';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/serverlessFunctionTestDataFamilyState';
import { LogicFunctionExecutionResult } from '@/logic-functions/components/LogicFunctionExecutionResult';
import {
type LogicFunctionTestData,
logicFunctionTestDataFamilyState,
} from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { useRecoilState } from 'recoil';
@@ -20,27 +23,29 @@ const StyledCodeEditorContainer = styled.div`
flex-direction: column;
`;
export const SettingsServerlessFunctionTestTab = ({
export const SettingsLogicFunctionTestTab = ({
handleExecute,
serverlessFunctionId,
logicFunctionId,
isTesting = false,
}: {
handleExecute: () => void;
serverlessFunctionId: string;
logicFunctionId: string;
isTesting?: boolean;
}) => {
const { t } = useLingui();
const [serverlessFunctionTestData, setServerlessFunctionTestData] =
useRecoilState(serverlessFunctionTestDataFamilyState(serverlessFunctionId));
const [logicFunctionTestData, setLogicFunctionTestData] =
useRecoilState<LogicFunctionTestData>(
logicFunctionTestDataFamilyState(logicFunctionId),
);
const onChange = (newInput: string) => {
setServerlessFunctionTestData((prev) => ({
setLogicFunctionTestData((prev) => ({
...prev,
input: JSON.parse(newInput),
}));
};
const testLogsTextAreaId = `${serverlessFunctionId}-test-logs`;
const testLogsTextAreaId = `${logicFunctionId}-test-logs`;
return (
<Section>
@@ -65,26 +70,26 @@ export const SettingsServerlessFunctionTestTab = ({
]}
/>
<CodeEditor
value={JSON.stringify(serverlessFunctionTestData.input, null, 4)}
value={JSON.stringify(logicFunctionTestData.input, null, 4)}
language="json"
height={100}
onChange={onChange}
variant="with-header"
/>
</StyledCodeEditorContainer>
<ServerlessFunctionExecutionResult
serverlessFunctionTestData={serverlessFunctionTestData}
<LogicFunctionExecutionResult
logicFunctionTestData={logicFunctionTestData}
maxHeight={
serverlessFunctionTestData.output.logs.length > 0 ? 200 : undefined
logicFunctionTestData.output.logs.length > 0 ? 200 : undefined
}
isTesting={isTesting}
/>
{serverlessFunctionTestData.output.logs.length > 0 && (
{logicFunctionTestData.output.logs.length > 0 && (
<StyledCodeEditorContainer>
<InputLabel>{t`Logs`}</InputLabel>
<TextArea
textAreaId={testLogsTextAreaId}
value={isTesting ? '' : serverlessFunctionTestData.output.logs}
value={isTesting ? '' : logicFunctionTestData.output.logs}
maxRows={20}
disabled
/>
@@ -7,7 +7,7 @@ import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { type ServerlessFunction } from '~/generated/graphql';
import { type LogicFunction } from '~/generated/graphql';
export const StyledRouteTriggerTableRow = styled(TableRow)`
grid-template-columns: 1fr 120px 120px;
@@ -35,10 +35,10 @@ const StyledEmptyState = styled.div`
text-align: center;
`;
export const SettingsServerlessFunctionTriggersTab = ({
serverlessFunction: _serverlessFunction,
export const SettingsLogicFunctionTriggersTab = ({
logicFunction: _logicFunction,
}: {
serverlessFunction: ServerlessFunction;
logicFunction: LogicFunction;
}) => {
const { t } = useLingui();
@@ -0,0 +1,2 @@
export const SETTINGS_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID =
'settings-logic-function-editor-tab-list';
@@ -1,7 +1,7 @@
import { gql } from '@apollo/client';
export const SERVERLESS_FUNCTION_FRAGMENT = gql`
fragment ServerlessFunctionFields on ServerlessFunction {
export const LOGIC_FUNCTION_FRAGMENT = gql`
fragment LogicFunctionFields on LogicFunction {
id
name
description
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const CREATE_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
mutation CreateOneLogicFunctionItem($input: CreateLogicFunctionInput!) {
createOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const DELETE_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
mutation DeleteOneLogicFunction($input: LogicFunctionIdInput!) {
deleteOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -0,0 +1,13 @@
import { gql } from '@apollo/client';
export const EXECUTE_ONE_LOGIC_FUNCTION = gql`
mutation ExecuteOneLogicFunction($input: ExecuteLogicFunctionInput!) {
executeOneLogicFunction(input: $input) {
data
logs
duration
status
error
}
}
`;
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const PUBLISH_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
mutation PublishOneLogicFunction($input: PublishLogicFunctionInput!) {
publishLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const UPDATE_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
mutation UpdateOneLogicFunction($input: UpdateLogicFunctionInput!) {
updateOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -1,7 +1,7 @@
import { gql } from '@apollo/client';
export const FIND_MANY_AVAILABLE_PACKAGES = gql`
query FindManyAvailablePackages($input: ServerlessFunctionIdInput!) {
query FindManyAvailablePackages($input: LogicFunctionIdInput!) {
getAvailablePackages(input: $input)
}
`;
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const FIND_MANY_LOGIC_FUNCTIONS = gql`
${LOGIC_FUNCTION_FRAGMENT}
query GetManyLogicFunctions {
findManyLogicFunctions {
...LogicFunctionFields
}
}
`;
@@ -0,0 +1,11 @@
import { gql } from '@apollo/client';
import { LOGIC_FUNCTION_FRAGMENT } from '@/settings/logic-functions/graphql/fragments/logicFunctionFragment';
export const FIND_ONE_LOGIC_FUNCTION = gql`
${LOGIC_FUNCTION_FRAGMENT}
query GetOneLogicFunction($input: LogicFunctionIdInput!) {
findOneLogicFunction(input: $input) {
...LogicFunctionFields
}
}
`;
@@ -0,0 +1,9 @@
import { gql } from '@apollo/client';
export const FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE = gql`
query FindOneLogicFunctionSourceCode(
$input: GetLogicFunctionSourceCodeInput!
) {
getLogicFunctionSourceCode(input: $input)
}
`;
@@ -0,0 +1,48 @@
import { useLogicFunctionUpdateFormState } from '@/settings/logic-functions/hooks/useLogicFunctionUpdateFormState';
import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
jest.mock('@/settings/logic-functions/hooks/useGetOneLogicFunction', () => ({
useGetOneLogicFunction: jest.fn(),
}));
jest.mock(
'@/settings/logic-functions/hooks/useGetOneLogicFunctionSourceCode',
() => ({
useGetOneLogicFunctionSourceCode: jest.fn(),
}),
);
describe('useLogicFunctionUpdateFormState', () => {
test('should return a form', () => {
const logicFunctionId = 'logicFunctionId';
const useGetOneLogicFunctionMock = jest.requireMock(
'@/settings/logic-functions/hooks/useGetOneLogicFunction',
);
useGetOneLogicFunctionMock.useGetOneLogicFunction.mockReturnValue({
logicFunction: { name: 'name' },
});
const useGetOneLogicFunctionSourceCodeMock = jest.requireMock(
'@/settings/logic-functions/hooks/useGetOneLogicFunctionSourceCode',
);
useGetOneLogicFunctionSourceCodeMock.useGetOneLogicFunctionSourceCode.mockReturnValue(
{
code: { src: { 'index.ts': 'export const handler = () => {}' } },
},
);
const { result } = renderHook(
() => useLogicFunctionUpdateFormState({ logicFunctionId }),
{
wrapper: RecoilRoot,
},
);
const { formValues } = result.current;
expect(formValues).toEqual({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
});
});
});
@@ -0,0 +1,27 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { EXECUTE_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/mutations/executeOneLogicFunction';
import { useMutation } from '@apollo/client';
import {
type ExecuteOneLogicFunctionMutation,
type ExecuteOneLogicFunctionMutationVariables,
type ExecuteLogicFunctionInput,
} from '~/generated-metadata/graphql';
export const useExecuteOneLogicFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const [mutate] = useMutation<
ExecuteOneLogicFunctionMutation,
ExecuteOneLogicFunctionMutationVariables
>(EXECUTE_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
const executeOneLogicFunction = async (input: ExecuteLogicFunctionInput) => {
return await mutate({
variables: {
input,
},
});
};
return { executeOneLogicFunction };
};
@@ -1,13 +1,13 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { useQuery } from '@apollo/client';
import { FIND_MANY_AVAILABLE_PACKAGES } from '@/settings/serverless-functions/graphql/queries/findManyAvailablePackages';
import { FIND_MANY_AVAILABLE_PACKAGES } from '@/settings/logic-functions/graphql/queries/findManyAvailablePackages';
import {
type FindManyAvailablePackagesQuery,
type FindManyAvailablePackagesQueryVariables,
type ServerlessFunctionIdInput,
type LogicFunctionIdInput,
} from '~/generated-metadata/graphql';
export const useGetAvailablePackages = (input: ServerlessFunctionIdInput) => {
export const useGetAvailablePackages = (input: LogicFunctionIdInput) => {
const apolloMetadataClient = useApolloCoreClient();
const { data } = useQuery<
FindManyAvailablePackagesQuery,
@@ -0,0 +1,24 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_MANY_LOGIC_FUNCTIONS } from '@/settings/logic-functions/graphql/queries/findManyLogicFunctions';
import { useQuery } from '@apollo/client';
import {
type GetManyLogicFunctionsQuery,
type GetManyLogicFunctionsQueryVariables,
} from '~/generated-metadata/graphql';
export const useGetManyLogicFunctions = () => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading, error } = useQuery<
GetManyLogicFunctionsQuery,
GetManyLogicFunctionsQueryVariables
>(FIND_MANY_LOGIC_FUNCTIONS, {
client: apolloMetadataClient ?? undefined,
});
return {
logicFunctions: data?.findManyLogicFunctions || [],
loading,
error,
};
};
@@ -0,0 +1,31 @@
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,
};
};
@@ -0,0 +1,31 @@
import { useQuery } from '@apollo/client';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE } from '@/settings/logic-functions/graphql/queries/findOneLogicFunctionSourceCode';
import {
type FindOneLogicFunctionSourceCodeQuery,
type FindOneLogicFunctionSourceCodeQueryVariables,
} from '~/generated-metadata/graphql';
export const useGetOneLogicFunctionSourceCode = ({
id,
version,
onCompleted,
}: {
id: string;
version: string;
onCompleted?: (data: FindOneLogicFunctionSourceCodeQuery) => void;
}) => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading } = useQuery<
FindOneLogicFunctionSourceCodeQuery,
FindOneLogicFunctionSourceCodeQueryVariables
>(FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE, {
client: apolloMetadataClient ?? undefined,
variables: {
input: { id, version },
},
onCompleted,
fetchPolicy: 'network-only',
});
return { code: data?.getLogicFunctionSourceCode, loading };
};
@@ -0,0 +1,107 @@
import { flattenSources } from '@/logic-functions/utils/flattenSources';
import { getFunctionInputFromSourceCode } from '@/logic-functions/utils/getFunctionInputFromSourceCode';
import { useGetOneLogicFunction } from '@/settings/logic-functions/hooks/useGetOneLogicFunction';
import { useGetOneLogicFunctionSourceCode } from '@/settings/logic-functions/hooks/useGetOneLogicFunctionSourceCode';
import { logicFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import { type Dispatch, type SetStateAction, useState } from 'react';
import { useRecoilState } from 'recoil';
import { type Sources } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type FindOneLogicFunctionSourceCodeQuery,
type GetOneLogicFunctionQuery,
} from '~/generated-metadata/graphql';
import { type LogicFunction } from '~/generated/graphql';
export type LogicFunctionNewFormValues = {
name: string;
description: string;
};
export type LogicFunctionFormValues = LogicFunctionNewFormValues & {
code: Sources;
};
type SetLogicFunctionFormValues = Dispatch<
SetStateAction<LogicFunctionFormValues>
>;
export const useLogicFunctionUpdateFormState = ({
logicFunctionId,
logicFunctionVersion = 'draft',
}: {
logicFunctionId: string;
logicFunctionVersion?: string;
}): {
formValues: LogicFunctionFormValues;
logicFunction: LogicFunction | null;
setFormValues: SetLogicFunctionFormValues;
loading: boolean;
} => {
const [formValues, setFormValues] = useState<LogicFunctionFormValues>({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
});
const [logicFunctionTestData, setLogicFunctionTestData] = useRecoilState(
logicFunctionTestDataFamilyState(logicFunctionId),
);
const { logicFunction, loading: logicFunctionLoading } =
useGetOneLogicFunction({
id: logicFunctionId,
onCompleted: (data: GetOneLogicFunctionQuery) => {
const fn = data?.findOneLogicFunction;
if (isDefined(fn)) {
setFormValues((prevState) => ({
...prevState,
name: fn.name || '',
description: fn.description || '',
}));
}
},
});
const { loading: logicFunctionSourceCodeLoading } =
useGetOneLogicFunctionSourceCode({
id: logicFunctionId,
version: logicFunctionVersion,
onCompleted: async (data: FindOneLogicFunctionSourceCodeQuery) => {
const code = data?.getLogicFunctionSourceCode;
setFormValues((prevState) => ({
...prevState,
code: code || prevState.code,
}));
if (logicFunctionTestData.shouldInitInput) {
const flattenedCode = flattenSources(code);
const sourceCode = flattenedCode.find(
(flatCode) => flatCode.path === logicFunction?.sourceHandlerPath,
);
if (isDefined(sourceCode)) {
const functionInput = await getFunctionInputFromSourceCode(
sourceCode.content,
);
setLogicFunctionTestData((prev) => ({
...prev,
input: functionInput,
shouldInitInput: false,
}));
}
}
},
});
return {
formValues,
setFormValues,
logicFunction,
loading: logicFunctionSourceCodeLoading || logicFunctionLoading,
};
};
@@ -0,0 +1,173 @@
import { useCallback } from 'react';
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_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/mutations/createOneLogicFunction';
import { DELETE_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/mutations/deleteOneLogicFunction';
import { UPDATE_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/mutations/updateOneLogicFunction';
import { FIND_MANY_LOGIC_FUNCTIONS } from '@/settings/logic-functions/graphql/queries/findManyLogicFunctions';
import { FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE } from '@/settings/logic-functions/graphql/queries/findOneLogicFunctionSourceCode';
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 { CrudOperationType } from 'twenty-shared/types';
import {
type CreateOneLogicFunctionItemMutation,
type CreateOneLogicFunctionItemMutationVariables,
type DeleteOneLogicFunctionMutation,
type DeleteOneLogicFunctionMutationVariables,
type UpdateOneLogicFunctionMutation,
type UpdateOneLogicFunctionMutationVariables,
} from '~/generated-metadata/graphql';
export const usePersistLogicFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const [createLogicFunctionMutation] = useMutation<
CreateOneLogicFunctionItemMutation,
CreateOneLogicFunctionItemMutationVariables
>(CREATE_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
const [updateLogicFunctionMutation] = useMutation<
UpdateOneLogicFunctionMutation,
UpdateOneLogicFunctionMutationVariables
>(UPDATE_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
const [deleteLogicFunctionMutation] = useMutation<
DeleteOneLogicFunctionMutation,
DeleteOneLogicFunctionMutationVariables
>(DELETE_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
const createLogicFunction = useCallback(
async (
variables: CreateOneLogicFunctionItemMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof createLogicFunctionMutation>>
>
> => {
try {
const result = await createLogicFunctionMutation({
variables,
awaitRefetchQueries: true,
refetchQueries: [getOperationName(FIND_MANY_LOGIC_FUNCTIONS) ?? ''],
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'logicFunction',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[createLogicFunctionMutation, handleMetadataError, enqueueErrorSnackBar],
);
const updateLogicFunction = useCallback(
async (
variables: UpdateOneLogicFunctionMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof updateLogicFunctionMutation>>
>
> => {
try {
const result = await updateLogicFunctionMutation({
variables,
refetchQueries: [
getOperationName(FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE) ?? '',
],
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'logicFunction',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[updateLogicFunctionMutation, handleMetadataError, enqueueErrorSnackBar],
);
const deleteLogicFunction = useCallback(
async (
variables: DeleteOneLogicFunctionMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof deleteLogicFunctionMutation>>
>
> => {
try {
const result = await deleteLogicFunctionMutation({
variables,
awaitRefetchQueries: true,
refetchQueries: [
getOperationName(FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE) ?? '',
],
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'logicFunction',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[deleteLogicFunctionMutation, handleMetadataError, enqueueErrorSnackBar],
);
return {
createLogicFunction,
updateLogicFunction,
deleteLogicFunction,
};
};
@@ -0,0 +1,34 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { PUBLISH_ONE_LOGIC_FUNCTION } from '@/settings/logic-functions/graphql/mutations/publishOneLogicFunction';
import { FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE } from '@/settings/logic-functions/graphql/queries/findOneLogicFunctionSourceCode';
import { useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import {
type PublishOneLogicFunctionMutation,
type PublishOneLogicFunctionMutationVariables,
type PublishLogicFunctionInput,
} from '~/generated-metadata/graphql';
export const usePublishOneLogicFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const [mutate] = useMutation<
PublishOneLogicFunctionMutation,
PublishOneLogicFunctionMutationVariables
>(PUBLISH_ONE_LOGIC_FUNCTION, {
client: apolloMetadataClient,
});
const publishOneLogicFunction = async (input: PublishLogicFunctionInput) => {
return await mutate({
variables: {
input,
},
awaitRefetchQueries: true,
refetchQueries: [
getOperationName(FIND_ONE_LOGIC_FUNCTION_SOURCE_CODE) ?? '',
],
});
};
return { publishOneLogicFunction };
};
@@ -1,21 +0,0 @@
import { SettingsServerlessFunctionNewForm } from '@/settings/serverless-functions/components/SettingsServerlessFunctionNewForm';
import { SettingsServerlessFunctionTabEnvironmentVariablesSection } from '@/settings/serverless-functions/components/SettingsServerlessFunctionTabEnvironmentVariablesSection';
import { type ServerlessFunctionFormValues } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
export const SettingsServerlessFunctionSettingsTab = ({
formValues,
onChange,
}: {
formValues: ServerlessFunctionFormValues;
onChange: (key: string) => (value: string) => void;
}) => {
return (
<>
<SettingsServerlessFunctionNewForm
formValues={formValues}
onChange={onChange}
/>
<SettingsServerlessFunctionTabEnvironmentVariablesSection />
</>
);
};
@@ -1,2 +0,0 @@
export const SETTINGS_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID =
'settings-serverless-function-editor-tab-list';
@@ -1,13 +0,0 @@
import { gql } from '@apollo/client';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
export const CREATE_ONE_SERVERLESS_FUNCTION = gql`
${SERVERLESS_FUNCTION_FRAGMENT}
mutation CreateOneServerlessFunctionItem(
$input: CreateServerlessFunctionInput!
) {
createOneServerlessFunction(input: $input) {
...ServerlessFunctionFields
}
}
`;
@@ -1,11 +0,0 @@
import { gql } from '@apollo/client';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
export const DELETE_ONE_SERVERLESS_FUNCTION = gql`
${SERVERLESS_FUNCTION_FRAGMENT}
mutation DeleteOneServerlessFunction($input: ServerlessFunctionIdInput!) {
deleteOneServerlessFunction(input: $input) {
...ServerlessFunctionFields
}
}
`;
@@ -1,15 +0,0 @@
import { gql } from '@apollo/client';
export const EXECUTE_ONE_SERVERLESS_FUNCTION = gql`
mutation ExecuteOneServerlessFunction(
$input: ExecuteServerlessFunctionInput!
) {
executeOneServerlessFunction(input: $input) {
data
logs
duration
status
error
}
}
`;
@@ -1,13 +0,0 @@
import { gql } from '@apollo/client';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
export const PUBLISH_ONE_SERVERLESS_FUNCTION = gql`
${SERVERLESS_FUNCTION_FRAGMENT}
mutation PublishOneServerlessFunction(
$input: PublishServerlessFunctionInput!
) {
publishServerlessFunction(input: $input) {
...ServerlessFunctionFields
}
}
`;
@@ -1,11 +0,0 @@
import { gql } from '@apollo/client';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
export const UPDATE_ONE_SERVERLESS_FUNCTION = gql`
${SERVERLESS_FUNCTION_FRAGMENT}
mutation UpdateOneServerlessFunction($input: UpdateServerlessFunctionInput!) {
updateOneServerlessFunction(input: $input) {
...ServerlessFunctionFields
}
}
`;
@@ -1,11 +0,0 @@
import { gql } from '@apollo/client';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
export const FIND_MANY_SERVERLESS_FUNCTIONS = gql`
${SERVERLESS_FUNCTION_FRAGMENT}
query GetManyServerlessFunctions {
findManyServerlessFunctions {
...ServerlessFunctionFields
}
}
`;
@@ -1,11 +0,0 @@
import { gql } from '@apollo/client';
import { SERVERLESS_FUNCTION_FRAGMENT } from '@/settings/serverless-functions/graphql/fragments/serverlessFunctionFragment';
export const FIND_ONE_SERVERLESS_FUNCTION = gql`
${SERVERLESS_FUNCTION_FRAGMENT}
query GetOneServerlessFunction($input: ServerlessFunctionIdInput!) {
findOneServerlessFunction(input: $input) {
...ServerlessFunctionFields
}
}
`;
@@ -1,9 +0,0 @@
import { gql } from '@apollo/client';
export const FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE = gql`
query FindOneServerlessFunctionSourceCode(
$input: GetServerlessFunctionSourceCodeInput!
) {
getServerlessFunctionSourceCode(input: $input)
}
`;
@@ -1,53 +0,0 @@
import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
import { renderHook } from '@testing-library/react';
import { RecoilRoot } from 'recoil';
jest.mock(
'@/settings/serverless-functions/hooks/useGetOneServerlessFunction',
() => ({
useGetOneServerlessFunction: jest.fn(),
}),
);
jest.mock(
'@/settings/serverless-functions/hooks/useGetOneServerlessFunctionSourceCode',
() => ({
useGetOneServerlessFunctionSourceCode: jest.fn(),
}),
);
describe('useServerlessFunctionUpdateFormState', () => {
test('should return a form', () => {
const serverlessFunctionId = 'serverlessFunctionId';
const useGetOneServerlessFunctionMock = jest.requireMock(
'@/settings/serverless-functions/hooks/useGetOneServerlessFunction',
);
useGetOneServerlessFunctionMock.useGetOneServerlessFunction.mockReturnValue(
{
serverlessFunction: { name: 'name' },
},
);
const useGetOneServerlessFunctionSourceCodeMock = jest.requireMock(
'@/settings/serverless-functions/hooks/useGetOneServerlessFunctionSourceCode',
);
useGetOneServerlessFunctionSourceCodeMock.useGetOneServerlessFunctionSourceCode.mockReturnValue(
{
code: { src: { 'index.ts': 'export const handler = () => {}' } },
},
);
const { result } = renderHook(
() => useServerlessFunctionUpdateFormState({ serverlessFunctionId }),
{
wrapper: RecoilRoot,
},
);
const { formValues } = result.current;
expect(formValues).toEqual({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
});
});
});
@@ -1,29 +0,0 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { EXECUTE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/executeOneServerlessFunction';
import { useMutation } from '@apollo/client';
import {
type ExecuteOneServerlessFunctionMutation,
type ExecuteOneServerlessFunctionMutationVariables,
type ExecuteServerlessFunctionInput,
} from '~/generated-metadata/graphql';
export const useExecuteOneServerlessFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const [mutate] = useMutation<
ExecuteOneServerlessFunctionMutation,
ExecuteOneServerlessFunctionMutationVariables
>(EXECUTE_ONE_SERVERLESS_FUNCTION, {
client: apolloMetadataClient,
});
const executeOneServerlessFunction = async (
input: ExecuteServerlessFunctionInput,
) => {
return await mutate({
variables: {
input,
},
});
};
return { executeOneServerlessFunction };
};
@@ -1,24 +0,0 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_MANY_SERVERLESS_FUNCTIONS } from '@/settings/serverless-functions/graphql/queries/findManyServerlessFunctions';
import { useQuery } from '@apollo/client';
import {
type GetManyServerlessFunctionsQuery,
type GetManyServerlessFunctionsQueryVariables,
} from '~/generated-metadata/graphql';
export const useGetManyServerlessFunctions = () => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading, error } = useQuery<
GetManyServerlessFunctionsQuery,
GetManyServerlessFunctionsQueryVariables
>(FIND_MANY_SERVERLESS_FUNCTIONS, {
client: apolloMetadataClient ?? undefined,
});
return {
serverlessFunctions: data?.findManyServerlessFunctions || [],
loading,
error,
};
};
@@ -1,31 +0,0 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/queries/findOneServerlessFunction';
import { useQuery } from '@apollo/client';
import {
type GetOneServerlessFunctionQuery,
type GetOneServerlessFunctionQueryVariables,
type ServerlessFunctionIdInput,
} from '~/generated-metadata/graphql';
export const useGetOneServerlessFunction = ({
id,
onCompleted,
}: ServerlessFunctionIdInput & {
onCompleted?: (data: GetOneServerlessFunctionQuery) => void;
}) => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading } = useQuery<
GetOneServerlessFunctionQuery,
GetOneServerlessFunctionQueryVariables
>(FIND_ONE_SERVERLESS_FUNCTION, {
client: apolloMetadataClient ?? undefined,
variables: {
input: { id },
},
onCompleted,
});
return {
serverlessFunction: data?.findOneServerlessFunction || null,
loading,
};
};
@@ -1,31 +0,0 @@
import { useQuery } from '@apollo/client';
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE } from '@/settings/serverless-functions/graphql/queries/findOneServerlessFunctionSourceCode';
import {
type FindOneServerlessFunctionSourceCodeQuery,
type FindOneServerlessFunctionSourceCodeQueryVariables,
} from '~/generated-metadata/graphql';
export const useGetOneServerlessFunctionSourceCode = ({
id,
version,
onCompleted,
}: {
id: string;
version: string;
onCompleted?: (data: FindOneServerlessFunctionSourceCodeQuery) => void;
}) => {
const apolloMetadataClient = useApolloCoreClient();
const { data, loading } = useQuery<
FindOneServerlessFunctionSourceCodeQuery,
FindOneServerlessFunctionSourceCodeQueryVariables
>(FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE, {
client: apolloMetadataClient ?? undefined,
variables: {
input: { id, version },
},
onCompleted,
fetchPolicy: 'network-only',
});
return { code: data?.getServerlessFunctionSourceCode, loading };
};
@@ -1,187 +0,0 @@
import { useCallback } from 'react';
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_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/createOneServerlessFunction';
import { DELETE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/deleteOneServerlessFunction';
import { UPDATE_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/updateOneServerlessFunction';
import { FIND_MANY_SERVERLESS_FUNCTIONS } from '@/settings/serverless-functions/graphql/queries/findManyServerlessFunctions';
import { FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE } from '@/settings/serverless-functions/graphql/queries/findOneServerlessFunctionSourceCode';
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 { CrudOperationType } from 'twenty-shared/types';
import {
type CreateOneServerlessFunctionItemMutation,
type CreateOneServerlessFunctionItemMutationVariables,
type DeleteOneServerlessFunctionMutation,
type DeleteOneServerlessFunctionMutationVariables,
type UpdateOneServerlessFunctionMutation,
type UpdateOneServerlessFunctionMutationVariables,
} from '~/generated-metadata/graphql';
export const usePersistServerlessFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const { handleMetadataError } = useMetadataErrorHandler();
const { enqueueErrorSnackBar } = useSnackBar();
const [createServerlessFunctionMutation] = useMutation<
CreateOneServerlessFunctionItemMutation,
CreateOneServerlessFunctionItemMutationVariables
>(CREATE_ONE_SERVERLESS_FUNCTION, {
client: apolloMetadataClient,
});
const [updateServerlessFunctionMutation] = useMutation<
UpdateOneServerlessFunctionMutation,
UpdateOneServerlessFunctionMutationVariables
>(UPDATE_ONE_SERVERLESS_FUNCTION, {
client: apolloMetadataClient,
});
const [deleteServerlessFunctionMutation] = useMutation<
DeleteOneServerlessFunctionMutation,
DeleteOneServerlessFunctionMutationVariables
>(DELETE_ONE_SERVERLESS_FUNCTION, {
client: apolloMetadataClient,
});
const createServerlessFunction = useCallback(
async (
variables: CreateOneServerlessFunctionItemMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof createServerlessFunctionMutation>>
>
> => {
try {
const result = await createServerlessFunctionMutation({
variables,
awaitRefetchQueries: true,
refetchQueries: [
getOperationName(FIND_MANY_SERVERLESS_FUNCTIONS) ?? '',
],
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'serverlessFunction',
operationType: CrudOperationType.CREATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[
createServerlessFunctionMutation,
handleMetadataError,
enqueueErrorSnackBar,
],
);
const updateServerlessFunction = useCallback(
async (
variables: UpdateOneServerlessFunctionMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof updateServerlessFunctionMutation>>
>
> => {
try {
const result = await updateServerlessFunctionMutation({
variables,
refetchQueries: [
getOperationName(FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE) ?? '',
],
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'serverlessFunction',
operationType: CrudOperationType.UPDATE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[
updateServerlessFunctionMutation,
handleMetadataError,
enqueueErrorSnackBar,
],
);
const deleteServerlessFunction = useCallback(
async (
variables: DeleteOneServerlessFunctionMutationVariables,
): Promise<
MetadataRequestResult<
Awaited<ReturnType<typeof deleteServerlessFunctionMutation>>
>
> => {
try {
const result = await deleteServerlessFunctionMutation({
variables,
awaitRefetchQueries: true,
refetchQueries: [
getOperationName(FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE) ?? '',
],
});
return {
status: 'successful',
response: result,
};
} catch (error) {
if (error instanceof ApolloError) {
handleMetadataError(error, {
primaryMetadataName: 'serverlessFunction',
operationType: CrudOperationType.DELETE,
});
} else {
enqueueErrorSnackBar({ message: t`An error occurred.` });
}
return {
status: 'failed',
error,
};
}
},
[
deleteServerlessFunctionMutation,
handleMetadataError,
enqueueErrorSnackBar,
],
);
return {
createServerlessFunction,
updateServerlessFunction,
deleteServerlessFunction,
};
};
@@ -1,36 +0,0 @@
import { useApolloCoreClient } from '@/object-metadata/hooks/useApolloCoreClient';
import { PUBLISH_ONE_SERVERLESS_FUNCTION } from '@/settings/serverless-functions/graphql/mutations/publishOneServerlessFunction';
import { FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE } from '@/settings/serverless-functions/graphql/queries/findOneServerlessFunctionSourceCode';
import { useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities';
import {
type PublishOneServerlessFunctionMutation,
type PublishOneServerlessFunctionMutationVariables,
type PublishServerlessFunctionInput,
} from '~/generated-metadata/graphql';
export const usePublishOneServerlessFunction = () => {
const apolloMetadataClient = useApolloCoreClient();
const [mutate] = useMutation<
PublishOneServerlessFunctionMutation,
PublishOneServerlessFunctionMutationVariables
>(PUBLISH_ONE_SERVERLESS_FUNCTION, {
client: apolloMetadataClient,
});
const publishOneServerlessFunction = async (
input: PublishServerlessFunctionInput,
) => {
return await mutate({
variables: {
input,
},
awaitRefetchQueries: true,
refetchQueries: [
getOperationName(FIND_ONE_SERVERLESS_FUNCTION_SOURCE_CODE) ?? '',
],
});
};
return { publishOneServerlessFunction };
};
@@ -1,107 +0,0 @@
import { flattenSources } from '@/serverless-functions/utils/flattenSources';
import { getFunctionInputFromSourceCode } from '@/serverless-functions/utils/getFunctionInputFromSourceCode';
import { useGetOneServerlessFunction } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunction';
import { useGetOneServerlessFunctionSourceCode } from '@/settings/serverless-functions/hooks/useGetOneServerlessFunctionSourceCode';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/serverlessFunctionTestDataFamilyState';
import { type Dispatch, type SetStateAction, useState } from 'react';
import { useRecoilState } from 'recoil';
import { type Sources } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import {
type FindOneServerlessFunctionSourceCodeQuery,
type GetOneServerlessFunctionQuery,
} from '~/generated-metadata/graphql';
import { type ServerlessFunction } from '~/generated/graphql';
export type ServerlessFunctionNewFormValues = {
name: string;
description: string;
};
export type ServerlessFunctionFormValues = ServerlessFunctionNewFormValues & {
code: Sources;
};
type SetServerlessFunctionFormValues = Dispatch<
SetStateAction<ServerlessFunctionFormValues>
>;
export const useServerlessFunctionUpdateFormState = ({
serverlessFunctionId,
serverlessFunctionVersion = 'draft',
}: {
serverlessFunctionId: string;
serverlessFunctionVersion?: string;
}): {
formValues: ServerlessFunctionFormValues;
serverlessFunction: ServerlessFunction | null;
setFormValues: SetServerlessFunctionFormValues;
loading: boolean;
} => {
const [formValues, setFormValues] = useState<ServerlessFunctionFormValues>({
name: '',
description: '',
code: { src: { 'index.ts': '' } },
});
const [serverlessFunctionTestData, setServerlessFunctionTestData] =
useRecoilState(serverlessFunctionTestDataFamilyState(serverlessFunctionId));
const { serverlessFunction, loading: serverlessFunctionLoading } =
useGetOneServerlessFunction({
id: serverlessFunctionId,
onCompleted: (data: GetOneServerlessFunctionQuery) => {
const fn = data?.findOneServerlessFunction;
if (isDefined(fn)) {
setFormValues((prevState) => ({
...prevState,
name: fn.name || '',
description: fn.description || '',
}));
}
},
});
const { loading: serverlessFunctionSourceCodeLoading } =
useGetOneServerlessFunctionSourceCode({
id: serverlessFunctionId,
version: serverlessFunctionVersion,
onCompleted: async (data: FindOneServerlessFunctionSourceCodeQuery) => {
const code = data?.getServerlessFunctionSourceCode;
setFormValues((prevState) => ({
...prevState,
code: code || prevState.code,
}));
if (serverlessFunctionTestData.shouldInitInput) {
const flattenedCode = flattenSources(code);
const sourceCode = flattenedCode.find(
(flatCode) =>
flatCode.path === serverlessFunction?.sourceHandlerPath,
);
if (isDefined(sourceCode)) {
const functionInput = await getFunctionInputFromSourceCode(
sourceCode.content,
);
setServerlessFunctionTestData((prev) => ({
...prev,
input: functionInput,
shouldInitInput: false,
}));
}
}
},
});
return {
formValues,
setFormValues,
serverlessFunction,
loading: serverlessFunctionSourceCodeLoading || serverlessFunctionLoading,
};
};
@@ -54,9 +54,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -73,9 +73,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -133,9 +133,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -152,9 +152,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -200,9 +200,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -219,9 +219,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -267,9 +267,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -286,9 +286,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -305,9 +305,9 @@ describe('generateWorkflowDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -35,9 +35,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -54,9 +54,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -73,9 +73,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -272,9 +272,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -291,9 +291,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -310,9 +310,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -511,9 +511,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -530,9 +530,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -549,9 +549,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -750,9 +750,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -769,9 +769,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -788,9 +788,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -807,9 +807,9 @@ describe('generateWorkflowRunDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -126,9 +126,9 @@ describe('getWorkflowVersionDiagram', () => {
continueOnFailure: { value: false },
},
input: {
serverlessFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'a5434be2-c10b-465c-acec-46492782a997',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
},
@@ -5,7 +5,7 @@ import {
} from '@/workflow/types/Workflow';
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
import { WorkflowEditActionAiAgent } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowEditActionAiAgent';
import { WorkflowActionServerlessFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowActionServerlessFunction';
import { WorkflowActionLogicFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowActionLogicFunction';
import { WorkflowEditActionCreateRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord';
import { WorkflowEditActionDeleteRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord';
import { WorkflowEditActionEmpty } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmpty';
@@ -106,7 +106,7 @@ export const WorkflowRunStepNodeDetail = ({
switch (stepDefinition.definition.type) {
case 'CODE': {
return (
<WorkflowActionServerlessFunction
<WorkflowActionLogicFunction
key={stepId}
action={stepDefinition.definition}
actionOptions={{
@@ -4,7 +4,7 @@ import {
} from '@/workflow/types/Workflow';
import { getStepDefinitionOrThrow } from '@/workflow/utils/getStepDefinitionOrThrow';
import { WorkflowEditActionAiAgent } from '@/workflow/workflow-steps/workflow-actions/ai-agent-action/components/WorkflowEditActionAiAgent';
import { WorkflowActionServerlessFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowActionServerlessFunction';
import { WorkflowActionLogicFunction } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowActionLogicFunction';
import { WorkflowEditActionCreateRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionCreateRecord';
import { WorkflowEditActionDeleteRecord } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionDeleteRecord';
import { WorkflowEditActionEmpty } from '@/workflow/workflow-steps/workflow-actions/components/WorkflowEditActionEmpty';
@@ -108,7 +108,7 @@ export const WorkflowStepDetail = ({
switch (stepDefinition.definition.type) {
case 'CODE': {
return (
<WorkflowActionServerlessFunction
<WorkflowActionLogicFunction
key={stepId}
action={stepDefinition.definition}
actionOptions={props}
@@ -41,9 +41,9 @@ describe('useUpdateStep', () => {
type: 'CODE' as const,
settings: {
input: {
serverlessFunctionId: 'id',
serverlessFunctionVersion: '1',
serverlessFunctionInput: {},
logicFunctionId: 'id',
logicFunctionVersion: '1',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -28,9 +28,9 @@ describe('getIsDescendantOfIterator', () => {
nextStepIds: ['iterator1'],
settings: {
input: {
serverlessFunctionId: 'func2',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func2',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -48,9 +48,9 @@ describe('getIsDescendantOfIterator', () => {
nextStepIds: [],
settings: {
input: {
serverlessFunctionId: 'func3',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func3',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -89,9 +89,9 @@ describe('getIsDescendantOfIterator', () => {
nextStepIds: [],
settings: {
input: {
serverlessFunctionId: 'func4',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func4',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -27,9 +27,9 @@ describe('getStepInfoHistoryItem', () => {
nextStepIds: [],
settings: {
input: {
serverlessFunctionId: 'func',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -12,9 +12,9 @@ describe('getWorkflowPreviousSteps', () => {
nextStepIds: ['step2', 'step3'],
settings: {
input: {
serverlessFunctionId: 'func1',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func1',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -31,9 +31,9 @@ describe('getWorkflowPreviousSteps', () => {
nextStepIds: ['step4'],
settings: {
input: {
serverlessFunctionId: 'func2',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func2',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -50,9 +50,9 @@ describe('getWorkflowPreviousSteps', () => {
nextStepIds: ['step4'],
settings: {
input: {
serverlessFunctionId: 'func3',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func3',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -69,9 +69,9 @@ describe('getWorkflowPreviousSteps', () => {
nextStepIds: [],
settings: {
input: {
serverlessFunctionId: 'func4',
serverlessFunctionVersion: '1.0.0',
serverlessFunctionInput: {},
logicFunctionId: 'func4',
logicFunctionVersion: '1.0.0',
logicFunctionInput: {},
},
outputSchema: {},
errorHandlingOptions: {
@@ -17,9 +17,9 @@ const mockFlow = {
valid: true,
settings: {
input: {
serverlessFunctionId: '',
serverlessFunctionInput: {},
serverlessFunctionVersion: '',
logicFunctionId: '',
logicFunctionInput: {},
logicFunctionVersion: '',
},
outputSchema: {},
errorHandlingOptions: {
@@ -36,9 +36,9 @@ const mockFlow = {
valid: true,
settings: {
input: {
serverlessFunctionId: '',
serverlessFunctionInput: {},
serverlessFunctionVersion: '',
logicFunctionId: '',
logicFunctionInput: {},
logicFunctionVersion: '',
},
outputSchema: {},
errorHandlingOptions: {
@@ -139,9 +139,9 @@ describe('getWorkflowRunStepContext', () => {
valid: true,
settings: {
input: {
serverlessFunctionId: '',
serverlessFunctionInput: {},
serverlessFunctionVersion: '',
logicFunctionId: '',
logicFunctionInput: {},
logicFunctionVersion: '',
},
outputSchema: {},
errorHandlingOptions: {
@@ -176,9 +176,9 @@ describe('getWorkflowRunStepContext', () => {
valid: true,
settings: {
input: {
serverlessFunctionId: '',
serverlessFunctionInput: {},
serverlessFunctionVersion: '',
logicFunctionId: '',
logicFunctionInput: {},
logicFunctionVersion: '',
},
outputSchema: {},
errorHandlingOptions: {
@@ -21,18 +21,17 @@ describe('getWorkflowRunStepExecutionStatus', () => {
steps: [
{
id: stepId,
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId:
'5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
logicFunctionInput: {
a: null,
b: null,
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {
link: {
@@ -87,18 +86,17 @@ describe('getWorkflowRunStepExecutionStatus', () => {
steps: [
{
id: stepId,
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId:
'5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
logicFunctionInput: {
a: null,
b: null,
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {
link: {
@@ -154,18 +152,17 @@ describe('getWorkflowRunStepExecutionStatus', () => {
steps: [
{
id: stepId,
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId:
'5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
logicFunctionInput: {
a: null,
b: null,
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {
link: {
@@ -188,18 +185,17 @@ describe('getWorkflowRunStepExecutionStatus', () => {
},
{
id: secondStepId,
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId:
'5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c',
logicFunctionInput: {
a: null,
b: null,
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {
link: {
@@ -5,16 +5,16 @@ describe('getWorkflowVariablesUsedInStep', () => {
it('returns the variables used in a one-level object', () => {
const step: WorkflowStep = {
id: '42e8b60e-dd44-417a-875f-823d63f16819',
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
logicFunctionInput: {
a: '{{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a.b.c.d}}',
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {},
errorHandlingOptions: {
@@ -97,16 +97,16 @@ Set {
it('returns all the variables used in a single field', () => {
const step: WorkflowStep = {
id: '42e8b60e-dd44-417a-875f-823d63f16819',
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
logicFunctionInput: {
a: '{{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.b}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.c}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.d}}',
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {},
errorHandlingOptions: {
@@ -137,16 +137,16 @@ Set {
it('returns the variables used many times only once', () => {
const step: WorkflowStep = {
id: '42e8b60e-dd44-417a-875f-823d63f16819',
name: 'Code - Serverless Function',
name: 'Code - Logic Function',
type: 'CODE',
valid: false,
settings: {
input: {
serverlessFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
serverlessFunctionInput: {
logicFunctionId: '5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2d',
logicFunctionInput: {
a: '{{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}} {{5f7b9b44-bb07-41ba-aef8-ec0eaa5eea2c.a}}',
},
serverlessFunctionVersion: 'draft',
logicFunctionVersion: 'draft',
},
outputSchema: {},
errorHandlingOptions: {
@@ -2,7 +2,7 @@ import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
import { lazy, Suspense } from 'react';
import { RightDrawerSkeletonLoader } from '~/loading/components/RightDrawerSkeletonLoader';
type WorkflowActionServerlessFunctionProps = {
type WorkflowActionLogicFunctionProps = {
action: WorkflowCodeAction;
actionOptions:
| {
@@ -14,32 +14,32 @@ type WorkflowActionServerlessFunctionProps = {
};
};
const WorkflowEditActionServerlessFunction = lazy(() =>
const WorkflowEditActionLogicFunction = lazy(() =>
import(
'@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionServerlessFunction'
'@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionLogicFunction'
).then((module) => ({
default: module.WorkflowEditActionServerlessFunction,
default: module.WorkflowEditActionLogicFunction,
})),
);
const WorkflowReadonlyActionServerlessFunction = lazy(() =>
const WorkflowReadonlyActionLogicFunction = lazy(() =>
import(
'@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowReadonlyActionServerlessFunction'
'@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowReadonlyActionLogicFunction'
).then((module) => ({
default: module.WorkflowReadonlyActionServerlessFunction,
default: module.WorkflowReadonlyActionLogicFunction,
})),
);
export const WorkflowActionServerlessFunction = ({
export const WorkflowActionLogicFunction = ({
action,
actionOptions,
}: WorkflowActionServerlessFunctionProps) => {
}: WorkflowActionLogicFunctionProps) => {
return (
<Suspense fallback={<RightDrawerSkeletonLoader />}>
{actionOptions.readonly ? (
<WorkflowReadonlyActionServerlessFunction action={action} />
<WorkflowReadonlyActionLogicFunction action={action} />
) : (
<WorkflowEditActionServerlessFunction
<WorkflowEditActionLogicFunction
action={action}
actionOptions={actionOptions}
/>
@@ -1,5 +1,8 @@
import { useGetAvailablePackages } from '@/settings/serverless-functions/hooks/useGetAvailablePackages';
import { useServerlessFunctionUpdateFormState } from '@/settings/serverless-functions/hooks/useServerlessFunctionUpdateFormState';
import { useGetAvailablePackages } from '@/settings/logic-functions/hooks/useGetAvailablePackages';
import {
type LogicFunctionFormValues,
useLogicFunctionUpdateFormState,
} from '@/settings/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';
@@ -9,11 +12,11 @@ import { type WorkflowCodeAction } from '@/workflow/types/Workflow';
import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/setNestedValue';
import { CmdEnterActionButton } from '@/action-menu/components/CmdEnterActionButton';
import { ServerlessFunctionExecutionResult } from '@/serverless-functions/components/ServerlessFunctionExecutionResult';
import { INDEX_FILE_NAME } from '@/serverless-functions/constants/IndexFileName';
import { useTestServerlessFunction } from '@/serverless-functions/hooks/useTestServerlessFunction';
import { getFunctionInputFromSourceCode } from '@/serverless-functions/utils/getFunctionInputFromSourceCode';
import { mergeDefaultFunctionInputAndFunctionInput } from '@/serverless-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
import { LogicFunctionExecutionResult } from '@/logic-functions/components/LogicFunctionExecutionResult';
import { INDEX_FILE_NAME } from '@/logic-functions/constants/IndexFileName';
import { useTestLogicFunction } from '@/logic-functions/hooks/useTestLogicFunction';
import { getFunctionInputFromSourceCode } from '@/logic-functions/utils/getFunctionInputFromSourceCode';
import { mergeDefaultFunctionInputAndFunctionInput } from '@/logic-functions/utils/mergeDefaultFunctionInputAndFunctionInput';
import { InputLabel } from '@/ui/input/components/InputLabel';
import { TextArea } from '@/ui/input/components/TextArea';
import { TabList } from '@/ui/layout/tab-list/components/TabList';
@@ -22,19 +25,22 @@ import { useHotkeysOnFocusedElement } from '@/ui/utilities/hotkey/hooks/useHotke
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilComponentValue } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentValue';
import { WorkflowStepBody } from '@/workflow/workflow-steps/components/WorkflowStepBody';
import { WorkflowEditActionServerlessFunctionFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionServerlessFunctionFields';
import { WorkflowServerlessFunctionCodeEditor } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowServerlessFunctionCodeEditor';
import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowServerlessFunctionTabListComponentId';
import { serverlessFunctionTestDataFamilyState } from '@/workflow/workflow-steps/workflow-actions/code-action/states/serverlessFunctionTestDataFamilyState';
import { WorkflowServerlessFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowServerlessFunctionTabId';
import { WorkflowEditActionLogicFunctionFields } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowEditActionLogicFunctionFields';
import { WorkflowLogicFunctionCodeEditor } from '@/workflow/workflow-steps/workflow-actions/code-action/components/WorkflowLogicFunctionCodeEditor';
import { WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/workflow-steps/workflow-actions/code-action/constants/WorkflowLogicFunctionTabListComponentId';
import {
type LogicFunctionTestData,
logicFunctionTestDataFamilyState,
} from '@/workflow/workflow-steps/workflow-actions/code-action/states/logicFunctionTestDataFamilyState';
import { WorkflowLogicFunctionTabId } from '@/workflow/workflow-steps/workflow-actions/code-action/types/WorkflowLogicFunctionTabId';
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWrongExportedFunctionMarkers';
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro';
import { SOURCE_FOLDER_NAME } from '@/serverless-functions/constants/SourceFolderName';
import { computeNewSources } from '@/serverless-functions/utils/computeNewSources';
import { usePersistServerlessFunction } from '@/settings/serverless-functions/hooks/usePersistServerlessFunction';
import { SOURCE_FOLDER_NAME } from '@/logic-functions/constants/SourceFolderName';
import { computeNewSources } from '@/logic-functions/utils/computeNewSources';
import { usePersistLogicFunction } from '@/settings/logic-functions/hooks/usePersistLogicFunction';
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';
@@ -71,7 +77,7 @@ const StyledFullScreenCodeEditorContainer = styled.div`
min-height: 0;
`;
type WorkflowEditActionServerlessFunctionProps = {
type WorkflowEditActionLogicFunctionProps = {
action: WorkflowCodeAction;
actionOptions:
| {
@@ -83,24 +89,24 @@ type WorkflowEditActionServerlessFunctionProps = {
};
};
type ServerlessFunctionInputFormData = {
[field: string]: string | ServerlessFunctionInputFormData;
type LogicFunctionInputFormData = {
[field: string]: string | LogicFunctionInputFormData;
};
export const WorkflowEditActionServerlessFunction = ({
export const WorkflowEditActionLogicFunction = ({
action,
actionOptions,
}: WorkflowEditActionServerlessFunctionProps) => {
}: WorkflowEditActionLogicFunctionProps) => {
const { t } = useLingui();
const [isFullScreen, setIsFullScreen] = useState(false);
const isMobile = useIsMobile();
const serverlessFunctionId = action.settings.input.serverlessFunctionId;
const fullScreenFocusId = `code-editor-fullscreen-${serverlessFunctionId}`;
const logicFunctionId = action.settings.input.logicFunctionId;
const fullScreenFocusId = `code-editor-fullscreen-${logicFunctionId}`;
const activeTabId = useRecoilComponentValue(
activeTabIdComponentState,
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID,
WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID,
);
const { updateServerlessFunction } = usePersistServerlessFunction();
const { updateLogicFunction } = usePersistLogicFunction();
const { getUpdatableWorkflowVersion } =
useGetUpdatableWorkflowVersionOrThrow();
@@ -109,21 +115,23 @@ export const WorkflowEditActionServerlessFunction = ({
);
const workflow = useWorkflowWithCurrentVersion(workflowVisualizerWorkflowId);
const { availablePackages } = useGetAvailablePackages({
id: serverlessFunctionId,
id: logicFunctionId,
});
const [serverlessFunctionTestData, setServerlessFunctionTestData] =
useRecoilState(serverlessFunctionTestDataFamilyState(serverlessFunctionId));
const [logicFunctionTestData, setLogicFunctionTestData] =
useRecoilState<LogicFunctionTestData>(
logicFunctionTestDataFamilyState(logicFunctionId),
);
const [functionInput, setFunctionInput] =
useState<ServerlessFunctionInputFormData>(
action.settings.input.serverlessFunctionInput,
useState<LogicFunctionInputFormData>(
action.settings.input.logicFunctionInput,
);
const { formValues, setFormValues, loading } =
useServerlessFunctionUpdateFormState({
serverlessFunctionId,
serverlessFunctionVersion: 'draft',
useLogicFunctionUpdateFormState({
logicFunctionId,
logicFunctionVersion: 'draft',
});
const updateOutputSchemaFromTestResult = async (testResult: object) => {
@@ -137,15 +145,15 @@ export const WorkflowEditActionServerlessFunction = ({
});
};
const { testServerlessFunction, isTesting } = useTestServerlessFunction({
serverlessFunctionId,
const { testLogicFunction, isTesting } = useTestLogicFunction({
logicFunctionId,
callback: updateOutputSchemaFromTestResult,
});
const handleSave = useDebouncedCallback(async () => {
await updateServerlessFunction({
await updateLogicFunction({
input: {
id: serverlessFunctionId,
id: logicFunctionId,
update: {
name: formValues.name,
description: formValues.description,
@@ -159,7 +167,7 @@ export const WorkflowEditActionServerlessFunction = ({
if (actionOptions.readonly === true) {
return;
}
setFormValues((prevState) => {
setFormValues((prevState: LogicFunctionFormValues) => {
return {
...prevState,
code: computeNewSources({
@@ -186,15 +194,15 @@ export const WorkflowEditActionServerlessFunction = ({
const newFunctionInput = await getFunctionInputFromSourceCode(sourceCode);
const newMergedInput = mergeDefaultFunctionInputAndFunctionInput({
newInput: newFunctionInput,
oldInput: action.settings.input.serverlessFunctionInput,
oldInput: action.settings.input.logicFunctionInput,
});
const newMergedTestInput = mergeDefaultFunctionInputAndFunctionInput({
newInput: newFunctionInput,
oldInput: serverlessFunctionTestData.input,
oldInput: logicFunctionTestData.input,
});
setFunctionInput(newMergedInput);
setServerlessFunctionTestData((prev) => ({
setLogicFunctionTestData((prev) => ({
...prev,
input: newMergedTestInput,
}));
@@ -214,7 +222,7 @@ export const WorkflowEditActionServerlessFunction = ({
},
input: {
...action.settings.input,
serverlessFunctionInput: newMergedInput,
logicFunctionInput: newMergedInput,
},
},
});
@@ -233,7 +241,7 @@ export const WorkflowEditActionServerlessFunction = ({
...action.settings,
input: {
...action.settings.input,
serverlessFunctionInput: updatedFunctionInput,
logicFunctionInput: updatedFunctionInput,
},
},
});
@@ -245,11 +253,11 @@ export const WorkflowEditActionServerlessFunction = ({
}
const updatedTestFunctionInput = setNestedValue(
serverlessFunctionTestData.input,
logicFunctionTestData.input,
path,
value,
);
setServerlessFunctionTestData((prev) => ({
setLogicFunctionTestData((prev) => ({
...prev,
input: updatedTestFunctionInput,
}));
@@ -261,7 +269,7 @@ export const WorkflowEditActionServerlessFunction = ({
}
if (!isTesting) {
await testServerlessFunction();
await testLogicFunction();
}
};
@@ -302,19 +310,19 @@ export const WorkflowEditActionServerlessFunction = ({
const tabs = [
{
id: WorkflowServerlessFunctionTabId.CODE,
id: WorkflowLogicFunctionTabId.CODE,
title: t`Code`,
Icon: IconCode,
},
{
id: WorkflowServerlessFunctionTabId.TEST,
id: WorkflowLogicFunctionTabId.TEST,
title: t`Test`,
Icon: IconPlayerPlay,
},
];
useEffect(() => {
setFunctionInput(action.settings.input.serverlessFunctionInput);
setFunctionInput(action.settings.input.logicFunctionInput);
}, [action]);
useHotkeysOnFocusedElement({
@@ -328,7 +336,7 @@ export const WorkflowEditActionServerlessFunction = ({
dependencies: [isFullScreen],
});
const testLogsTextAreaId = `${serverlessFunctionId}-test-logs`;
const testLogsTextAreaId = `${logicFunctionId}-test-logs`;
const breadcrumbLinks: BreadcrumbProps['links'] = [
{
@@ -358,7 +366,7 @@ export const WorkflowEditActionServerlessFunction = ({
handleExitFullScreen();
}
},
listenerId: `full-screen-overlay-${serverlessFunctionId}`,
listenerId: `full-screen-overlay-${logicFunctionId}`,
enabled: isFullScreen,
});
@@ -383,7 +391,7 @@ export const WorkflowEditActionServerlessFunction = ({
const fullScreenOverlay = renderFullScreenModal(
<div data-globally-prevent-click-outside="true">
<WorkflowEditActionServerlessFunctionFields
<WorkflowEditActionLogicFunctionFields
functionInput={functionInput}
VariablePicker={WorkflowVariablePicker}
onInputChange={handleInputChange}
@@ -415,20 +423,18 @@ export const WorkflowEditActionServerlessFunction = ({
<StyledTabList
tabs={tabs}
behaveAsLinks={false}
componentInstanceId={
WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID
}
componentInstanceId={WORKFLOW_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID}
/>
<WorkflowStepBody>
{activeTabId === WorkflowServerlessFunctionTabId.CODE && (
{activeTabId === WorkflowLogicFunctionTabId.CODE && (
<>
<WorkflowEditActionServerlessFunctionFields
<WorkflowEditActionLogicFunctionFields
functionInput={functionInput}
VariablePicker={WorkflowVariablePicker}
onInputChange={handleInputChange}
readonly={actionOptions.readonly}
/>
<WorkflowServerlessFunctionCodeEditor
<WorkflowLogicFunctionCodeEditor
value={indexFileContent}
onChange={handleCodeChange}
onMount={handleEditorDidMount}
@@ -444,28 +450,26 @@ export const WorkflowEditActionServerlessFunction = ({
/>
</>
)}
{activeTabId === WorkflowServerlessFunctionTabId.TEST && (
{activeTabId === WorkflowLogicFunctionTabId.TEST && (
<>
<WorkflowEditActionServerlessFunctionFields
functionInput={serverlessFunctionTestData.input}
<WorkflowEditActionLogicFunctionFields
functionInput={logicFunctionTestData.input}
onInputChange={handleTestInputChange}
readonly={actionOptions.readonly}
/>
<StyledCodeEditorContainer>
<InputLabel>{t`Result`}</InputLabel>
<ServerlessFunctionExecutionResult
serverlessFunctionTestData={serverlessFunctionTestData}
<LogicFunctionExecutionResult
logicFunctionTestData={logicFunctionTestData}
isTesting={isTesting}
/>
</StyledCodeEditorContainer>
{serverlessFunctionTestData.output.logs.length > 0 && (
{logicFunctionTestData.output.logs.length > 0 && (
<StyledCodeEditorContainer>
<InputLabel>{t`Logs`}</InputLabel>
<TextArea
textAreaId={testLogsTextAreaId}
value={
isTesting ? '' : serverlessFunctionTestData.output.logs
}
value={isTesting ? '' : logicFunctionTestData.output.logs}
maxRows={20}
disabled
/>
@@ -478,7 +482,7 @@ export const WorkflowEditActionServerlessFunction = ({
<WorkflowStepFooter
stepId={action.id}
additionalActions={
activeTabId === WorkflowServerlessFunctionTabId.TEST
activeTabId === WorkflowLogicFunctionTabId.TEST
? [
<CmdEnterActionButton
title={t`Test`}
@@ -18,7 +18,7 @@ const StyledContainer = styled.div`
}
`;
type WorkflowEditActionServerlessFunctionFieldsProps = {
type WorkflowEditActionLogicFunctionFieldsProps = {
functionInput: FunctionInput;
path?: string[];
readonly?: boolean;
@@ -26,13 +26,13 @@ type WorkflowEditActionServerlessFunctionFieldsProps = {
VariablePicker?: VariablePickerComponent;
};
export const WorkflowEditActionServerlessFunctionFields = ({
export const WorkflowEditActionLogicFunctionFields = ({
functionInput,
path = [],
readonly,
onInputChange,
VariablePicker,
}: WorkflowEditActionServerlessFunctionFieldsProps) => {
}: WorkflowEditActionLogicFunctionFieldsProps) => {
return (
<StyledContainer>
{Object.entries(functionInput).map(([inputKey, inputValue]) => {
@@ -44,7 +44,7 @@ export const WorkflowEditActionServerlessFunctionFields = ({
<div key={pathKey}>
<InputLabel>{inputKey}</InputLabel>
<FormNestedFieldInputContainer>
<WorkflowEditActionServerlessFunctionFields
<WorkflowEditActionLogicFunctionFields
functionInput={inputValue}
path={currentPath}
readonly={readonly}
@@ -24,7 +24,7 @@ const StyledFullScreenButtonContainer = styled.div`
z-index: 1;
`;
type WorkflowServerlessFunctionCodeEditorProps = {
type WorkflowLogicFunctionCodeEditorProps = {
value?: string;
onChange: (value: string) => void;
onMount: (editor: editor.IStandaloneCodeEditor, monaco: Monaco) => void;
@@ -34,7 +34,7 @@ type WorkflowServerlessFunctionCodeEditorProps = {
onEnterFullScreen?: () => void;
};
export const WorkflowServerlessFunctionCodeEditor = ({
export const WorkflowLogicFunctionCodeEditor = ({
value,
onChange,
onMount,
@@ -42,7 +42,7 @@ export const WorkflowServerlessFunctionCodeEditor = ({
readonly = false,
fullScreenMode = false,
onEnterFullScreen,
}: WorkflowServerlessFunctionCodeEditorProps) => {
}: WorkflowLogicFunctionCodeEditorProps) => {
const { t } = useLingui();
return (

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