Merge branch 'appbuilder/sprint-11' into feat/steps-v2

This commit is contained in:
johnsoncherian 2025-04-10 09:50:01 +05:30
commit 711dabe8ad
478 changed files with 18628 additions and 4319 deletions

View file

@ -11,7 +11,7 @@ jobs:
steps:
- name: Checkout code to main for Beta CE edition
- name: Checkout code to main for pre-release CE edition
if: "!contains(github.event.release.tag_name, 'ce-lts')"
uses: actions/checkout@v2
with:
@ -44,7 +44,7 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker image for Beta tag
- name: Build and Push Docker image for pre-release tag
if: "!contains(github.event.release.tag_name, '-ce-lts')"
uses: docker/build-push-action@v4
with:
@ -98,7 +98,7 @@ jobs:
if: "${{ github.event.release }}"
steps:
- name: Checkout code to main for Beta EE edition
- name: Checkout code to main for pre-release EE edition
if: "!contains(github.event.release.tag_name, 'ee-lts')"
uses: actions/checkout@v2
with:
@ -132,13 +132,13 @@ jobs:
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker image for Beta tag
- name: Build and Push Docker image for pre-release tag
if: "!contains(github.event.release.tag_name, '-ee-lts')"
uses: docker/build-push-action@v4
with:
context: .
args: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
file: docker/ee-production.Dockerfile
file: docker/ee/ee-production.Dockerfile
push: true
tags: tooljet/tooljet-ee:${{ github.event.release.tag_name }},tooljet/tooljet-ee:ee-lts-latest,tooljet/tooljet:ee-lts-latest,tooljet/tooljet:${{ github.event.release.tag_name }}
platforms: linux/amd64
@ -172,61 +172,61 @@ jobs:
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
build-tooljet-image-for-cloud-edtion:
# commented out for now, since cloud modularisation is not yet ready
runs-on: ubuntu-latest
if: "${{ github.event.release }}"
# build-tooljet-image-for-cloud-edtion:
steps:
- name: Checkout code to LTS for Cloud LTS edition
if: "contains(github.event.release.tag_name, '-cloud-lts')"
uses: actions/checkout@v2
with:
ref: refs/heads/lts-4.0
# runs-on: ubuntu-latest
# if: "${{ github.event.release }}"
# Create Docker Buildx builder with platform configuration
- name: Set up Docker Buildx
run: |
mkdir -p ~/.docker/cli-plugins
curl -SL https://github.com/docker/buildx/releases/download/v0.11.0/buildx-v0.11.0.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
chmod a+x ~/.docker/cli-plugins/docker-buildx
docker buildx create --name mybuilder --platform linux/arm64,linux/amd64,linux/amd64/v2,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
docker buildx use mybuilder
# steps:
# - name: Checkout code to LTS for Cloud LTS edition
# if: "contains(github.event.release.tag_name, '-cloud-lts')"
# uses: actions/checkout@v2
# with:
# ref: refs/heads/lts-4.0
- name: Set DOCKER_CLI_EXPERIMENTAL
run: echo "DOCKER_CLI_EXPERIMENTAL=enabled" >> $GITHUB_ENV
# # Create Docker Buildx builder with platform configuration
# - name: Set up Docker Buildx
# run: |
# mkdir -p ~/.docker/cli-plugins
# curl -SL https://github.com/docker/buildx/releases/download/v0.11.0/buildx-v0.11.0.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
# chmod a+x ~/.docker/cli-plugins/docker-buildx
# docker buildx create --name mybuilder --platform linux/arm64,linux/amd64,linux/amd64/v2,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
# docker buildx use mybuilder
- name: use mybuilder buildx
run: docker buildx use mybuilder
# - name: Set DOCKER_CLI_EXPERIMENTAL
# run: echo "DOCKER_CLI_EXPERIMENTAL=enabled" >> $GITHUB_ENV
- name: Docker Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
# - name: use mybuilder buildx
# run: docker buildx use mybuilder
- name: Build and Push Docker image for LTS tag
if: "contains(github.event.release.tag_name, '-cloud-lts')"
uses: docker/build-push-action@v4
with:
context: .
args: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
file: docker/cloud/cloud-server.Dockerfile
push: true
tags: tooljet/saas:${{ github.event.release.tag_name }}
platforms: linux/amd64
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
# - name: Docker Login
# uses: docker/login-action@v2
# with:
# username: ${{ secrets.DOCKER_USERNAME }}
# password: ${{ secrets.DOCKER_PASSWORD }}
- name: Send Slack Notification
run: |
if [[ "${{ job.status }}" == "success" ]]; then
message="ToolJet cloud image published:\n\`tooljet/saas:${{ github.event.release.tag_name }}\`"
else
message="Job '${{ env.JOB_NAME }}' failed! Image built:\n\`tooljet/saas:${{ github.event.release.tag_name }}\`"
fi
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
# - name: Build and Push Docker image for LTS tag
# if: "contains(github.event.release.tag_name, '-cloud-lts')"
# uses: docker/build-push-action@v4
# with:
# context: .
# args: ${{ secrets.CUSTOM_GITHUB_TOKEN }}
# file: docker/cloud/cloud-server.Dockerfile
# push: true
# tags: tooljet/saas:${{ github.event.release.tag_name }}
# platforms: linux/amd64
# env:
# DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
# DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
# - name: Send Slack Notification
# run: |
# if [[ "${{ job.status }}" == "success" ]]; then
# message="ToolJet cloud image published:\n\`tooljet/saas:${{ github.event.release.tag_name }}\`"
# else
# message="Job '${{ env.JOB_NAME }}' failed! Image built:\n\`tooljet/saas:${{ github.event.release.tag_name }}\`"
# fi
# curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}

37
.github/workflows/docs-netlify.yml vendored Normal file
View file

@ -0,0 +1,37 @@
name: Deploy to Netlify
on:
workflow_dispatch:
push:
branches:
- develop
paths:
- docs/**
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 18.18.2
- name: Install dependencies
run: npm install
working-directory: docs
- name: Build the project
run: GTM=${{ secrets.GTM }} ALGOLIA_API_KEY=${{ secrets.ALGOLIA_API_KEY }} npm run build
working-directory: docs
- name: Deploy to Netlify
run: |
npm install -g netlify-cli
netlify deploy --prod --dir=docs/build --auth=$NETLIFY_AUTH_TOKEN --site=${{ secrets.NETLIFY_SITE_ID }}
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}

View file

@ -1,16 +0,0 @@
name: Deploy docs to Netlify
on:
workflow_dispatch:
push:
branches:
- develop
paths:
- docs/**
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Trigger hook to deploy docs on Netlify
run: curl -X POST -d {} ${{ secrets.NETLIFY_HOOK }}

View file

@ -13,12 +13,42 @@ permissions:
jobs:
# Community Edition
create-ce-review-app:
if: ${{ github.event.action == 'labeled' && (github.event.label.name == 'create-ce-review-app' || github.event.label.name == 'review-app') }}
runs-on: ubuntu-latest
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 CE
id: create-ce-deployment
run: |
@ -34,7 +64,7 @@ jobs:
"name": "ToolJet CE PR #${{ env.PR_NUMBER }}",
"notifyOnFail": "default",
"ownerId": "tea-caeo4bj19n072h3dddc0",
"repo": "https://github.com/ToolJet/ToolJet",
"repo": "'"$REPO_URL"'",
"slug": "tooljet-ce-pr-${{ env.PR_NUMBER }}",
"suspended": "not_suspended",
"suspenders": [],

View file

@ -1,217 +0,0 @@
name: Tooljet release docker images build
on:
release:
types: [published]
workflow_dispatch:
inputs:
job-to-run:
description: Enter the job name (tooljet-ce)
options: ["tooljet-ce"]
required: true
image:
description: "Enter the latest image tag"
required: true
jobs:
build-tooljet-ce-image:
runs-on: ubuntu-latest
if: "${{ github.event.release }}"
steps:
- name: Checkout code to main
if: "!contains(github.event.release.tag_name, 'ce-lts')"
uses: actions/checkout@v2
with:
ref: refs/heads/main
- name: Checkout code to LTS-2.50
if: "contains(github.event.release.tag_name, '2.50')"
uses: actions/checkout@v2
with:
ref: refs/heads/lts-2.50
- name: Checkout code to LTS-3.0
if: "contains(github.event.release.tag_name, '-ce-lts')"
uses: actions/checkout@v2
with:
ref: refs/heads/lts-3.0
# Create Docker Buildx builder with platform configuration
- name: Set up Docker Buildx
run: |
mkdir -p ~/.docker/cli-plugins
curl -SL https://github.com/docker/buildx/releases/download/v0.11.0/buildx-v0.11.0.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
chmod a+x ~/.docker/cli-plugins/docker-buildx
docker buildx create --name mybuilder --platform linux/arm64,linux/amd64,linux/amd64/v2,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
docker buildx use mybuilder
- name: Set DOCKER_CLI_EXPERIMENTAL
run: echo "DOCKER_CLI_EXPERIMENTAL=enabled" >> $GITHUB_ENV
- name: use mybuilder buildx
run: docker buildx use mybuilder
- name: Docker Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker image for beta tag
if: "!contains(github.event.release.tag_name, '-ce-lts')"
uses: docker/build-push-action@v4
with:
context: .
file: docker/production.Dockerfile
push: true
tags: tooljet/tooljet-ce:${{ github.event.release.tag_name }},tooljet/tooljet-ce:ce-latest
platforms: linux/amd64
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker image for LTS 2.50 tag
if: "contains(github.event.release.tag_name, '2.50')"
uses: docker/build-push-action@v4
with:
context: .
file: docker/production.Dockerfile
push: true
tags: tooljet/tooljet-ce:${{ github.event.release.tag_name }}
platforms: linux/amd64
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker image for LTS 3.0 tag
if: "contains(github.event.release.tag_name, '-ce-lts')"
uses: docker/build-push-action@v4
with:
context: .
file: docker/production.Dockerfile
push: true
tags: tooljet/tooljet-ce:${{ github.event.release.tag_name }},tooljet/tooljet-ce:ce-lts-latest
platforms: linux/amd64
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
- name: Send Slack Notification
run: |
if [[ "${{ job.status }}" == "success" ]]; then
message="ToolJet community image published:\n\`tooljet/tooljet-ce:${{ github.event.release.tag_name }}\`"
else
message="Job '${{ env.JOB_NAME }}' failed! tooljet/tooljet-ce:${{ github.event.release.tag_name }}"
fi
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
# #Below code helps to trigger the workflow separately
tooljet-ce:
runs-on: ubuntu-latest
if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.job-to-run == 'tooljet-ce' }}
steps:
- name: Checkout code
uses: actions/checkout@v2
with:
ref: main
# Create Docker Buildx builder with platform configuration
- name: Set up Docker Buildx
run: |
mkdir -p ~/.docker/cli-plugins
curl -SL https://github.com/docker/buildx/releases/download/v0.11.0/buildx-v0.11.0.linux-amd64 -o ~/.docker/cli-plugins/docker-buildx
chmod a+x ~/.docker/cli-plugins/docker-buildx
docker buildx create --name mybuilder --platform linux/arm64,linux/amd64,linux/amd64/v2,linux/riscv64,linux/ppc64le,linux/s390x,linux/386,linux/mips64le,linux/mips64,linux/arm/v7,linux/arm/v6
docker buildx use mybuilder
- name: Set DOCKER_CLI_EXPERIMENTAL
run: echo "DOCKER_CLI_EXPERIMENTAL=enabled" >> $GITHUB_ENV
- name: use mybuilder buildx
run: docker buildx use mybuilder
- name: Docker Login
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker image
if: "!contains(github.event.release.tag_name, 'CE-LTS')"
uses: docker/build-push-action@v4
with:
context: .
file: docker/production.Dockerfile
push: true
tags: tooljet/tooljet-ce:${{ github.event.inputs.image }},tooljet/tooljet-ce:latest
platforms: linux/amd64
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push Docker image
if: "contains(github.event.release.tag_name, 'CE-LTS')"
uses: docker/build-push-action@v4
with:
context: .
file: docker/production.Dockerfile
push: true
tags: tooljet/tooljet-ce:${{ github.event.inputs.image }},tooljet/tooljet-ce:CE-LTS-latest
platforms: linux/amd64
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
- name: Send Slack Notification
run: |
if [[ "${{ job.status }}" == "success" ]]; then
message="ToolJet community image published:\n\`tooljet/tooljet-ce:${{ github.event.inputs.image }}\`"
else
message="Job '${{ env.JOB_NAME }}' failed! tooljet/tooljet-ce:${{ github.event.inputs.image }}"
fi
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL }}
update-lts-machine:
runs-on: ubuntu-latest
needs: build-tooljet-ce-image
if: "contains(github.event.release.tag_name, 'CE-LTS')"
steps:
- name: SSH into GCP VM instance
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.GCP_CE_LTS_INSTANCE_IP }}
username: ${{ secrets.GCP_USERNAME }}
key: ${{ secrets.EC2_INSTANCE_SSH_KEY }}
script: |
ls -lah
# Stop the Docker containers
sudo docker-compose down
# Check remaining images
sudo docker images
# Remove the existing tooljet/* images
sudo docker images -a | grep 'tooljet/' | awk '{print $3}' | xargs sudo docker rmi -f
# Check remaining images
sudo docker images
# Update docker-compose.yml with the new image for tooljet service
sed -i '/^[[:space:]]*tooljet:/,/^[[:space:]]*[^[:space:]]/ { /^[[:space:]]*image:/s|image:.*|image: tooljet/tooljet-ce:'"${{ github.event.release.tag_name }}"'| }' docker-compose.yml
# check the updated docker-compose.yml file
cat docker-compose.yml
# Start the Docker containers
sudo docker-compose up -d
#View containers
sudo docker ps

651
.github/workflows/vulnerability-ci.yml vendored Normal file
View file

@ -0,0 +1,651 @@
name: Vulnerability CI
# Controls when the workflow will run
on:
pull_request:
types: [labeled, unlabeled, closed]
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Schedule the workflow to run every two weeks once
schedule:
- cron: '30 5 */14 * *'
jobs:
PeriodicVulnerability-CheckOn-frontend-code:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: refs/heads/main
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: npm --prefix frontend install
- name: Running security audit
run: npm --prefix server audit --json > Periodic-frontend-audit.json
continue-on-error: true
- name: Parse audit summary
id: parse-audit
run: |
vulnerabilities=$(jq '.metadata.vulnerabilities' Periodic-frontend-audit.json)
moderate=$(echo $vulnerabilities | jq '.moderate')
high=$(echo $vulnerabilities | jq '.high')
critical=$(echo $vulnerabilities | jq '.critical')
echo "::set-output name=moderate::$moderate"
echo "::set-output name=high::$high"
echo "::set-output name=critical::$critical"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: Periodic-frontend-audit-report
path: Periodic-frontend-audit.json
- name: Send Slack Notification
run: |
message="Periodic Security Audit Report Of Frontend directory\n
Node module vulnerabilities summary:\n
🔴 Critical: ${{ steps.parse-audit.outputs.critical }}\n
🟠 High: ${{ steps.parse-audit.outputs.high }}\n
🟡 Moderate: ${{ steps.parse-audit.outputs.moderate }}\n
\nDownload Audit Report: http://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL_VUR }}
PeriodicVulnerability-CheckOn-server-code:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: refs/heads/main
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: npm --prefix server install
- name: Running security audit
run: npm --prefix server audit --json > Periodic-server-audit.json
continue-on-error: true
- name: Parse audit summary
id: parse-audit
run: |
vulnerabilities=$(jq '.metadata.vulnerabilities' Periodic-server-audit.json)
moderate=$(echo $vulnerabilities | jq '.moderate')
high=$(echo $vulnerabilities | jq '.high')
critical=$(echo $vulnerabilities | jq '.critical')
echo "::set-output name=moderate::$moderate"
echo "::set-output name=high::$high"
echo "::set-output name=critical::$critical"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: Periodic-server-audit-report
path: Periodic-server-audit.json
- name: Send Slack Notification
run: |
message="### Periodic Security Audit Report Of Server directory\n
Node module vulnerabilities summary:\n
🔴 Critical: ${{ steps.parse-audit.outputs.critical }}\n
🟠 High: ${{ steps.parse-audit.outputs.high }}\n
🟡 Moderate: ${{ steps.parse-audit.outputs.moderate }}\n
\nDownload Audit Report: http://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL_VUR }}
PeriodicVulnerability-CheckOn-marketplace-code:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: refs/heads/main
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: npm --prefix marketplace install
- name: Running security audit
run: npm --prefix marketplace audit --json > Periodic-marketplace-audit.json
continue-on-error: true
- name: Parse audit summary
id: parse-audit
run: |
vulnerabilities=$(jq '.metadata.vulnerabilities' Periodic-marketplace-audit.json)
moderate=$(echo $vulnerabilities | jq '.moderate')
high=$(echo $vulnerabilities | jq '.high')
critical=$(echo $vulnerabilities | jq '.critical')
echo "::set-output name=moderate::$moderate"
echo "::set-output name=high::$high"
echo "::set-output name=critical::$critical"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: Periodic-marketplace-audit-report
path: Periodic-marketplace-audit.json
- name: Send Slack Notification
run: |
message="Periodic Security Audit Report Of Marketplace directory\n
Node module vulnerabilities summary:\n
🔴 Critical: ${{ steps.parse-audit.outputs.critical }}\n
🟠 High: ${{ steps.parse-audit.outputs.high }}\n
🟡 Moderate: ${{ steps.parse-audit.outputs.moderate }}\n
\nDownload Audit Report: http://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL_VUR }}
PeriodicVulnerability-CheckOn-plugins-code:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: refs/heads/main
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: npm --prefix plugins install
- name: Running security audit
run: npm --prefix plugins audit --json > Periodic-plugins-audit.json
continue-on-error: true
- name: Parse audit summary
id: parse-audit
run: |
vulnerabilities=$(jq '.metadata.vulnerabilities' Periodic-plugins-audit.json)
moderate=$(echo $vulnerabilities | jq '.moderate')
high=$(echo $vulnerabilities | jq '.high')
critical=$(echo $vulnerabilities | jq '.critical')
echo "::set-output name=moderate::$moderate"
echo "::set-output name=high::$high"
echo "::set-output name=critical::$critical"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: Periodic-plugins-audit-report
path: Periodic-plugins-audit.json
- name: Send Slack Notification
run: |
message="Periodic Security Audit Report Of Plugins directory\n
Node module vulnerabilities summary:\n
🔴 Critical: ${{ steps.parse-audit.outputs.critical }}\n
🟠 High: ${{ steps.parse-audit.outputs.high }}\n
🟡 Moderate: ${{ steps.parse-audit.outputs.moderate }}\n
\nDownload Audit Report: http://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL_VUR }}
PeriodicVulnerability-CheckOn-cypress-code:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: refs/heads/main
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: npm --prefix cypress-tests install
- name: Running security audit
run: npm --prefix cypress-tests audit --json > Periodic-cypress-audit.json
continue-on-error: true
- name: Parse audit summary
id: parse-audit
run: |
vulnerabilities=$(jq '.metadata.vulnerabilities' Periodic-cypress-audit.json)
moderate=$(echo $vulnerabilities | jq '.moderate')
high=$(echo $vulnerabilities | jq '.high')
critical=$(echo $vulnerabilities | jq '.critical')
echo "::set-output name=moderate::$moderate"
echo "::set-output name=high::$high"
echo "::set-output name=critical::$critical"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: Periodic-cypress-audit-report
path: Periodic-cypress-audit.json
- name: Send Slack Notification
run: |
message="Periodic Security Audit Report Of Cypress directory\n
Node module vulnerabilities summary:\n
🔴 Critical: ${{ steps.parse-audit.outputs.critical }}\n
🟠 High: ${{ steps.parse-audit.outputs.high }}\n
🟡 Moderate: ${{ steps.parse-audit.outputs.moderate }}\n
\nDownload Audit Report: http://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL_VUR }}
PeriodicVulnerability-CheckOn-root-code:
if: github.event_name == 'schedule'
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: refs/heads/main
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: npm install
- name: Running security audit
run: npm audit --json > Periodic-root-audit.json
continue-on-error: true
- name: Parse audit summary
id: parse-audit
run: |
vulnerabilities=$(jq '.metadata.vulnerabilities' Periodic-root-audit.json)
moderate=$(echo $vulnerabilities | jq '.moderate')
high=$(echo $vulnerabilities | jq '.high')
critical=$(echo $vulnerabilities | jq '.critical')
echo "::set-output name=moderate::$moderate"
echo "::set-output name=high::$high"
echo "::set-output name=critical::$critical"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: Periodic-root-audit-report
path: Periodic-root-audit.json
- name: Send Slack Notification
run: |
message="Periodic Security Audit Report Of Root directory\n
Node module vulnerabilities summary:\n
🔴 Critical: ${{ steps.parse-audit.outputs.critical }}\n
🟠 High: ${{ steps.parse-audit.outputs.high }}\n
🟡 Moderate: ${{ steps.parse-audit.outputs.moderate }}\n
\nDownload Audit Report: http://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
curl -X POST -H 'Content-type: application/json' --data "{\"text\":\"$message\"}" ${{ secrets.SLACK_WEBHOOK_URL_VUR }}
ManualVulnerability-CheckOn-frontend-code:
if: ${{ github.event.action == 'labeled' && (github.event.label.name == 'frontend-vulnerability' || github.event.label.name == 'check-vulnerability') }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: npm --prefix frontend install
- name: Running security audit
run: npm --prefix frontend audit --json > frontend-audit.json
continue-on-error: true
- name: Parse audit summary
id: parse-audit
run: |
vulnerabilities=$(jq '.metadata.vulnerabilities' frontend-audit.json)
moderate=$(echo $vulnerabilities | jq '.moderate')
high=$(echo $vulnerabilities | jq '.high')
critical=$(echo $vulnerabilities | jq '.critical')
echo "::set-output name=moderate::$moderate"
echo "::set-output name=high::$high"
echo "::set-output name=critical::$critical"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: frontend-audit-report
path: frontend-audit.json
- name: Create or update PR comment
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
issue-number: ${{ github.event.pull_request.number }}
body: |
### Security Audit Report Of Frontend directory
**Node module vulnerabilities summary:**
🔴 Critical: ${{ steps.parse-audit.outputs.critical }}
🟠 High: ${{ steps.parse-audit.outputs.high }}
🟡 Moderate: ${{ steps.parse-audit.outputs.moderate }}
Please find the JSON file in the [summary page](${{ github.frontend_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
ManualVulnerability-CheckOn-server-code:
if: ${{ github.event.action == 'labeled' && (github.event.label.name == 'server-vulnerability' || github.event.label.name == 'check-vulnerability') }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: npm --prefix server install
- name: Running security audit
run: npm --prefix server audit --json > server-audit.json
continue-on-error: true
- name: Parse audit summary
id: parse-audit
run: |
vulnerabilities=$(jq '.metadata.vulnerabilities' server-audit.json)
moderate=$(echo $vulnerabilities | jq '.moderate')
high=$(echo $vulnerabilities | jq '.high')
critical=$(echo $vulnerabilities | jq '.critical')
echo "::set-output name=moderate::$moderate"
echo "::set-output name=high::$high"
echo "::set-output name=critical::$critical"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: server-audit-report
path: server-audit.json
- name: Create or update PR comment
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
issue-number: ${{ github.event.pull_request.number }}
body: |
### Security Audit Report Of Server directory
**Node module vulnerabilities summary:**
🔴 Critical: ${{ steps.parse-audit.outputs.critical }}
🟠 High: ${{ steps.parse-audit.outputs.high }}
🟡 Moderate: ${{ steps.parse-audit.outputs.moderate }}
Please find the JSON file in the [summary page](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
ManualVulnerability-CheckOn-marketplace-code:
if: ${{ github.event.action == 'labeled' && (github.event.label.name == 'marketplace-vulnerability' || github.event.label.name == 'check-vulnerability') }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: npm --prefix marketplace install
- name: Running security audit
run: npm --prefix marketplace audit --json > marketplace-audit.json
continue-on-error: true
- name: Parse audit summary
id: parse-audit
run: |
vulnerabilities=$(jq '.metadata.vulnerabilities' marketplace-audit.json)
moderate=$(echo $vulnerabilities | jq '.moderate')
high=$(echo $vulnerabilities | jq '.high')
critical=$(echo $vulnerabilities | jq '.critical')
echo "::set-output name=moderate::$moderate"
echo "::set-output name=high::$high"
echo "::set-output name=critical::$critical"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: marketplace-audit-report
path: marketplace-audit.json
- name: Create or update PR comment
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
issue-number: ${{ github.event.pull_request.number }}
body: |
### Security Audit Report Of Marketplace directory
**Node module vulnerabilities summary:**
🔴 Critical: ${{ steps.parse-audit.outputs.critical }}
🟠 High: ${{ steps.parse-audit.outputs.high }}
🟡 Moderate: ${{ steps.parse-audit.outputs.moderate }}
Please find the JSON file in the [summary page](${{ github.marketplace_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
ManualVulnerability-CheckOn-plugins-code:
if: ${{ github.event.action == 'labeled' && (github.event.label.name == 'plugins-vulnerability' || github.event.label.name == 'check-vulnerability') }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: npm --prefix plugins install
- name: Running security audit
run: npm --prefix plugins audit --json > plugins-audit.json
continue-on-error: true
- name: Parse audit summary
id: parse-audit
run: |
vulnerabilities=$(jq '.metadata.vulnerabilities' plugins-audit.json)
moderate=$(echo $vulnerabilities | jq '.moderate')
high=$(echo $vulnerabilities | jq '.high')
critical=$(echo $vulnerabilities | jq '.critical')
echo "::set-output name=moderate::$moderate"
echo "::set-output name=high::$high"
echo "::set-output name=critical::$critical"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: plugins-audit-report
path: plugins-audit.json
- name: Create or update PR comment
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
issue-number: ${{ github.event.pull_request.number }}
body: |
### Security Audit Report Of Plugins directory
**Node module vulnerabilities summary:**
🔴 Critical: ${{ steps.parse-audit.outputs.critical }}
🟠 High: ${{ steps.parse-audit.outputs.high }}
🟡 Moderate: ${{ steps.parse-audit.outputs.moderate }}
Please find the JSON file in the [summary page](${{ github.plugins_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
ManualVulnerability-CheckOn-cypress-code:
if: ${{ github.event.action == 'labeled' && (github.event.label.name == 'cypress-vulnerability' || github.event.label.name == 'check-vulnerability') }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: npm --prefix cypress-tests install
- name: Running security audit
run: npm --prefix cypress-tests audit --json > cypress-audit.json
continue-on-error: true
- name: Parse audit summary
id: parse-audit
run: |
vulnerabilities=$(jq '.metadata.vulnerabilities' cypress-audit.json)
moderate=$(echo $vulnerabilities | jq '.moderate')
high=$(echo $vulnerabilities | jq '.high')
critical=$(echo $vulnerabilities | jq '.critical')
echo "::set-output name=moderate::$moderate"
echo "::set-output name=high::$high"
echo "::set-output name=critical::$critical"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: cypress-audit-report
path: cypress-audit.json
- name: Create or update PR comment
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
issue-number: ${{ github.event.pull_request.number }}
body: |
### Security Audit Report Of Cypress directory
**Node module vulnerabilities summary:**
🔴 Critical: ${{ steps.parse-audit.outputs.critical }}
🟠 High: ${{ steps.parse-audit.outputs.high }}
🟡 Moderate: ${{ steps.parse-audit.outputs.moderate }}
Please find the JSON file in the [summary page](${{ github.cypress_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).
ManualVulnerability-CheckOn-root-code:
if: ${{ github.event.action == 'labeled' && (github.event.label.name == 'root-vulnerability' || github.event.label.name == 'check-vulnerability') }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.ref }}
- name: Use Node.js 18.18.2
uses: actions/setup-node@v3
with:
node-version: 18.18.2
- name: Install dependencies
run: npm install
- name: Running security audit
run: npm audit --json > root-audit.json
continue-on-error: true
- name: Parse audit summary
id: parse-audit
run: |
vulnerabilities=$(jq '.metadata.vulnerabilities' root-audit.json)
moderate=$(echo $vulnerabilities | jq '.moderate')
high=$(echo $vulnerabilities | jq '.high')
critical=$(echo $vulnerabilities | jq '.critical')
echo "::set-output name=moderate::$moderate"
echo "::set-output name=high::$high"
echo "::set-output name=critical::$critical"
- name: Upload audit report
uses: actions/upload-artifact@v4
with:
name: root-audit-report
path: root-audit.json
- name: Create or update PR comment
uses: peter-evans/create-or-update-comment@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
repository: ${{ github.repository }}
issue-number: ${{ github.event.pull_request.number }}
body: |
### Security Audit Report Of Root directory
**Node module vulnerabilities summary:**
🔴 Critical: ${{ steps.parse-audit.outputs.critical }}
🟠 High: ${{ steps.parse-audit.outputs.high }}
🟡 Moderate: ${{ steps.parse-audit.outputs.moderate }}
Please find the JSON file in the [summary page](${{ github.root_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}).

View file

@ -1 +1 @@
3.7.0
3.11.0

View file

@ -19,9 +19,9 @@ module.exports = defineConfig({
trashAssetsBeforeRuns: true,
e2e: {
setupNodeEvents(on, config) {
setupNodeEvents (on, config) {
on("task", {
readPdf(pathToPdf) {
readPdf (pathToPdf) {
return new Promise((resolve) => {
const pdfPath = path.resolve(pathToPdf);
let dataBuffer = fs.readFileSync(pdfPath);
@ -33,7 +33,7 @@ module.exports = defineConfig({
});
on("task", {
readXlsx(filePath) {
readXlsx (filePath) {
return new Promise((resolve, reject) => {
try {
let dataBuffer = fs.readFileSync(filePath);
@ -48,7 +48,7 @@ module.exports = defineConfig({
});
on("task", {
deleteFolder(folderName) {
deleteFolder (folderName) {
return new Promise((resolve, reject) => {
rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => {
if (err) {
@ -62,7 +62,7 @@ module.exports = defineConfig({
});
on("task", {
updateId({ dbconfig, sql }) {
dbConnection ({ dbconfig, sql }) {
const client = new pg.Pool(dbconfig);
return client.query(sql);
},

View file

@ -19,9 +19,9 @@ module.exports = defineConfig({
trashAssetsBeforeRuns: true,
e2e: {
setupNodeEvents(on, config) {
setupNodeEvents (on, config) {
on("task", {
readPdf(pathToPdf) {
readPdf (pathToPdf) {
return new Promise((resolve) => {
const pdfPath = path.resolve(pathToPdf);
let dataBuffer = fs.readFileSync(pdfPath);
@ -33,7 +33,7 @@ module.exports = defineConfig({
});
on("task", {
readXlsx(filePath) {
readXlsx (filePath) {
return new Promise((resolve, reject) => {
try {
let dataBuffer = fs.readFileSync(filePath);
@ -48,7 +48,7 @@ module.exports = defineConfig({
});
on("task", {
deleteFolder(folderName) {
deleteFolder (folderName) {
return new Promise((resolve, reject) => {
rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => {
if (err) {
@ -62,7 +62,7 @@ module.exports = defineConfig({
});
on("task", {
updateId({ dbconfig, sql }) {
dbConnection ({ dbconfig, sql }) {
const client = new pg.Pool(dbconfig);
return client.query(sql);
},

View file

@ -83,7 +83,7 @@ module.exports = defineConfig({
});
on("task", {
updateId ({ dbconfig, sql }) {
dbConnection ({ dbconfig, sql }) {
const client = new pg.Pool(dbconfig);
return client.query(sql);
},

View file

@ -19,9 +19,9 @@ module.exports = defineConfig({
trashAssetsBeforeRuns: true,
e2e: {
setupNodeEvents(on, config) {
setupNodeEvents (on, config) {
on("task", {
readPdf(pathToPdf) {
readPdf (pathToPdf) {
return new Promise((resolve) => {
const pdfPath = path.resolve(pathToPdf);
let dataBuffer = fs.readFileSync(pdfPath);
@ -33,7 +33,7 @@ module.exports = defineConfig({
});
on("task", {
readXlsx(filePath) {
readXlsx (filePath) {
return new Promise((resolve, reject) => {
try {
let dataBuffer = fs.readFileSync(filePath);
@ -48,7 +48,7 @@ module.exports = defineConfig({
});
on("task", {
deleteFolder(folderName) {
deleteFolder (folderName) {
return new Promise((resolve, reject) => {
rmdir(folderName, { maxRetries: 10, recursive: true }, (err) => {
if (err) {
@ -62,7 +62,7 @@ module.exports = defineConfig({
});
on("task", {
updateId({ dbconfig, sql }) {
dbConnection ({ dbconfig, sql }) {
const client = new pg.Pool(dbconfig);
return client.query(sql);
},

View file

@ -66,7 +66,7 @@ module.exports = defineConfig({
});
on("task", {
updateId ({ dbconfig, sql }) {
dbConnection ({ dbconfig, sql }) {
const client = new pg.Pool(dbconfig);
return client.query(sql);
},
@ -92,7 +92,11 @@ module.exports = defineConfig({
experimentalModfyObstructiveThirdPartyCode: true,
experimentalRunAllSpecs: true,
baseUrl: "http://localhost:8082",
specPattern: "cypress/e2e/happyPath/**/*.cy.js",
specPattern: [
"cypress/e2e/happyPath/platform/ceTestcases/userFlow/firstUserOnboarding.cy.js",
"cypress/e2e/happyPath/platform/ceTestcases/!(userFlow)/**/*.cy.js",
"cypress/e2e/happyPath/platform/commonTestcases/**/*.cy.js",
],
downloadsFolder: "cypress/downloads",
numTestsKeptInMemory: 0,
redirectionLimit: 10,

View file

@ -1,3 +1,5 @@
const envVar = Cypress.env("environment");
Cypress.Commands.add(
"apiLogin",
(
@ -75,14 +77,17 @@ Cypress.Commands.add("apiCreateApp", (appName = "testApp") => {
Cookie: `tj_auth_token = ${cookie.value}`,
},
body: {
created_at: "",
id: "",
is_maintenance_on: false,
is_public: null,
type: "front-end",
name: appName,
is_maintenance_on: false,
organization_id: "",
updated_at: "",
user_id: "",
created_at: "",
updated_at: "",
id: "",
is_public: null,
workflow_enabled: false,
creation_mode: "DEFAULT",
},
}).then((response) => {
{
@ -128,7 +133,7 @@ Cypress.Commands.add(
appId = Cypress.env("appId"),
componentSelector = "[data-cy='empty-editor-text']"
) => {
cy.intercept("GET", "/api/v2/apps/*").as("getAppData");
cy.intercept("GET", "/api/apps/*").as("getAppData");
cy.window({ log: false }).then((win) => {
win.localStorage.setItem("walkthroughCompleted", "true");
});
@ -175,7 +180,7 @@ Cypress.Commands.add("apiLogout", () => {
cy.request(
{
method: "GET",
url: `${Cypress.env("server_host")}/api/logout`,
url: `${Cypress.env("server_host")}/api/session/logout`,
headers: {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
Cookie: `tj_auth_token=${cookie.value}`,
@ -190,22 +195,36 @@ Cypress.Commands.add("apiLogout", () => {
Cypress.Commands.add(
"apiUserInvite",
(userName, userEmail, userRole = "end-user") => {
(userName, userEmail, userRole = "end-user", metaData = {}) => {
const requestBody =
envVar === "Enterprise"
? {
email: userEmail,
firstName: userName,
groups: [],
lastName: "",
role: userRole,
userMetadata: metaData,
}
: {
email: userEmail,
firstName: userName,
groups: [],
lastName: "",
role: userRole,
userMetadata: metaData,
};
cy.getCookie("tj_auth_token").then((cookie) => {
cy.request(
{
method: "POST",
url: `${Cypress.env("server_host")}/api/organization_users`,
url: `${Cypress.env("server_host")}/api/organization-users`,
headers: {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
Cookie: `tj_auth_token=${cookie.value}`,
},
body: {
first_name: userName,
email: userEmail,
groups: [],
role: userRole,
},
body: requestBody,
},
{ log: false }
).then((response) => {
@ -221,21 +240,26 @@ Cypress.Commands.add("apiAddQuery", (queryName, query, dataQueryId) => {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
Cookie: `tj_auth_token=${cookie.value}`,
};
cy.request({
method: "PATCH",
url: `${Cypress.env("server_host")}/api/data_queries/${dataQueryId}`,
headers: headers,
body: {
name: queryName,
options: {
mode: "sql",
transformationLanguage: "javascript",
enableTransformation: false,
query: query,
cy.apiGetAppData(Cypress.env("appId")).then((appData) => {
const editingVersionId = appData.editing_version.id;
cy.request({
method: "PATCH",
url: `${Cypress.env("server_host")}/api/data-queries/${dataQueryId}/versions/${editingVersionId}`,
headers: headers,
body: {
name: queryName,
options: {
mode: "sql",
transformationLanguage: "javascript",
enableTransformation: false,
query: query,
},
},
},
}).then((patchResponse) => {
expect(patchResponse.status).to.equal(200);
}).then((patchResponse) => {
expect(patchResponse.status).to.equal(200);
});
});
});
});
@ -262,7 +286,7 @@ Cypress.Commands.add(
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/data_queries`,
url: `${Cypress.env("server_host")}/api/data-queries`,
headers: {
"Content-Type": "application/json",
Cookie: authToken,
@ -315,7 +339,7 @@ Cypress.Commands.add(
cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/v2/apps/${appId}`,
url: `${Cypress.env("server_host")}/api/apps/${appId}`,
headers: {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
Cookie: `tj_auth_token=${cookie.value}`,
@ -432,11 +456,10 @@ Cypress.Commands.add("apiMakeAppPublic", (appId = Cypress.env("appId")) => {
Cypress.Commands.add("apiDeleteGranularPermission", (groupName) => {
cy.getAuthHeaders().then((headers) => {
// Fetch group permissions
cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/v2/group_permissions`,
url: `${Cypress.env("server_host")}/api/v2/group-permissions`,
headers: headers,
log: false,
}).then((response) => {
@ -451,7 +474,7 @@ Cypress.Commands.add("apiDeleteGranularPermission", (groupName) => {
// Fetch granular permissions for the specific group
cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/v2/group_permissions/${groupId}/granular-permissions`,
url: `${Cypress.env("server_host")}/api/v2/group-permissions/${groupId}/granular-permissions`,
headers,
log: false,
}).then((granularResponse) => {
@ -461,7 +484,7 @@ Cypress.Commands.add("apiDeleteGranularPermission", (groupName) => {
// Delete the granular permission
cy.request({
method: "DELETE",
url: `${Cypress.env("server_host")}/api/v2/group_permissions/granular-permissions/${granularPermissionId}`,
url: `${Cypress.env("server_host")}/api/v2/group-permissions/granular-permissions/${granularPermissionId}`,
headers,
log: false,
}).then((deleteResponse) => {
@ -483,11 +506,10 @@ Cypress.Commands.add(
resourcesToAdd = []
) => {
cy.getAuthHeaders().then((headers) => {
// Fetch group permissions
cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/v2/group_permissions`,
url: `${Cypress.env("server_host")}/api/v2/group-permissions`,
headers: headers,
log: false,
}).then((response) => {
@ -502,7 +524,7 @@ Cypress.Commands.add(
// Create granular permission
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/v2/group_permissions/granular-permissions`,
url: `${Cypress.env("server_host")}/api/v2/group-permissions/granular-permissions`,
headers: headers,
body: {
name,
@ -528,10 +550,9 @@ Cypress.Commands.add(
Cypress.Commands.add("apiReleaseApp", (appName) => {
cy.getAppId(appName).then((appId) => {
cy.getAuthHeaders().then((headers) => {
cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/v2/apps/${appId}`,
url: `${Cypress.env("server_host")}/api/apps/${appId}`,
headers,
})
.then((response) => {
@ -539,7 +560,7 @@ Cypress.Commands.add("apiReleaseApp", (appName) => {
const editingVersionId = response.body.editing_version.id;
cy.request({
method: "PUT",
url: `${Cypress.env("server_host")}/api/v2/apps/${appId}/release`,
url: `${Cypress.env("server_host")}/api/apps/${appId}/release`,
headers: headers,
body: {
versionToBeReleased: editingVersionId,
@ -556,7 +577,6 @@ Cypress.Commands.add("apiReleaseApp", (appName) => {
Cypress.Commands.add("apiAddAppSlug", (appName, slug) => {
cy.getAppId(appName).then((appId) => {
cy.getAuthHeaders().then((headers) => {
cy.request({
method: "PUT",
url: `${Cypress.env("server_host")}/api/apps/${appId}`,
@ -576,7 +596,6 @@ Cypress.Commands.add("apiAddAppSlug", (appName, slug) => {
Cypress.Commands.add("apiGetTableIdByName", (tableName) => {
cy.getAuthHeaders().then((headers) => {
cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/tooljet-db/organizations/${Cypress.env("workspaceId")}/tables`,
@ -594,7 +613,6 @@ Cypress.Commands.add("apiGetTableIdByName", (tableName) => {
Cypress.Commands.add("apiAddDataToTable", (tableName, data) => {
cy.apiGetTableIdByName(tableName).then((tableId) => {
cy.getAuthHeaders().then((headers) => {
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/tooljet-db/proxy/${tableId}`,
@ -612,7 +630,7 @@ Cypress.Commands.add("apiGetDataSourceIdByName", (dataSourceName) => {
cy.getAuthHeaders().then((headers) => {
cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/v2/data_sources`,
url: `${Cypress.env("server_host")}/api/data-sources`,
headers: headers,
}).then((response) => {
expect(response.status).to.equal(200);
@ -670,7 +688,7 @@ Cypress.Commands.add(
cy.request({
method: "PUT",
url: `${Cypress.env("server_host")}/api/v2/data_sources/${dataSourceId}?environment_id=${environmentId}`,
url: `${Cypress.env("server_host")}/api/data-sources/${dataSourceId}?environment_id=${environmentId}`,
headers: headers,
body: mergedData,
}).then((updateResponse) => {
@ -683,3 +701,15 @@ Cypress.Commands.add(
}
);
Cypress.Commands.add("apiGetAppData", (appId = Cypress.env("appId")) => {
cy.getAuthHeaders().then((headers) => {
cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/apps/${appId}`,
headers: headers,
}).then((response) => {
expect(response.status).to.equal(200);
return response.body;
});
});
});

View file

@ -7,13 +7,14 @@ import { importSelectors } from "Selectors/exportImport";
import { importText } from "Texts/exportImport";
import { onboardingSelectors } from "Selectors/onboarding";
const API_ENDPOINT =
Cypress.env("environment") === "Community"
? "/api/library_apps"
: "/api/library_apps/";
Cypress.Commands.add(
"appUILogin",
(email = "dev@tooljet.io", password = "password") => {
const API_ENDPOINT =
Cypress.env("environment") === "Community"
? "/api/library_apps/"
: "/api/library_apps";
cy.visit("/");
cy.wait(1000);
cy.clearAndType(onboardingSelectors.loginEmailInput, email);
@ -116,10 +117,8 @@ Cypress.Commands.add(
});
const splitIntoFlatArray = (value) => {
const regex =
/(\{|\}|\(|\)|\[|\]|,|:|;|=>|'[^']*'|"[^"]*"|[a-zA-Z0-9._+\-*]+|\s+)/g;
const regex = /(\{|\}|\(|\)|\[|\]|,|:|;|=>|'[^']*'|[a-zA-Z0-9._]+|\s+)/g;
let prefix = "";
return (
value.match(regex)?.reduce((acc, part) => {
if (part === "{{" || part === "((") {
@ -134,14 +133,6 @@ Cypress.Commands.add(
acc.push(prefix + " ");
} else if (part === ":") {
acc.push(prefix + ":");
} else if (part === '"') {
acc.push(prefix + '"');
} else if (
part.includes("-") ||
part.includes("+") ||
part.includes("*")
) {
acc.push(prefix + part);
} else {
acc.push(prefix + part);
prefix = "";
@ -236,9 +227,9 @@ Cypress.Commands.add(
.invoke("text")
.then((text) => {
cy.wrap(subject).realType(createBackspaceText(text)),
{
delay: 0,
};
{
delay: 0,
};
});
}
);
@ -313,19 +304,19 @@ Cypress.Commands.add("skipEditorPopover", () => {
});
Cypress.Commands.add("waitForAppLoad", () => {
const API_ENDPOINT =
Cypress.env("environment") === "Community"
? "/api/v2/data_sources"
: "/api/app-environments**";
// const API_ENDPOINT =
// Cypress.env("environment") === "Community"
// ? "/api/v2/data_sources"
// : "/api/app-environments**";
const TIMEOUT = 15000;
// const TIMEOUT = 15000;
cy.intercept("GET", API_ENDPOINT).as("appDs");
cy.wait("@appDs", { timeout: TIMEOUT });
cy.intercept("GET", "/api/data-queries/**").as("appDs");
cy.wait("@appDs", { timeout: 15000 });
});
Cypress.Commands.add("visitTheWorkspace", (workspaceName) => {
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select id from organizations where name='${workspaceName}';`,
}).then((resp) => {
@ -407,20 +398,13 @@ Cypress.Commands.add("getPosition", (componentName) => {
});
Cypress.Commands.add("defaultWorkspaceLogin", () => {
cy.task("updateId", {
dbconfig: Cypress.env("app_db"),
sql: `
SELECT id FROM organizations WHERE name = 'My workspace';
`,
}).then((resp) => {
const workspaceId = resp.rows[0].id;
cy.apiLogin("dev@tooljet.io", "password", workspaceId, "/my-workspace");
cy.apiLogin();
cy.visit("/");
cy.intercept("GET", "/api/library_apps").as("library_apps");
cy.get(commonSelectors.homePageLogo, { timeout: 10000 });
cy.wait("@library_apps");
});
cy.visit("/my-workspace");
cy.intercept("GET", API_ENDPOINT).as("library_apps");
cy.get(commonSelectors.homePageLogo, { timeout: 10000 });
cy.wait("@library_apps");
// });
});
Cypress.Commands.add(
@ -466,13 +450,13 @@ Cypress.Commands.add("releaseApp", () => {
Cypress.Commands.add("backToApps", () => {
cy.get(commonSelectors.editorPageLogo).click();
cy.get(commonSelectors.backToAppOption).click();
cy.intercept("GET", "/api/library_apps/").as("library_apps");
cy.intercept("GET", API_ENDPOINT).as("library_apps");
cy.get(commonSelectors.homePageLogo, { timeout: 10000 });
cy.wait("@library_apps");
});
Cypress.Commands.add("removeAssignedApps", () => {
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `DELETE FROM app_group_permissions;`,
});
@ -511,7 +495,7 @@ Cypress.Commands.add("skipWalkthrough", () => {
Cypress.Commands.add("appPrivacy", (appName, isPublic) => {
const isPublicValue = isPublic ? "true" : "false";
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `UPDATE apps SET is_public = ${isPublicValue} WHERE name = '${appName}';`,
});
@ -530,82 +514,6 @@ 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("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.`);
}
});
});
});
Cypress.Commands.add("verifyElement", (selector, text, eqValue) => {
const element =
eqValue !== undefined ? cy.get(selector).eq(eqValue) : cy.get(selector);
@ -619,10 +527,12 @@ Cypress.Commands.add("loginWithCredentials", (email, password) => {
cy.clearAndType(onboardingSelectors.loginEmailInput, email);
cy.clearAndType(onboardingSelectors.loginPasswordInput, password);
cy.get(onboardingSelectors.signInButton).click();
cy.wait(3000);
cy.get(commonSelectors.pageLogo).should("be.visible");
});
Cypress.Commands.add("getAppId", (appName) => {
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select id from apps where name='${appName}';`,
}).then((resp) => {

View file

@ -14,7 +14,7 @@ export const dataSourceSelector = {
dataSourceSearchInputField: '[data-cy="home-page-search-bar"]',
postgresDataSource: "[data-cy='data-source-postgresql']",
dataSourceNameInputField: '[data-cy="data-source-name-input-field"]',
dataSourceNameInputField: '[data-cy="added-ds-search-bar"]',
labelHost: '[data-cy="label-host"]',
labelPort: '[data-cy="label-port"]',
labelSsl: '[data-cy="label-ssl"]',
@ -28,7 +28,7 @@ export const dataSourceSelector = {
buttonTestConnection: '[data-cy="test-connection-button"]',
connectionFailedText: '[data-cy="test-connection-failed-text"]',
buttonSave: '[data-cy="db-connection-save-button"] > .tj-base-btn',
dangerAlertNotSupportSSL: ".go3958317564",
dangerAlertNotSupportSSL: '.go3958317564',
passwordTextField: '[data-cy="password-text-field"]',
textConnectionVerified: '[data-cy="test-connection-verified-text"]',
@ -102,6 +102,6 @@ export const dataSourceSelector = {
eventQuerySelectionField: '[data-cy="query-selection-field"]',
connectionAlertText: '[data-cy="connection-alert-text"]',
deleteDSButton: (datasourceName) => {
return `[data-cy="${cyParamName(datasourceName)}-delete-button"]`;
return `[data-cy="${cyParamName(datasourceName)}-delete-button"]`
},
};

View file

@ -6,7 +6,7 @@ export const groupsSelector = {
createNewGroupButton: "[data-cy=create-new-group-button]",
tableHeader: "[data-cy=table-header]",
groupName: "[data-cy=group-name]",
addNewGroupModalTitle: '[data-cy="create-new-group-title"]',
addNewGroupModalTitle: '[data-cy="add-new-group-title"]',
groupNameInput: "[data-cy=group-name-input]",
cancelButton: "[data-cy=cancel-button]",
workspaceVarCreateLabel: '[data-cy="workspace-variable-create-label"]',

View file

@ -173,7 +173,7 @@ export const commonText = {
// iframeLinkLabel: "Get embeddable link for this application",
// ifameLinkCopyButton: "copy",
},
groupInputFieldLabel: "Select Group",
groupInputFieldLabel: "Select groups",
documentationLink: "Read Documentation",
constantsNameError:
"Constant name should start with a letter or underscore and can only contain letters, numbers and underscores",
@ -181,7 +181,7 @@ export const commonText = {
"Value should be less than 10000 characters and cannot be empty",
createApp: "Create app",
appName: "App Name",
appName: "App name",
enterAppName: "Enter app name",
appNameInfoLabel: "App name must be unique and max 50 characters",
renameApp: "Rename app",

View file

@ -1,7 +1,7 @@
export const appVersionText = {
createNewVersion: "Create new version",
createVersion: "Create Version",
versionNameLabel: "Version Name",
versionNameLabel: "Version name",
createVersionFromLabel: "Create version from",
emptyToastMessage: "Version name should not be empty",
createdToastMessage: "Version Created",

View file

@ -1,10 +1,10 @@
export const groupsText = {
pageTitle: "User Groups",
createNewGroupButton: "Create new group",
createNewGroupButton: "Add new group",
tableHeader: "Name",
allUsers: "All users",
admin: "Admin",
cardTitle: "Create new group",
cardTitle: "Add new group",
cancelButton: "Cancel",
createGroupButton: "Create Group",
groupNameExistToast: "Group name already exist",
@ -52,7 +52,7 @@ export const groupsText = {
editGroupNameButton: "Rename",
deleteGroupButton: "Delete group",
editPermissionModalTitle: "Edit app permissions",
addPermissionModalTitle: "Add app permissions",
addPermissionModalTitle: "Add apps permissions",
appCreateHelperText: 'Create apps in this workspace',
appDeleteHelperText: 'Delete any app in this workspace',
appEditLabelText: 'Edit',
@ -63,7 +63,7 @@ export const groupsText = {
appHideLabel: "Hide from dashboard",
appHideLabelPermissionModal: "Hide from dashbaord",
groupChipText: 'All apps',
adminAccessHelperText: " Admin has edit access to all apps. These are not editableread documentation to know more !",
adminAccessHelperText: " Admin has all permissions. This is not editableread documentation to know more !",
enduserAccessHelperText: " End-user can only have permission to view appsread documentation to know more !",
nameTableHeader: 'Name',
permissionTableHeader: 'Permission',
@ -85,19 +85,19 @@ export const groupsText = {
warningText: "Users must be always be part of one default group. This will define the user count in your plan.",
continueButtonText: "Continue",
roleUpdateToastMessage: "Role updated successfully",
endUserToBuilderMessage: "Updating the user's details will change their role from end-user to builder. Are you sure you want to continue?",
endUserToAdminMessage: "Updating the user's details will change their role from end-user to admin. Are you sure you want to continue?",
builderToEnduserMessage: "This will also remove the user from any custom groups with builder-like permissions.Are you sure you want to continue?",
endUserToBuilderMessage: "Changing the user role from end-user to builder will grant access the user access to all resources.Are you sure you want to continue?",
endUserToAdminMessage: "Changing the user role from end-user to admin will grant the user access to all resources and settings.Are you sure you want to continue?",
builderToEnduserMessage: "Changing the user role from builder to end-user will revoke their access to edit all resources.Are you sure you want to continue?",
builderToAdminMessage: "Changing user role from builder to admin will grant access to all resources and settings.Are you sure you want to continue?",
adminToBuilderMessage: "Changing your user default group from admin to builder will revoke your access to settings.Are you sure you want to continue?",
adminToEnduserMessage: "Changing your user group from admin to end-user will revoke your access to settings.Are you sure you want to continue?",
adminToEnduserMessage: "Changing the user role from admin to end-user will revoke their access to edit all resources and settings.Are you sure you want to continue?",
modalHeader: "Can not remove last active admin",
modalMessage: "Cannot change role of last present admin, please add another admin and change the role",
userAddedToast: "Users added to the group",
changeUserRoleHeader: " Change in user role",
changeUserRoleMessage: "Granting this permission to the user group will result in a role change for the following user(s) from end-users to builders. Are you sure you want to continue?",
cantCreatePermissionModalHeader: "Cannot create permissions",
cantCreatePermissionModalMessage: "Cannot assign builder level permission to end users",
cantCreatePermissionModalMessage: "End-users can only be granted permission to view apps. If you wish to add this permission, kindly change the following users role from end-user to builder",
deletePermissionToast: "Deleted permission successfully",
createPermissionToast: "Permission created successfully!",
userEmptyPageTitle: "No users added yet",

View file

@ -57,7 +57,7 @@ export const usersText = {
buttonUploadCsvFile: "Upload CSV file",
helperTextBulkUpload:
"Download the ToolJet template to add user details or format your file in the same as the template. ToolJet wont be able to recognise files in any other format. ",
"Download the template to add user details or format your file in the same way as the template. Files in any other format may not be recognized. ",
helperTextSelectFile: "Select a CSV file to upload",
helperTextDropFile: "Or drag and drop it here",
};

View file

@ -8,7 +8,11 @@ export const editVersionText = {
export const deleteVersionText = {
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
)}?`;
},
@ -19,9 +23,13 @@ export const deleteVersionText = {
export const onlydeleteVersionText = {
deleteModalText: (text) => {
return `Are you sure you want to delete this version - ${cyParamName(
return `Deleting a version will permanently remove it from all environments.Are you sure you want to delete this version - ${cyParamName(
text
)}?`;
// `Are you sure you want to delete this version - ${cyParamName(
// text
// )}?`;
},
deleteToastMessage: (version) => {
return `Cannot delete only version of app`;

View file

@ -107,7 +107,7 @@ describe("App Import Functionality", () => {
);
cy.get(commonSelectors.appNameLabel).verifyVisibleElement(
"have.text",
"App Name"
"App name"
);
cy.get(commonSelectors.appNameInput)
.should("be.visible")

View file

@ -15,6 +15,7 @@ describe("App Slug", () => {
beforeEach(() => {
data.slug = `${fake.companyName.toLowerCase()}-app`;
data.appName = `${fake.companyName} App`;
cy.log(Cypress.env("workspaceId"));
cy.defaultWorkspaceLogin();
});
@ -24,6 +25,8 @@ describe("App Slug", () => {
cy.apiCreateApp(data.appName);
cy.wait(1000);
cy.apiLogout();
cy.log(Cypress.env("workspaceId"));
});
it("Verify app slug cases in global settings", () => {

View file

@ -6,7 +6,6 @@ import { setSignupStatus } from "Support/utils/manageSSO";
import { onboardingSelectors } from "Selectors/onboarding";
import { commonText } from "Texts/common";
import {
verifyConfirmEmailPage,
userSignUp,
addNewUser,
} from "Support/utils/onboarding";
@ -33,6 +32,8 @@ describe("Private and Public apps", {
cy.defaultWorkspaceLogin();
cy.skipWalkthrough();
cy.log(data.appName, "text1")
});
it("Verify private and public app share functionality", () => {
@ -85,7 +86,8 @@ describe("Private and Public apps", {
cy.get(onboardingSelectors.signInButton, { timeout: 20000 }).should("be.visible");
cy.wait(2000);
cy.loginWithCredentials("dev@tooljet.io", "password");
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
// Test public access
cy.get(commonSelectors.viewerPageLogo).click();
@ -104,7 +106,9 @@ describe("Private and Public apps", {
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
});
it("Verify app private and public app visibility for the same workspace user", () => {
@ -120,13 +124,17 @@ describe("Private and Public apps", {
cy.wait(2000);
cy.loginWithCredentials(data.email, "password");
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible", { timeout: 20000 });
// Test with private app valid session
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
cy.get(commonSelectors.viewerPageLogo).click();
// Test public access
@ -137,14 +145,18 @@ describe("Private and Public apps", {
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
// Test with public app with valid session
cy.apiLogin(data.email, "password");
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
});
it("Verify app private and public app visibility for the same instance user", () => {
@ -168,14 +180,18 @@ describe("Private and Public apps", {
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
// Verify public app with valid session
cy.apiLogin(data.email, "password");
cy.visitSlug({
actualUrl: `${Cypress.config("baseUrl")}/applications/${data.slug}`,
});
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
});
it("Should redirect to workspace login and handle signup flow of existing and non-existing user", () => {
@ -193,18 +209,24 @@ describe("Private and Public apps", {
);
// Test signup flow
cy.intercept("POST", "/api/onboarding/signup").as("signup");
cy.get(commonSelectors.createAnAccountLink).click();
cy.wait(3000);
cy.clearAndType(commonSelectors.inputFieldFullName, data.firstName);
cy.clearAndType(commonSelectors.inputFieldEmailAddress, data.email);
cy.clearAndType(onboardingSelectors.loginPasswordInput, "password");
cy.get(commonSelectors.signUpButton).click();
verifyConfirmEmailPage(data.email);
cy.wait('@signup').then((interception) => {
expect(interception.response.statusCode).to.eq(201);
});
// Process invitation
onboardUserFromAppLink(data.email, data.slug);
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
cy.get('[data-cy="viewer-page-logo"]').click();
logout();
@ -242,10 +264,14 @@ describe("Private and Public apps", {
cy.clearAndType(commonSelectors.inputFieldEmailAddress, data.email);
cy.clearAndType(onboardingSelectors.loginPasswordInput, "password");
cy.get(commonSelectors.signUpButton).click();
verifyConfirmEmailPage(data.email);
cy.wait('@signup').then((interception) => {
expect(interception.response.statusCode).to.eq(201);
});
onboardUserFromAppLink(data.email, data.slug, data.workspaceName, false);
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.get('.text-widget-section > div').should("be.visible");
});
it("Should verify restricted app access", () => {

View file

@ -111,7 +111,10 @@ describe("App Version", () => {
onlydeleteVersionText.deleteToastMessage("v3")
);
cy.get(appVersionSelectors.currentVersionField("v2")).should("be.visible");
cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible");
cy.wait(3000);
// cy.reload();
// cy.get(commonWidgetSelector.draggableWidget("text1")).should("be.visible", { timeout: 10000 });
// Preview and release verification
cy.openInCurrentTab(commonWidgetSelector.previewButton);
@ -120,7 +123,7 @@ describe("App Version", () => {
releasedVersionAndVerify("v2");
});
it("should verify version management with components and queries", () => {
it.only("should verify version management with components and queries", () => {
// Initial setup with component and datasource
cy.apiAddComponentToApp(
data.appName,
@ -132,7 +135,7 @@ describe("App Version", () => {
cy.waitForAutoSave();
cy.apiCreateGDS(
`${Cypress.env("server_host")}/api/v2/data_sources`,
`${Cypress.env("server_host")}/api/data-sources`,
data.datasourceName,
"restapi",
[{ key: "url", value: "https://jsonplaceholder.typicode.com/users" }]

View file

@ -183,7 +183,7 @@ describe("Datasource Manager", () => {
data.dsName1
);
cy.intercept("GET", "/api/v2/data_sources").as("datasource");
// cy.intercept("GET", "/api/v2/data_sources").as("datasource");
fillConnectionForm(
{
Host: Cypress.env("pg_host"),
@ -194,7 +194,8 @@ describe("Datasource Manager", () => {
},
".form-switch"
);
cy.wait("@datasource");
// cy.wait("@datasource");
cy.wait(1000);
cy.apiCreateApp(data.appName);
cy.openApp();
@ -223,7 +224,7 @@ describe("Datasource Manager", () => {
cy.get('[data-cy="databases-datasource-button"]').should("be.visible");
cy.apiCreateGDS(
`${Cypress.env("server_host")}/api/v2/data_sources`,
`${Cypress.env("server_host")}/api/data-sources`,
`cypress-${data.dsName2}-postgresql`,
"postgresql",
[

View file

@ -3,7 +3,12 @@ import { commonText } from "Texts/common";
import { onboardingSelectors } from "Selectors/onboarding";
import { onboardingText } from "Texts/onboarding";
import { logout } from "Support/utils/common";
import { bannerElementsVerification } from "Support/utils/onboarding";
import {
bannerElementsVerification,
onboardingStepOne,
onboardingStepTwo,
onboardingStepThree,
} from "Support/utils/onboarding";
describe("Self host onboarding", () => {
const envVar = Cypress.env("environment");
@ -24,9 +29,11 @@ describe("Self host onboarding", () => {
"have.text",
"Let's set up your admin account and workspace to get started!"
);
cy.get('[data-cy="set-up-tooljet-button"]')
.verifyVisibleElement("have.text", "Set up ToolJet")
.click();
cy.get('[data-cy="set-up-tooljet-button"]').verifyVisibleElement(
"have.text",
"Set up ToolJet"
);
cy.get('[data-cy="set-up-tooljet-button"]').click();
}
const commonElements = [
@ -72,7 +79,8 @@ describe("Self host onboarding", () => {
if (envVar === "Community") {
cy.get(commonSelectors.signUpTermsHelperText).should(($el) => {
expect($el.contents().first().text().trim()).to.eq(
commonText.selfHostSignUpTermsHelperText
// commonText.selfHostSignUpTermsHelperText
"By signing up you are agreeing to the"
);
});
} else if (envVar === "Enterprise") {
@ -110,68 +118,16 @@ describe("Self host onboarding", () => {
if (envVar === "Enterprise") {
bannerElementsVerification();
const companyPageTexts = [
{
selector: onboardingSelectors.tellUsAbit,
text: "Tell us a bit about yourself",
},
{
selector: onboardingSelectors.pageDescription,
text: "This information will help us improve ToolJet",
},
{
selector: '[data-cy="onboarding-company-name-label"]',
text: "Company name *",
},
{
selector: '[data-cy="onboarding-build-purpose-label"]',
text: "What would you like to build on ToolJet? *",
},
];
companyPageTexts.forEach((item) => {
cy.get(item.selector).should("be.visible").and("have.text", item.text);
});
cy.get(onboardingSelectors.companyNameInput).should("be.visible");
cy.get(onboardingSelectors.buildPurposeInput).should("be.visible");
cy.get(onboardingSelectors.onboardingSubmitButton).verifyVisibleElement(
"have.attr",
"disabled"
);
cy.get(onboardingSelectors.companyNameInput).type("Tooljet");
cy.get(onboardingSelectors.onboardingSubmitButton).should(
"have.attr",
"disabled"
);
cy.get(onboardingSelectors.buildPurposeInput).type("Exploring");
cy.get(onboardingSelectors.onboardingSubmitButton).verifyVisibleElement(
"have.text",
"Continue"
);
cy.get(onboardingSelectors.onboardingSubmitButton)
.should("be.enabled")
.click();
onboardingStepOne();
}
bannerElementsVerification();
cy.get(commonSelectors.setUpworkspaceCheckPoint)
.should("be.visible")
.and("have.text", "Set up your workspace!");
onboardingStepTwo();
cy.get(onboardingSelectors.pageDescription).verifyVisibleElement(
"have.text",
"Set up workspaces to manage users, applications & resources across various teams"
);
cy.get(commonSelectors.workspaceNameInputLabel)
.should("be.visible")
.and("have.text", commonText.workspaceNameInputLabel);
cy.clearAndType(commonSelectors.workspaceNameInputField, "My workspace");
cy.get(commonSelectors.OnbordingContinue)
.verifyVisibleElement("have.text", "Continue")
.click();
// if (envVar === "Enterprise") {
// bannerElementsVerification();
// onboardingStepTwo();
// }
if (envVar === "Enterprise") {
bannerElementsVerification();
@ -317,25 +273,15 @@ describe("Self host onboarding", () => {
cy.get(onboardingSelectors.declineButton).click();
bannerElementsVerification();
cy.get(
`[data-cy="we've-created-a-sample-application-for-you!-header"]`
).verifyVisibleElement(
"have.text",
"We've created a sample application for you!"
);
cy.get(onboardingSelectors.pageDescription).verifyVisibleElement(
"have.text",
"The sample application comes with a sample PostgreSQL database for you to play around with. You can also get started quickly with pre-built applications from our template collection!"
);
cy.get(onboardingSelectors.onboardingSubmitButton)
.verifyVisibleElement("have.text", "Continue")
.click();
onboardingStepThree();
}
cy.get(commonSelectors.skipbutton).click();
cy.get(commonSelectors.backLogo).click();
cy.get(commonSelectors.backtoapps).click();
cy.backToApps();
if (envVar === "Enterprise") {
cy.get(".btn-close").click();
}
if (envVar === "Enterprise") {
cy.get(".btn-close").click();

View file

@ -9,6 +9,7 @@ describe("Login functionality", () => {
let user;
const invalidEmail = fake.email;
const invalidPassword = fake.password;
const envVar = Cypress.env("environment");
beforeEach(() => {
cy.fixture("credentials/login.json").then((login) => {
@ -49,6 +50,9 @@ describe("Login functionality", () => {
it("Should be able to login with valid credentials", () => {
cy.appUILogin(user.email, user.password);
if (envVar === "Enterprise") {
cy.get(".btn-close").click();
}
cy.get(commonSelectors.settingsIcon).click();
cy.get(dashboardSelector.logoutLink);
});

View file

@ -41,7 +41,7 @@ describe("User signup", () => {
cy.wait(500);
verifyConfirmEmailPage(data.email);
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select invitation_token from users where email='${data.email}';`,
}).then((resp) => {
@ -67,9 +67,8 @@ describe("User signup", () => {
data.workspaceName = fake.companyName;
cy.visit("/");
cy.wait(8000);
cy.get(onboardingSelectors.createAnAccountLink).click();
cy.wait(6000);
cy.wait(2000);
cy.get(onboardingSelectors.nameInput).clear();
cy.get(onboardingSelectors.nameInput).type(data.fullName);
cy.clearAndType(onboardingSelectors.signupEmailInput, data.email);
@ -77,14 +76,17 @@ describe("User signup", () => {
onboardingSelectors.loginPasswordInput,
commonText.password
);
cy.intercept("POST", "/api/onboarding/signup").as("signup");
cy.get(commonSelectors.signUpButton).click();
cy.wait(8000);
cy.get(commonSelectors.resendEmailButton).click();
cy.task("updateId", {
cy.wait("@signup")
cy.get('[data-cy="check-your-mail-header"]').should("be.visible");
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select invitation_token from users where email='${data.email}';`,
}).then((resp) => {
invitationLink = `/invitations/${resp.rows[0].invitation_token}`;
cy.visit(invitationLink);
});
});
});

View file

@ -11,15 +11,8 @@ import {
inviteUserWithUserRole,
fetchAndVisitInviteLink,
} from "Support/utils/manageUsers";
import { addNewUser, visitWorkspaceInvitation, addNewUsertoworkspace } from "Support/utils/onboarding";
import { commonText } from "Texts/common";
import { setSignupStatus } from "Support/utils/manageSSO";
import { ssoSelector } from "Selectors/manageSSO";
import {
SignUpPageElements,
signUpLink,
verifyOnboardingQuestions,
} from "Support/utils/onboarding";
import { visitWorkspaceInvitation, addNewUser } from "Support/utils/onboarding";
import {
navigateToManageUsers,
@ -38,11 +31,16 @@ let invitationToken,
url = "";
const data = {};
const envVar = Cypress.env("environment");
describe("user invite flow cases", () => {
beforeEach(() => {
cy.defaultWorkspaceLogin();
if (envVar === "Enterprise") {
cy.get(".btn-close").click();
}
});
it("Should verify the Manage users page", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
@ -202,7 +200,7 @@ describe("user invite flow cases", () => {
});
});
it("Should verify the user onboarding with groups", () => {
it.skip("Should verify the user onboarding with groups", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
data.groupName1 = fake.firstName.replaceAll("[^A-Za-z]", "");
@ -264,6 +262,8 @@ describe("user invite flow cases", () => {
cy.wait(1000);
cy.defaultWorkspaceLogin();
cy.get(commonSelectors.homePageLogo, { timeout: 10000 }).should("be.visible");
navigateToManageGroups();
cy.get(groupsSelector.groupLink(data.groupName1)).click();
cy.get(groupsSelector.usersLink).click();
@ -350,10 +350,19 @@ describe("user invite flow cases", () => {
"have.text",
data.email
);
cy.get('[data-cy="modal-body"]>').verifyVisibleElement(
"have.text",
"Updating the user's details will change their role from end-user to admin. Are you sure you want to continue?"
);
if (envVar === "Enterprise") {
cy.get('[data-cy="modal-body"]>').verifyVisibleElement(
"have.text",
"Changing user default group from end-user to admin will affect the count of users covered by your plan.Are you sure you want to continue?"
);
} else {
cy.get('[data-cy="modal-body"]>').verifyVisibleElement(
"have.text",
"Changing the user role from end-user to admin will grant the user access to all resources and settings.Are you sure you want to continue?"
);
}
cy.get('.modal-footer > [data-cy="cancel-button"]').verifyVisibleElement(
"have.text",
"Cancel"
@ -427,153 +436,4 @@ describe("user invite flow cases", () => {
"Builder"
);
});
it("Should verify exisiting user invite flow", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
const workspaceName = data.firstName.toLowerCase();
addNewUser(data.firstName, data.email);
logout();
cy.defaultWorkspaceLogin();
cy.apiCreateWorkspace(workspaceName, workspaceName);
cy.visit(workspaceName);
navigateToManageUsers();
fillUserInviteForm(data.firstName, data.email);
cy.get(usersSelector.buttonInviteUsers).click();
cy.wait(2000);
visitWorkspaceInvitation(data.email, workspaceName);
cy.wait(3000);
cy.clearAndType(onboardingSelectors.loginEmailInput, data.email);
cy.clearAndType(onboardingSelectors.loginPasswordInput, "password");
cy.get(onboardingSelectors.signInButton).click();
cy.get(usersSelector.acceptInvite).click();
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.inviteToast);
logout();
cy.defaultWorkspaceLogin();
navigateToManageUsers();
searchUser(data.email);
cy.contains("td", data.email)
.parent()
.within(() => {
cy.get("td small").should("have.text", usersText.activeStatus);
});
});
it("should verify the user signup after invited in a workspace", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
data.signUpName = fake.firstName;
data.workspaceName = fake.companyName;
setSignupStatus(true);
navigateToManageUsers();
fillUserInviteForm(data.firstName, data.email);
cy.get(usersSelector.buttonInviteUsers).click();
cy.apiLogout();
cy.visit("/");
cy.get(commonSelectors.createAnAccountLink).click();
SignUpPageElements();
cy.wait(3000);
cy.clearAndType(onboardingSelectors.nameInput, data.signUpName);
cy.clearAndType(onboardingSelectors.signupEmailInput, data.email);
cy.clearAndType(
onboardingSelectors.loginPasswordInput,
commonText.password
);
cy.get(commonSelectors.signUpButton).click();
cy.wait(1000);
signUpLink(data.email);
cy.wait(1000);
visitWorkspaceInvitation(data.email, "My workspace");
cy.clearAndType(onboardingSelectors.signupEmailInput, data.email);
cy.clearAndType(onboardingSelectors.loginPasswordInput, usersText.password);
cy.get(onboardingSelectors.signInButton).click();
cy.wait(3000);
cy.get(commonSelectors.invitedUserName).verifyVisibleElement(
"have.text",
data.signUpName
);
cy.get(commonSelectors.acceptInviteButton).click();
});
it("should verify the user signup with same creds after invited in a workspace", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
data.signUpName = fake.firstName;
data.workspaceName = fake.companyName;
setSignupStatus(true);
navigateToManageUsers();
fillUserInviteForm(data.firstName, data.email);
cy.get(usersSelector.buttonInviteUsers).click();
logout();
cy.get(commonSelectors.createAnAccountLink).click();
SignUpPageElements();
cy.wait(5000);
cy.clearAndType(onboardingSelectors.nameInput, data.signUpName);
cy.clearAndType(onboardingSelectors.signupEmailInput, data.email);
cy.clearAndType(
onboardingSelectors.loginPasswordInput,
commonText.password
);
cy.get(commonSelectors.signUpButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
"The user is already registered. Please check your inbox for the activation link"
);
});
it("should verify exisiting user workspace signup from instance using form", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
data.signUpName = fake.firstName;
data.workspaceName = fake.firstName.toLowerCase();
setSignupStatus(true);
navigateToManageUsers();
addNewUser(data.firstName, data.email);
logout();
cy.wait(3000);
cy.get(commonSelectors.createAnAccountLink).click();
cy.wait(1000);
cy.clearAndType(onboardingSelectors.nameInput, data.firstName);
cy.clearAndType(onboardingSelectors.signupEmailInput, data.email);
cy.clearAndType(
onboardingSelectors.loginPasswordInput,
commonText.password
);
cy.get(commonSelectors.signUpButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
"User already exists in the workspace."
);
cy.apiLogin();
cy.apiCreateWorkspace(data.workspaceName, data.workspaceName);
cy.visit(`${data.workspaceName}`);
cy.wait(3000);
setSignupStatus(true, data.workspaceName);
logout();
cy.get(commonSelectors.createAnAccountLink).click();
cy.wait(3000);
cy.clearAndType(onboardingSelectors.nameInput, data.firstName);
cy.clearAndType(onboardingSelectors.signupEmailInput, data.email);
cy.clearAndType(
onboardingSelectors.loginPasswordInput,
commonText.password
);
cy.get(commonSelectors.signUpButton).click();
cy.defaultWorkspaceLogin();
visitWorkspaceInvitation(data.email, data.workspaceName);
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.inviteToast);
logout();
});
});

View file

@ -82,7 +82,7 @@ describe("Password reset functionality", () => {
});
// Get and visit reset password link
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select forgot_password_token from users where email='${data.email}';`,
}).then((resp) => {

View file

@ -0,0 +1,195 @@
import { commonSelectors } from "Selectors/common";
import { fake } from "Fixtures/fake";
import { usersText } from "Texts/manageUsers";
import { usersSelector } from "Selectors/manageUsers";
import { fillUserInviteForm } from "Support/utils/manageUsers";
import { commonText } from "Texts/common";
import { setSignupStatus } from "Support/utils/manageSSO";
import {
SignUpPageElements,
signUpLink,
verifyOnboardingQuestions,
visitWorkspaceInvitation,
addNewUser,
enableInstanceSignUp,
} from "Support/utils/onboarding";
import {
navigateToManageUsers,
logout,
searchUser,
} from "Support/utils/common";
import { onboardingSelectors } from "Selectors/onboarding";
const data = {};
const envVar = Cypress.env("environment");
describe("inviteflow edge cases", () => {
beforeEach(() => {
cy.defaultWorkspaceLogin();
if (envVar === "Enterprise") {
cy.get(".btn-close").click();
}
});
it("Should verify exisiting user invite flow", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
const workspaceName = data.firstName.toLowerCase();
addNewUser(data.firstName, data.email);
logout();
cy.defaultWorkspaceLogin();
cy.apiCreateWorkspace(workspaceName, workspaceName);
cy.visit(workspaceName);
navigateToManageUsers();
fillUserInviteForm(data.firstName, data.email);
cy.get(usersSelector.buttonInviteUsers).click();
cy.wait(2000);
visitWorkspaceInvitation(data.email, workspaceName);
cy.wait(3000);
cy.clearAndType(onboardingSelectors.loginEmailInput, data.email);
cy.clearAndType(onboardingSelectors.loginPasswordInput, "password");
cy.get(onboardingSelectors.signInButton).click();
cy.get(usersSelector.acceptInvite).click();
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.inviteToast);
logout();
cy.defaultWorkspaceLogin();
navigateToManageUsers();
searchUser(data.email);
cy.contains("td", data.email)
.parent()
.within(() => {
cy.get("td small").should("have.text", usersText.activeStatus);
});
});
it("should verify the user signup after invited in a workspace", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
data.signUpName = fake.firstName;
data.workspaceName = fake.companyName;
enableInstanceSignUp();
setSignupStatus(true);
navigateToManageUsers();
fillUserInviteForm(data.firstName, data.email);
cy.get(usersSelector.buttonInviteUsers).click();
cy.apiLogout();
cy.visit("/");
cy.get(commonSelectors.createAnAccountLink).click();
SignUpPageElements();
cy.wait(3000);
cy.clearAndType(onboardingSelectors.nameInput, data.signUpName);
cy.clearAndType(onboardingSelectors.signupEmailInput, data.email);
cy.clearAndType(
onboardingSelectors.loginPasswordInput,
commonText.password
);
cy.get(commonSelectors.signUpButton).click();
cy.wait(1000);
signUpLink(data.email);
if (envVar === "Enterprise") {
verifyOnboardingQuestions(data.workspaceName);
cy.wait(1000);
cy.get(commonSelectors.skipbutton).click();
cy.backToApps();
}
cy.wait(1000);
visitWorkspaceInvitation(data.email, "My workspace");
cy.clearAndType(onboardingSelectors.signupEmailInput, data.email);
cy.clearAndType(onboardingSelectors.loginPasswordInput, usersText.password);
cy.get(onboardingSelectors.signInButton).click();
cy.wait(3000);
cy.get(commonSelectors.invitedUserName).verifyVisibleElement(
"have.text",
data.signUpName
);
cy.get(commonSelectors.acceptInviteButton).click();
cy.get(commonSelectors.workspaceName).verifyVisibleElement(
"have.text",
"My workspace"
);
});
it("should verify the user signup with same creds after invited in a workspace", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
data.signUpName = fake.firstName;
data.workspaceName = fake.companyName;
setSignupStatus(true);
navigateToManageUsers();
fillUserInviteForm(data.firstName, data.email);
cy.get(usersSelector.buttonInviteUsers).click();
logout();
cy.get(commonSelectors.createAnAccountLink).click();
SignUpPageElements();
cy.wait(5000);
cy.clearAndType(onboardingSelectors.nameInput, data.signUpName);
cy.clearAndType(onboardingSelectors.signupEmailInput, data.email);
cy.clearAndType(
onboardingSelectors.loginPasswordInput,
commonText.password
);
cy.get(commonSelectors.signUpButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
"The user is already registered. Please check your inbox for the activation link"
);
});
it("should verify exisiting user workspace signup from instance using form", () => {
data.firstName = fake.firstName;
data.email = fake.email.toLowerCase().replaceAll("[^A-Za-z]", "");
data.signUpName = fake.firstName;
data.workspaceName = fake.firstName.toLowerCase();
setSignupStatus(true);
navigateToManageUsers();
addNewUser(data.firstName, data.email);
logout();
cy.wait(3000);
cy.get(commonSelectors.createAnAccountLink).click();
cy.wait(1000);
cy.clearAndType(onboardingSelectors.nameInput, data.firstName);
cy.clearAndType(onboardingSelectors.signupEmailInput, data.email);
cy.clearAndType(
onboardingSelectors.loginPasswordInput,
commonText.password
);
cy.get(commonSelectors.signUpButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
"User already exists in the workspace."
);
cy.apiLogin();
cy.apiCreateWorkspace(data.workspaceName, data.workspaceName);
cy.visit(`${data.workspaceName}`);
cy.wait(3000);
setSignupStatus(true, data.workspaceName);
logout();
cy.get(commonSelectors.createAnAccountLink).click();
cy.wait(3000);
cy.clearAndType(onboardingSelectors.nameInput, data.firstName);
cy.clearAndType(onboardingSelectors.signupEmailInput, data.email);
cy.clearAndType(
onboardingSelectors.loginPasswordInput,
commonText.password
);
cy.get(commonSelectors.signUpButton).click();
cy.defaultWorkspaceLogin();
visitWorkspaceInvitation(data.email, data.workspaceName);
cy.verifyToastMessage(commonSelectors.toastMessage, usersText.inviteToast);
logout();
});
});

View file

@ -262,8 +262,8 @@ describe("App creation", () => {
cy.get(importSelectors.dropDownMenu).click();
cy.get(commonSelectors.chooseFromTemplateButton).click();
cy.clearAndType('[data-cy="search-input-field"]', "Admin portal");
cy.get('[data-cy="admin-portal-list-item"]').click();
cy.clearAndType('[data-cy="search-input-field"]', "Admin panel");
cy.get('[data-cy="admin-panel-tooljet-db-list-item"]').click();
cy.get('[data-cy="create-application-from-template-button"]').click()
cy.wait(1000);
@ -277,7 +277,7 @@ describe("App creation", () => {
);
cy.get(commonSelectors.appNameInput).verifyVisibleElement(
"have.value",
"Admin portal"
"Admin Panel (ToolJet Database)"
);
cy.get(commonSelectors.appNameInfoLabel).verifyVisibleElement(
"have.text",

View file

@ -49,11 +49,11 @@ describe("dashboard", () => {
});
it("should verify the elements on empty dashboard", () => {
cy.intercept("GET", "/api/apps?page=1&folder=&searchKey=", {
cy.intercept("GET", "/api/apps?page=1&folder=&searchKey=&type=front-end", {
fixture: "intercept/emptyDashboard.json",
}).as("emptyDashboard");
cy.intercept("GET", "/api/folders?searchKey=", {
cy.intercept("GET", "/api/folder-apps?searchKey=&type=front-end", {
body: { folders: [] },
}).as("folders");
@ -87,7 +87,7 @@ describe("dashboard", () => {
cy.get(commonSelectors.createNewFolderButton).should("be.visible");
cy.get(commonSelectors.allApplicationLink).verifyVisibleElement(
"have.text",
commonText.allApplicationLink
commonText.allApplicationsLink
);
cy.get(commonSelectors.notificationsIcon).should("be.visible").click();
@ -312,6 +312,7 @@ describe("dashboard", () => {
cy.get(commonSelectors.appCard(data.cloneAppName)).should("be.visible");
cy.wait(3000)
viewAppCardOptions(data.cloneAppName);
cy.get(commonSelectors.appCardOptions(commonText.exportAppOption)).click();
cy.get(commonSelectors.exportAllButton).click();
@ -519,7 +520,7 @@ describe("dashboard", () => {
it("should verify the elements on empty dashboard for end user", () => {
cy.defaultWorkspaceLogin();
cy.intercept("GET", "/api/apps?page=1&folder=&searchKey=", {
cy.intercept("GET", "/api/apps?page=1&folder=&searchKey=&type=front-end", {
fixture: "intercept/emptyDashboard.json",
}).as("emptyDashboard")
roleBasedOnboarding(data.firstName, data.email, "end-user");

View file

@ -24,6 +24,7 @@ import {
exportAppModalSelectors,
importSelectors,
} from "Selectors/exportImport";
import { dashboardText } from "../../../../../../constants/texts/dashboard";
describe("Manage Groups", () => {
let data = {};
@ -114,9 +115,9 @@ describe("Manage Groups", () => {
cy.get(commonSelectors.cloneAppButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
commonText.cloneAppErrorToast
dashboardText.appClonedToast
);
cy.get(commonSelectors.cancelButton).click();
// cy.get(commonSelectors.cancelButton).click();
cy.apiLogout();
cy.apiLogin();

View file

@ -202,7 +202,6 @@ describe("Manage Groups", () => {
testDuplicateGroup();
createNewGroup();
// Verify permissions section
cy.get(groupsSelector.permissionsLink).click();
verifyPermissionSection();
@ -220,8 +219,8 @@ describe("Manage Groups", () => {
cy.get(`[data-cy="${groupName.toLowerCase()}-text"]`).click();
cy.get(`${groupsSelector.addEditPermissionModalTitle}:eq(2)`)
.verifyVisibleElement("have.text", groupsText.editPermissionModalTitle);
verifyModalFields(true, groupName);
cy.get(groupsSelector.editPermissionRadio).check();
verifyModalFields(true, groupName);
cy.get(groupsSelector.confimButton).click();
};

View file

@ -466,7 +466,7 @@ describe("Manage Groups", () => {
cy.wait(500);
cy.apiCreateGDS(
`${Cypress.env('server_host')}/api/v2/data_sources`,
`${Cypress.env('server_host')}/api/data-sources`,
`cypress-${data.dsName}-qc-postgresql`,
"postgresql",
[

View file

@ -111,24 +111,21 @@ export const onboardUserFromAppLink = (
WHERE u.email = '${email}' AND o.name = '${workspaceName}';
`;
return cy
.task("updateId", { dbconfig: dbConfig, sql: query })
.then((resp) => {
if (!resp.rows || resp.rows.length === 0) {
throw new Error(
`No records found for email: ${email} and workspace: ${workspaceName}`
);
}
cy.task("dbConnection", { dbconfig: dbConfig, sql: query }).then((resp) => {
if (!resp.rows || resp.rows.length === 0) {
throw new Error(
`No records found for email: ${email} and workspace: ${workspaceName}`
);
}
const { invitation_token, workspace_id, organization_token } =
resp.rows[0];
const token = isNonExistingUser ? organization_token : invitation_token;
const url = isNonExistingUser
? `${Cypress.config("baseUrl")}/invitations/${invitation_token}/workspaces/${organization_token}?oid=${workspace_id}&redirectTo=%2Fapplications%2F${slug}`
: `${Cypress.config("baseUrl")}/organization-invitations/${token}?oid=${workspace_id}&redirectTo=%2Fapplications%2F${slug}`;
const { invitation_token, workspace_id, organization_token } = resp.rows[0];
const token = isNonExistingUser ? organization_token : invitation_token;
const url = isNonExistingUser
? `${Cypress.config("baseUrl")}/invitations/${invitation_token}/workspaces/${organization_token}?oid=${workspace_id}&redirectTo=%2Fapplications%2F${slug}`
: `${Cypress.config("baseUrl")}/organization-invitations/${token}?oid=${workspace_id}&redirectTo=%2Fapplications%2F${slug}`;
cy.visit(url);
});
cy.visit(url);
});
};
export const resolveHost = () => {
@ -138,9 +135,8 @@ export const resolveHost = () => {
"http://localhost:8082": "http://localhost:8082",
"http://localhost:3000/apps": "http://localhost:3000/apps",
"http://localhost:4001": "http://localhost:3000",
"http://localhost:4001/apps": "http://localhost:3000/apps"
"http://localhost:4001/apps": "http://localhost:3000/apps",
};
return urlMapping[baseUrl];
};

View file

@ -91,7 +91,7 @@ export const navigateToAppEditor = (appName) => {
.find(commonSelectors.editButton)
.click({ force: true });
if (Cypress.env("environment") === "Community") {
cy.intercept("GET", "/api/v2/data_sources").as("appDs");
cy.intercept("GET", "/api/data-sources").as("appDs");
cy.wait("@appDs", { timeout: 15000 });
cy.skipEditorPopover();
} else {

View file

@ -4,7 +4,6 @@ import { cyParamName } from "Selectors/common";
import { commonSelectors, commonWidgetSelector } from "Selectors/common";
import { commonText } from "Texts/common";
import { dataSourceSelector } from "Selectors/dataSource";
import { verifyAppDelete } from "Support/utils/dashboard";
import { dataSourceText } from "Texts/dataSource";
import { navigateToAppEditor } from "Support/utils/common";
@ -62,16 +61,6 @@ export const deleteDatasource = (datasourceName) => {
// );
};
export const deleteAppandDatasourceAfterExecution = (
appName,
datasourceName
) => {
cy.backToApps();
cy.deleteApp(appName);
verifyAppDelete(appName);
deleteDatasource(datasourceName);
};
export const closeDSModal = () => {
cy.get("body").then(($body) => {
cy.wait(500);
@ -90,7 +79,7 @@ export const addQueryN = (queryName, query, dbName) => {
cy.clearAndType('[data-cy="gds-querymanager-search-bar"]', `${dbName}`);
}
});
cy.intercept("POST", "/api/data_queries").as("createQuery");
cy.intercept("POST", "/api/data-queries/**").as("createQuery");
cy.get(`[data-cy="${dbName}-add-query-card"] > .text-truncate`).click();
cy.get('[data-cy="query-rename-input"]').clear().type(queryName);
@ -107,7 +96,9 @@ export const addQueryN = (queryName, query, dbName) => {
export const addQuery = (queryName, query, dbName) => {
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(`${dbName}`);
cy.intercept("POST", "/api/data_queries").as("createQuery");
cy.intercept("POST", "/api/data-queries/**").as(
"createQuery"
);
cy.contains(`[id*="react-select-"]`, dbName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(queryName);
@ -141,7 +132,7 @@ export const addQueryAndOpenEditor = (queryName, query, dbName, appName) => {
cy.get('[data-cy="show-ds-popover-button"]').click();
cy.get(".css-4e90k9").type(`${dbName}`);
cy.get(".css-4e90k9").type(`${dbName}`);
cy.intercept("POST", "/api/data_queries").as("createQuery");
cy.intercept("POST", "/api/data-queries").as("createQuery");
cy.contains(`[id*="react-select-"]`, dbName).click();
cy.get('[data-cy="query-rename-input"]').clear().type(queryName);
@ -186,13 +177,13 @@ export const selectDatasource = (datasourceName) => {
export const createDataQuery = (appName, url, key, value) => {
let appId, versionId;
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select id from apps where name='${appName}';`,
}).then((resp) => {
appId = resp.rows[0].id;
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select id from app_versions where app_id='${appId}';`,
}).then((resp) => {
@ -206,7 +197,7 @@ export const createDataQuery = (appName, url, key, value) => {
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/data_queries`,
url: `${Cypress.env("server_host")}/api/data-queries`,
headers: headers,
body: {
app_id: appId,
@ -234,14 +225,7 @@ export const createDataQuery = (appName, url, key, value) => {
});
};
export const createRestAPIQuery = (
queryName,
dsName,
key = "",
value = "",
url = "",
run = true
) => {
export const createRestAPIQuery = (queryName, dsName, key = '', value = '', url = "", run = true) => {
cy.getCookie("tj_auth_token").then((cookie) => {
const headers = {
"Tj-Workspace-Id": Cypress.env("workspaceId"),
@ -251,7 +235,7 @@ export const createRestAPIQuery = (
cy.log(Cypress.env("appId"));
cy.request({
method: "GET",
url: `${Cypress.env("server_host")}/api/v2/apps/${Cypress.env("appId")}`,
url: `${Cypress.env("server_host")}/api/apps/${Cypress.env("appId")}`,
headers: headers,
}).then((response) => {
const editingVersionId = response.body.editing_version.id;
@ -281,7 +265,7 @@ export const createRestAPIQuery = (
cy.request({
method: "POST",
url: `${Cypress.env("server_host")}/api/data_queries`,
url: `${Cypress.env("server_host")}/api/data-queries/data-sources/${data_source_id}/versions/${editingVersionId}`,
headers: headers,
body: requestBody,
}).then((response) => {

View file

@ -5,7 +5,7 @@ import { navigateToManageGroups } from "Support/utils/common";
import { cyParamName } from "Selectors/common";
import { fake } from "Fixtures/fake";
import { onboardingSelectors } from "Selectors/onboarding";
import { fetchAndVisitInviteLink } from "Support/utils/onboarding";
import { fetchAndVisitInviteLink } from "Support/utils/manageUsers";
import { usersSelector } from "Selectors/manageUsers";
import { fillUserInviteForm } from "Support/utils/manageUsers";
import { navigateToManageUsers, logout } from "Support/utils/common";
@ -335,7 +335,7 @@ export const manageGroupsElements = () => {
);
cy.verifyElement(groupsSelector.confimButton, groupsText.updateButtonText);
cy.get(groupsSelector.confimButton).should("be.enabled");
cy.get(groupsSelector.confimButton).should("be.disabled");
cy.verifyElement(groupsSelector.cancelButton, groupsText.cancelButton);
cy.get(groupsSelector.cancelButton).click();
@ -542,7 +542,7 @@ export const manageGroupsElements = () => {
);
cy.verifyElement(groupsSelector.confimButton, groupsText.updateButtonText);
cy.get(groupsSelector.confimButton).should("be.enabled");
cy.get(groupsSelector.confimButton).should("be.disabled");
cy.verifyElement(groupsSelector.cancelButton, groupsText.cancelButton);
cy.get(groupsSelector.cancelButton).click();
//Add Modal
@ -680,7 +680,7 @@ export const createGroupAddAppAndUserToGroup = (groupName, email) => {
expect(response.status).to.equal(201);
});
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select id from users where email='${email}';`,
}).then((resp) => {
@ -864,7 +864,7 @@ export const createGroupsAndAddUserInGroup = (groupName, email) => {
export const inviteUserBasedOnRole = (firstName, email, role = "end-user") => {
fillUserInviteForm(firstName, email);
cy.get(".css-1dyz3mf").type(`${role}{enter}`);
cy.get(".css-1mlj61j").type(`${role}{enter}`);
cy.get(usersSelector.buttonInviteUsers).click();
cy.wait(500);

View file

@ -317,12 +317,12 @@ export const invitePageElements = () => {
.and("equal", "https://www.tooljet.com/privacy");
};
export const updateId = () => {
cy.task("updateId", {
export const dbConnection = () => {
cy.task("dbConnection", {
dbconfig: Cypress.config("db"),
sql: "update sso_configs set id='5edf41b2-ff2b-4932-9e2a-08aef4a303cc' where sso='google';",
});
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.config("db"),
sql: "update sso_configs set id='9628dee2-6fa9-4aca-9c98-ef950601c83e' where sso='git';",
});
@ -331,18 +331,18 @@ export const updateId = () => {
export const setSSOStatus = (workspaceName, ssoType, enabled) => {
let workspaceId;
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `SELECT id FROM organizations WHERE name = '${workspaceName}'`,
}).then((resp) => {
workspaceId = resp.rows[0].id;
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `SELECT * FROM sso_configs WHERE organization_id = '${workspaceId}' AND sso = '${ssoType}'`,
}).then((ssoConfigResp) => {
if (ssoConfigResp.rows.length > 0) {
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `UPDATE sso_configs SET enabled = ${enabled ? "true" : "false"
} WHERE organization_id = '${workspaceId}' AND sso = '${ssoType}'`,
@ -372,7 +372,7 @@ export const defaultSSO = (enable) => {
};
export const setSignupStatus = (enable, workspaceName = 'My workspace') => {
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `SELECT id FROM organizations WHERE name = '${workspaceName}'`,
}).then((resp) => {
@ -381,7 +381,7 @@ export const setSignupStatus = (enable, workspaceName = 'My workspace') => {
cy.getCookie("tj_auth_token").then((cookie) => {
cy.request({
method: "PATCH",
url: `${Cypress.env("server_host")}/api/organizations`,
url: `${Cypress.env("server_host")}/api/login-configs/organization-general`,
headers: {
"Tj-Workspace-Id": workspaceId,
Cookie: `tj_auth_token=${cookie.value}`,
@ -396,13 +396,13 @@ export const setSignupStatus = (enable, workspaceName = 'My workspace') => {
export const deleteOrganisationSSO = (workspaceName, services) => {
let workspaceId;
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select id from organizations where name='${workspaceName}';`,
}).then((resp) => {
workspaceId = resp.rows[0].id;
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `DELETE FROM sso_configs WHERE organization_id = '${workspaceId}' AND sso IN (${services
.map((service) => `'${service}'`)

View file

@ -6,6 +6,7 @@ import { ssoText } from "Texts/manageSSO";
import * as common from "Support/utils/common";
import { commonText } from "Texts/common";
import { onboardingSelectors } from "Selectors/onboarding";
const envVar = Cypress.env("environment");
export const manageUsersElements = () => {
cy.get(commonSelectors.breadcrumbTitle).should(($el) => {
@ -114,10 +115,17 @@ export const manageUsersElements = () => {
);
cy.get(usersSelector.buttonUploadCsvFile).click();
cy.get(usersSelector.helperTextBulkUpload).verifyVisibleElement(
"have.text",
usersText.helperTextBulkUpload
);
if (envVar === "Enterprise") {
cy.get(usersSelector.helperTextBulkUpload).verifyVisibleElement(
"have.text",
"Download the template to add user details or format your file in the same way as the template. Files in any other format may not be recognized. "
);
} else {
cy.get(usersSelector.helperTextBulkUpload).verifyVisibleElement(
"have.text",
usersText.helperTextBulkUpload
);
}
cy.get(usersSelector.buttonDownloadTemplate).verifyVisibleElement(
"have.text",
usersText.buttonDownloadTemplate
@ -320,44 +328,46 @@ export const inviteUserWithUserGroups = (
};
export const fetchAndVisitInviteLink = (email) => {
let invitationToken,
organizationToken,
workspaceId,
userId,
url = "";
let invitationToken, organizationToken, workspaceId, userId;
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select invitation_token from users where email='${email}';`,
}).then((resp) => {
invitationToken = resp.rows[0].invitation_token;
})
.then((resp) => {
invitationToken = resp.rows[0]?.invitation_token;
cy.task("updateId", {
dbconfig: Cypress.env("app_db"),
sql: "select id from organizations where name='My workspace';",
}).then((resp) => {
workspaceId = resp.rows[0].id;
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: "select id from organizations where name='My workspace';",
});
})
.then((resp) => {
workspaceId = resp.rows[0]?.id;
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select id from users where email='${email}';`,
}).then((resp) => {
userId = resp.rows[0].id;
cy.task("updateId", {
dbconfig: Cypress.env("app_db"),
sql: `select invitation_token from organization_users where user_id='${userId}';`,
}).then((resp) => {
organizationToken = resp.rows[1].invitation_token;
url = `/invitations/${invitationToken}/workspaces/${organizationToken}?oid=${workspaceId}`;
cy.apiLogout();
cy.wait(1000);
cy.visit(url);
});
});
})
.then((resp) => {
userId = resp.rows[0]?.id;
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select invitation_token from organization_users where user_id='${userId}';`,
});
})
.then((resp) => {
organizationToken =
resp.rows?.[1]?.invitation_token || resp.rows?.[0]?.invitation_token;
const url = `/invitations/${invitationToken}/workspaces/${organizationToken}?oid=${workspaceId}`;
cy.apiLogout();
cy.wait(1000);
cy.visit(url);
});
});
};
export const inviteUserWithUserRole = (firstName, email, role) => {
@ -389,4 +399,5 @@ export const inviteUserWithUserRole = (firstName, email, role) => {
cy.get(commonSelectors.signUpButton).click();
cy.wait(2000);
cy.get(commonSelectors.acceptInviteButton).click();
cy.get(commonSelectors.homePageLogo, { timeout: 10000 }).should("be.visible");
};

View file

@ -9,6 +9,7 @@ import { navigateToManageUsers, logout } from "Support/utils/common";
import { ssoSelector } from "Selectors/manageSSO";
import { ssoText } from "Texts/manageSSO";
import { onboardingSelectors } from "Selectors/onboarding";
import { fetchAndVisitInviteLink } from "Support/utils/manageUsers";
export const verifyConfirmEmailPage = (email) => {
cy.get(commonSelectors.pageLogo).should("be.visible");
@ -39,72 +40,11 @@ export const verifyConfirmEmailPage = (email) => {
);
};
export const verifyOnboardingQuestions = (fullName, workspaceName) => {
cy.wait(5000);
cy.get(commonSelectors.pageLogo).should("be.visible");
cy.get(commonSelectors.userAccountNameAvatar).should("be.visible");
cy.get(commonSelectors.createAccountCheckMark).should("be.visible");
cy.get(commonSelectors.createAccountCheckPoint).verifyVisibleElement(
"have.text",
commonText.createAccountCheckPoint
);
cy.get(commonSelectors.verifyEmailCheckMark).should("be.visible");
cy.get(commonSelectors.verifyEmailCheckPoint).verifyVisibleElement(
"have.text",
commonText.verifyEmailCheckPoint
);
cy.get(commonSelectors.setUpworkspaceCheckPoint).verifyVisibleElement(
"have.text",
commonText.setUpworkspaceCheckPoint
);
cy.get(commonSelectors.onboardingPorgressBubble).should("be.visible");
cy.get(commonSelectors.onboardingPageHeader).verifyVisibleElement(
"have.text",
commonText.companyPageHeader(fullName)
);
cy.get(commonSelectors.onboardingPageSubHeader).verifyVisibleElement(
"have.text",
commonText.onboardingPageSubHeader
);
cy.get(commonSelectors.companyNameInputField).should("be.visible");
cy.get(commonSelectors.continueButton).verifyVisibleElement(
"have.text",
commonText.continueButton
);
cy.get(commonSelectors.continueButton).should("be.disabled");
cy.clearAndType(commonSelectors.companyNameInputField, workspaceName);
cy.get(commonSelectors.continueButton).should("be.enabled").click();
cy.get(commonSelectors.backArrow).should("be.visible");
cy.get(commonSelectors.backArrowText).verifyVisibleElement(
"have.text",
commonText.backArrowText
);
cy.get(commonSelectors.onboardingPageHeader).verifyVisibleElement(
"have.text",
commonText.userRolePageHeader
);
verifyandModifyUserRole();
cy.get(commonSelectors.backArrow).should("be.visible");
cy.get(commonSelectors.onboardingPageHeader).verifyVisibleElement(
"have.text",
commonText.sizeOftheCompanyHeader
);
verifyandModifySizeOftheCompany();
cy.get(commonSelectors.backArrow).should("be.visible");
cy.get(commonSelectors.onboardingPageHeader).verifyVisibleElement(
"have.text",
"Enter your phone number"
);
cy.get(".form-control").should("be.visible");
cy.get(".tj-onboarding-phone-input-wrapper")
.find("input")
.type("919876543210");
cy.get(commonSelectors.continueButton).click();
export const verifyOnboardingQuestions = (workspaceName) => {
bannerElementsVerification();
onboardingStepOne();
onboardingStepTwo(workspaceName);
onboardingStepThree();
};
export const verifyInvalidInvitationLink = () => {
@ -128,13 +68,15 @@ export const verifyInvalidInvitationLink = () => {
export const userSignUp = (fullName, email, workspaceName = "test") => {
let invitationLink;
cy.intercept("GET", "/api/organizations/public-configs").as("publicConfig");
cy.intercept("GET", "/api/login-configs/public").as("publicConfig");
cy.visit("/");
cy.wait("@publicConfig");
cy.wait(2000)
cy.wait(2000);
cy.get(commonSelectors.createAnAccountLink, { timout: 10000 }).click();
cy.wait(2000);
cy.get(onboardingSelectors.nameInput, { timeout: 1000 }).should("not.be.disabled");
cy.get(onboardingSelectors.nameInput, { timeout: 1000 }).should(
"not.be.disabled"
);
cy.get(onboardingSelectors.nameInput).clear();
cy.get(onboardingSelectors.nameInput).type(fullName);
cy.clearAndType(onboardingSelectors.loginEmailInput, email);
@ -142,7 +84,7 @@ export const userSignUp = (fullName, email, workspaceName = "test") => {
cy.get(commonSelectors.signUpButton).click();
cy.wait(2500);
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select invitation_token from users where email='${email}';`,
}).then((resp) => {
@ -151,52 +93,14 @@ export const userSignUp = (fullName, email, workspaceName = "test") => {
cy.wait(2500);
});
if (Cypress.env("environment") !== "Community") {
cy.clearAndType('[data-cy="onboarding-workspace-name-input"]', workspaceName);
cy.clearAndType(
'[data-cy="onboarding-workspace-name-input"]',
workspaceName
);
cy.get('[data-cy="onboarding-submit-button"]').click();
}
};
export const fetchAndVisitInviteLink = (email) => {
let invitationToken,
organizationToken,
workspaceId,
userId,
url = "";
cy.task("updateId", {
dbconfig: Cypress.env("app_db"),
sql: `select invitation_token from users where email='${email}';`,
}).then((resp) => {
invitationToken = resp.rows[0].invitation_token;
cy.task("updateId", {
dbconfig: Cypress.env("app_db"),
sql: "select id from organizations where name='My workspace';",
}).then((resp) => {
workspaceId = resp.rows[0].id;
cy.task("updateId", {
dbconfig: Cypress.env("app_db"),
sql: `select id from users where email='${email}';`,
}).then((resp) => {
userId = resp.rows[0].id;
cy.task("updateId", {
dbconfig: Cypress.env("app_db"),
sql: `select invitation_token from organization_users where user_id='${userId}';`,
}).then((resp) => {
organizationToken = resp.rows[1].invitation_token;
url = `/invitations/${invitationToken}/workspaces/${organizationToken}?oid=${workspaceId}`;
cy.apiLogout();
cy.wait(1000);
cy.visit(url);
});
});
});
});
};
export const inviteUser = (firstName, email) => {
cy.apiUserInvite(firstName, email);
fetchAndVisitInviteLink(email);
@ -227,23 +131,22 @@ export const roleBasedOnboarding = (firstName, email, userRole) => {
cy.get(commonSelectors.acceptInviteButton).click();
};
export const visitWorkspaceInvitation = (email, workspaceName) => {
let workspaceId, userId, url, organizationToken;
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select id from organizations where name='${workspaceName}';`,
}).then((resp) => {
workspaceId = resp.rows[0].id;
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select id from users where email='${email}';`,
}).then((resp) => {
userId = resp.rows[0].id;
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select invitation_token from organization_users where organization_id= '${workspaceId}' AND user_id='${userId}';`,
}).then((resp) => {
@ -324,13 +227,14 @@ export const SignUpPageElements = () => {
export const signUpLink = (email) => {
let invitationLink;
cy.task("updateId", {
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `select invitation_token from users where email='${email}';`,
}).then((resp) => {
invitationLink = `/invitations/${resp.rows[0].invitation_token}`;
cy.visit(invitationLink);
cy.wait(3000);
});
};
@ -343,4 +247,94 @@ export const bannerElementsVerification = () => {
bannerElements.forEach((element) => {
cy.get(element.selector).should("be.visible");
});
};
};
export const enableInstanceSignUp = (allow = true) => {
const value = allow ? "true" : "false";
cy.task("dbConnection", {
dbconfig: Cypress.env("app_db"),
sql: `UPDATE instance_settings SET value = '${value}' WHERE key = 'ALLOW_PERSONAL_WORKSPACE';
UPDATE instance_settings SET value = '${value}' WHERE key = 'ENABLE_SIGNUP';`,
});
};
export const onboardingStepOne = () => {
const companyPageTexts = [
{
selector: onboardingSelectors.tellUsAbit,
text: "Tell us a bit about yourself",
},
{
selector: onboardingSelectors.pageDescription,
text: "This information will help us improve ToolJet",
},
{
selector: '[data-cy="onboarding-company-name-label"]',
text: "Company name *",
},
{
selector: '[data-cy="onboarding-build-purpose-label"]',
text: "What would you like to build on ToolJet? *",
},
];
companyPageTexts.forEach((item) => {
cy.get(item.selector).should("be.visible").and("have.text", item.text);
});
cy.get(onboardingSelectors.companyNameInput).should("be.visible");
cy.get(onboardingSelectors.buildPurposeInput).should("be.visible");
cy.get(onboardingSelectors.onboardingSubmitButton).verifyVisibleElement(
"have.attr",
"disabled"
);
cy.get(onboardingSelectors.companyNameInput).type("Tooljet");
cy.get(onboardingSelectors.onboardingSubmitButton).should(
"have.attr",
"disabled"
);
cy.get(onboardingSelectors.buildPurposeInput).type("Exploring");
cy.get(onboardingSelectors.onboardingSubmitButton).verifyVisibleElement(
"have.text",
"Continue"
);
cy.get(onboardingSelectors.onboardingSubmitButton)
.should("be.enabled")
.click();
};
export const onboardingStepTwo = (workspaceName = "My workspace") => {
cy.get(commonSelectors.setUpworkspaceCheckPoint)
.should("be.visible")
.and("have.text", "Set up your workspace!");
cy.get(onboardingSelectors.pageDescription).verifyVisibleElement(
"have.text",
"Set up workspaces to manage users, applications & resources across various teams"
);
cy.get(commonSelectors.workspaceNameInputLabel)
.should("be.visible")
.and("have.text", commonText.workspaceNameInputLabel);
cy.clearAndType(commonSelectors.workspaceNameInputField, workspaceName);
cy.get(commonSelectors.OnbordingContinue)
.verifyVisibleElement("have.text", "Continue")
.click();
};
export const onboardingStepThree = () => {
cy.get(
`[data-cy="we've-created-a-sample-application-for-you!-header"]`
).verifyVisibleElement(
"have.text",
"We've created a sample application for you!"
);
cy.get(onboardingSelectors.pageDescription).verifyVisibleElement(
"have.text",
"The sample application comes with a sample PostgreSQL database for you to play around with. You can also get started quickly with pre-built applications from our template collection!"
);
cy.get(onboardingSelectors.onboardingSubmitButton)
.verifyVisibleElement("have.text", "Continue")
.click();
};

View file

@ -48,23 +48,24 @@ export const selectAndAddDataSource = (
dataSourceName
) => {
cy.get(commonSelectors.globalDataSourceIcon).click();
cy.wait(1000);
cy.wait(1000)
cy.get(`[data-cy="${cyParamName(dscategory)}-datasource-button"]`).click();
cy.wait(500);
cy.wait(500)
cy.get(postgreSqlSelector.dataSourceSearchInputField).type(dataSource);
cy.get(`[data-cy="data-source-${dataSource.toLowerCase()}"]`)
cy.get(`[data-cy="data-source-${(dataSource).toLowerCase()}"]`)
.parent()
.within(() => {
cy.get(
`[data-cy="data-source-${dataSource.toLowerCase()}"]>>>.datasource-card-title`
`[data-cy="data-source-${(
dataSource
).toLowerCase()}"]>>>.datasource-card-title`
).realHover("mouse");
cy.get(
`[data-cy="${cyParamName(dataSource).toLowerCase()}-add-button"]`
).click();
});
cy.wait(1000);
cy.get(postgreSqlSelector.buttonSave).should("be.disabled");
cy.get(postgreSqlSelector.buttonSave).should("be.disabled")
cy.clearAndType(
'[data-cy="data-source-name-input-field"]',
cyParamName(`cypress-${dataSourceName}-${dataSource}`)
@ -125,8 +126,7 @@ export const fillDataSourceTextField = (
);
cy.get(`[data-cy="${cyParamName(fieldName)}-text-field"]`).then(($field) => {
if ($field.is(":disabled")) {
cy.get(".datasource-edit-btn").wait(500).click();
cy.wait(500);
cy.get(".datasource-edit-btn").click();
}
});
cy.get(`[data-cy="${cyParamName(fieldName)}-text-field"]`)
@ -134,9 +134,7 @@ export const fillDataSourceTextField = (
.should("eq", placeholder.replace(/\u00a0/g, " "));
cy.get(`[data-cy="${cyParamName(fieldName)}-text-field"]`)
.wait(500)
.clear()
.wait(500)
.type(input, args);
};
@ -185,4 +183,4 @@ export const addWidgetsToAddUser = () => {
addEventHandlerToRunQuery("add_data_using_widgets");
};
export const addListviewToVerifyData = () => {};
export const addListviewToVerifyData = () => { };

View file

@ -7,10 +7,7 @@ import {
confirmVersionModalSelectors,
editVersionSelectors,
} from "Selectors/version";
import {
deleteVersionText,
releasedVersionText,
} from "Texts/version";
import { deleteVersionText, releasedVersionText } from "Texts/version";
import { verifyComponent } from "Support/utils/basicComponents";
export const navigateToCreateNewVersionModal = (value) => {
@ -98,9 +95,16 @@ export const deleteVersionAndVerify = (value, toastMessageText) => {
.click({ force: true });
});
cy.get(commonSelectors.modalMessage).verifyVisibleElement("have.text", deleteVersionText.deleteModalText(value))
cy.get(commonSelectors.modalMessage).verifyVisibleElement(
"have.text",
deleteVersionText.deleteModalText(value)
);
cy.get(confirmVersionModalSelectors.yesButton).click();
cy.verifyToastMessage(commonSelectors.toastMessage, deleteVersionText.deleteToastMessage(value), false);
cy.verifyToastMessage(
commonSelectors.toastMessage,
deleteVersionText.deleteToastMessage(value),
false
);
};
export const verifyDuplicateVersion = (newVersion = [], version) => {
@ -111,7 +115,8 @@ export const verifyDuplicateVersion = (newVersion = [], version) => {
cy.get(appVersionSelectors.createNewVersionButton).click();
cy.verifyToastMessage(
commonSelectors.toastMessage,
appVersionText.versionNameAlreadyExists
// appVersionText.versionNameAlreadyExists
"Already exists!"
);
};
@ -139,7 +144,7 @@ export const verifyVersionAfterPreview = (currentVersion) => {
.click();
cy.url().should("include", "/home");
cy.wait(2000);
cy.get('[data-cy^="draggable-widget-table"]').should('be.visible')
cy.get('[data-cy^="draggable-widget-table"]').should("be.visible");
cy.url().should("include", `version=${currentVersion}`);
cy.get('[data-cy="viewer-page-logo"]').click();
cy.wait(8000);
@ -149,5 +154,5 @@ export const switchVersionAndVerify = (currentVersion, newVersion) => {
cy.get(appVersionSelectors.currentVersionField(currentVersion))
.should("be.visible")
.click();
cy.get('.app-version-name').contains(newVersion).click();
}
cy.get(".app-version-name").contains(newVersion).click();
};

View file

@ -1 +1 @@
3.7.0
3.11.0

View file

@ -0,0 +1,39 @@
import React from 'react';
const EmailInput = ({ fill = '#D7DBDF', width = 24, className = '', viewBox = '0 0 49 48' }) => (
<svg
width={width}
height={width}
viewBox={viewBox}
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M2.39498 1.9349C3.35945 0.97044 4.66757 0.428589 6.03153 0.428589H43.7458C45.1097 0.428589 46.418 0.97044 47.3825 1.9349C48.347 2.89935 48.8887 4.20746 48.8887 5.57145V17.3819C47.4096 16.4176 45.6432 15.8572 43.7458 15.8572H28.3172C23.11 15.8572 18.8887 20.0785 18.8887 25.2857V31.2857H6.03153C4.66754 31.2857 3.35945 30.7439 2.39498 29.7794C1.43051 28.815 0.888672 27.5068 0.888672 26.1429V5.57145C0.888672 4.20749 1.43051 2.89935 2.39498 1.9349Z"
fill="#CCD1D5"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M12.8886 10.2858C14.0721 10.2858 15.0315 11.2451 15.0315 12.4286V19.2858C15.0315 20.4692 14.0721 21.4286 12.8886 21.4286C11.7052 21.4286 10.7458 20.4692 10.7458 19.2858V12.4286C10.7458 11.2451 11.7052 10.2858 12.8886 10.2858Z"
fill="#4368E3"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M28.3172 20.1428C25.4769 20.1428 23.1744 22.4453 23.1744 25.2857V36.4285C23.1744 39.2688 25.4769 41.5714 28.3172 41.5714H43.7458C46.586 41.5714 48.8887 39.2688 48.8887 36.4285V25.2857C48.8887 22.4453 46.586 20.1428 43.7458 20.1428H28.3172Z"
fill="#4368E3"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M48.7087 23.9319L36.0315 29.7828L23.3545 23.9318C23.2371 24.3631 23.1744 24.8171 23.1744 25.2857V28.5688L35.1337 34.0886C35.7034 34.3515 36.36 34.3515 36.9298 34.0886L48.8887 28.5689V25.2857C48.8887 24.8172 48.8259 24.3632 48.7087 23.9319Z"
fill="#CCD1D5"
/>
</svg>
);
export default EmailInput;

View file

@ -0,0 +1,22 @@
import React from 'react';
const HorizontalDivider = ({ fill = '#D7DBDF', width = 24, className = '', viewBox = '0 0 49 48' }) => (
<svg xmlns="http://www.w3.org/2000/svg" width={width} height={width} viewBox={viewBox} fill="none">
<g clip-path="url(#clip0_0_153)">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M0.888672 23.7143C0.888672 21.9785 2.29578 20.5714 4.03153 20.5714H41.7458C43.4816 20.5714 44.8887 21.9785 44.8887 23.7143C44.8887 25.45 43.4816 26.8571 41.7458 26.8571H4.03153C2.29578 26.8571 0.888672 25.45 0.888672 23.7143Z"
fill="#CCD1D5"
/>
<circle cx="22.8887" cy="23.7144" r="5.15332" fill="#4368E3" />
</g>
<defs>
<clipPath id="clip0_0_153">
<rect width="48" height="48" fill="white" transform="translate(0.888672)" />
</clipPath>
</defs>
</svg>
);
export default HorizontalDivider;

View file

@ -13,8 +13,6 @@ import Customcomponent from './customcomponent.jsx';
import Datepicker from './datepicker.jsx';
import DateTimePickerV2 from './datetimepickerV2.jsx';
import Daterangepicker from './daterangepicker.jsx';
import Divider from './divider.jsx';
import DividerHorizondal from './dividerhorizontal.jsx';
import Downstatistics from './downstatistics.jsx';
import Dropdown from './dropdown.jsx';
import Filepicker from './filepicker.jsx';
@ -59,6 +57,9 @@ import Upstatistics from './upstatistics.jsx';
import Verticaldivider from './verticaldivider.jsx';
import TimePicker from './timepicker.jsx';
import DatepickerV2 from './datepickerv2.jsx';
import HorizontalDivider from './horizontalDivider.jsx';
import PhoneInput from './phoneinput.jsx';
import EmailInput from './emailinput.jsx';
const WidgetIcon = (props) => {
switch (props.name) {
@ -99,12 +100,14 @@ const WidgetIcon = (props) => {
return <Datepicker {...props} />;
case 'datetimepickerv2':
return <DateTimePickerV2 {...props} />;
case 'emailinput':
return <EmailInput {...props} />;
case 'phoneinput':
return <PhoneInput {...props} />;
case 'daterangepicker':
return <Daterangepicker {...props} />;
case 'horizontaldivider':
return <Divider {...props} />;
case 'divider-horizondal':
return <DividerHorizondal {...props} />;
return <HorizontalDivider {...props} />;
case 'downstatistics':
return <Downstatistics {...props} />;
case 'dropdown':
@ -180,6 +183,7 @@ const WidgetIcon = (props) => {
case 'text':
return <Text {...props} />;
case 'textarea':
case 'textarealegacy':
return <TextArea {...props} />;
case 'textinput':
return <Textinput {...props} />;

View file

@ -0,0 +1,36 @@
import React from 'react';
const PhoneInput = ({ fill = '#D7DBDF', width = 24, className = '', viewBox = '0 0 49 48' }) => (
<svg
width={width}
height={width}
viewBox={viewBox}
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.60296 9.85718C4.99933 9.85718 2.88867 11.9678 2.88867 14.5715V33.4286C2.88867 36.0322 4.99933 38.1429 7.60296 38.1429H42.1744C44.7779 38.1429 46.8887 36.0322 46.8887 33.4286V14.5715C46.8887 11.9678 44.7779 9.85718 42.1744 9.85718H7.60296Z"
fill="#CCD1D5"
/>
<path
d="M26.853 27.0714C25.7681 27.0714 24.8887 27.9508 24.8887 29.0357C24.8887 30.1205 25.7681 31 26.853 31H32.8884C33.9733 31 34.8527 30.1205 34.8527 29.0357C34.8527 27.9508 33.9733 27.0714 32.8884 27.0714H26.853Z"
fill="#4368E3"
/>
<g clip-path="url(#clip0_39_14)">
<path
d="M14.113 30.4517C13.4512 30.8793 12.6626 31.0657 11.8797 30.9794C11.0969 30.8932 10.3675 30.5396 9.81433 29.9781L9.33073 29.5045C9.11871 29.2873 9 28.9956 9 28.6919C9 28.3881 9.11871 28.0965 9.33073 27.8793L11.3834 25.845C11.5984 25.6333 11.8879 25.5147 12.1894 25.5147C12.4909 25.5147 12.7803 25.6333 12.9954 25.845C13.2122 26.0573 13.5035 26.1762 13.8068 26.1762C14.11 26.1762 14.4013 26.0573 14.6181 25.845L17.8422 22.616C17.9497 22.5099 18.0351 22.3834 18.0934 22.2439C18.1517 22.1043 18.1817 21.9546 18.1817 21.8034C18.1817 21.6522 18.1517 21.5024 18.0934 21.3629C18.0351 21.2234 17.9497 21.0969 17.8422 20.9908C17.6308 20.7754 17.5124 20.4855 17.5124 20.1835C17.5124 19.8815 17.6308 19.5916 17.8422 19.3763L19.8841 17.3312C20.1009 17.1189 20.3922 17 20.6954 17C20.9987 17 21.29 17.1189 21.5068 17.3312L21.9797 17.8156C22.5403 18.3696 22.8933 19.1001 22.9794 19.8842C23.0656 20.6682 22.8795 21.4581 22.4525 22.1209C20.2279 25.4045 17.3973 28.2321 14.113 30.4517Z"
fill="#4368E3"
/>
</g>
<defs>
<clipPath id="clip0_39_14">
<rect width="14" height="14" fill="white" transform="translate(9 17)" />
</clipPath>
</defs>
</svg>
);
export default PhoneInput;

View file

@ -1,14 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="29px" height="29px" viewBox="0 0 29 29" version="1.1">
<defs>
<clipPath id="clip1">
<path d="M 5.273438 5.273438 L 23.726562 5.273438 L 23.726562 23.726562 L 5.273438 23.726562 Z M 5.273438 5.273438 "/>
</clipPath>
</defs>
<g id="surface1">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(24.313725%,38.823529%,86.666667%);fill-opacity:1;" d="M 3.953125 0 L 25.046875 0 C 27.230469 0 29 1.769531 29 3.953125 L 29 25.046875 C 29 27.230469 27.230469 29 25.046875 29 L 3.953125 29 C 1.769531 29 0 27.230469 0 25.046875 L 0 3.953125 C 0 1.769531 1.769531 0 3.953125 0 Z M 3.953125 0 "/>
<g clip-path="url(#clip1)" clip-rule="nonzero">
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(99.215686%,99.215686%,99.607843%);fill-opacity:1;" d="M 13.6875 23.726562 C 13.6875 22.445312 13.679688 21.207031 13.695312 19.972656 C 13.699219 19.695312 13.617188 19.511719 13.421875 19.320312 C 12.117188 18.03125 10.824219 16.734375 9.527344 15.4375 C 9.425781 15.335938 9.324219 15.292969 9.179688 15.292969 C 7.957031 15.300781 6.730469 15.296875 5.503906 15.296875 C 5.429688 15.296875 5.351562 15.296875 5.277344 15.296875 C 5.277344 15.265625 5.269531 15.25 5.273438 15.242188 C 5.761719 14.300781 6.15625 13.304688 6.785156 12.441406 C 7.320312 11.714844 7.992188 11.175781 8.917969 11.042969 C 9.222656 11 9.53125 11.011719 9.839844 11.007812 C 10.386719 11.003906 10.933594 11.003906 11.480469 11.011719 C 11.628906 11.015625 11.71875 10.960938 11.808594 10.847656 C 13.429688 8.804688 15.410156 7.222656 17.804688 6.171875 C 19.144531 5.585938 20.539062 5.242188 22.019531 5.273438 C 22.578125 5.289062 23.136719 5.277344 23.695312 5.277344 C 23.707031 5.304688 23.726562 5.324219 23.726562 5.339844 C 23.71875 6.171875 23.746094 7.003906 23.699219 7.835938 C 23.625 9.25 23.164062 10.566406 22.535156 11.828125 C 21.484375 13.9375 20.007812 15.707031 18.15625 17.164062 C 18.035156 17.261719 17.980469 17.355469 17.980469 17.511719 C 17.992188 18.148438 17.96875 18.785156 17.988281 19.421875 C 18.023438 20.503906 17.589844 21.367188 16.75 22.019531 C 16.347656 22.328125 15.902344 22.589844 15.457031 22.835938 C 14.890625 23.148438 14.300781 23.421875 13.6875 23.726562 Z M 19.421875 10.996094 C 19.414062 10.203125 18.78125 9.574219 17.988281 9.578125 C 17.199219 9.578125 16.554688 10.21875 16.554688 11 C 16.550781 11.789062 17.214844 12.445312 18.003906 12.4375 C 18.792969 12.429688 19.425781 11.785156 19.421875 10.996094 Z M 19.421875 10.996094 "/>
</g>
<path style=" stroke:none;fill-rule:nonzero;fill:rgb(99.215686%,99.215686%,99.607843%);fill-opacity:1;" d="M 10.761719 20.328125 C 9.726562 21.703125 8.207031 22.195312 6.511719 22.453125 C 6.839844 20.816406 7.25 19.257812 8.648438 18.214844 C 9.347656 18.914062 10.054688 19.621094 10.761719 20.328125 Z M 10.761719 20.328125 "/>
</g>
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="32" height="32" fill="#4368E3"/>
<path d="M28.2237 16L25.8501 19.3949L25.4999 19.8933L23.4766 22.7859V22.7898L22.7762 23.7865H13.2743L13.9747 22.7859L16.0019 19.8933H20.749L21.0992 19.3949L23.4766 16L21.0992 12.6051L20.749 12.1068H16.6984L18.7256 9.21409V9.2102L19.426 8.21353H22.7762L23.4766 9.2102V9.21409L25.4999 12.1068L25.8501 12.6051L28.2237 16Z" fill="white"/>
<path d="M13.9747 16L11.6011 19.3949L11.2509 19.8933L8.5272 23.7865H3.77625L6.49997 19.8933L9.2237 16L6.49997 12.1068L3.77625 8.21353H8.5272L11.2509 12.1068L11.6011 12.6051L13.9747 16Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 685 B

View file

@ -0,0 +1,4 @@
<svg width="26" height="26" viewBox="0 0 63 63" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M57.7503 31.5L52.6532 38.7905L51.9012 39.8606L47.5561 46.0726V46.0809L46.0521 48.2213H25.647L27.151 46.0726L31.5045 39.8606H41.6986L42.4507 38.7905L47.5561 31.5L42.4507 24.2095L41.6986 23.1394H33.0001L37.3536 16.9274V16.9191L38.8576 14.7787H46.0521L47.5561 16.9191V16.9274L51.9012 23.1394L52.6532 24.2095L57.7503 31.5Z" fill="#4368E3"/>
<path d="M27.1503 31.5L22.0532 38.7905L21.3012 39.8606L15.4521 48.2213H5.24951L11.0987 39.8606L16.9478 31.5L11.0987 23.1394L5.24951 14.7787H15.4521L21.3012 23.1394L22.0532 24.2095L27.1503 31.5Z" fill="#4368E3"/>
</svg>

After

Width:  |  Height:  |  Size: 661 B

View file

@ -1,131 +0,0 @@
import { decodeEntities } from '@/_helpers/utils';
export const defaultWhiteLabellingSettings = {
WHITE_LABEL_LOGO: 'assets/images/rocket.svg',
WHITE_LABEL_TEXT: 'ToolJet',
WHITE_LABEL_FAVICON: 'assets/images/logo.svg',
};
export const whiteLabellingOptions = {
WHITE_LABEL_LOGO: 'App Logo',
WHITE_LABEL_TEXT: 'Page Title',
WHITE_LABEL_FAVICON: 'Favicon',
};
export async function fetchWhiteLabelDetails() {}
export async function checkWhiteLabelsDefaultState() {
return true;
}
export async function resetToDefaultWhiteLabels() {}
export function retrieveWhiteLabelText() {
return window.public_config?.WHITE_LABEL_TEXT || defaultWhiteLabellingSettings.WHITE_LABEL_TEXT;
}
export function retrieveWhiteLabelLogo() {
return window.public_config?.WHITE_LABEL_LOGO || defaultWhiteLabellingSettings.WHITE_LABEL_LOGO;
}
export function retrieveWhiteLabelFavicon() {
return window.public_config?.WHITE_LABEL_FAVICON || defaultWhiteLabellingSettings.WHITE_LABEL_FAVICON;
}
export const pageTitles = {
INSTANCE_SETTINGS: 'Settings',
WORKSPACE_SETTINGS: 'Workspace settings',
INTEGRATIONS: 'Marketplace',
WORKFLOWS: 'Workflows',
DATABASE: 'Database',
DATA_SOURCES: 'Data sources',
AUDIT_LOGS: 'Audit logs',
ACCOUNT_SETTINGS: 'Profile settings',
SETTINGS: 'Profile settings',
EDITOR: 'Editor',
WORKFLOW_EDITOR: 'workflowEditor',
VIEWER: 'Viewer',
DASHBOARD: 'Dashboard',
WORKSPACE_CONSTANTS: 'Workspace constants',
};
// to set favicon and title from router for individual pages
export async function setFaviconAndTitle(whiteLabelFavicon, whiteLabelText, location) {
if (!whiteLabelFavicon || !whiteLabelText) {
whiteLabelFavicon = await retrieveWhiteLabelFavicon();
whiteLabelText = await retrieveWhiteLabelText();
}
// Set favicon
let links = document.querySelectorAll("link[rel='icon']");
if (links.length === 0) {
const link = document.createElement('link');
link.rel = 'icon';
link.type = 'image/svg+xml';
document.getElementsByTagName('head')[0].appendChild(link);
links = [link];
}
links.forEach((link) => {
link.href = `${whiteLabelFavicon || defaultWhiteLabellingSettings.WHITE_LABEL_FAVICON}`;
});
// Set title
const isEditorOrViewerGoingToRender = ['/apps/', '/applications/'].some((path) => location?.pathname.includes(path));
if (isEditorOrViewerGoingToRender) {
return;
}
const pathToTitle = {
'instance-settings': pageTitles.INSTANCE_SETTINGS,
'workspace-settings': pageTitles.WORKSPACE_SETTINGS,
integrations: pageTitles.INTEGRATIONS,
workflows: pageTitles.WORKFLOWS,
'data-sources': pageTitles.DATA_SOURCES,
'audit-logs': pageTitles.AUDIT_LOGS,
'account-settings': pageTitles.ACCOUNT_SETTINGS,
settings: pageTitles.INSTANCE_SETTINGS,
login: '',
signUp: '',
error: '',
signup: '',
'organization-invitations': '',
invitation: '',
'forgot-password': '',
'reset-password': '',
'workspace-constants': pageTitles.WORKSPACE_CONSTANTS,
setup: '',
};
const pageTitleKey = Object.keys(pathToTitle).find((path) => location?.pathname.includes(path));
const pageTitle = pathToTitle[pageTitleKey];
//For undefined routes
if (pageTitle === undefined) {
return;
}
if (pageTitleKey && !isEditorOrViewerGoingToRender) {
document.title = pageTitle
? `${decodeEntities(pageTitle)} | ${whiteLabelText || defaultWhiteLabellingSettings.WHITE_LABEL_TEXT}`
: `${decodeEntities(whiteLabelText) || defaultWhiteLabellingSettings.WHITE_LABEL_TEXT}`;
}
}
export async function fetchAndSetWindowTitle(pageDetails) {
const whiteLabelText = retrieveWhiteLabelText();
let pageTitleKey = pageDetails?.page || '';
let pageTitle = '';
switch (pageTitleKey) {
case pageTitles.VIEWER: {
const titlePrefix = pageDetails?.preview ? 'Preview - ' : '';
pageTitle = `${titlePrefix}${pageDetails?.appName || 'My App'}`;
break;
}
case pageTitles.EDITOR:
case pageTitles.WORKFLOW_EDITOR: {
pageTitle = pageDetails?.appName || 'My App';
break;
}
default: {
pageTitle = pageTitleKey;
break;
}
}
document.title = !(pageDetails?.preview === false) ? `${pageTitle} | ${whiteLabelText}` : `${pageTitle}`;
}

@ -1 +1 @@
Subproject commit d93ee7e1318f044ef2327671b8b257648071453d
Subproject commit dcd948d284b5f14a868480830e09b90496db8572

View file

@ -38,6 +38,7 @@
"@sentry/react": "^7.100.1",
"@sentry/webpack-plugin": "^2.14.0",
"@tabler/icons-react": "^2.4.0",
"@tanstack/react-table": "^8.20.5",
"@tanstack/react-virtual": "^3.10.8",
"@textea/json-viewer": "^3.3.2",
"@tooljet/plugins": "../plugins",
@ -96,6 +97,7 @@
"react-circular-progressbar": "^2.1.0",
"react-color": "^2.19.3",
"react-copy-to-clipboard": "^5.1.0",
"react-currency-input-field": "^3.10.0",
"react-datepicker": "^7.6.0",
"react-dates": "^21.8.0",
"react-datetime": "^3.2.0",
@ -118,6 +120,7 @@
"react-multi-select-component": "^4.3.4",
"react-pdf": "^6.2.2",
"react-phone-input-2": "^2.15.1",
"react-phone-number-input": "^3.4.12",
"react-plotly.js": "^2.6.0",
"react-qr-reader": "^2.2.1",
"react-rnd": "^10.4.1",
@ -25856,6 +25859,25 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@tanstack/react-table": {
"version": "8.21.2",
"resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.2.tgz",
"integrity": "sha512-11tNlEDTdIhMJba2RBH+ecJ9l1zgS2kjmexDPAraulc8jeNA4xocSNeyzextT0XJyASil4XsCYlJmf5jEWAtYg==",
"dependencies": {
"@tanstack/table-core": "8.21.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/@tanstack/react-virtual": {
"version": "3.10.8",
"license": "MIT",
@ -25871,6 +25893,18 @@
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@tanstack/table-core": {
"version": "8.21.2",
"resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.2.tgz",
"integrity": "sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==",
"engines": {
"node": ">=12"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/virtual-core": {
"version": "3.10.8",
"license": "MIT",
@ -29820,6 +29854,11 @@
"node": ">=10"
}
},
"node_modules/country-flag-icons": {
"version": "1.5.18",
"resolved": "https://registry.npmjs.org/country-flag-icons/-/country-flag-icons-1.5.18.tgz",
"integrity": "sha512-z+Uzesi8u8IdkViqqbzzbkf3+a7WJpcET5B7sPwTg7GXqPYpVEgNlZ/FC3l8KO4mEf+mNkmzKLppKTN4PlCJEQ=="
},
"node_modules/country-regex": {
"version": "1.1.0",
"license": "MIT",
@ -34649,6 +34688,26 @@
"version": "0.2.4",
"license": "MIT"
},
"node_modules/input-format": {
"version": "0.3.14",
"resolved": "https://registry.npmjs.org/input-format/-/input-format-0.3.14.tgz",
"integrity": "sha512-gHMrgrbCgmT4uK5Um5eVDUohuV9lcs95ZUUN9Px2Y0VIfjTzT2wF8Q3Z4fwLFm7c5Z2OXCm53FHoovj6SlOKdg==",
"dependencies": {
"prop-types": "^15.8.1"
},
"peerDependencies": {
"react": ">=18.1.0",
"react-dom": ">=18.1.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/internal-slot": {
"version": "1.0.7",
"license": "MIT",
@ -36786,6 +36845,11 @@
"url": "https://github.com/sponsors/dmonad"
}
},
"node_modules/libphonenumber-js": {
"version": "1.12.6",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.6.tgz",
"integrity": "sha512-PJiS4ETaUfCOFLpmtKzAbqZQjCCKVu2OhTV4SVNNE7c2nu/dACvtCqj4L0i/KWNnIgRv7yrILvBj5Lonv5Ncxw=="
},
"node_modules/lie": {
"version": "3.1.1",
"license": "MIT",
@ -41248,6 +41312,14 @@
"framework-utils": "^1.1.0"
}
},
"node_modules/react-currency-input-field": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/react-currency-input-field/-/react-currency-input-field-3.10.0.tgz",
"integrity": "sha512-GRmZogHh1e1LrmgXg/fKHSuRLYUnj/c/AumfvfuDMA0UX1mDR6u2NR0fzDemRdq4tNHNLucJeJ2OKCr3ehqyDA==",
"peerDependencies": {
"react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/react-date-picker": {
"version": "10.6.0",
"license": "MIT",
@ -42035,6 +42107,22 @@
"react-dom": "^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0"
}
},
"node_modules/react-phone-number-input": {
"version": "3.4.12",
"resolved": "https://registry.npmjs.org/react-phone-number-input/-/react-phone-number-input-3.4.12.tgz",
"integrity": "sha512-Raob77KdtLGm49iC6nuOX9qy6Mg16idkgC7Y1mHmvG2WBYoauHpzxYNlfmFskQKeiztrJIwPhPzBhjFwjenNCA==",
"dependencies": {
"classnames": "^2.5.1",
"country-flag-icons": "^1.5.17",
"input-format": "^0.3.10",
"libphonenumber-js": "^1.11.20",
"prop-types": "^15.8.1"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/react-plotly.js": {
"version": "2.6.0",
"license": "MIT",

View file

@ -33,6 +33,7 @@
"@sentry/react": "^7.100.1",
"@sentry/webpack-plugin": "^2.14.0",
"@tabler/icons-react": "^2.4.0",
"@tanstack/react-table": "^8.20.5",
"@tanstack/react-virtual": "^3.10.8",
"@textea/json-viewer": "^3.3.2",
"@tooljet/plugins": "../plugins",
@ -91,6 +92,7 @@
"react-circular-progressbar": "^2.1.0",
"react-color": "^2.19.3",
"react-copy-to-clipboard": "^5.1.0",
"react-currency-input-field": "^3.10.0",
"react-datepicker": "^7.6.0",
"react-dates": "^21.8.0",
"react-datetime": "^3.2.0",
@ -113,6 +115,7 @@
"react-multi-select-component": "^4.3.4",
"react-pdf": "^6.2.2",
"react-phone-input-2": "^2.15.1",
"react-phone-number-input": "^3.4.12",
"react-plotly.js": "^2.6.0",
"react-qr-reader": "^2.2.1",
"react-rnd": "^10.4.1",
@ -260,4 +263,4 @@
"jsx"
]
}
}
}

View file

@ -4,7 +4,7 @@ import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom';
import { authorizeWorkspace, updateCurrentSession } from '@/_helpers/authorizeWorkspace';
import { authenticationService, tooljetService } from '@/_services';
import { withRouter } from '@/_hoc/withRouter';
import { PrivateRoute, AdminRoute, AppsRoute, SwitchWorkspaceRoute, OrganizationInviteRoute } from '@/Routes';
import { PrivateRoute, AdminRoute, AppsRoute, SwitchWorkspaceRoute } from '@/Routes';
import { HomePage } from '@/HomePage';
import { TooljetDatabase } from '@/TooljetDatabase';
import { Authorize } from '@/Oauth2';
@ -17,7 +17,6 @@ import { MarketplacePlugins } from '@/MarketplacePage/MarketplacePlugins';
import SwitchWorkspacePage from '@/HomePage/SwitchWorkspacePage';
import { lt } from 'semver';
import Toast from '@/_ui/Toast';
import { VerificationSuccessInfoScreen } from '@/SuccessInfoScreen';
import '@/_styles/theme.scss';
import AppLoader from '@/AppLoader';
export const BreadCrumbContext = React.createContext({});
@ -51,6 +50,7 @@ const AppWrapper = (props) => {
}),
shallow
);
return (
<Suspense fallback={null}>
<BrowserRouter basename={window.public_config?.SUB_PATH || '/'}>
@ -93,7 +93,6 @@ class AppComponent extends React.Component {
setFaviconAndTitle();
authorizeWorkspace();
this.fetchMetadata();
setFaviconAndTitle(null, null, this.props.location);
setInterval(this.fetchMetadata, 1000 * 60 * 60 * 1);
}
// check if its getting routed from editor
@ -196,14 +195,6 @@ class AppComponent extends React.Component {
{auth(this.props)}
<Route path="/sso/:origin/:configId" exact element={<Oauth {...this.props} />} />
<Route path="/sso/:origin" exact element={<Oauth {...this.props} />} />
<Route
path="/invitations/:token/workspaces/:organizationToken"
element={
<OrganizationInviteRoute {...this.props}>
<VerificationSuccessInfoScreen />
</OrganizationInviteRoute>
}
/>
<Route
exact
path="/:workspaceId/apps/:slug/:pageHandle?/*"

View file

@ -48,7 +48,6 @@ export const ConfigHandle = ({
}, shallow);
let height = visibility === false ? 10 : widgetHeight;
return (
<div
className={`config-handle ${customClassName}`}
@ -60,7 +59,7 @@ export const ConfigHandle = ({
: position === 'top'
? '-20px'
: `${height - (CONFIG_HANDLE_HEIGHT + BUFFER_HEIGHT)}px`,
visibility: _showHandle ? 'visible' : 'hidden',
visibility: _showHandle || visibility === false ? 'visible' : 'hidden',
left: '-1px',
}}
onClick={(e) => {

View file

@ -5,7 +5,12 @@ import WidgetWrapper from './WidgetWrapper';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import { useDrop } from 'react-dnd';
import { addChildrenWidgetsToParent, addNewWidgetToTheEditor, computeViewerBackgroundColor } from './appCanvasUtils';
import {
addChildrenWidgetsToParent,
addNewWidgetToTheEditor,
computeViewerBackgroundColor,
getSubContainerWidthAfterPadding,
} from './appCanvasUtils';
import {
CANVAS_WIDTHS,
NO_OF_GRIDS,
@ -20,6 +25,7 @@ import NoComponentCanvasContainer from './NoComponentCanvasContainer';
import { RIGHT_SIDE_BAR_TAB } from '../RightSideBar/rightSidebarConstants';
import { isPDFSupported } from '@/_helpers/appUtils';
import toast from 'react-hot-toast';
import useSortedComponents from '../_hooks/useSortedComponents';
//TODO: Revisit the logic of height (dropRef)
@ -103,12 +109,7 @@ export const Container = React.memo(
if (canvasWidth !== undefined) {
if (componentType === 'Listview' && listViewMode == 'grid') return canvasWidth / columns - 2;
if (id === 'canvas') return canvasWidth;
if (componentType === 'Container' || componentType === 'Form') {
return (
canvasWidth - (2 * CONTAINER_FORM_CANVAS_PADDING + 2 * SUBCONTAINER_CANVAS_BORDER_WIDTH + 2 * BOX_PADDING)
);
}
return canvasWidth - 2; // Need to update this 2 to correct value for other subcontainers
return getSubContainerWidthAfterPadding(canvasWidth, componentType, id);
}
return realCanvasRef?.current?.offsetWidth;
}
@ -146,6 +147,8 @@ export const Container = React.memo(
[setLastCanvasClickPosition]
);
const sortedComponents = useSortedComponents(components, currentLayout, id);
return (
<div
// {...(config.COMMENT_FEATURE_ENABLE && showComments && { onClick: handleAddThread })}
@ -197,7 +200,7 @@ export const Container = React.memo(
data-parent-type={id === 'canvas' ? 'canvas' : componentType}
style={{ height: !showEmptyContainer ? '100%' : 'auto' }} //TODO: remove hardcoded height & canvas condition
>
{components.map((id) => (
{sortedComponents.map((id) => (
<WidgetWrapper
id={id}
key={id}

View file

@ -23,8 +23,6 @@ import {
handleDeactivateTargets,
handleActivateNonDraggingComponents,
} from './gridUtils';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import { resolveWidgetFieldValue } from '@/_helpers/utils';
import { dragContextBuilder, getAdjustedDropPosition } from './helpers/dragEnd';
import useStore from '@/AppBuilder/_stores/store';
import './Grid.css';
@ -67,6 +65,9 @@ export default function Grid({ gridWidth, currentLayout }) {
const prevDragParentId = useRef(null);
const newDragParentId = useRef(null);
const [isGroupDragging, setIsGroupDragging] = useState(false);
const checkIfAnyWidgetVisibilityChanged = useStore((state) => state.checkIfAnyWidgetVisibilityChanged(), shallow);
const getExposedValueOfComponent = useStore((state) => state.getExposedValueOfComponent, shallow);
const setReorderContainerChildren = useStore((state) => state.setReorderContainerChildren, shallow);
useEffect(() => {
const selectedSet = new Set(selectedComponents);
@ -318,7 +319,7 @@ export default function Grid({ gridWidth, currentLayout }) {
useEffect(() => {
reloadGrid();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedComponents, openModalWidgetId, boxList, currentLayout]);
}, [selectedComponents, openModalWidgetId, boxList, currentLayout, checkIfAnyWidgetVisibilityChanged]);
const updateNewPosition = (events, parent = null) => {
const posWithParent = {
@ -330,6 +331,8 @@ export default function Grid({ gridWidth, currentLayout }) {
const isComponentVisible = (id) => {
const component = getResolvedComponent(id);
const componentExposedVisibility = getExposedValueOfComponent(id)?.isVisible;
if (componentExposedVisibility === false) return false;
let visibility;
if (isArray(component)) {
visibility = component?.[0]?.properties?.visibility ?? component?.[0]?.styles?.visibility ?? null;
@ -536,6 +539,7 @@ export default function Grid({ gridWidth, currentLayout }) {
})
);
}
setReorderContainerChildren(draggedOverElemId ?? 'canvas');
} catch (error) {
console.error('Error dragging group', error);
}
@ -631,13 +635,13 @@ export default function Grid({ gridWidth, currentLayout }) {
// When clicked on widget boundary/resizer, select the component
setSelectedComponents([e.target.id]);
}
showGridLines();
if (!isComponentVisible(e.target.id)) {
return false;
}
handleActivateNonDraggingComponents();
useGridStore.getState().actions.setResizingComponentId(e.target.id);
e.setMin([gridWidth, GRID_HEIGHT]);
showGridLines();
}}
onResizeEnd={(e) => {
try {
@ -646,10 +650,12 @@ export default function Grid({ gridWidth, currentLayout }) {
return id === e.target.id;
});
hideGridLines();
if (!e.lastEvent) {
return;
}
let _gridWidth = useGridStore.getState().subContainerWidths[currentWidget.component?.parent] || gridWidth;
let width = Math.round(e?.lastEvent?.width / _gridWidth) * _gridWidth;
const height = Math.round(e?.lastEvent?.height / GRID_HEIGHT) * GRID_HEIGHT;
const currentWidth = currentWidget.width * _gridWidth;
const diffWidth = e.lastEvent?.width - currentWidth;
const diffHeight = e.lastEvent?.height - currentWidget?.height;
@ -696,6 +702,7 @@ export default function Grid({ gridWidth, currentLayout }) {
resizeData.gw = _gridWidth;
}
handleResizeStop([resizeData]);
setReorderContainerChildren(currentWidget?.parent ?? 'canvas');
} catch (error) {
console.error('ResizeEnd error ->', error);
}
@ -775,6 +782,11 @@ export default function Grid({ gridWidth, currentLayout }) {
ev.target.style.transform = `translate(${posX}px, ${posY}px)`;
});
}
const groupParentId =
boxList.find(({ id }) => id === groupResizeDataRef.current[0].target.id)?.parent ?? 'canvas';
setReorderContainerChildren(groupParentId);
groupResizeDataRef.current = [];
reloadGrid();
} catch (error) {
@ -841,6 +853,8 @@ export default function Grid({ gridWidth, currentLayout }) {
useStore.getState().setDraggingComponentId(null);
isDraggingRef.current = false;
}
const oldParentId = boxList.find((b) => b.id === e.target.id)?.parent ?? 'canvas';
prevDragParentId.current = null;
newDragParentId.current = null;
setDragParentId(null);
@ -872,7 +886,6 @@ export default function Grid({ gridWidth, currentLayout }) {
left = dragged.left * sourcegridWidth;
top = dragged.top;
!isModalToCanvas ??
toast.error(`${dragged.widgetType} is not compatible as a child component of ${target.widgetType}`);
}
@ -880,6 +893,12 @@ export default function Grid({ gridWidth, currentLayout }) {
// Apply transform for smooth transition
e.target.style.transform = `translate(${left}px, ${top}px)`;
// Force reordering of conatiner if the parent has not changed
const newParentId = target.slotId === 'real-canvas' ? 'canvas' : target.slotId;
if (oldParentId === newParentId) {
setReorderContainerChildren(newParentId);
}
// Select the dragged component after drop
setTimeout(() => setSelectedComponents([dragged.id]));
} catch (error) {

View file

@ -7,10 +7,15 @@ import { renderTooltip } from '@/_helpers/appUtils';
import { useTranslation } from 'react-i18next';
import ErrorBoundary from '@/_ui/ErrorBoundary';
import { BOX_PADDING } from './appCanvasConstants';
const shouldAddBoxShadowAndVisibility = [
const SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY = [
'Table',
'TextInput',
'TextArea',
'PasswordInput',
'EmailInput',
'PhoneInput',
'CurrencyInput',
'NumberInput',
'Text',
'Checkbox',
@ -25,6 +30,8 @@ const shouldAddBoxShadowAndVisibility = [
'DaterangePicker',
'DatePickerV2',
'TimePicker',
'Divider',
'VerticalDivider',
'Link',
];
@ -87,6 +94,7 @@ const RenderWidget = ({
...{ widgetValue: value },
...{ validationObject: unResolvedValidation },
customResolveObjects: customResolvables,
componentType,
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[validateWidget, customResolvables, unResolvedValidation, resolvedValidation]
@ -141,23 +149,22 @@ const RenderWidget = ({
placement={inCanvas ? 'auto' : 'top'}
delay={{ show: 500, hide: 0 }}
trigger={
inCanvas && shouldAddBoxShadowAndVisibility.includes(component?.component)
inCanvas && SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY.includes(component?.component)
? !resolvedProperties?.tooltip?.toString().trim()
? null
: ['hover', 'focus']
: !resolvedGeneralProperties?.tooltip?.toString().trim()
? null
: ['hover', 'focus']
? null
: ['hover', 'focus']
}
overlay={(props) =>
renderTooltip({
props,
text: inCanvas
? `${
shouldAddBoxShadowAndVisibility.includes(component?.component)
? resolvedProperties?.tooltip
: resolvedGeneralProperties?.tooltip
}`
? `${SHOULD_ADD_BOX_SHADOW_AND_VISIBILITY.includes(component?.component)
? resolvedProperties?.tooltip
: resolvedGeneralProperties?.tooltip
}`
: `${t(`widget.${component?.name}.description`, component?.description)}`,
})
}

View file

@ -37,6 +37,8 @@ const WidgetWrapper = memo(
});
const visibility = useStore((state) => {
const component = state.getResolvedComponent(id, subContainerIndex);
const componentExposedVisibility = state.getExposedValueOfComponent(id)?.isVisible;
if (componentExposedVisibility === false) return false;
if (component?.properties?.visibility === false || component?.styles?.visibility === false) return false;
return true;
});
@ -52,7 +54,7 @@ const WidgetWrapper = memo(
height: visibility === false ? '10px' : `${height}px`,
transform: `translate(${layoutData.left * gridWidth}px, ${layoutData.top}px)`,
WebkitFontSmoothing: 'antialiased',
border: visibility === false ? `1px solid var(--border-default)` : 'none',
border: visibility === false && mode === 'edit' ? `1px solid var(--border-default)` : 'none',
};
if (!componentType) return null;

View file

@ -23,3 +23,7 @@ export const CONTAINER_FORM_CANVAS_PADDING = 7;
export const SUBCONTAINER_CANVAS_BORDER_WIDTH = 1;
export const BOX_PADDING = 2;
export const TAB_CANVAS_PADDING = 7.5;
export const MODAL_CANVAS_PADDING = 5;

View file

@ -6,7 +6,16 @@ import { toast } from 'react-hot-toast';
import _, { debounce } from 'lodash';
import { useGridStore } from '@/_stores/gridStore';
import { findHighestLevelofSelection } from './Grid/gridUtils';
import { CANVAS_WIDTHS, NO_OF_GRIDS, WIDGETS_WITH_DEFAULT_CHILDREN } from './appCanvasConstants';
import {
CANVAS_WIDTHS,
NO_OF_GRIDS,
WIDGETS_WITH_DEFAULT_CHILDREN,
CONTAINER_FORM_CANVAS_PADDING,
SUBCONTAINER_CANVAS_BORDER_WIDTH,
BOX_PADDING,
TAB_CANVAS_PADDING,
MODAL_CANVAS_PADDING,
} from './appCanvasConstants';
export function snapToGrid(canvasWidth, x, y) {
const gridX = canvasWidth / 43;
@ -40,8 +49,16 @@ export const addNewWidgetToTheEditor = (componentType, eventMonitorObject, curre
left = Math.round(left / gridWidth);
// Adjust widget width based on the dropping canvas width
const mainCanvasWidth = useGridStore.getState().subContainerWidths['canvas'];
const width = Math.round((defaultWidth * mainCanvasWidth) / gridWidth);
let width = Math.round((defaultWidth * mainCanvasWidth) / gridWidth);
// Ensure minimum width
width = Math.max(width, 1);
// Adjust position and width if exceeding grid bounds
if (width + left > NO_OF_GRIDS) {
left = Math.max(0, NO_OF_GRIDS - width);
width = Math.min(width, NO_OF_GRIDS);
}
if (currentLayout === 'mobile') {
componentData.definition.others.showOnDesktop.value = `{{false}}`;
componentData.definition.others.showOnMobile.value = `{{true}}`;
@ -504,7 +521,7 @@ export function pasteComponents(targetParentId, copiedComponentObj) {
targetParentId === key ||
(components?.[key]?.component.component === 'Tabs' &&
targetParentId?.split('-')?.slice(0, -1)?.join('-') === key) ||
(['Container', 'Form', 'Modal'].includes(components?.[key]?.component.component) &&
(['Container', 'Form', 'ModalV2'].includes(components?.[key]?.component.component) &&
['header', 'footer'].some((section) => targetParentId.includes(section)))
)
) {
@ -516,6 +533,7 @@ export function pasteComponents(targetParentId, copiedComponentObj) {
}
pastedComponents.forEach((component) => {
component = deepClone(component);
const newComponentId = isCut ? component.id : uuidv4();
const componentName = computeComponentName(component.component.component, {
...components,
@ -556,15 +574,28 @@ export function pasteComponents(targetParentId, copiedComponentObj) {
componentData.definition.others.showOnMobile.value = currentLayout === 'mobile' ? `{{true}}` : `{{false}}`;
// Adjust width if parent changed
let width = component.layouts.desktop.width;
let width = component.layouts[currentLayout].width;
if (targetParentId !== component.component?.parent) {
const containerWidth = useGridStore.getState().subContainerWidths[targetParentId || 'canvas'];
const oldContainerWidth = useGridStore.getState().subContainerWidths[component?.component?.parent || 'canvas'];
width = Math.round((width * oldContainerWidth) / containerWidth);
// Ensure minimum width
width = Math.max(width, 1);
// Adjust position and width if exceeding grid bounds
if (width + component.layouts[currentLayout].left > NO_OF_GRIDS) {
component.layouts[currentLayout].left = Math.max(0, NO_OF_GRIDS - width);
width = Math.min(width, NO_OF_GRIDS);
}
}
component.layouts[currentLayout].width = width;
component.layouts[currentLayout] = {
...component.layouts[currentLayout],
width,
};
const newComponent = {
component: {
...componentData,
@ -712,3 +743,25 @@ export const getSubContainerIdWithSlots = (parentId) => {
}
return cleanParentId;
};
export const getSubContainerWidthAfterPadding = (canvasWidth, componentType, componentId) => {
let padding = 2; //Need to update this 2 to correct value for other subcontainers
if (componentType === 'Container' || componentType === 'Form') {
padding = 2 * CONTAINER_FORM_CANVAS_PADDING + 2 * SUBCONTAINER_CANVAS_BORDER_WIDTH + 2 * BOX_PADDING;
}
if (componentType === 'Tabs') {
padding = 2 * TAB_CANVAS_PADDING + 2 * SUBCONTAINER_CANVAS_BORDER_WIDTH + 2 * BOX_PADDING;
}
if (componentType === 'ModalV2') {
const isModalHeader = componentId?.includes('header');
if (isModalHeader) {
const isModalHeaderCloseBtnEnabled = !useStore.getState().getResolvedComponent(componentId)?.properties
?.hideCloseButton;
console.log('isModalHeaderCloseBtnEnabled', isModalHeaderCloseBtnEnabled);
padding = 2 * (MODAL_CANVAS_PADDING + (isModalHeaderCloseBtnEnabled ? 56 : 0));
} else {
padding = 2 * MODAL_CANVAS_PADDING;
}
}
return canvasWidth - padding;
};

View file

@ -1,5 +1,5 @@
.active-target {
outline: 1px solid #4af;
outline: 1px solid #4af !important;
}
.main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover {

View file

@ -9,12 +9,16 @@ export const Color = ({
value,
onChange,
pickerStyle = {},
colorMap = {},
cyLabel,
asBoxShadowPopover = true,
meta,
outerWidth = '142px',
component,
styleDefinition,
componentType = 'color',
CustomOptionList = () => {},
SwatchesToggle = () => {},
}) => {
value = component == 'Button' ? computeColor(styleDefinition, value, meta) : value;
const [showPicker, setShowPicker] = useState(false);
@ -68,9 +72,11 @@ export const Color = ({
const ColorPicker = () => {
return (
<>
{showPicker && (
{SwatchesToggle()}
{showPicker && componentType === 'swatches' && CustomOptionList()}
{showPicker && componentType === 'color' && (
<div>
<div style={coverStyles} onClick={() => setShowPicker(false)} />
{/* <div style={coverStyles} onClick={() => setShowPicker(false)} /> */}
<div style={pickerStyle}>
<SketchPicker
onFocus={() => setShowPicker(true)}
@ -107,7 +113,9 @@ export const Color = ({
></div>
<div className="col tj-text-xsm p-0 color-slate12" data-cy={`${String(cyLabel)}-value`}>
{value}
{colorMap?.[value]
? 'Brand/' + colorMap?.[value]?.charAt(0).toUpperCase() + colorMap?.[value]?.slice(1)
: value}
</div>
</div>
);

View file

@ -18,9 +18,11 @@ import { NumberInput } from '../CodeBuilder/Elements/NumberInput';
import { Datepicker } from '../CodeBuilder/Elements/Datepicker';
import TableRowHeightInput from '../CodeBuilder/Elements/TableRowHeightInput';
import { TimePicker } from '../CodeBuilder/Elements/TimePicker';
import { ColorSwatches } from '@/modules/Appbuilder/components';
const AllElements = {
Color,
ColorSwatches,
Json,
Toggle,
Select,

View file

@ -337,6 +337,7 @@ export const FxParamTypeMapping = Object.freeze({
color: 'Color',
json: 'Json',
code: 'Code',
colorSwatches: 'ColorSwatches',
toggle: 'Toggle',
select: 'Select',
alignButtons: 'AlignButtons',

View file

@ -3,6 +3,7 @@ import { Button } from '@/components/ui/Button/Button';
import ExportAppModal from '@/HomePage/ExportAppModal';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import cx from 'classnames';
const AppExport = ({ darkMode }) => {
const { app } = useStore(
@ -28,22 +29,20 @@ const AppExport = ({ darkMode }) => {
darkMode={darkMode}
/>
)}
<div className="d-flex align-items-center global-popover-div-wrap mb-3">
<p className="tj-text-xsm color-slate12 w-full m-auto">Export app</p>
<div>
<Button
fill="var(--indigo9)"
leadingIcon="fileupload"
className="tw-w-[158px] !tw-text-[var(--indigo9)] !tw-bg-[var(--indigo3)] hover:!tw-text-[var(--indigo10)] hover:!tw-bg-[var(--indigo4)] active:!tw-text-[var(--indigo9)] active:!tw-bg-[var(--indigo5) focus-visible:!tw-text-[var(--indigo10)] focus-visible:!tw-bg-[var(--indigo3)]"
onClick={() => {
setIsExportingApp(true);
document.getElementById('maintenance-app-modal').click();
}}
data-cy="button-user-status-change"
>
Export this app
</Button>
</div>
<div className={cx({ 'dark-theme': darkMode })}>
<Button
fill="rgb(172, 178, 185)"
leadingIcon="fileupload"
variant="tertiary"
className={cx('app-export-btn')}
onClick={() => {
setIsExportingApp(true);
document.getElementById('maintenance-app-modal').click();
}}
data-cy="button-user-status-change"
>
Export app
</Button>
</div>
{/* {isExportingApp && <ExportAppModal app={app} setIsExportingApp={toggleExportingApp} darkMode={darkMode} />} */}
</>

View file

@ -34,7 +34,7 @@ const AppModeToggle = ({ darkMode }) => {
exposedTheme = darkMode ? 'dark' : 'light';
}
onAppModeChange({ appMode: value });
globalSettingsChanged({ theme: { name: exposedTheme } });
// globalSettingsChanged({ theme: { name: exposedTheme } });
setResolvedGlobals('theme', { name: exposedTheme });
}}
defaultValue={appMode}

View file

@ -13,20 +13,11 @@ import { Confirm } from '@/Editor/Viewer/Confirm';
import { shallow } from 'zustand/shallow';
const CanvasSettings = ({ darkMode }) => {
const {
globalSettings,
globalSettingsChanged,
isMaintenanceOn,
toggleAppMaintenance,
resolveOthers,
getCanvasBackgroundColor,
} = useStore(
const { globalSettings, globalSettingsChanged, resolveOthers, getCanvasBackgroundColor } = useStore(
(state) => ({
globalSettings: state.globalSettings,
updateGlobalSettings: state.updateGlobalSettings,
isMaintenanceOn: state.app.isMaintenanceOn,
globalSettingsChanged: state.globalSettingsChanged,
toggleAppMaintenance: state.toggleAppMaintenance,
resolveOthers: state.resolveOthers,
getCanvasBackgroundColor: state.getCanvasBackgroundColor,
}),
@ -73,43 +64,10 @@ const CanvasSettings = ({ darkMode }) => {
boxShadow: showPicker && '0px 0px 0px 1px #C6D4F9',
};
const { hideHeader, canvasMaxWidth, canvasMaxWidthType, backgroundFxQuery } = globalSettings ?? {};
const { canvasMaxWidth, canvasMaxWidthType, backgroundFxQuery } = globalSettings ?? {};
return (
<>
<Confirm
show={showConfirmation}
message={
isMaintenanceOn
? 'Users will now be able to launch the released version of this app, do you wish to continue?'
: 'Users will not be able to launch the app until maintenance mode is turned off, do you wish to continue?'
}
onConfirm={() => toggleAppMaintenance()}
onCancel={() => setConfirmationShow(false)}
darkMode={darkMode}
/>
<div className="tw-flex tw-mb-3">
<SwitchComponent
align="right"
label="Hide header for launched apps"
size="default"
checked={hideHeader}
onCheckedChange={(e) => globalSettingsChanged({ hideHeader: e })}
data-cy={`toggle-hide-header-for-launched-apps`}
className="tw-w-full"
/>
</div>
<div className="tw-flex tw-mb-3">
<SwitchComponent
align="right"
label="Maintenance mode"
size="default"
checked={isMaintenanceOn}
onCheckedChange={() => setConfirmationShow(true)}
data-cy={`toggle-maintenance-mode`}
className="tw-w-full"
/>
</div>
<div className="d-flex mb-3">
<span data-cy={`label-max-canvas-width`} className="w-full m-auto">
{t('leftSidebar.Settings.maxWidthOfCanvas', 'Max width of canvas')}

View file

@ -0,0 +1,31 @@
import React from 'react';
import SwitchComponent from '@/components/ui/Switch/Index';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
const HideHeaderToggle = () => {
const { globalSettings, globalSettingsChanged } = useStore(
(state) => ({
globalSettings: state.globalSettings,
globalSettingsChanged: state.globalSettingsChanged,
}),
shallow
);
const { hideHeader } = globalSettings || {};
return (
<div className="tw-flex tw-mb-3">
<SwitchComponent
align="right"
label="Hide header for launched apps"
size="default"
checked={hideHeader}
onCheckedChange={(e) => globalSettingsChanged({ hideHeader: e })}
data-cy={`toggle-hide-header-for-launched-apps`}
className="tw-w-full"
/>
</div>
);
};
export default HideHeaderToggle;

View file

@ -0,0 +1,45 @@
import React, { useState } from 'react';
import useStore from '@/AppBuilder/_stores/store';
import SwitchComponent from '@/components/ui/Switch/Index';
import { shallow } from 'zustand/shallow';
import { Confirm } from '@/Editor/Viewer/Confirm';
const MaintenanceMode = ({ darkMode }) => {
const [showConfirmation, setConfirmationShow] = useState(false);
const { isMaintenanceOn, toggleAppMaintenance } = useStore(
(state) => ({
isMaintenanceOn: state.app.isMaintenanceOn,
toggleAppMaintenance: state.toggleAppMaintenance,
}),
shallow
);
return (
<>
<Confirm
show={showConfirmation}
message={
isMaintenanceOn
? 'Users will now be able to launch the released version of this app, do you wish to continue?'
: 'Users will not be able to launch the app until maintenance mode is turned off, do you wish to continue?'
}
onConfirm={() => toggleAppMaintenance()}
onCancel={() => setConfirmationShow(false)}
darkMode={darkMode}
/>
<div className="tw-flex tw-mb-3">
<SwitchComponent
align="right"
label="Maintenance mode"
size="default"
checked={isMaintenanceOn}
onCheckedChange={() => setConfirmationShow(true)}
data-cy={`toggle-maintenance-mode`}
className="tw-w-full"
/>
</div>
</>
);
};
export default MaintenanceMode;

View file

@ -132,7 +132,11 @@ const SlugInput = () => {
</div>
)}
</div>
<label className="label label-success label-updated" data-cy="app-link-success-label">
<label
className="label label-success label-updated"
data-cy="app-link-success-label"
style={{ padding: '0px' }}
>
{isSlugUpdated ? `Link updated successfully!` : ''}
</label>
</div>

View file

@ -7,6 +7,9 @@ import AppExport from './AppExport';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
import AppModeToggle from './AppModeToggle';
import { ThemeSelect } from '@/modules/Appbuilder/components';
import MaintenanceMode from './MaintenanceMode';
import HideHeaderToggle from './HideHeaderToggle';
const GlobalSettings = ({ darkMode }) => {
const shouldFreeze = useStore((state) => state.getShouldFreeze());
@ -16,16 +19,27 @@ const GlobalSettings = ({ darkMode }) => {
<div>
<div bsPrefix="global-settings-popover" className="global-settings-panel">
<HeaderSection>
<HeaderSection.PanelHeader title="Global settings" />
<HeaderSection.PanelHeader title="Global settings">
<div className="d-flex w-100 justify-content-end">
<AppExport darkMode={darkMode} />
</div>
</HeaderSection.PanelHeader>
</HeaderSection>
<div className="card-body">
<div className="card-body" style={{ paddingBottom: '0px' }}>
<SlugInput />
</div>
<div style={{ padding: '12px 16px' }} className={cx({ disabled: shouldFreeze })}>
<MaintenanceMode darkMode={darkMode} />
<HideHeaderToggle darkMode={darkMode} />
</div>
<div className={cx({ 'dark-theme': darkMode })}>
<span className="canvas-styles-header">Canvas Styles</span>
</div>
<div style={{ padding: '12px 16px' }} className={cx({ disabled: shouldFreeze })}>
<div className="tj-text-xsm color-slate12 ">
<CanvasSettings darkMode={darkMode} />
<AppModeToggle darkMode={darkMode} />
<AppExport darkMode={darkMode} />
<ThemeSelect darkMode={darkMode} />
</div>
</div>
</div>

View file

@ -4,6 +4,7 @@ import cx from 'classnames';
import PlusRectangle from '@/_ui/Icon/solidIcons/PlusRectangle';
import Remove from '@/_ui/Icon/bulkIcons/Remove';
import ParameterForm from './ParameterForm';
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
const ParameterDetails = ({ darkMode, onSubmit, isEdit, name, defaultValue, onRemove, otherParams }) => {
const [showModal, setShowModal] = useState(false);
@ -47,6 +48,17 @@ const ParameterDetails = ({ darkMode, onSubmit, isEdit, name, defaultValue, onRe
}
};
usePopoverObserver(
document.getElementsByClassName('query-details')[0],
isEdit
? document.getElementById(`query-param-${String(name).toLowerCase()}`)
: document.getElementById('runjs-param-add-btn'),
document.getElementById('parameter-form-popover'),
showModal,
() => setShowModal(true),
closeMenu
);
return (
<OverlayTrigger
trigger="click"

View file

@ -1,4 +1,4 @@
import React, { useState, forwardRef, useRef, useEffect } from 'react';
import React, { useState, forwardRef, useRef, useEffect, useCallback } from 'react';
import RenameIcon from '../Icons/RenameIcon';
import Eye1 from '@/_ui/Icon/solidIcons/Eye1';
import Play from '@/_ui/Icon/solidIcons/Play';
@ -13,6 +13,7 @@ import { decodeEntities } from '@/_helpers/utils';
import { canDeleteDataSource, canReadDataSource, canUpdateDataSource } from '@/_helpers';
import useStore from '@/AppBuilder/_stores/store';
import { useModuleId } from '@/AppBuilder/_contexts/ModuleContext';
import { debounce } from 'lodash';
export const QueryManagerHeader = forwardRef(({ darkMode, setActiveTab, activeTab }, ref) => {
const moduleId = useModuleId();
@ -166,16 +167,17 @@ const NameInput = ({ onInput, value, darkMode, isDiabled, selectedQuery }) => {
}
}, [isFocused]);
const debouncedHandleInput = useCallback(
debounce((newName) => {
onInput(newName);
}, 300),
[onInput]
);
const handleChange = (event) => {
const sanitizedValue = event.target.value.replace(/[ \t&]/g, '');
setName(sanitizedValue);
};
const handleInput = (newName) => {
const result = onInput(newName);
if (!result) {
setName(value);
}
debouncedHandleInput(sanitizedValue);
};
return (
@ -200,12 +202,12 @@ const NameInput = ({ onInput, value, darkMode, isDiabled, selectedQuery }) => {
event.persist();
if (event.keyCode === 13) {
setIsFocused(false);
handleInput(event.target.value);
debouncedHandleInput(event.target.value);
}
}}
onBlur={({ target }) => {
setIsFocused(false);
handleInput(target.value);
debouncedHandleInput(target.value);
}}
/>
) : (

View file

@ -219,4 +219,11 @@
.react-datepicker__navigation{
overflow: visible !important;
height: inherit !important;
}
.tjdb-td-wrapper{
.react-datepicker-time__input{
input{
line-height: normal !important;
}
}
}

View file

@ -9,6 +9,7 @@ import Remove from '@/_ui/Icon/bulkIcons/Remove';
import { v4 as uuidv4 } from 'uuid';
import { isEmpty } from 'lodash';
import { ToolTip } from '@/_components/ToolTip';
import usePopoverObserver from '@/AppBuilder/_hooks/usePopoverObserver';
const DropDownSelect = ({
darkMode,
@ -130,6 +131,15 @@ const DropDownSelect = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selected]);
usePopoverObserver(
document.getElementsByClassName('query-details')[0],
document.getElementById(popoverBtnId.current),
document.getElementById(popoverId.current),
showMenu,
() => setShowMenu(true),
() => setShowMenu(false)
);
function checkElementPosition() {
if (isForeignKeyInEditCell) {
return 'bottom-start';

View file

@ -37,7 +37,7 @@ const QueryManager = ({ mode, darkMode }) => {
useEffect(() => {
if (selectedQuery) {
const selectedDS = [...dataSources, ...globalDataSources, ...(sampleDataSource?.length ? sampleDataSource : [])]
const selectedDS = [...dataSources, ...globalDataSources, !!sampleDataSource && sampleDataSource]
.filter(Boolean)
.find((datasource) => datasource.id === selectedQuery?.data_source_id);
//TODO: currently type is not taken into account. May create issues in importing REST apis. to be revamped when import app is revamped

View file

@ -118,7 +118,10 @@ export const ComponentsManagerTab = ({ darkMode }) => {
'TextInput',
'NumberInput',
'PasswordInput',
'Textarea',
'TextArea',
'EmailInput',
'PhoneInput',
'CurrencyInput',
'ToggleSwitchV2',
'DropdownV2',
'MultiselectV2',

View file

@ -42,12 +42,18 @@ const CustomDragLayer = ({ size }) => {
const canvasBounds = item?.canvasRef?.getBoundingClientRect();
const height = size.height;
const width = (canvasWidth * size.width) / NO_OF_GRIDS;
const mainCanvasWidth = document.getElementById('real-canvas')?.offsetWidth || 0;
let width = (mainCanvasWidth * size.width) / NO_OF_GRIDS;
// Calculate position relative to the current canvas (parent or child)
const left = currentOffset.x - (canvasBounds?.left || 0);
const top = currentOffset.y - (canvasBounds?.top || 0);
// Adjust position and width if exceeding grid bounds
if (width >= canvasWidth) {
width = canvasWidth;
}
const [x, y] = snapToGrid(canvasWidth, left, top);
return (
<div

View file

@ -1 +1,9 @@
export const LEGACY_ITEMS = ['ToggleSwitch', 'DropDown', 'Multiselect', 'RadioButton', 'Datepicker', 'Modal'];
export const LEGACY_ITEMS = [
'ToggleSwitch',
'DropDown',
'Multiselect',
'RadioButton',
'Datepicker',
'Modal',
'TextArea',
];

View file

@ -0,0 +1,132 @@
import React, { useMemo, useState } from 'react';
import Accordion from '@/_ui/Accordion';
import { baseComponentProperties } from '../DefaultComponent';
import Select from '@/_ui/Select';
import useStore from '@/AppBuilder/_stores/store';
import flags from 'react-phone-number-input/flags';
import FxButton from '@/AppBuilder/CodeBuilder/Elements/FxButton';
import CodeHinter from '@/AppBuilder/CodeEditor';
import cx from 'classnames';
import { CurrencyMap } from '@/AppBuilder/Widgets/PhoneCurrency/constants';
export const CurrencyInput = ({ componentMeta, darkMode, ...restProps }) => {
const {
layoutPropertyChanged,
component,
paramUpdated,
dataQueries,
currentState,
eventsChanged,
apps,
allComponents,
} = restProps;
const properties = Object.keys(componentMeta.properties);
const events = Object.keys(componentMeta.events);
const validations = Object.keys(componentMeta.validation || {});
const resolvedProperties = useStore((state) => state.getResolvedComponent(component.id)?.properties);
const defaultCountry = resolvedProperties?.defaultCountry || 'US';
const isDefaultCountryFxOn = componentMeta?.definition?.properties?.dateFormat?.fxActive || false;
const options = useMemo(() => {
return Object.keys(CurrencyMap).map((country) => ({
label: `${CurrencyMap[country].prefix} (${CurrencyMap[country].currency})`,
value: country,
}));
}, []);
const renderCustomOption = ({ label, value: optionValue }) => {
const optionStyle = {
display: 'flex',
alignItems: 'center',
justifyContent: 'start',
height: '18px',
gap: '6px',
cursor: 'pointer',
fontFamily: 'IBM Plex Sans',
fontSize: '12px',
lineHeight: '18px',
fontWeight: '400',
color: darkMode ? '#fff' : '#1B1F24',
};
const FlagIcon = flags[optionValue];
return (
<div style={optionStyle} className={`selectedOption ${optionValue !== 'none' && 'custom-phone-input-options'}`}>
<div>{FlagIcon ? <FlagIcon style={{ width: '22px', height: '16px' }} /> : null}</div>
{label}
</div>
);
};
const getCountryDropdown = () => {
return (
<div className="mb-2">
<div className="d-flex justify-content-between mb-1">
<label className="form-label"> Default Currency</label>
<div
className={cx({
'hide-fx': !isDefaultCountryFxOn,
})}
>
<FxButton
active={isDefaultCountryFxOn}
onPress={() => {
paramUpdated({ name: 'dateFormat' }, 'fxActive', !isDefaultCountryFxOn, 'properties');
}}
/>
</div>
</div>
{isDefaultCountryFxOn ? (
<CodeHinter
initialValue={defaultCountry}
theme={darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
onChange={(value) => paramUpdated({ name: 'defaultCountry' }, 'value', value, 'properties')}
/>
) : (
<Select
width="100%"
options={options}
value={defaultCountry}
customOption={renderCustomOption}
onChange={(value) => {
paramUpdated({ name: 'defaultCountry' }, 'value', value, 'properties');
}}
/>
)}
</div>
);
};
const filteredProperties = properties.filter(
(property) => componentMeta.properties[property].section !== 'additionalActions'
);
const additionalActions = properties.filter(
(property) => componentMeta.properties[property].section === 'additionalActions'
);
const accordionItems = baseComponentProperties(
filteredProperties,
events,
component,
componentMeta,
layoutPropertyChanged,
paramUpdated,
dataQueries,
currentState,
eventsChanged,
apps,
allComponents,
validations,
darkMode,
null,
additionalActions
);
accordionItems[0].children.splice(4, 0, getCountryDropdown());
return <Accordion items={accordionItems} />;
};

View file

@ -14,8 +14,12 @@ const SHOW_ADDITIONAL_ACTIONS = [
'Text',
'Container',
'TextInput',
'TextArea',
'NumberInput',
'PasswordInput',
'EmailInput',
'PhoneInput',
'CurrencyInput',
'ToggleSwitchV2',
'Checkbox',
'DropdownV2',
@ -23,6 +27,8 @@ const SHOW_ADDITIONAL_ACTIONS = [
'Button',
'RichTextEditor',
'Image',
'Divider',
'VerticalDivider',
'ModalV2',
'Link',
];
@ -33,9 +39,12 @@ const PROPERTIES_VS_ACCORDION_TITLE = {
NumberInput: 'Data',
ToggleSwitchV2: 'Data',
Checkbox: 'Data',
TextArea: 'Data',
Button: 'Data',
Image: 'Data',
Container: 'Data',
Divider: 'Data',
VerticalDivider: 'Data',
ModalV2: 'Data',
Link: 'Data',
};
@ -122,6 +131,10 @@ export const baseComponentProperties = (
'Modal',
'TextInput',
'PasswordInput',
'TextArea',
'EmailInput',
'PhoneInput',
'CurrencyInput',
'NumberInput',
'Text',
'Table',
@ -131,6 +144,8 @@ export const baseComponentProperties = (
'DropdownV2',
'MultiselectV2',
'Image',
'Divider',
'VerticalDivider',
'Link',
],
Layout: [],
@ -271,7 +286,6 @@ export const baseComponentProperties = (
</>
),
});
return items.filter(
(item) => !(item.title in accordionFilters && accordionFilters[item.title].includes(componentMeta.component))
);

View file

@ -0,0 +1,135 @@
import React, { useMemo, useState } from 'react';
import Accordion from '@/_ui/Accordion';
import { baseComponentProperties } from '../DefaultComponent';
import Select from '@/_ui/Select';
import useStore from '@/AppBuilder/_stores/store';
import { getCountries } from 'react-phone-number-input/input';
import en from 'react-phone-number-input/locale/en';
import flags from 'react-phone-number-input/flags';
import FxButton from '@/AppBuilder/CodeBuilder/Elements/FxButton';
import CodeHinter from '@/AppBuilder/CodeEditor';
import cx from 'classnames';
export const PhoneInput = ({ componentMeta, darkMode, ...restProps }) => {
const {
layoutPropertyChanged,
component,
paramUpdated,
dataQueries,
currentState,
eventsChanged,
apps,
allComponents,
} = restProps;
const properties = Object.keys(componentMeta.properties);
const events = Object.keys(componentMeta.events);
const validations = Object.keys(componentMeta.validation || {});
const resolvedProperties = useStore((state) => state.getResolvedComponent(component.id)?.properties);
const defaultCountry = resolvedProperties?.defaultCountry || 'US';
const isDefaultCountryFxOn = componentMeta?.definition?.properties?.dateFormat?.fxActive || false;
const options = useMemo(
() =>
getCountries().map((country) => ({
label: `${en[country]}`,
value: country,
})),
[]
);
const renderCustomOption = ({ label, value: optionValue }) => {
const optionStyle = {
display: 'flex',
alignItems: 'center',
justifyContent: 'start',
height: '18px',
gap: '6px',
cursor: 'pointer',
fontFamily: 'IBM Plex Sans',
fontSize: '12px',
lineHeight: '18px',
fontWeight: '400',
color: darkMode ? '#fff' : '#1B1F24',
};
const FlagIcon = flags[optionValue];
return (
<div style={optionStyle} className={`selectedOption ${optionValue !== 'none' && 'custom-phone-input-options'}`}>
<div>{FlagIcon ? <FlagIcon style={{ width: '22px', height: '16px' }} /> : null}</div>
{label}
</div>
);
};
const getCountryDropdown = () => {
return (
<div className="mb-2">
<div className="d-flex justify-content-between mb-1">
<label className="form-label"> Default Country</label>
<div
className={cx({
'hide-fx': !isDefaultCountryFxOn,
})}
>
<FxButton
active={isDefaultCountryFxOn}
onPress={() => {
paramUpdated({ name: 'dateFormat' }, 'fxActive', !isDefaultCountryFxOn, 'properties');
}}
/>
</div>
</div>
{isDefaultCountryFxOn ? (
<CodeHinter
initialValue={defaultCountry}
theme={darkMode ? 'monokai' : 'default'}
mode="javascript"
lineNumbers={false}
onChange={(value) => paramUpdated({ name: 'defaultCountry' }, 'value', value, 'properties')}
/>
) : (
<Select
width="100%"
options={options}
value={defaultCountry}
customOption={renderCustomOption}
onChange={(value) => {
paramUpdated({ name: 'defaultCountry' }, 'value', value, 'properties');
}}
/>
)}
</div>
);
};
const filteredProperties = properties.filter(
(property) => componentMeta.properties[property].section !== 'additionalActions'
);
const additionalActions = properties.filter(
(property) => componentMeta.properties[property].section === 'additionalActions'
);
const accordionItems = baseComponentProperties(
filteredProperties,
events,
component,
componentMeta,
layoutPropertyChanged,
paramUpdated,
dataQueries,
currentState,
eventsChanged,
apps,
allComponents,
validations,
darkMode,
null,
additionalActions
);
accordionItems[0].children.splice(3, 0, getCountryDropdown());
return <Accordion items={accordionItems} />;
};

View file

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import Accordion from '@/_ui/Accordion';
import { EventManager } from '../EventManager';
import { renderElement } from '../Utils';
@ -14,6 +14,7 @@ import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import SortableList from '@/_components/SortableList';
import Trash from '@/_ui/Icon/solidIcons/Trash';
import { shallow } from 'zustand/shallow';
import { sortArray } from '@/Editor/Components/DropdownV2/utils';
export function Select({ componentMeta, darkMode, ...restProps }) {
const {
@ -27,10 +28,13 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
allComponents,
pages,
} = restProps;
const isInitialRender = useRef(true);
const getResolvedValue = useStore((state) => state.getResolvedValue, shallow);
const isMultiSelect = component?.component?.component === 'MultiselectV2';
const isDynamicOptionsEnabled = getResolvedValue(component?.component?.definition?.properties?.advanced?.value);
const isSortingEnabled = componentMeta?.properties['sort'] ?? false;
const sort = component?.component?.definition?.properties?.sort?.value;
const constructOptions = () => {
let optionsValue = component?.component?.definition?.properties?.options?.value;
@ -89,6 +93,15 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
paramUpdated({ name: 'options' }, 'value', options, 'properties', false, props);
};
const updateSortParam = (value) => {
paramUpdated({ name: 'sort' }, 'value', value, 'properties');
};
const updateOptions = (options) => {
setOptions(options);
updateAllOptionsParams(options);
};
const generateNewOptions = () => {
let found = false;
let label = '';
@ -114,8 +127,8 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
const handleAddOption = () => {
let _option = generateNewOptions();
const _items = [...options, _option];
setOptions(_items);
updateAllOptionsParams(_items);
const sortedItems = sortArray(_items, sort);
updateOptions(sortedItems);
};
const handleDeleteOption = (index) => {
@ -134,8 +147,7 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
}
return option;
});
setOptions(_options);
updateAllOptionsParams(_options);
updateOptions(_options);
};
const handleValueChange = (value, index) => {
@ -148,16 +160,17 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
}
return option;
});
setOptions(_options);
updateAllOptionsParams(_options);
updateOptions(_options);
};
const reorderOptions = async (startIndex, endIndex) => {
const result = [...options];
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
setOptions(result);
updateAllOptionsParams(result);
updateOptions(result);
if (isSortingEnabled && sort !== 'none') {
updateSortParam('none');
}
};
const onDragEnd = ({ source, destination }) => {
@ -200,9 +213,8 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
};
}
});
setOptions(_options);
updateOptions(_options);
setMarkedAsDefault(_value);
updateAllOptionsParams(_options);
}
};
@ -219,8 +231,7 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
}
return option;
});
setOptions(_options);
updateAllOptionsParams(_options);
updateOptions(_options);
};
const handleDisableChange = (value, index) => {
@ -236,8 +247,7 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
}
return option;
});
setOptions(_options);
updateAllOptionsParams(_options);
updateOptions(_options);
};
const handleOnFxPress = (active, index, key) => {
@ -253,12 +263,20 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
}
return option;
});
setOptions(_options);
updateAllOptionsParams(_options);
updateOptions(_options);
};
useEffect(() => {
setOptions(constructOptions());
if (!isInitialRender.current && isSortingEnabled) {
const sortedOptions = sortArray([...options], sort);
updateOptions(sortedOptions);
}
}, [sort]);
useEffect(() => {
const sortedOptions = sortArray(constructOptions(), sort);
updateOptions(sortedOptions);
isInitialRender.current = false;
}, [isMultiSelect, component?.id]);
const _renderOverlay = (item, index) => {
@ -385,6 +403,12 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
trigger="click"
placement="left"
rootClose
onExited={() => {
if (isSortingEnabled && sort !== 'none') {
const sortedOptions = sortArray([...options], sort);
updateOptions(sortedOptions);
}
}}
overlay={_renderOverlay(item, index)}
onToggle={(isOpen) => {
if (!isOpen) {
@ -515,6 +539,17 @@ export function Select({ componentMeta, darkMode, ...restProps }) {
currentState,
allComponents
)}
{isSortingEnabled &&
renderElement(
component,
componentMeta,
paramUpdated,
dataQueries,
'sort',
'properties',
currentState,
allComponents
)}
</>
),
});

View file

@ -53,6 +53,7 @@ export const PropertiesTabElements = ({
{ label: 'Image', value: 'image' },
{ label: 'Link', value: 'link' },
{ label: 'JSON', value: 'json' },
{ label: 'Markdown', value: 'markdown' },
// Following column types are deprecated
{ label: 'Default', value: 'default' },
{ label: 'Dropdown', value: 'dropdown' },

View file

@ -128,6 +128,7 @@ export const StylesTabElements = ({
undefined,
'number',
'json',
'markdown',
'boolean',
'select',
'text',

View file

@ -631,6 +631,8 @@ class TableComponent extends React.Component {
return 'Multiselect';
case 'json':
return 'JSON';
case 'markdown':
return 'Markdown';
default:
capitalize(text ?? '');
}

View file

@ -30,6 +30,8 @@ import { appService } from '@/_services';
import { deepClone } from '@/_helpers/utilities/utils.helpers';
import useStore from '@/AppBuilder/_stores/store';
import { useEventActions, useEvents } from '@/AppBuilder/_stores/slices/eventsSlice';
import ToggleGroup from '@/ToolJetUI/SwitchGroup/ToggleGroup';
import ToggleGroupItem from '@/ToolJetUI/SwitchGroup/ToggleGroupItem';
export const EventManager = ({
sourceId,
@ -503,7 +505,7 @@ export const EventManager = ({
)}
{event.actionId === 'open-webpage' && (
<div className="p-1">
<div>
<label className="form-label mt-1">{t('editor.inspector.eventManager.url', 'URL')}</label>
<CodeHinter
type="basic"
@ -512,6 +514,17 @@ export const EventManager = ({
usePortalEditor={false}
component={component}
/>
<div className="d-flex align-items-center justify-content-between mt-3">
<label className="form-label mt-1">Open in</label>
<ToggleGroup
onValueChange={(_value) => handlerChanged(index, 'windowTarget', _value)}
defaultValue={event?.windowTarget || 'newTab'}
style={{ width: '74%' }}
>
<ToggleGroupItem value="newTab">New tab</ToggleGroupItem>
<ToggleGroupItem value="currentTab">Current tab</ToggleGroupItem>
</ToggleGroup>
</div>
</div>
)}

View file

@ -8,6 +8,8 @@ import { validateQueryName, convertToKebabCase, resolveReferences } from '@/_hel
import { useHotkeys } from 'react-hotkeys-hook';
import { DefaultComponent } from './Components/DefaultComponent';
import { FilePicker } from './Components/FilePicker';
import { PhoneInput } from './Components/PhoneInput/PhoneInput.jsx';
import { CurrencyInput } from './Components/CurrencyInput/CurrencyInput.jsx';
import { Modal } from './Components/Modal';
import { ModalV2 } from './Components/ModalV2';
import { CustomComponent } from './Components/CustomComponent';
@ -37,6 +39,7 @@ import { Select } from './Components/Select';
import { Steps } from './Components/Steps.jsx';
import { deepClone } from '@/_helpers/utilities/utils.helpers';
import useStore from '@/AppBuilder/_stores/store';
// import { componentTypes } from '@/Editor/WidgetManager/components';
import { componentTypes } from '@/AppBuilder/WidgetManager/componentTypes';
import { copyComponents } from '@/AppBuilder/AppCanvas/appCanvasUtils.js';
import DatetimePickerV2 from './Components/DatetimePickerV2.jsx';
@ -67,7 +70,11 @@ const INSPECTOR_HEADER_OPTIONS = [
const NEW_REVAMPED_COMPONENTS = [
'Text',
'TextInput',
'TextArea',
'PasswordInput',
'EmailInput',
'PhoneInput',
'CurrencyInput',
'NumberInput',
'Table',
'ToggleSwitchV2',
@ -80,6 +87,8 @@ const NEW_REVAMPED_COMPONENTS = [
'Icon',
'Image',
'Container',
'Divider',
'VerticalDivider',
'ModalV2',
'Link',
'Steps',
@ -532,8 +541,8 @@ export const Inspector = ({ componentDefinitionChanged, darkMode, pages, selecte
componentMeta.displayName === 'Toggle Switch (Legacy)'
? 'Toggle (Legacy)'
: componentMeta.displayName === 'Toggle Switch'
? 'Toggle Switch'
: componentMeta.component,
? 'Toggle Switch'
: componentMeta.component,
})}
</small>
</span>
@ -735,6 +744,10 @@ const GetAccordion = React.memo(
return <DatetimePickerV2 {...restProps} componentName={componentName} />;
case 'Steps':
return <Steps {...restProps} />;
case 'PhoneInput':
return <PhoneInput {...restProps} />;
case 'CurrencyInput':
return <CurrencyInput {...restProps} componentName={componentName} />;
default: {
return <DefaultComponent {...restProps} />;

View file

@ -43,6 +43,9 @@ export function renderCustomStyles(
componentConfig.component == 'TextInput' ||
componentConfig.component == 'NumberInput' ||
componentConfig.component == 'PasswordInput' ||
componentConfig.component == 'EmailInput' ||
componentConfig.component == 'PhoneInput' ||
componentConfig.component == 'CurrencyInput' ||
componentConfig.component == 'ToggleSwitchV2' ||
componentConfig.component == 'Checkbox' ||
componentConfig.component == 'Table' ||

View file

@ -13,6 +13,7 @@ const NEW_WIDGETS = [
'DatePickerV2',
'TimePicker',
'ModalV2',
'TextArea',
];
export const WidgetBox = ({ component, darkMode }) => {

View file

@ -134,7 +134,7 @@ const MobileNavigationMenu = ({ pages, switchPage, currentPageId, darkMode, chan
version: selectedVersionName,
env: selectedEnvironmentName,
};
switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), true);
switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams));
};
var styles = {
bmBurgerButton: {

View file

@ -29,7 +29,9 @@ const PreviewSettings = ({ isMobileLayout, showHeader, darkMode }) => {
const renderOverlay = () => (
<div className={classNames({ 'dark-theme theme-dark': darkMode })} style={{ borderRadius: '6px' }}>
<div className="preview-settings-overlay" style={{ borderColor: darkMode ? '#2B3036' : '#E4E7EB' }}>
<span className="preview-settings-text">Preview settings</span>
<span className="preview-settings-text" data-cy="preview-settings-text">
Preview settings
</span>
{editingVersion && (
<>
<AppVersionsManager darkMode={darkMode} />
@ -59,10 +61,14 @@ const PreviewSettings = ({ isMobileLayout, showHeader, darkMode }) => {
className="released-version-no-header-mbl-preview"
style={{ backgroundColor: 'var(--slate5)', top: '7px', left: showHeader ? '61%' : '41%' }}
>
<span className="preview-chip" style={{ color: 'var(--slate12)' }}>
<span className="preview-chip" style={{ color: 'var(--slate12)' }} data-cy="preview-chip">
Preview
</span>
<span style={{ marginLeft: '12px', cursor: 'pointer' }} onClick={props.onClick}>
<span
style={{ marginLeft: '12px', cursor: 'pointer' }}
onClick={props.onClick}
data-cy="preview-settings"
>
<Icon name="settings" height={12} width={12} fill="#889099" />
</span>
</div>
@ -108,11 +114,11 @@ const PreviewSettings = ({ isMobileLayout, showHeader, darkMode }) => {
className="released-version-no-header-mbl-preview"
style={{ backgroundColor: 'var(--slate5)', top: showHeader ? '' : '14px' }}
>
<span className="preview-chip" style={{ color: 'var(--slate12)' }}>
<span className="preview-chip" style={{ color: 'var(--slate12)' }} data-cy="preview-chip">
Preview
</span>
<OverlayTrigger rootClose trigger="click" placement="bottom" overlay={renderOverlay()}>
<span style={{ marginLeft: '12px', cursor: 'pointer' }}>
<span style={{ marginLeft: '12px', cursor: 'pointer' }} data-cy="preview-settings">
<Icon name="settings" height={12} width={12} fill="#889099" />
</span>
</OverlayTrigger>

View file

@ -95,7 +95,7 @@ export const ViewerSidebarNavigation = ({
version: selectedVersionName,
env: selectedEnvironmentName,
};
switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams), true);
switchPage(pageId, pages.find((page) => page.id === pageId)?.handle, Object.entries(queryParams));
};
const isLicensed =

View file

@ -58,6 +58,9 @@ import {
linkConfig,
iconConfig,
boundedBoxConfig,
emailinputConfig,
phoneinputConfig,
currencyinputConfig,
} from '../widgets';
export const widgets = [
@ -70,6 +73,9 @@ export const widgets = [
textinputConfig,
numberinputConfig,
passinputConfig,
emailinputConfig,
phoneinputConfig,
currencyinputConfig,
datepickerConfig,
datetimePickerV2Config,
datePickerV2Config,

View file

@ -61,7 +61,7 @@ export const buttonConfig = {
accordian: 'button',
},
backgroundColor: {
type: 'color',
type: 'colorSwatches',
displayName: 'Background',
validation: {
schema: { type: 'string' },
@ -74,7 +74,7 @@ export const buttonConfig = {
accordian: 'button',
},
textColor: {
type: 'color',
type: 'colorSwatches',
displayName: 'Text color',
validation: {
schema: { type: 'string' },
@ -83,7 +83,7 @@ export const buttonConfig = {
accordian: 'button',
},
borderColor: {
type: 'color',
type: 'colorSwatches',
displayName: 'Border color',
validation: {
schema: { type: 'string' },
@ -92,7 +92,7 @@ export const buttonConfig = {
accordian: 'button',
},
loaderColor: {
type: 'color',
type: 'colorSwatches',
displayName: 'Loader color',
validation: {
schema: { type: 'string' },
@ -110,7 +110,7 @@ export const buttonConfig = {
visibility: false,
},
iconColor: {
type: 'color',
type: 'colorSwatches',
displayName: 'Icon color',
validation: { schema: { type: 'string' } },
accordian: 'button',
@ -219,10 +219,10 @@ export const buttonConfig = {
events: [],
styles: {
textColor: { value: '#FFFFFF' },
borderColor: { value: '#4368E3' },
borderColor: { value: 'var(--primary-brand)' },
loaderColor: { value: '#FFFFFF' },
borderRadius: { value: '{{6}}' },
backgroundColor: { value: '#4368E3' },
backgroundColor: { value: 'var(--primary-brand)' },
iconColor: { value: '#FFFFFF' },
direction: { value: 'left' },
padding: { value: 'default' },

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