merge base

This commit is contained in:
Vijaykant Yadav 2025-05-19 10:53:23 +05:30
commit 3f201e9a28
227 changed files with 5733 additions and 2493 deletions

View file

@ -13,16 +13,18 @@ jobs:
Cypress-App-Builder: Cypress-App-Builder:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
if: | if: |
contains(github.event.pull_request.labels.*.name, 'run-ce-app-builder') || contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-ce') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-app-builder') || contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-ee') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress') contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-ce')
strategy: strategy:
matrix: matrix:
edition: >- edition: >-
${{ ${{
contains(github.event.pull_request.labels.*.name, 'run-ce-app-builder') && fromJson('["ce"]') || contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-ce') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-app-builder') && fromJson('["ee"]') || contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-ee') && fromJson('["ee"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') || contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') ||
fromJson('[]') fromJson('[]')
}} }}
@ -158,35 +160,35 @@ jobs:
name: screenshots-appbuilder-${{ matrix.edition }} name: screenshots-appbuilder-${{ matrix.edition }}
path: cypress-tests/cypress/screenshots path: cypress-tests/cypress/screenshots
Cypress-App-builder-Subpath: # Cypress-App-builder-Subpath:
runs-on: ubuntu-22.04 # runs-on: ubuntu-22.04
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || # if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-subpath') # contains(github.event.pull_request.labels.*.name, 'run-cypress-app-builder-subpath')
steps: # steps:
- name: Checkout # - name: Checkout
uses: actions/checkout@v3 # uses: actions/checkout@v3
with: # with:
ref: ${{ github.event.pull_request.head.ref }} # ref: ${{ github.event.pull_request.head.ref }}
- name: Create Cypress environment file # - name: Create Cypress environment file
id: create-json # id: create-json
uses: jsdaniell/create-json@1.1.2 # uses: jsdaniell/create-json@1.1.2
with: # with:
name: "cypress.env.json" # name: "cypress.env.json"
json: ${{ secrets.CYPRESS_SECRETS }} # json: ${{ secrets.CYPRESS_SECRETS }}
dir: "./cypress-tests" # dir: "./cypress-tests"
- name: App Builder subpath # - name: App Builder subpath
uses: cypress-io/github-action@v5 # uses: cypress-io/github-action@v5
with: # with:
working-directory: ./cypress-tests # working-directory: ./cypress-tests
config: "baseUrl=http://localhost:80/apps/tooljet/" # config: "baseUrl=http://localhost:80/apps/tooljet/"
config-file: cypress-app-builder.config.js # config-file: cypress-app-builder.config.js
- name: Capture Screenshots # - name: Capture Screenshots
uses: actions/upload-artifact@v4 # uses: actions/upload-artifact@v4
if: always() # if: always()
with: # with:
name: screenshots # name: screenshots
path: cypress-tests/cypress/screenshots # path: cypress-tests/cypress/screenshots

View file

@ -15,16 +15,18 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-marketplace') || contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-ce') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-marketplace') contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-ee') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-ce')
strategy: strategy:
matrix: matrix:
edition: >- edition: >-
${{ ${{
contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') || contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') ||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-marketplace') && fromJson('["ce"]') || contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-marketplace') && fromJson('["ee"]') || contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-ce') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-ee') && fromJson('["ee"]') ||
fromJson('[]') fromJson('[]')
}} }}
@ -159,13 +161,12 @@ jobs:
"password": "password" "password": "password"
}' }'
- name: Create Cypress environment file - name: Create Cypress environment file
id: create-json id: create-json
uses: jsdaniell/create-json@1.1.2 uses: jsdaniell/create-json@1.1.2
with: with:
name: "cypress.env.json" name: "cypress.env.json"
json: ${{ secrets.CYPRESS_SECRETS }} json: ${{ secrets.CYPRESS_SECRETS_MARKETPLACE }}
dir: "./cypress-tests" dir: "./cypress-tests"
- name: Marketplace - name: Marketplace
@ -186,7 +187,7 @@ jobs:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-subpath') contains(github.event.pull_request.labels.*.name, 'run-cypress-marketplace-subpath')
steps: steps:
- name: Checkout - name: Checkout

View file

@ -13,15 +13,18 @@ jobs:
Cypress-Platform: Cypress-Platform:
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
if: contains(github.event.pull_request.labels.*.name, 'run-cypress') || if: contains(github.event.pull_request.labels.*.name, 'run-cypress') ||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-platform') || contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ce') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-platform') contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ee') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-ce')
strategy: strategy:
matrix: matrix:
edition: >- edition: >-
${{ ${{
contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') || contains(github.event.pull_request.labels.*.name, 'run-cypress') && fromJson('["ce", "ee"]') ||
contains(github.event.pull_request.labels.*.name, 'run-ce-cypress-platform') && fromJson('["ce"]') || contains(github.event.pull_request.labels.*.name, 'run-cypress-ce') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-ee-cypress-platform') && fromJson('["ee"]') || contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ce') && fromJson('["ce"]') ||
contains(github.event.pull_request.labels.*.name, 'run-cypress-platform-ee') && fromJson('["ee"]') ||
fromJson('[]') fromJson('[]')
}} }}

View file

@ -80,7 +80,7 @@ jobs:
}, },
{ {
"key": "PG_USER", "key": "PG_USER",
"value": "tooljet" "value": "postgres"
}, },
{ {
"key": "PG_PASS", "key": "PG_PASS",
@ -100,7 +100,7 @@ jobs:
}, },
{ {
"key": "TOOLJET_DB_USER", "key": "TOOLJET_DB_USER",
"value": "tooljet" "value": "postgres"
}, },
{ {
"key": "TOOLJET_DB_PASS", "key": "TOOLJET_DB_PASS",
@ -116,7 +116,7 @@ jobs:
}, },
{ {
"key": "PGRST_DB_URI", "key": "PGRST_DB_URI",
"value": "postgres://tooljet:postgres@localhost/${{ env.PR_NUMBER }}-ce-tjdb" "value": "postgres://postgres:postgres@localhost/${{ env.PR_NUMBER }}-ce-tjdb"
}, },
{ {
"key": "PGRST_HOST", "key": "PGRST_HOST",
@ -168,7 +168,11 @@ jobs:
} }
], ],
"serviceDetails": { "serviceDetails": {
"disk": null, "disk": {
"name": "tooljet-ce-pr-${{ env.PR_NUMBER }}-postgresql",
"mountPath": "/var/lib/postgresql/13/main",
"sizeGB": 10
},
"env": "docker", "env": "docker",
"envSpecificDetails": { "envSpecificDetails": {
"dockerCommand": "", "dockerCommand": "",
@ -279,35 +283,35 @@ jobs:
console.log(e) console.log(e)
} }
- name: Install PostgreSQL client # - name: Install PostgreSQL client
run: | # run: |
sudo apt update # sudo apt update
sudo apt install postgresql-client -y # sudo apt install postgresql-client -y
- name: Wait after installing PostgreSQL # - name: Wait after installing PostgreSQL
run: sleep 25 # run: sleep 25
- name: Drop PostgreSQL PR databases # - name: Drop PostgreSQL PR databases
env: # env:
PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} # PGHOST: ${{ secrets.RENDER_DS_PG_HOST }}
PGPORT: 5432 # PGPORT: 5432
PGUSER: ${{ secrets.RENDER_DS_PG_USER }} # PGUSER: ${{ secrets.RENDER_DS_PG_USER }}
PGDATABASE: ${{ env.PR_NUMBER }}-ce # PGDATABASE: ${{ env.PR_NUMBER }}-ce
PGTJBDATABASE: ${{ env.PR_NUMBER }}-ce-tjdb # PGTJBDATABASE: ${{ env.PR_NUMBER }}-ce-tjdb
run: | # run: |
if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then # if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then
echo "Database $PGDATABASE exists, deleting..." # echo "Database $PGDATABASE exists, deleting..."
PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;" # PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;"
else # else
echo "Database $PGDATABASE does not exist." # echo "Database $PGDATABASE does not exist."
fi # fi
if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then # if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then
echo "Database $PGTJBDATABASE exists, deleting..." # echo "Database $PGTJBDATABASE exists, deleting..."
PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;" # PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;"
else # else
echo "Database $PGTJBDATABASE does not exist." # echo "Database $PGTJBDATABASE does not exist."
fi # fi
suspend-ce-review-app: suspend-ce-review-app:
if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-ce-review-app' }} if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-ce-review-app' }}
@ -317,7 +321,7 @@ jobs:
- name: Suspend service - name: Suspend service
run: | run: |
export SERVICE_ID=$(curl --request GET \ export SERVICE_ID=$(curl --request GET \
--url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --url 'https://api.render.com/v1/services?name=ToolJet%20CE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--header 'accept: application/json' \ --header 'accept: application/json' \
--header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \
jq -r '.[0].service.id') jq -r '.[0].service.id')
@ -349,7 +353,7 @@ jobs:
- name: Resume service - name: Resume service
run: | run: |
export SERVICE_ID=$(curl --request GET \ export SERVICE_ID=$(curl --request GET \
--url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --url 'https://api.render.com/v1/services?name=ToolJet%20CE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--header 'accept: application/json' \ --header 'accept: application/json' \
--header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \
jq -r '.[0].service.id') jq -r '.[0].service.id')
@ -389,6 +393,39 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Sync repo
uses: actions/checkout@v3
- name: Check if Forked Repository
id: check_repo
run: |
if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
echo "is_fork=true" >> $GITHUB_ENV
echo "FORKED_OWNER=${{ github.event.pull_request.head.repo.owner.login }}" >> $GITHUB_ENV
else
echo "is_fork=false" >> $GITHUB_ENV
fi
- name: Set Repository URL
run: |
if [[ "$is_fork" == "true" ]]; then
echo "REPO_URL=https://github.com/${FORKED_OWNER}/ToolJet" >> $GITHUB_ENV
else
echo "REPO_URL=https://github.com/ToolJet/ToolJet" >> $GITHUB_ENV
fi
- name: Fetch and Checkout Forked Branch
if: env.is_fork == 'true'
run: |
git fetch origin pull/${{ github.event.number }}/head:${{ env.BRANCH_NAME }}
git checkout ${{ env.BRANCH_NAME }}
- name: Checkout Default Branch
if: env.is_fork == 'false'
uses: actions/checkout@v3
- name: Creating deployment for Enterprise Edition - name: Creating deployment for Enterprise Edition
id: create-ee-deployment id: create-ee-deployment
run: | run: |
@ -404,7 +441,7 @@ jobs:
"name": "ToolJet EE PR #${{ env.PR_NUMBER }}", "name": "ToolJet EE PR #${{ env.PR_NUMBER }}",
"notifyOnFail": "default", "notifyOnFail": "default",
"ownerId": "tea-caeo4bj19n072h3dddc0", "ownerId": "tea-caeo4bj19n072h3dddc0",
"repo": "https://github.com/ToolJet/ToolJet", "repo": "'"$REPO_URL"'",
"slug": "tooljet-ee-pr-${{ env.PR_NUMBER }}", "slug": "tooljet-ee-pr-${{ env.PR_NUMBER }}",
"suspended": "not_suspended", "suspended": "not_suspended",
"suspenders": [], "suspenders": [],
@ -420,7 +457,7 @@ jobs:
}, },
{ {
"key": "PG_USER", "key": "PG_USER",
"value": "tooljet" "value": "postgres"
}, },
{ {
"key": "PG_PASS", "key": "PG_PASS",
@ -440,7 +477,7 @@ jobs:
}, },
{ {
"key": "TOOLJET_DB_USER", "key": "TOOLJET_DB_USER",
"value": "tooljet" "value": "postgres"
}, },
{ {
"key": "TOOLJET_DB_PASS", "key": "TOOLJET_DB_PASS",
@ -456,7 +493,7 @@ jobs:
}, },
{ {
"key": "PGRST_DB_URI", "key": "PGRST_DB_URI",
"value": "postgres://tooljet:postgres@localhost/${{ env.PR_NUMBER }}-ee-tjdb" "value": "postgres://postgres:postgres@localhost/${{ env.PR_NUMBER }}-ee-tjdb"
}, },
{ {
"key": "PGRST_HOST", "key": "PGRST_HOST",
@ -536,7 +573,11 @@ jobs:
} }
], ],
"serviceDetails": { "serviceDetails": {
"disk": null, "disk": {
"name": "tooljet-ee-pr-${{ env.PR_NUMBER }}-postgresql",
"mountPath": "/var/lib/postgresql/13/main",
"sizeGB": 10
},
"env": "docker", "env": "docker",
"envSpecificDetails": { "envSpecificDetails": {
"dockerCommand": "", "dockerCommand": "",
@ -549,7 +590,7 @@ jobs:
"port": 80, "port": 80,
"protocol": "TCP" "protocol": "TCP"
}], }],
"plan": "starter", "plan": "standard",
"pullRequestPreviewsEnabled": "no", "pullRequestPreviewsEnabled": "no",
"region": "oregon", "region": "oregon",
"url": "https://tooljet-ee-pr-${{ env.PR_NUMBER }}.onrender.com" "url": "https://tooljet-ee-pr-${{ env.PR_NUMBER }}.onrender.com"
@ -647,35 +688,35 @@ jobs:
console.log(e) console.log(e)
} }
- name: Install PostgreSQL client # - name: Install PostgreSQL client
run: | # run: |
sudo apt update # sudo apt update
sudo apt install postgresql-client -y # sudo apt install postgresql-client -y
- name: Wait after installing PostgreSQL # - name: Wait after installing PostgreSQL
run: sleep 25 # run: sleep 25
- name: Drop PostgreSQL PR databases # - name: Drop PostgreSQL PR databases
env: # env:
PGHOST: ${{ secrets.RENDER_DS_PG_HOST }} # PGHOST: ${{ secrets.RENDER_DS_PG_HOST }}
PGPORT: 5432 # PGPORT: 5432
PGUSER: ${{ secrets.RENDER_DS_PG_USER }} # PGUSER: ${{ secrets.RENDER_DS_PG_USER }}
PGDATABASE: ${{ env.PR_NUMBER }}-ee # PGDATABASE: ${{ env.PR_NUMBER }}-ee
PGTJBDATABASE: ${{ env.PR_NUMBER }}-ee-tjdb # PGTJBDATABASE: ${{ env.PR_NUMBER }}-ee-tjdb
run: | # run: |
if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then # if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGDATABASE; then
echo "Database $PGDATABASE exists, deleting..." # echo "Database $PGDATABASE exists, deleting..."
PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;" # PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGDATABASE\" ;"
else # else
echo "Database $PGDATABASE does not exist." # echo "Database $PGDATABASE does not exist."
fi # fi
if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then # if PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -lqt | cut -d \| -f 1 | grep -qw $PGTJBDATABASE; then
echo "Database $PGTJBDATABASE exists, deleting..." # echo "Database $PGTJBDATABASE exists, deleting..."
PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;" # PGPASSWORD=${{ secrets.RENDER_DS_PG_PASS }} psql -h $PGHOST -p $PGPORT -U $PGUSER -d postgres -c "drop database \"$PGTJBDATABASE\" ;"
else # else
echo "Database $PGTJBDATABASE does not exist." # echo "Database $PGTJBDATABASE does not exist."
fi # fi
suspend-ee-review-app: suspend-ee-review-app:
if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-ee-review-app' }} if: ${{ github.event.action == 'labeled' && github.event.label.name == 'suspend-ee-review-app' }}
@ -685,7 +726,7 @@ jobs:
- name: Suspend service - name: Suspend service
run: | run: |
export SERVICE_ID=$(curl --request GET \ export SERVICE_ID=$(curl --request GET \
--url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --url 'https://api.render.com/v1/services?name=ToolJet%20EE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--header 'accept: application/json' \ --header 'accept: application/json' \
--header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \
jq -r '.[0].service.id') jq -r '.[0].service.id')
@ -717,7 +758,7 @@ jobs:
- name: Resume service - name: Resume service
run: | run: |
export SERVICE_ID=$(curl --request GET \ export SERVICE_ID=$(curl --request GET \
--url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \ --url 'https://api.render.com/v1/services?name=ToolJet%20EE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--header 'accept: application/json' \ --header 'accept: application/json' \
--header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \ --header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \
jq -r '.[0].service.id') jq -r '.[0].service.id')

View file

@ -1 +1 @@
3.11.0 3.12.1

View file

@ -39,11 +39,11 @@ module.exports = defineConfig({
chromeWebSecurity: false, chromeWebSecurity: false,
trashAssetsBeforeRuns: true, trashAssetsBeforeRuns: true,
e2e: { e2e: {
setupNodeEvents(on, config) { setupNodeEvents (on, config) {
config.baseUrl = environment.baseUrl; config.baseUrl = environment.baseUrl;
on("task", { on("task", {
readPdf(pathToPdf) { readPdf (pathToPdf) {
return new Promise((resolve) => { return new Promise((resolve) => {
const pdfPath = path.resolve(pathToPdf); const pdfPath = path.resolve(pathToPdf);
let dataBuffer = fs.readFileSync(pdfPath); let dataBuffer = fs.readFileSync(pdfPath);
@ -55,7 +55,7 @@ module.exports = defineConfig({
}); });
on("task", { on("task", {
readXlsx(filePath) { readXlsx (filePath) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
try { try {
let dataBuffer = fs.readFileSync(filePath); let dataBuffer = fs.readFileSync(filePath);
@ -69,7 +69,7 @@ module.exports = defineConfig({
}); });
on("task", { on("task", {
deleteFolder(folderName) { deleteFolder (folderName) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => { rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => {
if (err) { if (err) {
@ -83,7 +83,7 @@ module.exports = defineConfig({
}); });
on("task", { on("task", {
dbConnection({ dbconfig, sql }) { dbConnection ({ dbconfig, sql }) {
const client = new pg.Pool(dbconfig); const client = new pg.Pool(dbconfig);
return client.query(sql); return client.query(sql);
}, },
@ -97,9 +97,9 @@ module.exports = defineConfig({
baseUrl: environment.baseUrl, baseUrl: environment.baseUrl,
configFile: environment.configFile, configFile: environment.configFile,
specPattern: [ specPattern: [
"cypress/e2e/happyPath/platform/ceTestcases/userFlow/firstUserOnboarding.cy.js", "cypress/e2e/happyPath/platform/firstUser/firstUserOnboarding.cy.js",
"cypress/e2e/happyPath/platform/commonTestcases/workspace/dashboard.cy.js", "cypress/e2e/happyPath/platform/ceTestcases/apps/appSlug.cy.js",
"cypress/e2e/happyPath/platform/ceTestcases/!(userFlow)/**/*.cy.js", "cypress/e2e/happyPath/platform/ceTestcases/**/!(*appSlug).cy.js",
"cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js", "cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js",
], ],
numTestsKeptInMemory: 1, numTestsKeptInMemory: 1,

View file

@ -689,7 +689,7 @@ Cypress.Commands.add(
name: dataSourceName, name: dataSourceName,
options: [ options: [
{ key: "connection_type", value: "manual", encrypted: false }, { key: "connection_type", value: "manual", encrypted: false },
{ key: "host", value: "35.238.9.114" }, { key: "host", value: "9.234.17.31" },
{ key: "port", value: 5432 }, { key: "port", value: 5432 },
{ key: "database", value: "student" }, { key: "database", value: "student" },
{ key: "username", value: "postgres" }, { key: "username", value: "postgres" },

View file

@ -6,6 +6,7 @@ import { passwordInputText } from "Texts/passwordInput";
import { importSelectors } from "Selectors/exportImport"; import { importSelectors } from "Selectors/exportImport";
import { importText } from "Texts/exportImport"; import { importText } from "Texts/exportImport";
import { onboardingSelectors } from "Selectors/onboarding"; import { onboardingSelectors } from "Selectors/onboarding";
import { selectAppCardOption } from "Support/utils/common";
const API_ENDPOINT = const API_ENDPOINT =
Cypress.env("environment") === "Community" Cypress.env("environment") === "Community"
@ -160,13 +161,15 @@ Cypress.Commands.add(
Cypress.Commands.add("deleteApp", (appName) => { Cypress.Commands.add("deleteApp", (appName) => {
cy.intercept("DELETE", "/api/apps/*").as("appDeleted"); cy.intercept("DELETE", "/api/apps/*").as("appDeleted");
cy.get(commonSelectors.appCard(appName)) selectAppCardOption(
.realHover() appName,
.find(commonSelectors.appCardOptionsButton) commonSelectors.appCardOptions(commonText.deleteAppOption)
.realHover() );
.click();
cy.get(commonSelectors.deleteAppOption).click();
cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click(); cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
);
cy.wait("@appDeleted"); cy.wait("@appDeleted");
}); });
@ -394,39 +397,37 @@ Cypress.Commands.add("getPosition", (componentName) => {
}); });
Cypress.Commands.add("defaultWorkspaceLogin", () => { Cypress.Commands.add("defaultWorkspaceLogin", () => {
cy.apiLogin(); cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `
SELECT id FROM organizations WHERE name = 'My workspace';`,
}).then((resp) => {
const workspaceId = resp.rows[0].id;
// cy.intercept("GET", API_ENDPOINT).as("library_apps"); cy.apiLogin(
cy.visit("/my-workspace"); "dev@tooljet.io",
cy.wait(2000) "password",
cy.get(commonSelectors.homePageLogo, { timeout: 10000 }); workspaceId,
// cy.wait("@library_apps"); "/my-workspace"
).then(() => {
cy.visit("/");
cy.wait(2000);
cy.get(commonSelectors.homePageLogo, { timeout: 10000 });
});
});
}); });
Cypress.Commands.add( Cypress.Commands.add("visitSlug", ({ actualUrl }) => {
"visitSlug", cy.visit(actualUrl);
({ cy.wait(1000);
actualUrl,
errorUrls = [ cy.url().then((currentUrl) => {
`${Cypress.config("baseUrl")}/error/unknown`, if (currentUrl !== actualUrl) {
`${Cypress.config("baseUrl")}/error/restricted`, cy.visit(actualUrl);
], cy.wait(1000);
}) => {
if (!actualUrl) {
throw new Error("actualUrl is required for visitSlug command.");
} }
});
cy.visit(actualUrl); });
cy.url().then((url) => {
if (errorUrls.includes(url)) {
cy.log(`Navigation resulted in error URL: ${url}. Retrying...`);
cy.visit(actualUrl);
cy.wait(1000);
}
});
}
);
Cypress.Commands.add("releaseApp", () => { Cypress.Commands.add("releaseApp", () => {
@ -513,13 +514,58 @@ Cypress.Commands.overwrite(
} }
); );
Cypress.Commands.add("installMarketplacePlugin", (pluginName) => {
const MARKETPLACE_URL = `${Cypress.config("baseUrl")}/integrations/marketplace`;
cy.visit(MARKETPLACE_URL);
cy.wait(1000);
cy.get('[data-cy="-list-item"]').eq(0).click();
cy.wait(1000);
cy.get("body").then(($body) => {
if ($body.find(".plugins-card").length === 0) {
cy.log("No plugins found, proceeding to install...");
installPlugin(pluginName);
} else {
cy.get(".plugins-card").then(($cards) => {
const isInstalled = $cards.toArray().some((card) => {
return (
Cypress.$(card)
.find(".font-weight-medium.text-capitalize")
.text()
.trim() === pluginName
);
});
if (isInstalled) {
cy.log(`${pluginName} is already installed. Skipping installation.`);
cy.get(commonSelectors.globalDataSourceIcon).click();
} else {
installPlugin(pluginName);
cy.get(commonSelectors.globalDataSourceIcon).click();
}
});
}
});
function installPlugin (pluginName) {
cy.get('[data-cy="-list-item"]').eq(1).click();
cy.wait(1000);
cy.contains(".plugins-card", pluginName).within(() => {
cy.get(".marketplace-install").click();
cy.wait(1000);
});
}
});
Cypress.Commands.add("verifyElement", (selector, text, eqValue) => { Cypress.Commands.add("verifyElement", (selector, text, eqValue) => {
const element = const element =
eqValue !== undefined ? cy.get(selector).eq(eqValue) : cy.get(selector); eqValue !== undefined ? cy.get(selector).eq(eqValue) : cy.get(selector);
element.should("be.visible").and("have.text", text); element.should("be.visible").and("have.text", text);
}); });
Cypress.Commands.add("getAppId", (appName) => { Cypress.Commands.add("getAppId", (appName) => {
cy.task("dbConnection", { cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"), dbconfig: Cypress.env("app_db"),
@ -529,3 +575,33 @@ Cypress.Commands.add("getAppId", (appName) => {
return appId; return appId;
}); });
}); });
Cypress.Commands.add("uninstallMarketplacePlugin", (pluginName) => {
const MARKETPLACE_URL = `${Cypress.config("baseUrl")}/integrations/marketplace`;
cy.visit(MARKETPLACE_URL);
cy.wait(1000);
cy.get('[data-cy="-list-item"]').eq(0).click();
cy.wait(1000);
cy.get(".plugins-card").each(($card) => {
cy.wrap($card)
.find(".font-weight-medium.text-capitalize")
.invoke("text")
.then((text) => {
if (text.trim() === pluginName) {
cy.wrap($card).find(".link-primary").contains("Remove").click();
cy.wait(1000);
cy.get('[data-cy="delete-plugin-title"]').should("be.visible");
cy.get('[data-cy="yes-button"]').click();
cy.wait(2000);
cy.log(`${pluginName} has been successfully uninstalled.`);
} else {
cy.log(`${pluginName} is not installed. Skipping uninstallation.`);
}
});
});
});

View file

@ -6,7 +6,8 @@ export const commonSelectors = {
toastMessage: ".go3958317564", toastMessage: ".go3958317564",
oldToastMessage: ".go318386747", oldToastMessage: ".go318386747",
appSlugAccept: '[data-cy="app-slug-accepted-label"]', appSlugAccept: '[data-cy="app-slug-accepted-label"]',
newToastMessage: '.drawer-container > [style="position: fixed; z-index: 9999; inset: 16px; pointer-events: none;"] > .go4109123758 > .go2072408551 > .go3958317564', newToastMessage:
'.drawer-container > [style="position: fixed; z-index: 9999; inset: 16px; pointer-events: none;"] > .go4109123758 > .go2072408551 > .go3958317564',
toastCloseButton: '[data-cy="toast-close-button"]', toastCloseButton: '[data-cy="toast-close-button"]',
editButton: "[data-cy=edit-button]", editButton: "[data-cy=edit-button]",
workspaceConstantNameInput: '[data-cy="name-input-field"]', workspaceConstantNameInput: '[data-cy="name-input-field"]',
@ -18,7 +19,7 @@ export const commonSelectors = {
appCardOptionsButton: "[data-cy=app-card-menu-icon]", appCardOptionsButton: "[data-cy=app-card-menu-icon]",
autoSave: "[data-cy=autosave-indicator]", autoSave: "[data-cy=autosave-indicator]",
nameInputFieldd: "[data-cy=name-input-field]", nameInputFieldd: "[data-cy=name-input-field]",
valueInputFieldd: '[data-cy=value-input-field]', valueInputFieldd: "[data-cy=value-input-field]",
skipButton: ".driver-close-btn", skipButton: ".driver-close-btn",
skipInstallationModal: "[data-cy=skip-button]", skipInstallationModal: "[data-cy=skip-button]",
homePageLogo: "[data-cy=home-page-logo]", homePageLogo: "[data-cy=home-page-logo]",
@ -395,7 +396,7 @@ export const commonWidgetSelector = {
modalCloseButton: '[data-cy="modal-close-button"]', modalCloseButton: '[data-cy="modal-close-button"]',
iframeLinkLabel: '[data-cy="iframe-link-label"]', iframeLinkLabel: '[data-cy="iframe-link-label"]',
ifameLinkCopyButton: '[data-cy="iframe-link-copy-button"]', ifameLinkCopyButton: '[data-cy="iframe-link-copy-button"]',
appSlugLabel: '[data-cy="input-field-label"]', appSlugLabel: '[data-cy="unique-app-slug-field-label"]',
appSlugInput: '[data-cy="app-slug-input-field"]', appSlugInput: '[data-cy="app-slug-input-field"]',
appSlugInfoLabel: '[data-cy="helper-text"]', appSlugInfoLabel: '[data-cy="helper-text"]',
appLinkLabel: '[data-cy="app-link-label"]', appLinkLabel: '[data-cy="app-link-label"]',

View file

@ -24,7 +24,7 @@ export const restAPISelector = {
return `[data-cy="${cyParamName(header)}-delete-button-${cyParamName(index)}"]`; return `[data-cy="${cyParamName(header)}-delete-button-${cyParamName(index)}"]`;
}, },
addMoreButton: (header) => { addMoreButton: (header) => {
return `[data-cy="${cyParamName(header)}-add-more-button"]`; return `[data-cy="${cyParamName(header)}-add-button"]`;
}, },
dropdownLabel: (label) => { dropdownLabel: (label) => {
return `[data-cy="${cyParamName(label)}-dropdown-label"]`; return `[data-cy="${cyParamName(label)}-dropdown-label"]`;

View file

@ -4,8 +4,8 @@ export const postgreSqlText = {
allDataSources: () => { allDataSources: () => {
return Cypress.env("marketplace_action") return Cypress.env("marketplace_action")
? "All data sources (44)" ? "All data sources (45)"
: "All data sources (45)"; : "All data sources (43)";
}, },
commonlyUsed: "Commonly used (5)", commonlyUsed: "Commonly used (5)",
allDatabase: () => { allDatabase: () => {

View file

@ -8,11 +8,7 @@ export const editVersionText = {
export const deleteVersionText = { export const deleteVersionText = {
deleteModalText: (text) => { deleteModalText: (text) => {
// return `Are you sure you want to delete this version - ${cyParamName( return `Are you sure you want to delete this version - ${cyParamName(
// text
// )}?`;
return `Deleting a version will permanently remove it from all environments.Are you sure you want to delete this version - ${cyParamName(
text text
)}?`; )}?`;
}, },

View file

@ -32,7 +32,7 @@ import {
addSupportCSAData, addSupportCSAData,
} from "Support/utils/events"; } from "Support/utils/events";
describe("Editor- Test Button widget", () => { describe("Editor- Test Button widget ", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.apiCreateApp(`${fake.companyName}-button-App`); cy.apiCreateApp(`${fake.companyName}-button-App`);

View file

@ -66,7 +66,7 @@ describe("Add all Data sources to app", () => {
cy.apiLogin(); cy.apiLogin();
}); });
it("Should verify global data source page", () => { it.skip("Should verify global data source page", () => {
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug);
cy.visit(`${data.workspaceSlug}`); cy.visit(`${data.workspaceSlug}`);
@ -87,7 +87,7 @@ describe("Add all Data sources to app", () => {
); );
}); });
it("Should add all data sources in data source page", () => { it.skip("Should add all data sources in data source page", () => {
cy.visit(`${data.workspaceSlug}`); cy.visit(`${data.workspaceSlug}`);
dataSources.forEach((dsName) => { dataSources.forEach((dsName) => {
@ -109,7 +109,7 @@ describe("Add all Data sources to app", () => {
}); });
}); });
it("Should add all data sources in the app", () => { it.skip("Should add all data sources in the app", () => {
cy.visit(`${data.workspaceSlug}`); cy.visit(`${data.workspaceSlug}`);
cy.get(commonSelectors.dashboardIcon).click(); cy.get(commonSelectors.dashboardIcon).click();
cy.get(commonSelectors.appCreateButton).click(); cy.get(commonSelectors.appCreateButton).click();
@ -135,7 +135,7 @@ describe("Add all Data sources to app", () => {
}); });
}); });
it("Should install all makretplace plugins and add them into the app", () => { it.skip("Should install all makretplace plugins and add them into the app", () => {
cy.visit(`${data.workspaceSlug}`); cy.visit(`${data.workspaceSlug}`);
const dataSourcesMarketplace = [ const dataSourcesMarketplace = [
"Plivo", "Plivo",
@ -189,12 +189,15 @@ describe("Add all Data sources to app", () => {
cy.wrap(dataSourcesMarketplace).each((dsName) => { cy.wrap(dataSourcesMarketplace).each((dsName) => {
cy.get(commonSelectors.globalDataSourceIcon).click(); cy.get(commonSelectors.globalDataSourceIcon).click();
selectAndAddDataSource("databases", dsName, dsName); selectAndAddDataSource("databases", dsName, dsName);
cy.wait(500); cy.wait(1000);
}); });
cy.get(commonSelectors.dashboardIcon).click(); cy.get(commonSelectors.dashboardIcon).should("be.visible").click();
cy.get(commonSelectors.appCreateButton).click(); cy.get(commonSelectors.appCreateButton).should("be.visible").click();
cy.get(commonSelectors.appNameInput).click().type(data.dsNamefake1); cy.get(commonSelectors.appNameInput)
.should("be.visible")
.click()
.type(data.dsNamefake1);
cy.get(commonSelectors.createAppButton).click(); cy.get(commonSelectors.createAppButton).click();
cy.skipWalkthrough(); cy.skipWalkthrough();
@ -203,7 +206,7 @@ describe("Add all Data sources to app", () => {
cy.get(".css-4e90k9").type( cy.get(".css-4e90k9").type(
`cypress-${cyParamName(dsName)}-${cyParamName(dsName)}` `cypress-${cyParamName(dsName)}-${cyParamName(dsName)}`
); );
cy.wait(500); cy.wait(1000);
cy.contains( cy.contains(
`[id*="react-select-"]`, `[id*="react-select-"]`,
@ -212,7 +215,7 @@ describe("Add all Data sources to app", () => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wait(500); cy.wait(1000);
}); });
}); });
}); });

View file

@ -19,13 +19,14 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {}; const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
data.dsName1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); data.queryName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source Airtable", () => { describe("Data source Airtable", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on connection AirTable form", () => { it("Should verify elements on connection AirTable form", () => {
@ -199,7 +200,7 @@ describe("Data source Airtable", () => {
cy.get('[data-cy="show-ds-popover-button"]').click(); cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(`${data.dsName}`); cy.get(".css-4e90k9").type(`${data.dsName}`);
cy.contains(`[id*="react-select-"]`, data.dsName).click(); cy.contains(`[id*="react-select-"]`, data.dsName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName1); cy.get('[data-cy="query-rename-input"]').clear().type(data.queryName);
cy.get(airTableSelector.operationSelectDropdown) cy.get(airTableSelector.operationSelectDropdown)
.click() .click()
@ -225,7 +226,7 @@ describe("Data source Airtable", () => {
cy.get(dataSourceSelector.queryPreviewButton).click(); cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage( cy.verifyToastMessage(
commonSelectors.toastMessage, commonSelectors.toastMessage,
`Query (${data.dsName1}) completed.` `Query (${data.queryName}) completed.`
); );
// Verify Delete record operation // Verify Delete record operation
@ -277,7 +278,7 @@ describe("Data source Airtable", () => {
cy.get(dataSourceSelector.queryPreviewButton).click(); cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage( cy.verifyToastMessage(
commonSelectors.toastMessage, commonSelectors.toastMessage,
`Query (${data.dsName1}) completed.` `Query (${data.queryName}) completed.`
); );
deleteAppandDatasourceAfterExecution( deleteAppandDatasourceAfterExecution(
data.dsName, data.dsName,

View file

@ -20,15 +20,15 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {}; const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source amazon athena", () => { describe("Data source amazon athena", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on amazon athena connection form", () => { it.skip("Should verify elements on amazon athena connection form", () => {
const Accesskey = Cypress.env("amazonathena_accessKey"); const Accesskey = Cypress.env("amazonathena_accessKey");
const Secretkey = Cypress.env("amazonathena_secretKey"); const Secretkey = Cypress.env("amazonathena_secretKey");
const DbName = Cypress.env("amazonathena_DbName"); const DbName = Cypress.env("amazonathena_DbName");
@ -97,7 +97,7 @@ describe("Data source amazon athena", () => {
deleteDatasource(`cypress-${data.dsName}-Amazon-Athena`); deleteDatasource(`cypress-${data.dsName}-Amazon-Athena`);
}); });
it("Should verify the functionality of amazon athena connection form.", () => { it.skip("Should verify the functionality of amazon athena connection form.", () => {
const Accesskey = Cypress.env("amazonathena_accessKey"); const Accesskey = Cypress.env("amazonathena_accessKey");
const Secretkey = Cypress.env("amazonathena_secretKey"); const Secretkey = Cypress.env("amazonathena_secretKey");
const DbName = Cypress.env("amazonathena_DbName"); const DbName = Cypress.env("amazonathena_DbName");
@ -134,7 +134,7 @@ describe("Data source amazon athena", () => {
deleteDatasource(`cypress-${data.dsName}-amazon-Athena`); deleteDatasource(`cypress-${data.dsName}-amazon-Athena`);
}); });
it("Should able to run the query with valid conection", () => { it.skip("Should able to run the query with valid conection", () => {
const Accesskey = Cypress.env("amazonathena_accessKey"); const Accesskey = Cypress.env("amazonathena_accessKey");
const Secretkey = Cypress.env("amazonathena_secretKey"); const Secretkey = Cypress.env("amazonathena_secretKey");
const DbName = Cypress.env("amazonathena_DbName"); const DbName = Cypress.env("amazonathena_DbName");
@ -188,11 +188,13 @@ describe("Data source amazon athena", () => {
cy.get(".css-4e90k9").type(`${data.dsName}`); cy.get(".css-4e90k9").type(`${data.dsName}`);
cy.contains(`[id*="react-select-"]`, data.dsName).click(); cy.contains(`[id*="react-select-"]`, data.dsName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName); cy.get('[data-cy="query-rename-input"]').clear().type(data.dsName);
cy.get(`[data-cy="list-query-${data.dsName}"]`).should("be.visible");
cy.get('[data-cy="query-input-field"]').clearAndTypeOnCodeMirror( cy.get('[data-cy="query-input-field"]').clearAndTypeOnCodeMirror(
"SHOW DATABASES;" "SHOW DATABASES;"
); );
cy.get(
'[data-cy="query-input-field"] >>> .cm-editor >> .cm-content > .cm-line'
).should("have.text", "SHOW DATABASES;");
cy.get(dataSourceSelector.queryPreviewButton).click(); cy.get(dataSourceSelector.queryPreviewButton).click();
cy.verifyToastMessage( cy.verifyToastMessage(
commonSelectors.toastMessage, commonSelectors.toastMessage,

View file

@ -20,15 +20,15 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {}; const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source amazon ses", () => { describe("Data source amazon ses", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on amazonses connection form", () => { it.skip("Should verify elements on amazonses connection form", () => {
const Accesskey = Cypress.env("amazonSes_accessKey"); const Accesskey = Cypress.env("amazonSes_accessKey");
const Secretkey = Cypress.env("amazonSes_secretKey"); const Secretkey = Cypress.env("amazonSes_secretKey");
@ -80,7 +80,7 @@ describe("Data source amazon ses", () => {
deleteDatasource(`cypress-${data.dsName}-Amazon-ses`); deleteDatasource(`cypress-${data.dsName}-Amazon-ses`);
}); });
it("Should verify the functionality of amazonses connection form.", () => { it.skip("Should verify the functionality of amazonses connection form.", () => {
selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName); selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName);
cy.get(".react-select__dropdown-indicator").eq(1).click(); cy.get(".react-select__dropdown-indicator").eq(1).click();
@ -112,7 +112,7 @@ describe("Data source amazon ses", () => {
deleteDatasource(`cypress-${data.dsName}-amazon-ses`); deleteDatasource(`cypress-${data.dsName}-amazon-ses`);
}); });
it("Should able to run the query with valid conection", () => { it.skip("Should able to run the query with valid conection", () => {
const email = "adish" + "@" + "tooljet.com"; const email = "adish" + "@" + "tooljet.com";
selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName); selectAndAddDataSource("databases", amazonSesText.AmazonSES, data.dsName);

View file

@ -20,15 +20,15 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {}; const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source AppWrite", () => { describe("Data source AppWrite", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on appwrite connection form", () => { it.skip("Should verify elements on appwrite connection form", () => {
const Host = Cypress.env("appwrite_host"); const Host = Cypress.env("appwrite_host");
const ProjectID = Cypress.env("appwrite_projectID"); const ProjectID = Cypress.env("appwrite_projectID");
const DatabaseID = Cypress.env("appwrite_databaseID"); const DatabaseID = Cypress.env("appwrite_databaseID");
@ -100,7 +100,7 @@ describe("Data source AppWrite", () => {
deleteDatasource(`cypress-${data.dsName}-Appwrite`); deleteDatasource(`cypress-${data.dsName}-Appwrite`);
}); });
it("Should verify the functionality of appwrite connection form.", () => { it.skip("Should verify the functionality of appwrite connection form.", () => {
const Host = Cypress.env("appwrite_host"); const Host = Cypress.env("appwrite_host");
const ProjectID = Cypress.env("appwrite_projectID"); const ProjectID = Cypress.env("appwrite_projectID");
const DatabaseID = Cypress.env("appwrite_databaseID"); const DatabaseID = Cypress.env("appwrite_databaseID");
@ -150,7 +150,7 @@ describe("Data source AppWrite", () => {
deleteDatasource(`cypress-${data.dsName}-Appwrite`); deleteDatasource(`cypress-${data.dsName}-Appwrite`);
}); });
it("Should be able to run the query with a valid connection", () => { it.skip("Should be able to run the query with a valid connection", () => {
const Host = Cypress.env("appwrite_host"); const Host = Cypress.env("appwrite_host");
const ProjectID = Cypress.env("appwrite_projectID"); const ProjectID = Cypress.env("appwrite_projectID");
const DatabaseID = Cypress.env("appwrite_databaseID"); const DatabaseID = Cypress.env("appwrite_databaseID");

View file

@ -20,15 +20,15 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {}; const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source AWS Lambda", () => { describe("Data source AWS Lambda", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on AWS Lambda connection form", () => { it.skip("Should verify elements on AWS Lambda connection form", () => {
const Accesskey = Cypress.env("awslamda_access"); const Accesskey = Cypress.env("awslamda_access");
const Secretkey = Cypress.env("awslamda_secret"); const Secretkey = Cypress.env("awslamda_secret");
@ -80,9 +80,10 @@ describe("Data source AWS Lambda", () => {
); );
deleteDatasource(`cypress-${data.dsName}-aws-lambda`); deleteDatasource(`cypress-${data.dsName}-aws-lambda`);
cy.uninstallMarketplacePlugin("AWS Lambda");
}); });
it("Should verify the functionality of AWS Lambda connection form", () => { it.skip("Should verify the functionality of AWS Lambda connection form", () => {
const Accesskey = Cypress.env("awslamda_access"); const Accesskey = Cypress.env("awslamda_access");
const Secretkey = Cypress.env("awslamda_secret"); const Secretkey = Cypress.env("awslamda_secret");
@ -113,9 +114,10 @@ describe("Data source AWS Lambda", () => {
); );
deleteDatasource(`cypress-${data.dsName}-aws-lambda`); deleteDatasource(`cypress-${data.dsName}-aws-lambda`);
cy.uninstallMarketplacePlugin("AWS Lambda");
}); });
it("Should able to run the query with valid conection", () => { it.skip("Should able to run the query with valid conection", () => {
const Accesskey = Cypress.env("awslamda_access"); const Accesskey = Cypress.env("awslamda_access");
const Secretkey = Cypress.env("awslamda_secret"); const Secretkey = Cypress.env("awslamda_secret");

View file

@ -21,15 +21,15 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {}; const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source AWS Textract", () => { describe("Data source AWS Textract", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on AWS Textract connection form", () => { it.skip("Should verify elements on AWS Textract connection form", () => {
const Accesskey = Cypress.env("awstextract_access"); const Accesskey = Cypress.env("awstextract_access");
const Secretkey = Cypress.env("awstextract_secret"); const Secretkey = Cypress.env("awstextract_secret");
@ -87,7 +87,7 @@ describe("Data source AWS Textract", () => {
deleteDatasource(`cypress-${data.dsName}-aws-textract`); deleteDatasource(`cypress-${data.dsName}-aws-textract`);
}); });
it("Should verify functionality of AWS Textract connection form", () => { it.skip("Should verify functionality of AWS Textract connection form", () => {
const Accesskey = Cypress.env("awstextract_access"); const Accesskey = Cypress.env("awstextract_access");
const Secretkey = Cypress.env("awstextract_secret"); const Secretkey = Cypress.env("awstextract_secret");
@ -122,9 +122,10 @@ describe("Data source AWS Textract", () => {
); );
deleteDatasource(`cypress-${data.dsName}-aws-textract`); deleteDatasource(`cypress-${data.dsName}-aws-textract`);
cy.uninstallMarketplacePlugin("AWS Textract");
}); });
it("Should able to run the query with valid conection", () => { it.skip("Should able to run the query with valid conection", () => {
const Accesskey = Cypress.env("awstextract_access"); const Accesskey = Cypress.env("awstextract_access");
const Secretkey = Cypress.env("awstextract_secret"); const Secretkey = Cypress.env("awstextract_secret");

View file

@ -17,8 +17,8 @@ data.customText = fake.randomSentence;
describe("Data source Azure Blob Storage", () => { describe("Data source Azure Blob Storage", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -20,15 +20,15 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {}; const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source baserow", () => { describe("Data source baserow", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on baserow connection form", () => { it.skip("Should verify elements on baserow connection form", () => {
const Apikey = Cypress.env("baserow_apikey"); const Apikey = Cypress.env("baserow_apikey");
cy.get(commonSelectors.globalDataSourceIcon).click(); cy.get(commonSelectors.globalDataSourceIcon).click();
@ -78,7 +78,7 @@ describe("Data source baserow", () => {
deleteDatasource(`cypress-${data.dsName}-baserow`); deleteDatasource(`cypress-${data.dsName}-baserow`);
}); });
it("Should verify the functionality of baserow connection form.", () => { it.skip("Should verify the functionality of baserow connection form.", () => {
const Apikey = Cypress.env("baserow_apikey"); const Apikey = Cypress.env("baserow_apikey");
selectAndAddDataSource("databases", baseRowText.baserow, data.dsName); selectAndAddDataSource("databases", baseRowText.baserow, data.dsName);
@ -103,7 +103,7 @@ describe("Data source baserow", () => {
deleteDatasource(`cypress-${data.dsName}-baserow`); deleteDatasource(`cypress-${data.dsName}-baserow`);
}); });
it("Should be able to run the query with a valid connection", () => { it.skip("Should be able to run the query with a valid connection", () => {
const baserowTableID = Cypress.env("baserow_tableid"); const baserowTableID = Cypress.env("baserow_tableid");
const baserowRowID = Cypress.env("baserow_rowid"); const baserowRowID = Cypress.env("baserow_rowid");
const Apikey = Cypress.env("baserow_apikey"); const Apikey = Cypress.env("baserow_apikey");

View file

@ -16,8 +16,8 @@ const data = {};
describe("Data source BigQuery", () => { describe("Data source BigQuery", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -19,8 +19,8 @@ const data = {};
describe("Data sources", () => { describe("Data sources", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -19,8 +19,8 @@ const data = {};
describe("Data sources", () => { describe("Data sources", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -21,8 +21,8 @@ const data = {};
describe("Data sources", () => { describe("Data sources", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -19,8 +19,8 @@ const data = {};
describe("Data source DynamoDB", () => { describe("Data source DynamoDB", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -17,9 +17,12 @@ import {
const data = {}; const data = {};
describe("Data source Elasticsearch", () => { describe("Data source Elasticsearch", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.lastName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
data.dataSourceName = fake.lastName
.toLowerCase()
.replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on Elasticsearch connection form", () => { it("Should verify elements on Elasticsearch connection form", () => {
@ -123,14 +126,14 @@ describe("Data source Elasticsearch", () => {
"have.text", "have.text",
elasticsearchText.errorConnectionRefused elasticsearchText.errorConnectionRefused
); );
deleteDatasource(`cypress-${data.lastName}-elasticsearch`); deleteDatasource(`cypress-${data.dataSourceName}-elasticsearch`);
}); });
it("Should verify the functionality of Elasticsearch connection form.", () => { it("Should verify the functionality of Elasticsearch connection form.", () => {
selectAndAddDataSource( selectAndAddDataSource(
"databases", "databases",
elasticsearchText.elasticSearch, elasticsearchText.elasticSearch,
data.lastName data.dataSourceName
); );
fillDataSourceTextField( fillDataSourceTextField(
@ -210,12 +213,12 @@ describe("Data source Elasticsearch", () => {
); );
cy.get( cy.get(
`[data-cy="cypress-${data.lastName}-elasticsearch-button"]` `[data-cy="cypress-${data.dataSourceName}-elasticsearch-button"]`
).verifyVisibleElement( ).verifyVisibleElement(
"have.text", "have.text",
`cypress-${data.lastName}-elasticsearch` `cypress-${data.dataSourceName}-elasticsearch`
); );
deleteDatasource(`cypress-${data.lastName}-elasticsearch`); deleteDatasource(`cypress-${data.dataSourceName}-elasticsearch`);
}); });
}); });

View file

@ -17,8 +17,8 @@ const data = {};
describe("Data source Firestore", () => { describe("Data source Firestore", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -19,12 +19,12 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {}; const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source GraphQL", () => { describe("Data source GraphQL", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on GraphQL connection form", () => { it("Should verify elements on GraphQL connection form", () => {

View file

@ -20,13 +20,13 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {}; const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
data.dsName1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", ""); data.dsName1 = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source HarperDB", () => { describe("Data source HarperDB", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on HarperDB connection form", () => { it("Should verify elements on HarperDB connection form", () => {
@ -102,6 +102,7 @@ describe("Data source HarperDB", () => {
); );
deleteDatasource(`cypress-${data.dsName}-HarperDB`); deleteDatasource(`cypress-${data.dsName}-HarperDB`);
cy.uninstallMarketplacePlugin("HarperDB");
}); });
it("Should verify functionality of HarperDB connection form", () => { it("Should verify functionality of HarperDB connection form", () => {
@ -156,9 +157,10 @@ describe("Data source HarperDB", () => {
); );
deleteDatasource(`cypress-${data.dsName}-HarperDB`); deleteDatasource(`cypress-${data.dsName}-HarperDB`);
cy.uninstallMarketplacePlugin("HarperDB");
}); });
it("Should be able to run the query with a valid connection", () => { it.skip("Should be able to run the query with a valid connection", () => {
const Host = Cypress.env("harperdb_host"); const Host = Cypress.env("harperdb_host");
const Port = Cypress.env("harperdb_port"); const Port = Cypress.env("harperdb_port");
const Username = Cypress.env("harperdb_username"); const Username = Cypress.env("harperdb_username");

View file

@ -23,8 +23,8 @@ const data = {};
describe("Data sources", () => { describe("Data sources", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -19,8 +19,8 @@ const data = {};
describe("Data sources", () => { describe("Data sources", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -20,12 +20,12 @@ import {
import { dataSourceSelector } from "../../../../../constants/selectors/dataSource"; import { dataSourceSelector } from "../../../../../constants/selectors/dataSource";
const data = {}; const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source minio", () => { describe("Data source minio", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on minio connection form", () => { it("Should verify elements on minio connection form", () => {
@ -157,7 +157,7 @@ describe("Data source minio", () => {
deleteDatasource(`cypress-${data.dsName}-minio`); deleteDatasource(`cypress-${data.dsName}-minio`);
}); });
it("Should be able to run the query with a valid connection", () => { it.skip("Should be able to run the query with a valid connection", () => {
const Host = Cypress.env("minio_host"); const Host = Cypress.env("minio_host");
const Port = Cypress.env("minio_port"); const Port = Cypress.env("minio_port");
const AccessKey = Cypress.env("minio_accesskey"); const AccessKey = Cypress.env("minio_accesskey");

View file

@ -27,8 +27,8 @@ const data = {};
describe("Data source MongoDB", () => { describe("Data source MongoDB", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");
@ -133,7 +133,7 @@ describe("Data source MongoDB", () => {
"have.text", "have.text",
mongoDbText.errorConnectionRefused mongoDbText.errorConnectionRefused
); );
cy.get('[data-cy="query-select-dropdown"]').type( cy.get('[data-cy="connection-type-select-dropdown"]').type(
mongoDbText.optionConnectUsingConnectionString mongoDbText.optionConnectUsingConnectionString
); );
cy.get('[data-cy="label-connection-string"]').verifyVisibleElement( cy.get('[data-cy="label-connection-string"]').verifyVisibleElement(

View file

@ -26,8 +26,8 @@ const data = {};
describe("Data sources MySql", () => { describe("Data sources MySql", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -15,7 +15,7 @@ import {
describe("Data sources", () => { describe("Data sources", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
// cy.createApp(); // cy.createApp();
}); });

View file

@ -20,14 +20,14 @@ const data = {};
describe("Data sources", () => { describe("Data sources", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on connection form", () => { it.skip("Should verify elements on connection form", () => {
cy.log(process.env.NODE_ENV); cy.log(process.env.NODE_ENV);
cy.log(postgreSqlText.allDatabase()); cy.log(postgreSqlText.allDatabase());
cy.get(commonSelectors.globalDataSourceIcon).click(); cy.get(commonSelectors.globalDataSourceIcon).click();
@ -140,7 +140,7 @@ describe("Data sources", () => {
deleteDatasource(`cypress-${data.dataSourceName}-postgresql`); deleteDatasource(`cypress-${data.dataSourceName}-postgresql`);
}); });
it("Should verify the functionality of PostgreSQL connection form.", () => { it.skip("Should verify the functionality of PostgreSQL connection form.", () => {
selectAndAddDataSource( selectAndAddDataSource(
"databases", "databases",
postgreSqlText.postgreSQL, postgreSqlText.postgreSQL,

View file

@ -23,7 +23,7 @@ data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source Redis", () => { describe("Data source Redis", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
}); });
it("Should verify elements on connection Redis form", () => { it("Should verify elements on connection Redis form", () => {
@ -215,7 +215,7 @@ describe("Data source Redis", () => {
deleteDatasource(`cypress-${data.dsName}-redis`); deleteDatasource(`cypress-${data.dsName}-redis`);
}); });
it("Should able to run the query with valid conection", () => { it.skip("Should able to run the query with valid conection", () => {
selectAndAddDataSource("databases", redisText.redis, data.dsName); selectAndAddDataSource("databases", redisText.redis, data.dsName);
fillDataSourceTextField( fillDataSourceTextField(

View file

@ -20,7 +20,7 @@ const clientAuthenticationDropdown =
describe("Data source Rest API", () => { describe("Data source Rest API", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");
@ -332,7 +332,6 @@ describe("Data source Rest API", () => {
deleteDatasource(`cypress-${data.dataSourceName}-restapi`); deleteDatasource(`cypress-${data.dataSourceName}-restapi`);
}); });
it("Should verify basic connection for Rest API", () => { it("Should verify basic connection for Rest API", () => {
const storedId = Cypress.env("storedId");
cy.apiCreateGDS( cy.apiCreateGDS(
`${Cypress.env("server_host")}/api/data-sources`, `${Cypress.env("server_host")}/api/data-sources`,
`cypress-${data.dataSourceName}-restapi`, `cypress-${data.dataSourceName}-restapi`,

View file

@ -19,8 +19,8 @@ const data = {};
describe("Data sources", () => { describe("Data sources", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -20,7 +20,7 @@ const data = {};
describe("Data sources AWS S3", () => { describe("Data sources AWS S3", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -15,7 +15,7 @@ import {
describe("Data sources", () => { describe("Data sources", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
// cy.createApp(); // cy.createApp();
}); });

View file

@ -13,8 +13,8 @@ const data = {};
describe("Data source SMTP", () => { describe("Data source SMTP", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -20,8 +20,8 @@ import {
const data = {}; const data = {};
describe("Data sources", () => { describe("Data sources", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -21,8 +21,8 @@ const data = {};
describe("Data sources", () => { describe("Data sources", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -21,15 +21,15 @@ import { dataSourceSelector } from "../../../../../constants/selectors/dataSourc
import { pluginSelectors } from "Selectors/plugins"; import { pluginSelectors } from "Selectors/plugins";
const data = {}; const data = {};
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
describe("Data source Twilio", () => { describe("Data source Twilio", () => {
beforeEach(() => { beforeEach(() => {
cy.apiLogin(); cy.apiLogin();
cy.defaultWorkspaceLogin(); cy.visit("/");
data.dsName = fake.lastName.toLowerCase().replaceAll("[^A-Za-z]", "");
}); });
it("Should verify elements on Twilio connection form", () => { it.skip("Should verify elements on Twilio connection form", () => {
const AuthToken = Cypress.env("twilio_auth_token"); const AuthToken = Cypress.env("twilio_auth_token");
const AccountSID = Cypress.env("twilio_account_SID"); const AccountSID = Cypress.env("twilio_account_SID");
const MessageSID = Cypress.env("twilio_messaging_service_SID"); const MessageSID = Cypress.env("twilio_messaging_service_SID");
@ -89,7 +89,7 @@ describe("Data source Twilio", () => {
deleteDatasource(`cypress-${data.dsName}-twilio`); deleteDatasource(`cypress-${data.dsName}-twilio`);
}); });
it("Should verify functionality of Twilio connection form", () => { it.skip("Should verify functionality of Twilio connection form", () => {
const AuthToken = Cypress.env("twilio_auth_token"); const AuthToken = Cypress.env("twilio_auth_token");
const AccountSID = Cypress.env("twilio_account_SID"); const AccountSID = Cypress.env("twilio_account_SID");
const MessageSID = Cypress.env("twilio_messaging_service_SID"); const MessageSID = Cypress.env("twilio_messaging_service_SID");
@ -128,7 +128,7 @@ describe("Data source Twilio", () => {
deleteDatasource(`cypress-${data.dsName}-twilio`); deleteDatasource(`cypress-${data.dsName}-twilio`);
}); });
it("Should be able to run the query with a valid connection", () => { it.skip("Should be able to run the query with a valid connection", () => {
const AuthToken = Cypress.env("twilio_auth_token"); const AuthToken = Cypress.env("twilio_auth_token");
const AccountSID = Cypress.env("twilio_account_SID"); const AccountSID = Cypress.env("twilio_account_SID");
const MessageSID = Cypress.env("twilio_messaging_service_SID"); const MessageSID = Cypress.env("twilio_messaging_service_SID");

View file

@ -20,7 +20,7 @@ const data = {};
describe("Data sources", () => { describe("Data sources", () => {
beforeEach(() => { beforeEach(() => {
cy.appUILogin(); cy.apiLogin();
data.dataSourceName = fake.lastName data.dataSourceName = fake.lastName
.toLowerCase() .toLowerCase()
.replaceAll("[^A-Za-z]", ""); .replaceAll("[^A-Za-z]", "");

View file

@ -46,9 +46,7 @@ describe("App Export", () => {
}); });
it("Verify the elements of export dialog box", () => { it("Verify the elements of export dialog box", () => {
cy.window({ log: false }).then((win) => { cy.skipWalkthrough()
win.localStorage.setItem("walkthroughCompleted", "true");
});
cy.apiLogin(); cy.apiLogin();
cy.visit(`${data.workspaceSlug}`); cy.visit(`${data.workspaceSlug}`);

View file

@ -34,6 +34,7 @@ describe("App Import Functionality", () => {
cy.apiLogin(); cy.apiLogin();
cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug); cy.apiCreateWorkspace(data.workspaceName, data.workspaceSlug);
cy.apiLogout(); cy.apiLogout();
cy.skipWalkthrough()
}); });
it("should verify app import functionality", () => { it("should verify app import functionality", () => {
@ -100,12 +101,13 @@ describe("App Import Functionality", () => {
.and("have.text", importText.appImportedToastMessage); .and("have.text", importText.appImportedToastMessage);
// Verify imported app // Verify imported app
cy.get(".driver-close-btn").click(); cy.get(commonSelectors.toastCloseButton).click();
cy.wait(500); cy.wait(500);
cy.get(commonSelectors.appNameInput).verifyVisibleElement( cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"contain.value", "contain.value",
"three-versions" "three-versions"
); );
cy.get(appVersionSelectors.currentVersionField("v3")).should("be.visible");
// Configure app // Configure app
cy.skipEditorPopover(); cy.skipEditorPopover();

View file

@ -27,17 +27,21 @@ describe("App Slug", () => {
}); });
it("Verify app slug cases in global settings", () => { it("Verify app slug cases in global settings", () => {
cy.apiLogin();
const workspaceId = Cypress.env("workspaceId"); const workspaceId = Cypress.env("workspaceId");
const appId = Cypress.env("appId"); const appId = Cypress.env("appId");
const appUrl = `${host}/${Cypress.env("workspaceId")}/apps/${Cypress.env("appId")}/`;
cy.visit("/my-workspace"); cy.apiLogin();
cy.wait(1000); cy.skipWalkthrough();
cy.window({ log: false }).then((win) => { cy.visit(appUrl);
win.localStorage.setItem("walkthroughCompleted", "true"); cy.url().then((url) => {
if (url !== appUrl) {
cy.visit(appUrl);
}
}); });
cy.visit(`/${Cypress.env("workspaceId")}/apps/${Cypress.env("appId")}/`); cy.url().should("eq", appUrl);
cy.wait(1000); cy.wait(1000);
cy.get(commonSelectors.leftSideBarSettingsButton).click(); cy.get(commonSelectors.leftSideBarSettingsButton).click();

View file

@ -78,11 +78,11 @@ describe("Private and Public apps", {
// Test private access // Test private access
logout(); logout();
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
cy.visitSlug({ cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
}); });
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible"); cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
cy.wait(2000); cy.wait(2000);
cy.appUILogin(); cy.appUILogin();
@ -116,6 +116,9 @@ describe("Private and Public apps", {
inviteUserToWorkspace(data.firstName, data.email); inviteUserToWorkspace(data.firstName, data.email);
logout(); logout();
cy.visit("/");
cy.wait(2000);
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
// Test private access // Test private access
cy.visitSlug({ cy.visitSlug({
@ -141,6 +144,8 @@ describe("Private and Public apps", {
cy.wait(1000); cy.wait(1000);
cy.apiMakeAppPublic(); cy.apiMakeAppPublic();
logout(); logout();
cy.wait(1000);
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
cy.visitSlug({ cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
@ -177,6 +182,9 @@ describe("Private and Public apps", {
cy.apiMakeAppPublic(); cy.apiMakeAppPublic();
logout(); logout();
cy.wait(1000);
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
cy.visitSlug({ cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`, actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
}); });
@ -229,6 +237,8 @@ describe("Private and Public apps", {
cy.get('[data-cy="viewer-page-logo"]').click(); cy.get('[data-cy="viewer-page-logo"]').click();
logout(); logout();
cy.wait(1000);
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
// Setup new workspace and app // Setup new workspace and app
cy.defaultWorkspaceLogin(); cy.defaultWorkspaceLogin();

View file

@ -123,7 +123,7 @@ describe("App Version", () => {
releasedVersionAndVerify("v2"); releasedVersionAndVerify("v2");
}); });
it.only("should verify version management with components and queries", () => { it("should verify version management with components and queries", () => {
// Initial setup with component and datasource // Initial setup with component and datasource
cy.apiAddComponentToApp( cy.apiAddComponentToApp(
data.appName, data.appName,

View file

@ -80,8 +80,8 @@ describe("Workspace constants", () => {
addNewconstants("restapiHeaderKey", "customHeader"); addNewconstants("restapiHeaderKey", "customHeader");
addNewconstants("restapiHeaderValue", "key=value"); addNewconstants("restapiHeaderValue", "key=value");
addNewconstants("deleteConst", "deleteconst"); addNewconstants("deleteConst", "deleteconst");
addNewconstants("gconst", "236"); addNewconstants("gconst", "108");
addNewconstants("gconstUrl", "http://34.66.166.236:4000/"); addNewconstants("gconstUrl", "http://20.29.40.108:4000/");
addNewconstants("gconstEndpoint", "production"); addNewconstants("gconstEndpoint", "production");
// create secret constants // create secret constants
@ -118,6 +118,7 @@ describe("Workspace constants", () => {
//Verify all static and datasource queries output in components //Verify all static and datasource queries output in components
for (let i = 3; i <= 16; i++) { for (let i = 3; i <= 16; i++) {
cy.log("Verifying textinput" + i);
cy.get(commonWidgetSelector.draggableWidget(`textinput${i}`)) cy.get(commonWidgetSelector.draggableWidget(`textinput${i}`))
.verifyVisibleElement("have.value", "Production environment testing"); .verifyVisibleElement("have.value", "Production environment testing");
} }

View file

@ -44,6 +44,164 @@ describe("dashboard", () => {
cy.visit(`${data.workspaceSlug}`); cy.visit(`${data.workspaceSlug}`);
}); });
// it("Should verify app card elements and app card operations", () => {
// const customLayout = {
// desktop: { top: 100, left: 20 },
// mobile: { width: 8, height: 50 },
// };
// cy.apiCreateApp(data.appName);
// cy.visit(`${data.workspaceSlug}`);
// cy.wait(2000);
// cy.get(commonSelectors.appCreationDetails).should("be.visible");
// cy.get(commonSelectors.appCard(data.appName)).should("be.visible");
// cy.get(commonSelectors.appTitle(data.appName)).verifyVisibleElement(
// "have.text",
// data.appName
// );
// viewAppCardOptions(data.appName);
// cy.get(
// commonSelectors.appCardOptions(commonText.changeIconOption)
// ).verifyVisibleElement("have.text", commonText.changeIconOption);
// cy.get(
// commonSelectors.appCardOptions(commonText.addToFolderOption)
// ).verifyVisibleElement("have.text", commonText.addToFolderOption);
// cy.get(
// commonSelectors.appCardOptions(commonText.cloneAppOption)
// ).verifyVisibleElement("have.text", commonText.cloneAppOption);
// cy.get(
// commonSelectors.appCardOptions(commonText.exportAppOption)
// ).verifyVisibleElement("have.text", commonText.exportAppOption);
// cy.get(
// commonSelectors.appCardOptions(commonText.deleteAppOption)
// ).verifyVisibleElement("have.text", commonText.deleteAppOption);
// modifyAndVerifyAppCardIcon(data.appName);
// createFolder(data.folderName);
// viewAppCardOptions(data.appName);
// cy.get(
// commonSelectors.appCardOptions(commonText.addToFolderOption)
// ).click();
// verifyModal(
// dashboardText.addToFolderTitle,
// dashboardText.addToFolderButton,
// dashboardSelector.selectFolder
// );
// cy.get(dashboardSelector.moveAppText).verifyVisibleElement(
// "have.text",
// dashboardText.moveAppText(data.appName)
// );
// cy.get(dashboardSelector.selectFolder).click();
// cy.get(commonSelectors.folderList).contains(data.folderName).click();
// cy.get(dashboardSelector.addToFolderButton).click();
// cy.verifyToastMessage(
// commonSelectors.toastMessage,
// commonText.AddedToFolderToast,
// false
// );
// cy.get(dashboardSelector.folderName(data.folderName)).verifyVisibleElement(
// "have.text",
// dashboardText.folderName(`${data.folderName} (1)`)
// );
// cy.get(dashboardSelector.folderName(data.folderName)).click();
// cy.get(commonSelectors.appCard(data.appName))
// .contains(data.appName)
// .should("be.visible");
// viewAppCardOptions(data.appName);
// cy.get(commonSelectors.appCardOptions(commonText.removeFromFolderOption))
// .verifyVisibleElement("have.text", commonText.removeFromFolderOption)
// .click();
// verifyConfirmationModal(commonText.appRemovedFromFolderMessage);
// cancelModal(commonText.cancelButton);
// viewAppCardOptions(data.appName);
// cy.get(
// commonSelectors.appCardOptions(commonText.removeFromFolderOption)
// ).click();
// cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
// cy.verifyToastMessage(
// commonSelectors.toastMessage,
// commonText.appRemovedFromFolderTaost,
// false
// );
// cy.get(commonSelectors.modalComponent).should("not.exist");
// cy.get(commonSelectors.empytyFolderImage).should("be.visible");
// cy.get(commonSelectors.emptyFolderText).verifyVisibleElement(
// "have.text",
// commonText.emptyFolderText
// );
// cy.get(commonSelectors.allApplicationsLink).click();
// deleteFolder(data.folderName);
// cy.get(commonSelectors.allApplicationsLink).click();
// cy.wait(1000);
// viewAppCardOptions(data.appName);
// cy.wait(2000);
// cy.get(commonSelectors.appCardOptions(commonText.exportAppOption)).click();
// cy.get(commonSelectors.exportAllButton).click();
// cy.exec("ls ./cypress/downloads/").then((result) => {
// const downloadedAppExportFileName = result.stdout.split("\n")[0];
// expect(downloadedAppExportFileName).to.contain.string("app");
// });
// viewAppCardOptions(data.appName);
// cy.get(commonSelectors.appCardOptions(commonText.cloneAppOption)).click();
// cy.get('[data-cy="clone-app"]').click();
// cy.get(".go3958317564")
// .should("be.visible")
// .and("have.text", dashboardText.appClonedToast);
// cy.wait(3000);
// cy.renameApp(data.cloneAppName);
// cy.apiAddComponentToApp(data.cloneAppName, "button", 25, 25);
// cy.backToApps();
// cy.wait("@appLibrary");
// cy.wait(1000);
// cy.get(commonSelectors.appCard(data.cloneAppName)).should("be.visible");
// cy.wait(1000);
// viewAppCardOptions(data.cloneAppName);
// cy.get(commonSelectors.deleteAppOption).click();
// cy.get(commonSelectors.modalMessage).verifyVisibleElement(
// "have.text",
// commonText.deleteAppModalMessage(data.cloneAppName)
// );
// cy.get(
// commonSelectors.buttonSelector(commonText.cancelButton)
// ).verifyVisibleElement("have.text", commonText.cancelButton);
// cy.get(
// commonSelectors.buttonSelector(commonText.modalYesButton)
// ).verifyVisibleElement("have.text", commonText.modalYesButton);
// cancelModal(commonText.cancelButton);
// viewAppCardOptions(data.cloneAppName);
// cy.get(commonSelectors.deleteAppOption).click();
// cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
// cy.verifyToastMessage(
// commonSelectors.toastMessage,
// commonText.appDeletedToast,
// false
// );
// verifyAppDelete(data.cloneAppName);
// cy.wait("@appLibrary");
// cy.deleteApp(data.appName);
// verifyAppDelete(data.appName);
// });
it("should verify the elements on empty dashboard", () => { it("should verify the elements on empty dashboard", () => {
cy.intercept("GET", "/api/metadata", { cy.intercept("GET", "/api/metadata", {
body: { body: {
@ -171,181 +329,6 @@ describe("dashboard", () => {
verifyTooltip(dashboardSelector.modeToggle, "Mode"); verifyTooltip(dashboardSelector.modeToggle, "Mode");
}); });
it.skip("Should verify app card elements and app card operations", () => {
const customLayout = {
desktop: { top: 100, left: 20 },
mobile: { width: 8, height: 50 },
};
cy.apiCreateApp(data.appName);
cy.openApp();
cy.apiAddComponentToApp(data.appName, "text1", customLayout);
cy.backToApps();
cy.wait(500);
cy.get(commonSelectors.appCard(data.appName))
.parent()
.within(() => {
cy.get(commonSelectors.appCard(data.appName)).should("be.visible");
cy.get(commonSelectors.appTitle(data.appName)).verifyVisibleElement(
"have.text",
data.appName
);
cy.get(commonSelectors.appCreationDetails).should("be.visible");
//Add the edited details
});
viewAppCardOptions(data.appName);
cy.get(
commonSelectors.appCardOptions(commonText.changeIconOption)
).verifyVisibleElement("have.text", commonText.changeIconOption);
cy.get(
commonSelectors.appCardOptions(commonText.addToFolderOption)
).verifyVisibleElement("have.text", commonText.addToFolderOption);
cy.get(
commonSelectors.appCardOptions(commonText.cloneAppOption)
).verifyVisibleElement("have.text", commonText.cloneAppOption);
cy.get(
commonSelectors.appCardOptions(commonText.exportAppOption)
).verifyVisibleElement("have.text", commonText.exportAppOption);
cy.get(
commonSelectors.appCardOptions(commonText.deleteAppOption)
).verifyVisibleElement("have.text", commonText.deleteAppOption);
modifyAndVerifyAppCardIcon(data.appName);
createFolder(data.folderName);
viewAppCardOptions(data.appName);
cy.get(
commonSelectors.appCardOptions(commonText.addToFolderOption)
).click();
verifyModal(
dashboardText.addToFolderTitle,
dashboardText.addToFolderButton,
dashboardSelector.selectFolder
);
cy.get(dashboardSelector.moveAppText).verifyVisibleElement(
"have.text",
dashboardText.moveAppText(data.appName)
);
cy.get(dashboardSelector.selectFolder).click();
cy.get(commonSelectors.folderList).contains(data.folderName).click();
cy.get(dashboardSelector.addToFolderButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.AddedToFolderToast
);
cy.get(dashboardSelector.folderName(data.folderName)).verifyVisibleElement(
"have.text",
dashboardText.folderName(`${data.folderName} (1)`)
);
cy.get(dashboardSelector.folderName(data.folderName)).click();
cy.get(commonSelectors.appCard(data.appName))
.contains(data.appName)
.should("be.visible");
cy.wait(2000);
viewAppCardOptions(data.appName);
cy.get(commonSelectors.appCardOptions(commonText.removeFromFolderOption))
.verifyVisibleElement("have.text", commonText.removeFromFolderOption)
.click();
verifyConfirmationModal(commonText.appRemovedFromFolderMessage);
cancelModal(commonText.cancelButton);
cy.wait(3000);
viewAppCardOptions(data.appName);
cy.get(
commonSelectors.appCardOptions(commonText.removeFromFolderOption)
).click();
cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appRemovedFromFolderTaost
);
cy.get(commonSelectors.modalComponent).should("not.exist");
cy.get(commonSelectors.empytyFolderImage).should("be.visible");
cy.get(commonSelectors.emptyFolderText).verifyVisibleElement(
"have.text",
commonText.emptyFolderText
);
cy.get(commonSelectors.allApplicationsLink).click();
deleteFolder(data.folderName);
cy.get(commonSelectors.allApplicationsLink).click();
cy.wait(3000);
viewAppCardOptions(data.appName);
cy.get(commonSelectors.appCardOptions(commonText.cloneAppOption)).click();
cy.get('[data-cy="clone-app"]').click();
cy.get(".go3958317564")
.should("be.visible")
.and("have.text", dashboardText.appClonedToast);
cy.wait(3000);
cy.renameApp(data.cloneAppName);
cy.apiAddComponentToApp(data.cloneAppName, "button", 25, 25);
cy.backToApps();
cy.wait("@appLibrary");
cy.wait(1000);
cy.reloadAppForTheElement(data.cloneAppName);
cy.get(commonSelectors.appCard(data.cloneAppName)).should("be.visible");
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.get(commonSelectors.dashboardIcon).click();
cy.wait(3000);
cy.reloadAppForTheElement(data.cloneAppName);
viewAppCardOptions(data.cloneAppName);
cy.get(commonSelectors.appCardOptions(commonText.exportAppOption)).click();
cy.get(commonSelectors.exportAllButton).click();
cy.exec("ls ./cypress/downloads/").then((result) => {
const downloadedAppExportFileName = result.stdout.split("\n")[0];
expect(downloadedAppExportFileName).to.contain.string("app");
});
cy.wait(3000);
cy.reloadAppForTheElement(data.cloneAppName);
viewAppCardOptions(data.cloneAppName);
cy.get(commonSelectors.deleteAppOption).click();
cy.get(commonSelectors.modalMessage).verifyVisibleElement(
"have.text",
commonText.deleteAppModalMessage(data.cloneAppName)
);
cy.get(
commonSelectors.buttonSelector(commonText.cancelButton)
).verifyVisibleElement("have.text", commonText.cancelButton);
cy.get(
commonSelectors.buttonSelector(commonText.modalYesButton)
).verifyVisibleElement("have.text", commonText.modalYesButton);
cancelModal(commonText.cancelButton);
cy.wait(3000);
cy.reloadAppForTheElement(data.cloneAppName);
viewAppCardOptions(data.cloneAppName);
cy.get(commonSelectors.deleteAppOption).click();
cy.get(commonSelectors.buttonSelector(commonText.modalYesButton)).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
);
verifyAppDelete(data.cloneAppName);
cy.wait("@appLibrary");
cy.deleteApp(data.appName);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
);
verifyAppDelete(data.appName);
});
it("Should verify the app CRUD operation", () => { it("Should verify the app CRUD operation", () => {
const customLayout = { const customLayout = {
desktop: { top: 100, left: 20 }, desktop: { top: 100, left: 20 },
@ -369,10 +352,7 @@ describe("dashboard", () => {
cy.wait("@appLibrary"); cy.wait("@appLibrary");
cy.deleteApp(data.appName); cy.deleteApp(data.appName);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
);
verifyAppDelete(data.appName); verifyAppDelete(data.appName);
}); });
@ -493,10 +473,7 @@ describe("dashboard", () => {
cy.get(commonSelectors.allApplicationsLink).click(); cy.get(commonSelectors.allApplicationsLink).click();
cy.deleteApp(data.appName); cy.deleteApp(data.appName);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
);
verifyAppDelete(data.appName); verifyAppDelete(data.appName);
logout(); logout();
}); });

View file

@ -78,14 +78,16 @@ describe("Manage Groups", () => {
cy.createApp(data.appName); cy.createApp(data.appName);
cy.verifyToastMessage( cy.verifyToastMessage(
commonSelectors.toastMessage, commonSelectors.toastMessage,
commonText.appCreatedToast commonText.appCreatedToast,
false
); );
cy.backToApps(); cy.backToApps();
cy.deleteApp(data.appName); cy.deleteApp(data.appName);
cy.verifyToastMessage( cy.verifyToastMessage(
commonSelectors.toastMessage, commonSelectors.toastMessage,
commonText.appDeletedToast commonText.appDeletedToast,
false
); );
// Folder operations // Folder operations
@ -115,7 +117,8 @@ describe("Manage Groups", () => {
cy.get(commonSelectors.cloneAppButton).click(); cy.get(commonSelectors.cloneAppButton).click();
cy.verifyToastMessage( cy.verifyToastMessage(
commonSelectors.toastMessage, commonSelectors.toastMessage,
dashboardText.appClonedToast dashboardText.appClonedToast,
false
); );
// cy.get(commonSelectors.cancelButton).click(); // cy.get(commonSelectors.cancelButton).click();
cy.apiLogout(); cy.apiLogout();
@ -177,14 +180,16 @@ describe("Manage Groups", () => {
cy.createApp(data.appName); cy.createApp(data.appName);
cy.verifyToastMessage( cy.verifyToastMessage(
commonSelectors.toastMessage, commonSelectors.toastMessage,
commonText.appCreatedToast commonText.appCreatedToast,
false
); );
cy.backToApps(); cy.backToApps();
cy.deleteApp(data.appName); cy.deleteApp(data.appName);
cy.verifyToastMessage( cy.verifyToastMessage(
commonSelectors.toastMessage, commonSelectors.toastMessage,
commonText.appDeletedToast commonText.appDeletedToast,
false
); );
// Folder operations // Folder operations

View file

@ -204,10 +204,7 @@ describe("Manage Groups", () => {
cy.wait(2500); cy.wait(2500);
cy.deleteApp(data.appName); cy.deleteApp(data.appName);
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.appDeletedToast
);
// Folder operations // Folder operations
createFolder(data.folderName); createFolder(data.folderName);

View file

@ -2127,7 +2127,7 @@
"encrypted": false "encrypted": false
}, },
"host": { "host": {
"value": "35.238.9.114", "value": "9.234.17.31",
"encrypted": false "encrypted": false
}, },
"port": { "port": {

View file

@ -585,7 +585,7 @@
"encrypted": false "encrypted": false
}, },
"host": { "host": {
"value": "35.238.9.114", "value": "9.234.17.31",
"encrypted": false "encrypted": false
}, },
"port": { "port": {

View file

@ -1862,7 +1862,7 @@
"encrypted": false "encrypted": false
}, },
"host": { "host": {
"value": "35.238.9.114", "value": "9.234.17.31",
"encrypted": false "encrypted": false
}, },
"port": { "port": {

View file

@ -2766,7 +2766,7 @@
"name": "restapiStaticUrlG", "name": "restapiStaticUrlG",
"options": { "options": {
"method": "get", "method": "get",
"url": "http://34.66.166.236:4000/{{constants.gconstEndpoint}}", "url": "http://20.29.40.108:4000/{{constants.gconstEndpoint}}",
"url_params": [ "url_params": [
[ [
"", "",
@ -2814,7 +2814,7 @@
"name": "restapiUrlS", "name": "restapiUrlS",
"options": { "options": {
"method": "get", "method": "get",
"url": "http://34.66.166.236:4000/{{secrets.sconstEndpoint}}", "url": "http://20.29.40.108:4000/{{secrets.sconstEndpoint}}",
"url_params": [ "url_params": [
[ [
"", "",
@ -2908,7 +2908,7 @@
"name": "restapiUrlGS", "name": "restapiUrlGS",
"options": { "options": {
"method": "get", "method": "get",
"url": "http://34.66.166.{{constants.gconst}}{{secrets.sconst}}/production", "url": "http://20.29.40.{{constants.gconst}}{{secrets.sconst}}/production",
"url_params": [ "url_params": [
[ [
"", "",
@ -3419,7 +3419,7 @@
"environmentId": "dab04b8d-7d1a-468a-b219-b2e1d0169d8c", "environmentId": "dab04b8d-7d1a-468a-b219-b2e1d0169d8c",
"options": { "options": {
"url": { "url": {
"value": "http://34.66.166.236:4000/{{constants.gconstEndpoint}}", "value": "http://20.29.40.108:4000/{{constants.gconstEndpoint}}",
"encrypted": false "encrypted": false
}, },
"auth_type": { "auth_type": {
@ -3540,7 +3540,7 @@
"environmentId": "dab04b8d-7d1a-468a-b219-b2e1d0169d8c", "environmentId": "dab04b8d-7d1a-468a-b219-b2e1d0169d8c",
"options": { "options": {
"url": { "url": {
"value": "http://34.66.166.236:4000/{{secrets.sconstEndpoint}}", "value": "http://20.29.40.108:4000/{{secrets.sconstEndpoint}}",
"encrypted": false "encrypted": false
}, },
"auth_type": { "auth_type": {
@ -3782,7 +3782,7 @@
"environmentId": "dab04b8d-7d1a-468a-b219-b2e1d0169d8c", "environmentId": "dab04b8d-7d1a-468a-b219-b2e1d0169d8c",
"options": { "options": {
"url": { "url": {
"value": "http://34.66.166.{{constants.gconst}}{{secrets.sconst}}/production", "value": "http://20.29.40.{{constants.gconst}}{{secrets.sconst}}/production",
"encrypted": false "encrypted": false
}, },
"auth_type": { "auth_type": {

View file

@ -101,11 +101,14 @@ export const navigateToAppEditor = (appName) => {
export const viewAppCardOptions = (appName) => { export const viewAppCardOptions = (appName) => {
cy.wait(1000); cy.wait(1000);
cy.reloadAppForTheElement(appName); cy.get(commonSelectors.appCard(appName))
.realHover()
.find(commonSelectors.appCardOptionsButton)
.realHover()
cy.contains("div", appName) cy.contains("div", appName)
.parent() .parent()
.within(() => { .within(() => {
cy.get(commonSelectors.appCardOptionsButton).invoke("click"); cy.get(commonSelectors.appCardOptionsButton).click();
}); });
}; };
@ -185,8 +188,9 @@ export const searchUser = (email) => {
}; };
export const selectAppCardOption = (appName, appCardOption) => { export const selectAppCardOption = (appName, appCardOption) => {
cy.wait(1000);
viewAppCardOptions(appName); viewAppCardOptions(appName);
cy.get(appCardOption).should("be.visible").click({ force: true }); cy.get(appCardOption).should("be.visible").click();
}; };
export const navigateToDatabase = () => { export const navigateToDatabase = () => {

View file

@ -53,6 +53,8 @@ export const modifyAndVerifyAppCardIcon = (appName) => {
}; };
export const verifyAppDelete = (appName) => { export const verifyAppDelete = (appName) => {
cy.get("body").should("exist").and("be.visible");
cy.get('[data-cy="dashboard-section-header"]').should("be.visible");
cy.get("body").then(($title) => { cy.get("body").then(($title) => {
if (!$title.text().includes(commonText.introductionMessage)) { if (!$title.text().includes(commonText.introductionMessage)) {
cy.clearAndType(commonSelectors.homePageSearchBar, appName); cy.clearAndType(commonSelectors.homePageSearchBar, appName);

View file

@ -239,7 +239,8 @@ export const createRestAPIQuery = (
key = "", key = "",
value = "", value = "",
url = "", url = "",
run = true run = true,
kind = "restapi"
) => { ) => {
cy.getCookie("tj_auth_token").then((cookie) => { cy.getCookie("tj_auth_token").then((cookie) => {
const headers = { const headers = {
@ -247,7 +248,6 @@ export const createRestAPIQuery = (
Cookie: `tj_auth_token=${cookie.value}`, Cookie: `tj_auth_token=${cookie.value}`,
}; };
cy.log(Cypress.env("appId"));
cy.request({ cy.request({
method: "GET", method: "GET",
url: `${Cypress.env("server_host")}/api/apps/${Cypress.env("appId")}`, url: `${Cypress.env("server_host")}/api/apps/${Cypress.env("appId")}`,
@ -255,13 +255,13 @@ export const createRestAPIQuery = (
}).then((response) => { }).then((response) => {
const editingVersionId = response.body.editing_version.id; const editingVersionId = response.body.editing_version.id;
const data_source_id = Cypress.env(`${dsName}-id`); const data_source_id = Cypress.env(kind);
const requestBody = { const requestBody = {
app_id: Cypress.env("appId"), app_id: Cypress.env("appId"),
app_version_id: editingVersionId, app_version_id: editingVersionId,
name: queryName, name: queryName,
kind: "restapi", kind: kind,
options: { options: {
method: "get", method: "get",
url: url, url: url,

View file

@ -18,79 +18,97 @@ export const createAndRunRestAPIQuery = (
method: "GET", method: "GET",
url: `${Cypress.env("server_host")}/api/apps/${Cypress.env("appId")}`, url: `${Cypress.env("server_host")}/api/apps/${Cypress.env("appId")}`,
headers, headers,
}).then((response) => { }).then((appResponse) => {
const editingVersionId = response.body.editing_version.id; const currentEnvironmentId = appResponse.body.editorEnvironment.id;
const data_source_id = Cypress.env(`${dsName}-id`); const editingVersionId = appResponse.body.editing_version.id;
const useJsonBody =
["POST", "PATCH", "PUT"].includes(method.toUpperCase()) &&
jsonBody !== null;
const queryOptions = {
method: method.toLowerCase(),
url: url + urlSuffix,
url_params: [["", ""]],
headers: headersList.length ? headersList : [["", ""]],
body: !useJsonBody && bodyList.length ? bodyList : [["", ""]],
json_body: useJsonBody ? jsonBody : null,
body_toggle: useJsonBody,
runOnPageLoad: run,
transformationLanguage: "javascript",
enableTransformation: false,
};
const requestBody = {
app_id: Cypress.env("appId"),
app_version_id: editingVersionId,
name: queryName,
kind: "restapi",
options: queryOptions,
data_source_id,
plugin_id: null,
};
cy.request({ cy.request({
method: "POST", method: "GET",
url: `${Cypress.env("server_host")}/api/data-queries/data-sources/${data_source_id}/versions/${editingVersionId}`, url: `${Cypress.env("server_host")}/api/data-sources/${Cypress.env("workspaceId")}/environments/${currentEnvironmentId}/versions/${editingVersionId}`,
headers, headers,
body: requestBody, }).then((dsResponse) => {
}).then((createResponse) => { expect(dsResponse.status).to.eq(200);
expect(createResponse.status).to.equal(201);
const queryId = createResponse.body.id;
cy.log("Query created successfully:", queryId);
const createdOptions = createResponse.body.options; const dataSource = dsResponse.body.data_sources.find(
expect(createdOptions.method).to.equal(queryOptions.method); (ds) => ds.name === dsName
expect(createdOptions.url).to.equal(queryOptions.url); );
expect(createdOptions.headers).to.deep.equal(queryOptions.headers);
if (useJsonBody) { if (!dataSource) {
expect(createdOptions.json_body).to.deep.equal( throw new Error(`Data source '${dsName}' not found.`);
queryOptions.json_body
);
expect(createdOptions.body_toggle).to.equal(true);
} else {
expect(createdOptions.body).to.deep.equal(queryOptions.body);
expect(createdOptions.body_toggle).to.equal(false);
} }
expect(createdOptions.runOnPageLoad).to.equal(run); const data_source_id = dataSource.id;
cy.log("Metadata verified successfully"); const useJsonBody =
if (run) { ["POST", "PATCH", "PUT"].includes(method.toUpperCase()) &&
cy.request({ jsonBody !== null;
method: "POST",
url: `${Cypress.env("server_host")}/api/data-queries/${queryId}/run`, const queryOptions = {
headers, method: method.toLowerCase(),
}).then((runResponse) => { url: url + urlSuffix,
expect([200, 201]).to.include(runResponse.status); url_params: [["", ""]],
cy.log("Query executed successfully:", runResponse.body); headers: headersList.length ? headersList : [["", ""]],
if (runResponse.body?.data.id) { body: !useJsonBody && bodyList.length ? bodyList : [["", ""]],
cy.writeFile("cypress/fixtures/restAPI/storedId.json", { json_body: useJsonBody ? jsonBody : null,
id: runResponse.body.data.id, body_toggle: useJsonBody,
}); runOnPageLoad: run,
cy.log("Stored ID:", runResponse.body.data.id); transformationLanguage: "javascript",
} enableTransformation: false,
}); };
}
const requestBody = {
app_id: Cypress.env("appId"),
app_version_id: editingVersionId,
name: queryName,
kind: "restapi",
options: queryOptions,
data_source_id,
plugin_id: null,
};
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/data-queries/data-sources/${data_source_id}/versions/${editingVersionId}`,
headers,
body: requestBody,
}).then((createResponse) => {
expect(createResponse.status).to.equal(201);
const queryId = createResponse.body.id;
cy.log("Query created successfully:", queryId);
const createdOptions = createResponse.body.options;
expect(createdOptions.method).to.equal(queryOptions.method);
expect(createdOptions.url).to.equal(queryOptions.url);
expect(createdOptions.headers).to.deep.equal(queryOptions.headers);
if (useJsonBody) {
expect(createdOptions.json_body).to.deep.equal(
queryOptions.json_body
);
expect(createdOptions.body_toggle).to.equal(true);
} else {
expect(createdOptions.body).to.deep.equal(queryOptions.body);
expect(createdOptions.body_toggle).to.equal(false);
}
expect(createdOptions.runOnPageLoad).to.equal(run);
cy.log("Metadata verified successfully");
if (run) {
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/data-queries/${queryId}/run`,
headers,
}).then((runResponse) => {
expect([200, 201]).to.include(runResponse.status);
cy.log("Query executed successfully:", runResponse.body);
if (runResponse.body?.data.id) {
cy.writeFile("cypress/fixtures/restAPI/storedId.json", {
id: runResponse.body.data.id,
});
cy.log("Stored ID:", runResponse.body.data.id);
}
});
}
});
}); });
}); });
}); });

View file

@ -115,8 +115,8 @@ export const verifyDuplicateVersion = (newVersion = [], version) => {
cy.get(appVersionSelectors.createNewVersionButton).click(); cy.get(appVersionSelectors.createNewVersionButton).click();
cy.verifyToastMessage( cy.verifyToastMessage(
commonSelectors.toastMessage, commonSelectors.toastMessage,
// appVersionText.versionNameAlreadyExists appVersionText.versionNameAlreadyExists
"Already exists!" // "Already exists!"
); );
}; };

View file

@ -38,7 +38,7 @@ COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
ENV NODE_ENV=production ENV NODE_ENV=production
ENV NODE_OPTIONS="--max-old-space-size=4096" ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN apt-get update && apt-get install -y postgresql-client freetds-dev libaio1 wget supervisor RUN apt-get update && apt-get install -y freetds-dev libaio1 wget supervisor
# Install Instantclient Basic Light Oracle and Dependencies # Install Instantclient Basic Light Oracle and Dependencies
WORKDIR /opt/oracle WORKDIR /opt/oracle
@ -54,9 +54,6 @@ ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21
WORKDIR / WORKDIR /
RUN mkdir -p /app /var/log/supervisor
COPY /deploy/docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# copy npm scripts # copy npm scripts
COPY --from=builder /app/package.json ./app/package.json COPY --from=builder /app/package.json ./app/package.json
# copy plugins dependencies # copy plugins dependencies
@ -77,32 +74,64 @@ COPY --from=builder /app/server/dist ./app/server/dist
WORKDIR /app WORKDIR /app
# Install PostgreSQL
USER root USER root
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
RUN echo "deb http://deb.debian.org/debian"
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
USER postgres
RUN service postgresql start && \ # Explicitly create PG main directory with correct ownership
psql -c "create role tooljet with login superuser password 'postgres';" RUN mkdir -p /var/lib/postgresql/13/main && \
USER root chown -R postgres:postgres /var/lib/postgresql
RUN mkdir -p /var/log/supervisor /var/run/postgresql && \
chown -R postgres:postgres /var/run/postgresql /var/log/supervisor
# Remove existing data and create directory with proper ownership
RUN rm -rf /var/lib/postgresql/13/main && \
mkdir -p /var/lib/postgresql/13/main && \
chown -R postgres:postgres /var/lib/postgresql
# Initialize PostgreSQL
RUN su - postgres -c "/usr/lib/postgresql/13/bin/initdb -D /var/lib/postgresql/13/main"
# Configure Supervisor to manage PostgREST, ToolJet, and Redis
RUN echo "[supervisord] \n" \
"nodaemon=true \n" \
"user=root \n" \
"\n" \
"[program:postgrest] \n" \
"command=/bin/postgrest \n" \
"autostart=true \n" \
"autorestart=true \n" \
"\n" \
"[program:tooljet] \n" \
"user=root \n" \
"command=/bin/bash -c '/app/server/scripts/boot.sh' \n" \
"autostart=true \n" \
"autorestart=true \n" \
"stderr_logfile=/dev/stdout \n" \
"stderr_logfile_maxbytes=0 \n" \
"stdout_logfile=/dev/stdout \n" \
"stdout_logfile_maxbytes=0 \n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf
# ENV defaults # ENV defaults
ENV TOOLJET_HOST=http://localhost \ ENV TOOLJET_HOST=http://localhost \
PORT=80 \
NODE_ENV=production \ NODE_ENV=production \
LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \ LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \
SECRET_KEY_BASE=replace_with_secret_key_base \ SECRET_KEY_BASE=replace_with_secret_key_base \
PG_DB=tooljet_production \ PG_DB=tooljet_production \
PG_USER=tooljet \ PG_USER=postgres \
PG_PASS=postgres \ PG_PASS=postgres \
PG_HOST=localhost \ PG_HOST=localhost \
ENABLE_TOOLJET_DB=true \ ENABLE_TOOLJET_DB=true \
TOOLJET_DB_HOST=localhost \ TOOLJET_DB_HOST=localhost \
TOOLJET_DB_USER=tooljet \ TOOLJET_DB_USER=postgres \
TOOLJET_DB_PASS=postgres \ TOOLJET_DB_PASS=postgres \
TOOLJET_DB=tooljet_db \ TOOLJET_DB=tooljet_db \
PGRST_HOST=http://localhost:3000 \ PGRST_HOST=http://localhost:3000 \
PGRST_DB_URI=postgres://tooljet:postgres@localhost/tooljet_db \ PGRST_DB_URI=postgres://postgres:postgres@localhost/tooljet_db \
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \ PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
PGRST_DB_PRE_CONFIG=postgrest.pre_config \ PGRST_DB_PRE_CONFIG=postgrest.pre_config \
ORM_LOGGING=true \ ORM_LOGGING=true \
@ -110,4 +139,7 @@ ENV TOOLJET_HOST=http://localhost \
HOME=/home/appuser \ HOME=/home/appuser \
TERM=xterm TERM=xterm
CMD ["/usr/bin/supervisord"]
RUN chmod +x ./server/scripts/preview.sh
# Set the entrypoint
ENTRYPOINT ["./server/scripts/preview.sh"]

View file

@ -9,7 +9,7 @@ WORKDIR /app
ARG CUSTOM_GITHUB_TOKEN ARG CUSTOM_GITHUB_TOKEN
ARG BRANCH_NAME ARG BRANCH_NAME
# Clone and checkout the frontend repository # Clone and checkout the frontend repositorys
RUN git config --global url."https://x-access-token:${CUSTOM_GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/" RUN git config --global url."https://x-access-token:${CUSTOM_GITHUB_TOKEN}@github.com/".insteadOf "https://github.com/"
RUN git config --global http.version HTTP/1.1 RUN git config --global http.version HTTP/1.1
@ -66,7 +66,7 @@ COPY --from=postgrest/postgrest:v12.2.0 /bin/postgrest /bin
ENV NODE_ENV=production ENV NODE_ENV=production
ENV TOOLJET_EDITION=ee ENV TOOLJET_EDITION=ee
ENV NODE_OPTIONS="--max-old-space-size=4096" ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN apt-get update && apt-get install -y postgresql-client freetds-dev libaio1 wget supervisor RUN apt-get update && apt-get install -y freetds-dev libaio1 wget supervisor
# Install Instantclient Basic Light Oracle and Dependencies # Install Instantclient Basic Light Oracle and Dependencies
WORKDIR /opt/oracle WORKDIR /opt/oracle
@ -82,9 +82,6 @@ ENV LD_LIBRARY_PATH="/opt/oracle/instantclient_11_2:/opt/oracle/instantclient_21
WORKDIR / WORKDIR /
RUN mkdir -p /app /var/log/supervisor
COPY /deploy/docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# copy npm scripts # copy npm scripts
COPY --from=builder /app/package.json ./app/package.json COPY --from=builder /app/package.json ./app/package.json
# copy plugins dependencies # copy plugins dependencies
@ -106,38 +103,73 @@ COPY --from=builder /app/server/dist ./app/server/dist
WORKDIR /app WORKDIR /app
# ENV defaults # Install PostgreSQL
USER root USER root
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ bullseye-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
RUN echo "deb http://deb.debian.org/debian" RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor --fix-missing
RUN apt update && apt -y install postgresql-13 postgresql-client-13 supervisor
USER postgres
RUN service postgresql start && \ # Explicitly create PG main directory with correct ownership
psql -c "create role tooljet with login superuser password 'postgres';" RUN mkdir -p /var/lib/postgresql/13/main && \
USER root chown -R postgres:postgres /var/lib/postgresql
RUN mkdir -p /var/log/supervisor /var/run/postgresql && \
chown -R postgres:postgres /var/run/postgresql /var/log/supervisor
# Remove existing data and create directory with proper ownership
RUN rm -rf /var/lib/postgresql/13/main && \
mkdir -p /var/lib/postgresql/13/main && \
chown -R postgres:postgres /var/lib/postgresql
# Initialize PostgreSQL
RUN su - postgres -c "/usr/lib/postgresql/13/bin/initdb -D /var/lib/postgresql/13/main"
# Configure Supervisor to manage PostgREST, ToolJet, and Redis
RUN echo "[supervisord] \n" \
"nodaemon=true \n" \
"user=root \n" \
"\n" \
"[program:postgrest] \n" \
"command=/bin/postgrest \n" \
"autostart=true \n" \
"autorestart=true \n" \
"\n" \
"[program:tooljet] \n" \
"user=root \n" \
"command=/bin/bash -c '/app/server/scripts/boot.sh' \n" \
"autostart=true \n" \
"autorestart=true \n" \
"stderr_logfile=/dev/stdout \n" \
"stderr_logfile_maxbytes=0 \n" \
"stdout_logfile=/dev/stdout \n" \
"stdout_logfile_maxbytes=0 \n" | sed 's/ //' > /etc/supervisor/conf.d/supervisord.conf
# ENV defaults # ENV defaults
ENV TOOLJET_HOST=http://localhost \ ENV TOOLJET_HOST=http://localhost \
PORT=80 \
NODE_ENV=production \ NODE_ENV=production \
LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \ LOCKBOX_MASTER_KEY=replace_with_lockbox_master_key \
SECRET_KEY_BASE=replace_with_secret_key_base \ SECRET_KEY_BASE=replace_with_secret_key_base \
PG_DB=tooljet_production \ PG_DB=tooljet_production \
PG_USER=tooljet \ PG_USER=postgres \
PG_PASS=postgres \ PG_PASS=postgres \
PG_HOST=localhost \ PG_HOST=localhost \
ENABLE_TOOLJET_DB=true \ ENABLE_TOOLJET_DB=true \
TOOLJET_DB_HOST=localhost \ TOOLJET_DB_HOST=localhost \
TOOLJET_DB_USER=tooljet \ TOOLJET_DB_USER=postgres \
TOOLJET_DB_PASS=postgres \ TOOLJET_DB_PASS=postgres \
TOOLJET_DB=tooljet_db \ TOOLJET_DB=tooljet_db \
PGRST_HOST=http://localhost:3000 \ PGRST_HOST=http://localhost:3000 \
PGRST_DB_URI=postgres://tooljet:postgres@localhost/tooljet_db \ PGRST_DB_URI=postgres://postgres:postgres@localhost/tooljet_db \
PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \ PGRST_JWT_SECRET=r9iMKoe5CRMgvJBBtp4HrqN7QiPpUToj \
PGRST_DB_PRE_CONFIG=postgrest.pre_config \ PGRST_DB_PRE_CONFIG=postgrest.pre_config \
ORM_LOGGING=true \ ORM_LOGGING=true \
DEPLOYMENT_PLATFORM=docker:local \ DEPLOYMENT_PLATFORM=docker:local \
REDIS_PASS= \ HOME=/home/appuser \
TERM=xterm TERM=xterm
CMD ["/usr/bin/supervisord"]
RUN chmod +x ./server/scripts/preview.sh
# Set the entrypoint
ENTRYPOINT ["./server/scripts/preview.sh"]

View file

@ -1 +1 @@
3.11.0 3.12.1

@ -1 +1 @@
Subproject commit 518f3334b12a83785fd37dd53b0245d72848211a Subproject commit 1b77a556709211daed8924821383db9dccc95eb5

View file

@ -58,6 +58,7 @@
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"draft-js": "^0.11.7", "draft-js": "^0.11.7",
"draft-js-export-html": "^1.4.1", "draft-js-export-html": "^1.4.1",
"draft-js-import-html": "^1.4.1",
"driver.js": "^0.9.8", "driver.js": "^0.9.8",
"emoji-mart": "^5.5.2", "emoji-mart": "^5.5.2",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",

View file

@ -232,6 +232,7 @@ export const getAllChildComponents = (allComponents, parentId) => {
const childTabId = componentParentId.split('-').at(-1); const childTabId = componentParentId.split('-').at(-1);
if (componentParentId === `${parentId}-${childTabId}`) { if (componentParentId === `${parentId}-${childTabId}`) {
childComponent.isParentTabORCalendar = true; childComponent.isParentTabORCalendar = true;
childComponent.events = useStore.getState().eventsSlice.getEventsByComponentsId(componentId);
childComponents.push(childComponent); childComponents.push(childComponent);
// Recursively find children of the current child component // Recursively find children of the current child component
const childrenOfChild = getAllChildComponents(allComponents, componentId); const childrenOfChild = getAllChildComponents(allComponents, componentId);
@ -242,6 +243,7 @@ export const getAllChildComponents = (allComponents, parentId) => {
if (componentParentId === parentId) { if (componentParentId === parentId) {
let childComponent = deepClone(allComponents[componentId]); let childComponent = deepClone(allComponents[componentId]);
childComponent.id = componentId; childComponent.id = componentId;
childComponent.events = useStore.getState().eventsSlice.getEventsByComponentsId(componentId);
childComponents.push(childComponent); childComponents.push(childComponent);
// Recursively find children of the current child component // Recursively find children of the current child component

View file

@ -0,0 +1,27 @@
/* eslint-disable import/no-unresolved */
import React from 'react';
import { openSearchPanel } from '@codemirror/search';
import './SearchBox.scss';
import { Button as ButtonComponent } from '@/components/ui/Button/Button.jsx';
export const CodeHinterBtns = ({ view, isPanelOpen, renderCopilot }) => {
return (
<div
className="d-flex tw-flex-col align-items-end tw-gap-[2.5px] w-100 position-absolute tw-pt-[4px] tw-pr-[4px]"
style={{ top: isPanelOpen ? '44px' : 0 }}
>
{!isPanelOpen && (
<ButtonComponent
iconOnly
trailingIcon="search01"
size="small"
variant="outline"
ariaLabel="Open search panel"
className="codehinter-search-btn"
onClick={() => openSearchPanel(view)}
/>
)}
{renderCopilot && renderCopilot()}
</div>
);
};

View file

@ -20,10 +20,12 @@ import { PreviewBox } from './PreviewBox';
import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils'; import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils';
import useStore from '@/AppBuilder/_stores/store'; import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
import { syntaxTree } from '@codemirror/language';
import { search, searchKeymap, searchPanelOpen } from '@codemirror/search'; import { search, searchKeymap, searchPanelOpen } from '@codemirror/search';
import { handleSearchPanel, SearchBtn } from './SearchBox'; import { handleSearchPanel } from './SearchBox';
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks'; import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
import { isInsideParent } from './utils'; import { isInsideParent } from './utils';
import { CodeHinterBtns } from './CodehinterOverlayTriggers';
const langSupport = Object.freeze({ const langSupport = Object.freeze({
javascript: javascript(), javascript: javascript(),
@ -66,7 +68,7 @@ const MultiLineCodeEditor = (props) => {
const context = useContext(CodeHinterContext); const context = useContext(CodeHinterContext);
const { suggestionList } = createReferencesLookup(context, true); const { suggestionList: paramList } = createReferencesLookup(context, true);
const currentValueRef = useRef(initialValue); const currentValueRef = useRef(initialValue);
@ -74,6 +76,7 @@ const MultiLineCodeEditor = (props) => {
const [editorView, setEditorView] = React.useState(null); const [editorView, setEditorView] = React.useState(null);
const [isSearchPanelOpen, setIsSearchPanelOpen] = React.useState(false);
const { queryPanelKeybindings } = useQueryPanelKeyHooks(onChange, currentValueRef, 'multiline'); const { queryPanelKeybindings } = useQueryPanelKeyHooks(onChange, currentValueRef, 'multiline');
const handleOnBlur = () => { const handleOnBlur = () => {
@ -146,8 +149,29 @@ const MultiLineCodeEditor = (props) => {
return suggestion.hint.includes(nearestSubstring); return suggestion.hint.includes(nearestSubstring);
}); });
const localVariables = new Set();
// Traverse the syntax tree to extract variable declarations
syntaxTree(context.state).iterate({
enter: (node) => {
// JavaScript: Detect variable declarations (var, let, const)
if (node.name === 'VariableDefinition') {
const varName = context.state.sliceDoc(node.from, node.to);
if (varName && varName.startsWith(nearestSubstring)) localVariables.add(varName);
}
},
});
// Convert Set to an array of completion suggestions
const localVariableSuggestions = [...localVariables].map((varName) => ({
hint: varName,
type: 'variable',
}));
const suggestionList = paramList.filter((paramSuggestion) => paramSuggestion.hint.includes(nearestSubstring));
const suggestions = generateHints( const suggestions = generateHints(
[...JSLangHints, ...autoSuggestionList, ...suggestionList], [...localVariableSuggestions, ...JSLangHints, ...autoSuggestionList, ...suggestionList],
null, null,
nearestSubstring nearestSubstring
).map((hint) => { ).map((hint) => {
@ -204,6 +228,7 @@ const MultiLineCodeEditor = (props) => {
return { return {
from: context.pos, from: context.pos,
options: [...suggestions], options: [...suggestions],
filter: false,
}; };
} }
@ -237,7 +262,7 @@ const MultiLineCodeEditor = (props) => {
]); ]);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), []); const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [paramList]);
const { handleTogglePopupExapand, isOpen, setIsOpen, forceUpdate } = portalProps; const { handleTogglePopupExapand, isOpen, setIsOpen, forceUpdate } = portalProps;
let cyLabel = paramLabel ? paramLabel.toLowerCase().trim().replace(/\s+/g, '-') : props.cyLabel; let cyLabel = paramLabel ? paramLabel.toLowerCase().trim().replace(/\s+/g, '-') : props.cyLabel;
@ -258,7 +283,7 @@ const MultiLineCodeEditor = (props) => {
ref={wrapperRef} ref={wrapperRef}
> >
<div className={`${className} ${darkMode && 'cm-codehinter-dark-themed'}`}> <div className={`${className} ${darkMode && 'cm-codehinter-dark-themed'}`}>
<SearchBtn view={editorView} /> <CodeHinterBtns view={editorView} isPanelOpen={isSearchPanelOpen} renderCopilot={renderCopilot} />
<CodeHinter.PopupIcon <CodeHinter.PopupIcon
callback={handleTogglePopupExapand} callback={handleTogglePopupExapand}
icon="portal-open" icon="portal-open"
@ -266,7 +291,6 @@ const MultiLineCodeEditor = (props) => {
isMultiEditor={true} isMultiEditor={true}
isQueryManager={isInsideQueryPane} isQueryManager={isInsideQueryPane}
/> />
{renderCopilot && renderCopilot()}
<CodeHinter.Portal <CodeHinter.Portal
isCopilotEnabled={false} isCopilotEnabled={false}
@ -326,12 +350,7 @@ const MultiLineCodeEditor = (props) => {
readOnly={readOnly} readOnly={readOnly}
editable={editable} //for transformations in query manager editable={editable} //for transformations in query manager
onCreateEditor={(view) => setEditorView(view)} onCreateEditor={(view) => setEditorView(view)}
onUpdate={(view) => { onUpdate={(view) => setIsSearchPanelOpen(searchPanelOpen(view.state))}
const icon = document.querySelector('.codehinter-search-btn');
if (searchPanelOpen(view.state)) {
icon.style.display = 'none';
} else icon.style.display = 'block';
}}
/> />
</div> </div>
{showPreview && ( {showPreview && (

View file

@ -9,7 +9,6 @@ import {
findPrevious, findPrevious,
replaceNext, replaceNext,
replaceAll, replaceAll,
openSearchPanel,
} from '@codemirror/search'; } from '@codemirror/search';
import './SearchBox.scss'; import './SearchBox.scss';
import InputComponent from '@/components/ui/Input/Index.jsx'; import InputComponent from '@/components/ui/Input/Index.jsx';
@ -162,22 +161,3 @@ function SearchPanel({ view }) {
</div> </div>
); );
} }
export const SearchBtn = ({ view }) => {
return (
<div
className="d-flex justify-content-end w-100 position-absolute tw-pt-[3px] tw-pr-[4px] codehinter-search-btn-wrapper"
style={{ top: 0 }}
>
<ButtonComponent
iconOnly
trailingIcon="search01"
size="small"
variant="outline"
ariaLabel="Open search panel"
className="codehinter-search-btn"
onClick={() => openSearchPanel(view)}
/>
</div>
);
};

View file

@ -44,7 +44,5 @@
} }
.code-hinter-wrapper .codehinter-search-btn { .code-hinter-wrapper .codehinter-search-btn {
display: block; z-index: 1000;
padding-top: 1px;
z-index: 10000;
} }

View file

@ -1,12 +1,18 @@
/* eslint-disable import/no-unresolved */ /* eslint-disable import/no-unresolved */
import React, { useEffect, useMemo, useRef, useState } from 'react'; import React, { useEffect, useMemo, useRef, useState, useContext } from 'react';
import { PreviewBox } from './PreviewBox'; import { PreviewBox } from './PreviewBox';
import { ToolTip } from '@/Editor/Inspector/Elements/Components/ToolTip'; import { ToolTip } from '@/Editor/Inspector/Elements/Components/ToolTip';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { camelCase, isEmpty, noop, get } from 'lodash'; import { camelCase, isEmpty, noop, get } from 'lodash';
import CodeMirror from '@uiw/react-codemirror'; import CodeMirror from '@uiw/react-codemirror';
import { javascript } from '@codemirror/lang-javascript'; import { javascript } from '@codemirror/lang-javascript';
import { autocompletion, completionKeymap, completionStatus, acceptCompletion } from '@codemirror/autocomplete'; import {
autocompletion,
completionKeymap,
completionStatus,
acceptCompletion,
startCompletion,
} from '@codemirror/autocomplete';
import { defaultKeymap } from '@codemirror/commands'; import { defaultKeymap } from '@codemirror/commands';
import { keymap } from '@codemirror/view'; import { keymap } from '@codemirror/view';
import FxButton from '../CodeBuilder/Elements/FxButton'; import FxButton from '../CodeBuilder/Elements/FxButton';
@ -22,6 +28,8 @@ import CodeHinter from './CodeHinter';
import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils'; import { removeNestedDoubleCurlyBraces } from '@/_helpers/utils';
import useStore from '@/AppBuilder/_stores/store'; import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow'; import { shallow } from 'zustand/shallow';
import { CodeHinterContext } from '../CodeBuilder/CodeHinterContext';
import { createReferencesLookup } from '@/_stores/utils';
import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks'; import { useQueryPanelKeyHooks } from './useQueryPanelKeyHooks';
const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => { const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...restProps }) => {
@ -73,6 +81,7 @@ const SingleLineCodeEditor = ({ componentName, fieldMeta = {}, componentId, ...r
if (typeof initialValue === 'string' && (initialValue?.includes('components') || initialValue?.includes('queries'))) { if (typeof initialValue === 'string' && (initialValue?.includes('components') || initialValue?.includes('queries'))) {
newInitialValue = replaceIdsWithName(initialValue); newInitialValue = replaceIdsWithName(initialValue);
} }
//! Re render the component when the componentName changes as the initialValue is not updated //! Re render the component when the componentName changes as the initialValue is not updated
// const { variablesExposedForPreview } = useContext(EditorContext) || {}; // const { variablesExposedForPreview } = useContext(EditorContext) || {};
@ -199,9 +208,14 @@ const EditorInput = ({
wrapperRef, wrapperRef,
showSuggestions, showSuggestions,
}) => { }) => {
const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow); const codeHinterContext = useContext(CodeHinterContext);
const { suggestionList: paramHints } = createReferencesLookup(codeHinterContext, true);
const getSuggestions = useStore((state) => state.getSuggestions, shallow); const getSuggestions = useStore((state) => state.getSuggestions, shallow);
const [codeMirrorView, setCodeMirrorView] = useState(undefined);
const getServerSideGlobalSuggestions = useStore((state) => state.getServerSideGlobalSuggestions, shallow);
const { queryPanelKeybindings } = useQueryPanelKeyHooks(onBlurUpdate, currentValue, 'singleline'); const { queryPanelKeybindings } = useQueryPanelKeyHooks(onBlurUpdate, currentValue, 'singleline');
const isInsideQueryManager = useMemo( const isInsideQueryManager = useMemo(
@ -209,16 +223,16 @@ const EditorInput = ({
[wrapperRef.current] [wrapperRef.current]
); );
function autoCompleteExtensionConfig(context) { function autoCompleteExtensionConfig(context) {
const hints = getSuggestions(); const hintsWithoutParamHints = getSuggestions();
const serverHints = getServerSideGlobalSuggestions(isInsideQueryManager); const serverHints = getServerSideGlobalSuggestions(isInsideQueryManager);
const allHints = {
...hints,
appHints: [...hints.appHints, ...serverHints],
};
let word = context.matchBefore(/\w*/); let word = context.matchBefore(/\w*/);
const hints = {
...hintsWithoutParamHints,
appHints: [...hintsWithoutParamHints.appHints, ...serverHints, ...paramHints],
};
const totalReferences = (context.state.doc.toString().match(/{{/g) || []).length; const totalReferences = (context.state.doc.toString().match(/{{/g) || []).length;
let queryInput = context.state.doc.toString(); let queryInput = context.state.doc.toString();
@ -247,17 +261,18 @@ const EditorInput = ({
queryInput = '{{' + currentWord + '}}'; queryInput = '{{' + currentWord + '}}';
} }
let completions = getAutocompletion(queryInput, validationType, allHints, totalReferences, originalQueryInput); let completions = getAutocompletion(queryInput, validationType, hints, totalReferences, originalQueryInput);
return { return {
from: word.from, from: word.from,
options: completions, options: completions,
validFor: /^\{\{.*\}\}$/, validFor: /^\{\{.*\}\}$/,
filter: false,
}; };
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [isInsideQueryManager]); const overRideFunction = React.useCallback((context) => autoCompleteExtensionConfig(context), [isInsideQueryManager, paramHints]);
const autoCompleteConfig = autocompletion({ const autoCompleteConfig = autocompletion({
override: [overRideFunction], override: [overRideFunction],
@ -424,6 +439,9 @@ const EditorInput = ({
ref={previewRef} ref={previewRef}
> >
<CodeMirror <CodeMirror
onCreateEditor={(view) => {
setCodeMirrorView(view);
}}
value={currentValue} value={currentValue}
placeholder={placeholder} placeholder={placeholder}
height={isInsideQueryPane ? '100%' : showLineNumbers ? '400px' : '100%'} height={isInsideQueryPane ? '100%' : showLineNumbers ? '400px' : '100%'}
@ -460,11 +478,16 @@ const EditorInput = ({
theme={theme} theme={theme}
indentWithTab={false} indentWithTab={false}
readOnly={disabled} readOnly={disabled}
onKeyDown={(event) => {
if (event.key === 'Backspace') {
startCompletion(codeMirrorView);
}
}}
/> />
</div> </div>
</ErrorBoundary> </ErrorBoundary >
</CodeHinter.Portal> </CodeHinter.Portal >
</div> </div >
); );
}; };

View file

@ -67,7 +67,8 @@ export const getAutocompletion = (input, fieldType, hints, totalReferences = 1,
originalQueryInput, originalQueryInput,
searchInput searchInput
); );
return orderSuggestions(suggestions, fieldType);
return suggestions;
}; };
function orderSuggestions(suggestions, validationType) { function orderSuggestions(suggestions, validationType) {
@ -90,10 +91,18 @@ export const generateHints = (hints, totalReferences = 1, input, searchText) =>
const hasDepth = currentWord.includes('.'); const hasDepth = currentWord.includes('.');
const lastDepth = getLastSubstring(currentWord); const lastDepth = getLastSubstring(currentWord);
const displayLabel = getLastDepth(displayedHint); let displayLabel = getLastDepth(displayedHint);
if (type != 'js_method') {
const currentWordDepth = currentWord.split('.').length;
displayLabel = hint
.split('.')
.slice(currentWordDepth - 1)
.join('.');
}
return { return {
displayLabel: lastDepth === '' ? displayedHint : displayLabel, displayLabel,
label: displayedHint, label: displayedHint,
info: displayedHint, info: displayedHint,
type: type === 'js_method' ? 'js_methods' : type?.toLowerCase(), type: type === 'js_method' ? 'js_methods' : type?.toLowerCase(),
@ -154,40 +163,24 @@ export const generateHints = (hints, totalReferences = 1, input, searchText) =>
}; };
function filterHintsByDepth(input, hints) { function filterHintsByDepth(input, hints) {
if (input === '') return hints; const inputParts = input.split('.');
const inputDepth = inputParts.length + 1;
const inputDepth = input.includes('.') ? input.split('.').length : 0; const hintsWithDepth = hints.map((hint) => {
const hintParts = hint.hint.split('.');
const filteredHints = hints.filter((cm) => { return {
const hintParts = cm.hint.split('.'); ...hint,
depth: hintParts.length,
let shouldInclude = };
(cm.hint.startsWith(input) && hintParts.length === inputDepth + 1) ||
(cm.hint.startsWith(input) && hintParts.length === inputDepth);
const shouldFuzzyMatch = !shouldInclude ? hintParts.length > inputDepth : false;
if (shouldFuzzyMatch) {
// fuzzy match
let matchedDepth = -1;
for (let i = 0; i < hintParts.length; i++) {
if (hintParts[i].includes(input)) {
matchedDepth = i;
break;
}
}
if (matchedDepth !== -1) {
shouldInclude = hintParts.length === matchedDepth + 1;
}
} else if (input.endsWith('.')) {
shouldInclude = cm.hint.startsWith(input) && hintParts.length === inputDepth;
}
return shouldInclude;
}); });
return filteredHints; const filteredHints = hintsWithDepth.filter((hint) => {
return hint.depth <= inputDepth;
});
const sortedHints = filteredHints.sort((hint1, hint2) => hint1.depth - hint2.depth);
return sortedHints;
} }
export function findNearestSubstring(inputStr, currentCurosorPos) { export function findNearestSubstring(inputStr, currentCurosorPos) {

View file

@ -1,8 +1,20 @@
import React from 'react'; import React, { useState } from 'react';
import Select from '@/_ui/Select'; import Select from '@/_ui/Select';
import { decodeEntities } from '@/_helpers/utils'; import { decodeEntities } from '@/_helpers/utils';
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
export const ChangeDataSource = ({ dataSources, onChange, value, isVersionReleased }) => { export const ChangeDataSource = ({ dataSources, onChange, value, isVersionReleased }) => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
usePopoverObserver(
document.getElementsByClassName('query-details')[0],
document.querySelector('.change-data-source-select.react-select__control'),
document.querySelector('.change-data-source-select.react-select__menu'),
isMenuOpen,
() => (document.querySelector('.change-data-source-select.react-select__menu').style.display = 'block'),
() => (document.querySelector('.change-data-source-select.react-select__menu').style.display = 'none')
);
return ( return (
<Select <Select
className="w-100" className="w-100"
@ -14,6 +26,13 @@ export const ChangeDataSource = ({ dataSources, onChange, value, isVersionReleas
}} }}
useMenuPortal={true} useMenuPortal={true}
isDisabled={isVersionReleased} isDisabled={isVersionReleased}
customClassPrefix="change-data-source-select"
onMenuOpen={() => {
setIsMenuOpen(true);
}}
onMenuClose={() => {
setIsMenuOpen(false);
}}
/> />
); );
}; };

View file

@ -254,7 +254,7 @@ const RunButton = ({ buttonLoadingState }) => {
<ButtonComponent <ButtonComponent
size="medium" size="medium"
variant="secondary" variant="secondary"
onClick={() => runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true)} onClick={() => runQuery(selectedQuery?.id, selectedQuery?.name, undefined, 'edit', {}, true, undefined, true)}
leadingIcon="play01" leadingIcon="play01"
disabled={isInDraft} disabled={isInDraft}
isLoading={isLoading} isLoading={isLoading}

View file

@ -31,6 +31,9 @@ class Restapi extends React.Component {
codeHinterHeight: 32, // Default height codeHinterHeight: 32, // Default height
}; };
this.codeHinterRef = React.createRef(); this.codeHinterRef = React.createRef();
this.isMenuOpenRef = React.createRef();
this.prevIsMenuOpenRef = React.createRef(false);
this.intersectionObserver = null;
this.resizeObserver = null; this.resizeObserver = null;
} }
@ -47,6 +50,9 @@ class Restapi extends React.Component {
if (this.codeHinterRef.current && !this.resizeObserver) { if (this.codeHinterRef.current && !this.resizeObserver) {
this.setupResizeObserver(); this.setupResizeObserver();
} }
if (!this.intersectionObserver) {
this.setupIntersectionObserver();
}
} }
componentDidMount() { componentDidMount() {
@ -75,6 +81,7 @@ class Restapi extends React.Component {
}, 1000); }, 1000);
this.setupResizeObserver(); this.setupResizeObserver();
this.setupIntersectionObserver();
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
@ -84,6 +91,9 @@ class Restapi extends React.Component {
if (this.resizeObserver) { if (this.resizeObserver) {
this.resizeObserver.disconnect(); this.resizeObserver.disconnect();
} }
if (this.intersectionObserver) {
this.intersectionObserver.disconnect();
}
} }
setupResizeObserver() { setupResizeObserver() {
@ -132,6 +142,33 @@ class Restapi extends React.Component {
this.resizeObserver.observe(element); this.resizeObserver.observe(element);
} }
setupIntersectionObserver() {
const container = document.getElementsByClassName('query-details')[0];
const trigger = document.querySelector('.restapi-method-select.react-select__control');
if (this.intersectionObserver) {
this.intersectionObserver.disconnect();
}
this.intersectionObserver = new IntersectionObserver(
([entry]) => {
const popover = document.querySelector('.restapi-method-select.react-select__menu');
if (entry.isIntersecting) {
if (this.prevIsMenuOpenRef.current) {
popover.style.display = 'block';
this.prevIsMenuOpenRef.current = false;
}
} else if (this.isMenuOpenRef.current) {
popover.style.display = 'none';
this.prevIsMenuOpenRef.current = true;
}
},
{ root: container, threshold: [0.5] }
);
this.intersectionObserver.observe(trigger);
}
initizalizeRetryNetworkErrorsToggle = () => { initizalizeRetryNetworkErrorsToggle = () => {
const isRetryNetworkErrorToggleUnused = this.props.options.retry_network_errors === null; const isRetryNetworkErrorToggleUnused = this.props.options.retry_network_errors === null;
if (isRetryNetworkErrorToggleUnused) { if (isRetryNetworkErrorToggleUnused) {
@ -287,6 +324,13 @@ class Restapi extends React.Component {
height={32} height={32}
styles={this.customSelectStyles(this.props.darkMode, 91)} styles={this.customSelectStyles(this.props.darkMode, 91)}
useCustomStyles={true} useCustomStyles={true}
customClassPrefix="restapi-method-select"
onMenuOpen={() => {
this.isMenuOpenRef.current = true;
}}
onMenuClose={() => {
this.isMenuOpenRef.current = false;
}}
/> />
</div> </div>
<div <div

View file

@ -53,7 +53,11 @@ export const BulkUploadPrimaryKey = () => {
<div className="field flex-grow-1 minw-400-w-400"> <div className="field flex-grow-1 minw-400-w-400">
<CodeHinter <CodeHinter
type="basic" type="basic"
initialValue={`{{${JSON.stringify(bulkUpdatePrimaryKey?.rows_update ?? [])}}}`} initialValue={
bulkUpdatePrimaryKey?.rows_update
? `{{${JSON.stringify(bulkUpdatePrimaryKey?.rows_update ?? [])}}}`
: null
}
className="codehinter-plugins" className="codehinter-plugins"
placeholder="{{ [ { 'column1': 'value', ... } ] }}" placeholder="{{ [ { 'column1': 'value', ... } ] }}"
onChange={(newValue) => { onChange={(newValue) => {

View file

@ -0,0 +1,78 @@
import React, { useContext, useEffect } from 'react';
import { TooljetDatabaseContext } from '@/TooljetDatabase/index';
import { resolveReferences } from '@/AppBuilder/CodeEditor/utils';
import CodeHinter from '@/AppBuilder/CodeEditor';
export const BulkUpsertPrimaryKey = () => {
const {
columns,
bulkUpsertPrimaryKey,
handleBulkUpsertRowsOptionChanged,
handlePrimaryKeyOptionChangedForBulkUpsert,
} = useContext(TooljetDatabaseContext);
useEffect(() => {
const primaryKeys = columns.reduce((acc, column) => {
if (column?.keytype === 'PRIMARY KEY' || column?.isPrimaryKey) {
acc.push(column?.accessor);
}
return acc;
}, []);
if (primaryKeys.length > 0) {
handlePrimaryKeyOptionChangedForBulkUpsert(primaryKeys);
}
}, [columns]);
const handleRowsChange = (value) => {
handleBulkUpsertRowsOptionChanged(value);
};
return (
<div className="tab-content-wrapper tj-db-field-wrapper mt-2 d-flex flex-column custom-gap-16">
<div className="field-container d-flex tooljetdb-worflow-operations">
<label className="form-label flex-shrink-0">Primary key</label>
<div
className="field flex-grow-1 minw-400-w-400 px-1"
style={{ height: '28px', background: 'var(--controls-switch-tag)', borderRadius: '6px' }}
>
<input
type="text"
value={bulkUpsertPrimaryKey?.primary_key?.join(', ') || ''}
style={{
width: '100%',
height: '100%',
border: '0',
color: 'var(--text-placeholder)',
background: 'transparent',
}}
disabled
placeholder={''}
/>
</div>
</div>
<div className="field-container d-flex tooljetdb-worflow-operations">
<label className="form-label flex-shrink-0" data-cy="">
Rows to upsert
</label>
<div className="field flex-grow-1 minw-400-w-400">
<CodeHinter
type="basic"
initialValue={
bulkUpsertPrimaryKey?.rows
? typeof bulkUpsertPrimaryKey?.rows === 'string'
? bulkUpsertPrimaryKey?.rows
: JSON.stringify(bulkUpsertPrimaryKey?.rows)
: bulkUpsertPrimaryKey?.rows
}
className="codehinter-plugins"
placeholder="{{ [ { 'column1': 'value', ... } ] }}"
onChange={handleRowsChange}
/>
</div>
</div>
</div>
);
};
export default BulkUpsertPrimaryKey;

View file

@ -1,5 +1,4 @@
import CodeHinter from '@/AppBuilder/CodeEditor'; import CodeHinter from '@/AppBuilder/CodeEditor';
import { resolveReferences } from '@/Editor/CodeEditor/utils';
import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import Trash from '@/_ui/Icon/solidIcons/Trash'; import Trash from '@/_ui/Icon/solidIcons/Trash';
import React from 'react'; import React from 'react';
@ -43,8 +42,7 @@ const RenderColumnUI = ({
placeholder="key" placeholder="key"
onChange={(newValue) => { onChange={(newValue) => {
if (isJSonTypeColumn) { if (isJSonTypeColumn) {
const [_, __, resolvedValue] = resolveReferences(`{{${newValue}}}`); handleValueChange(newValue);
handleValueChange(resolvedValue);
} else { } else {
handleValueChange(newValue); handleValueChange(newValue);
} }

View file

@ -220,7 +220,9 @@ function DataSourceSelect({
if (isFirstPageLoaded && offset >= totalRecords) return; if (isFirstPageLoaded && offset >= totalRecords) return;
if (foreignKeys.length < 1) return; if (foreignKeys.length < 1) return;
setIsLoadingFKDetails(true); setIsLoadingFKDetails(true);
const referencedColumns = foreignKeys.find((item) => item.column_names[0] === cellColumnName); const referencedColumns = Array.isArray(foreignKeys)
? foreignKeys.find((item) => item.column_names[0] === cellColumnName)
: undefined;
if (!referencedColumns?.referenced_column_names?.length) return; if (!referencedColumns?.referenced_column_names?.length) return;
const selectQuery = new PostgrestQueryBuilder(); const selectQuery = new PostgrestQueryBuilder();
@ -709,7 +711,8 @@ const MenuList = ({
...props ...props
}) => { }) => {
const menuListStyles = getStyles('menuList', props); const menuListStyles = getStyles('menuList', props);
const referencedColumnDetails = foreignKeys?.find((item) => item.column_names[0] === cellColumnName); const referencedColumnDetails =
Array.isArray(foreignKeys) && foreignKeys?.find((item) => item?.column_names[0] === cellColumnName);
const handleNavigateToReferencedTable = () => { const handleNavigateToReferencedTable = () => {
const data = { const data = {

View file

@ -16,6 +16,7 @@ import { getPrivateRoute } from '@/_helpers/routes';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { deepClone } from '@/_helpers/utilities/utils.helpers'; import { deepClone } from '@/_helpers/utilities/utils.helpers';
import { BulkUploadPrimaryKey } from './BulkUploadPrimaryKey'; import { BulkUploadPrimaryKey } from './BulkUploadPrimaryKey';
import BulkUpsertPrimaryKey from './BulkUpsertPrimaryKey';
import './styles.scss'; import './styles.scss';
import CodeHinter from '@/AppBuilder/CodeEditor'; import CodeHinter from '@/AppBuilder/CodeEditor';
@ -46,6 +47,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
const [tableForeignKeyInfo, setTableForeignKeyInfo] = useState({}); const [tableForeignKeyInfo, setTableForeignKeyInfo] = useState({});
const [bulkUpdatePrimaryKey, setBulkUpdatePrimaryKey] = useState(() => options['bulk_update_with_primary_key'] || {}); const [bulkUpdatePrimaryKey, setBulkUpdatePrimaryKey] = useState(() => options['bulk_update_with_primary_key'] || {});
const [bulkUpsertPrimaryKey, setBulkUpsertPrimaryKey] = useState(() => options['bulk_upsert_with_primary_key'] || {});
const joinOptions = options['join_table']?.['joins'] || [ const joinOptions = options['join_table']?.['joins'] || [
{ conditions: { conditionsList: [{ leftField: { table: selectedTableId } }] } }, { conditions: { conditionsList: [{ leftField: { table: selectedTableId } }] } },
@ -196,6 +198,11 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [bulkUpdatePrimaryKey]); }, [bulkUpdatePrimaryKey]);
useEffect(() => {
mounted && optionchanged('bulk_upsert_with_primary_key', bulkUpsertPrimaryKey);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [bulkUpsertPrimaryKey]);
useEffect(() => { useEffect(() => {
mounted && optionchanged('update_rows', updateRowsOptions); mounted && optionchanged('update_rows', updateRowsOptions);
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
@ -234,10 +241,18 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
setBulkUpdatePrimaryKey((prev) => ({ ...prev, rows_update: value })); setBulkUpdatePrimaryKey((prev) => ({ ...prev, rows_update: value }));
}; };
const handleBulkUpsertRowsOptionChanged = (value) => {
setBulkUpsertPrimaryKey((prev) => ({ ...prev, rows: value }));
};
const handlePrimaryKeyOptionChangedForBulkUpdate = (value) => { const handlePrimaryKeyOptionChangedForBulkUpdate = (value) => {
setBulkUpdatePrimaryKey((prev) => ({ ...prev, primary_key: value })); setBulkUpdatePrimaryKey((prev) => ({ ...prev, primary_key: value }));
}; };
const handlePrimaryKeyOptionChangedForBulkUpsert = (value) => {
setBulkUpsertPrimaryKey((prev) => ({ ...prev, primary_key: value }));
};
const loadTableInformation = async (tableId, isNewTableAdded) => { const loadTableInformation = async (tableId, isNewTableAdded) => {
const tableDetails = findTableDetails(tableId); const tableDetails = findTableDetails(tableId);
if (tableDetails?.table_name && !tableInfo[tableDetails?.table_name]) { if (tableDetails?.table_name && !tableInfo[tableDetails?.table_name]) {
@ -340,8 +355,11 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
tableForeignKeyInfo, tableForeignKeyInfo,
setTableForeignKeyInfo, setTableForeignKeyInfo,
bulkUpdatePrimaryKey, bulkUpdatePrimaryKey,
bulkUpsertPrimaryKey,
handleBulkUpdateWithPrimaryKeysRowsUpdateOptionChanged, handleBulkUpdateWithPrimaryKeysRowsUpdateOptionChanged,
handleBulkUpsertRowsOptionChanged,
handlePrimaryKeyOptionChangedForBulkUpdate, handlePrimaryKeyOptionChangedForBulkUpdate,
handlePrimaryKeyOptionChangedForBulkUpsert,
}), }),
[ [
organizationId, organizationId,
@ -357,6 +375,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
joinOrderByOptions, joinOrderByOptions,
selectedTableId, selectedTableId,
bulkUpdatePrimaryKey, bulkUpdatePrimaryKey,
bulkUpsertPrimaryKey,
] ]
); );
@ -517,6 +536,8 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
return JoinTable; return JoinTable;
case 'bulk_update_with_primary_key': case 'bulk_update_with_primary_key':
return BulkUploadPrimaryKey; return BulkUploadPrimaryKey;
case 'bulk_upsert_with_primary_key':
return BulkUpsertPrimaryKey;
} }
}; };
@ -527,6 +548,7 @@ const ToolJetDbOperations = ({ optionchanged, options, darkMode, isHorizontalLay
{ label: 'Delete rows', value: 'delete_rows' }, { label: 'Delete rows', value: 'delete_rows' },
{ label: 'Join tables', value: 'join_tables' }, { label: 'Join tables', value: 'join_tables' },
{ label: 'Bulk update with primary key', value: 'bulk_update_with_primary_key' }, { label: 'Bulk update with primary key', value: 'bulk_update_with_primary_key' },
{ label: 'Bulk upsert with primary key', value: 'bulk_upsert_with_primary_key' },
]; ];
const ComponentToRender = getComponent(operation); const ComponentToRender = getComponent(operation);

View file

@ -5,14 +5,25 @@ import CodeHinter from '@/AppBuilder/CodeEditor';
import './workflows-query.scss'; import './workflows-query.scss';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import useStore from '@/AppBuilder/_stores/store'; import useStore from '@/AppBuilder/_stores/store';
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
export function Workflows({ options, optionsChanged, currentState }) { export function Workflows({ options, optionsChanged, currentState }) {
const [workflowOptions, setWorkflowOptions] = useState([]); const [workflowOptions, setWorkflowOptions] = useState([]);
const [isMenuOpen, setIsMenuOpen] = useState(false);
const [_selectedWorkflowId, setSelectedWorkflowId] = useState(undefined); const [_selectedWorkflowId, setSelectedWorkflowId] = useState(undefined);
const [params, setParams] = useState([...(options.params ?? [{ key: '', value: '' }])]); const [params, setParams] = useState([...(options.params ?? [{ key: '', value: '' }])]);
const appId = useStore((state) => state.app.appId); const appId = useStore((state) => state.app.appId);
usePopoverObserver(
document.getElementsByClassName('query-details')[0],
document.querySelector('.workflow-select.react-select__control'),
document.querySelector('.workflow-select.react-select__menu'),
isMenuOpen,
() => (document.querySelector('.workflow-select.react-select__menu').style.display = 'block'),
() => (document.querySelector('.workflow-select.react-select__menu').style.display = 'none')
);
useEffect(() => { useEffect(() => {
appsService.getWorkflows(appId).then(({ workflows }) => { appsService.getWorkflows(appId).then(({ workflows }) => {
setWorkflowOptions( setWorkflowOptions(
@ -50,6 +61,13 @@ export function Workflows({ options, optionsChanged, currentState }) {
customWrap={true} customWrap={true}
width="300px" width="300px"
menuPlacement="bottom" menuPlacement="bottom"
customClassPrefix="workflow-select"
onMenuOpen={() => {
setIsMenuOpen(true);
}}
onMenuClose={() => {
setIsMenuOpen(false);
}}
/> />
<label className="my-2">Params</label> <label className="my-2">Params</label>
<div className="grid"></div> <div className="grid"></div>

View file

@ -0,0 +1,538 @@
import React, { useState, useEffect } from 'react';
import Accordion from '@/_ui/Accordion';
import { EventManager } from '../EventManager';
import { renderElement } from '../Utils';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
import Popover from 'react-bootstrap/Popover';
import List from '@/ToolJetUI/List/List';
import { DragDropContext, Droppable, Draggable } from 'react-beautiful-dnd';
import useStore from '@/AppBuilder/_stores/store';
import CodeHinter from '@/AppBuilder/CodeEditor';
import AddNewButton from '@/ToolJetUI/Buttons/AddNewButton/AddNewButton';
import ListGroup from 'react-bootstrap/ListGroup';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import SortableList from '@/_components/SortableList';
import Trash from '@/_ui/Icon/solidIcons/Trash';
import { shallow } from 'zustand/shallow';
import Switch from '@/Editor/CodeBuilder/Elements/Switch';
import { usePrevious } from '@dnd-kit/utilities';
export function Steps({ componentMeta, darkMode, ...restProps }) {
const {
layoutPropertyChanged,
component,
dataQueries,
paramUpdated,
currentState,
eventsChanged,
apps,
allComponents,
pages,
} = restProps;
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
const isDynamicOptionsEnabled = getResolvedValue(component?.component?.definition?.properties?.advanced?.value);
const variant = component?.component?.definition?.properties?.variant?.value;
const prevVariant = usePrevious(variant)
console.log("variant", component?.component?.definition);
const [options, setOptions] = useState([]);
const [hoveredOptionIndex, setHoveredOptionIndex] = useState(null);
let properties = [];
let additionalActions = [];
let optionsProperties = [];
for (const [key] of Object.entries(componentMeta?.properties)) {
if (componentMeta?.properties[key]?.section === 'additionalActions') {
additionalActions.push(key);
} else if (componentMeta?.properties[key]?.accordian === 'Options') {
optionsProperties.push(key);
} else {
properties.push(key);
}
}
// the default style of "number" & "titles" type are different for completed label
// TODO: Need to revisit this logic when text custom themes are implemented
useEffect(() => {
const completedLabelColor = component?.component?.definition?.styles?.completedLabel?.value;
if (variant !== prevVariant) {
if (variant === "numbers" && completedLabelColor === "#1B1F24") {
paramUpdated({ name: 'completedLabel' }, 'value', "#FFFFFF", 'styles', false, {});
} else if (variant === "titles" && completedLabelColor === "#FFFFFF") {
paramUpdated({ name: 'completedLabel' }, 'value', "#1B1F24", 'styles', false, {});
}
}
}, [variant])
const getItemStyle = (isDragging, draggableStyle) => ({
userSelect: 'none',
...draggableStyle,
});
const updateAllOptionsParams = (options, props) => {
paramUpdated({ name: 'steps' }, 'value', options, 'properties', false, props);
};
const generateNewOptions = () => {
let found = false;
let label = '';
let currentNumber = options.length + 1;
while (!found) {
label = `step ${currentNumber}`;
if (options.find((option) => option.name === label) === undefined) {
found = true;
}
currentNumber += 1;
}
return {
name: label,
id: currentNumber - 1,
tooltip: label,
visible: { value: '{{true}}' },
disabled: { value: '{{false}}' },
};
};
const handleAddOption = () => {
let _option = generateNewOptions();
const _items = [...options, _option];
setOptions(_items);
updateAllOptionsParams(_items);
};
const handleDeleteOption = (index) => {
const _items = options.filter((option, i) => i !== index);
setOptions(_items);
updateAllOptionsParams(_items, { isParamFromDropdownOptions: true });
};
const handleLabelChange = (propertyName, value, index) => {
const _options = options.map((option, i) => {
if (i === index) {
return {
...option,
[propertyName]: value,
};
}
return option;
});
setOptions(_options);
updateAllOptionsParams(_options);
};
const reorderOptions = async (startIndex, endIndex) => {
const result = [...options];
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
setOptions(result);
updateAllOptionsParams(result);
};
const onDragEnd = ({ source, destination }) => {
if (!destination || source?.index === destination?.index) {
return;
}
reorderOptions(source.index, destination.index);
};
const handleOnFxPress = (active, index, key) => {
const _options = options.map((option, i) => {
if (i === index) {
return {
...option,
[key]: {
...option[key],
fxActive: active,
},
};
}
return option;
});
setOptions(_options);
updateAllOptionsParams(_options);
};
const _renderOverlay = (item, index) => {
return (
<Popover className={`${darkMode && 'dark-theme theme-dark'}`} style={{ minWidth: '248px' }}>
<Popover.Body>
<div className="field mb-3" data-cy={`input-and-label-column-name`}>
<label data-cy={`label-column-name`} className="font-weight-500 mb-1 font-size-12">
{'Id'}
</label>
<CodeHinter
type={'basic'}
initialValue={item?.id + ''}
theme={darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={'Option label'}
onChange={(value) => handleLabelChange('id', value, index)}
/>
</div>
<div className="field mb-3" data-cy={`input-and-label-column-name`}>
<label data-cy={`label-column-name`} className="font-weight-500 mb-1 font-size-12">
{'Label'}
</label>
<CodeHinter
type={'basic'}
initialValue={item?.name}
theme={darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={'Option label'}
onChange={(value) => handleLabelChange('name', value, index)}
/>
</div>
<div className="field mb-3" data-cy={`input-and-label-column-name`}>
<label data-cy={`label-column-name`} className="font-weight-500 mb-1 font-size-12">
{'Tooltip'}
</label>
<CodeHinter
type={'basic'}
initialValue={item?.tooltip + ''}
theme={darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
placeholder={'Tooltip'}
onChange={(value) => handleLabelChange('tooltip', value, index)}
/>
</div>
<div className="field mb-2" data-cy={`input-and-label-column-name`}>
<CodeHinter
initialValue={item?.visible?.value}
theme={darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
component={component}
type={'fxEditor'}
paramLabel={'Visibility'}
onChange={(value) =>
handleLabelChange(
'visible',
{
value,
},
index
)
}
paramName={'visible'}
onFxPress={(active) => handleOnFxPress(active, index, 'visible')}
fxActive={item?.visible?.fxActive}
fieldMeta={{
type: 'toggle',
displayName: 'Make editable',
}}
paramType={'toggle'}
/>
</div>
<div className="field" data-cy={`input-and-label-column-name`}>
<CodeHinter
initialValue={item?.disabled?.value}
theme={darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
component={component}
type={'fxEditor'}
paramLabel={'Disable'}
paramName={'disable'}
onChange={(value) => handleLabelChange('disabled', { value }, index)}
onFxPress={(active) => handleOnFxPress(active, index, 'disabled')}
fxActive={item?.disabled?.fxActive}
fieldMeta={{
type: 'toggle',
displayName: 'Make editable',
}}
paramType={'toggle'}
/>
</div>
</Popover.Body>
</Popover>
);
};
const _renderOptions = () => {
return (
<List style={{ marginBottom: '20px' }}>
<DragDropContext
onDragEnd={(result) => {
onDragEnd(result);
}}
>
<Droppable droppableId="droppable">
{({ innerRef, droppableProps, placeholder }) => (
<div className="w-100" {...droppableProps} ref={innerRef}>
{options?.map((item, index) => {
return (
<Draggable key={item.name} draggableId={item.name} index={index}>
{(provided, snapshot) => (
<div
key={index}
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
style={getItemStyle(snapshot.isDragging, provided.draggableProps.style)}
>
<OverlayTrigger
trigger="click"
placement="left"
rootClose
overlay={_renderOverlay(item, index)}
>
<div key={item.name + item.id}>
<ListGroup.Item
style={{ marginBottom: '8px', backgroundColor: 'var(--slate3)' }}
onMouseEnter={() => setHoveredOptionIndex(index)}
onMouseLeave={() => setHoveredOptionIndex(null)}
{...restProps}
>
<div className="row">
<div className="col-auto d-flex align-items-center">
<SortableList.DragHandle show />
</div>
<div className="col text-truncate cursor-pointer" style={{ padding: '0px' }}>
{getResolvedValue(item.name)}
</div>
<div className="col-auto">
{index === hoveredOptionIndex && (
<ButtonSolid
variant="danger"
size="xs"
className={'delete-icon-btn'}
onClick={(e) => {
e.stopPropagation();
handleDeleteOption(index);
}}
>
<span className="d-flex">
<Trash fill={'var(--tomato9)'} width={12} />
</span>
</ButtonSolid>
)}
</div>
</div>
</ListGroup.Item>
</div>
</OverlayTrigger>
</div>
)}
</Draggable>
);
})}
{placeholder}
</div>
)}
</Droppable>
</DragDropContext>
<AddNewButton onClick={handleAddOption} dataCy="add-new-dropdown-option" className="mt-0">
Add new option
</AddNewButton>
</List>
);
};
const isDynamicStepsEnabled = getResolvedValue(component?.component?.definition?.properties?.advanced?.value);
useEffect(() => {
setOptions(constructSteps());
}, [component?.id, isDynamicStepsEnabled]);
const constructSteps = () => {
try {
let optionsValue = isDynamicOptionsEnabled
? component?.component?.definition?.properties?.schema?.value
: component?.component?.definition?.properties?.steps?.value;
let options = [];
if (isDynamicOptionsEnabled || typeof optionsValue === 'string') {
options = getResolvedValue(optionsValue);
} else {
options = optionsValue?.map((option) => option);
}
return options.map((option) => {
const newOption = { ...option };
Object.keys(option).forEach((key) => {
if (typeof option[key]?.value === 'boolean') {
newOption[key]['value'] = `{{${option[key]?.value}}}`;
}
});
if (!('visible' in newOption)) {
newOption['visible'] = { value: '{{true}}' };
}
return newOption;
});
} catch (error) {
return [];
}
};
let items = [];
items.push({
title: 'Steps',
isOpen: true,
children: (
<>
{properties
.filter((property) => !optionsProperties.includes(property))
?.map((property) => {
if (property === 'steps') {
return (
<>
{renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'advanced',
'properties',
currentState,
allComponents
)}
{isDynamicStepsEnabled
? renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'schema',
'properties',
currentState,
allComponents
)
: _renderOptions()}
</>
);
}
// else if (property === 'variant') {
// return renderTest(
// component,
// componentMeta,
// paramUpdated,
// dataQueries,
// 'variant',
// 'properties',
// currentState,
// allComponents,
// handleLabelChange
// );
// }
return renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
property,
'properties',
currentState,
allComponents,
darkMode
);
})}
</>
),
});
items.push({
title: 'Events',
isOpen: true,
children: (
<EventManager
sourceId={component?.id}
eventSourceType="component"
eventMetaDefinition={componentMeta}
dataQueries={dataQueries}
components={allComponents}
eventsChanged={eventsChanged}
apps={apps}
darkMode={darkMode}
pages={pages}
/>
),
});
items.push({
title: `Additional Actions`,
isOpen: true,
children: additionalActions.map((property) => {
return renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
property,
'properties',
currentState,
allComponents,
darkMode,
componentMeta.properties?.[property]?.placeholder
);
}),
});
items.push({
title: 'Devices',
isOpen: true,
children: (
<>
{renderElement(
component,
componentMeta,
layoutPropertyChanged,
dataQueries,
'showOnDesktop',
'others',
currentState,
allComponents
)}
{renderElement(
component,
componentMeta,
layoutPropertyChanged,
dataQueries,
'showOnMobile',
'others',
currentState,
allComponents
)}
</>
),
});
return <Accordion items={items} />;
}
function renderTest(...props) {
const [
component,
componentMeta,
paramUpdated,
dataQueries,
param,
paramType,
currentState,
components = {},
darkMode = false,
placeholder = '',
validationFn,
] = props;
const value = componentMeta?.definition?.properties?.variant?.value;
return (
<div style={{ marginBottom: 8 }}>
<Switch
value={value}
onChange={(e) => {
paramUpdated({ name: 'variant' }, 'value', e, 'properties', false, props);
}}
meta={{
...componentMeta.properties[param],
fullWidth: true,
}}
paramName={param}
isIcon={false}
component={component.component.definition.name}
/>
</div>
);
}

View file

@ -255,7 +255,7 @@ export const PropertiesTabElements = ({
paramType="properties" paramType="properties"
/> />
</div> </div>
{resolveReferences(column?.isEditable) && ( {(column?.fxActiveFields?.includes('isEditable') || resolveReferences(column?.isEditable)) && (
<ValidationProperties <ValidationProperties
column={column} column={column}
index={index} index={index}

View file

@ -1,4 +1,4 @@
import React, { useState, useEffect, useContext } from 'react'; import React, { useState, useEffect, useContext, useRef } from 'react';
import { ActionTypes } from '@/Editor/ActionTypes'; import { ActionTypes } from '@/Editor/ActionTypes';
import OverlayTrigger from 'react-bootstrap/OverlayTrigger'; import OverlayTrigger from 'react-bootstrap/OverlayTrigger';
@ -32,6 +32,9 @@ import useStore from '@/AppBuilder/_stores/store';
import { useEventActions, useEvents } from '@/AppBuilder/_stores/slices/eventsSlice'; import { useEventActions, useEvents } from '@/AppBuilder/_stores/slices/eventsSlice';
import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup'; import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup';
import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem'; import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem';
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import { components as selectComponents } from 'react-select';
export const EventManager = ({ export const EventManager = ({
sourceId, sourceId,
@ -82,6 +85,8 @@ export const EventManager = ({
const [events, setEvents] = useState([]); const [events, setEvents] = useState([]);
const [focusedEventIndex, setFocusedEventIndex] = useState(null); const [focusedEventIndex, setFocusedEventIndex] = useState(null);
const lastFocusedEventIndex = useRef(null);
const shouldSkipOnToggle = useRef(null);
const { t } = useTranslation(); const { t } = useTranslation();
@ -101,8 +106,23 @@ export const EventManager = ({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(currentEvents)]); }, [JSON.stringify(currentEvents)]);
let actionOptions = ActionTypes.map((action) => { let groupedOptions = ActionTypes.reduce((acc, action) => {
return { name: action.name, value: action.id }; const groupName = action.group;
if (!acc[groupName]) {
acc[groupName] = [];
}
acc[groupName].push({
label: action.name,
value: action.id,
});
return acc;
}, {});
let actionOptions = Object.keys(groupedOptions).map((groupName) => {
return { label: groupName, options: groupedOptions[groupName] };
}); });
let checkIfClicksAreInsideOf = document.querySelector('.cm-completionListIncompleteBottom'); let checkIfClicksAreInsideOf = document.querySelector('.cm-completionListIncompleteBottom');
@ -124,6 +144,46 @@ export const EventManager = ({
}), }),
}; };
const actionStyles = {
...styles,
menuList: (base) => ({
...base,
padding: '8px 0 8px 8px',
'&::-webkit-scrollbar': {
width: '10px',
},
'&::-webkit-scrollbar-track': {
background: 'transparent',
},
'&::-webkit-scrollbar-thumb': {
background: '#E4E7EB',
border: '1px solid transparent',
backgroundClip: 'content-box',
},
'&::-webkit-scrollbar-thumb:hover': {
background: '#E4E7EB !important',
border: '1px solid transparent !important',
backgroundClip: 'content-box !important',
},
'&:hover': {
'&::-webkit-scrollbar-thumb': {
background: '#E4E7EB !important',
border: '1px solid transparent !important',
backgroundClip: 'content-box !important',
},
},
}),
group: (base) => ({
...base,
padding: 0,
}),
groupHeading: (base) => ({
...base,
margin: 0,
padding: '0',
}),
};
const actionLookup = Object.fromEntries(ActionTypes.map((actionType) => [actionType.id, actionType])); const actionLookup = Object.fromEntries(ActionTypes.map((actionType) => [actionType.id, actionType]));
let alertTypes = [ let alertTypes = [
@ -394,6 +454,29 @@ export const EventManager = ({
return defaultValue; return defaultValue;
}; };
const formatGroupLabel = (data) => {
if (data.label === 'run-action') return;
return (
<div
className="tw-border-x-0 tw-border-t-0 tw-border-b-[0.5px] tw-border-solid tw-my-[4px]"
style={{ borderColor: 'var(--border-weak)' }}
></div>
);
};
const CustomOption = (props) => {
return (
<selectComponents.Option {...props}>
<div className="d-flex align-items-center">
<div style={{ width: '16px', marginRight: '6px' }}>
{props.isSelected && <SolidIcon name="tickv3" width="16px" height="16px" />}
</div>
<span>{props.label}</span>
</div>
</selectComponents.Option>
);
};
function eventPopover(event, index) { function eventPopover(event, index) {
return ( return (
<Popover <Popover
@ -433,13 +516,17 @@ export const EventManager = ({
<Select <Select
className={`${darkMode ? 'select-search-dark' : 'select-search'} w-100`} className={`${darkMode ? 'select-search-dark' : 'select-search'} w-100`}
options={actionOptions} options={actionOptions}
value={event.actionId} value={actionOptions
.flatMap((group) => group.options)
.find((option) => option.value === event.actionId)}
components={{ Option: CustomOption }}
search={false} search={false}
onChange={(value) => handlerChanged(index, 'actionId', value)} onChange={(value) => handlerChanged(index, 'actionId', value)}
placeholder={t('globals.select', 'Select') + '...'} placeholder={t('globals.select', 'Select') + '...'}
styles={styles} styles={actionStyles}
useMenuPortal={false} useMenuPortal={false}
useCustomStyles={true} useCustomStyles={true}
formatGroupLabel={formatGroupLabel}
/> />
</div> </div>
</div> </div>
@ -1006,10 +1093,21 @@ export const EventManager = ({
placement={popoverPlacement || 'left'} placement={popoverPlacement || 'left'}
rootClose={true} rootClose={true}
overlay={eventPopover(event.event, index)} overlay={eventPopover(event.event, index)}
onHide={() => setFocusedEventIndex(null)}
onToggle={(showing) => { onToggle={(showing) => {
// If the toggle action should be skipped (e.g., due to a previous state change), reset the flag and exit early.
if (shouldSkipOnToggle.current) {
shouldSkipOnToggle.current = false;
return;
}
// If there is already a focused event, set the skip flag to prevent unnecessary state updates.
if (focusedEventIndex !== null && showing) {
shouldSkipOnToggle.current = true;
}
if (showing) { if (showing) {
setFocusedEventIndex(index); setFocusedEventIndex(index);
lastFocusedEventIndex.current = index;
} else { } else {
setFocusedEventIndex(null); setFocusedEventIndex(null);
} }
@ -1018,6 +1116,7 @@ export const EventManager = ({
> >
<div <div
key={index} key={index}
id={`${sourceId}-${index}`}
ref={provided.innerRef} ref={provided.innerRef}
{...provided.draggableProps} {...provided.draggableProps}
{...provided.dragHandleProps} {...provided.dragHandleProps}
@ -1061,6 +1160,17 @@ export const EventManager = ({
); );
}; };
const shouldUsePopoverObserver = events.length !== 0 && eventSourceType === 'data_query';
usePopoverObserver(
shouldUsePopoverObserver ? document.getElementsByClassName('query-details')[0] : null,
document.getElementById(`${sourceId}-${lastFocusedEventIndex.current}`),
document.getElementById('popover-basic'),
focusedEventIndex !== null,
() => (document.getElementById('popover-basic').style.display = 'block'),
() => (document.getElementById('popover-basic').style.display = 'none')
);
if (events.length === 0) { if (events.length === 0) {
return ( return (
<> <>

View file

@ -36,6 +36,7 @@ import Inspect from '@/_ui/Icon/solidIcons/Inspect';
import classNames from 'classnames'; import classNames from 'classnames';
import { EMPTY_ARRAY } from '@/_stores/editorStore'; import { EMPTY_ARRAY } from '@/_stores/editorStore';
import { Select } from './Components/Select'; import { Select } from './Components/Select';
import { Steps } from './Components/Steps.jsx';
import { deepClone } from '@/_helpers/utilities/utils.helpers'; import { deepClone } from '@/_helpers/utilities/utils.helpers';
import useStore from '@/AppBuilder/_stores/store'; import useStore from '@/AppBuilder/_stores/store';
// import { componentTypes } from '@/Editor/WidgetManager/components'; // import { componentTypes } from '@/Editor/WidgetManager/components';
@ -90,6 +91,7 @@ const NEW_REVAMPED_COMPONENTS = [
'VerticalDivider', 'VerticalDivider',
'ModalV2', 'ModalV2',
'Link', 'Link',
'Steps',
]; ];
export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selectedComponentId }) => { export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selectedComponentId }) => {
@ -539,8 +541,8 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte
componentMeta.displayName === 'Toggle Switch (Legacy)' componentMeta.displayName === 'Toggle Switch (Legacy)'
? 'Toggle (Legacy)' ? 'Toggle (Legacy)'
: componentMeta.displayName === 'Toggle Switch' : componentMeta.displayName === 'Toggle Switch'
? 'Toggle Switch' ? 'Toggle Switch'
: componentMeta.component, : componentMeta.component,
})} })}
</small> </small>
</span> </span>
@ -740,6 +742,8 @@ const GetAccordion = React.memo(
case 'DatePickerV2': case 'DatePickerV2':
case 'TimePicker': case 'TimePicker':
return <DatetimePickerV2 {...restProps} componentName={componentName} />; return <DatetimePickerV2 {...restProps} componentName={componentName} />;
case 'Steps':
return <Steps {...restProps} />;
case 'PhoneInput': case 'PhoneInput':
return <PhoneInput {...restProps} />; return <PhoneInput {...restProps} />;
case 'CurrencyInput': case 'CurrencyInput':

View file

@ -14,6 +14,9 @@ const NEW_WIDGETS = [
'TimePicker', 'TimePicker',
'ModalV2', 'ModalV2',
'TextArea', 'TextArea',
'EmailInput',
'PhoneInput',
'CurrencyInput',
]; ];
export const WidgetBox = ({ component, darkMode }) => { export const WidgetBox = ({ component, darkMode }) => {

View file

@ -168,6 +168,7 @@ export const Viewer = ({ id: appId, darkMode, moduleId = 'canvas', switchDarkMod
showViewerNavigation={!isPagesSidebarHidden} showViewerNavigation={!isPagesSidebarHidden}
handleAppEnvironmentChanged={handleAppEnvironmentChanged} handleAppEnvironmentChanged={handleAppEnvironmentChanged}
changeToDarkMode={changeToDarkMode} changeToDarkMode={changeToDarkMode}
switchPage={switchPage}
/> />
)} )}
<div className="sub-section"> <div className="sub-section">

View file

@ -3,8 +3,8 @@ export const containerConfig = {
displayName: 'Container', displayName: 'Container',
description: 'Group components', description: 'Group components',
defaultSize: { defaultSize: {
width: 10, width: 13,
height: 200, height: 480,
}, },
component: 'Container', component: 'Container',
others: { others: {

View file

@ -4,25 +4,38 @@ export const stepsConfig = {
description: 'Step-by-step navigation aid', description: 'Step-by-step navigation aid',
component: 'Steps', component: 'Steps',
properties: { properties: {
variant: {
type: 'switch',
displayName: 'Variant',
validation: { schema: { type: 'string' }, defaultValue: 'titles' },
options: [
{ displayName: 'Label', value: 'titles' },
{ displayName: 'Number', value: 'numbers' },
{ displayName: 'Plain', value: 'plain' },
],
accordian: 'label',
},
schema: {
type: 'code',
displayName: 'Schema',
conditionallyRender: {
key: 'advanced',
value: true,
},
accordian: 'Options',
},
steps: { steps: {
type: 'code', type: 'code',
displayName: 'Steps', displayName: '',
showLabel: false,
validation: { validation: {
schema: { schema: {
type: 'array', type: 'array',
element: { type: 'object', object: { id: { type: 'number' } } }, element: { type: 'object' },
}, },
defaultValue: `[{ name: 'step 1'}, {name: 'step 2'}]`, defaultValue: `[{ name: 'step 1'}, {name: 'step 2'}]`,
}, },
}, },
currentStep: {
type: 'code',
displayName: 'Current step',
validation: {
schema: { type: 'number' },
defaultValue: 1,
},
},
stepsSelectable: { stepsSelectable: {
type: 'toggle', type: 'toggle',
displayName: 'Steps selectable', displayName: 'Steps selectable',
@ -30,7 +43,38 @@ export const stepsConfig = {
schema: { type: 'boolean' }, schema: { type: 'boolean' },
defaultValue: false, defaultValue: false,
}, },
section: 'additionalActions',
}, },
disabledState: {
type: 'toggle',
displayName: 'Disable',
validation: { schema: { type: 'boolean' } },
section: 'additionalActions',
},
visibility: {
type: 'toggle',
displayName: 'Visibility',
validation: { schema: { type: 'boolean' }, defaultValue: true },
section: 'additionalActions',
},
advanced: {
type: 'toggle',
displayName: 'Dynamic options',
validation: {
schema: { type: 'boolean' },
defaultValue: true,
},
accordian: 'Options',
},
currentStep: {
type: 'code',
displayName: 'Current step',
validation: {
schema: { type: 'number' },
defaultValue: 1,
},
},
}, },
defaultSize: { defaultSize: {
width: 22, width: 22,
@ -40,46 +84,126 @@ export const stepsConfig = {
showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' }, showOnDesktop: { type: 'toggle', displayName: 'Show on desktop' },
showOnMobile: { type: 'toggle', displayName: 'Show on mobile' }, showOnMobile: { type: 'toggle', displayName: 'Show on mobile' },
}, },
actions: [
{
handle: 'setStep',
displayName: 'Set step',
params: [
{
handle: 'option',
displayName: 'Option',
},
],
},
{
handle: 'setVisibility',
displayName: 'Set visibility',
params: [{ handle: 'visible', displayName: 'Value', defaultValue: '{{false}}', type: 'toggle' }],
},
{
handle: 'setDisabled',
displayName: 'Set disabled',
params: [{ handle: 'disable', displayName: 'Value', defaultValue: '{{true}}', type: 'toggle' }],
},
{
handle: 'resetSteps',
displayName: 'Reset steps',
params: [],
},
{
handle: 'setStepVisible',
displayName: 'Set step visible',
params: [
{
handle: 'id',
displayName: 'Step id',
},
{
handle: 'visibility',
displayName: 'visibility',
defaultValue: '{{false}}',
type: 'toggle',
},
],
},
{
handle: 'setStepDisable',
displayName: 'Set step disable',
params: [
{
handle: 'id',
displayName: 'Step id',
},
{
handle: 'disabled',
displayName: 'disabled',
defaultValue: '{{true}}',
type: 'toggle',
},
],
},
],
events: { events: {
onSelect: { displayName: 'On select' }, onSelect: { displayName: 'On select' },
}, },
styles: { styles: {
color: { incompletedAccent: {
type: 'colorSwatches', type: 'colorSwatches',
displayName: 'colorSwatches', displayName: 'Incompleted accent',
validation: {
schema: { type: 'string' },
defaultValue: '#CCD1D5',
},
accordian: 'steps',
},
incompletedLabel: {
type: 'colorSwatches',
displayName: 'Incompleted label',
validation: {
schema: { type: 'string' },
defaultValue: '#1B1F24',
},
accordian: 'steps',
},
completedAccent: {
type: 'colorSwatches',
displayName: 'Completed accent',
validation: { validation: {
schema: { type: 'string' }, schema: { type: 'string' },
defaultValue: 'var(--primary-brand)', defaultValue: 'var(--primary-brand)',
}, },
accordian: 'steps',
}, },
textColor: { completedLabel: {
type: 'colorSwatches', type: 'colorSwatches',
displayName: 'Text color', displayName: 'Completed label',
validation: { validation: {
schema: { type: 'string' }, schema: { type: 'string' },
defaultValue: '#000000', defaultValue: '#1B1F24',
}, },
accordian: 'steps',
}, },
theme: { currentStepLabel: {
type: 'select', type: 'colorSwatches',
displayName: 'Theme', displayName: 'Current step label',
validation: {
schema: { type: 'string' },
defaultValue: '#1B1F24',
},
accordian: 'steps',
},
padding: {
type: 'switch',
displayName: 'Padding',
validation: {
schema: { type: 'union', schemas: [{ type: 'string' }, { type: 'number' }] },
defaultValue: 'default',
},
options: [ options: [
{ name: 'titles', value: 'titles' }, { displayName: 'Default', value: 'default' },
{ name: 'numbers', value: 'numbers' }, { displayName: 'None', value: 'none' },
{ name: 'plain', value: 'plain' },
], ],
validation: { accordian: 'container',
schema: { type: 'string' },
defaultValue: 'titles',
},
},
visibility: {
type: 'toggle',
displayName: 'Visibility',
validation: {
schema: { type: 'boolean' },
defaultValue: true,
},
}, },
}, },
exposedVariables: { exposedVariables: {
@ -92,17 +216,35 @@ export const stepsConfig = {
}, },
properties: { properties: {
steps: { steps: {
value: `{{ [{ name: 'step 1', tooltip: 'some tooltip', id: 1},{ name: 'step 2', tooltip: 'some tooltip', id: 2},{ name: 'step 3', tooltip: 'some tooltip', id: 3},{ name: 'step 4', tooltip: 'some tooltip', id: 4},{ name: 'step 5', tooltip: 'some tooltip', id: 5}]}}`, value: [
{ name: 'step 1', tooltip: '', id: 1, visible: { value: true }, disabled: { value: false } },
{ name: 'step 2', tooltip: '', id: 2, visible: { value: true }, disabled: { value: false } },
{ name: 'step 3', tooltip: '', id: 3, visible: { value: true }, disabled: { value: false } },
{ name: 'step 4', tooltip: '', id: 4, visible: { value: true }, disabled: { value: false } },
{ name: 'step 5', tooltip: '', id: 5, visible: { value: true }, disabled: { value: false } },
],
}, },
schema: {
value: `{{ [{ name: 'step 1', tooltip: '', id: 1,visible: true, disabled: false},{ name: 'step 2', tooltip: '', id: 2,visible: true, disabled: false},{ name: 'step 3', tooltip: '', id: 3,visible: true, disabled: false},{ name: 'step 4', tooltip: '', id: 4,visible: true, disabled: false},{ name: 'step 5', tooltip: '', id: 5,visible: true, disabled: false}]}}`,
},
disabledState: { value: '{{false}}' },
variant: { value: 'titles' },
currentStep: { value: '{{3}}' }, currentStep: { value: '{{3}}' },
stepsSelectable: { value: true }, stepsSelectable: { value: true },
advanced: { value: `{{false}}` },
visibility: { value: '{{true}}' },
}, },
events: [], events: [],
styles: { styles: {
visibility: { value: '{{true}}' }, visibility: { value: '{{true}}' },
theme: { value: 'titles' }, // color: { value: '' },
color: { value: 'var(--primary-brand)' }, // textColor: { value: '' },
textColor: { value: '' }, padding: { value: 'default' },
incompletedAccent: { value: '#E4E7EB' },
incompletedLabel: { value: '#1B1F24' },
completedAccent: { value: 'var(--primary-brand)' },
completedLabel: { value: '#1B1F24' },
currentStepLabel: { value: '#1B1F24' },
}, },
}, },
}; };

View file

@ -216,248 +216,237 @@ const useAppData = (appId, moduleId, darkMode, mode = 'edit', { environmentId, v
} }
// const appDataPromise = appService.fetchApp(appId); // const appDataPromise = appService.fetchApp(appId);
appDataPromise appDataPromise.then(async (result) => {
.then(async (result) => { let appData = { ...result };
let appData = { ...result }; let editorEnvironment = result.editorEnvironment;
let editorEnvironment = result.editorEnvironment; if (isPreviewForVersion) {
if (isPreviewForVersion) { const rawDataQueries = appData?.data_queries;
const rawDataQueries = appData?.data_queries; const rawEditingVersionDataQueries = appData?.editing_version?.data_queries;
const rawEditingVersionDataQueries = appData?.editing_version?.data_queries; appData = convertAllKeysToSnakeCase(appData);
appData = convertAllKeysToSnakeCase(appData);
appData.data_queries = rawDataQueries; appData.data_queries = rawDataQueries;
if (appData.editing_version && rawEditingVersionDataQueries) { if (appData.editing_version && rawEditingVersionDataQueries) {
appData.editing_version.data_queries = rawEditingVersionDataQueries; appData.editing_version.data_queries = rawEditingVersionDataQueries;
} }
editorEnvironment = {
id: environmentId,
name: queryParams.env,
};
}
let constantsResp;
if (mode !== 'edit') {
try {
const queryParams = { slug: slug };
const viewerEnvironment = await appEnvironmentService.getEnvironment(environmentId, queryParams);
editorEnvironment = { editorEnvironment = {
id: environmentId, id: viewerEnvironment?.environment?.id,
name: queryParams.env, name: viewerEnvironment?.environment?.name,
}; };
constantsResp =
isPublicAccess && appData.is_public
? await orgEnvironmentConstantService.getConstantsFromPublicApp(slug, viewerEnvironment?.environment?.id)
: await orgEnvironmentConstantService.getConstantsFromApp(slug, viewerEnvironment?.environment?.id);
} catch (error) {
console.error('Error fetching viewer environment:', error);
} }
}
let constantsResp; if (mode === 'edit') {
if (mode !== 'edit') { constantsResp = await orgEnvironmentConstantService.getConstantsFromEnvironment(editorEnvironment?.id);
try { }
const queryParams = { slug: slug }; // get the constants for specific environment
const viewerEnvironment = await appEnvironmentService.getEnvironment(environmentId, queryParams); constantsResp.constants = extractEnvironmentConstantsFromConstantsList(
editorEnvironment = { constantsResp?.constants,
id: viewerEnvironment?.environment?.id, editorEnvironment?.name
name: viewerEnvironment?.environment?.name, );
};
constantsResp =
isPublicAccess && appData.is_public
? await orgEnvironmentConstantService.getConstantsFromPublicApp(
slug,
viewerEnvironment?.environment?.id
)
: await orgEnvironmentConstantService.getConstantsFromApp(slug, viewerEnvironment?.environment?.id);
} catch (error) {
console.error('Error fetching viewer environment:', error);
}
}
if (mode === 'edit') { setIsPublicAccess(isPublicAccess && mode !== 'edit' && appData.is_public);
constantsResp = await orgEnvironmentConstantService.getConstantsFromEnvironment(editorEnvironment?.id);
}
// get the constants for specific environment
constantsResp.constants = extractEnvironmentConstantsFromConstantsList(
constantsResp?.constants,
editorEnvironment?.name
);
setIsPublicAccess(isPublicAccess && mode !== 'edit' && appData.is_public); fetchAndInjectCustomStyles(isPublicAccess && mode !== 'edit' && appData.is_public);
fetchAndInjectCustomStyles(isPublicAccess && mode !== 'edit' && appData.is_public); const pages = appData.pages.map((page) => {
return page;
const pages = appData.pages.map((page) => {
return page;
});
const conversation = appData.ai_conversation;
const docsConversation = appData.ai_conversation_learn;
if (setConversation && setDocsConversation) {
setConversation(conversation);
setDocsConversation(docsConversation);
// important to control ai inputs
getCreditBalance();
}
let showWalkthrough = true;
// if app was created from propmt, and no earlier messages are present in the conversation, send the prompt message
// handles the getappdataby slug api call. Gets the homePageId from the appData.
const homePageId =
appData.editing_version?.homePageId || appData.editing_version?.home_page_id || appData.home_page_id;
setApp({
appName: appData.name,
appId: appData.id,
slug: appData.slug,
currentAppEnvironmentId: editorEnvironment.id,
isMaintenanceOn:
'is_maintenance_on' in result
? result.is_maintenance_on
: 'isMaintenanceOn' in result
? result.isMaintenanceOn
: false,
organizationId: appData.organizationId || appData.organization_id,
homePageId: homePageId,
isPublic: appData.is_public,
creationMode: appData.creation_mode,
});
setIsEditorFreezed(appData.should_freeze_editor);
const global_settings = mapKeys(
appData.editing_version?.global_settings || appData.global_settings,
(value, key) => camelCase(key)
);
if (!global_settings?.theme) {
global_settings.theme = baseTheme;
}
setGlobalSettings(global_settings);
setPages(pages, moduleId);
setPageSettings(
computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings))
);
// set starting page as homepage initially
let startingPage = appData.pages.find((page) => page.id === homePageId);
//no access to homepage, set to the next available page
if (startingPage?.restricted) {
startingPage = appData.pages.find((page) => !page?.restricted);
}
if (initialLoadRef.current) {
// if initial load, check if the path has a page handle and set that as the starting page
const initialLoadPath = location.pathname.split('/').pop();
const page = appData.pages.find((page) => page.handle === initialLoadPath && !page.isPageGroup);
if (page) {
// if page is disabled, and not editing redirect to home page
const shouldRedirect = page?.restricted || (mode !== 'edit' && page?.disabled);
if (shouldRedirect) {
const newUrl = window.location.href.replace(initialLoadPath, startingPage.handle);
window.history.replaceState(null, null, newUrl);
if (page?.restricted) {
toast.error('Access to this page is restricted. Contact admin to know more.', {
className: 'text-nowrap w-auto mw-100',
});
}
} else {
startingPage = page;
}
}
// navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${startingPage.handle}`);
}
// Add page id and handle to the state on initial load
const currentState = window.history.state || {};
const pageInfo = {
id: startingPage.id,
handle: startingPage.handle,
};
const newState = { ...currentState, ...pageInfo };
window.history.replaceState(newState, '', window.location.href);
setCurrentPageHandle(startingPage.handle);
updateFeatureAccess();
setCurrentPageId(startingPage.id, moduleId);
setResolvedPageConstants({
id: startingPage?.id,
handle: startingPage?.handle,
name: startingPage?.name,
});
setComponentNameIdMapping(moduleId);
updateEventsField('events', appData.events);
setCurrentVersionId(appData.editing_version?.id || appData.current_version_id);
setAppHomePageId(homePageId);
const queryData =
isPublicAccess || (mode !== 'edit' && appData.is_public)
? appData
: await dataqueryService.getAll(appData.editing_version?.id || appData.current_version_id);
const dataQueries = queryData.data_queries || queryData?.editing_version?.data_queries;
dataQueries.forEach((query) => normalizeQueryTransformationOptions(query));
setQueries(dataQueries);
if (dataQueries?.length > 0) {
setSelectedQuery(dataQueries[0]?.id);
initialiseResolvedQuery(dataQueries.map((query) => query.id));
}
const constants = constantsResp?.constants;
if (constants) {
const orgConstants = {};
const orgSecrets = {};
constants.map((constant) => {
if (constant.type !== 'Secret') {
orgConstants[constant.name] = constant.value;
} else {
orgSecrets[constant.name] = constant.value;
}
});
setResolvedConstants(orgConstants);
setSecrets(orgSecrets);
}
setQueryMapping(moduleId);
setResolvedGlobals('environment', editorEnvironment);
setResolvedGlobals('mode', { value: mode });
setResolvedGlobals('currentUser', {
...user,
groups: currentSession?.groups,
role: currentSession?.role?.name,
ssoUserInfo: currentSession?.ssoUserInfo,
...(currentSession?.currentUser?.metadata && !isEmpty(currentSession?.currentUser?.metadata)
? { metadata: currentSession?.currentUser?.metadata }
: {}),
});
setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search))));
initDependencyGraph(moduleId);
setCurrentMode(mode); // TODO: set mode based on the slug/appDef
if (
state.ai &&
state?.prompt &&
initialLoadRef.current &&
(conversation?.aiConversationMessages || []).length === 0
) {
setSelectedSidebarItem('tooljetai');
toggleLeftSidebar('true');
sendMessage(state.prompt);
setConversationZeroState(true);
showWalkthrough = false;
}
// fetchDataSources(appData.editing_version.id, editorEnvironment.id);
if (!isPublicAccess) {
const envFromQueryParams = mode === 'view' && new URLSearchParams(location?.search)?.get('env');
useStore.getState().init(appData.editing_version?.id || appData.current_version_id, envFromQueryParams);
fetchGlobalDataSources(
appData.organization_id,
appData.editing_version?.id || appData.current_version_id,
editorEnvironment.id
);
}
useStore.getState().updateEditingVersion(appData.editing_version?.id || appData.current_version_id); //check if this is needed
updateReleasedVersionId(appData.current_version_id);
setEditorLoading(false);
initialLoadRef.current = false;
// only show if app is not created from prompt
if (showWalkthrough) initEditorWalkThrough();
checkAndSetTrueBuildSuggestionsFlag();
return () => {
document.title = retrieveWhiteLabelText();
};
})
.catch((error) => {
if (isPublicAccess) {
if (mode !== 'edit') {
handleError('view', error);
}
}
}); });
const conversation = appData.ai_conversation;
const docsConversation = appData.ai_conversation_learn;
if (setConversation && setDocsConversation) {
setConversation(conversation);
setDocsConversation(docsConversation);
// important to control ai inputs
getCreditBalance();
}
let showWalkthrough = true;
// if app was created from propmt, and no earlier messages are present in the conversation, send the prompt message
// handles the getappdataby slug api call. Gets the homePageId from the appData.
const homePageId =
appData.editing_version?.homePageId || appData.editing_version?.home_page_id || appData.home_page_id;
setApp({
appName: appData.name,
appId: appData.id,
slug: appData.slug,
currentAppEnvironmentId: editorEnvironment.id,
isMaintenanceOn:
'is_maintenance_on' in result
? result.is_maintenance_on
: 'isMaintenanceOn' in result
? result.isMaintenanceOn
: false,
organizationId: appData.organizationId || appData.organization_id,
homePageId: homePageId,
isPublic: appData.is_public,
creationMode: appData.creation_mode,
});
setIsEditorFreezed(appData.should_freeze_editor);
const global_settings = mapKeys(
appData.editing_version?.global_settings || appData.global_settings,
(value, key) => camelCase(key)
);
if (!global_settings?.theme) {
global_settings.theme = baseTheme;
}
setGlobalSettings(global_settings);
setPages(pages, moduleId);
setPageSettings(
computePageSettings(deepCamelCase(appData?.editing_version?.page_settings ?? appData?.page_settings))
);
// set starting page as homepage initially
let startingPage = appData.pages.find((page) => page.id === homePageId);
//no access to homepage, set to the next available page
if (startingPage?.restricted) {
startingPage = appData.pages.find((page) => !page?.restricted);
}
if (initialLoadRef.current) {
// if initial load, check if the path has a page handle and set that as the starting page
const initialLoadPath = location.pathname.split('/').pop();
const page = appData.pages.find((page) => page.handle === initialLoadPath && !page.isPageGroup);
if (page) {
// if page is disabled, and not editing redirect to home page
const shouldRedirect = page?.restricted || (mode !== 'edit' && page?.disabled);
if (shouldRedirect) {
const newUrl = window.location.href.replace(initialLoadPath, startingPage.handle);
window.history.replaceState(null, null, newUrl);
if (page?.restricted) {
toast.error('Access to this page is restricted. Contact admin to know more.', {
className: 'text-nowrap w-auto mw-100',
});
}
} else {
startingPage = page;
}
}
// navigate(`/${getWorkspaceId()}/apps/${slug ?? appId}/${startingPage.handle}`);
}
// Add page id and handle to the state on initial load
const currentState = window.history.state || {};
const pageInfo = {
id: startingPage.id,
handle: startingPage.handle,
};
const newState = { ...currentState, ...pageInfo };
window.history.replaceState(newState, '', window.location.href);
setCurrentPageHandle(startingPage.handle);
updateFeatureAccess();
setCurrentPageId(startingPage.id, moduleId);
setResolvedPageConstants({
id: startingPage?.id,
handle: startingPage?.handle,
name: startingPage?.name,
});
setComponentNameIdMapping(moduleId);
updateEventsField('events', appData.events);
setCurrentVersionId(appData.editing_version?.id || appData.current_version_id);
setAppHomePageId(homePageId);
const queryData =
isPublicAccess || (mode !== 'edit' && appData.is_public)
? appData
: await dataqueryService.getAll(appData.editing_version?.id || appData.current_version_id);
const dataQueries = queryData.data_queries || queryData?.editing_version?.data_queries;
dataQueries.forEach((query) => normalizeQueryTransformationOptions(query));
setQueries(dataQueries);
if (dataQueries?.length > 0) {
setSelectedQuery(dataQueries[0]?.id);
initialiseResolvedQuery(dataQueries.map((query) => query.id));
}
const constants = constantsResp?.constants;
if (constants) {
const orgConstants = {};
const orgSecrets = {};
constants.map((constant) => {
if (constant.type !== 'Secret') {
orgConstants[constant.name] = constant.value;
} else {
orgSecrets[constant.name] = constant.value;
}
});
setResolvedConstants(orgConstants);
setSecrets(orgSecrets);
}
setQueryMapping(moduleId);
setResolvedGlobals('environment', editorEnvironment);
setResolvedGlobals('mode', { value: mode });
setResolvedGlobals('currentUser', {
...user,
groups: currentSession?.groups,
role: currentSession?.role?.name,
ssoUserInfo: currentSession?.ssoUserInfo,
...(currentSession?.currentUser?.metadata && !isEmpty(currentSession?.currentUser?.metadata)
? { metadata: currentSession?.currentUser?.metadata }
: {}),
});
setResolvedGlobals('urlparams', JSON.parse(JSON.stringify(queryString.parse(location?.search))));
initDependencyGraph(moduleId);
setCurrentMode(mode); // TODO: set mode based on the slug/appDef
if (
state.ai &&
state?.prompt &&
initialLoadRef.current &&
(conversation?.aiConversationMessages || []).length === 0
) {
setSelectedSidebarItem('tooljetai');
toggleLeftSidebar('true');
sendMessage(state.prompt);
setConversationZeroState(true);
showWalkthrough = false;
}
// fetchDataSources(appData.editing_version.id, editorEnvironment.id);
if (!isPublicAccess) {
const envFromQueryParams = mode === 'view' && new URLSearchParams(location?.search)?.get('env');
useStore.getState().init(appData.editing_version?.id || appData.current_version_id, envFromQueryParams);
fetchGlobalDataSources(
appData.organization_id,
appData.editing_version?.id || appData.current_version_id,
editorEnvironment.id
);
}
useStore.getState().updateEditingVersion(appData.editing_version?.id || appData.current_version_id); //check if this is needed
updateReleasedVersionId(appData.current_version_id);
setEditorLoading(false);
initialLoadRef.current = false;
// only show if app is not created from prompt
if (showWalkthrough) initEditorWalkThrough();
checkAndSetTrueBuildSuggestionsFlag();
return () => {
document.title = retrieveWhiteLabelText();
};
});
}, [setApp, setEditorLoading, currentSession]); }, [setApp, setEditorLoading, currentSession]);
useEffect(() => { useEffect(() => {

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