diff --git a/.vscode/settings.json b/.vscode/settings.json index aa98f88ab..0bb6069e2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,13 +11,6 @@ "editor.formatOnSave": true, "editor.tabSize": 2, "typescript.updateImportsOnFileMove.enabled": "always", - "playwright.env": { - "DATABASE_URL": "postgresql://postgres:typebot@127.0.0.1:5432/typebot", - "NEXT_PUBLIC_VIEWER_URL": "http://localhost:3001", - "NEXTAUTH_URL": "http://localhost:3000", - "ENCRYPTION_SECRET": "H+KbL/OFrqbEuDy/1zX8bsPG+spXri3S", - "S3_ENDPOINT": "http://localhost:9000" - }, "[prisma]": { "editor.defaultFormatter": "Prisma.prisma" } diff --git a/apps/builder/package.json b/apps/builder/package.json index c45ab1402..ca4f5e067 100644 --- a/apps/builder/package.json +++ b/apps/builder/package.json @@ -99,7 +99,7 @@ }, "devDependencies": { "@chakra-ui/styled-system": "2.9.1", - "@playwright/test": "1.41.2", + "@playwright/test": "1.43.1", "@typebot.io/billing": "workspace:*", "@typebot.io/forge": "workspace:*", "@typebot.io/forge-repository": "workspace:*", @@ -123,12 +123,13 @@ "@types/qs": "6.9.7", "@types/react": "18.2.15", "@types/tinycolor2": "1.4.3", - "dotenv-cli": "7.2.1", + "dotenv-cli": "7.4.1", "eslint": "8.44.0", "eslint-config-custom": "workspace:*", "next-runtime-env": "1.6.2", "superjson": "1.12.4", "typescript": "5.4.5", - "zod": "3.22.4" + "zod": "3.22.4", + "dotenv": "16.4.5" } } diff --git a/apps/builder/playwright.config.ts b/apps/builder/playwright.config.ts index 8ef248c58..91b2463ed 100644 --- a/apps/builder/playwright.config.ts +++ b/apps/builder/playwright.config.ts @@ -1,4 +1,8 @@ import { defineConfig, devices } from '@playwright/test' +import { resolve } from 'path' + +// eslint-disable-next-line @typescript-eslint/no-var-requires +require('dotenv').config({ path: resolve(__dirname, '../../.env') }) export default defineConfig({ timeout: process.env.CI ? 50 * 1000 : 40 * 1000, diff --git a/apps/builder/src/components/inputs/SwitchWithLabel.tsx b/apps/builder/src/components/inputs/SwitchWithLabel.tsx index 0a50c5be0..4813108a0 100644 --- a/apps/builder/src/components/inputs/SwitchWithLabel.tsx +++ b/apps/builder/src/components/inputs/SwitchWithLabel.tsx @@ -6,8 +6,9 @@ import { Switch, SwitchProps, } from '@chakra-ui/react' -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { MoreInfoTooltip } from '../MoreInfoTooltip' +import { isDefined } from '@typebot.io/lib' export type SwitchWithLabelProps = { label: string @@ -32,6 +33,11 @@ export const SwitchWithLabel = ({ if (onCheckChange) onCheckChange(!isChecked) } + useEffect(() => { + if (isChecked === undefined && isDefined(initialValue)) + setIsChecked(initialValue) + }, [initialValue, isChecked]) + return ( diff --git a/apps/builder/src/components/inputs/VariableSearchInput.tsx b/apps/builder/src/components/inputs/VariableSearchInput.tsx index 9d665e0b7..3e6493098 100644 --- a/apps/builder/src/components/inputs/VariableSearchInput.tsx +++ b/apps/builder/src/components/inputs/VariableSearchInput.tsx @@ -123,7 +123,7 @@ export const VariableSearchInput = ({ if (!inputValue || inputValue === '') return const id = 'v' + createId() onSelectVariable({ id, name: inputValue }) - createVariable({ id, name: inputValue }) + createVariable({ id, name: inputValue, isSessionVariable: true }) inputRef.current?.blur() onClose() } diff --git a/apps/builder/src/features/blocks/inputs/date/date.spec.ts b/apps/builder/src/features/blocks/inputs/date/date.spec.ts index 83291aad2..04ca6f7df 100644 --- a/apps/builder/src/features/blocks/inputs/date/date.spec.ts +++ b/apps/builder/src/features/blocks/inputs/date/date.spec.ts @@ -24,7 +24,7 @@ test.describe('Date input block', () => { 'date' ) await page.locator('[data-testid="from-date"]').fill('2021-01-01') - await page.getByRole('button', { name: 'Send' }).click() + await page.locator('form').getByRole('button').click() await expect(page.locator('text="01/01/2021"')).toBeVisible() await page.click(`text=Pick a date`) diff --git a/apps/builder/src/features/blocks/inputs/rating/rating.spec.ts b/apps/builder/src/features/blocks/inputs/rating/rating.spec.ts index db174fa86..eba8fb871 100644 --- a/apps/builder/src/features/blocks/inputs/rating/rating.spec.ts +++ b/apps/builder/src/features/blocks/inputs/rating/rating.spec.ts @@ -32,7 +32,7 @@ test('options should work', async ({ page }) => { await page.click('text=Test') await expect(page.locator(`text=Send`)).toBeHidden() - await page.getByRole('button', { name: '8' }).click() + await page.getByRole('checkbox', { name: '8' }).click() await page.locator(`text=Send`).click() await expect(page.getByTestId('guest-bubble')).toHaveText('8') await page.click('text=Rate from 0 to 10') diff --git a/apps/builder/src/features/blocks/logic/setVariable/components/SetVariableSettings.tsx b/apps/builder/src/features/blocks/logic/setVariable/components/SetVariableSettings.tsx index 3c08c05f3..7499ebb9b 100644 --- a/apps/builder/src/features/blocks/logic/setVariable/components/SetVariableSettings.tsx +++ b/apps/builder/src/features/blocks/logic/setVariable/components/SetVariableSettings.tsx @@ -1,7 +1,6 @@ import { Alert, AlertIcon, FormLabel, Stack, Tag, Text } from '@chakra-ui/react' import { CodeEditor } from '@/components/inputs/CodeEditor' import { SetVariableBlock, Variable } from '@typebot.io/schemas' -import React from 'react' import { VariableSearchInput } from '@/components/inputs/VariableSearchInput' import { SwitchWithLabel } from '@/components/inputs/SwitchWithLabel' import { Select } from '@/components/inputs/Select' @@ -13,6 +12,7 @@ import { } from '@typebot.io/schemas/features/blocks/logic/setVariable/constants' import { TextInput } from '@/components/inputs' import { isDefined } from '@typebot.io/lib' +import { useTypebot } from '@/features/editor/providers/TypebotProvider' type Props = { options: SetVariableBlock['options'] @@ -24,7 +24,12 @@ const setVarTypes = valueTypes.filter( ) export const SetVariableSettings = ({ options, onOptionsChange }: Props) => { - const updateVariableId = (variable?: Variable) => + const { typebot, updateVariable } = useTypebot() + const selectedVariable = typebot?.variables.find( + (variable) => variable.id === options?.variableId + ) + + const updateVariableId = (variable?: Pick) => onOptionsChange({ ...options, variableId: variable?.id, @@ -36,6 +41,13 @@ export const SetVariableSettings = ({ options, onOptionsChange }: Props) => { type: type as NonNullable['type'], }) + const updateIsSessionVariable = (isSavingInResults: boolean) => { + if (!selectedVariable?.id) return + updateVariable(selectedVariable.id, { + isSessionVariable: !isSavingInResults, + }) + } + return ( @@ -49,22 +61,34 @@ export const SetVariableSettings = ({ options, onOptionsChange }: Props) => { /> - - - Value: - - ({ + label: type, + value: type, + icon: + type === 'Contact name' || type === 'Phone number' ? ( + + ) : undefined, + }))} + onSelect={updateValueType} + /> + + + {selectedVariable && ( + + )} @@ -91,7 +115,7 @@ const SetVariableValue = ({ isExecutedOnClient, }) - const updateItemVariableId = (variable?: Variable) => { + const updateItemVariableId = (variable?: Pick) => { if (!options || options.type !== 'Map item with same index') return onOptionsChange({ ...options, @@ -102,7 +126,7 @@ const SetVariableValue = ({ }) } - const updateBaseListVariableId = (variable?: Variable) => { + const updateBaseListVariableId = (variable?: Pick) => { if (!options || options.type !== 'Map item with same index') return onOptionsChange({ ...options, @@ -113,7 +137,7 @@ const SetVariableValue = ({ }) } - const updateTargetListVariableId = (variable?: Variable) => { + const updateTargetListVariableId = (variable?: Pick) => { if (!options || options.type !== 'Map item with same index') return onOptionsChange({ ...options, diff --git a/apps/builder/src/features/blocks/logic/setVariable/setVariable.spec.ts b/apps/builder/src/features/blocks/logic/setVariable/setVariable.spec.ts index bfee76958..43616d1d0 100644 --- a/apps/builder/src/features/blocks/logic/setVariable/setVariable.spec.ts +++ b/apps/builder/src/features/blocks/logic/setVariable/setVariable.spec.ts @@ -28,11 +28,16 @@ test.describe('Set variable block', () => { .fill('1000 * {{Num}}') await page.click('text=Click to edit...', { force: true }) + await expect(page.getByText('Save in results?')).toBeHidden() await page.fill( 'input[placeholder="Select a variable"] >> nth=-1', 'Custom var' ) await page.getByRole('menuitem', { name: 'Create Custom var' }).click() + await expect(page.getByText('Save in results?')).toBeVisible() + await expect( + page.getByRole('group').nth(1).locator('.chakra-switch') + ).not.toHaveAttribute('data-checked') await page .getByTestId('code-editor') .getByRole('textbox') diff --git a/apps/builder/src/features/results/results.spec.ts b/apps/builder/src/features/results/results.spec.ts index c5672d55a..f2618d0f9 100644 --- a/apps/builder/src/features/results/results.spec.ts +++ b/apps/builder/src/features/results/results.spec.ts @@ -33,6 +33,7 @@ test('table features should work', async ({ page }) => { await expect(page.locator('text=Additional information')).toBeVisible() await expect(page.locator('text=utm_source')).toBeVisible() await expect(page.locator('text=utm_userid')).toBeVisible() + await expect(page.locator('text=utm_session')).toBeHidden() }) await test.step('Resize columns', async () => { diff --git a/apps/builder/src/features/workspace/workspaces.spec.ts b/apps/builder/src/features/workspace/workspaces.spec.ts index 6d51ec44e..b93f6249f 100644 --- a/apps/builder/src/features/workspace/workspaces.spec.ts +++ b/apps/builder/src/features/workspace/workspaces.spec.ts @@ -177,7 +177,7 @@ test("can't add new members when limit is reached", async ({ page }) => { await page.click('button >> text="Invite"') await expect( page.locator( - 'text="Upgrade your plan to work with more team members, and unlock awesome power features 🚀"' + 'text="Upgrade your plan to work with more team members, and unlock awesome power features"' ) ).toBeVisible() await expect(page.locator('button >> text="Invite"')).toBeDisabled() diff --git a/apps/builder/src/test/assets/typebots/results/submissionHeader.json b/apps/builder/src/test/assets/typebots/results/submissionHeader.json index 8f15257df..9d37f8726 100644 --- a/apps/builder/src/test/assets/typebots/results/submissionHeader.json +++ b/apps/builder/src/test/assets/typebots/results/submissionHeader.json @@ -258,8 +258,21 @@ } ], "variables": [ - { "id": "giiLFGw5xXBCHzvp1qAbdX", "name": "Name" }, - { "id": "3VFChNVSCXQ2rXv4DrJ8Ah", "name": "Email" }, + { + "id": "giiLFGw5xXBCHzvp1qAbdX", + "name": "Name", + "isSessionVariable": true + }, + { + "id": "3VFChNVSCXQ2rXv4DrJ8Ah", + "name": "Email", + "isSessionVariable": true + }, + { + "id": "8Q8t9YCc3ieAEXSYkmnCxH", + "name": "utm_session", + "isSessionVariable": true + }, { "id": "8Q8t9YCc3ieAEXSYkmnCxH", "name": "utm_source" }, { "id": "ds54chWAyWC4zjkdWAm3Vc", "name": "utm_userid" } ], diff --git a/apps/docs/editor/blocks/logic/set-variable.mdx b/apps/docs/editor/blocks/logic/set-variable.mdx index f66a1d227..b0e539327 100644 --- a/apps/docs/editor/blocks/logic/set-variable.mdx +++ b/apps/docs/editor/blocks/logic/set-variable.mdx @@ -126,6 +126,14 @@ This value block allows you to find the `Id` from `Ids` with the same index as ` /> +## Save in results + +By default, new variables are not persisted in the [Results](../../../results) table. They are only stored for the current user chat session. Enabling this option will save the variable in the `Results` table. + +## Execute on client + +This option is useful when you want to execute the custom code on the client side. This is only necessary when you need access the user's browser information. So, if you need access to `window`, `document`, `navigator`, etc., you should enable this option. + ## Get user's geo location For this you can provide the following custom code: diff --git a/apps/viewer/package.json b/apps/viewer/package.json index 30a725630..cdfcd33f3 100644 --- a/apps/viewer/package.json +++ b/apps/viewer/package.json @@ -40,7 +40,7 @@ "dotenv": "16.4.5", "@faire/mjml-react": "3.3.0", "@paralleldrive/cuid2": "2.2.1", - "@playwright/test": "1.36.0", + "@playwright/test": "1.43.1", "@typebot.io/emails": "workspace:*", "@typebot.io/env": "workspace:*", "@typebot.io/forge": "workspace:*", @@ -55,7 +55,8 @@ "@types/papaparse": "5.3.7", "@types/qs": "6.9.7", "@types/react": "18.2.15", - "dotenv-cli": "7.2.1", + "dotenv-cli": "7.4.1", + "dotenv": "16.4.5", "eslint": "8.44.0", "eslint-config-custom": "workspace:*", "google-auth-library": "8.9.0", diff --git a/apps/viewer/playwright.config.ts b/apps/viewer/playwright.config.ts index 44f664cc8..f8eb434e3 100644 --- a/apps/viewer/playwright.config.ts +++ b/apps/viewer/playwright.config.ts @@ -1,6 +1,8 @@ import { defineConfig, devices } from '@playwright/test' +import { resolve } from 'path' -process.env.SKIP_ENV_CHECK = 'true' +// eslint-disable-next-line @typescript-eslint/no-var-requires +require('dotenv').config({ path: resolve(__dirname, '../../.env') }) export default defineConfig({ timeout: process.env.CI ? 50 * 1000 : 40 * 1000, diff --git a/packages/bot-engine/queries/upsertResult.ts b/packages/bot-engine/queries/upsertResult.ts index d0dc2ee5d..9947bd112 100644 --- a/packages/bot-engine/queries/upsertResult.ts +++ b/packages/bot-engine/queries/upsertResult.ts @@ -1,7 +1,7 @@ import prisma from '@typebot.io/lib/prisma' import { Prisma } from '@typebot.io/prisma' import { TypebotInSession } from '@typebot.io/schemas' -import { filterVariablesWithValues } from '@typebot.io/variables/filterVariablesWithValues' +import { filterNonSessionVariablesWithValues } from '@typebot.io/variables/filterVariablesWithValues' type Props = { resultId: string @@ -15,7 +15,9 @@ export const upsertResult = ({ hasStarted, isCompleted, }: Props): Prisma.PrismaPromise => { - const variablesWithValue = filterVariablesWithValues(typebot.variables) + const variablesWithValue = filterNonSessionVariablesWithValues( + typebot.variables + ) return prisma.result.upsert({ where: { id: resultId }, update: { diff --git a/packages/results/parseResultHeader.ts b/packages/results/parseResultHeader.ts index 601bc3e26..d88b9c28e 100644 --- a/packages/results/parseResultHeader.ts +++ b/packages/results/parseResultHeader.ts @@ -149,7 +149,8 @@ const parseVariablesHeaders = ( if ( existingInputResultHeaders.some((existingInputResultHeader) => existingInputResultHeader.variableIds?.includes(variable.id) - ) + ) || + variable.isSessionVariable ) return existingHeaders diff --git a/packages/schemas/features/typebot/variable.ts b/packages/schemas/features/typebot/variable.ts index 3e23e30ab..2edce1d98 100644 --- a/packages/schemas/features/typebot/variable.ts +++ b/packages/schemas/features/typebot/variable.ts @@ -2,27 +2,27 @@ import { z } from '../../zod' export const listVariableValue = z.array(z.string().nullable()) -export const variableSchema = z.object({ +const baseVariableSchema = z.object({ id: z.string(), name: z.string(), + isSessionVariable: z.boolean().optional(), +}) + +export const variableSchema = baseVariableSchema.extend({ value: z.string().or(listVariableValue).nullish(), }) /** * Variable when retrieved from the database */ -export const variableWithValueSchema = z.object({ - id: z.string(), - name: z.string(), +export const variableWithValueSchema = baseVariableSchema.extend({ value: z.string().or(listVariableValue), }) /** * Variable when computed or retrieved from a block */ -const VariableWithUnknowValueSchema = z.object({ - id: z.string(), - name: z.string(), +const VariableWithUnknowValueSchema = baseVariableSchema.extend({ value: z.unknown(), }) diff --git a/packages/variables/filterVariablesWithValues.ts b/packages/variables/filterVariablesWithValues.ts index 2d710bda5..81900bcd4 100644 --- a/packages/variables/filterVariablesWithValues.ts +++ b/packages/variables/filterVariablesWithValues.ts @@ -1,9 +1,9 @@ import { isDefined } from '@typebot.io/lib' import { Variable, VariableWithValue } from '../schemas' -export const filterVariablesWithValues = ( +export const filterNonSessionVariablesWithValues = ( variables: Variable[] ): VariableWithValue[] => - variables.filter((variable) => - isDefined(variable.value) + variables.filter( + (variable) => isDefined(variable.value) && !variable.isSessionVariable ) as VariableWithValue[] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 289584e91..1d4de5c8e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -283,8 +283,8 @@ importers: specifier: 2.9.1 version: 2.9.1 '@playwright/test': - specifier: 1.41.2 - version: 1.41.2 + specifier: 1.43.1 + version: 1.43.1 '@typebot.io/billing': specifier: workspace:* version: link:../../packages/billing @@ -354,9 +354,12 @@ importers: '@types/tinycolor2': specifier: 1.4.3 version: 1.4.3 + dotenv: + specifier: 16.4.5 + version: 16.4.5 dotenv-cli: - specifier: 7.2.1 - version: 7.2.1 + specifier: 7.4.1 + version: 7.4.1 eslint: specifier: 8.44.0 version: 8.44.0 @@ -649,8 +652,8 @@ importers: specifier: 2.2.1 version: 2.2.1 '@playwright/test': - specifier: 1.36.0 - version: 1.36.0 + specifier: 1.43.1 + version: 1.43.1 '@typebot.io/emails': specifier: workspace:* version: link:../../packages/emails @@ -703,8 +706,8 @@ importers: specifier: 16.4.5 version: 16.4.5 dotenv-cli: - specifier: 7.2.1 - version: 7.2.1 + specifier: 7.4.1 + version: 7.4.1 eslint: specifier: 8.44.0 version: 8.44.0 @@ -7932,14 +7935,6 @@ packages: fsevents: 2.3.2 dev: true - /@playwright/test@1.41.2: - resolution: {integrity: sha512-qQB9h7KbibJzrDpkXkYvsmiDJK14FULCCZgEcoe2AvFAS64oCirWTwzTlAYEbKaRxWs5TFesE1Na6izMv3HfGg==} - engines: {node: '>=16'} - hasBin: true - dependencies: - playwright: 1.41.2 - dev: true - /@playwright/test@1.42.1: resolution: {integrity: sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==} engines: {node: '>=16'} @@ -7948,6 +7943,14 @@ packages: playwright: 1.42.1 dev: false + /@playwright/test@1.43.1: + resolution: {integrity: sha512-HgtQzFgNEEo4TE22K/X7sYTYNqEMMTZmFS8kTq6m8hXj+m1D8TgwgIbumHddJa9h4yl4GkKb8/bgAl2+g7eDgA==} + engines: {node: '>=16'} + hasBin: true + dependencies: + playwright: 1.43.1 + dev: true + /@popperjs/core@2.11.8: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false @@ -13213,6 +13216,16 @@ packages: minimist: 1.2.8 dev: true + /dotenv-cli@7.4.1: + resolution: {integrity: sha512-fE1aywjRrWGxV3miaiUr3d2zC/VAiuzEGghi+QzgIA9fEf/M5hLMaRSXb4IxbUAwGmaLi0IozdZddnVU96acag==} + hasBin: true + dependencies: + cross-spawn: 7.0.3 + dotenv: 16.4.5 + dotenv-expand: 10.0.0 + minimist: 1.2.8 + dev: true + /dotenv-expand@10.0.0: resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} engines: {node: '>=12'} @@ -19262,26 +19275,16 @@ packages: hasBin: true dev: true - /playwright-core@1.41.2: - resolution: {integrity: sha512-VaTvwCA4Y8kxEe+kfm2+uUUw5Lubf38RxF7FpBxLPmGe5sdNkSg5e3ChEigaGrX7qdqT3pt2m/98LiyvU2x6CA==} - engines: {node: '>=16'} - hasBin: true - dev: true - /playwright-core@1.42.1: resolution: {integrity: sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==} engines: {node: '>=16'} hasBin: true dev: false - /playwright@1.41.2: - resolution: {integrity: sha512-v0bOa6H2GJChDL8pAeLa/LZC4feoAMbSQm1/jF/ySsWWoaNItvrMP7GEkvEEFyCTUYKMxjQKaTSg5up7nR6/8A==} + /playwright-core@1.43.1: + resolution: {integrity: sha512-EI36Mto2Vrx6VF7rm708qSnesVQKbxEWvPrfA1IPY6HgczBplDx7ENtx+K2n4kJ41sLLkuGfmb0ZLSSXlDhqPg==} engines: {node: '>=16'} hasBin: true - dependencies: - playwright-core: 1.41.2 - optionalDependencies: - fsevents: 2.3.2 dev: true /playwright@1.42.1: @@ -19294,6 +19297,16 @@ packages: fsevents: 2.3.2 dev: false + /playwright@1.43.1: + resolution: {integrity: sha512-V7SoH0ai2kNt1Md9E3Gwas5B9m8KR2GVvwZnAI6Pg0m3sh7UvgiYhRrhsziCmqMJNouPckiOhk8T+9bSAK0VIA==} + engines: {node: '>=16'} + hasBin: true + dependencies: + playwright-core: 1.43.1 + optionalDependencies: + fsevents: 2.3.2 + dev: true + /pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'}